Merge branch 'master' into diggingInvaders
Also make edgeCost.cpp compile because I stopped midsentence for some reason. Conflicts: library/modules/Maps.cppdevelop
commit
f8261348ff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
#include "Export.h"
|
||||
#include <string>
|
||||
|
||||
namespace DFHack {
|
||||
namespace Once {
|
||||
DFHACK_EXPORT bool alreadyDone(std::string);
|
||||
DFHACK_EXPORT bool doOnce(std::string);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,591 @@
|
||||
local _ENV = mkmodule('dfhack.workshops')
|
||||
|
||||
local utils = require 'utils'
|
||||
|
||||
input_filter_defaults = {
|
||||
item_type = -1,
|
||||
item_subtype = -1,
|
||||
mat_type = -1,
|
||||
mat_index = -1,
|
||||
flags1 = {},
|
||||
-- Instead of noting those that allow artifacts, mark those that forbid them.
|
||||
-- Leaves actually enabling artifacts to the discretion of the API user,
|
||||
-- which is the right thing because unlike the game UI these filters are
|
||||
-- used in a way that does not give the user a chance to choose manually.
|
||||
flags2 = { allow_artifact = true },
|
||||
flags3 = {},
|
||||
flags4 = 0,
|
||||
flags5 = 0,
|
||||
reaction_class = '',
|
||||
has_material_reaction_product = '',
|
||||
metal_ore = -1,
|
||||
min_dimension = -1,
|
||||
has_tool_use = -1,
|
||||
quantity = 1
|
||||
}
|
||||
local fuel={item_type=df.item_type.BAR,mat_type=df.builtin_mats.COAL}
|
||||
jobs_furnace={
|
||||
[df.furnace_type.Smelter]={
|
||||
{
|
||||
name="Melt metal object",
|
||||
items={fuel,{flags2={allow_melt_dump=true}}},--also maybe melt_designated
|
||||
job_fields={job_type=df.job_type.MeltMetalObject}
|
||||
}
|
||||
},
|
||||
[df.furnace_type.MagmaSmelter]={
|
||||
{
|
||||
name="Melt metal object",
|
||||
items={{flags2={allow_melt_dump=true}}},--also maybe melt_designated
|
||||
job_fields={job_type=df.job_type.MeltMetalObject}
|
||||
}
|
||||
},
|
||||
--[[ [df.furnace_type.MetalsmithsForge]={
|
||||
unpack(concat(furnaces,mechanism,anvil,crafts,coins,flask))
|
||||
|
||||
},
|
||||
]]
|
||||
--MetalsmithsForge,
|
||||
--MagmaForge
|
||||
--[[
|
||||
forges:
|
||||
weapons and ammo-> from raws...
|
||||
armor -> raws
|
||||
furniture -> builtins?
|
||||
siege eq-> builtin (only balista head)
|
||||
trap eq -> from raws+ mechanisms
|
||||
other object-> anvil, crafts, goblets,toys,instruments,nestbox... (raws?) flask, coins,stud with iron
|
||||
metal clothing-> raws???
|
||||
]]
|
||||
[df.furnace_type.GlassFurnace]={
|
||||
{
|
||||
name="collect sand",
|
||||
items={},
|
||||
job_fields={job_type=df.job_type.CollectSand}
|
||||
},
|
||||
--glass crafts x3
|
||||
},
|
||||
[df.furnace_type.WoodFurnace]={
|
||||
defaults={item_type=df.item_type.WOOD,vector_id=df.job_item_vector_id.WOOD},
|
||||
{
|
||||
name="make charcoal",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.MakeCharcoal}
|
||||
},
|
||||
{
|
||||
name="make ash",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.MakeAsh}
|
||||
}
|
||||
},
|
||||
[df.furnace_type.Kiln]={
|
||||
{
|
||||
name="collect clay",
|
||||
items={},
|
||||
job_fields={job_type=df.job_type.CollectClay}
|
||||
}
|
||||
},
|
||||
}
|
||||
jobs_workshop={
|
||||
|
||||
[df.workshop_type.Jewelers]={
|
||||
{
|
||||
name="cut gems",
|
||||
items={{item_type=df.item_type.ROUGH,flags1={unrotten=true}}},
|
||||
job_fields={job_type=df.job_type.CutGems}
|
||||
},
|
||||
{
|
||||
name="encrust finished goods with gems",
|
||||
items={{item_type=df.item_type.SMALLGEM},{flags1={improvable=true,finished_goods=true}}},
|
||||
job_fields={job_type=df.job_type.EncrustWithGems}
|
||||
},
|
||||
{
|
||||
name="encrust ammo with gems",
|
||||
items={{item_type=df.item_type.SMALLGEM},{flags1={improvable=true,ammo=true}}},
|
||||
job_fields={job_type=df.job_type.EncrustWithGems}
|
||||
},
|
||||
{
|
||||
name="encrust furniture with gems",
|
||||
items={{item_type=df.item_type.SMALLGEM},{flags1={improvable=true,furniture=true}}},
|
||||
job_fields={job_type=df.job_type.EncrustWithGems}
|
||||
},
|
||||
},
|
||||
[df.workshop_type.Fishery]={
|
||||
{
|
||||
name="prepare raw fish",
|
||||
items={{item_type=df.item_type.FISH_RAW,flags1={unrotten=true}}},
|
||||
job_fields={job_type=df.job_type.PrepareRawFish}
|
||||
},
|
||||
{
|
||||
name="extract from raw fish",
|
||||
items={{flags1={unrotten=true,extract_bearing_fish=true}},{item_type=df.item_type.FLASK,flags1={empty=true,glass=true}}},
|
||||
job_fields={job_type=df.job_type.ExtractFromRawFish}
|
||||
},
|
||||
{
|
||||
name="catch live fish",
|
||||
items={},
|
||||
job_fields={job_type=df.job_type.CatchLiveFish}
|
||||
}, -- no items?
|
||||
},
|
||||
[df.workshop_type.Still]={
|
||||
{
|
||||
name="brew drink",
|
||||
items={{flags1={distillable=true},vector_id=22},{flags1={empty=true},flags3={food_storage=true}}},
|
||||
job_fields={job_type=df.job_type.BrewDrink}
|
||||
},
|
||||
{
|
||||
name="extract from plants",
|
||||
items={{item_type=df.item_type.PLANT,flags1={unrotten=true,extract_bearing_plant=true}},{item_type=df.item_type.FLASK,flags1={empty=true}}},
|
||||
job_fields={job_type=df.job_type.ExtractFromPlants}
|
||||
},
|
||||
--mead from raws?
|
||||
},
|
||||
[df.workshop_type.Masons]={
|
||||
defaults={item_type=df.item_type.BOULDER,item_subtype=-1,vector_id=df.job_item_vector_id.BOULDER, mat_type=0,mat_index=-1,flags3={hard=true}},--flags2={non_economic=true},
|
||||
{
|
||||
name="construct armor stand",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.ConstructArmorStand}
|
||||
},
|
||||
|
||||
{
|
||||
name="construct blocks",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.ConstructBlocks}
|
||||
},
|
||||
{
|
||||
name="construct throne",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.ConstructThrone}
|
||||
},
|
||||
{
|
||||
name="construct coffin",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.ConstructCoffin}
|
||||
},
|
||||
{
|
||||
name="construct door",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.ConstructDoor}
|
||||
},
|
||||
{
|
||||
name="construct floodgate",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.ConstructFloodgate}
|
||||
},
|
||||
{
|
||||
name="construct hatch cover",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.ConstructHatchCover}
|
||||
},
|
||||
{
|
||||
name="construct grate",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.ConstructGrate}
|
||||
},
|
||||
{
|
||||
name="construct cabinet",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.ConstructCabinet}
|
||||
},
|
||||
{
|
||||
name="construct chest",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.ConstructChest}
|
||||
},
|
||||
{
|
||||
name="construct statue",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.ConstructStatue}
|
||||
},
|
||||
{
|
||||
name="construct slab",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.ConstructSlab}
|
||||
},
|
||||
{
|
||||
name="construct table",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.ConstructTable}
|
||||
},
|
||||
{
|
||||
name="construct weapon rack",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.ConstructWeaponRack}
|
||||
},
|
||||
{
|
||||
name="construct quern",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.ConstructQuern}
|
||||
},
|
||||
{
|
||||
name="construct millstone",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.ConstructMillstone}
|
||||
},
|
||||
},
|
||||
[df.workshop_type.Carpenters]={
|
||||
--training weapons, wooden shields
|
||||
defaults={item_type=df.item_type.WOOD,vector_id=df.job_item_vector_id.WOOD},
|
||||
|
||||
{
|
||||
name="make barrel",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.MakeBarrel}
|
||||
},
|
||||
|
||||
{
|
||||
name="make bucket",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.MakeBucket}
|
||||
},
|
||||
{
|
||||
name="make animal trap",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.MakeAnimalTrap}
|
||||
},
|
||||
{
|
||||
name="make cage",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.MakeCage}
|
||||
},
|
||||
{
|
||||
name="construct bed",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.ConstructBed}
|
||||
},
|
||||
{
|
||||
name="construct bin",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.ConstructBin}
|
||||
},
|
||||
{
|
||||
name="construct armor stand",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.ConstructArmorStand}
|
||||
},
|
||||
{
|
||||
name="construct blocks",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.ConstructBlocks}
|
||||
},
|
||||
{
|
||||
name="construct throne",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.ConstructThrone}
|
||||
},
|
||||
{
|
||||
name="construct coffin",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.ConstructCoffin}
|
||||
},
|
||||
{
|
||||
name="construct door",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.ConstructDoor}
|
||||
},
|
||||
{
|
||||
name="construct floodgate",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.ConstructFloodgate}
|
||||
},
|
||||
{
|
||||
name="construct hatch cover",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.ConstructHatchCover}
|
||||
},
|
||||
{
|
||||
name="construct grate",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.ConstructGrate}
|
||||
},
|
||||
{
|
||||
name="construct cabinet",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.ConstructCabinet}
|
||||
},
|
||||
{
|
||||
name="construct chest",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.ConstructChest}
|
||||
},
|
||||
{
|
||||
name="construct statue",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.ConstructStatue}
|
||||
},
|
||||
{
|
||||
name="construct table",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.ConstructTable}
|
||||
},
|
||||
{
|
||||
name="construct weapon rack",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.ConstructWeaponRack}
|
||||
},
|
||||
{
|
||||
name="construct splint",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.ConstructSplint}
|
||||
},
|
||||
{
|
||||
name="construct crutch",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.ConstructCrutch}
|
||||
},
|
||||
},
|
||||
[df.workshop_type.Kitchen]={
|
||||
--mat_type=2,3,4
|
||||
defaults={flags1={unrotten=true,cookable=true}},
|
||||
{
|
||||
name="prepare easy meal",
|
||||
items={{flags1={solid=true}},{}},
|
||||
job_fields={job_type=df.job_type.PrepareMeal,mat_type=2}
|
||||
},
|
||||
{
|
||||
name="prepare fine meal",
|
||||
items={{flags1={solid=true}},{},{}},
|
||||
job_fields={job_type=df.job_type.PrepareMeal,mat_type=3}
|
||||
},
|
||||
{
|
||||
name="prepare lavish meal",
|
||||
items={{flags1={solid=true}},{},{},{}},
|
||||
job_fields={job_type=df.job_type.PrepareMeal,mat_type=4}
|
||||
},
|
||||
},
|
||||
[df.workshop_type.Butchers]={
|
||||
{
|
||||
name="butcher an animal",
|
||||
items={{flags1={butcherable=true,unrotten=true,nearby=true}}},
|
||||
job_fields={job_type=df.job_type.ButcherAnimal}
|
||||
},
|
||||
{
|
||||
name="extract from land animal",
|
||||
items={{flags1={extract_bearing_vermin=true,unrotten=true}},{item_type=df.item_type.FLASK,flags1={empty=true,glass=true}}},
|
||||
job_fields={job_type=df.job_type.ExtractFromLandAnimal}
|
||||
},
|
||||
{
|
||||
name="catch live land animal",
|
||||
items={},
|
||||
job_fields={job_type=df.job_type.CatchLiveLandAnimal}
|
||||
},
|
||||
},
|
||||
[df.workshop_type.Mechanics]={
|
||||
{
|
||||
name="construct mechanisms",
|
||||
items={{item_type=df.item_type.BOULDER,item_subtype=-1,vector_id=df.job_item_vector_id.BOULDER, mat_type=0,mat_index=-1,quantity=1,
|
||||
flags3={hard=true}}},
|
||||
job_fields={job_type=df.job_type.ConstructMechanisms}
|
||||
},
|
||||
{
|
||||
name="construct traction bench",
|
||||
items={{item_type=df.item_type.TABLE},{item_type=df.item_type.MECHANISM},{item_type=df.item_type.CHAIN}},
|
||||
job_fields={job_type=df.job_type.ConstructTractionBench}
|
||||
},
|
||||
},
|
||||
[df.workshop_type.Loom]={
|
||||
{
|
||||
name="weave plant thread cloth",
|
||||
items={{item_type=df.item_type.THREAD,quantity=15000,min_dimension=15000,flags1={collected=true},flags2={plant=true}}},
|
||||
job_fields={job_type=df.job_type.WeaveCloth}
|
||||
},
|
||||
{
|
||||
name="weave silk thread cloth",
|
||||
items={{item_type=df.item_type.THREAD,quantity=15000,min_dimension=15000,flags1={collected=true},flags2={silk=true}}},
|
||||
job_fields={job_type=df.job_type.WeaveCloth}
|
||||
},
|
||||
{
|
||||
name="weave yarn cloth",
|
||||
items={{item_type=df.item_type.THREAD,quantity=15000,min_dimension=15000,flags1={collected=true},flags2={yarn=true}}},
|
||||
job_fields={job_type=df.job_type.WeaveCloth}
|
||||
},
|
||||
{
|
||||
name="weave inorganic cloth",
|
||||
items={{item_type=df.item_type.THREAD,quantity=15000,min_dimension=15000,flags1={collected=true},mat_type=0}},
|
||||
job_fields={job_type=df.job_type.WeaveCloth}
|
||||
},
|
||||
{
|
||||
name="collect webs",
|
||||
items={{item_type=df.item_type.THREAD,quantity=10,min_dimension=10,flags1={undisturbed=true}}},
|
||||
job_fields={job_type=df.job_type.CollectWebs}
|
||||
},
|
||||
},
|
||||
[df.workshop_type.Leatherworks]={
|
||||
defaults={item_type=SKIN_TANNED},
|
||||
{
|
||||
name="construct leather bag",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.ConstructChest}
|
||||
},
|
||||
{
|
||||
name="construct waterskin",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.MakeFlask}
|
||||
},
|
||||
{
|
||||
name="construct backpack",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.MakeBackpack}
|
||||
},
|
||||
{
|
||||
name="construct quiver",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.MakeQuiver}
|
||||
},
|
||||
{
|
||||
name="sew leather image",
|
||||
items={{item_type=-1,flags1={empty=true},flags2={sewn_imageless=true}},{}},
|
||||
job_fields={job_type=df.job_type.SewImage}
|
||||
},
|
||||
},
|
||||
[df.workshop_type.Dyers]={
|
||||
{
|
||||
name="dye thread",
|
||||
items={{item_type=df.item_type.THREAD,quantity=15000,min_dimension=15000,flags1={collected=true},flags2={dyeable=true}},
|
||||
{flags1={unrotten=true},flags2={dye=true}}},
|
||||
job_fields={job_type=df.job_type.DyeThread}
|
||||
},
|
||||
{
|
||||
name="dye cloth",
|
||||
items={{item_type=df.item_type.CLOTH,quantity=10000,min_dimension=10000,flags2={dyeable=true}},
|
||||
{flags1={unrotten=true},flags2={dye=true}}},
|
||||
job_fields={job_type=df.job_type.DyeThread}
|
||||
},
|
||||
},
|
||||
[df.workshop_type.Siege]={
|
||||
{
|
||||
name="construct balista parts",
|
||||
items={{item_type=df.item_type.WOOD}},
|
||||
job_fields={job_type=df.job_type.ConstructBallistaParts}
|
||||
},
|
||||
{
|
||||
name="construct catapult parts",
|
||||
items={{item_type=df.item_type.WOOD}},
|
||||
job_fields={job_type=df.job_type.ConstructCatapultParts}
|
||||
},
|
||||
{
|
||||
name="assemble balista arrow",
|
||||
items={{item_type=df.item_type.WOOD}},
|
||||
job_fields={job_type=df.job_type.AssembleSiegeAmmo}
|
||||
},
|
||||
{
|
||||
name="assemble tipped balista arrow",
|
||||
items={{item_type=df.item_type.WOOD},{item_type=df.item_type.BALLISTAARROWHEAD}},
|
||||
job_fields={job_type=df.job_type.AssembleSiegeAmmo}
|
||||
},
|
||||
},
|
||||
}
|
||||
local function matchIds(bid1,wid1,cid1,bid2,wid2,cid2)
|
||||
if bid1~=-1 and bid2~=-1 and bid1~=bid2 then
|
||||
return false
|
||||
end
|
||||
if wid1~=-1 and wid2~=-1 and wid1~=wid2 then
|
||||
return false
|
||||
end
|
||||
if cid1~=-1 and cid2~=-1 and cid1~=cid2 then
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
local function scanRawsReaction(buildingId,workshopId,customId)
|
||||
local ret={}
|
||||
for idx,reaction in ipairs(df.global.world.raws.reactions) do
|
||||
for k,v in pairs(reaction.building.type) do
|
||||
if matchIds(buildingId,workshopId,customId,v,reaction.building.subtype[k],reaction.building.custom[k]) then
|
||||
table.insert(ret,reaction)
|
||||
end
|
||||
end
|
||||
end
|
||||
return ret
|
||||
end
|
||||
local function reagentToJobItem(reagent,react_id,reagentId)
|
||||
local ret_item
|
||||
ret_item=utils.clone_with_default(reagent, input_filter_defaults)
|
||||
ret_item.reaction_id=react_id
|
||||
ret_item.reagent_index=reagentId
|
||||
return ret_item
|
||||
end
|
||||
local function addReactionJobs(ret,bid,wid,cid)
|
||||
local reactions=scanRawsReaction(bid,wid or -1,cid or -1)
|
||||
for idx,react in pairs(reactions) do
|
||||
local job={name=react.name,
|
||||
items={},job_fields={job_type=df.job_type.CustomReaction,reaction_name=react.code}
|
||||
}
|
||||
for reagentId,reagent in pairs(react.reagents) do
|
||||
table.insert(job.items,reagentToJobItem(reagent,idx,reagentId))
|
||||
end
|
||||
if react.flags.FUEL then
|
||||
table.insert(job.items,fuel)
|
||||
end
|
||||
table.insert(ret,job)
|
||||
end
|
||||
end
|
||||
local function scanRawsOres()
|
||||
local ret={}
|
||||
for idx,ore in ipairs(df.global.world.raws.inorganics) do
|
||||
if #ore.metal_ore.mat_index~=0 then
|
||||
ret[idx]=ore
|
||||
end
|
||||
end
|
||||
return ret
|
||||
end
|
||||
local function addSmeltJobs(ret,use_fuel)
|
||||
local ores=scanRawsOres()
|
||||
for idx,ore in pairs(ores) do
|
||||
print("adding:",ore.material.state_name.Solid)
|
||||
printall(ore)
|
||||
local job={name="smelt "..ore.material.state_name.Solid,job_fields={job_type=df.job_type.SmeltOre,mat_type=df.builtin_mats.INORGANIC,mat_index=idx},items={
|
||||
{item_type=df.item_type.BOULDER,mat_type=df.builtin_mats.INORGANIC,mat_index=idx,vector_id=df.job_item_vector_id.BOULDER}}}
|
||||
if use_fuel then
|
||||
table.insert(job.items,fuel)
|
||||
end
|
||||
table.insert(ret,job)
|
||||
end
|
||||
return ret
|
||||
end
|
||||
function getJobs(buildingId,workshopId,customId)
|
||||
local ret={}
|
||||
local c_jobs
|
||||
if buildingId==df.building_type.Workshop then
|
||||
c_jobs=jobs_workshop[workshopId]
|
||||
elseif buildingId==df.building_type.Furnace then
|
||||
c_jobs=jobs_furnace[workshopId]
|
||||
|
||||
if workshopId == df.furnace_type.Smelter or workshopId == df.furnace_type.MagmaSmelter then
|
||||
c_jobs=utils.clone(c_jobs,true)
|
||||
addSmeltJobs(c_jobs,workshopId == df.furnace_type.Smelter)
|
||||
end
|
||||
else
|
||||
return nil
|
||||
end
|
||||
if c_jobs==nil then
|
||||
c_jobs={}
|
||||
else
|
||||
c_jobs=utils.clone(c_jobs,true)
|
||||
end
|
||||
|
||||
addReactionJobs(c_jobs,buildingId,workshopId,customId)
|
||||
for jobId,contents in pairs(c_jobs) do
|
||||
if jobId~="defaults" then
|
||||
local entry={}
|
||||
entry.name=contents.name
|
||||
local lclDefaults=utils.clone(input_filter_defaults,true)
|
||||
if c_jobs.defaults ~=nil then
|
||||
utils.assign(lclDefaults,c_jobs.defaults)
|
||||
end
|
||||
entry.items={}
|
||||
for k,item in pairs(contents.items) do
|
||||
entry.items[k]=utils.clone(lclDefaults,true)
|
||||
utils.assign(entry.items[k],item)
|
||||
end
|
||||
if contents.job_fields~=nil then
|
||||
entry.job_fields={}
|
||||
utils.assign(entry.job_fields,contents.job_fields)
|
||||
end
|
||||
ret[jobId]=entry
|
||||
end
|
||||
end
|
||||
--get jobs, add in from raws
|
||||
return ret
|
||||
end
|
||||
return _ENV
|
@ -0,0 +1,285 @@
|
||||
-- Stock dialog for selecting buildings
|
||||
|
||||
local _ENV = mkmodule('gui.buildings')
|
||||
|
||||
local gui = require('gui')
|
||||
local widgets = require('gui.widgets')
|
||||
local dlg = require('gui.dialogs')
|
||||
local utils = require('utils')
|
||||
|
||||
ARROW = string.char(26)
|
||||
|
||||
WORKSHOP_ABSTRACT={
|
||||
[df.building_type.Civzone]=true,[df.building_type.Stockpile]=true,
|
||||
}
|
||||
WORKSHOP_SPECIAL={
|
||||
[df.building_type.Workshop]=true,[df.building_type.Furnace]=true,[df.building_type.Trap]=true,
|
||||
[df.building_type.Construction]=true,[df.building_type.SiegeEngine]=true
|
||||
}
|
||||
BuildingDialog = defclass(BuildingDialog, gui.FramedScreen)
|
||||
|
||||
BuildingDialog.focus_path = 'BuildingDialog'
|
||||
|
||||
BuildingDialog.ATTRS{
|
||||
prompt = 'Type or select a building from this list',
|
||||
frame_style = gui.GREY_LINE_FRAME,
|
||||
frame_inset = 1,
|
||||
frame_title = 'Select Building',
|
||||
-- new attrs
|
||||
none_caption = 'none',
|
||||
hide_none = false,
|
||||
use_abstract = true,
|
||||
use_workshops = true,
|
||||
use_tool_workshop=true,
|
||||
use_furnace = true,
|
||||
use_construction = true,
|
||||
use_siege = true,
|
||||
use_trap = true,
|
||||
use_custom = true,
|
||||
building_filter = DEFAULT_NIL,
|
||||
on_select = DEFAULT_NIL,
|
||||
on_cancel = DEFAULT_NIL,
|
||||
on_close = DEFAULT_NIL,
|
||||
}
|
||||
|
||||
function BuildingDialog:init(info)
|
||||
self:addviews{
|
||||
widgets.Label{
|
||||
text = {
|
||||
self.prompt, '\n\n',
|
||||
'Category: ', { text = self:cb_getfield('context_str'), pen = COLOR_CYAN }
|
||||
},
|
||||
text_pen = COLOR_WHITE,
|
||||
frame = { l = 0, t = 0 },
|
||||
},
|
||||
widgets.Label{
|
||||
view_id = 'back',
|
||||
visible = false,
|
||||
text = { { key = 'LEAVESCREEN', text = ': Back' } },
|
||||
frame = { r = 0, b = 0 },
|
||||
auto_width = true,
|
||||
},
|
||||
widgets.FilteredList{
|
||||
view_id = 'list',
|
||||
not_found_label = 'No matching buildings',
|
||||
frame = { l = 0, r = 0, t = 4, b = 2 },
|
||||
icon_width = 2,
|
||||
on_submit = self:callback('onSubmitItem'),
|
||||
},
|
||||
widgets.Label{
|
||||
text = { {
|
||||
key = 'SELECT', text = ': Select',
|
||||
disabled = function() return not self.subviews.list:canSubmit() end
|
||||
} },
|
||||
frame = { l = 0, b = 0 },
|
||||
}
|
||||
}
|
||||
self:initBuiltinMode()
|
||||
end
|
||||
|
||||
function BuildingDialog:getWantedFrameSize(rect)
|
||||
return math.max(self.frame_width or 40, #self.prompt), math.min(28, rect.height-8)
|
||||
end
|
||||
|
||||
function BuildingDialog:onDestroy()
|
||||
if self.on_close then
|
||||
self.on_close()
|
||||
end
|
||||
end
|
||||
|
||||
function BuildingDialog:initBuiltinMode()
|
||||
local choices = {}
|
||||
if not self.hide_none then
|
||||
table.insert(choices, { text = self.none_caption, type_id = -1, subtype_id = -1, custom_id=-1})
|
||||
end
|
||||
|
||||
if self.use_workshops then
|
||||
table.insert(choices, {
|
||||
icon = ARROW, text = 'workshop', key = 'CUSTOM_SHIFT_W',
|
||||
cb = self:callback('initWorkshopMode')
|
||||
})
|
||||
end
|
||||
if self.use_furnace then
|
||||
table.insert(choices, {
|
||||
icon = ARROW, text = 'furnaces', key = 'CUSTOM_SHIFT_F',
|
||||
cb = self:callback('initFurnaceMode')
|
||||
})
|
||||
end
|
||||
if self.use_trap then
|
||||
table.insert(choices, {
|
||||
icon = ARROW, text = 'traps', key = 'CUSTOM_SHIFT_T',
|
||||
cb = self:callback('initTrapMode')
|
||||
})
|
||||
end
|
||||
if self.use_construction then
|
||||
table.insert(choices, {
|
||||
icon = ARROW, text = 'constructions', key = 'CUSTOM_SHIFT_C',
|
||||
cb = self:callback('initConstructionMode')
|
||||
})
|
||||
end
|
||||
if self.use_siege then
|
||||
table.insert(choices, {
|
||||
icon = ARROW, text = 'siege engine', key = 'CUSTOM_SHIFT_S',
|
||||
cb = self:callback('initSiegeMode')
|
||||
})
|
||||
end
|
||||
if self.use_custom then
|
||||
table.insert(choices, {
|
||||
icon = ARROW, text = 'custom workshop', key = 'CUSTOM_SHIFT_U',
|
||||
cb = self:callback('initCustomMode')
|
||||
})
|
||||
end
|
||||
|
||||
|
||||
|
||||
for i=0,df.building_type._last_item do
|
||||
if (not WORKSHOP_ABSTRACT[i] or self.use_abstract)and not WORKSHOP_SPECIAL[i] then
|
||||
self:addBuilding(choices, df.building_type[i], i, -1,-1,nil)
|
||||
end
|
||||
end
|
||||
|
||||
self:pushContext('Any building', choices)
|
||||
end
|
||||
|
||||
function BuildingDialog:initWorkshopMode()
|
||||
local choices = {}
|
||||
|
||||
for i=0,df.workshop_type._last_item do
|
||||
if i~=df.workshop_type.Custom and (i~=df.workshop_type.Tool or self.use_tool_workshop) then
|
||||
self:addBuilding(choices, df.workshop_type[i], df.building_type.Workshop, i,-1,nil)
|
||||
end
|
||||
end
|
||||
|
||||
self:pushContext('Workshops', choices)
|
||||
end
|
||||
function BuildingDialog:initTrapMode()
|
||||
local choices = {}
|
||||
|
||||
for i=0,df.trap_type._last_item do
|
||||
self:addBuilding(choices, df.trap_type[i], df.building_type.Trap, i,-1,nil)
|
||||
end
|
||||
|
||||
self:pushContext('Traps', choices)
|
||||
end
|
||||
|
||||
function BuildingDialog:initConstructionMode()
|
||||
local choices = {}
|
||||
|
||||
for i=0,df.construction_type._last_item do
|
||||
self:addBuilding(choices, df.construction_type[i], df.building_type.Construction, i,-1,nil)
|
||||
end
|
||||
|
||||
self:pushContext('Constructions', choices)
|
||||
end
|
||||
|
||||
function BuildingDialog:initFurnaceMode()
|
||||
local choices = {}
|
||||
|
||||
for i=0,df.furnace_type._last_item do
|
||||
self:addBuilding(choices, df.furnace_type[i], df.building_type.Furnace, i,-1,nil)
|
||||
end
|
||||
|
||||
self:pushContext('Furnaces', choices)
|
||||
end
|
||||
|
||||
function BuildingDialog:initSiegeMode()
|
||||
local choices = {}
|
||||
|
||||
for i=0,df.siegeengine_type._last_item do
|
||||
self:addBuilding(choices, df.siegeengine_type[i], df.building_type.SiegeEngine, i,-1,nil)
|
||||
end
|
||||
|
||||
self:pushContext('Siege weapons', choices)
|
||||
end
|
||||
function BuildingDialog:initCustomMode()
|
||||
local choices = {}
|
||||
local raws=df.global.world.raws.buildings.all
|
||||
for k,v in pairs(raws) do
|
||||
self:addBuilding(choices, v.name, df.building_type.Workshop,df.workshop_type.Custom,v.id,v)
|
||||
end
|
||||
|
||||
self:pushContext('Custom workshops', choices)
|
||||
end
|
||||
|
||||
function BuildingDialog:addBuilding(choices, name,type_id, subtype_id, custom_id, parent)
|
||||
-- Check the filter
|
||||
if self.building_filter and not self.building_filter(name,type_id,subtype_id,custom_id, parent) then
|
||||
return
|
||||
end
|
||||
|
||||
table.insert(choices, {
|
||||
text = name:lower(),
|
||||
customshop = parent,
|
||||
type_id = type_id, subtype_id = subtype_id, custom_id=custom_id
|
||||
})
|
||||
end
|
||||
|
||||
function BuildingDialog:pushContext(name, choices)
|
||||
if not self.back_stack then
|
||||
self.back_stack = {}
|
||||
self.subviews.back.visible = false
|
||||
else
|
||||
table.insert(self.back_stack, {
|
||||
context_str = self.context_str,
|
||||
all_choices = self.subviews.list:getChoices(),
|
||||
edit_text = self.subviews.list:getFilter(),
|
||||
selected = self.subviews.list:getSelected(),
|
||||
})
|
||||
self.subviews.back.visible = true
|
||||
end
|
||||
|
||||
self.context_str = name
|
||||
self.subviews.list:setChoices(choices, 1)
|
||||
end
|
||||
|
||||
function BuildingDialog:onGoBack()
|
||||
local save = table.remove(self.back_stack)
|
||||
self.subviews.back.visible = (#self.back_stack > 0)
|
||||
|
||||
self.context_str = save.context_str
|
||||
self.subviews.list:setChoices(save.all_choices)
|
||||
self.subviews.list:setFilter(save.edit_text, save.selected)
|
||||
end
|
||||
|
||||
function BuildingDialog:submitBuilding(type_id,subtype_id,custom_id,choice,index)
|
||||
self:dismiss()
|
||||
|
||||
if self.on_select then
|
||||
self.on_select(type_id,subtype_id,custom_id,choice,index)
|
||||
end
|
||||
end
|
||||
|
||||
function BuildingDialog:onSubmitItem(idx, item)
|
||||
if item.cb then
|
||||
item:cb(idx)
|
||||
else
|
||||
self:submitBuilding(item.type_id, item.subtype_id,item.custom_id,item,idx)
|
||||
end
|
||||
end
|
||||
|
||||
function BuildingDialog:onInput(keys)
|
||||
if keys.LEAVESCREEN or keys.LEAVESCREEN_ALL then
|
||||
if self.subviews.back.visible and not keys.LEAVESCREEN_ALL then
|
||||
self:onGoBack()
|
||||
else
|
||||
self:dismiss()
|
||||
if self.on_cancel then
|
||||
self.on_cancel()
|
||||
end
|
||||
end
|
||||
else
|
||||
self:inputToSubviews(keys)
|
||||
end
|
||||
end
|
||||
|
||||
function showBuildingPrompt(title, prompt, on_select, on_cancel, build_filter)
|
||||
BuildingDialog{
|
||||
frame_title = title,
|
||||
prompt = prompt,
|
||||
building_filter = build_filter,
|
||||
on_select = on_select,
|
||||
on_cancel = on_cancel,
|
||||
}:show()
|
||||
end
|
||||
|
||||
return _ENV
|
@ -0,0 +1,16 @@
|
||||
|
||||
#include "modules/Once.h"
|
||||
#include <unordered_set>
|
||||
|
||||
using namespace std;
|
||||
|
||||
static unordered_set<string> thingsDone;
|
||||
|
||||
bool DFHack::Once::alreadyDone(string bob) {
|
||||
return thingsDone.find(bob) != thingsDone.end();
|
||||
}
|
||||
|
||||
bool DFHack::Once::doOnce(string bob) {
|
||||
return thingsDone.insert(bob).second;
|
||||
}
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit fbf671a7d5aacb41cb44059eb16a1ee9cad419be
|
||||
Subproject commit 20ecaa0393df1ea111861d67c789aaaa56a37c58
|
@ -1,7 +1,7 @@
|
||||
#!/bin/sh
|
||||
DF_DIR=$(dirname "$0")
|
||||
cd "${DF_DIR}"
|
||||
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:"./stonesense/deplibs":"./hack"
|
||||
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:"./hack/libs":"./hack"
|
||||
export SDL_DISABLE_LOCK_KEYS=1 # Work around for bug in Debian/Ubuntu SDL patch.
|
||||
#export SDL_VIDEO_CENTERED=1 # Centre the screen. Messes up resizing.
|
||||
./libs/Dwarf_Fortress $* # Go, go, go! :)
|
||||
|
@ -0,0 +1,8 @@
|
||||
#!/bin/sh
|
||||
|
||||
DF_DIR=$(dirname "$0")
|
||||
cd "${DF_DIR}"
|
||||
|
||||
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:"./hack/libs":"./hack"
|
||||
|
||||
./isoworld/isoworld "$@"
|
@ -0,0 +1,481 @@
|
||||
#include "Core.h"
|
||||
#include "DataDefs.h"
|
||||
#include "Export.h"
|
||||
#include "PluginManager.h"
|
||||
|
||||
#include "modules/EventManager.h"
|
||||
#include "modules/Job.h"
|
||||
#include "modules/Maps.h"
|
||||
#include "modules/Once.h"
|
||||
#include "modules/World.h"
|
||||
|
||||
#include "df/building.h"
|
||||
#include "df/caste_raw.h"
|
||||
#include "df/creature_interaction_effect.h"
|
||||
#include "df/creature_raw.h"
|
||||
#include "df/general_ref.h"
|
||||
#include "df/general_ref_building_holderst.h"
|
||||
#include "df/general_ref_type.h"
|
||||
#include "df/general_ref_unit_workerst.h"
|
||||
#include "df/global_objects.h"
|
||||
#include "df/item.h"
|
||||
#include "df/item_boulderst.h"
|
||||
#include "df/job.h"
|
||||
#include "df/job_type.h"
|
||||
#include "df/reaction.h"
|
||||
#include "df/reaction_product.h"
|
||||
#include "df/reaction_product_type.h"
|
||||
#include "df/reaction_product_itemst.h"
|
||||
#include "df/syndrome.h"
|
||||
#include "df/unit_syndrome.h"
|
||||
#include "df/ui.h"
|
||||
#include "df/unit.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <unordered_set>
|
||||
#include <unordered_map>
|
||||
|
||||
using namespace std;
|
||||
using namespace DFHack;
|
||||
|
||||
static bool enabled = false;
|
||||
|
||||
namespace ResetPolicy {
|
||||
typedef enum {DoNothing, ResetDuration, AddDuration, NewInstance} ResetPolicy;
|
||||
}
|
||||
|
||||
DFHACK_PLUGIN("autoSyndrome");
|
||||
|
||||
command_result autoSyndrome(color_ostream& out, vector<string>& parameters);
|
||||
void processJob(color_ostream& out, void* jobPtr);
|
||||
int32_t giveSyndrome(color_ostream& out, int32_t workerId, df::syndrome* syndrome, ResetPolicy::ResetPolicy policy);
|
||||
|
||||
DFhackCExport command_result plugin_init(color_ostream& out, vector<PluginCommand> &commands) {
|
||||
commands.push_back(PluginCommand("autoSyndrome", "Automatically give units syndromes when they complete jobs, as configured in the raw files.\n", &autoSyndrome, false,
|
||||
"autoSyndrome:\n"
|
||||
" autoSyndrome 0 //disable\n"
|
||||
" autoSyndrome 1 //enable\n"
|
||||
" autoSyndrome disable //disable\n"
|
||||
" autoSyndrome enable //enable\n"
|
||||
"\n"
|
||||
"autoSyndrome looks for recently completed jobs matching certain conditions, and if it finds one, then it will give the unit that finished that job the syndrome specified in the raw files. See Readme.rst for full details.\n"
|
||||
));
|
||||
|
||||
//EventManager::EventHandler handle(processJob, 5);
|
||||
//EventManager::registerListener(EventManager::EventType::JOB_COMPLETED, handle, plugin_self);
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
DFhackCExport command_result plugin_shutdown(color_ostream& out) {
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
/*DFhackCExport command_result plugin_onstatechange(color_ostream& out, state_change_event e) {
|
||||
return CR_OK;
|
||||
}*/
|
||||
|
||||
command_result autoSyndrome(color_ostream& out, vector<string>& parameters) {
|
||||
if ( parameters.size() > 1 )
|
||||
return CR_WRONG_USAGE;
|
||||
|
||||
bool wasEnabled = enabled;
|
||||
if ( parameters.size() == 1 ) {
|
||||
if ( parameters[0] == "enable" ) {
|
||||
enabled = true;
|
||||
} else if ( parameters[0] == "disable" ) {
|
||||
enabled = false;
|
||||
} else {
|
||||
int32_t a = atoi(parameters[0].c_str());
|
||||
if ( a < 0 || a > 1 )
|
||||
return CR_WRONG_USAGE;
|
||||
|
||||
enabled = (bool)a;
|
||||
}
|
||||
}
|
||||
|
||||
out.print("autoSyndrome is %s\n", enabled ? "enabled" : "disabled");
|
||||
if ( enabled == wasEnabled )
|
||||
return CR_OK;
|
||||
|
||||
EventManager::unregisterAll(plugin_self);
|
||||
if ( enabled ) {
|
||||
EventManager::EventHandler handle(processJob, 0);
|
||||
EventManager::registerListener(EventManager::EventType::JOB_COMPLETED, handle, plugin_self);
|
||||
}
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
bool maybeApply(color_ostream& out, df::syndrome* syndrome, int32_t workerId, df::unit* unit, ResetPolicy::ResetPolicy policy) {
|
||||
df::creature_raw* creature = df::global::world->raws.creatures.all[unit->race];
|
||||
df::caste_raw* caste = creature->caste[unit->caste];
|
||||
std::string& creature_name = creature->creature_id;
|
||||
std::string& creature_caste = caste->caste_id;
|
||||
//check that the syndrome applies to that guy
|
||||
/*
|
||||
* If there is no affected class or affected creature, then anybody who isn't immune is fair game.
|
||||
*
|
||||
* Otherwise, it works like this:
|
||||
* add all the affected class creatures
|
||||
* add all the affected creatures
|
||||
* remove all the immune class creatures
|
||||
* remove all the immune creatures
|
||||
* you're affected if and only if you're in the remaining list after all of that
|
||||
**/
|
||||
bool applies = syndrome->syn_affected_class.size() == 0 && syndrome->syn_affected_creature.size() == 0;
|
||||
for ( size_t c = 0; c < syndrome->syn_affected_class.size(); c++ ) {
|
||||
if ( applies )
|
||||
break;
|
||||
for ( size_t d = 0; d < caste->creature_class.size(); d++ ) {
|
||||
if ( *syndrome->syn_affected_class[c] == *caste->creature_class[d] ) {
|
||||
applies = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
for ( size_t c = 0; c < syndrome->syn_immune_class.size(); c++ ) {
|
||||
if ( !applies )
|
||||
break;
|
||||
for ( size_t d = 0; d < caste->creature_class.size(); d++ ) {
|
||||
if ( *syndrome->syn_immune_class[c] == *caste->creature_class[d] ) {
|
||||
applies = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( syndrome->syn_affected_creature.size() != syndrome->syn_affected_caste.size() ) {
|
||||
if ( DFHack::Once::doOnce("autoSyndrome: different affected creature/caste sizes.") ) {
|
||||
out.print("%s, line %d: different affected creature/caste sizes.\n", __FILE__, __LINE__);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
for ( size_t c = 0; c < syndrome->syn_affected_creature.size(); c++ ) {
|
||||
if ( creature_name != *syndrome->syn_affected_creature[c] )
|
||||
continue;
|
||||
if ( *syndrome->syn_affected_caste[c] == "ALL" ||
|
||||
*syndrome->syn_affected_caste[c] == creature_caste ) {
|
||||
applies = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
for ( size_t c = 0; c < syndrome->syn_immune_creature.size(); c++ ) {
|
||||
if ( creature_name != *syndrome->syn_immune_creature[c] )
|
||||
continue;
|
||||
if ( *syndrome->syn_immune_caste[c] == "ALL" ||
|
||||
*syndrome->syn_immune_caste[c] == creature_caste ) {
|
||||
applies = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if ( !applies ) {
|
||||
return false;
|
||||
}
|
||||
if ( giveSyndrome(out, workerId, syndrome, policy) < 0 )
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void processJob(color_ostream& out, void* jobPtr) {
|
||||
CoreSuspender suspender;
|
||||
df::job* job = (df::job*)jobPtr;
|
||||
if ( job == NULL ) {
|
||||
if ( DFHack::Once::doOnce("autoSyndrome_processJob_null job") )
|
||||
out.print("Error %s line %d: null job.\n", __FILE__, __LINE__);
|
||||
return;
|
||||
}
|
||||
if ( job->completion_timer > 0 )
|
||||
return;
|
||||
|
||||
if ( job->job_type != df::job_type::CustomReaction )
|
||||
return;
|
||||
|
||||
df::reaction* reaction = NULL;
|
||||
for ( size_t a = 0; a < df::global::world->raws.reactions.size(); a++ ) {
|
||||
df::reaction* candidate = df::global::world->raws.reactions[a];
|
||||
if ( candidate->code != job->reaction_name )
|
||||
continue;
|
||||
reaction = candidate;
|
||||
break;
|
||||
}
|
||||
if ( reaction == NULL ) {
|
||||
if ( DFHack::Once::doOnce("autoSyndrome processJob couldNotFind") )
|
||||
out.print("%s, line %d: could not find reaction \"%s\".\n", __FILE__, __LINE__, job->reaction_name.c_str() );
|
||||
return;
|
||||
}
|
||||
|
||||
int32_t workerId = -1;
|
||||
for ( size_t a = 0; a < job->general_refs.size(); a++ ) {
|
||||
if ( job->general_refs[a]->getType() != df::enums::general_ref_type::UNIT_WORKER )
|
||||
continue;
|
||||
if ( workerId != -1 ) {
|
||||
if ( DFHack::Once::doOnce("autoSyndrome processJob two workers same job") )
|
||||
out.print("%s, line %d: Found two workers on the same job.\n", __FILE__, __LINE__);
|
||||
}
|
||||
workerId = ((df::general_ref_unit_workerst*)job->general_refs[a])->unit_id;
|
||||
if (workerId == -1) {
|
||||
if ( DFHack::Once::doOnce("autoSyndrome processJob invalid worker") )
|
||||
out.print("%s, line %d: invalid worker.\n", __FILE__, __LINE__);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
df::unit* worker = df::unit::find(workerId);
|
||||
if ( worker == NULL ) {
|
||||
//out.print("%s, line %d: invalid worker.\n", __FILE__, __LINE__);
|
||||
//this probably means that it finished before EventManager could get a copy of the job while the job was running
|
||||
//TODO: consider printing a warning once
|
||||
return;
|
||||
}
|
||||
|
||||
//find the building that made it
|
||||
int32_t buildingId = -1;
|
||||
for ( size_t a = 0; a < job->general_refs.size(); a++ ) {
|
||||
if ( job->general_refs[a]->getType() != df::enums::general_ref_type::BUILDING_HOLDER )
|
||||
continue;
|
||||
if ( buildingId != -1 ) {
|
||||
if ( DFHack::Once::doOnce("autoSyndrome processJob two buildings same job") )
|
||||
out.print("%s, line %d: Found two buildings for the same job.\n", __FILE__, __LINE__);
|
||||
}
|
||||
buildingId = ((df::general_ref_building_holderst*)job->general_refs[a])->building_id;
|
||||
if (buildingId == -1) {
|
||||
if ( DFHack::Once::doOnce("autoSyndrome processJob invalid building") )
|
||||
out.print("%s, line %d: invalid building.\n", __FILE__, __LINE__);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
df::building* building = df::building::find(buildingId);
|
||||
if ( building == NULL ) {
|
||||
if ( DFHack::Once::doOnce("autoSyndrome processJob couldn't find building") )
|
||||
out.print("%s, line %d: error: couldn't find building %d.\n", __FILE__, __LINE__, buildingId);
|
||||
return;
|
||||
}
|
||||
|
||||
//find all of the products it makes. Look for a stone.
|
||||
for ( size_t a = 0; a < reaction->products.size(); a++ ) {
|
||||
bool appliedSomething = false;
|
||||
df::reaction_product_type type = reaction->products[a]->getType();
|
||||
//out.print("type = %d\n", (int32_t)type);
|
||||
if ( type != df::enums::reaction_product_type::item )
|
||||
continue;
|
||||
df::reaction_product_itemst* bob = (df::reaction_product_itemst*)reaction->products[a];
|
||||
//out.print("item_type = %d\n", (int32_t)bob->item_type);
|
||||
if ( bob->item_type != df::enums::item_type::BOULDER )
|
||||
continue;
|
||||
|
||||
if ( bob->mat_index < 0 )
|
||||
continue;
|
||||
|
||||
//for now don't worry about subtype
|
||||
df::inorganic_raw* inorganic = df::global::world->raws.inorganics[bob->mat_index];
|
||||
|
||||
//maybe add each syndrome to the guy who did the job, or someone in the building, and maybe execute a command
|
||||
for ( size_t b = 0; b < inorganic->material.syndrome.size(); b++ ) {
|
||||
df::syndrome* syndrome = inorganic->material.syndrome[b];
|
||||
bool workerOnly = true;
|
||||
bool allowMultipleTargets = false;
|
||||
bool foundCommand = false;
|
||||
bool destroyRock = true;
|
||||
bool foundAutoSyndrome = false;
|
||||
ResetPolicy::ResetPolicy policy = ResetPolicy::NewInstance;
|
||||
string commandStr;
|
||||
vector<string> args;
|
||||
for ( size_t c = 0; c < syndrome->syn_class.size(); c++ ) {
|
||||
std::string& clazz = *syndrome->syn_class[c];
|
||||
//special syn_classes
|
||||
if ( clazz == "\\AUTO_SYNDROME" ) {
|
||||
foundAutoSyndrome = true;
|
||||
continue;
|
||||
} else if ( clazz == "\\ALLOW_NONWORKER_TARGETS" ) {
|
||||
workerOnly = false;
|
||||
continue;
|
||||
} else if ( clazz == "\\ALLOW_MULTIPLE_TARGETS" ) {
|
||||
allowMultipleTargets = true;
|
||||
continue;
|
||||
} else if ( clazz == "\\PRESERVE_ROCK" ) {
|
||||
destroyRock = false;
|
||||
continue;
|
||||
} else if ( clazz == "\\RESET_POLICY DoNothing" ) {
|
||||
policy = ResetPolicy::DoNothing;
|
||||
continue;
|
||||
} else if ( clazz == "\\RESET_POLICY ResetDuration" ) {
|
||||
policy = ResetPolicy::ResetDuration;
|
||||
continue;
|
||||
} else if ( clazz == "\\RESET_POLICY AddDuration" ) {
|
||||
policy = ResetPolicy::AddDuration;
|
||||
continue;
|
||||
} else if ( clazz == "\\RESET_POLICY NewInstance" ) {
|
||||
policy = ResetPolicy::NewInstance;
|
||||
continue;
|
||||
}
|
||||
//special arguments for a DFHack console command
|
||||
if ( foundCommand ) {
|
||||
if ( commandStr == "" ) {
|
||||
commandStr = clazz;
|
||||
} else {
|
||||
stringstream bob;
|
||||
if ( clazz == "\\LOCATION" ) {
|
||||
bob << job->pos.x;
|
||||
args.push_back(bob.str());
|
||||
bob.str("");
|
||||
bob.clear();
|
||||
|
||||
bob << job->pos.y;
|
||||
args.push_back(bob.str());
|
||||
bob.str("");
|
||||
bob.clear();
|
||||
|
||||
bob << job->pos.z;
|
||||
args.push_back(bob.str());
|
||||
bob.str("");
|
||||
bob.clear();
|
||||
} else if ( clazz == "\\WORKER_ID" ) {
|
||||
bob << workerId;
|
||||
args.push_back(bob.str());
|
||||
} else if ( clazz == "\\REACTION_INDEX" ) {
|
||||
bob << reaction->index;
|
||||
args.push_back(bob.str());
|
||||
} else {
|
||||
args.push_back(clazz);
|
||||
}
|
||||
}
|
||||
} else if ( clazz == "\\COMMAND" ) {
|
||||
foundCommand = true;
|
||||
}
|
||||
}
|
||||
if ( !foundAutoSyndrome ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( commandStr != "" ) {
|
||||
Core::getInstance().runCommand(out, commandStr, args);
|
||||
}
|
||||
|
||||
if ( destroyRock ) {
|
||||
//find the rock and kill it before it can boil and cause problems and ugliness
|
||||
for ( size_t c = 0; c < df::global::world->items.all.size(); c++ ) {
|
||||
df::item* item = df::global::world->items.all[c];
|
||||
if ( item->pos.z != building->z )
|
||||
continue;
|
||||
if ( item->pos.x < building->x1 || item->pos.x > building->x2 )
|
||||
continue;
|
||||
if ( item->pos.y < building->y1 || item->pos.y > building->y2 )
|
||||
continue;
|
||||
if ( item->getType() != df::enums::item_type::BOULDER )
|
||||
continue;
|
||||
//make sure it's the right type of boulder
|
||||
df::item_boulderst* boulder = (df::item_boulderst*)item;
|
||||
if ( boulder->mat_index != bob->mat_index )
|
||||
continue;
|
||||
|
||||
boulder->flags.bits.hidden = true;
|
||||
boulder->flags.bits.forbid = true;
|
||||
boulder->flags.bits.garbage_collect = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ( maybeApply(out, syndrome, workerId, worker, policy) ) {
|
||||
appliedSomething = true;
|
||||
}
|
||||
|
||||
if ( workerOnly )
|
||||
continue;
|
||||
|
||||
if ( appliedSomething && !allowMultipleTargets )
|
||||
continue;
|
||||
|
||||
//now try applying it to everybody inside the building
|
||||
for ( size_t a = 0; a < df::global::world->units.active.size(); a++ ) {
|
||||
df::unit* unit = df::global::world->units.active[a];
|
||||
if ( unit == worker )
|
||||
continue; //we already tried giving it to him, so no doubling up
|
||||
if ( unit->pos.z != building->z )
|
||||
continue;
|
||||
if ( unit->pos.x < building->x1 || unit->pos.x > building->x2 )
|
||||
continue;
|
||||
if ( unit->pos.y < building->y1 || unit->pos.y > building->y2 )
|
||||
continue;
|
||||
if ( maybeApply(out, syndrome, unit->id, unit, policy) ) {
|
||||
appliedSomething = true;
|
||||
if ( !allowMultipleTargets )
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Heavily based on https://gist.github.com/4061959/
|
||||
**/
|
||||
int32_t giveSyndrome(color_ostream& out, int32_t workerId, df::syndrome* syndrome, ResetPolicy::ResetPolicy policy) {
|
||||
df::unit* unit = df::unit::find(workerId);
|
||||
if ( !unit ) {
|
||||
if ( DFHack::Once::doOnce("autoSyndrome giveSyndrome couldn't find unit") )
|
||||
out.print("%s line %d: Couldn't find unit %d.\n", __FILE__, __LINE__, workerId);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ( policy != ResetPolicy::NewInstance ) {
|
||||
//figure out if already there
|
||||
for ( size_t a = 0; a < unit->syndromes.active.size(); a++ ) {
|
||||
df::unit_syndrome* unitSyndrome = unit->syndromes.active[a];
|
||||
if ( unitSyndrome->type != syndrome->id )
|
||||
continue;
|
||||
int32_t most = 0;
|
||||
switch(policy) {
|
||||
case ResetPolicy::DoNothing:
|
||||
return -1;
|
||||
case ResetPolicy::ResetDuration:
|
||||
for ( size_t b = 0; b < unitSyndrome->symptoms.size(); b++ ) {
|
||||
unitSyndrome->symptoms[b]->ticks = 0; //might cause crashes with transformations
|
||||
}
|
||||
unitSyndrome->ticks = 0;
|
||||
break;
|
||||
case ResetPolicy::AddDuration:
|
||||
if ( unitSyndrome->symptoms.size() != syndrome->ce.size() ) {
|
||||
if ( DFHack::Once::doOnce("autoSyndrome giveSyndrome incorrect symptom count") )
|
||||
out.print("%s, line %d. Incorrect symptom count %d != %d\n", __FILE__, __LINE__, unitSyndrome->symptoms.size(), syndrome->ce.size());
|
||||
break;
|
||||
}
|
||||
for ( size_t b = 0; b < unitSyndrome->symptoms.size(); b++ ) {
|
||||
if ( syndrome->ce[b]->end == -1 )
|
||||
continue;
|
||||
unitSyndrome->symptoms[b]->ticks -= syndrome->ce[b]->end;
|
||||
if ( syndrome->ce[b]->end > most )
|
||||
most = syndrome->ce[b]->end;
|
||||
}
|
||||
unitSyndrome->ticks -= most;
|
||||
break;
|
||||
default:
|
||||
if ( DFHack::Once::doOnce("autoSyndrome giveSyndrome invalid reset policy") )
|
||||
out.print("%s, line %d: invalid reset policy %d.\n", __FILE__, __LINE__, policy);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
df::unit_syndrome* unitSyndrome = new df::unit_syndrome();
|
||||
unitSyndrome->type = syndrome->id;
|
||||
unitSyndrome->year = DFHack::World::ReadCurrentYear();
|
||||
unitSyndrome->year_time = DFHack::World::ReadCurrentTick();
|
||||
unitSyndrome->ticks = 0;
|
||||
unitSyndrome->unk1 = 0;
|
||||
unitSyndrome->flags = 0; //TODO: typecast?
|
||||
|
||||
for ( size_t a = 0; a < syndrome->ce.size(); a++ ) {
|
||||
df::unit_syndrome::T_symptoms* symptom = new df::unit_syndrome::T_symptoms();
|
||||
symptom->unk1 = 0;
|
||||
symptom->unk2 = 0;
|
||||
symptom->ticks = 0;
|
||||
symptom->flags = 2; //TODO: ???
|
||||
unitSyndrome->symptoms.push_back(symptom);
|
||||
}
|
||||
unit->syndromes.active.push_back(unitSyndrome);
|
||||
return 0;
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,619 @@
|
||||
#include "uicommon.h"
|
||||
|
||||
#include "modules/Gui.h"
|
||||
|
||||
#include "df/world.h"
|
||||
#include "df/world_raws.h"
|
||||
#include "df/building_def.h"
|
||||
#include "df/viewscreen_dwarfmodest.h"
|
||||
#include "df/building_stockpilest.h"
|
||||
#include "modules/Items.h"
|
||||
#include "df/building_tradedepotst.h"
|
||||
#include "df/general_ref_building_holderst.h"
|
||||
#include "df/job.h"
|
||||
#include "df/job_item_ref.h"
|
||||
#include "modules/Job.h"
|
||||
#include "df/ui.h"
|
||||
#include "df/caravan_state.h"
|
||||
#include "df/mandate.h"
|
||||
#include "modules/Maps.h"
|
||||
#include "modules/World.h"
|
||||
|
||||
using df::global::world;
|
||||
using df::global::cursor;
|
||||
using df::global::ui;
|
||||
using df::building_stockpilest;
|
||||
|
||||
DFHACK_PLUGIN("autotrade");
|
||||
#define PLUGIN_VERSION 0.2
|
||||
|
||||
|
||||
/*
|
||||
* Stockpile Access
|
||||
*/
|
||||
|
||||
static building_stockpilest *get_selected_stockpile()
|
||||
{
|
||||
if (!Gui::dwarfmode_hotkey(Core::getTopViewscreen()) ||
|
||||
ui->main.mode != ui_sidebar_mode::QueryBuilding)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return virtual_cast<building_stockpilest>(world->selected_building);
|
||||
}
|
||||
|
||||
static bool can_trade()
|
||||
{
|
||||
if (df::global::ui->caravans.size() == 0)
|
||||
return false;
|
||||
|
||||
for (auto it = df::global::ui->caravans.begin(); it != df::global::ui->caravans.end(); it++)
|
||||
{
|
||||
auto caravan = *it;
|
||||
auto trade_state = caravan->trade_state;
|
||||
auto time_remaining = caravan->time_remaining;
|
||||
if ((trade_state != 1 && trade_state != 2) || time_remaining == 0)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
class StockpileInfo {
|
||||
public:
|
||||
|
||||
StockpileInfo(df::building_stockpilest *sp_) : sp(sp_)
|
||||
{
|
||||
readBuilding();
|
||||
}
|
||||
|
||||
StockpileInfo(PersistentDataItem &config)
|
||||
{
|
||||
this->config = config;
|
||||
id = config.ival(1);
|
||||
}
|
||||
|
||||
bool inStockpile(df::item *i)
|
||||
{
|
||||
df::item *container = Items::getContainer(i);
|
||||
if (container)
|
||||
return inStockpile(container);
|
||||
|
||||
if (i->pos.z != z) return false;
|
||||
if (i->pos.x < x1 || i->pos.x >= x2 ||
|
||||
i->pos.y < y1 || i->pos.y >= y2) return false;
|
||||
int e = (i->pos.x - x1) + (i->pos.y - y1) * sp->room.width;
|
||||
return sp->room.extents[e] == 1;
|
||||
}
|
||||
|
||||
bool isValid()
|
||||
{
|
||||
auto found = df::building::find(id);
|
||||
return found && found == sp && found->getType() == building_type::Stockpile;
|
||||
}
|
||||
|
||||
bool load()
|
||||
{
|
||||
auto found = df::building::find(id);
|
||||
if (!found || found->getType() != building_type::Stockpile)
|
||||
return false;
|
||||
|
||||
sp = virtual_cast<df::building_stockpilest>(found);
|
||||
if (!sp)
|
||||
return false;
|
||||
|
||||
readBuilding();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int32_t getId()
|
||||
{
|
||||
return id;
|
||||
}
|
||||
|
||||
bool matches(df::building_stockpilest* sp)
|
||||
{
|
||||
return this->sp == sp;
|
||||
}
|
||||
|
||||
void save()
|
||||
{
|
||||
config = DFHack::World::AddPersistentData("autotrade/stockpiles");
|
||||
config.ival(1) = id;
|
||||
}
|
||||
|
||||
void remove()
|
||||
{
|
||||
DFHack::World::DeletePersistentData(config);
|
||||
}
|
||||
|
||||
private:
|
||||
PersistentDataItem config;
|
||||
df::building_stockpilest* sp;
|
||||
int x1, x2, y1, y2, z;
|
||||
int32_t id;
|
||||
|
||||
void readBuilding()
|
||||
{
|
||||
id = sp->id;
|
||||
z = sp->z;
|
||||
x1 = sp->room.x;
|
||||
x2 = sp->room.x + sp->room.width;
|
||||
y1 = sp->room.y;
|
||||
y2 = sp->room.y + sp->room.height;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* Depot Access
|
||||
*/
|
||||
|
||||
class TradeDepotInfo
|
||||
{
|
||||
public:
|
||||
TradeDepotInfo() : depot(0)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool findDepot()
|
||||
{
|
||||
if (isValid())
|
||||
return true;
|
||||
|
||||
reset();
|
||||
for(auto bld_it = world->buildings.all.begin(); bld_it != world->buildings.all.end(); bld_it++)
|
||||
{
|
||||
auto bld = *bld_it;
|
||||
if (!isUsableDepot(bld))
|
||||
continue;
|
||||
|
||||
depot = bld;
|
||||
id = depot->id;
|
||||
break;
|
||||
}
|
||||
|
||||
return depot;
|
||||
}
|
||||
|
||||
bool assignItem(df::item *item)
|
||||
{
|
||||
auto href = df::allocate<df::general_ref_building_holderst>();
|
||||
if (!href)
|
||||
return false;
|
||||
|
||||
auto job = new df::job();
|
||||
|
||||
df::coord tpos(depot->centerx, depot->centery, depot->z);
|
||||
job->pos = tpos;
|
||||
|
||||
job->job_type = job_type::BringItemToDepot;
|
||||
|
||||
// job <-> item link
|
||||
if (!Job::attachJobItem(job, item, df::job_item_ref::Hauled))
|
||||
{
|
||||
delete job;
|
||||
delete href;
|
||||
return false;
|
||||
}
|
||||
|
||||
// job <-> building link
|
||||
href->building_id = id;
|
||||
depot->jobs.push_back(job);
|
||||
job->general_refs.push_back(href);
|
||||
|
||||
// add to job list
|
||||
Job::linkIntoWorld(job);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void reset()
|
||||
{
|
||||
depot = 0;
|
||||
}
|
||||
|
||||
private:
|
||||
int32_t id;
|
||||
df::building *depot;
|
||||
|
||||
bool isUsableDepot(df::building* bld)
|
||||
{
|
||||
if (bld->getType() != building_type::TradeDepot)
|
||||
return false;
|
||||
|
||||
if (bld->getBuildStage() < bld->getMaxBuildStage())
|
||||
return false;
|
||||
|
||||
if (bld->jobs.size() == 1 && bld->jobs[0]->job_type == job_type::DestroyBuilding)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool isValid()
|
||||
{
|
||||
if (!depot)
|
||||
return false;
|
||||
|
||||
auto found = df::building::find(id);
|
||||
return found && found == depot && isUsableDepot(found);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
static TradeDepotInfo depot_info;
|
||||
|
||||
|
||||
/*
|
||||
* Item Manipulation
|
||||
*/
|
||||
|
||||
static bool check_mandates(df::item *item)
|
||||
{
|
||||
for (auto it = world->mandates.begin(); it != world->mandates.end(); it++)
|
||||
{
|
||||
auto mandate = *it;
|
||||
|
||||
if (mandate->mode != 0)
|
||||
continue;
|
||||
|
||||
if (item->getType() != mandate->item_type ||
|
||||
(mandate->item_subtype != -1 && item->getSubtype() != mandate->item_subtype))
|
||||
continue;
|
||||
|
||||
if (mandate->mat_type != -1 && item->getMaterial() != mandate->mat_type)
|
||||
continue;
|
||||
|
||||
if (mandate->mat_index != -1 && item->getMaterialIndex() != mandate->mat_index)
|
||||
continue;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool is_valid_item(df::item *item)
|
||||
{
|
||||
for (size_t i = 0; i < item->general_refs.size(); i++)
|
||||
{
|
||||
df::general_ref *ref = item->general_refs[i];
|
||||
|
||||
switch (ref->getType())
|
||||
{
|
||||
case general_ref_type::CONTAINED_IN_ITEM:
|
||||
return false;
|
||||
|
||||
case general_ref_type::UNIT_HOLDER:
|
||||
return false;
|
||||
|
||||
case general_ref_type::BUILDING_HOLDER:
|
||||
return false;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < item->specific_refs.size(); i++)
|
||||
{
|
||||
df::specific_ref *ref = item->specific_refs[i];
|
||||
|
||||
if (ref->type == specific_ref_type::JOB)
|
||||
{
|
||||
// Ignore any items assigned to a job
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!check_mandates(item))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void mark_all_in_stockpiles(vector<StockpileInfo> &stockpiles, bool announce)
|
||||
{
|
||||
if (!depot_info.findDepot())
|
||||
{
|
||||
if (announce)
|
||||
Gui::showAnnouncement("Cannot trade, no valid depot available", COLOR_RED, true);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<df::item*> &items = world->items.other[items_other_id::IN_PLAY];
|
||||
|
||||
|
||||
// Precompute a bitmask with the bad flags
|
||||
df::item_flags bad_flags;
|
||||
bad_flags.whole = 0;
|
||||
|
||||
#define F(x) bad_flags.bits.x = true;
|
||||
F(dump); F(forbid); F(garbage_collect);
|
||||
F(hostile); F(on_fire); F(rotten); F(trader);
|
||||
F(in_building); F(construction); F(artifact);
|
||||
F(spider_web); F(owned); F(in_job);
|
||||
#undef F
|
||||
|
||||
size_t marked_count = 0;
|
||||
size_t error_count = 0;
|
||||
for (size_t i = 0; i < items.size(); i++)
|
||||
{
|
||||
df::item *item = items[i];
|
||||
if (item->flags.whole & bad_flags.whole)
|
||||
continue;
|
||||
|
||||
if (!is_valid_item(item))
|
||||
continue;
|
||||
|
||||
for (auto it = stockpiles.begin(); it != stockpiles.end(); it++)
|
||||
{
|
||||
if (!it->inStockpile(item))
|
||||
continue;
|
||||
|
||||
// In case of container, check contained items for mandates
|
||||
bool mandates_ok = true;
|
||||
vector<df::item*> contained_items;
|
||||
Items::getContainedItems(item, &contained_items);
|
||||
for (auto cit = contained_items.begin(); cit != contained_items.end(); cit++)
|
||||
{
|
||||
if (!check_mandates(*cit))
|
||||
{
|
||||
mandates_ok = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!mandates_ok)
|
||||
continue;
|
||||
|
||||
if (depot_info.assignItem(item))
|
||||
{
|
||||
++marked_count;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (++error_count < 5)
|
||||
{
|
||||
Gui::showZoomAnnouncement(df::announcement_type::CANCEL_JOB, item->pos,
|
||||
"Cannot trade item from stockpile " + int_to_string(it->getId()), COLOR_RED, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (marked_count)
|
||||
Gui::showAnnouncement("Marked " + int_to_string(marked_count) + " items for trade", COLOR_GREEN, false);
|
||||
else if (announce)
|
||||
Gui::showAnnouncement("No more items to mark", COLOR_RED, true);
|
||||
|
||||
if (error_count >= 5)
|
||||
{
|
||||
Gui::showAnnouncement(int_to_string(error_count) + " items were not marked", COLOR_RED, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Stockpile Monitoring
|
||||
*/
|
||||
|
||||
class StockpileMonitor
|
||||
{
|
||||
public:
|
||||
bool isMonitored(df::building_stockpilest *sp)
|
||||
{
|
||||
for (auto it = monitored_stockpiles.begin(); it != monitored_stockpiles.end(); it++)
|
||||
{
|
||||
if (it->matches(sp))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void add(df::building_stockpilest *sp)
|
||||
{
|
||||
auto pile = StockpileInfo(sp);
|
||||
if (pile.isValid())
|
||||
{
|
||||
monitored_stockpiles.push_back(StockpileInfo(sp));
|
||||
monitored_stockpiles.back().save();
|
||||
}
|
||||
}
|
||||
|
||||
void remove(df::building_stockpilest *sp)
|
||||
{
|
||||
for (auto it = monitored_stockpiles.begin(); it != monitored_stockpiles.end(); it++)
|
||||
{
|
||||
if (it->matches(sp))
|
||||
{
|
||||
it->remove();
|
||||
monitored_stockpiles.erase(it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void doCycle()
|
||||
{
|
||||
if (!can_trade())
|
||||
return;
|
||||
|
||||
for (auto it = monitored_stockpiles.begin(); it != monitored_stockpiles.end();)
|
||||
{
|
||||
if (!it->isValid())
|
||||
{
|
||||
it = monitored_stockpiles.erase(it);
|
||||
continue;
|
||||
}
|
||||
|
||||
++it;
|
||||
}
|
||||
|
||||
mark_all_in_stockpiles(monitored_stockpiles, false);
|
||||
}
|
||||
|
||||
void reset()
|
||||
{
|
||||
monitored_stockpiles.clear();
|
||||
std::vector<PersistentDataItem> items;
|
||||
DFHack::World::GetPersistentData(&items, "autotrade/stockpiles");
|
||||
|
||||
for (auto i = items.begin(); i != items.end(); i++)
|
||||
{
|
||||
auto pile = StockpileInfo(*i);
|
||||
if (pile.load())
|
||||
monitored_stockpiles.push_back(StockpileInfo(pile));
|
||||
else
|
||||
pile.remove();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
vector<StockpileInfo> monitored_stockpiles;
|
||||
};
|
||||
|
||||
static StockpileMonitor monitor;
|
||||
|
||||
#define DELTA_TICKS 600
|
||||
|
||||
DFhackCExport command_result plugin_onupdate ( color_ostream &out )
|
||||
{
|
||||
if(!Maps::IsValid())
|
||||
return CR_OK;
|
||||
|
||||
static decltype(world->frame_counter) last_frame_count = 0;
|
||||
|
||||
if (DFHack::World::ReadPauseState())
|
||||
return CR_OK;
|
||||
|
||||
if (world->frame_counter - last_frame_count < DELTA_TICKS)
|
||||
return CR_OK;
|
||||
|
||||
last_frame_count = world->frame_counter;
|
||||
|
||||
monitor.doCycle();
|
||||
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Interface
|
||||
*/
|
||||
|
||||
struct trade_hook : public df::viewscreen_dwarfmodest
|
||||
{
|
||||
typedef df::viewscreen_dwarfmodest interpose_base;
|
||||
|
||||
bool handleInput(set<df::interface_key> *input)
|
||||
{
|
||||
building_stockpilest *sp = get_selected_stockpile();
|
||||
if (!sp)
|
||||
return false;
|
||||
|
||||
if (input->count(interface_key::CUSTOM_M))
|
||||
{
|
||||
if (!can_trade())
|
||||
return false;
|
||||
|
||||
vector<StockpileInfo> wrapper;
|
||||
wrapper.push_back(StockpileInfo(sp));
|
||||
mark_all_in_stockpiles(wrapper, true);
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (input->count(interface_key::CUSTOM_U))
|
||||
{
|
||||
if (monitor.isMonitored(sp))
|
||||
monitor.remove(sp);
|
||||
else
|
||||
monitor.add(sp);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
DEFINE_VMETHOD_INTERPOSE(void, feed, (set<df::interface_key> *input))
|
||||
{
|
||||
if (!handleInput(input))
|
||||
INTERPOSE_NEXT(feed)(input);
|
||||
}
|
||||
|
||||
DEFINE_VMETHOD_INTERPOSE(void, render, ())
|
||||
{
|
||||
INTERPOSE_NEXT(render)();
|
||||
|
||||
building_stockpilest *sp = get_selected_stockpile();
|
||||
if (!sp)
|
||||
return;
|
||||
|
||||
auto dims = Gui::getDwarfmodeViewDims();
|
||||
int left_margin = dims.menu_x1 + 1;
|
||||
int x = left_margin;
|
||||
int y = 23;
|
||||
|
||||
if (can_trade())
|
||||
OutputHotkeyString(x, y, "Mark all for trade", "m", true, left_margin);
|
||||
|
||||
OutputToggleString(x, y, "Auto trade", "u", monitor.isMonitored(sp), true, left_margin);
|
||||
}
|
||||
};
|
||||
|
||||
IMPLEMENT_VMETHOD_INTERPOSE(trade_hook, feed);
|
||||
IMPLEMENT_VMETHOD_INTERPOSE(trade_hook, render);
|
||||
|
||||
static command_result autotrade_cmd(color_ostream &out, vector <string> & parameters)
|
||||
{
|
||||
if (!parameters.empty())
|
||||
{
|
||||
if (parameters.size() == 1 && toLower(parameters[0])[0] == 'v')
|
||||
{
|
||||
out << "Building Plan" << endl << "Version: " << PLUGIN_VERSION << endl;
|
||||
}
|
||||
}
|
||||
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
|
||||
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event)
|
||||
{
|
||||
switch (event)
|
||||
{
|
||||
case DFHack::SC_MAP_LOADED:
|
||||
depot_info.reset();
|
||||
monitor.reset();
|
||||
break;
|
||||
case DFHack::SC_MAP_UNLOADED:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
|
||||
{
|
||||
if (!gps || !INTERPOSE_HOOK(trade_hook, feed).apply() || !INTERPOSE_HOOK(trade_hook, render).apply())
|
||||
out.printerr("Could not insert autotrade hooks!\n");
|
||||
|
||||
commands.push_back(
|
||||
PluginCommand(
|
||||
"autotrade", "Automatically send items in marked stockpiles to trade depot, when trading is possible.",
|
||||
autotrade_cmd, false, ""));
|
||||
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
|
||||
{
|
||||
return CR_OK;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,264 @@
|
||||
// Create arbitrary items
|
||||
|
||||
#include "Core.h"
|
||||
#include "Console.h"
|
||||
#include "Export.h"
|
||||
#include "PluginManager.h"
|
||||
#include "MiscUtils.h"
|
||||
|
||||
#include "modules/Maps.h"
|
||||
#include "modules/Gui.h"
|
||||
#include "modules/Items.h"
|
||||
#include "modules/Materials.h"
|
||||
|
||||
#include "DataDefs.h"
|
||||
#include "df/game_type.h"
|
||||
#include "df/world.h"
|
||||
#include "df/ui.h"
|
||||
#include "df/unit.h"
|
||||
#include "df/historical_entity.h"
|
||||
#include "df/world_site.h"
|
||||
#include "df/item.h"
|
||||
#include "df/creature_raw.h"
|
||||
#include "df/caste_raw.h"
|
||||
#include "df/reaction_reagent.h"
|
||||
#include "df/reaction_product_itemst.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace DFHack;
|
||||
|
||||
using df::global::world;
|
||||
using df::global::ui;
|
||||
using df::global::gametype;
|
||||
|
||||
DFHACK_PLUGIN("createitem");
|
||||
|
||||
command_result df_createitem (color_ostream &out, vector <string> & parameters);
|
||||
|
||||
DFhackCExport command_result plugin_init (color_ostream &out, std::vector<PluginCommand> &commands)
|
||||
{
|
||||
commands.push_back(PluginCommand("createitem", "Create arbitrary item at the selected unit's feet.", df_createitem));
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
|
||||
{
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
bool makeItem (df::reaction_product_itemst *prod, df::unit *unit, bool second_item = false)
|
||||
{
|
||||
vector<df::item *> out_items;
|
||||
vector<df::reaction_reagent *> in_reag;
|
||||
vector<df::item *> in_items;
|
||||
bool is_gloves = (prod->item_type == df::item_type::GLOVES);
|
||||
bool is_shoes = (prod->item_type == df::item_type::SHOES);
|
||||
|
||||
prod->produce(unit, &out_items, &in_reag, &in_items, 1, df::job_skill::NONE,
|
||||
df::historical_entity::find(unit->civ_id),
|
||||
((*gametype == df::game_type::DWARF_MAIN) || (*gametype == df::game_type::DWARF_RECLAIM)) ? df::world_site::find(ui->site_id) : NULL);
|
||||
if (!out_items.size())
|
||||
return false;
|
||||
// if we asked to make shoes and we got twice as many as we asked, then we're okay
|
||||
// otherwise, make a second set because shoes are normally made in pairs
|
||||
if (is_shoes && out_items.size() == prod->count * 2)
|
||||
is_shoes = false;
|
||||
for (size_t i = 0; i < out_items.size(); i++)
|
||||
{
|
||||
out_items[i]->moveToGround(unit->pos.x, unit->pos.y, unit->pos.z);
|
||||
if (is_gloves)
|
||||
{
|
||||
// if the reaction creates gloves without handedness, then create 2 sets (left and right)
|
||||
if (out_items[i]->getGloveHandedness() > 0)
|
||||
is_gloves = false;
|
||||
else
|
||||
out_items[i]->setGloveHandedness(second_item ? 2 : 1);
|
||||
}
|
||||
}
|
||||
if ((is_gloves || is_shoes) && !second_item)
|
||||
return makeItem(prod, unit, true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
command_result df_createitem (color_ostream &out, vector <string> & parameters)
|
||||
{
|
||||
string item_str, material_str;
|
||||
df::item_type item_type = df::item_type::NONE;
|
||||
int16_t item_subtype = -1;
|
||||
int16_t mat_type = -1;
|
||||
int32_t mat_index = -1;
|
||||
int count = 1;
|
||||
|
||||
if ((parameters.size() < 2) || (parameters.size() > 3))
|
||||
{
|
||||
out.print("Syntax: createitem <item> <material> [count]\n"
|
||||
" <item> - Item token for what you wish to create, as specified in custom\n"
|
||||
" reactions. If the item has no subtype, omit the :NONE.\n"
|
||||
" <material> - The material you want the item to be made of, as specified\n"
|
||||
" in custom reactions. For REMAINS, FISH, FISH_RAW, VERMIN,\n"
|
||||
" PET, and EGG, replace this with a creature ID and caste.\n"
|
||||
" [count] - How many of the item you wish to create.\n"
|
||||
);
|
||||
return CR_WRONG_USAGE;
|
||||
}
|
||||
item_str = parameters[0];
|
||||
material_str = parameters[1];
|
||||
|
||||
if (parameters.size() == 3)
|
||||
{
|
||||
stringstream ss(parameters[2]);
|
||||
ss >> count;
|
||||
if (count < 1)
|
||||
{
|
||||
out.printerr("You cannot produce less than one item!\n");
|
||||
return CR_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
ItemTypeInfo item;
|
||||
MaterialInfo material;
|
||||
vector<string> tokens;
|
||||
|
||||
if (!item.find(item_str))
|
||||
{
|
||||
out.printerr("Unrecognized item type!\n");
|
||||
return CR_FAILURE;
|
||||
}
|
||||
item_type = item.type;
|
||||
item_subtype = item.subtype;
|
||||
switch (item.type)
|
||||
{
|
||||
case df::item_type::INSTRUMENT:
|
||||
case df::item_type::TOY:
|
||||
case df::item_type::WEAPON:
|
||||
case df::item_type::ARMOR:
|
||||
case df::item_type::SHOES:
|
||||
case df::item_type::SHIELD:
|
||||
case df::item_type::HELM:
|
||||
case df::item_type::GLOVES:
|
||||
case df::item_type::AMMO:
|
||||
case df::item_type::PANTS:
|
||||
case df::item_type::SIEGEAMMO:
|
||||
case df::item_type::TRAPCOMP:
|
||||
case df::item_type::TOOL:
|
||||
if (item_subtype == -1)
|
||||
{
|
||||
out.printerr("You must specify a subtype!\n");
|
||||
return CR_FAILURE;
|
||||
}
|
||||
default:
|
||||
if (!material.find(material_str))
|
||||
{
|
||||
out.printerr("Unrecognized material!\n");
|
||||
return CR_FAILURE;
|
||||
}
|
||||
mat_type = material.type;
|
||||
mat_index = material.index;
|
||||
break;
|
||||
|
||||
case df::item_type::REMAINS:
|
||||
case df::item_type::FISH:
|
||||
case df::item_type::FISH_RAW:
|
||||
case df::item_type::VERMIN:
|
||||
case df::item_type::PET:
|
||||
case df::item_type::EGG:
|
||||
split_string(&tokens, material_str, ":");
|
||||
if (tokens.size() != 2)
|
||||
{
|
||||
out.printerr("You must specify a creature ID and caste for this item type!\n");
|
||||
return CR_FAILURE;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < world->raws.creatures.all.size(); i++)
|
||||
{
|
||||
df::creature_raw *creature = world->raws.creatures.all[i];
|
||||
if (creature->creature_id == tokens[0])
|
||||
{
|
||||
for (size_t j = 0; j < creature->caste.size(); j++)
|
||||
{
|
||||
df::caste_raw *caste = creature->caste[j];
|
||||
if (creature->caste[j]->caste_id == tokens[1])
|
||||
{
|
||||
mat_type = i;
|
||||
mat_index = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (mat_type == -1)
|
||||
{
|
||||
out.printerr("The creature you specified has no such caste!\n");
|
||||
return CR_FAILURE;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (mat_type == -1)
|
||||
{
|
||||
out.printerr("Unrecognized creature ID!\n");
|
||||
return CR_FAILURE;
|
||||
}
|
||||
break;
|
||||
|
||||
case df::item_type::CORPSE:
|
||||
case df::item_type::CORPSEPIECE:
|
||||
case df::item_type::FOOD:
|
||||
out.printerr("Cannot create that type of item!\n");
|
||||
return CR_FAILURE;
|
||||
break;
|
||||
}
|
||||
|
||||
CoreSuspender suspend;
|
||||
|
||||
df::unit *unit = Gui::getSelectedUnit(out, true);
|
||||
if (!unit)
|
||||
{
|
||||
out.printerr("No unit selected!\n");
|
||||
return CR_FAILURE;
|
||||
}
|
||||
if (!Maps::IsValid())
|
||||
{
|
||||
out.printerr("Map is not available.\n");
|
||||
return CR_FAILURE;
|
||||
}
|
||||
df::map_block *block = Maps::getTileBlock(unit->pos.x, unit->pos.y, unit->pos.z);
|
||||
if (block == NULL)
|
||||
{
|
||||
out.printerr("Unit does not reside in a valid map block, somehow?\n");
|
||||
return CR_FAILURE;
|
||||
}
|
||||
|
||||
df::reaction_product_itemst *prod = df::allocate<df::reaction_product_itemst>();
|
||||
prod->item_type = item_type;
|
||||
prod->item_subtype = item_subtype;
|
||||
prod->mat_type = mat_type;
|
||||
prod->mat_index = mat_index;
|
||||
prod->probability = 100;
|
||||
prod->count = count;
|
||||
switch (item_type)
|
||||
{
|
||||
case df::item_type::BAR:
|
||||
case df::item_type::POWDER_MISC:
|
||||
case df::item_type::LIQUID_MISC:
|
||||
case df::item_type::DRINK:
|
||||
prod->product_dimension = 150;
|
||||
break;
|
||||
case df::item_type::THREAD:
|
||||
prod->product_dimension = 15000;
|
||||
break;
|
||||
case df::item_type::CLOTH:
|
||||
prod->product_dimension = 10000;
|
||||
break;
|
||||
default:
|
||||
prod->product_dimension = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
bool result = makeItem(prod, unit);
|
||||
delete prod;
|
||||
if (!result)
|
||||
{
|
||||
out.printerr("Failed to create item!\n");
|
||||
return CR_FAILURE;
|
||||
}
|
||||
return CR_OK;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,35 @@
|
||||
|
||||
#include "Core.h"
|
||||
#include "Console.h"
|
||||
#include "DataDefs.h"
|
||||
#include "Export.h"
|
||||
#include "PluginManager.h"
|
||||
|
||||
#include "modules/Once.h"
|
||||
|
||||
using namespace DFHack;
|
||||
using namespace df::enums;
|
||||
|
||||
command_result onceExample (color_ostream &out, std::vector <std::string> & parameters);
|
||||
|
||||
DFHACK_PLUGIN("onceExample");
|
||||
|
||||
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
|
||||
{
|
||||
commands.push_back(PluginCommand(
|
||||
"onceExample", "Test the doOnce command.",
|
||||
onceExample, false,
|
||||
" This command tests the doOnce command..\n"
|
||||
));
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
command_result onceExample (color_ostream &out, std::vector <std::string> & parameters)
|
||||
{
|
||||
out.print("Already done = %d.\n", DFHack::Once::alreadyDone("onceExample_1"));
|
||||
if ( DFHack::Once::doOnce("onceExample_1") ) {
|
||||
out.print("Printing this message once!\n");
|
||||
}
|
||||
return CR_OK;
|
||||
}
|
||||
|
@ -0,0 +1,32 @@
|
||||
|
||||
#include "Console.h"
|
||||
#include "Core.h"
|
||||
#include "DataDefs.h"
|
||||
#include "Export.h"
|
||||
#include "PluginManager.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
using namespace DFHack;
|
||||
using namespace std;
|
||||
|
||||
command_result printArgs (color_ostream &out, std::vector <std::string> & parameters);
|
||||
|
||||
DFHACK_PLUGIN("printArgs");
|
||||
|
||||
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
|
||||
{
|
||||
commands.push_back(PluginCommand(
|
||||
"printArgs", "Print the arguments given.",
|
||||
printArgs, false
|
||||
));
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
command_result printArgs (color_ostream &out, std::vector <std::string> & parameters)
|
||||
{
|
||||
for ( size_t a = 0; a < parameters.size(); a++ ) {
|
||||
out << "Argument " << (a+1) << ": \"" << parameters[a] << "\"" << endl;
|
||||
}
|
||||
return CR_OK;
|
||||
}
|
@ -0,0 +1,199 @@
|
||||
#include "Core.h"
|
||||
#include "DataDefs.h"
|
||||
#include "Export.h"
|
||||
#include "PluginManager.h"
|
||||
|
||||
#include "modules/EventManager.h"
|
||||
#include "modules/MapCache.h"
|
||||
#include "modules/Maps.h"
|
||||
|
||||
#include "df/coord.h"
|
||||
#include "df/global_objects.h"
|
||||
#include "df/job.h"
|
||||
#include "df/map_block.h"
|
||||
#include "df/tile_dig_designation.h"
|
||||
#include "df/world.h"
|
||||
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using namespace DFHack;
|
||||
using namespace std;
|
||||
|
||||
command_result digSmart (color_ostream &out, std::vector <std::string> & parameters);
|
||||
|
||||
DFHACK_PLUGIN("digSmart");
|
||||
|
||||
void onDig(color_ostream& out, void* ptr);
|
||||
void maybeExplore(color_ostream& out, MapExtras::MapCache& cache, df::coord pt, set<df::coord>& jobLocations);
|
||||
EventManager::EventHandler digHandler(onDig, 0);
|
||||
|
||||
bool enabled = false;
|
||||
bool digAll = false;
|
||||
set<string> autodigMaterials;
|
||||
|
||||
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
|
||||
{
|
||||
commands.push_back(PluginCommand(
|
||||
"digSmart", "Automatically dig out veins as you discover them.",
|
||||
digSmart, false,
|
||||
"Example:\n"
|
||||
" digSmart 0\n"
|
||||
" disable plugin\n"
|
||||
" digSmart 1\n"
|
||||
" enable plugin\n"
|
||||
" digSmart 0 MICROCLINE COAL_BITUMINOUS 1\n"
|
||||
" disable plugin and remove microcline and bituminous coal from being monitored, then re-enable plugin"
|
||||
" digSmart 1 MICROCLINE 0 COAL_BITUMINOUS 1\n"
|
||||
" don't monitor microcline, do monitor COAL_BITUMINOUS, then enable plugin\n"
|
||||
" digSmart CLEAR\n"
|
||||
" remove all inorganics from monitoring\n"
|
||||
" digSmart digAll1\n"
|
||||
" enable digAll mode: dig any vein, regardless of the monitor list\n"
|
||||
" digSmart digAll0\n"
|
||||
" disable digAll mode\n"
|
||||
"\n"
|
||||
"Note that while order matters, multiple commands can be sequenced in one line. It is recommended to alter your dfhack.init file so that you won't have to type in every mineral type you want to dig every time you start the game. Material names are case sensitive.\n"
|
||||
));
|
||||
EventManager::registerListener(EventManager::EventType::JOB_COMPLETED, digHandler, plugin_self);
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
void onDig(color_ostream& out, void* ptr) {
|
||||
CoreSuspender bob;
|
||||
df::job* job = (df::job*)ptr;
|
||||
if ( job->completion_timer > 0 )
|
||||
return;
|
||||
|
||||
if ( job->job_type != df::enums::job_type::Dig &&
|
||||
job->job_type != df::enums::job_type::CarveUpwardStaircase &&
|
||||
job->job_type != df::enums::job_type::CarveDownwardStaircase &&
|
||||
job->job_type != df::enums::job_type::CarveUpDownStaircase &&
|
||||
job->job_type != df::enums::job_type::CarveRamp &&
|
||||
job->job_type != df::enums::job_type::DigChannel )
|
||||
return;
|
||||
|
||||
set<df::coord> jobLocations;
|
||||
for ( df::job_list_link* link = &df::global::world->job_list; link != NULL; link = link->next ) {
|
||||
if ( link->item == NULL )
|
||||
continue;
|
||||
|
||||
if ( link->item->job_type != df::enums::job_type::Dig &&
|
||||
link->item->job_type != df::enums::job_type::CarveUpwardStaircase &&
|
||||
link->item->job_type != df::enums::job_type::CarveDownwardStaircase &&
|
||||
link->item->job_type != df::enums::job_type::CarveUpDownStaircase &&
|
||||
link->item->job_type != df::enums::job_type::CarveRamp &&
|
||||
link->item->job_type != df::enums::job_type::DigChannel )
|
||||
continue;
|
||||
|
||||
jobLocations.insert(link->item->pos);
|
||||
}
|
||||
|
||||
MapExtras::MapCache cache;
|
||||
df::coord pos = job->pos;
|
||||
for ( int16_t a = -1; a <= 1; a++ ) {
|
||||
for ( int16_t b = -1; b <= 1; b++ ) {
|
||||
maybeExplore(out, cache, df::coord(pos.x+a,pos.y+b,pos.z), jobLocations);
|
||||
}
|
||||
}
|
||||
cache.trash();
|
||||
}
|
||||
|
||||
void maybeExplore(color_ostream& out, MapExtras::MapCache& cache, df::coord pt, set<df::coord>& jobLocations) {
|
||||
if ( !Maps::isValidTilePos(pt) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
df::map_block* block = Maps::getTileBlock(pt);
|
||||
if (!block)
|
||||
return;
|
||||
|
||||
if ( block->designation[pt.x&0xF][pt.y&0xF].bits.hidden )
|
||||
return;
|
||||
|
||||
df::tiletype type = block->tiletype[pt.x&0xF][pt.y&0xF];
|
||||
if ( ENUM_ATTR(tiletype, material, type) != df::enums::tiletype_material::MINERAL )
|
||||
return;
|
||||
if ( ENUM_ATTR(tiletype, shape, type) != df::enums::tiletype_shape::WALL )
|
||||
return;
|
||||
|
||||
if ( block->designation[pt.x&0xF][pt.y&0xF].bits.dig != df::enums::tile_dig_designation::No )
|
||||
return;
|
||||
|
||||
uint32_t xMax,yMax,zMax;
|
||||
Maps::getSize(xMax,yMax,zMax);
|
||||
if ( pt.x == 0 || pt.y == 0 || pt.x+1 == xMax*16 || pt.y+1 == yMax*16 )
|
||||
return;
|
||||
if ( jobLocations.find(pt) != jobLocations.end() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
int16_t mat = cache.veinMaterialAt(pt);
|
||||
if ( mat == -1 )
|
||||
return;
|
||||
if ( !digAll ) {
|
||||
df::inorganic_raw* inorganic = df::global::world->raws.inorganics[mat];
|
||||
if ( autodigMaterials.find(inorganic->id) == autodigMaterials.end() ) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
block->designation[pt.x&0xF][pt.y&0xF].bits.dig = df::enums::tile_dig_designation::Default;
|
||||
block->flags.bits.designated = true;
|
||||
// *df::global::process_dig = true;
|
||||
// *df::global::process_jobs = true;
|
||||
}
|
||||
|
||||
command_result digSmart (color_ostream &out, std::vector <std::string> & parameters)
|
||||
{
|
||||
bool adding = true;
|
||||
set<string> toAdd, toRemove;
|
||||
for ( size_t a = 0; a < parameters.size(); a++ ) {
|
||||
int32_t i = (int32_t)strtol(parameters[a].c_str(), NULL, 0);
|
||||
if ( i == 0 && parameters[a] == "0" ) {
|
||||
EventManager::unregisterAll(plugin_self);
|
||||
adding = false;
|
||||
continue;
|
||||
} else if ( i == 1 ) {
|
||||
EventManager::unregisterAll(plugin_self);
|
||||
EventManager::registerListener(EventManager::EventType::JOB_COMPLETED, digHandler, plugin_self);
|
||||
adding = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( parameters[a] == "CLEAR" )
|
||||
autodigMaterials.clear();
|
||||
|
||||
if ( parameters[a] == "digAll0" ) {
|
||||
digAll = false;
|
||||
continue;
|
||||
}
|
||||
if ( parameters[a] == "digAll1" ) {
|
||||
digAll = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
for ( size_t b = 0; b < df::global::world->raws.inorganics.size(); b++ ) {
|
||||
df::inorganic_raw* inorganic = df::global::world->raws.inorganics[b];
|
||||
if ( parameters[a] == inorganic->id ) {
|
||||
if ( adding )
|
||||
toAdd.insert(parameters[a]);
|
||||
else
|
||||
toRemove.insert(parameters[a]);
|
||||
goto loop;
|
||||
}
|
||||
}
|
||||
|
||||
out.print("Could not find material \"%s\".\n", parameters[a].c_str());
|
||||
return CR_WRONG_USAGE;
|
||||
|
||||
loop: continue;
|
||||
}
|
||||
|
||||
autodigMaterials.insert(toAdd.begin(), toAdd.end());
|
||||
for ( auto a = toRemove.begin(); a != toRemove.end(); a++ )
|
||||
autodigMaterials.erase(*a);
|
||||
|
||||
return CR_OK;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,327 @@
|
||||
#include "Core.h"
|
||||
#include <Console.h>
|
||||
#include <Export.h>
|
||||
#include <PluginManager.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <VTableInterpose.h>
|
||||
|
||||
#include "df/building_workshopst.h"
|
||||
|
||||
#include "df/unit.h"
|
||||
#include "df/item.h"
|
||||
#include "df/item_actual.h"
|
||||
#include "df/unit_wound.h"
|
||||
#include "df/world.h"
|
||||
#include "df/reaction.h"
|
||||
#include "df/reaction_reagent_itemst.h"
|
||||
#include "df/reaction_product_itemst.h"
|
||||
|
||||
#include "df/proj_itemst.h"
|
||||
#include "df/proj_unitst.h"
|
||||
|
||||
#include "MiscUtils.h"
|
||||
#include "LuaTools.h"
|
||||
|
||||
using std::vector;
|
||||
using std::string;
|
||||
using std::stack;
|
||||
using namespace DFHack;
|
||||
using namespace df::enums;
|
||||
|
||||
using df::global::gps;
|
||||
using df::global::world;
|
||||
using df::global::ui;
|
||||
|
||||
typedef df::reaction_product_itemst item_product;
|
||||
|
||||
DFHACK_PLUGIN("eventful");
|
||||
|
||||
struct ReagentSource {
|
||||
int idx;
|
||||
df::reaction_reagent *reagent;
|
||||
|
||||
ReagentSource() : idx(-1), reagent(NULL) {}
|
||||
};
|
||||
|
||||
struct MaterialSource : ReagentSource {
|
||||
bool product;
|
||||
std::string product_name;
|
||||
|
||||
int mat_type, mat_index;
|
||||
|
||||
MaterialSource() : product(false), mat_type(-1), mat_index(-1) {}
|
||||
};
|
||||
|
||||
struct ProductInfo {
|
||||
df::reaction *react;
|
||||
item_product *product;
|
||||
|
||||
MaterialSource material;
|
||||
|
||||
bool isValid() {
|
||||
return true;//due to mat_type being -1 = any
|
||||
}
|
||||
};
|
||||
|
||||
struct ReactionInfo {
|
||||
df::reaction *react;
|
||||
|
||||
std::vector<ProductInfo> products;
|
||||
};
|
||||
|
||||
static std::map<std::string, ReactionInfo> reactions;
|
||||
static std::map<df::reaction_product*, ProductInfo*> products;
|
||||
|
||||
static ReactionInfo *find_reaction(const std::string &name)
|
||||
{
|
||||
auto it = reactions.find(name);
|
||||
return (it != reactions.end()) ? &it->second : NULL;
|
||||
}
|
||||
|
||||
static bool is_lua_hook(const std::string &name)
|
||||
{
|
||||
return name.size() > 9 && memcmp(name.data(), "LUA_HOOK_", 9) == 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Hooks
|
||||
*/
|
||||
static void handle_fillsidebar(color_ostream &out,df::building_workshopst*,bool *call_native){};
|
||||
static void handle_postfillsidebar(color_ostream &out,df::building_workshopst*){};
|
||||
|
||||
static void handle_reaction_done(color_ostream &out,df::reaction*, df::unit *unit, std::vector<df::item*> *in_items,std::vector<df::reaction_reagent*> *in_reag
|
||||
, std::vector<df::item*> *out_items,bool *call_native){};
|
||||
static void handle_contaminate_wound(color_ostream &out,df::item_actual*,df::unit* unit, df::unit_wound* wound, uint8_t a1, int16_t a2){};
|
||||
static void handle_projitem_ci(color_ostream &out,df::proj_itemst*,bool){};
|
||||
static void handle_projitem_cm(color_ostream &out,df::proj_itemst*){};
|
||||
static void handle_projunit_ci(color_ostream &out,df::proj_unitst*,bool){};
|
||||
static void handle_projunit_cm(color_ostream &out,df::proj_unitst*){};
|
||||
|
||||
DEFINE_LUA_EVENT_2(onWorkshopFillSidebarMenu, handle_fillsidebar, df::building_workshopst*,bool* );
|
||||
DEFINE_LUA_EVENT_1(postWorkshopFillSidebarMenu, handle_postfillsidebar, df::building_workshopst*);
|
||||
|
||||
DEFINE_LUA_EVENT_6(onReactionComplete, handle_reaction_done,df::reaction*, df::unit *, std::vector<df::item*> *,std::vector<df::reaction_reagent*> *,std::vector<df::item*> *,bool *);
|
||||
DEFINE_LUA_EVENT_5(onItemContaminateWound, handle_contaminate_wound, df::item_actual*,df::unit* , df::unit_wound* , uint8_t , int16_t );
|
||||
//projectiles
|
||||
DEFINE_LUA_EVENT_2(onProjItemCheckImpact, handle_projitem_ci, df::proj_itemst*,bool );
|
||||
DEFINE_LUA_EVENT_1(onProjItemCheckMovement, handle_projitem_cm, df::proj_itemst*);
|
||||
DEFINE_LUA_EVENT_2(onProjUnitCheckImpact, handle_projunit_ci, df::proj_unitst*,bool );
|
||||
DEFINE_LUA_EVENT_1(onProjUnitCheckMovement, handle_projunit_cm, df::proj_unitst* );
|
||||
|
||||
DFHACK_PLUGIN_LUA_EVENTS {
|
||||
DFHACK_LUA_EVENT(onWorkshopFillSidebarMenu),
|
||||
DFHACK_LUA_EVENT(postWorkshopFillSidebarMenu),
|
||||
DFHACK_LUA_EVENT(onReactionComplete),
|
||||
DFHACK_LUA_EVENT(onItemContaminateWound),
|
||||
DFHACK_LUA_EVENT(onProjItemCheckImpact),
|
||||
DFHACK_LUA_EVENT(onProjItemCheckMovement),
|
||||
DFHACK_LUA_EVENT(onProjUnitCheckImpact),
|
||||
DFHACK_LUA_EVENT(onProjUnitCheckMovement),
|
||||
DFHACK_LUA_END
|
||||
};
|
||||
struct workshop_hook : df::building_workshopst{
|
||||
typedef df::building_workshopst interpose_base;
|
||||
DEFINE_VMETHOD_INTERPOSE(void,fillSidebarMenu,())
|
||||
{
|
||||
CoreSuspendClaimer suspend;
|
||||
color_ostream_proxy out(Core::getInstance().getConsole());
|
||||
bool call_native=true;
|
||||
onWorkshopFillSidebarMenu(out,this,&call_native);
|
||||
if(call_native)
|
||||
INTERPOSE_NEXT(fillSidebarMenu)();
|
||||
postWorkshopFillSidebarMenu(out,this);
|
||||
}
|
||||
};
|
||||
IMPLEMENT_VMETHOD_INTERPOSE(workshop_hook, fillSidebarMenu);
|
||||
struct product_hook : item_product {
|
||||
typedef item_product interpose_base;
|
||||
|
||||
DEFINE_VMETHOD_INTERPOSE(
|
||||
void, produce,
|
||||
(df::unit *unit, std::vector<df::item*> *out_items,
|
||||
std::vector<df::reaction_reagent*> *in_reag,
|
||||
std::vector<df::item*> *in_items,
|
||||
int32_t quantity, df::job_skill skill,
|
||||
df::historical_entity *entity, df::world_site *site)
|
||||
) {
|
||||
if (auto product = products[this])
|
||||
{
|
||||
df::reaction* this_reaction=product->react;
|
||||
CoreSuspendClaimer suspend;
|
||||
color_ostream_proxy out(Core::getInstance().getConsole());
|
||||
bool call_native=true;
|
||||
onReactionComplete(out,this_reaction,unit,in_items,in_reag,out_items,&call_native);
|
||||
if(!call_native)
|
||||
return;
|
||||
}
|
||||
|
||||
INTERPOSE_NEXT(produce)(unit, out_items, in_reag, in_items, quantity, skill, entity, site);
|
||||
}
|
||||
};
|
||||
|
||||
IMPLEMENT_VMETHOD_INTERPOSE(product_hook, produce);
|
||||
|
||||
|
||||
struct item_hooks :df::item_actual {
|
||||
typedef df::item_actual interpose_base;
|
||||
|
||||
DEFINE_VMETHOD_INTERPOSE(void, contaminateWound,(df::unit* unit, df::unit_wound* wound, uint8_t a1, int16_t a2))
|
||||
{
|
||||
CoreSuspendClaimer suspend;
|
||||
color_ostream_proxy out(Core::getInstance().getConsole());
|
||||
onItemContaminateWound(out,this,unit,wound,a1,a2);
|
||||
INTERPOSE_NEXT(contaminateWound)(unit,wound,a1,a2);
|
||||
}
|
||||
|
||||
};
|
||||
IMPLEMENT_VMETHOD_INTERPOSE(item_hooks, contaminateWound);
|
||||
|
||||
struct proj_item_hook: df::proj_itemst{
|
||||
typedef df::proj_itemst interpose_base;
|
||||
DEFINE_VMETHOD_INTERPOSE(bool,checkImpact,(bool mode))
|
||||
{
|
||||
CoreSuspendClaimer suspend;
|
||||
color_ostream_proxy out(Core::getInstance().getConsole());
|
||||
onProjItemCheckImpact(out,this,mode);
|
||||
return INTERPOSE_NEXT(checkImpact)(mode); //returns destroy item or not?
|
||||
}
|
||||
DEFINE_VMETHOD_INTERPOSE(bool,checkMovement,())
|
||||
{
|
||||
CoreSuspendClaimer suspend;
|
||||
color_ostream_proxy out(Core::getInstance().getConsole());
|
||||
onProjItemCheckMovement(out,this);
|
||||
return INTERPOSE_NEXT(checkMovement)();
|
||||
}
|
||||
};
|
||||
IMPLEMENT_VMETHOD_INTERPOSE(proj_item_hook,checkImpact);
|
||||
IMPLEMENT_VMETHOD_INTERPOSE(proj_item_hook,checkMovement);
|
||||
|
||||
struct proj_unit_hook: df::proj_unitst{
|
||||
typedef df::proj_unitst interpose_base;
|
||||
DEFINE_VMETHOD_INTERPOSE(bool,checkImpact,(bool mode))
|
||||
{
|
||||
CoreSuspendClaimer suspend;
|
||||
color_ostream_proxy out(Core::getInstance().getConsole());
|
||||
onProjUnitCheckImpact(out,this,mode);
|
||||
return INTERPOSE_NEXT(checkImpact)(mode); //returns destroy item or not?
|
||||
}
|
||||
DEFINE_VMETHOD_INTERPOSE(bool,checkMovement,())
|
||||
{
|
||||
CoreSuspendClaimer suspend;
|
||||
color_ostream_proxy out(Core::getInstance().getConsole());
|
||||
onProjUnitCheckMovement(out,this);
|
||||
return INTERPOSE_NEXT(checkMovement)();
|
||||
}
|
||||
};
|
||||
IMPLEMENT_VMETHOD_INTERPOSE(proj_unit_hook,checkImpact);
|
||||
IMPLEMENT_VMETHOD_INTERPOSE(proj_unit_hook,checkMovement);
|
||||
/*
|
||||
* Scan raws for matching reactions.
|
||||
*/
|
||||
|
||||
|
||||
static void parse_product(
|
||||
color_ostream &out, ProductInfo &info, df::reaction *react, item_product *prod
|
||||
) {
|
||||
info.react = react;
|
||||
info.product = prod;
|
||||
info.material.mat_type = prod->mat_type;
|
||||
info.material.mat_index = prod->mat_index;
|
||||
}
|
||||
|
||||
static bool find_reactions(color_ostream &out)
|
||||
{
|
||||
reactions.clear();
|
||||
|
||||
auto &rlist = world->raws.reactions;
|
||||
|
||||
for (size_t i = 0; i < rlist.size(); i++)
|
||||
{
|
||||
if (!is_lua_hook(rlist[i]->code))
|
||||
continue;
|
||||
reactions[rlist[i]->code].react = rlist[i];
|
||||
}
|
||||
|
||||
for (auto it = reactions.begin(); it != reactions.end(); ++it)
|
||||
{
|
||||
auto &prod = it->second.react->products;
|
||||
auto &out_prod = it->second.products;
|
||||
|
||||
for (size_t i = 0; i < prod.size(); i++)
|
||||
{
|
||||
auto itprod = strict_virtual_cast<item_product>(prod[i]);
|
||||
if (!itprod) continue;
|
||||
|
||||
out_prod.push_back(ProductInfo());
|
||||
parse_product(out, out_prod.back(), it->second.react, itprod);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < prod.size(); i++)
|
||||
{
|
||||
if (out_prod[i].isValid())
|
||||
products[out_prod[i].product] = &out_prod[i];
|
||||
}
|
||||
}
|
||||
|
||||
return !products.empty();
|
||||
}
|
||||
|
||||
static void enable_hooks(bool enable)
|
||||
{
|
||||
INTERPOSE_HOOK(workshop_hook,fillSidebarMenu).apply(enable);
|
||||
INTERPOSE_HOOK(item_hooks,contaminateWound).apply(enable);
|
||||
INTERPOSE_HOOK(proj_unit_hook,checkImpact).apply(enable);
|
||||
INTERPOSE_HOOK(proj_unit_hook,checkMovement).apply(enable);
|
||||
INTERPOSE_HOOK(proj_item_hook,checkImpact).apply(enable);
|
||||
INTERPOSE_HOOK(proj_item_hook,checkMovement).apply(enable);
|
||||
}
|
||||
static void world_specific_hooks(color_ostream &out,bool enable)
|
||||
{
|
||||
if(enable && find_reactions(out))
|
||||
{
|
||||
out.print("Detected reaction hooks - enabling plugin.\n");
|
||||
INTERPOSE_HOOK(product_hook, produce).apply(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
INTERPOSE_HOOK(product_hook, produce).apply(false);
|
||||
reactions.clear();
|
||||
products.clear();
|
||||
}
|
||||
}
|
||||
void disable_all_hooks(color_ostream &out)
|
||||
{
|
||||
world_specific_hooks(out,false);
|
||||
enable_hooks(false);
|
||||
}
|
||||
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event)
|
||||
{
|
||||
switch (event) {
|
||||
case SC_WORLD_LOADED:
|
||||
world_specific_hooks(out,true);
|
||||
break;
|
||||
case SC_WORLD_UNLOADED:
|
||||
world_specific_hooks(out,false);
|
||||
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
|
||||
{
|
||||
if (Core::getInstance().isWorldLoaded())
|
||||
plugin_onstatechange(out, SC_WORLD_LOADED);
|
||||
enable_hooks(true);
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
|
||||
{
|
||||
disable_all_hooks(out);
|
||||
return CR_OK;
|
||||
}
|
@ -0,0 +1,182 @@
|
||||
|
||||
#include "Core.h"
|
||||
#include "Console.h"
|
||||
#include "DataDefs.h"
|
||||
#include "Export.h"
|
||||
#include "PluginManager.h"
|
||||
|
||||
#include "modules/World.h"
|
||||
|
||||
#include "df/construction.h"
|
||||
#include "df/game_mode.h"
|
||||
#include "df/map_block.h"
|
||||
#include "df/map_block_column.h"
|
||||
#include "df/world.h"
|
||||
#include "df/z_level_flags.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using namespace std;
|
||||
|
||||
using namespace DFHack;
|
||||
using namespace df::enums;
|
||||
|
||||
command_result infiniteSky (color_ostream &out, std::vector <std::string> & parameters);
|
||||
|
||||
DFHACK_PLUGIN("infiniteSky");
|
||||
|
||||
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
|
||||
{
|
||||
commands.push_back(PluginCommand(
|
||||
"infiniteSky",
|
||||
"Creates new sky levels on request, or as you construct up.",
|
||||
infiniteSky, false,
|
||||
"Usage:\n"
|
||||
" infiniteSky\n"
|
||||
" creates one more z-level\n"
|
||||
" infiniteSky [n]\n"
|
||||
" creates n more z-level(s)\n"
|
||||
" infiniteSky enable\n"
|
||||
" enables monitoring of constructions\n"
|
||||
" infiniteSky disable\n"
|
||||
" disable monitoring of constructions\n"
|
||||
"\n"
|
||||
"If construction monitoring is enabled, then the plugin will automatically create new sky z-levels as you construct upward.\n"
|
||||
));
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
|
||||
{
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event)
|
||||
{
|
||||
switch (event) {
|
||||
case SC_GAME_LOADED:
|
||||
// initialize from the world just loaded
|
||||
break;
|
||||
case SC_GAME_UNLOADED:
|
||||
// cleanup
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return CR_OK;
|
||||
}
|
||||
*/
|
||||
|
||||
static size_t constructionSize = 0;
|
||||
static bool enabled = false;
|
||||
void doInfiniteSky(color_ostream& out, int32_t howMany);
|
||||
|
||||
DFhackCExport command_result plugin_onupdate ( color_ostream &out )
|
||||
{
|
||||
if ( !enabled )
|
||||
return CR_OK;
|
||||
if ( !Core::getInstance().isMapLoaded() )
|
||||
return CR_OK;
|
||||
{
|
||||
t_gamemodes mode;
|
||||
if ( !World::ReadGameMode(mode) )
|
||||
return CR_FAILURE;
|
||||
if ( mode.g_mode != df::enums::game_mode::DWARF )
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
if ( df::global::world->constructions.size() == constructionSize )
|
||||
return CR_OK;
|
||||
int32_t zNow = df::global::world->map.z_count_block;
|
||||
for ( size_t a = constructionSize; a < df::global::world->constructions.size(); a++ ) {
|
||||
df::construction* construct = df::global::world->constructions[a];
|
||||
if ( construct->pos.z+2 < zNow )
|
||||
continue;
|
||||
doInfiniteSky(out, 1);
|
||||
zNow = df::global::world->map.z_count_block;
|
||||
///break;
|
||||
}
|
||||
constructionSize = df::global::world->constructions.size();
|
||||
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
void doInfiniteSky(color_ostream& out, int32_t howMany) {
|
||||
df::world* world = df::global::world;
|
||||
CoreSuspender suspend;
|
||||
int32_t x_count_block = world->map.x_count_block;
|
||||
int32_t y_count_block = world->map.y_count_block;
|
||||
for ( int32_t count = 0; count < howMany; count++ ) {
|
||||
//change the size of the pointer stuff
|
||||
int32_t z_count_block = world->map.z_count_block;
|
||||
df::map_block**** block_index = world->map.block_index;
|
||||
for ( int32_t a = 0; a < x_count_block; a++ ) {
|
||||
for ( int32_t b = 0; b < y_count_block; b++ ) {
|
||||
df::map_block** blockColumn = new df::map_block*[z_count_block+1];
|
||||
memcpy(blockColumn, block_index[a][b], z_count_block*sizeof(df::map_block*));
|
||||
blockColumn[z_count_block] = NULL;
|
||||
delete[] block_index[a][b];
|
||||
block_index[a][b] = blockColumn;
|
||||
|
||||
//deal with map_block_column stuff even though it'd probably be fine
|
||||
df::map_block_column* column = world->map.column_index[a][b];
|
||||
if ( !column ) {
|
||||
out.print("%s, line %d: column is null (%d, %d).\n", __FILE__, __LINE__, a, b);
|
||||
continue;
|
||||
}
|
||||
df::map_block_column::T_unmined_glyphs* glyphs = new df::map_block_column::T_unmined_glyphs;
|
||||
glyphs->x[0] = 0;
|
||||
glyphs->x[1] = 1;
|
||||
glyphs->x[2] = 2;
|
||||
glyphs->x[3] = 3;
|
||||
glyphs->y[0] = 0;
|
||||
glyphs->y[1] = 0;
|
||||
glyphs->y[2] = 0;
|
||||
glyphs->y[3] = 0;
|
||||
glyphs->tile[0] = 'e';
|
||||
glyphs->tile[1] = 'x';
|
||||
glyphs->tile[2] = 'p';
|
||||
glyphs->tile[3] = '^';
|
||||
column->unmined_glyphs.push_back(glyphs);
|
||||
}
|
||||
}
|
||||
df::z_level_flags* flags = new df::z_level_flags[z_count_block+1];
|
||||
memcpy(flags, world->map.z_level_flags, z_count_block*sizeof(df::z_level_flags));
|
||||
flags[z_count_block].whole = 0;
|
||||
flags[z_count_block].bits.update = 1;
|
||||
world->map.z_count_block++;
|
||||
world->map.z_count++;
|
||||
delete[] world->map.z_level_flags;
|
||||
world->map.z_level_flags = flags;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
command_result infiniteSky (color_ostream &out, std::vector <std::string> & parameters)
|
||||
{
|
||||
if ( parameters.size() > 1 )
|
||||
return CR_WRONG_USAGE;
|
||||
if ( parameters.size() == 0 ) {
|
||||
out.print("Construction monitoring is %s.\n", enabled ? "enabled" : "disabled");
|
||||
return CR_OK;
|
||||
}
|
||||
if (parameters[0] == "enable") {
|
||||
enabled = true;
|
||||
out.print("Construction monitoring enabled.\n");
|
||||
return CR_OK;
|
||||
}
|
||||
if (parameters[0] == "disable") {
|
||||
enabled = false;
|
||||
out.print("Construction monitoring disabled.\n");
|
||||
constructionSize = 0;
|
||||
return CR_OK;
|
||||
}
|
||||
int32_t howMany = 0;
|
||||
howMany = atoi(parameters[0].c_str());
|
||||
out.print("InfiniteSky: creating %d new z-level%s of sky.\n", howMany, howMany == 1 ? "" : "s" );
|
||||
doInfiniteSky(out, howMany);
|
||||
return CR_OK;
|
||||
}
|
@ -0,0 +1 @@
|
||||
Subproject commit aa3b1bd51f269c07b3235392fd7ed21fe9171f3f
|
@ -0,0 +1,366 @@
|
||||
// This is a generic plugin that does nothing useful apart from acting as an example... of a plugin that does nothing :D
|
||||
|
||||
// some headers required for a plugin. Nothing special, just the basics.
|
||||
#include "Core.h"
|
||||
#include <Console.h>
|
||||
#include <Export.h>
|
||||
#include <PluginManager.h>
|
||||
|
||||
// DF data structure definition headers
|
||||
#include "DataDefs.h"
|
||||
#include "df/world.h"
|
||||
#include "df/map_block.h"
|
||||
#include "df/builtin_mats.h"
|
||||
#include "df/tile_designation.h"
|
||||
|
||||
//DFhack specific headers
|
||||
#include "modules/Maps.h"
|
||||
#include "modules/MapCache.h"
|
||||
#include "modules/Materials.h"
|
||||
|
||||
//Needed for writing the protobuff stuff to a file.
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <iomanip>
|
||||
|
||||
#include "isoworldremote.pb.h"
|
||||
|
||||
#include "RemoteServer.h"
|
||||
|
||||
using namespace DFHack;
|
||||
using namespace df::enums;
|
||||
using namespace isoworldremote;
|
||||
|
||||
|
||||
// Here go all the command declarations...
|
||||
// mostly to allow having the mandatory stuff on top of the file and commands on the bottom
|
||||
command_result isoWorldRemote (color_ostream &out, std::vector <std::string> & parameters);
|
||||
|
||||
static command_result GetEmbarkTile(color_ostream &stream, const TileRequest *in, EmbarkTile *out);
|
||||
static command_result GetEmbarkInfo(color_ostream &stream, const MapRequest *in, MapReply *out);
|
||||
static command_result GetRawNames(color_ostream &stream, const MapRequest *in, RawNames *out);
|
||||
|
||||
bool gather_embark_tile_layer(int EmbX, int EmbY, int EmbZ, EmbarkTileLayer * tile, MapExtras::MapCache * MP);
|
||||
bool gather_embark_tile(int EmbX, int EmbY, EmbarkTile * tile, MapExtras::MapCache * MP);
|
||||
|
||||
|
||||
// A plugin must be able to return its name and version.
|
||||
// The name string provided must correspond to the filename - skeleton.plug.so or skeleton.plug.dll in this case
|
||||
DFHACK_PLUGIN("isoworldremote");
|
||||
|
||||
// Mandatory init function. If you have some global state, create it here.
|
||||
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
|
||||
{
|
||||
//// Fill the command list with your commands.
|
||||
//commands.push_back(PluginCommand(
|
||||
// "isoworldremote", "Dump north-west embark tile to text file for debug purposes.",
|
||||
// isoWorldRemote, false, /* true means that the command can't be used from non-interactive user interface */
|
||||
// // Extended help string. Used by CR_WRONG_USAGE and the help command:
|
||||
// " This command does nothing at all.\n"
|
||||
// "Example:\n"
|
||||
// " isoworldremote\n"
|
||||
// " Does nothing.\n"
|
||||
//));
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
DFhackCExport RPCService *plugin_rpcconnect(color_ostream &)
|
||||
{
|
||||
RPCService *svc = new RPCService();
|
||||
svc->addFunction("GetEmbarkTile", GetEmbarkTile);
|
||||
svc->addFunction("GetEmbarkInfo", GetEmbarkInfo);
|
||||
svc->addFunction("GetRawNames", GetRawNames);
|
||||
return svc;
|
||||
}
|
||||
|
||||
// This is called right before the plugin library is removed from memory.
|
||||
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
|
||||
{
|
||||
// You *MUST* kill all threads you created before this returns.
|
||||
// If everything fails, just return CR_FAILURE. Your plugin will be
|
||||
// in a zombie state, but things won't crash.
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
// Called to notify the plugin about important state changes.
|
||||
// Invoked with DF suspended, and always before the matching plugin_onupdate.
|
||||
// More event codes may be added in the future.
|
||||
/*
|
||||
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event)
|
||||
{
|
||||
switch (event) {
|
||||
case SC_GAME_LOADED:
|
||||
// initialize from the world just loaded
|
||||
break;
|
||||
case SC_GAME_UNLOADED:
|
||||
// cleanup
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return CR_OK;
|
||||
}
|
||||
*/
|
||||
|
||||
// Whatever you put here will be done in each game step. Don't abuse it.
|
||||
// It's optional, so you can just comment it out like this if you don't need it.
|
||||
/*
|
||||
DFhackCExport command_result plugin_onupdate ( color_ostream &out )
|
||||
{
|
||||
// whetever. You don't need to suspend DF execution here.
|
||||
return CR_OK;
|
||||
}
|
||||
*/
|
||||
|
||||
//// A command! It sits around and looks pretty. And it's nice and friendly.
|
||||
//command_result isoWorldRemote (color_ostream &out, std::vector <std::string> & parameters)
|
||||
//{
|
||||
// // It's nice to print a help message you get invalid options
|
||||
// // from the user instead of just acting strange.
|
||||
// // This can be achieved by adding the extended help string to the
|
||||
// // PluginCommand registration as show above, and then returning
|
||||
// // CR_WRONG_USAGE from the function. The same string will also
|
||||
// // be used by 'help your-command'.
|
||||
// if (!parameters.empty())
|
||||
// return CR_WRONG_USAGE;
|
||||
// // Commands are called from threads other than the DF one.
|
||||
// // Suspend this thread until DF has time for us. If you
|
||||
// // use CoreSuspender, it'll automatically resume DF when
|
||||
// // execution leaves the current scope.
|
||||
// CoreSuspender suspend;
|
||||
// // Actually do something here. Yay.
|
||||
// out.print("Doing a test...\n");
|
||||
// MapExtras::MapCache MC;
|
||||
// EmbarkTile test_tile;
|
||||
// if(!gather_embark_tile(0,0, &test_tile, &MC))
|
||||
// return CR_FAILURE;
|
||||
// //test-write the file to check it.
|
||||
// std::ofstream output_file("tile.p", std::ios_base::binary);
|
||||
// output_file << test_tile.SerializeAsString();
|
||||
// output_file.close();
|
||||
//
|
||||
// //load it again to verify.
|
||||
// std::ifstream input_file("tile.p", std::ios_base::binary);
|
||||
// std::string input_string( (std::istreambuf_iterator<char>(input_file) ),
|
||||
// (std::istreambuf_iterator<char>() ) );
|
||||
// EmbarkTile verify_tile;
|
||||
// verify_tile.ParseFromString(input_string);
|
||||
// //write contents to text file.
|
||||
// std::ofstream debug_text("tile.txt", std::ios_base::trunc);
|
||||
// debug_text << "world coords:" << verify_tile.world_x()<< "," << verify_tile.world_y()<< "," << verify_tile.world_z() << std::endl;
|
||||
// for(int i = 0; i < verify_tile.tile_layer_size(); i++) {
|
||||
// debug_text << "layer: " << i << std::endl;
|
||||
// for(int j = 0; j < 48; j++) {
|
||||
// debug_text << " ";
|
||||
// for(int k = 0; k < 48; k++) {
|
||||
// debug_text << verify_tile.tile_layer(i).mat_type_table(j*48+k) << ",";
|
||||
// }
|
||||
// debug_text << " ";
|
||||
// for(int k = 0; k < 48; k++) {
|
||||
// debug_text << std::setw(3) << verify_tile.tile_layer(i).mat_subtype_table(j*48+k) << ",";
|
||||
// }
|
||||
// debug_text << std::endl;
|
||||
// }
|
||||
// debug_text << std::endl;
|
||||
// }
|
||||
// // Give control back to DF.
|
||||
// return CR_OK;
|
||||
//}
|
||||
|
||||
static command_result GetEmbarkTile(color_ostream &stream, const TileRequest *in, EmbarkTile *out)
|
||||
{
|
||||
MapExtras::MapCache MC;
|
||||
gather_embark_tile(in->want_x() * 3, in->want_y() * 3, out, &MC);
|
||||
MC.trash();
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
static command_result GetEmbarkInfo(color_ostream &stream, const MapRequest *in, MapReply *out)
|
||||
{
|
||||
if(!Core::getInstance().isWorldLoaded()) {
|
||||
out->set_available(false);
|
||||
return CR_OK;
|
||||
}
|
||||
if(!Core::getInstance().isMapLoaded()) {
|
||||
out->set_available(false);
|
||||
return CR_OK;
|
||||
}
|
||||
if(!df::global::gamemode) {
|
||||
out->set_available(false);
|
||||
return CR_OK;
|
||||
}
|
||||
if((*df::global::gamemode != game_mode::ADVENTURE) && (*df::global::gamemode != game_mode::DWARF)) {
|
||||
out->set_available(false);
|
||||
return CR_OK;
|
||||
}
|
||||
if(!DFHack::Maps::IsValid()) {
|
||||
out->set_available(false);
|
||||
return CR_OK;
|
||||
}
|
||||
if(!in->has_save_folder()) { //probably should send the stuff anyway, but nah.
|
||||
out->set_available(false);
|
||||
return CR_OK;
|
||||
}
|
||||
if(!(in->save_folder() == df::global::world->cur_savegame.save_dir)) { //isoworld has a different map loaded, don't bother trying to load tiles for it, we don't have them.
|
||||
out->set_available(false);
|
||||
return CR_OK;
|
||||
}
|
||||
out->set_available(true);
|
||||
out->set_current_year(*df::global::cur_year);
|
||||
out->set_current_season(*df::global::cur_season);
|
||||
out->set_region_x(df::global::world->map.region_x);
|
||||
out->set_region_y(df::global::world->map.region_y);
|
||||
out->set_region_size_x(df::global::world->map.x_count_block / 3);
|
||||
out->set_region_size_y(df::global::world->map.y_count_block / 3);
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
int coord_to_index_48(int x, int y) {
|
||||
return y*48+x;
|
||||
}
|
||||
|
||||
bool gather_embark_tile(int EmbX, int EmbY, EmbarkTile * tile, MapExtras::MapCache * MP) {
|
||||
tile->set_is_valid(false);
|
||||
tile->set_world_x(df::global::world->map.region_x + (EmbX/3));
|
||||
tile->set_world_y(df::global::world->map.region_y + (EmbY/3));
|
||||
tile->set_world_z(df::global::world->map.region_z + 1); //adding one because floors get shifted one downwards.
|
||||
tile->set_current_year(*df::global::cur_year);
|
||||
tile->set_current_season(*df::global::cur_season);
|
||||
int num_valid_layers = 0;
|
||||
for(int z = 0; z < MP->maxZ(); z++)
|
||||
{
|
||||
EmbarkTileLayer * tile_layer = tile->add_tile_layer();
|
||||
num_valid_layers += gather_embark_tile_layer(EmbX, EmbY, z, tile_layer, MP);
|
||||
}
|
||||
if(num_valid_layers > 0)
|
||||
tile->set_is_valid(true);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
bool gather_embark_tile_layer(int EmbX, int EmbY, int EmbZ, EmbarkTileLayer * tile, MapExtras::MapCache * MP)
|
||||
{
|
||||
for(int i = tile->mat_type_table_size(); i < 2304; i++) { //This is needed so we have a full array to work with, otherwise the size isn't updated correctly.
|
||||
tile->add_mat_type_table(AIR);
|
||||
tile->add_mat_subtype_table(0);
|
||||
}
|
||||
int num_valid_blocks = 0;
|
||||
for(int yy = 0; yy < 3; yy++) {
|
||||
for(int xx = 0; xx < 3; xx++) {
|
||||
DFCoord current_coord, upper_coord;
|
||||
current_coord.x = EmbX+xx;
|
||||
current_coord.y = EmbY+yy;
|
||||
current_coord.z = EmbZ;
|
||||
upper_coord = current_coord;
|
||||
upper_coord.z += 1;
|
||||
MapExtras::Block * b = MP->BlockAt(current_coord);
|
||||
MapExtras::Block * b_upper = MP->BlockAt(upper_coord);
|
||||
if(b && b->getRaw()) {
|
||||
for(int block_y=0; block_y<16; block_y++) {
|
||||
for(int block_x=0; block_x<16; block_x++) {
|
||||
df::coord2d block_coord;
|
||||
block_coord.x = block_x;
|
||||
block_coord.y = block_y;
|
||||
df::tiletype tile_type = b->tiletypeAt(block_coord);
|
||||
df::tiletype upper_tile = df::tiletype::Void;
|
||||
if(b_upper && b_upper->getRaw()) {
|
||||
upper_tile = b_upper->tiletypeAt(block_coord);
|
||||
}
|
||||
df::tile_designation designation = b->DesignationAt(block_coord);
|
||||
DFHack::t_matpair actual_mat;
|
||||
if(tileShapeBasic(tileShape(upper_tile)) == tiletype_shape_basic::Floor && (tileMaterial(tile_type) != tiletype_material::FROZEN_LIQUID) && (tileMaterial(tile_type) != tiletype_material::BROOK)) { //if the upper tile is a floor, use that material instead. Unless it's ice.
|
||||
actual_mat = b_upper->staticMaterialAt(block_coord);
|
||||
}
|
||||
else {
|
||||
actual_mat = b->staticMaterialAt(block_coord);
|
||||
}
|
||||
if(((tileMaterial(tile_type) == tiletype_material::FROZEN_LIQUID) || (tileMaterial(tile_type) == tiletype_material::BROOK)) && (tileShapeBasic(tileShape(tile_type)) == tiletype_shape_basic::Floor)) {
|
||||
tile_type = tiletype::OpenSpace;
|
||||
}
|
||||
unsigned int array_index = coord_to_index_48(xx*16+block_x, yy*16+block_y);
|
||||
//make a new fake material at the given index
|
||||
if(tileMaterial(tile_type) == tiletype_material::FROZEN_LIQUID && !((tileShapeBasic(tileShape(upper_tile)) == tiletype_shape_basic::Floor) && (tileMaterial(upper_tile) != tiletype_material::FROZEN_LIQUID))) { //Ice.
|
||||
tile->set_mat_type_table(array_index, BasicMaterial::LIQUID); //Ice is totally a liquid, shut up.
|
||||
tile->set_mat_subtype_table(array_index, LiquidType::ICE);
|
||||
num_valid_blocks++;
|
||||
}
|
||||
else if(designation.bits.flow_size && (tileShapeBasic(tileShape(upper_tile)) != tiletype_shape_basic::Floor)) { //Contains either water or lava.
|
||||
tile->set_mat_type_table(array_index, BasicMaterial::LIQUID);
|
||||
if(designation.bits.liquid_type) //Magma
|
||||
tile->set_mat_subtype_table(array_index, LiquidType::MAGMA);
|
||||
else //water
|
||||
tile->set_mat_subtype_table(array_index, LiquidType::WATER);
|
||||
num_valid_blocks++;
|
||||
}
|
||||
else if(((tileShapeBasic(tileShape(tile_type)) != tiletype_shape_basic::Open) ||
|
||||
(tileShapeBasic(tileShape(upper_tile)) == tiletype_shape_basic::Floor)) &&
|
||||
((tileShapeBasic(tileShape(tile_type)) != tiletype_shape_basic::Floor) ||
|
||||
(tileShapeBasic(tileShape(upper_tile)) == tiletype_shape_basic::Floor))) { //if the upper tile is a floor, we don't skip, otherwise we do.
|
||||
if(actual_mat.mat_type == builtin_mats::INORGANIC) { //inorganic
|
||||
tile->set_mat_type_table(array_index, BasicMaterial::INORGANIC);
|
||||
tile->set_mat_subtype_table(array_index, actual_mat.mat_index);
|
||||
}
|
||||
else if(actual_mat.mat_type == 419) { //Growing plants
|
||||
tile->set_mat_type_table(array_index, BasicMaterial::PLANT);
|
||||
tile->set_mat_subtype_table(array_index, actual_mat.mat_index);
|
||||
}
|
||||
else if(actual_mat.mat_type >= 420) { //Wooden constructions. Different from growing plants.
|
||||
tile->set_mat_type_table(array_index, BasicMaterial::WOOD);
|
||||
tile->set_mat_subtype_table(array_index, actual_mat.mat_index);
|
||||
}
|
||||
else { //Unknown and unsupported stuff. Will just be drawn as grey.
|
||||
tile->set_mat_type_table(array_index, BasicMaterial::OTHER);
|
||||
tile->set_mat_subtype_table(array_index, actual_mat.mat_type);
|
||||
}
|
||||
num_valid_blocks++;
|
||||
}
|
||||
else {
|
||||
tile->set_mat_type_table(array_index, BasicMaterial::AIR);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return (num_valid_blocks >0);
|
||||
}
|
||||
|
||||
static command_result GetRawNames(color_ostream &stream, const MapRequest *in, RawNames *out){
|
||||
if(!Core::getInstance().isWorldLoaded()) {
|
||||
out->set_available(false);
|
||||
return CR_OK;
|
||||
}
|
||||
if(!Core::getInstance().isMapLoaded()) {
|
||||
out->set_available(false);
|
||||
return CR_OK;
|
||||
}
|
||||
if(!df::global::gamemode) {
|
||||
out->set_available(false);
|
||||
return CR_OK;
|
||||
}
|
||||
if((*df::global::gamemode != game_mode::ADVENTURE) && (*df::global::gamemode != game_mode::DWARF)) {
|
||||
out->set_available(false);
|
||||
return CR_OK;
|
||||
}
|
||||
if(!DFHack::Maps::IsValid()) {
|
||||
out->set_available(false);
|
||||
return CR_OK;
|
||||
}
|
||||
if(!in->has_save_folder()) { //probably should send the stuff anyway, but nah.
|
||||
out->set_available(false);
|
||||
return CR_OK;
|
||||
}
|
||||
if(!(in->save_folder() == df::global::world->cur_savegame.save_dir)) { //isoworld has a different map loaded, don't bother trying to load tiles for it, we don't have them.
|
||||
out->set_available(false);
|
||||
return CR_OK;
|
||||
}
|
||||
out->set_available(true);
|
||||
for(int i = 0; i < df::global::world->raws.inorganics.size(); i++){
|
||||
out->add_inorganic(df::global::world->raws.inorganics[i]->id);
|
||||
}
|
||||
|
||||
for(int i = 0; i < df::global::world->raws.plants.all.size(); i++){
|
||||
out->add_organic(df::global::world->raws.plants.all[i]->id);
|
||||
}
|
||||
return CR_OK;
|
||||
}
|
@ -0,0 +1,112 @@
|
||||
local _ENV = mkmodule('plugins.eventful')
|
||||
--[[
|
||||
|
||||
|
||||
--]]
|
||||
local function getShopName(btype,bsubtype,bcustom)
|
||||
local typenames_shop={[df.workshop_type.Carpenters]="CARPENTERS",[df.workshop_type.Farmers]="FARMERS",
|
||||
[df.workshop_type.Masons]="MASONS",[df.workshop_type.Craftsdwarfs]="CRAFTSDWARFS",
|
||||
[df.workshop_type.Jewelers]="JEWELERS",[df.workshop_type.MetalsmithsForge]="METALSMITHSFORGE",
|
||||
[df.workshop_type.MagmaForge]="MAGMAFORGE",[df.workshop_type.Bowyers]="BOWYERS",
|
||||
[df.workshop_type.Mechanics]="MECHANICS",[df.workshop_type.Siege]="SIEGE",
|
||||
[df.workshop_type.Butchers]="BUTCHERS",[df.workshop_type.Leatherworks]="LEATHERWORKS",
|
||||
[df.workshop_type.Tanners]="TANNERS",[df.workshop_type.Clothiers]="CLOTHIERS",
|
||||
[df.workshop_type.Fishery]="FISHERY",[df.workshop_type.Still]="STILL",
|
||||
[df.workshop_type.Loom]="LOOM",[df.workshop_type.Quern]="QUERN",
|
||||
[df.workshop_type.Kennels]="KENNELS",[df.workshop_type.Ashery]="ASHERY",
|
||||
[df.workshop_type.Kitchen]="KITCHEN",[df.workshop_type.Dyers]="DYERS",
|
||||
[df.workshop_type.Tool]="TOOL",[df.workshop_type.Millstone]="MILLSTONE",
|
||||
}
|
||||
local typenames_furnace={[df.furnace_type.WoodFurnace]="WOOD_FURNACE",[df.furnace_type.Smelter]="SMELTER",
|
||||
[df.furnace_type.GlassFurnace]="GLASS_FURNACE",[df.furnace_type.MagmaSmelter]="MAGMA_SMELTER",
|
||||
[df.furnace_type.MagmaGlassFurnace]="MAGMA_GLASS_FURNACE",[df.furnace_type.MagmaKiln]="MAGMA_KILN",
|
||||
[df.furnace_type.Kiln]="KILN"}
|
||||
if btype==df.building_type.Workshop then
|
||||
if typenames_shop[bsubtype]~=nil then
|
||||
return typenames_shop[bsubtype]
|
||||
else
|
||||
return nil --todo add custom (not very useful)
|
||||
end
|
||||
elseif btype==df.building_type.Furnace then
|
||||
if typenames_furnace[bsubtype]~=nil then
|
||||
return typenames_furnace[bsubtype]
|
||||
else
|
||||
return nil --todo add custom (not very useful)
|
||||
end
|
||||
end
|
||||
end
|
||||
_registeredStuff={}
|
||||
local function unregall(state)
|
||||
if state==SC_WORLD_UNLOADED then
|
||||
onReactionComplete._library=nil
|
||||
postWorkshopFillSidebarMenu._library=nil
|
||||
dfhack.onStateChange.eventful= nil
|
||||
_registeredStuff={}
|
||||
end
|
||||
end
|
||||
local function onReact(reaction,unit,input_items,input_reagents,output_items,call_native)
|
||||
if _registeredStuff.reactionCallbacks and _registeredStuff.reactionCallbacks[reaction.code] then
|
||||
_registeredStuff.reactionCallbacks[reaction.code](reaction,unit,input_items,input_reagents,output_items,call_native)
|
||||
end
|
||||
end
|
||||
local function onPostSidebar(workshop)
|
||||
local shop_id=getShopName(workshop:getType(),workshop:getSubtype(),workshop:getCustomType())
|
||||
if shop_id then
|
||||
if _registeredStuff.shopNonNative and _registeredStuff.shopNonNative[shop_id] then
|
||||
if _registeredStuff.shopNonNative[shop_id].all then
|
||||
--[[for _,button in ipairs(df.global.ui_sidebar_menus.workshop_job.choices_all) do
|
||||
button.is_hidden=true
|
||||
end]]
|
||||
df.global.ui_sidebar_menus.workshop_job.choices_visible:resize(0)
|
||||
else
|
||||
--todo by name
|
||||
end
|
||||
end
|
||||
|
||||
if _registeredStuff.reactionToShop and _registeredStuff.reactionToShop[shop_id] then
|
||||
for _,reaction_name in ipairs(_registeredStuff.reactionToShop[shop_id]) do
|
||||
local new_button=df.interface_button_building_new_jobst:new()
|
||||
--new_button.hotkey_id=--todo get hotkey
|
||||
new_button.is_hidden=false
|
||||
new_button.building=workshop
|
||||
new_button.job_type=df.job_type.CustomReaction --could be used for other stuff too i guess...
|
||||
new_button.reaction_name=reaction_name
|
||||
new_button.is_custom=true
|
||||
local wjob=df.global.ui_sidebar_menus.workshop_job
|
||||
wjob.choices_all:insert("#",new_button)
|
||||
wjob.choices_visible:insert("#",new_button)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function registerReaction(reaction_name,callback)
|
||||
_registeredStuff.reactionCallbacks=_registeredStuff.reactionCallbacks or {}
|
||||
_registeredStuff.reactionCallbacks[reaction_name]=callback
|
||||
onReactionComplete._library=onReact
|
||||
dfhack.onStateChange.eventful=unregall
|
||||
end
|
||||
|
||||
function removeNative(shop_name,name)
|
||||
_registeredStuff.shopNonNative=_registeredStuff.shopNonNative or {}
|
||||
local shops=_registeredStuff.shopNonNative
|
||||
shops[shop_name]=shops[shop_name] or {}
|
||||
if name~=nil then
|
||||
table.insert(shops[shop_name],name)
|
||||
else
|
||||
shops[shop_name].all=true
|
||||
end
|
||||
postWorkshopFillSidebarMenu._library=onPostSidebar
|
||||
dfhack.onStateChange.eventful=unregall
|
||||
end
|
||||
|
||||
function addReactionToShop(reaction_name,shop_name)
|
||||
_registeredStuff.reactionToShop=_registeredStuff.reactionToShop or {}
|
||||
local shops=_registeredStuff.reactionToShop
|
||||
shops[shop_name]=shops[shop_name] or {}
|
||||
table.insert(shops[shop_name],reaction_name)
|
||||
postWorkshopFillSidebarMenu._library=onPostSidebar
|
||||
dfhack.onStateChange.eventful=unregall
|
||||
end
|
||||
|
||||
return _ENV
|
@ -1,13 +0,0 @@
|
||||
local _ENV = mkmodule('plugins.reactionhooks')
|
||||
|
||||
--[[
|
||||
|
||||
Native events:
|
||||
|
||||
* onReactionComplete(burrow)
|
||||
|
||||
--]]
|
||||
|
||||
rawset_default(_ENV, dfhack.reactionhooks)
|
||||
|
||||
return _ENV
|
@ -0,0 +1,12 @@
|
||||
local _ENV = mkmodule('plugins.zone')
|
||||
|
||||
--[[
|
||||
|
||||
Native functions:
|
||||
|
||||
* autobutcher_isEnabled()
|
||||
* autowatch_isEnabled()
|
||||
|
||||
--]]
|
||||
|
||||
return _ENV
|
@ -0,0 +1,263 @@
|
||||
#include <sstream>
|
||||
|
||||
#include "Core.h"
|
||||
#include <Console.h>
|
||||
#include <Export.h>
|
||||
#include <PluginManager.h>
|
||||
#include <VTableInterpose.h>
|
||||
|
||||
#include "DataDefs.h"
|
||||
|
||||
#include "df/building.h"
|
||||
#include "df/enabler.h"
|
||||
#include "df/item.h"
|
||||
#include "df/ui.h"
|
||||
#include "df/unit.h"
|
||||
#include "df/viewscreen_dwarfmodest.h"
|
||||
#include "df/world.h"
|
||||
|
||||
#include "modules/Gui.h"
|
||||
#include "modules/Screen.h"
|
||||
|
||||
|
||||
using std::set;
|
||||
using std::string;
|
||||
using std::ostringstream;
|
||||
|
||||
using namespace DFHack;
|
||||
using namespace df::enums;
|
||||
|
||||
using df::global::enabler;
|
||||
using df::global::gps;
|
||||
using df::global::world;
|
||||
using df::global::ui;
|
||||
|
||||
|
||||
static int32_t last_x, last_y, last_z;
|
||||
static size_t max_list_size = 100000; // Avoid iterating over huge lists
|
||||
|
||||
struct mousequery_hook : public df::viewscreen_dwarfmodest
|
||||
{
|
||||
typedef df::viewscreen_dwarfmodest interpose_base;
|
||||
|
||||
void send_key(const df::interface_key &key)
|
||||
{
|
||||
set<df::interface_key> tmp;
|
||||
tmp.insert(key);
|
||||
//INTERPOSE_NEXT(feed)(&tmp);
|
||||
this->feed(&tmp);
|
||||
}
|
||||
|
||||
df::interface_key get_default_query_mode(const int32_t &x, const int32_t &y, const int32_t &z)
|
||||
{
|
||||
bool fallback_to_building_query = false;
|
||||
|
||||
// Check for unit under cursor
|
||||
size_t count = world->units.all.size();
|
||||
if (count <= max_list_size)
|
||||
{
|
||||
for(size_t i = 0; i < count; i++)
|
||||
{
|
||||
df::unit *unit = world->units.all[i];
|
||||
|
||||
if(unit->pos.x == x && unit->pos.y == y && unit->pos.z == z)
|
||||
return df::interface_key::D_VIEWUNIT;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
fallback_to_building_query = true;
|
||||
}
|
||||
|
||||
// Check for building under cursor
|
||||
count = world->buildings.all.size();
|
||||
if (count <= max_list_size)
|
||||
{
|
||||
for(size_t i = 0; i < count; i++)
|
||||
{
|
||||
df::building *bld = world->buildings.all[i];
|
||||
|
||||
if (z == bld->z &&
|
||||
x >= bld->x1 && x <= bld->x2 &&
|
||||
y >= bld->y1 && y <= bld->y2)
|
||||
{
|
||||
df::building_type type = bld->getType();
|
||||
|
||||
if (type == building_type::Stockpile)
|
||||
{
|
||||
fallback_to_building_query = true;
|
||||
break; // Check for items in stockpile first
|
||||
}
|
||||
|
||||
// For containers use item view, fir everything else, query view
|
||||
return (type == building_type::Box || type == building_type::Cabinet ||
|
||||
type == building_type::Weaponrack || type == building_type::Armorstand)
|
||||
? df::interface_key::D_BUILDITEM : df::interface_key::D_BUILDJOB;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
fallback_to_building_query = true;
|
||||
}
|
||||
|
||||
|
||||
// Check for items under cursor
|
||||
count = world->items.all.size();
|
||||
if (count <= max_list_size)
|
||||
{
|
||||
for(size_t i = 0; i < count; i++)
|
||||
{
|
||||
df::item *item = world->items.all[i];
|
||||
if (z == item->pos.z && x == item->pos.x && y == item->pos.y &&
|
||||
!item->flags.bits.in_building && !item->flags.bits.hidden &&
|
||||
!item->flags.bits.in_job && !item->flags.bits.in_chest &&
|
||||
!item->flags.bits.in_inventory)
|
||||
{
|
||||
return df::interface_key::D_LOOK;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
fallback_to_building_query = true;
|
||||
}
|
||||
|
||||
return (fallback_to_building_query) ? df::interface_key::D_BUILDJOB : df::interface_key::D_LOOK;
|
||||
}
|
||||
|
||||
bool handle_mouse(const set<df::interface_key> *input)
|
||||
{
|
||||
int32_t cx, cy, vz;
|
||||
if (enabler->tracking_on)
|
||||
{
|
||||
if (enabler->mouse_lbut)
|
||||
{
|
||||
int32_t mx, my;
|
||||
if (Gui::getMousePos(mx, my))
|
||||
{
|
||||
int32_t vx, vy;
|
||||
if (Gui::getViewCoords(vx, vy, vz))
|
||||
{
|
||||
cx = vx + mx - 1;
|
||||
cy = vy + my - 1;
|
||||
|
||||
using namespace df::enums::ui_sidebar_mode;
|
||||
df::interface_key key = interface_key::NONE;
|
||||
bool cursor_still_here = (last_x == cx && last_y == cy && last_z == vz);
|
||||
switch(ui->main.mode)
|
||||
{
|
||||
case QueryBuilding:
|
||||
if (cursor_still_here)
|
||||
key = df::interface_key::D_BUILDITEM;
|
||||
break;
|
||||
|
||||
case BuildingItems:
|
||||
if (cursor_still_here)
|
||||
key = df::interface_key::D_VIEWUNIT;
|
||||
break;
|
||||
|
||||
case ViewUnits:
|
||||
if (cursor_still_here)
|
||||
key = df::interface_key::D_LOOK;
|
||||
break;
|
||||
|
||||
case LookAround:
|
||||
if (cursor_still_here)
|
||||
key = df::interface_key::D_BUILDJOB;
|
||||
break;
|
||||
|
||||
case Default:
|
||||
break;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
enabler->mouse_lbut = 0;
|
||||
|
||||
// Can't check limits earlier as we must be sure we are in query or default mode we can clear the button flag
|
||||
// Otherwise the feed gets stuck in a loop
|
||||
uint8_t menu_width, area_map_width;
|
||||
Gui::getMenuWidth(menu_width, area_map_width);
|
||||
int32_t w = gps->dimx;
|
||||
if (menu_width == 1) w -= 57; //Menu is open doubly wide
|
||||
else if (menu_width == 2 && area_map_width == 3) w -= 33; //Just the menu is open
|
||||
else if (menu_width == 2 && area_map_width == 2) w -= 26; //Just the area map is open
|
||||
|
||||
if (mx < 1 || mx > w || my < 1 || my > gps->dimy - 2)
|
||||
return false;
|
||||
|
||||
while (ui->main.mode != Default)
|
||||
{
|
||||
send_key(df::interface_key::LEAVESCREEN);
|
||||
}
|
||||
|
||||
if (key == interface_key::NONE)
|
||||
key = get_default_query_mode(cx, cy, vz);
|
||||
|
||||
send_key(key);
|
||||
|
||||
// Force UI refresh
|
||||
Gui::setCursorCoords(cx, cy, vz);
|
||||
send_key(interface_key::CURSOR_DOWN_Z);
|
||||
send_key(interface_key::CURSOR_UP_Z);
|
||||
last_x = cx;
|
||||
last_y = cy;
|
||||
last_z = vz;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (enabler->mouse_rbut)
|
||||
{
|
||||
// Escape out of query mode
|
||||
using namespace df::enums::ui_sidebar_mode;
|
||||
if (ui->main.mode == QueryBuilding || ui->main.mode == BuildingItems ||
|
||||
ui->main.mode == ViewUnits || ui->main.mode == LookAround)
|
||||
{
|
||||
while (ui->main.mode != Default)
|
||||
{
|
||||
enabler->mouse_rbut = 0;
|
||||
send_key(df::interface_key::LEAVESCREEN);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
DEFINE_VMETHOD_INTERPOSE(void, feed, (set<df::interface_key> *input))
|
||||
{
|
||||
if (!handle_mouse(input))
|
||||
INTERPOSE_NEXT(feed)(input);
|
||||
}
|
||||
};
|
||||
|
||||
IMPLEMENT_VMETHOD_INTERPOSE(mousequery_hook, feed);
|
||||
|
||||
DFHACK_PLUGIN("mousequery");
|
||||
|
||||
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
|
||||
{
|
||||
if (!gps || !INTERPOSE_HOOK(mousequery_hook, feed).apply())
|
||||
out.printerr("Could not insert mousequery hooks!\n");
|
||||
|
||||
last_x = last_y = last_z = -1;
|
||||
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event)
|
||||
{
|
||||
switch (event) {
|
||||
case SC_MAP_LOADED:
|
||||
last_x = last_y = last_z = -1;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return CR_OK;
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
|
||||
#include "Console.h"
|
||||
#include "Core.h"
|
||||
#include "DataDefs.h"
|
||||
#include "Export.h"
|
||||
#include "PluginManager.h"
|
||||
|
||||
#include "modules/Buildings.h"
|
||||
#include "modules/EventManager.h"
|
||||
#include "modules/Maps.h"
|
||||
|
||||
#include "df/coord.h"
|
||||
#include "df/building.h"
|
||||
#include "df/building_def.h"
|
||||
#include "df/map_block.h"
|
||||
#include "df/tile_designation.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
using namespace DFHack;
|
||||
using namespace std;
|
||||
|
||||
DFHACK_PLUGIN("outsideOnly");
|
||||
|
||||
void buildingCreated(color_ostream& out, void* data);
|
||||
|
||||
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
|
||||
{
|
||||
EventManager::EventHandler handler(buildingCreated,1);
|
||||
EventManager::registerListener(EventManager::EventType::BUILDING, handler, plugin_self);
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
// This is called right before the plugin library is removed from memory.
|
||||
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
|
||||
{
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
void buildingCreated(color_ostream& out, void* data) {
|
||||
int32_t id = (int32_t)data;
|
||||
df::building* building = df::building::find(id);
|
||||
if ( building == NULL )
|
||||
return;
|
||||
|
||||
if ( building->getCustomType() < 0 )
|
||||
return;
|
||||
string prefix("OUTSIDE_ONLY");
|
||||
df::building_def* def = df::global::world->raws.buildings.all[building->getCustomType()];
|
||||
if (def->code.compare(0, prefix.size(), prefix)) {
|
||||
return;
|
||||
}
|
||||
|
||||
//now, just check if it was created inside, and if so, scuttle it
|
||||
df::coord pos(building->centerx,building->centery,building->z);
|
||||
|
||||
df::tile_designation* des = Maps::getTileDesignation(pos);
|
||||
if ( des->bits.outside )
|
||||
return;
|
||||
|
||||
Buildings::deconstruct(building);
|
||||
}
|
||||
|
@ -0,0 +1,59 @@
|
||||
package isoworldremote;
|
||||
|
||||
//Describes a very basic material structure for the map embark
|
||||
option optimize_for = LITE_RUNTIME;
|
||||
|
||||
enum BasicMaterial {
|
||||
AIR = 0;
|
||||
OTHER = 1;
|
||||
INORGANIC = 2;
|
||||
LIQUID = 3;
|
||||
PLANT = 4;
|
||||
WOOD = 5;
|
||||
};
|
||||
|
||||
enum LiquidType {
|
||||
ICE = 0;
|
||||
WATER = 1;
|
||||
MAGMA = 2;
|
||||
}
|
||||
|
||||
message EmbarkTileLayer {
|
||||
repeated BasicMaterial mat_type_table = 4 [packed=true];
|
||||
repeated int32 mat_subtype_table = 5 [packed=true];
|
||||
}
|
||||
|
||||
message EmbarkTile {
|
||||
required int32 world_x = 1;
|
||||
required int32 world_y = 2;
|
||||
required sint32 world_z = 3;
|
||||
repeated EmbarkTileLayer tile_layer = 4;
|
||||
optional int32 current_year = 5;
|
||||
optional int32 current_season = 6;
|
||||
optional bool is_valid = 7;
|
||||
}
|
||||
|
||||
message TileRequest {
|
||||
optional int32 want_x = 1;
|
||||
optional int32 want_y = 2;
|
||||
}
|
||||
|
||||
message MapRequest {
|
||||
optional string save_folder = 1;
|
||||
}
|
||||
|
||||
message MapReply {
|
||||
required bool available = 1;
|
||||
optional int32 region_x = 2;
|
||||
optional int32 region_y = 3;
|
||||
optional int32 region_size_x = 4;
|
||||
optional int32 region_size_y = 5;
|
||||
optional int32 current_year = 6;
|
||||
optional int32 current_season = 7;
|
||||
}
|
||||
|
||||
message RawNames {
|
||||
required bool available = 1;
|
||||
repeated string inorganic = 2;
|
||||
repeated string organic = 3;
|
||||
}
|
@ -1,322 +0,0 @@
|
||||
#include "Core.h"
|
||||
#include <Console.h>
|
||||
#include <Export.h>
|
||||
#include <PluginManager.h>
|
||||
#include <modules/Gui.h>
|
||||
#include <modules/Screen.h>
|
||||
#include <modules/Maps.h>
|
||||
#include <modules/Job.h>
|
||||
#include <modules/Items.h>
|
||||
#include <modules/Units.h>
|
||||
#include <TileTypes.h>
|
||||
#include <vector>
|
||||
#include <cstdio>
|
||||
#include <stack>
|
||||
#include <string>
|
||||
#include <cmath>
|
||||
#include <string.h>
|
||||
|
||||
#include <VTableInterpose.h>
|
||||
#include "df/item_liquid_miscst.h"
|
||||
#include "df/item_constructed.h"
|
||||
#include "df/builtin_mats.h"
|
||||
#include "df/world.h"
|
||||
#include "df/job.h"
|
||||
#include "df/job_item.h"
|
||||
#include "df/job_item_ref.h"
|
||||
#include "df/ui.h"
|
||||
#include "df/report.h"
|
||||
#include "df/reaction.h"
|
||||
#include "df/reaction_reagent_itemst.h"
|
||||
#include "df/reaction_product_itemst.h"
|
||||
#include "df/matter_state.h"
|
||||
#include "df/contaminant.h"
|
||||
|
||||
#include "MiscUtils.h"
|
||||
#include "LuaTools.h"
|
||||
|
||||
using std::vector;
|
||||
using std::string;
|
||||
using std::stack;
|
||||
using namespace DFHack;
|
||||
using namespace df::enums;
|
||||
|
||||
using df::global::gps;
|
||||
using df::global::world;
|
||||
using df::global::ui;
|
||||
|
||||
typedef df::reaction_product_itemst item_product;
|
||||
|
||||
DFHACK_PLUGIN("reactionhooks");
|
||||
|
||||
struct ReagentSource {
|
||||
int idx;
|
||||
df::reaction_reagent *reagent;
|
||||
|
||||
ReagentSource() : idx(-1), reagent(NULL) {}
|
||||
};
|
||||
|
||||
struct MaterialSource : ReagentSource {
|
||||
bool product;
|
||||
std::string product_name;
|
||||
|
||||
int mat_type, mat_index;
|
||||
|
||||
MaterialSource() : product(false), mat_type(-1), mat_index(-1) {}
|
||||
};
|
||||
|
||||
struct ProductInfo {
|
||||
df::reaction *react;
|
||||
item_product *product;
|
||||
|
||||
MaterialSource material;
|
||||
|
||||
bool isValid() {
|
||||
return (material.mat_type >= 0 || material.reagent);
|
||||
}
|
||||
};
|
||||
|
||||
struct ReactionInfo {
|
||||
df::reaction *react;
|
||||
|
||||
std::vector<ProductInfo> products;
|
||||
};
|
||||
|
||||
static std::map<std::string, ReactionInfo> reactions;
|
||||
static std::map<df::reaction_product*, ProductInfo*> products;
|
||||
|
||||
static ReactionInfo *find_reaction(const std::string &name)
|
||||
{
|
||||
auto it = reactions.find(name);
|
||||
return (it != reactions.end()) ? &it->second : NULL;
|
||||
}
|
||||
|
||||
static bool is_lua_hook(const std::string &name)
|
||||
{
|
||||
return name.size() > 9 && memcmp(name.data(), "LUA_HOOK_", 9) == 0;
|
||||
}
|
||||
|
||||
static void find_material(int *type, int *index, df::item *input, MaterialSource &mat)
|
||||
{
|
||||
if (input && mat.reagent)
|
||||
{
|
||||
MaterialInfo info(input);
|
||||
|
||||
if (mat.product)
|
||||
{
|
||||
if (!info.findProduct(info, mat.product_name))
|
||||
{
|
||||
color_ostream_proxy out(Core::getInstance().getConsole());
|
||||
out.printerr("Cannot find product '%s'\n", mat.product_name.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
*type = info.type;
|
||||
*index = info.index;
|
||||
}
|
||||
else
|
||||
{
|
||||
*type = mat.mat_type;
|
||||
*index = mat.mat_index;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Hooks
|
||||
*/
|
||||
|
||||
typedef std::map<int, std::vector<df::item*> > item_table;
|
||||
|
||||
static void index_items(item_table &table, df::job *job, ReactionInfo *info)
|
||||
{
|
||||
for (int i = job->items.size()-1; i >= 0; i--)
|
||||
{
|
||||
auto iref = job->items[i];
|
||||
if (iref->job_item_idx < 0) continue;
|
||||
auto iitem = job->job_items[iref->job_item_idx];
|
||||
|
||||
if (iitem->contains.empty())
|
||||
{
|
||||
table[iitem->reagent_index].push_back(iref->item);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::vector<df::item*> contents;
|
||||
Items::getContainedItems(iref->item, &contents);
|
||||
|
||||
for (int j = contents.size()-1; j >= 0; j--)
|
||||
{
|
||||
for (int k = iitem->contains.size()-1; k >= 0; k--)
|
||||
{
|
||||
int ridx = iitem->contains[k];
|
||||
auto reag = info->react->reagents[ridx];
|
||||
|
||||
if (reag->matchesChild(contents[j], info->react, iitem->reaction_id))
|
||||
table[ridx].push_back(contents[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
df::item* find_item(ReagentSource &info, item_table &table)
|
||||
{
|
||||
if (!info.reagent)
|
||||
return NULL;
|
||||
if (table[info.idx].empty())
|
||||
return NULL;
|
||||
return table[info.idx].back();
|
||||
}
|
||||
|
||||
|
||||
|
||||
df::item* find_item(
|
||||
ReagentSource &info,
|
||||
std::vector<df::reaction_reagent*> *in_reag,
|
||||
std::vector<df::item*> *in_items
|
||||
) {
|
||||
if (!info.reagent)
|
||||
return NULL;
|
||||
for (int i = in_items->size(); i >= 0; i--)
|
||||
if ((*in_reag)[i] == info.reagent)
|
||||
return (*in_items)[i];
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void handle_reaction_done(color_ostream &out,df::reaction*, df::unit *unit, std::vector<df::item*> *in_items,std::vector<df::reaction_reagent*> *in_reag
|
||||
, std::vector<df::item*> *out_items,bool *call_native){};
|
||||
|
||||
DEFINE_LUA_EVENT_6(onReactionComplete, handle_reaction_done,df::reaction*, df::unit *, std::vector<df::item*> *,std::vector<df::reaction_reagent*> *,std::vector<df::item*> *,bool *);
|
||||
|
||||
|
||||
DFHACK_PLUGIN_LUA_EVENTS {
|
||||
DFHACK_LUA_EVENT(onReactionComplete),
|
||||
DFHACK_LUA_END
|
||||
};
|
||||
|
||||
struct product_hook : item_product {
|
||||
typedef item_product interpose_base;
|
||||
|
||||
DEFINE_VMETHOD_INTERPOSE(
|
||||
void, produce,
|
||||
(df::unit *unit, std::vector<df::item*> *out_items,
|
||||
std::vector<df::reaction_reagent*> *in_reag,
|
||||
std::vector<df::item*> *in_items,
|
||||
int32_t quantity, df::job_skill skill,
|
||||
df::historical_entity *entity, df::world_site *site)
|
||||
) {
|
||||
if (auto product = products[this])
|
||||
{
|
||||
df::reaction* this_reaction=product->react;
|
||||
CoreSuspendClaimer suspend;
|
||||
color_ostream_proxy out(Core::getInstance().getConsole());
|
||||
bool call_native=true;
|
||||
onReactionComplete(out,this_reaction,unit,in_items,in_reag,out_items,&call_native);
|
||||
if(!call_native)
|
||||
return;
|
||||
}
|
||||
|
||||
INTERPOSE_NEXT(produce)(unit, out_items, in_reag, in_items, quantity, skill, entity, site);
|
||||
}
|
||||
};
|
||||
|
||||
IMPLEMENT_VMETHOD_INTERPOSE(product_hook, produce);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Scan raws for matching reactions.
|
||||
*/
|
||||
|
||||
|
||||
static void parse_product(
|
||||
color_ostream &out, ProductInfo &info, df::reaction *react, item_product *prod
|
||||
) {
|
||||
info.react = react;
|
||||
info.product = prod;
|
||||
info.material.mat_type = prod->mat_type;
|
||||
info.material.mat_index = prod->mat_index;
|
||||
}
|
||||
|
||||
static bool find_reactions(color_ostream &out)
|
||||
{
|
||||
reactions.clear();
|
||||
|
||||
auto &rlist = world->raws.reactions;
|
||||
|
||||
for (size_t i = 0; i < rlist.size(); i++)
|
||||
{
|
||||
if (!is_lua_hook(rlist[i]->code))
|
||||
continue;
|
||||
reactions[rlist[i]->code].react = rlist[i];
|
||||
}
|
||||
|
||||
for (auto it = reactions.begin(); it != reactions.end(); ++it)
|
||||
{
|
||||
auto &prod = it->second.react->products;
|
||||
auto &out_prod = it->second.products;
|
||||
|
||||
for (size_t i = 0; i < prod.size(); i++)
|
||||
{
|
||||
auto itprod = strict_virtual_cast<item_product>(prod[i]);
|
||||
if (!itprod) continue;
|
||||
|
||||
out_prod.push_back(ProductInfo());
|
||||
parse_product(out, out_prod.back(), it->second.react, itprod);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < prod.size(); i++)
|
||||
{
|
||||
if (out_prod[i].isValid())
|
||||
products[out_prod[i].product] = &out_prod[i];
|
||||
}
|
||||
}
|
||||
|
||||
return !products.empty();
|
||||
}
|
||||
|
||||
static void enable_hooks(bool enable)
|
||||
{
|
||||
INTERPOSE_HOOK(product_hook, produce).apply(enable);
|
||||
}
|
||||
|
||||
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event)
|
||||
{
|
||||
switch (event) {
|
||||
case SC_WORLD_LOADED:
|
||||
if (find_reactions(out))
|
||||
{
|
||||
out.print("Detected reaction hooks - enabling plugin.\n");
|
||||
enable_hooks(true);
|
||||
}
|
||||
else
|
||||
enable_hooks(false);
|
||||
break;
|
||||
case SC_WORLD_UNLOADED:
|
||||
enable_hooks(false);
|
||||
reactions.clear();
|
||||
products.clear();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
|
||||
{
|
||||
if (Core::getInstance().isWorldLoaded())
|
||||
plugin_onstatechange(out, SC_WORLD_LOADED);
|
||||
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
|
||||
{
|
||||
enable_hooks(false);
|
||||
return CR_OK;
|
||||
}
|
@ -0,0 +1,315 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
|
||||
#include "Core.h"
|
||||
#include <Console.h>
|
||||
#include <Export.h>
|
||||
#include <PluginManager.h>
|
||||
#include <VTableInterpose.h>
|
||||
|
||||
|
||||
// DF data structure definition headers
|
||||
#include "DataDefs.h"
|
||||
#include "MiscUtils.h"
|
||||
#include "Types.h"
|
||||
#include "df/viewscreen_dwarfmodest.h"
|
||||
#include "df/world.h"
|
||||
#include "df/building_constructionst.h"
|
||||
#include "df/building.h"
|
||||
#include "df/job.h"
|
||||
#include "df/job_item.h"
|
||||
|
||||
#include "modules/Gui.h"
|
||||
#include "modules/Screen.h"
|
||||
#include "modules/Buildings.h"
|
||||
#include "modules/Maps.h"
|
||||
|
||||
#include "modules/World.h"
|
||||
|
||||
using std::map;
|
||||
using std::string;
|
||||
using std::vector;
|
||||
|
||||
using namespace DFHack;
|
||||
using namespace df::enums;
|
||||
|
||||
using df::global::gps;
|
||||
using df::global::ui;
|
||||
using df::global::world;
|
||||
|
||||
DFHACK_PLUGIN("resume");
|
||||
#define PLUGIN_VERSION 0.2
|
||||
|
||||
#ifndef HAVE_NULLPTR
|
||||
#define nullptr 0L
|
||||
#endif
|
||||
|
||||
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
|
||||
{
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
template <class T, typename Fn>
|
||||
static void for_each_(vector<T> &v, Fn func)
|
||||
{
|
||||
for_each(v.begin(), v.end(), func);
|
||||
}
|
||||
|
||||
template <class T, class V, typename Fn>
|
||||
static void transform_(vector<T> &src, vector<V> &dst, Fn func)
|
||||
{
|
||||
transform(src.begin(), src.end(), back_inserter(dst), func);
|
||||
}
|
||||
|
||||
void OutputString(int8_t color, int &x, int &y, const std::string &text, bool newline = false, int left_margin = 0)
|
||||
{
|
||||
Screen::paintString(Screen::Pen(' ', color, 0), x, y, text);
|
||||
if (newline)
|
||||
{
|
||||
++y;
|
||||
x = left_margin;
|
||||
}
|
||||
else
|
||||
x += text.length();
|
||||
}
|
||||
|
||||
df::job *get_suspended_job(df::building *bld)
|
||||
{
|
||||
if (bld->getBuildStage() != 0)
|
||||
return nullptr;
|
||||
|
||||
if (bld->jobs.size() == 0)
|
||||
return nullptr;
|
||||
|
||||
auto job = bld->jobs[0];
|
||||
if (job->flags.bits.suspend)
|
||||
return job;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
struct SuspendedBuilding
|
||||
{
|
||||
df::building *bld;
|
||||
df::coord pos;
|
||||
bool was_resumed;
|
||||
bool is_planned;
|
||||
|
||||
SuspendedBuilding(df::building *bld_) : bld(bld_), was_resumed(false), is_planned(false)
|
||||
{
|
||||
pos = df::coord(bld->centerx, bld->centery, bld->z);
|
||||
}
|
||||
|
||||
bool isValid()
|
||||
{
|
||||
return bld && Buildings::findAtTile(pos) == bld && get_suspended_job(bld);
|
||||
}
|
||||
};
|
||||
|
||||
static bool enabled = false;
|
||||
static bool buildings_scanned = false;
|
||||
static vector<SuspendedBuilding> suspended_buildings, resumed_buildings;
|
||||
|
||||
void scan_for_suspended_buildings()
|
||||
{
|
||||
if (buildings_scanned)
|
||||
return;
|
||||
|
||||
for (auto b = world->buildings.all.begin(); b != world->buildings.all.end(); b++)
|
||||
{
|
||||
auto bld = *b;
|
||||
auto job = get_suspended_job(bld);
|
||||
if (job)
|
||||
{
|
||||
SuspendedBuilding sb(bld);
|
||||
sb.is_planned = job->job_items.size() == 1 && job->job_items[0]->item_type == item_type::NONE;
|
||||
|
||||
auto it = find_if(resumed_buildings.begin(), resumed_buildings.end(),
|
||||
[&] (SuspendedBuilding &rsb) { return rsb.bld == bld; });
|
||||
|
||||
sb.was_resumed = it != resumed_buildings.end();
|
||||
|
||||
suspended_buildings.push_back(sb);
|
||||
}
|
||||
}
|
||||
|
||||
buildings_scanned = true;
|
||||
}
|
||||
|
||||
void show_suspended_buildings()
|
||||
{
|
||||
int32_t vx, vy, vz;
|
||||
if (!Gui::getViewCoords(vx, vy, vz))
|
||||
return;
|
||||
|
||||
auto dims = Gui::getDwarfmodeViewDims();
|
||||
int left_margin = vx + dims.map_x2;
|
||||
int bottom_margin = vy + dims.y2;
|
||||
|
||||
for (auto sb = suspended_buildings.begin(); sb != suspended_buildings.end();)
|
||||
{
|
||||
if (!sb->isValid())
|
||||
{
|
||||
sb = suspended_buildings.erase(sb);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sb->bld->z == vz && sb->bld->centerx >= vx && sb->bld->centerx <= left_margin &&
|
||||
sb->bld->centery >= vy && sb->bld->centery <= bottom_margin)
|
||||
{
|
||||
int x = sb->bld->centerx - vx + 1;
|
||||
int y = sb->bld->centery - vy + 1;
|
||||
auto color = COLOR_YELLOW;
|
||||
if (sb->is_planned)
|
||||
color = COLOR_GREEN;
|
||||
else if (sb->was_resumed)
|
||||
color = COLOR_RED;
|
||||
|
||||
OutputString(color, x, y, "X");
|
||||
}
|
||||
|
||||
sb++;
|
||||
}
|
||||
}
|
||||
|
||||
void clear_scanned()
|
||||
{
|
||||
buildings_scanned = false;
|
||||
suspended_buildings.clear();
|
||||
}
|
||||
|
||||
void resume_suspended_buildings(color_ostream &out)
|
||||
{
|
||||
out << "Resuming all buildings." << endl;
|
||||
|
||||
for (auto isb = resumed_buildings.begin(); isb != resumed_buildings.end();)
|
||||
{
|
||||
if (isb->isValid())
|
||||
{
|
||||
isb++;
|
||||
continue;
|
||||
}
|
||||
|
||||
isb = resumed_buildings.erase(isb);
|
||||
}
|
||||
|
||||
scan_for_suspended_buildings();
|
||||
for (auto sb = suspended_buildings.begin(); sb != suspended_buildings.end(); sb++)
|
||||
{
|
||||
if (sb->is_planned)
|
||||
continue;
|
||||
|
||||
resumed_buildings.push_back(*sb);
|
||||
sb->bld->jobs[0]->flags.bits.suspend = false;
|
||||
}
|
||||
|
||||
clear_scanned();
|
||||
|
||||
out << resumed_buildings.size() << " buildings resumed" << endl;
|
||||
}
|
||||
|
||||
|
||||
//START Viewscreen Hook
|
||||
struct resume_hook : public df::viewscreen_dwarfmodest
|
||||
{
|
||||
//START UI Methods
|
||||
typedef df::viewscreen_dwarfmodest interpose_base;
|
||||
|
||||
DEFINE_VMETHOD_INTERPOSE(void, render, ())
|
||||
{
|
||||
INTERPOSE_NEXT(render)();
|
||||
|
||||
if (enabled && DFHack::World::ReadPauseState() && ui->main.mode == ui_sidebar_mode::Default)
|
||||
{
|
||||
scan_for_suspended_buildings();
|
||||
show_suspended_buildings();
|
||||
}
|
||||
else
|
||||
{
|
||||
clear_scanned();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
IMPLEMENT_VMETHOD_INTERPOSE(resume_hook, render);
|
||||
|
||||
|
||||
static command_result resume_cmd(color_ostream &out, vector <string> & parameters)
|
||||
{
|
||||
bool show_help = false;
|
||||
if (parameters.empty())
|
||||
{
|
||||
show_help = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto cmd = parameters[0][0];
|
||||
if (cmd == 'v')
|
||||
{
|
||||
out << "Resume" << endl << "Version: " << PLUGIN_VERSION << endl;
|
||||
}
|
||||
else if (cmd == 's')
|
||||
{
|
||||
enabled = true;
|
||||
out << "Overlay enabled" << endl;
|
||||
}
|
||||
else if (cmd == 'h')
|
||||
{
|
||||
enabled = false;
|
||||
out << "Overlay disabled" << endl;
|
||||
}
|
||||
else if (cmd == 'a')
|
||||
{
|
||||
resume_suspended_buildings(out);
|
||||
}
|
||||
else
|
||||
{
|
||||
show_help = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (show_help)
|
||||
return CR_WRONG_USAGE;
|
||||
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
|
||||
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
|
||||
{
|
||||
if (!gps || !INTERPOSE_HOOK(resume_hook, render).apply())
|
||||
out.printerr("Could not insert resume hooks!\n");
|
||||
|
||||
commands.push_back(
|
||||
PluginCommand(
|
||||
"resume", "A plugin to help display and resume suspended constructions conveniently",
|
||||
resume_cmd, false,
|
||||
"resume show\n"
|
||||
" Show overlay when paused:\n"
|
||||
" Yellow: Suspended construction\n"
|
||||
" Red: Suspended after resume attempt, possibly stuck\n"
|
||||
" Green: Planned building waiting for materials\n"
|
||||
"resume hide\n"
|
||||
" Hide overlay\n"
|
||||
"resume all\n"
|
||||
" Resume all suspended building constructions\n"
|
||||
));
|
||||
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
|
||||
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event)
|
||||
{
|
||||
switch (event) {
|
||||
case SC_MAP_LOADED:
|
||||
suspended_buildings.clear();
|
||||
resumed_buildings.clear();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return CR_OK;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,40 @@
|
||||
#include "Console.h"
|
||||
#include "Core.h"
|
||||
#include "DataDefs.h"
|
||||
#include "Export.h"
|
||||
#include "PluginManager.h"
|
||||
|
||||
//#include "df/world.h"
|
||||
|
||||
using namespace DFHack;
|
||||
|
||||
command_result skeleton2 (color_ostream &out, std::vector <std::string> & parameters);
|
||||
|
||||
DFHACK_PLUGIN("skeleton2");
|
||||
|
||||
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
|
||||
{
|
||||
commands.push_back(PluginCommand(
|
||||
"skeleton2",
|
||||
"shortHelpString",
|
||||
skeleton2,
|
||||
false, //allow non-interactive use
|
||||
"longHelpString"
|
||||
));
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
|
||||
{
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
command_result skeleton2 (color_ostream &out, std::vector <std::string> & parameters)
|
||||
{
|
||||
if (!parameters.empty())
|
||||
return CR_WRONG_USAGE;
|
||||
CoreSuspender suspend;
|
||||
out.print("blah");
|
||||
return CR_OK;
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1 +1 @@
|
||||
Subproject commit cb97cf308c6e09638c0de94894473c9bd0f561fd
|
||||
Subproject commit 0d41614ff3dae9245e786ad667b0e463fe0dea3e
|
@ -0,0 +1,198 @@
|
||||
|
||||
#include "Core.h"
|
||||
#include "Console.h"
|
||||
#include "DataDefs.h"
|
||||
#include "Export.h"
|
||||
#include "PluginManager.h"
|
||||
|
||||
#include "modules/EventManager.h"
|
||||
#include "modules/Once.h"
|
||||
|
||||
#include "df/caste_raw.h"
|
||||
#include "df/creature_raw.h"
|
||||
#include "df/syndrome.h"
|
||||
#include "df/unit.h"
|
||||
#include "df/unit_syndrome.h"
|
||||
#include "df/world.h"
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
using namespace DFHack;
|
||||
using namespace std;
|
||||
|
||||
static bool enabled = false;
|
||||
|
||||
DFHACK_PLUGIN("syndromeTrigger");
|
||||
|
||||
void syndromeHandler(color_ostream& out, void* ptr);
|
||||
|
||||
command_result syndromeTrigger(color_ostream& out, vector<string>& parameters);
|
||||
|
||||
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
|
||||
{
|
||||
commands.push_back(PluginCommand("syndromeTrigger", "Run commands and enable true transformations, configured by the raw files.\n", &syndromeTrigger, false,
|
||||
"syndromeTrigger:\n"
|
||||
" syndromeTrigger 0 //disable\n"
|
||||
" syndromeTrigger 1 //enable\n"
|
||||
" syndromeTrigger disable //disable\n"
|
||||
" syndromeTrigger enable //enable\n"
|
||||
"\n"
|
||||
"See Readme.rst for details.\n"
|
||||
));
|
||||
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
command_result syndromeTrigger(color_ostream& out, vector<string>& parameters) {
|
||||
if ( parameters.size() > 1 )
|
||||
return CR_WRONG_USAGE;
|
||||
|
||||
bool wasEnabled = enabled;
|
||||
if ( parameters.size() == 1 ) {
|
||||
if ( parameters[0] == "enable" ) {
|
||||
enabled = true;
|
||||
} else if ( parameters[0] == "disable" ) {
|
||||
enabled = false;
|
||||
} else {
|
||||
int32_t a = atoi(parameters[0].c_str());
|
||||
if ( a < 0 || a > 1 )
|
||||
return CR_WRONG_USAGE;
|
||||
|
||||
enabled = (bool)a;
|
||||
}
|
||||
}
|
||||
|
||||
out.print("syndromeTrigger is %s\n", enabled ? "enabled" : "disabled");
|
||||
if ( enabled == wasEnabled )
|
||||
return CR_OK;
|
||||
|
||||
EventManager::unregisterAll(plugin_self);
|
||||
if ( enabled ) {
|
||||
EventManager::EventHandler handle(syndromeHandler, 1);
|
||||
EventManager::registerListener(EventManager::EventType::SYNDROME, handle, plugin_self);
|
||||
}
|
||||
return CR_OK;
|
||||
|
||||
}
|
||||
|
||||
void syndromeHandler(color_ostream& out, void* ptr) {
|
||||
EventManager::SyndromeData* data = (EventManager::SyndromeData*)ptr;
|
||||
|
||||
if ( !ptr ) {
|
||||
if ( DFHack::Once::doOnce("syndromeTrigger_null data") ) {
|
||||
out.print("%s, %d: null pointer from EventManager.\n", __FILE__, __LINE__);
|
||||
}
|
||||
return;
|
||||
}
|
||||
//out.print("Syndrome started: unit %d, syndrome %d.\n", data->unitId, data->syndromeIndex);
|
||||
|
||||
df::unit* unit = df::unit::find(data->unitId);
|
||||
if (!unit) {
|
||||
if ( DFHack::Once::doOnce("syndromeTrigger_no find unit" ) )
|
||||
out.print("%s, line %d: couldn't find unit.\n", __FILE__, __LINE__);
|
||||
return;
|
||||
}
|
||||
|
||||
df::unit_syndrome* unit_syndrome = unit->syndromes.active[data->syndromeIndex];
|
||||
//out.print(" syndrome type %d\n", unit_syndrome->type);
|
||||
df::syndrome* syndrome = df::global::world->raws.syndromes.all[unit_syndrome->type];
|
||||
|
||||
bool foundPermanent = false;
|
||||
bool foundCommand = false;
|
||||
bool foundAutoSyndrome = false;
|
||||
string commandStr;
|
||||
vector<string> args;
|
||||
int32_t raceId = -1;
|
||||
df::creature_raw* creatureRaw = NULL;
|
||||
int32_t casteId = -1;
|
||||
for ( size_t a = 0; a < syndrome->syn_class.size(); a++ ) {
|
||||
std::string& clazz = *syndrome->syn_class[a];
|
||||
//out.print(" clazz %d = %s\n", a, clazz.c_str());
|
||||
if ( foundCommand ) {
|
||||
if ( commandStr == "" ) {
|
||||
commandStr = clazz;
|
||||
continue;
|
||||
}
|
||||
stringstream bob;
|
||||
if ( clazz == "\\LOCATION" ) {
|
||||
bob << unit->pos.x;
|
||||
args.push_back(bob.str());
|
||||
bob.str("");
|
||||
bob.clear();
|
||||
|
||||
bob << unit->pos.y;
|
||||
args.push_back(bob.str());
|
||||
bob.str("");
|
||||
bob.clear();
|
||||
|
||||
bob << unit->pos.z;
|
||||
args.push_back(bob.str());
|
||||
bob.str("");
|
||||
bob.clear();
|
||||
} else if ( clazz == "\\UNIT_ID" ) {
|
||||
bob << unit->id;
|
||||
args.push_back(bob.str());
|
||||
bob.str("");
|
||||
bob.clear();
|
||||
} else if ( clazz == "\\SYNDROME_ID" ) {
|
||||
bob << unit_syndrome->type;
|
||||
args.push_back(bob.str());
|
||||
bob.str("");
|
||||
bob.clear();
|
||||
} else {
|
||||
args.push_back(clazz);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if ( clazz == "\\AUTO_SYNDROME" ) {
|
||||
foundAutoSyndrome = true;
|
||||
continue;
|
||||
}
|
||||
if ( clazz == "\\COMMAND" ) {
|
||||
foundCommand = true;
|
||||
continue;
|
||||
}
|
||||
if ( clazz == "\\PERMANENT" ) {
|
||||
foundPermanent = true;
|
||||
continue;
|
||||
}
|
||||
if ( !foundPermanent )
|
||||
continue;
|
||||
if ( raceId == -1 ) {
|
||||
//find the race with the name
|
||||
string& name = *syndrome->syn_class[a];
|
||||
for ( size_t b = 0; b < df::global::world->raws.creatures.all.size(); b++ ) {
|
||||
df::creature_raw* creature = df::global::world->raws.creatures.all[b];
|
||||
if ( creature->creature_id != name )
|
||||
continue;
|
||||
raceId = b;
|
||||
creatureRaw = creature;
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if ( raceId != -1 && casteId == -1 ) {
|
||||
string& name = *syndrome->syn_class[a];
|
||||
for ( size_t b = 0; b < creatureRaw->caste.size(); b++ ) {
|
||||
df::caste_raw* caste = creatureRaw->caste[b];
|
||||
if ( caste->caste_id != name )
|
||||
continue;
|
||||
casteId = b;
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if ( !foundAutoSyndrome && commandStr != "" ) {
|
||||
Core::getInstance().runCommand(out, commandStr, args);
|
||||
}
|
||||
|
||||
if ( !foundPermanent || raceId == -1 || casteId == -1 )
|
||||
return;
|
||||
|
||||
unit->enemy.normal_race = raceId;
|
||||
unit->enemy.normal_caste = casteId;
|
||||
//that's it!
|
||||
}
|
||||
|
@ -0,0 +1,145 @@
|
||||
#include "Console.h"
|
||||
#include "Core.h"
|
||||
#include "DataDefs.h"
|
||||
#include "Export.h"
|
||||
#include "PluginManager.h"
|
||||
|
||||
#include "modules/EventManager.h"
|
||||
#include "modules/Once.h"
|
||||
|
||||
#include "df/block_burrow.h"
|
||||
#include "df/block_burrow_link.h"
|
||||
#include "df/burrow.h"
|
||||
#include "df/map_block.h"
|
||||
#include "df/tile_bitmask.h"
|
||||
#include "df/tile_dig_designation.h"
|
||||
#include "df/tiletype.h"
|
||||
#include "df/tiletype_shape.h"
|
||||
#include "df/world.h"
|
||||
|
||||
//#include "df/world.h"
|
||||
|
||||
using namespace DFHack;
|
||||
|
||||
void checkFarms(color_ostream& out, void* ptr);
|
||||
command_result treefarm (color_ostream &out, std::vector <std::string> & parameters);
|
||||
|
||||
EventManager::EventHandler handler(&checkFarms, -1);
|
||||
int32_t frequency = 1200*30;
|
||||
|
||||
DFHACK_PLUGIN("treefarm");
|
||||
|
||||
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
|
||||
{
|
||||
commands.push_back(PluginCommand(
|
||||
"treefarm",
|
||||
"automatically manages special burrows and regularly schedules tree chopping and digging when appropriate",
|
||||
treefarm,
|
||||
false, //allow non-interactive use
|
||||
"treefarm\n"
|
||||
" enables treefarm monitoring, starting next frame\n"
|
||||
"treefarm n\n"
|
||||
" enables treefarm monitoring, starting next frame\n"
|
||||
" sets monitoring interval to n frames\n"
|
||||
" if n is less than one, disables monitoring\n"
|
||||
"\n"
|
||||
"Every time the plugin runs, it checks for burrows with a name containing the string \"treefarm\". For each such burrow, it checks every tile in it for fully-grown trees and for diggable walls. For each fully-grown tree it finds, it designates the tree to be chopped, and for each natural wall it finds, it designates the wall to be dug.\n"
|
||||
));
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
|
||||
{
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
void checkFarms(color_ostream& out, void* ptr) {
|
||||
EventManager::unregisterAll(plugin_self);
|
||||
EventManager::registerTick(handler, frequency, plugin_self);
|
||||
CoreSuspender suspend;
|
||||
|
||||
df::world* world = df::global::world;
|
||||
df::ui* ui = df::global::ui;
|
||||
int32_t xOffset = world->map.region_x*3;
|
||||
int32_t yOffset = world->map.region_y*3;
|
||||
int32_t zOffset = world->map.region_z;
|
||||
//for each burrow named treefarm or obsidianfarm, check if you can dig/chop any obsidian/trees
|
||||
for ( size_t a = 0; a < df::burrow::get_vector().size(); a++ ) {
|
||||
df::burrow* burrow = df::burrow::get_vector()[a];
|
||||
if ( !burrow || burrow->name.find("treefarm") == std::string::npos )
|
||||
continue;
|
||||
|
||||
if ( burrow->block_x.size() != burrow->block_y.size() || burrow->block_x.size() != burrow->block_z.size() )
|
||||
continue;
|
||||
|
||||
for ( size_t b = 0; b < burrow->block_x.size(); b++ ) {
|
||||
int32_t x=burrow->block_x[b] - xOffset;
|
||||
int32_t y=burrow->block_y[b] - yOffset;
|
||||
int32_t z=burrow->block_z[b] - zOffset;
|
||||
|
||||
df::map_block* block = world->map.block_index[x][y][z];
|
||||
if ( !block )
|
||||
continue;
|
||||
|
||||
df::block_burrow_link* link = &block->block_burrows;
|
||||
df::tile_bitmask mask;
|
||||
for ( ; link != NULL; link = link->next ) {
|
||||
if ( link->item == NULL )
|
||||
continue;
|
||||
if ( link->item->id == burrow->id ) {
|
||||
mask = link->item->tile_bitmask;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ( link == NULL )
|
||||
continue;
|
||||
|
||||
for ( int32_t x = 0; x < 16; x++ ) {
|
||||
for ( int32_t y = 0; y < 16; y++ ) {
|
||||
if ( !mask.getassignment(x,y) )
|
||||
continue;
|
||||
df::tiletype type = block->tiletype[x][y];
|
||||
df::tiletype_shape shape = ENUM_ATTR(tiletype, shape, type);
|
||||
if ( !block->designation[x][y].bits.hidden &&
|
||||
shape != df::enums::tiletype_shape::WALL &&
|
||||
shape != df::enums::tiletype_shape::TREE )
|
||||
continue;
|
||||
if ( shape != df::enums::tiletype_shape::TREE ) {
|
||||
if ( x == 0 && (block->map_pos.x/16) == 0 )
|
||||
continue;
|
||||
if ( y == 0 && (block->map_pos.y/16) == 0 )
|
||||
continue;
|
||||
if ( x == 15 && (block->map_pos.x/16) == world->map.x_count_block-1 )
|
||||
continue;
|
||||
if ( y == 15 && (block->map_pos.y/16) == world->map.y_count_block-1 )
|
||||
continue;
|
||||
}
|
||||
|
||||
block->designation[x][y].bits.dig = df::enums::tile_dig_designation::Default;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
command_result treefarm (color_ostream &out, std::vector <std::string> & parameters)
|
||||
{
|
||||
EventManager::unregisterAll(plugin_self);
|
||||
|
||||
if ( parameters.size() > 1 )
|
||||
return CR_WRONG_USAGE;
|
||||
if ( parameters.size() == 1 ) {
|
||||
int32_t i = atoi(parameters[0].c_str());
|
||||
if ( i < 1 ) {
|
||||
out.print("treefarm disabled\n");
|
||||
return CR_OK;
|
||||
}
|
||||
frequency = i;
|
||||
}
|
||||
|
||||
EventManager::registerTick(handler, 1, plugin_self);
|
||||
|
||||
out.print("treefarm enabled with update frequency %d ticks\n", frequency);
|
||||
return CR_OK;
|
||||
}
|
||||
|
@ -0,0 +1,580 @@
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <set>
|
||||
|
||||
#include "Core.h"
|
||||
#include "MiscUtils.h"
|
||||
#include <Console.h>
|
||||
#include <Export.h>
|
||||
#include <PluginManager.h>
|
||||
#include <VTableInterpose.h>
|
||||
|
||||
#include "modules/Screen.h"
|
||||
|
||||
#include "df/enabler.h"
|
||||
|
||||
|
||||
using std::string;
|
||||
using std::vector;
|
||||
using std::map;
|
||||
using std::ostringstream;
|
||||
using std::set;
|
||||
|
||||
using namespace DFHack;
|
||||
using namespace df::enums;
|
||||
|
||||
using df::global::enabler;
|
||||
using df::global::gps;
|
||||
|
||||
|
||||
#ifndef HAVE_NULLPTR
|
||||
#define nullptr 0L
|
||||
#endif
|
||||
|
||||
#define COLOR_TITLE COLOR_BLUE
|
||||
#define COLOR_UNSELECTED COLOR_GREY
|
||||
#define COLOR_SELECTED COLOR_WHITE
|
||||
#define COLOR_HIGHLIGHTED COLOR_GREEN
|
||||
|
||||
|
||||
template <class T, typename Fn>
|
||||
static void for_each_(vector<T> &v, Fn func)
|
||||
{
|
||||
for_each(v.begin(), v.end(), func);
|
||||
}
|
||||
|
||||
template <class T, class V, typename Fn>
|
||||
static void for_each_(map<T, V> &v, Fn func)
|
||||
{
|
||||
for_each(v.begin(), v.end(), func);
|
||||
}
|
||||
|
||||
template <class T, class V, typename Fn>
|
||||
static void transform_(vector<T> &src, vector<V> &dst, Fn func)
|
||||
{
|
||||
transform(src.begin(), src.end(), back_inserter(dst), func);
|
||||
}
|
||||
|
||||
typedef int8_t UIColor;
|
||||
|
||||
void OutputString(UIColor color, int &x, int &y, const std::string &text,
|
||||
bool newline = false, int left_margin = 0, const UIColor bg_color = 0)
|
||||
{
|
||||
Screen::paintString(Screen::Pen(' ', color, bg_color), x, y, text);
|
||||
if (newline)
|
||||
{
|
||||
++y;
|
||||
x = left_margin;
|
||||
}
|
||||
else
|
||||
x += text.length();
|
||||
}
|
||||
|
||||
void OutputHotkeyString(int &x, int &y, const char *text, const char *hotkey, bool newline = false,
|
||||
int left_margin = 0, int8_t text_color = COLOR_WHITE, int8_t hotkey_color = COLOR_LIGHTGREEN)
|
||||
{
|
||||
OutputString(hotkey_color, x, y, hotkey);
|
||||
string display(": ");
|
||||
display.append(text);
|
||||
OutputString(text_color, x, y, display, newline, left_margin);
|
||||
}
|
||||
|
||||
void OutputFilterString(int &x, int &y, const char *text, const char *hotkey, bool state, bool newline = false,
|
||||
int left_margin = 0, int8_t hotkey_color = COLOR_LIGHTGREEN)
|
||||
{
|
||||
OutputString(hotkey_color, x, y, hotkey);
|
||||
OutputString(COLOR_WHITE, x, y, ": ");
|
||||
OutputString((state) ? COLOR_WHITE : COLOR_GREY, x, y, text, newline, left_margin);
|
||||
}
|
||||
|
||||
void OutputToggleString(int &x, int &y, const char *text, const char *hotkey, bool state, bool newline = true, int left_margin = 0, int8_t color = COLOR_WHITE)
|
||||
{
|
||||
OutputHotkeyString(x, y, text, hotkey);
|
||||
OutputString(COLOR_WHITE, x, y, ": ");
|
||||
if (state)
|
||||
OutputString(COLOR_GREEN, x, y, "Enabled", newline, left_margin);
|
||||
else
|
||||
OutputString(COLOR_GREY, x, y, "Disabled", newline, left_margin);
|
||||
}
|
||||
|
||||
const int ascii_to_enum_offset = interface_key::STRING_A048 - '0';
|
||||
|
||||
inline string int_to_string(const int n)
|
||||
{
|
||||
return static_cast<ostringstream*>( &(ostringstream() << n) )->str();
|
||||
}
|
||||
|
||||
static void set_to_limit(int &value, const int maximum, const int min = 0)
|
||||
{
|
||||
if (value < min)
|
||||
value = min;
|
||||
else if (value > maximum)
|
||||
value = maximum;
|
||||
}
|
||||
|
||||
|
||||
inline void paint_text(const UIColor color, const int &x, const int &y, const std::string &text, const UIColor background = 0)
|
||||
{
|
||||
Screen::paintString(Screen::Pen(' ', color, background), x, y, text);
|
||||
}
|
||||
|
||||
static string pad_string(string text, const int size, const bool front = true, const bool trim = false)
|
||||
{
|
||||
if (text.length() > size)
|
||||
{
|
||||
if (trim && size > 10)
|
||||
{
|
||||
text = text.substr(0, size-3);
|
||||
text.append("...");
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
string aligned(size - text.length(), ' ');
|
||||
if (front)
|
||||
{
|
||||
aligned.append(text);
|
||||
return aligned;
|
||||
}
|
||||
else
|
||||
{
|
||||
text.append(aligned);
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* List classes
|
||||
*/
|
||||
template <typename T>
|
||||
class ListEntry
|
||||
{
|
||||
public:
|
||||
T elem;
|
||||
string text, keywords;
|
||||
bool selected;
|
||||
|
||||
ListEntry(const string text, const T elem, const string keywords = "") :
|
||||
elem(elem), text(text), selected(false), keywords(keywords)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class ListColumn
|
||||
{
|
||||
public:
|
||||
int highlighted_index;
|
||||
int display_start_offset;
|
||||
unsigned short text_clip_at;
|
||||
int32_t bottom_margin, search_margin, left_margin;
|
||||
bool multiselect;
|
||||
bool allow_null;
|
||||
bool auto_select;
|
||||
bool force_sort;
|
||||
bool allow_search;
|
||||
bool feed_changed_highlight;
|
||||
|
||||
ListColumn()
|
||||
{
|
||||
bottom_margin = 3;
|
||||
clear();
|
||||
left_margin = 2;
|
||||
search_margin = 63;
|
||||
highlighted_index = 0;
|
||||
text_clip_at = 0;
|
||||
multiselect = false;
|
||||
allow_null = true;
|
||||
auto_select = false;
|
||||
force_sort = false;
|
||||
allow_search = true;
|
||||
feed_changed_highlight = false;
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
list.clear();
|
||||
display_list.clear();
|
||||
display_start_offset = 0;
|
||||
max_item_width = title.length();
|
||||
resize();
|
||||
}
|
||||
|
||||
void resize()
|
||||
{
|
||||
display_max_rows = gps->dimy - 4 - bottom_margin;
|
||||
}
|
||||
|
||||
void add(ListEntry<T> &entry)
|
||||
{
|
||||
list.push_back(entry);
|
||||
if (entry.text.length() > max_item_width)
|
||||
max_item_width = entry.text.length();
|
||||
}
|
||||
|
||||
void add(const string &text, const T &elem)
|
||||
{
|
||||
list.push_back(ListEntry<T>(text, elem));
|
||||
if (text.length() > max_item_width)
|
||||
max_item_width = text.length();
|
||||
}
|
||||
|
||||
int fixWidth()
|
||||
{
|
||||
if (text_clip_at > 0 && max_item_width > text_clip_at)
|
||||
max_item_width = text_clip_at;
|
||||
|
||||
for (auto it = list.begin(); it != list.end(); it++)
|
||||
{
|
||||
it->text = pad_string(it->text, max_item_width, false);
|
||||
}
|
||||
|
||||
return left_margin + max_item_width;
|
||||
}
|
||||
|
||||
virtual void display_extras(const T &elem, int32_t &x, int32_t &y) const {}
|
||||
|
||||
void display(const bool is_selected_column) const
|
||||
{
|
||||
int32_t y = 2;
|
||||
paint_text(COLOR_TITLE, left_margin, y, title);
|
||||
|
||||
int last_index_able_to_display = display_start_offset + display_max_rows;
|
||||
for (int i = display_start_offset; i < display_list.size() && i < last_index_able_to_display; i++)
|
||||
{
|
||||
++y;
|
||||
UIColor fg_color = (display_list[i]->selected) ? COLOR_SELECTED : COLOR_UNSELECTED;
|
||||
UIColor bg_color = (is_selected_column && i == highlighted_index) ? COLOR_HIGHLIGHTED : COLOR_BLACK;
|
||||
|
||||
string item_label = display_list[i]->text;
|
||||
if (text_clip_at > 0 && item_label.length() > text_clip_at)
|
||||
item_label.resize(text_clip_at);
|
||||
|
||||
paint_text(fg_color, left_margin, y, item_label, bg_color);
|
||||
int x = left_margin + display_list[i]->text.length() + 1;
|
||||
display_extras(display_list[i]->elem, x, y);
|
||||
}
|
||||
|
||||
if (is_selected_column && allow_search)
|
||||
{
|
||||
y = gps->dimy - 3;
|
||||
int32_t x = search_margin;
|
||||
OutputHotkeyString(x, y, "Search" ,"S");
|
||||
OutputString(COLOR_WHITE, x, y, ": ");
|
||||
OutputString(COLOR_WHITE, x, y, search_string);
|
||||
OutputString(COLOR_LIGHTGREEN, x, y, "_");
|
||||
}
|
||||
}
|
||||
|
||||
void filterDisplay()
|
||||
{
|
||||
ListEntry<T> *prev_selected = (getDisplayListSize() > 0) ? display_list[highlighted_index] : NULL;
|
||||
display_list.clear();
|
||||
|
||||
search_string = toLower(search_string);
|
||||
vector<string> search_tokens;
|
||||
if (!search_string.empty())
|
||||
split_string(&search_tokens, search_string, " ");
|
||||
|
||||
for (size_t i = 0; i < list.size(); i++)
|
||||
{
|
||||
ListEntry<T> *entry = &list[i];
|
||||
|
||||
bool include_item = true;
|
||||
if (!search_string.empty())
|
||||
{
|
||||
string item_string = toLower(list[i].text);
|
||||
for (auto si = search_tokens.begin(); si != search_tokens.end(); si++)
|
||||
{
|
||||
if (!si->empty() && item_string.find(*si) == string::npos &&
|
||||
list[i].keywords.find(*si) == string::npos)
|
||||
{
|
||||
include_item = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (include_item)
|
||||
{
|
||||
display_list.push_back(entry);
|
||||
if (entry == prev_selected)
|
||||
highlighted_index = display_list.size() - 1;
|
||||
}
|
||||
else if (auto_select)
|
||||
{
|
||||
entry->selected = false;
|
||||
}
|
||||
}
|
||||
changeHighlight(0);
|
||||
feed_changed_highlight = true;
|
||||
}
|
||||
|
||||
void selectDefaultEntry()
|
||||
{
|
||||
for (size_t i = 0; i < display_list.size(); i++)
|
||||
{
|
||||
if (display_list[i]->selected)
|
||||
{
|
||||
highlighted_index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void validateHighlight()
|
||||
{
|
||||
set_to_limit(highlighted_index, display_list.size() - 1);
|
||||
|
||||
if (highlighted_index < display_start_offset)
|
||||
display_start_offset = highlighted_index;
|
||||
else if (highlighted_index >= display_start_offset + display_max_rows)
|
||||
display_start_offset = highlighted_index - display_max_rows + 1;
|
||||
|
||||
if (auto_select || (!allow_null && list.size() == 1))
|
||||
display_list[highlighted_index]->selected = true;
|
||||
|
||||
feed_changed_highlight = true;
|
||||
}
|
||||
|
||||
void changeHighlight(const int highlight_change, const int offset_shift = 0)
|
||||
{
|
||||
if (!initHighlightChange())
|
||||
return;
|
||||
|
||||
highlighted_index += highlight_change + offset_shift * display_max_rows;
|
||||
|
||||
display_start_offset += offset_shift * display_max_rows;
|
||||
set_to_limit(display_start_offset, max(0, (int)(display_list.size())-display_max_rows));
|
||||
validateHighlight();
|
||||
}
|
||||
|
||||
void setHighlight(const int index)
|
||||
{
|
||||
if (!initHighlightChange())
|
||||
return;
|
||||
|
||||
highlighted_index = index;
|
||||
validateHighlight();
|
||||
}
|
||||
|
||||
bool initHighlightChange()
|
||||
{
|
||||
if (display_list.size() == 0)
|
||||
return false;
|
||||
|
||||
if (auto_select && !multiselect)
|
||||
{
|
||||
for (auto it = list.begin(); it != list.end(); it++)
|
||||
{
|
||||
it->selected = false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void toggleHighlighted()
|
||||
{
|
||||
if (auto_select)
|
||||
return;
|
||||
|
||||
ListEntry<T> *entry = display_list[highlighted_index];
|
||||
if (!multiselect || !allow_null)
|
||||
{
|
||||
int selected_count = 0;
|
||||
for (size_t i = 0; i < list.size(); i++)
|
||||
{
|
||||
if (!multiselect && !entry->selected)
|
||||
list[i].selected = false;
|
||||
if (!allow_null && list[i].selected)
|
||||
selected_count++;
|
||||
}
|
||||
|
||||
if (!allow_null && entry->selected && selected_count == 1)
|
||||
return;
|
||||
}
|
||||
|
||||
entry->selected = !entry->selected;
|
||||
}
|
||||
|
||||
vector<T> getSelectedElems(bool only_one = false)
|
||||
{
|
||||
vector<T> results;
|
||||
for (auto it = list.begin(); it != list.end(); it++)
|
||||
{
|
||||
if ((*it).selected)
|
||||
{
|
||||
results.push_back(it->elem);
|
||||
if (only_one)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
T getFirstSelectedElem()
|
||||
{
|
||||
vector<T> results = getSelectedElems(true);
|
||||
if (results.size() == 0)
|
||||
return nullptr;
|
||||
else
|
||||
return results[0];
|
||||
}
|
||||
|
||||
void clearSelection()
|
||||
{
|
||||
for_each_(list, [] (ListEntry<T> &e) { e.selected = false; });
|
||||
}
|
||||
|
||||
void selectItem(const T elem)
|
||||
{
|
||||
int i = 0;
|
||||
for (; i < display_list.size(); i++)
|
||||
{
|
||||
if (display_list[i]->elem == elem)
|
||||
{
|
||||
setHighlight(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void clearSearch()
|
||||
{
|
||||
search_string.clear();
|
||||
filterDisplay();
|
||||
}
|
||||
|
||||
size_t getDisplayListSize()
|
||||
{
|
||||
return display_list.size();
|
||||
}
|
||||
|
||||
vector<ListEntry<T>*> &getDisplayList()
|
||||
{
|
||||
return display_list;
|
||||
}
|
||||
|
||||
size_t getBaseListSize()
|
||||
{
|
||||
return list.size();
|
||||
}
|
||||
|
||||
bool feed(set<df::interface_key> *input)
|
||||
{
|
||||
feed_changed_highlight = false;
|
||||
if (input->count(interface_key::CURSOR_UP))
|
||||
{
|
||||
changeHighlight(-1);
|
||||
}
|
||||
else if (input->count(interface_key::CURSOR_DOWN))
|
||||
{
|
||||
changeHighlight(1);
|
||||
}
|
||||
else if (input->count(interface_key::STANDARDSCROLL_PAGEUP))
|
||||
{
|
||||
changeHighlight(0, -1);
|
||||
}
|
||||
else if (input->count(interface_key::STANDARDSCROLL_PAGEDOWN))
|
||||
{
|
||||
changeHighlight(0, 1);
|
||||
}
|
||||
else if (input->count(interface_key::SELECT) && !auto_select)
|
||||
{
|
||||
toggleHighlighted();
|
||||
}
|
||||
else if (input->count(interface_key::CUSTOM_SHIFT_S))
|
||||
{
|
||||
clearSearch();
|
||||
}
|
||||
else if (enabler->tracking_on && gps->mouse_x != -1 && gps->mouse_y != -1 && enabler->mouse_lbut)
|
||||
{
|
||||
return setHighlightByMouse();
|
||||
}
|
||||
else if (allow_search)
|
||||
{
|
||||
// Search query typing mode always on
|
||||
df::interface_key last_token = *input->rbegin();
|
||||
if ((last_token >= interface_key::STRING_A096 && last_token <= interface_key::STRING_A123) ||
|
||||
last_token == interface_key::STRING_A032)
|
||||
{
|
||||
// Standard character
|
||||
search_string += last_token - ascii_to_enum_offset;
|
||||
filterDisplay();
|
||||
}
|
||||
else if (last_token == interface_key::STRING_A000)
|
||||
{
|
||||
// Backspace
|
||||
if (search_string.length() > 0)
|
||||
{
|
||||
search_string.erase(search_string.length()-1);
|
||||
filterDisplay();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool setHighlightByMouse()
|
||||
{
|
||||
if (gps->mouse_y >= 3 && gps->mouse_y < display_max_rows + 3 &&
|
||||
gps->mouse_x >= left_margin && gps->mouse_x < left_margin + max_item_width)
|
||||
{
|
||||
int new_index = display_start_offset + gps->mouse_y - 3;
|
||||
if (new_index < display_list.size())
|
||||
setHighlight(new_index);
|
||||
|
||||
enabler->mouse_lbut = enabler->mouse_rbut = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void sort()
|
||||
{
|
||||
if (force_sort || list.size() < 100)
|
||||
std::sort(list.begin(), list.end(),
|
||||
[] (ListEntry<T> const& a, ListEntry<T> const& b) { return a.text.compare(b.text) < 0; });
|
||||
|
||||
filterDisplay();
|
||||
}
|
||||
|
||||
void setTitle(const string t)
|
||||
{
|
||||
title = t;
|
||||
if (title.length() > max_item_width)
|
||||
max_item_width = title.length();
|
||||
}
|
||||
|
||||
size_t getDisplayedListSize()
|
||||
{
|
||||
return display_list.size();
|
||||
}
|
||||
|
||||
private:
|
||||
vector<ListEntry<T>> list;
|
||||
vector<ListEntry<T>*> display_list;
|
||||
string search_string;
|
||||
string title;
|
||||
int display_max_rows;
|
||||
int max_item_width;
|
||||
};
|
||||
|
||||
|
@ -0,0 +1,93 @@
|
||||
#include "Core.h"
|
||||
#include "Console.h"
|
||||
#include "Export.h"
|
||||
#include "PluginManager.h"
|
||||
#include "DataDefs.h"
|
||||
|
||||
#include "modules/EventManager.h"
|
||||
#include "modules/World.h"
|
||||
|
||||
#include "df/global_objects.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
using namespace std;
|
||||
using namespace DFHack;
|
||||
|
||||
DFHACK_PLUGIN("workNow");
|
||||
|
||||
static int mode = 0;
|
||||
|
||||
DFhackCExport command_result workNow(color_ostream& out, vector<string>& parameters);
|
||||
|
||||
void jobCompletedHandler(color_ostream& out, void* ptr);
|
||||
EventManager::EventHandler handler(jobCompletedHandler,1);
|
||||
|
||||
DFhackCExport command_result plugin_init(color_ostream& out, std::vector<PluginCommand> &commands) {
|
||||
commands.push_back(PluginCommand("workNow", "makes dwarves look for jobs whever they finish one, or every time you pause", workNow, false, "When workNow is active, every time the game pauses, DF will make dwarves perform any appropriate available jobs. This includes when you one step through the game using the pause menu. When workNow is in mode 2, it will make dwarves look for jobs every time a job completes.\n"
|
||||
"workNow\n"
|
||||
" print workNow status\n"
|
||||
"workNow 0\n"
|
||||
" deactivate workNow\n"
|
||||
"workNow 1\n"
|
||||
" activate workNow (look for jobs on pause, and only then)\n"
|
||||
"workNow 2\n"
|
||||
" make dwarves look for jobs whenever a job completes\n"
|
||||
));
|
||||
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
DFhackCExport command_result plugin_shutdown ( color_ostream &out ) {
|
||||
mode = 0;
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event e) {
|
||||
if ( !mode )
|
||||
return CR_OK;
|
||||
if ( e == DFHack::SC_WORLD_UNLOADED ) {
|
||||
mode = 0;
|
||||
return CR_OK;
|
||||
}
|
||||
if ( e != DFHack::SC_PAUSED )
|
||||
return CR_OK;
|
||||
|
||||
*df::global::process_jobs = true;
|
||||
*df::global::process_dig = true;
|
||||
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
DFhackCExport command_result workNow(color_ostream& out, vector<string>& parameters) {
|
||||
if ( parameters.size() == 0 ) {
|
||||
out.print("workNow status = %d\n", mode);
|
||||
return CR_OK;
|
||||
}
|
||||
if ( parameters.size() > 1 ) {
|
||||
return CR_WRONG_USAGE;
|
||||
}
|
||||
int32_t a = atoi(parameters[0].c_str());
|
||||
|
||||
if (a < 0 || a > 2)
|
||||
return CR_WRONG_USAGE;
|
||||
|
||||
if ( a == 2 && mode != 2 ) {
|
||||
EventManager::registerListener(EventManager::EventType::JOB_COMPLETED, handler, plugin_self);
|
||||
} else if ( mode == 2 && a != 2 ) {
|
||||
EventManager::unregister(EventManager::EventType::JOB_COMPLETED, handler, plugin_self);
|
||||
}
|
||||
|
||||
mode = a;
|
||||
out.print("workNow status = %d\n", mode);
|
||||
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
void jobCompletedHandler(color_ostream& out, void* ptr) {
|
||||
if ( mode < 2 )
|
||||
return;
|
||||
|
||||
*df::global::process_jobs = true;
|
||||
*df::global::process_dig = true;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,165 +1,176 @@
|
||||
class AutoFarm
|
||||
|
||||
def initialize
|
||||
@thresholds = Hash.new(50)
|
||||
@lastcounts = Hash.new(0)
|
||||
end
|
||||
|
||||
def setthreshold(id, v)
|
||||
if df.world.raws.plants.all.find { |r| r.id == id }
|
||||
@thresholds[id] = v.to_i
|
||||
else
|
||||
puts "No plant with id #{id}"
|
||||
end
|
||||
end
|
||||
|
||||
def setdefault(v)
|
||||
@thresholds.default = v.to_i
|
||||
end
|
||||
|
||||
def is_plantable (plant)
|
||||
has_seed = plant.flags[:SEED]
|
||||
season = df.cur_season
|
||||
harvest = df.cur_season_tick + plant.growdur * 10
|
||||
will_finish = harvest < 10080
|
||||
can_plant = has_seed && plant.flags[season]
|
||||
can_plant = can_plant && (will_finish || plant.flags[(season+1)%4])
|
||||
can_plant
|
||||
end
|
||||
|
||||
def find_plantable_plants
|
||||
plantable = {}
|
||||
counts = Hash.new(0)
|
||||
|
||||
df.world.items.other[:SEEDS].each { |i|
|
||||
if (!i.flags.dump && !i.flags.forbid && !i.flags.garbage_collect &&
|
||||
!i.flags.hostile && !i.flags.on_fire && !i.flags.rotten &&
|
||||
!i.flags.trader && !i.flags.in_building && !i.flags.construction &&
|
||||
!i.flags.artifact)
|
||||
counts[i.mat_index] = counts[i.mat_index] + i.stack_size
|
||||
end
|
||||
}
|
||||
|
||||
counts.keys.each { |i|
|
||||
if df.ui.tasks.known_plants[i]
|
||||
plant = df.world.raws.plants.all[i]
|
||||
if is_plantable(plant)
|
||||
plantable[i] = :Surface if (plant.underground_depth_min == 0 || plant.underground_depth_max == 0)
|
||||
plantable[i] = :Underground if (plant.underground_depth_min > 0 || plant.underground_depth_max > 0)
|
||||
end
|
||||
end
|
||||
}
|
||||
|
||||
return plantable
|
||||
end
|
||||
|
||||
def set_farms( plants, farms)
|
||||
return if farms.length == 0
|
||||
if plants.length == 0
|
||||
plants = [-1]
|
||||
end
|
||||
|
||||
season = df.cur_season
|
||||
|
||||
idx = 0
|
||||
|
||||
farms.each { |f|
|
||||
f.plant_id[season] = plants[idx]
|
||||
idx = (idx + 1) % plants.length
|
||||
}
|
||||
end
|
||||
|
||||
def process
|
||||
return false unless @running
|
||||
|
||||
plantable = find_plantable_plants
|
||||
counts = Hash.new(0)
|
||||
|
||||
df.world.items.other[:PLANT].each { |i|
|
||||
if (!i.flags.dump && !i.flags.forbid && !i.flags.garbage_collect &&
|
||||
!i.flags.hostile && !i.flags.on_fire && !i.flags.rotten &&
|
||||
!i.flags.trader && !i.flags.in_building && !i.flags.construction &&
|
||||
!i.flags.artifact && plantable.has_key?(i.mat_index))
|
||||
counts[i.mat_index] = counts[i.mat_index] + i.stack_size
|
||||
end
|
||||
}
|
||||
|
||||
plants_s = []
|
||||
plants_u = []
|
||||
|
||||
@lastcounts.clear
|
||||
|
||||
plantable.each_key { |k|
|
||||
plant = df.world.raws.plants.all[k]
|
||||
if (counts[k] < @thresholds[plant.id])
|
||||
plants_s.push(k) if plantable[k] == :Surface
|
||||
plants_u.push(k) if plantable[k] == :Underground
|
||||
end
|
||||
@lastcounts[plant.id] = counts[k]
|
||||
}
|
||||
|
||||
farms_s = []
|
||||
farms_u = []
|
||||
df.world.buildings.other[:FARM_PLOT].each { |f|
|
||||
if (f.flags.exists)
|
||||
outside = df.map_designation_at(f.centerx,f.centery,f.z).outside
|
||||
farms_s.push(f) if outside
|
||||
farms_u.push(f) unless outside
|
||||
end
|
||||
}
|
||||
|
||||
set_farms(plants_s, farms_s)
|
||||
set_farms(plants_u, farms_u)
|
||||
|
||||
end
|
||||
|
||||
def start
|
||||
@onupdate = df.onupdate_register('autofarm', 100) { process }
|
||||
@running = true
|
||||
end
|
||||
|
||||
def stop
|
||||
df.onupdate_unregister(@onupdate)
|
||||
@running = false
|
||||
end
|
||||
|
||||
def status
|
||||
stat = @running ? "Running." : "Stopped."
|
||||
@thresholds.each { |k,v|
|
||||
stat += "\n#{k} limit #{v} current #{@lastcounts[k]}"
|
||||
}
|
||||
stat += "\nDefault: #{@thresholds.default}"
|
||||
stat
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
$AutoFarm = AutoFarm.new unless $AutoFarm
|
||||
|
||||
case $script_args[0]
|
||||
when 'start'
|
||||
$AutoFarm.start
|
||||
|
||||
when 'end', 'stop'
|
||||
$AutoFarm.stop
|
||||
|
||||
when 'default'
|
||||
$AutoFarm.setdefault($script_args[1])
|
||||
|
||||
when 'threshold'
|
||||
t = $script_args[1]
|
||||
$script_args[2..-1].each {|i|
|
||||
$AutoFarm.setthreshold(i, t)
|
||||
}
|
||||
|
||||
when 'delete'
|
||||
$AutoFarm.stop
|
||||
$AutoFarm = nil
|
||||
|
||||
else
|
||||
if $AutoFarm
|
||||
puts $AutoFarm.status
|
||||
else
|
||||
puts "AI not started"
|
||||
end
|
||||
end
|
||||
class AutoFarm
|
||||
|
||||
def initialize
|
||||
@thresholds = Hash.new(50)
|
||||
@lastcounts = Hash.new(0)
|
||||
end
|
||||
|
||||
def setthreshold(id, v)
|
||||
list = df.world.raws.plants.all.find_all { |plt| plt.flags[:SEED] }.map { |plt| plt.id }
|
||||
if tok = df.match_rawname(id, list)
|
||||
@thresholds[tok] = v.to_i
|
||||
else
|
||||
puts "No plant with id #{id}, try one of " +
|
||||
list.map { |w| w =~ /[^\w]/ ? w.inspect : w }.sort.join(' ')
|
||||
end
|
||||
end
|
||||
|
||||
def setdefault(v)
|
||||
@thresholds.default = v.to_i
|
||||
end
|
||||
|
||||
def is_plantable(plant)
|
||||
has_seed = plant.flags[:SEED]
|
||||
season = df.cur_season
|
||||
harvest = df.cur_season_tick + plant.growdur * 10
|
||||
will_finish = harvest < 10080
|
||||
can_plant = has_seed && plant.flags[season]
|
||||
can_plant = can_plant && (will_finish || plant.flags[(season+1)%4])
|
||||
can_plant
|
||||
end
|
||||
|
||||
def find_plantable_plants
|
||||
plantable = {}
|
||||
counts = Hash.new(0)
|
||||
|
||||
df.world.items.other[:SEEDS].each { |i|
|
||||
if (!i.flags.dump && !i.flags.forbid && !i.flags.garbage_collect &&
|
||||
!i.flags.hostile && !i.flags.on_fire && !i.flags.rotten &&
|
||||
!i.flags.trader && !i.flags.in_building && !i.flags.construction &&
|
||||
!i.flags.artifact)
|
||||
counts[i.mat_index] += i.stack_size
|
||||
end
|
||||
}
|
||||
|
||||
counts.keys.each { |i|
|
||||
if df.ui.tasks.known_plants[i]
|
||||
plant = df.world.raws.plants.all[i]
|
||||
if is_plantable(plant)
|
||||
plantable[i] = :Surface if (plant.underground_depth_min == 0 || plant.underground_depth_max == 0)
|
||||
plantable[i] = :Underground if (plant.underground_depth_min > 0 || plant.underground_depth_max > 0)
|
||||
end
|
||||
end
|
||||
}
|
||||
|
||||
return plantable
|
||||
end
|
||||
|
||||
def set_farms(plants, farms)
|
||||
return if farms.length == 0
|
||||
if plants.length == 0
|
||||
plants = [-1]
|
||||
end
|
||||
|
||||
season = df.cur_season
|
||||
|
||||
farms.each_with_index { |f, idx|
|
||||
f.plant_id[season] = plants[idx % plants.length]
|
||||
}
|
||||
end
|
||||
|
||||
def process
|
||||
plantable = find_plantable_plants
|
||||
@lastcounts = Hash.new(0)
|
||||
|
||||
df.world.items.other[:PLANT].each { |i|
|
||||
if (!i.flags.dump && !i.flags.forbid && !i.flags.garbage_collect &&
|
||||
!i.flags.hostile && !i.flags.on_fire && !i.flags.rotten &&
|
||||
!i.flags.trader && !i.flags.in_building && !i.flags.construction &&
|
||||
!i.flags.artifact && plantable.has_key?(i.mat_index))
|
||||
id = df.world.raws.plants.all[i.mat_index].id
|
||||
@lastcounts[id] += i.stack_size
|
||||
end
|
||||
}
|
||||
|
||||
return unless @running
|
||||
|
||||
plants_s = []
|
||||
plants_u = []
|
||||
|
||||
plantable.each_key { |k|
|
||||
plant = df.world.raws.plants.all[k]
|
||||
if (@lastcounts[plant.id] < @thresholds[plant.id])
|
||||
plants_s.push(k) if plantable[k] == :Surface
|
||||
plants_u.push(k) if plantable[k] == :Underground
|
||||
end
|
||||
}
|
||||
|
||||
farms_s = []
|
||||
farms_u = []
|
||||
df.world.buildings.other[:FARM_PLOT].each { |f|
|
||||
if (f.flags.exists)
|
||||
underground = df.map_designation_at(f.centerx,f.centery,f.z).subterranean
|
||||
farms_s.push(f) unless underground
|
||||
farms_u.push(f) if underground
|
||||
end
|
||||
}
|
||||
|
||||
set_farms(plants_s, farms_s)
|
||||
set_farms(plants_u, farms_u)
|
||||
end
|
||||
|
||||
def start
|
||||
return if @running
|
||||
@onupdate = df.onupdate_register('autofarm', 1200) { process }
|
||||
@running = true
|
||||
end
|
||||
|
||||
def stop
|
||||
df.onupdate_unregister(@onupdate)
|
||||
@running = false
|
||||
end
|
||||
|
||||
def status
|
||||
stat = @running ? "Running." : "Stopped."
|
||||
@lastcounts.each { |k,v|
|
||||
stat << "\n#{k} limit #{@thresholds.fetch(k, 'default')} current #{v}"
|
||||
}
|
||||
@thresholds.each { |k,v|
|
||||
stat << "\n#{k} limit #{v} current 0" unless @lastcounts.has_key?(k)
|
||||
}
|
||||
stat << "\nDefault: #{@thresholds.default}"
|
||||
stat
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
$AutoFarm ||= AutoFarm.new
|
||||
|
||||
case $script_args[0]
|
||||
when 'start', 'enable'
|
||||
$AutoFarm.start
|
||||
puts $AutoFarm.status
|
||||
|
||||
when 'end', 'stop', 'disable'
|
||||
$AutoFarm.stop
|
||||
puts 'Stopped.'
|
||||
|
||||
when 'default'
|
||||
$AutoFarm.setdefault($script_args[1])
|
||||
|
||||
when 'threshold'
|
||||
t = $script_args[1]
|
||||
$script_args[2..-1].each {|i|
|
||||
$AutoFarm.setthreshold(i, t)
|
||||
}
|
||||
|
||||
when 'delete'
|
||||
$AutoFarm.stop
|
||||
$AutoFarm = nil
|
||||
|
||||
when 'help', '?'
|
||||
puts <<EOS
|
||||
Automatically handle crop selection in farm plots based on current plant stocks.
|
||||
Selects a crop for planting if current stock is below a threshold.
|
||||
Selected crops are dispatched on all farmplots.
|
||||
|
||||
Usage:
|
||||
autofarm start
|
||||
autofarm default 30
|
||||
autofarm threshold 150 helmet_plump tail_pig
|
||||
EOS
|
||||
|
||||
else
|
||||
$AutoFarm.process
|
||||
puts $AutoFarm.status
|
||||
end
|
||||
|
@ -1,58 +1,58 @@
|
||||
class AutoUnsuspend
|
||||
|
||||
def initialize
|
||||
end
|
||||
|
||||
def process
|
||||
return false unless @running
|
||||
joblist = df.world.job_list.next
|
||||
count = 0
|
||||
|
||||
while joblist
|
||||
job = joblist.item
|
||||
joblist = joblist.next
|
||||
|
||||
if job.job_type == :ConstructBuilding
|
||||
if (job.flags.suspend)
|
||||
item = job.items[0].item
|
||||
job.flags.suspend = false
|
||||
count += 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
puts "Unsuspended #{count} job(s)." unless count == 0
|
||||
|
||||
end
|
||||
|
||||
def start
|
||||
@onupdate = df.onupdate_register('autounsuspend', 5) { process }
|
||||
@running = true
|
||||
end
|
||||
|
||||
def stop
|
||||
df.onupdate_unregister(@onupdate)
|
||||
@running = false
|
||||
end
|
||||
|
||||
def status
|
||||
@running ? 'Running.' : 'Stopped.'
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
case $script_args[0]
|
||||
when 'start'
|
||||
$AutoUnsuspend = AutoUnsuspend.new unless $AutoUnsuspend
|
||||
$AutoUnsuspend.start
|
||||
|
||||
when 'end', 'stop'
|
||||
$AutoUnsuspend.stop
|
||||
|
||||
else
|
||||
if $AutoUnsuspend
|
||||
puts $AutoUnsuspend.status
|
||||
else
|
||||
puts 'Not loaded.'
|
||||
end
|
||||
end
|
||||
class AutoUnsuspend
|
||||
|
||||
def initialize
|
||||
end
|
||||
|
||||
def process
|
||||
return false unless @running
|
||||
joblist = df.world.job_list.next
|
||||
count = 0
|
||||
|
||||
while joblist
|
||||
job = joblist.item
|
||||
joblist = joblist.next
|
||||
|
||||
if job.job_type == :ConstructBuilding
|
||||
if (job.flags.suspend)
|
||||
item = job.items[0].item
|
||||
job.flags.suspend = false
|
||||
count += 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
puts "Unsuspended #{count} job(s)." unless count == 0
|
||||
|
||||
end
|
||||
|
||||
def start
|
||||
@onupdate = df.onupdate_register('autounsuspend', 5) { process }
|
||||
@running = true
|
||||
end
|
||||
|
||||
def stop
|
||||
df.onupdate_unregister(@onupdate)
|
||||
@running = false
|
||||
end
|
||||
|
||||
def status
|
||||
@running ? 'Running.' : 'Stopped.'
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
case $script_args[0]
|
||||
when 'start'
|
||||
$AutoUnsuspend = AutoUnsuspend.new unless $AutoUnsuspend
|
||||
$AutoUnsuspend.start
|
||||
|
||||
when 'end', 'stop'
|
||||
$AutoUnsuspend.stop
|
||||
|
||||
else
|
||||
if $AutoUnsuspend
|
||||
puts $AutoUnsuspend.status
|
||||
else
|
||||
puts 'Not loaded.'
|
||||
end
|
||||
end
|
||||
|
@ -1,161 +1,177 @@
|
||||
# create arbitrary items under cursor
|
||||
|
||||
category = $script_args[0] || 'help'
|
||||
mat_raw = $script_args[1] || 'list'
|
||||
count = $script_args[2]
|
||||
|
||||
|
||||
category = df.match_rawname(category, ['help', 'bars', 'boulders', 'plants', 'logs', 'webs']) || 'help'
|
||||
|
||||
if category == 'help'
|
||||
puts <<EOS
|
||||
Create items under the cursor.
|
||||
Usage:
|
||||
create [category] [raws token] [number]
|
||||
|
||||
Item categories:
|
||||
bars, boulders, plants, logs, web
|
||||
|
||||
Raw token:
|
||||
either a full token (PLANT_MAT:ADLER:WOOD) or the middle part only
|
||||
(the missing part is autocompleted depending on the item category)
|
||||
use 'list' to show all possibilities
|
||||
|
||||
Exemples:
|
||||
create boulders hematite 30
|
||||
create bars CREATURE_MAT:CAT:SOAP 10
|
||||
create web cave_giant
|
||||
create plants list
|
||||
|
||||
EOS
|
||||
throw :script_finished
|
||||
|
||||
elsif mat_raw == 'list'
|
||||
# allowed with no cursor
|
||||
|
||||
elsif df.cursor.x == -30000
|
||||
puts "Please place the game cursor somewhere"
|
||||
throw :script_finished
|
||||
|
||||
elsif !(maptile = df.map_tile_at(df.cursor))
|
||||
puts "Error: unallocated map block !"
|
||||
throw :script_finished
|
||||
|
||||
elsif !maptile.shape_passablehigh
|
||||
puts "Error: impassible tile !"
|
||||
throw :script_finished
|
||||
end
|
||||
|
||||
|
||||
def match_list(tok, list)
|
||||
if tok != 'list'
|
||||
tok = df.match_rawname(tok, list)
|
||||
if not tok
|
||||
puts "Invalid raws token, use one in:"
|
||||
tok = 'list'
|
||||
end
|
||||
end
|
||||
if tok == 'list'
|
||||
puts list.map { |w| w =~ /[^\w]/ ? w.inspect : w }.join(' ')
|
||||
throw :script_finished
|
||||
end
|
||||
tok
|
||||
end
|
||||
|
||||
|
||||
case category
|
||||
when 'bars'
|
||||
# create metal bar, eg createbar INORGANIC:IRON
|
||||
cls = DFHack::ItemBarst
|
||||
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
|
||||
list = df.world.raws.inorganics.find_all { |ino|
|
||||
ino.material.flags[:IS_METAL]
|
||||
}.map { |ino| ino.id }
|
||||
mat_raw = match_list(mat_raw, list)
|
||||
mat_raw = "INORGANIC:#{mat_raw}"
|
||||
puts mat_raw
|
||||
end
|
||||
customize = lambda { |item|
|
||||
item.dimension = 150
|
||||
item.subtype = -1
|
||||
}
|
||||
|
||||
when 'boulders'
|
||||
cls = DFHack::ItemBoulderst
|
||||
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
|
||||
list = df.world.raws.inorganics.find_all { |ino|
|
||||
ino.material.flags[:IS_STONE]
|
||||
}.map { |ino| ino.id }
|
||||
mat_raw = match_list(mat_raw, list)
|
||||
mat_raw = "INORGANIC:#{mat_raw}"
|
||||
puts mat_raw
|
||||
end
|
||||
|
||||
when 'plants'
|
||||
cls = DFHack::ItemPlantst
|
||||
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
|
||||
list = df.world.raws.plants.all.find_all { |plt|
|
||||
plt.material.find { |mat| mat.id == 'STRUCTURAL' }
|
||||
}.map { |plt| plt.id }
|
||||
mat_raw = match_list(mat_raw, list)
|
||||
mat_raw = "PLANT_MAT:#{mat_raw}:STRUCTURAL"
|
||||
puts mat_raw
|
||||
end
|
||||
|
||||
when 'logs'
|
||||
cls = DFHack::ItemWoodst
|
||||
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
|
||||
list = df.world.raws.plants.all.find_all { |plt|
|
||||
plt.material.find { |mat| mat.id == 'WOOD' }
|
||||
}.map { |plt| plt.id }
|
||||
mat_raw = match_list(mat_raw, list)
|
||||
mat_raw = "PLANT_MAT:#{mat_raw}:WOOD"
|
||||
puts mat_raw
|
||||
end
|
||||
|
||||
when 'webs'
|
||||
cls = DFHack::ItemThreadst
|
||||
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
|
||||
list = df.world.raws.creatures.all.find_all { |cre|
|
||||
cre.material.find { |mat| mat.id == 'SILK' }
|
||||
}.map { |cre| cre.creature_id }
|
||||
mat_raw = match_list(mat_raw, list)
|
||||
mat_raw = "CREATURE_MAT:#{mat_raw}:SILK"
|
||||
puts mat_raw
|
||||
end
|
||||
count ||= 1
|
||||
customize = lambda { |item|
|
||||
item.flags.spider_web = true
|
||||
item.dimension = 15000 # XXX may depend on creature (this is for GCS)
|
||||
}
|
||||
|
||||
end
|
||||
|
||||
|
||||
mat = df.decode_mat mat_raw
|
||||
|
||||
count ||= 20
|
||||
count.to_i.times {
|
||||
item = cls.cpp_new
|
||||
item.id = df.item_next_id
|
||||
item.stack_size = 1
|
||||
item.mat_type = mat.mat_type
|
||||
item.mat_index = mat.mat_index
|
||||
|
||||
customize[item] if customize
|
||||
|
||||
df.item_next_id += 1
|
||||
item.categorize(true)
|
||||
df.world.items.all << item
|
||||
|
||||
item.pos = df.cursor
|
||||
item.flags.on_ground = true
|
||||
df.map_tile_at.mapblock.items << item.id
|
||||
df.map_tile_at.occupancy.item = true
|
||||
}
|
||||
|
||||
|
||||
# move game view, so that the ui menu updates
|
||||
df.curview.feed_keys(:CURSOR_UP_Z)
|
||||
df.curview.feed_keys(:CURSOR_DOWN_Z)
|
||||
|
||||
# create first necessity items under cursor
|
||||
|
||||
category = $script_args[0] || 'help'
|
||||
mat_raw = $script_args[1] || 'list'
|
||||
count = $script_args[2]
|
||||
|
||||
|
||||
category = df.match_rawname(category, ['help', 'bars', 'boulders', 'plants', 'logs', 'webs', 'anvils']) || 'help'
|
||||
|
||||
if category == 'help'
|
||||
puts <<EOS
|
||||
Create first necessity items under the cursor.
|
||||
Usage:
|
||||
create-items [category] [raws token] [number]
|
||||
|
||||
Item categories:
|
||||
bars, boulders, plants, logs, webs, anvils
|
||||
|
||||
Raw token:
|
||||
Either a full token (PLANT_MAT:ADLER:WOOD) or the middle part only
|
||||
(the missing part is autocompleted depending on the item category)
|
||||
Use 'list' to show all possibilities
|
||||
|
||||
Exemples:
|
||||
create-items boulders hematite 30
|
||||
create-items bars CREATURE_MAT:CAT:SOAP 10
|
||||
create-items web cave_giant
|
||||
create-items plants list
|
||||
|
||||
EOS
|
||||
throw :script_finished
|
||||
|
||||
elsif mat_raw == 'list'
|
||||
# allowed with no cursor
|
||||
|
||||
elsif df.cursor.x == -30000
|
||||
puts "Please place the game cursor somewhere"
|
||||
throw :script_finished
|
||||
|
||||
elsif !(maptile = df.map_tile_at(df.cursor))
|
||||
puts "Error: unallocated map block !"
|
||||
throw :script_finished
|
||||
|
||||
elsif !maptile.shape_passablehigh
|
||||
puts "Error: impassible tile !"
|
||||
throw :script_finished
|
||||
end
|
||||
|
||||
|
||||
def match_list(tok, list)
|
||||
if tok != 'list'
|
||||
tok = df.match_rawname(tok, list)
|
||||
if not tok
|
||||
puts "Invalid raws token, use one in:"
|
||||
tok = 'list'
|
||||
end
|
||||
end
|
||||
if tok == 'list'
|
||||
puts list.map { |w| w =~ /[^\w]/ ? w.inspect : w }.join(' ')
|
||||
throw :script_finished
|
||||
end
|
||||
tok
|
||||
end
|
||||
|
||||
|
||||
case category
|
||||
when 'bars'
|
||||
# create metal bar, eg createbar INORGANIC:IRON
|
||||
cls = DFHack::ItemBarst
|
||||
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
|
||||
list = df.world.raws.inorganics.find_all { |ino|
|
||||
ino.material.flags[:IS_METAL]
|
||||
}.map { |ino| ino.id }
|
||||
mat_raw = match_list(mat_raw, list)
|
||||
mat_raw = "INORGANIC:#{mat_raw}"
|
||||
puts mat_raw
|
||||
end
|
||||
customize = lambda { |item|
|
||||
item.dimension = 150
|
||||
item.subtype = -1
|
||||
}
|
||||
|
||||
when 'boulders'
|
||||
cls = DFHack::ItemBoulderst
|
||||
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
|
||||
list = df.world.raws.inorganics.find_all { |ino|
|
||||
ino.material.flags[:IS_STONE]
|
||||
}.map { |ino| ino.id }
|
||||
mat_raw = match_list(mat_raw, list)
|
||||
mat_raw = "INORGANIC:#{mat_raw}"
|
||||
puts mat_raw
|
||||
end
|
||||
|
||||
when 'plants'
|
||||
cls = DFHack::ItemPlantst
|
||||
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
|
||||
list = df.world.raws.plants.all.find_all { |plt|
|
||||
plt.material.find { |mat| mat.id == 'STRUCTURAL' }
|
||||
}.map { |plt| plt.id }
|
||||
mat_raw = match_list(mat_raw, list)
|
||||
mat_raw = "PLANT_MAT:#{mat_raw}:STRUCTURAL"
|
||||
puts mat_raw
|
||||
end
|
||||
|
||||
when 'logs'
|
||||
cls = DFHack::ItemWoodst
|
||||
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
|
||||
list = df.world.raws.plants.all.find_all { |plt|
|
||||
plt.material.find { |mat| mat.id == 'WOOD' }
|
||||
}.map { |plt| plt.id }
|
||||
mat_raw = match_list(mat_raw, list)
|
||||
mat_raw = "PLANT_MAT:#{mat_raw}:WOOD"
|
||||
puts mat_raw
|
||||
end
|
||||
|
||||
when 'webs'
|
||||
cls = DFHack::ItemThreadst
|
||||
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
|
||||
list = df.world.raws.creatures.all.find_all { |cre|
|
||||
cre.material.find { |mat| mat.id == 'SILK' }
|
||||
}.map { |cre| cre.creature_id }
|
||||
mat_raw = match_list(mat_raw, list)
|
||||
mat_raw = "CREATURE_MAT:#{mat_raw}:SILK"
|
||||
puts mat_raw
|
||||
end
|
||||
count ||= 1
|
||||
customize = lambda { |item|
|
||||
item.flags.spider_web = true
|
||||
item.dimension = 15000 # XXX may depend on creature (this is for GCS)
|
||||
}
|
||||
|
||||
when 'anvils'
|
||||
cls = DFHack::ItemAnvilst
|
||||
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
|
||||
list = df.world.raws.inorganics.find_all { |ino|
|
||||
ino.material.flags[:IS_METAL]
|
||||
}.map { |ino| ino.id }
|
||||
mat_raw = match_list(mat_raw, list)
|
||||
mat_raw = "INORGANIC:#{mat_raw}"
|
||||
puts mat_raw
|
||||
end
|
||||
count ||= 1
|
||||
|
||||
end
|
||||
|
||||
|
||||
mat = df.decode_mat mat_raw
|
||||
|
||||
count ||= 20
|
||||
count.to_i.times {
|
||||
item = cls.cpp_new
|
||||
item.id = df.item_next_id
|
||||
item.stack_size = 1
|
||||
item.mat_type = mat.mat_type
|
||||
item.mat_index = mat.mat_index
|
||||
|
||||
customize[item] if customize
|
||||
|
||||
df.item_next_id += 1
|
||||
item.categorize(true)
|
||||
df.world.items.all << item
|
||||
|
||||
item.pos = df.cursor
|
||||
item.flags.on_ground = true
|
||||
df.map_tile_at.mapblock.items << item.id
|
||||
df.map_tile_at.occupancy.item = true
|
||||
}
|
||||
|
||||
|
||||
# move game view, so that the ui menu updates
|
||||
if df.cursor.z > 5
|
||||
df.curview.feed_keys(:CURSOR_DOWN_Z)
|
||||
df.curview.feed_keys(:CURSOR_UP_Z)
|
||||
else
|
||||
df.curview.feed_keys(:CURSOR_UP_Z)
|
||||
df.curview.feed_keys(:CURSOR_DOWN_Z)
|
||||
end
|
||||
|
@ -1,67 +1,67 @@
|
||||
# show death cause of a creature
|
||||
|
||||
def display_death_event(e)
|
||||
str = "The #{e.victim_hf_tg.race_tg.name[0]} #{e.victim_hf_tg.name} died in year #{e.year}"
|
||||
str << " (cause: #{e.death_cause.to_s.downcase}),"
|
||||
str << " killed by the #{e.slayer_race_tg.name[0]} #{e.slayer_hf_tg.name}" if e.slayer_hf != -1
|
||||
str << " using a #{df.world.raws.itemdefs.weapons[e.weapon.item_subtype].name}" if e.weapon.item_type == :WEAPON
|
||||
str << ", shot by a #{df.world.raws.itemdefs.weapons[e.weapon.bow_item_subtype].name}" if e.weapon.bow_item_type == :WEAPON
|
||||
|
||||
puts str.chomp(',') + '.'
|
||||
end
|
||||
|
||||
def display_death_unit(u)
|
||||
death_info = u.counters.death_tg
|
||||
killer = death_info.killer_tg if death_info
|
||||
|
||||
str = "The #{u.race_tg.name[0]}"
|
||||
str << " #{u.name}" if u.name.has_name
|
||||
str << " died"
|
||||
str << " in year #{death_info.event_year}" if death_info
|
||||
str << " (cause: #{u.counters.death_cause.to_s.downcase})," if u.counters.death_cause != -1
|
||||
str << " killed by the #{killer.race_tg.name[0]} #{killer.name}" if killer
|
||||
|
||||
puts str.chomp(',') + '.'
|
||||
end
|
||||
|
||||
item = df.item_find(:selected)
|
||||
unit = df.unit_find(:selected)
|
||||
|
||||
if !item or !item.kind_of?(DFHack::ItemBodyComponent)
|
||||
item = df.world.items.other[:ANY_CORPSE].find { |i| df.at_cursor?(i) }
|
||||
end
|
||||
|
||||
if item and item.kind_of?(DFHack::ItemBodyComponent)
|
||||
hf = item.hist_figure_id
|
||||
elsif unit
|
||||
hf = unit.hist_figure_id
|
||||
end
|
||||
|
||||
if not hf
|
||||
puts "Please select a corpse in the loo'k' menu, or an unit in the 'u'nitlist screen"
|
||||
|
||||
elsif hf == -1
|
||||
if unit ||= item.unit_tg
|
||||
display_death_unit(unit)
|
||||
else
|
||||
puts "Not a historical figure, cannot death find info"
|
||||
end
|
||||
|
||||
else
|
||||
histfig = df.world.history.figures.binsearch(hf)
|
||||
unit = histfig ? df.unit_find(histfig.unit_id) : nil
|
||||
if unit and not unit.flags1.dead and not unit.flags3.ghostly
|
||||
puts "#{unit.name} is not dead yet !"
|
||||
|
||||
else
|
||||
events = df.world.history.events
|
||||
(0...events.length).reverse_each { |i|
|
||||
e = events[i]
|
||||
if e.kind_of?(DFHack::HistoryEventHistFigureDiedst) and e.victim_hf == hf
|
||||
display_death_event(e)
|
||||
break
|
||||
end
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
# show death cause of a creature
|
||||
|
||||
def display_death_event(e)
|
||||
str = "The #{e.victim_hf_tg.race_tg.name[0]} #{e.victim_hf_tg.name} died in year #{e.year}"
|
||||
str << " (cause: #{e.death_cause.to_s.downcase}),"
|
||||
str << " killed by the #{e.slayer_race_tg.name[0]} #{e.slayer_hf_tg.name}" if e.slayer_hf != -1
|
||||
str << " using a #{df.world.raws.itemdefs.weapons[e.weapon.item_subtype].name}" if e.weapon.item_type == :WEAPON
|
||||
str << ", shot by a #{df.world.raws.itemdefs.weapons[e.weapon.bow_item_subtype].name}" if e.weapon.bow_item_type == :WEAPON
|
||||
|
||||
puts str.chomp(',') + '.'
|
||||
end
|
||||
|
||||
def display_death_unit(u)
|
||||
death_info = u.counters.death_tg
|
||||
killer = death_info.killer_tg if death_info
|
||||
|
||||
str = "The #{u.race_tg.name[0]}"
|
||||
str << " #{u.name}" if u.name.has_name
|
||||
str << " died"
|
||||
str << " in year #{death_info.event_year}" if death_info
|
||||
str << " (cause: #{u.counters.death_cause.to_s.downcase})," if u.counters.death_cause != -1
|
||||
str << " killed by the #{killer.race_tg.name[0]} #{killer.name}" if killer
|
||||
|
||||
puts str.chomp(',') + '.'
|
||||
end
|
||||
|
||||
item = df.item_find(:selected)
|
||||
unit = df.unit_find(:selected)
|
||||
|
||||
if !item or !item.kind_of?(DFHack::ItemBodyComponent)
|
||||
item = df.world.items.other[:ANY_CORPSE].find { |i| df.at_cursor?(i) }
|
||||
end
|
||||
|
||||
if item and item.kind_of?(DFHack::ItemBodyComponent)
|
||||
hf = item.hist_figure_id
|
||||
elsif unit
|
||||
hf = unit.hist_figure_id
|
||||
end
|
||||
|
||||
if not hf
|
||||
puts "Please select a corpse in the loo'k' menu, or an unit in the 'u'nitlist screen"
|
||||
|
||||
elsif hf == -1
|
||||
if unit ||= item.unit_tg
|
||||
display_death_unit(unit)
|
||||
else
|
||||
puts "Not a historical figure, cannot death find info"
|
||||
end
|
||||
|
||||
else
|
||||
histfig = df.world.history.figures.binsearch(hf)
|
||||
unit = histfig ? df.unit_find(histfig.unit_id) : nil
|
||||
if unit and not unit.flags1.dead and not unit.flags3.ghostly
|
||||
puts "#{unit.name} is not dead yet !"
|
||||
|
||||
else
|
||||
events = df.world.history.events
|
||||
(0...events.length).reverse_each { |i|
|
||||
e = events[i]
|
||||
if e.kind_of?(DFHack::HistoryEventHistFigureDiedst) and e.victim_hf == hf
|
||||
display_death_event(e)
|
||||
break
|
||||
end
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -0,0 +1,18 @@
|
||||
-- invasion-now civ_id delay : schedules an invasion in the near future
|
||||
|
||||
args = {...}
|
||||
num = tonumber(args[1])
|
||||
|
||||
if ( num ~= nil ) then
|
||||
time = tonumber(args[2]) or 1
|
||||
time2 = tonumber(args[3]) or time
|
||||
for i = time,time2 do
|
||||
df.global.timed_events:insert('#',{
|
||||
new = df.timed_event,
|
||||
type = df.timed_event_type.CivAttack,
|
||||
season = df.global.cur_season,
|
||||
season_ticks = df.global.cur_season_tick+i,
|
||||
entity = df.historical_entity.find(num)
|
||||
})
|
||||
end
|
||||
end
|
@ -0,0 +1,10 @@
|
||||
# list indexes in world.item.other[] where current selected item appears
|
||||
|
||||
tg = df.item_find
|
||||
raise 'select an item' if not tg
|
||||
|
||||
o = df.world.items.other
|
||||
# discard ANY/BAD
|
||||
o._indexenum::ENUM.sort.transpose[1][1..-2].each { |k|
|
||||
puts k if o[k].find { |i| i == tg }
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
# unforbid all items
|
||||
|
||||
df.world.items.all.each { |i| i.flags.forbid = false }
|
@ -1,38 +1,38 @@
|
||||
# designate an area for digging according to a plan in csv format
|
||||
|
||||
raise "usage: digfort <plan filename>" if not $script_args[0]
|
||||
planfile = File.read($script_args[0])
|
||||
|
||||
if df.cursor.x == -30000
|
||||
raise "place the game cursor to the top-left corner of the design"
|
||||
end
|
||||
|
||||
tiles = planfile.lines.map { |l|
|
||||
l.sub(/#.*/, '').split(';').map { |t| t.strip }
|
||||
}
|
||||
|
||||
x = x0 = df.cursor.x
|
||||
y = df.cursor.y
|
||||
z = df.cursor.z
|
||||
|
||||
tiles.each { |line|
|
||||
next if line.empty? or line == ['']
|
||||
line.each { |tile|
|
||||
t = df.map_tile_at(x, y, z)
|
||||
s = t.shape_basic
|
||||
case tile
|
||||
when 'd'; t.dig(:Default) if s == :Wall
|
||||
when 'u'; t.dig(:UpStair) if s == :Wall
|
||||
when 'j'; t.dig(:DownStair) if s == :Wall or s == :Floor
|
||||
when 'i'; t.dig(:UpDownStair) if s == :Wall
|
||||
when 'h'; t.dig(:Channel) if s == :Wall or s == :Floor
|
||||
when 'r'; t.dig(:Ramp) if s == :Wall
|
||||
when 'x'; t.dig(:No)
|
||||
end
|
||||
x += 1
|
||||
}
|
||||
x = x0
|
||||
y += 1
|
||||
}
|
||||
|
||||
puts 'done'
|
||||
# designate an area for digging according to a plan in csv format
|
||||
|
||||
raise "usage: digfort <plan filename>" if not $script_args[0]
|
||||
planfile = File.read($script_args[0])
|
||||
|
||||
if df.cursor.x == -30000
|
||||
raise "place the game cursor to the top-left corner of the design"
|
||||
end
|
||||
|
||||
tiles = planfile.lines.map { |l|
|
||||
l.sub(/#.*/, '').split(';').map { |t| t.strip }
|
||||
}
|
||||
|
||||
x = x0 = df.cursor.x
|
||||
y = df.cursor.y
|
||||
z = df.cursor.z
|
||||
|
||||
tiles.each { |line|
|
||||
next if line.empty? or line == ['']
|
||||
line.each { |tile|
|
||||
t = df.map_tile_at(x, y, z)
|
||||
s = t.shape_basic
|
||||
case tile
|
||||
when 'd'; t.dig(:Default) if s == :Wall
|
||||
when 'u'; t.dig(:UpStair) if s == :Wall
|
||||
when 'j'; t.dig(:DownStair) if s == :Wall or s == :Floor
|
||||
when 'i'; t.dig(:UpDownStair) if s == :Wall
|
||||
when 'h'; t.dig(:Channel) if s == :Wall or s == :Floor
|
||||
when 'r'; t.dig(:Ramp) if s == :Wall
|
||||
when 'x'; t.dig(:No)
|
||||
end
|
||||
x += 1
|
||||
}
|
||||
x = x0
|
||||
y += 1
|
||||
}
|
||||
|
||||
puts 'done'
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue