Merge branch 'develop' of github.com:DFHack/dfhack into remote_reader
commit
48e714b65b
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,42 @@
|
||||
-- lua/plugins/repeatUtil.lua
|
||||
-- author expwnent
|
||||
-- tools for registering callbacks periodically
|
||||
-- vaguely based on a script by Putnam
|
||||
|
||||
local _ENV = mkmodule("repeat-util")
|
||||
|
||||
repeating = repeating or {}
|
||||
|
||||
dfhack.onStateChange.repeatUtilStateChange = function(code)
|
||||
if code == SC_WORLD_UNLOADED then
|
||||
repeating = {}
|
||||
end
|
||||
end
|
||||
|
||||
function cancel(name)
|
||||
if not repeating[name] then
|
||||
return false
|
||||
end
|
||||
dfhack.timeout_active(repeating[name],nil)
|
||||
repeating[name] = nil
|
||||
return true
|
||||
end
|
||||
|
||||
function scheduleEvery(name,time,timeUnits,func)
|
||||
cancel(name)
|
||||
local function helper()
|
||||
func()
|
||||
repeating[name] = dfhack.timeout(time,timeUnits,helper)
|
||||
end
|
||||
helper()
|
||||
end
|
||||
|
||||
function scheduleUnlessAlreadyScheduled(name,time,timeUnits,func)
|
||||
if repeating[name] then
|
||||
return
|
||||
end
|
||||
scheduleEvery(name,time,timeUnits,func)
|
||||
end
|
||||
|
||||
return _ENV
|
||||
|
@ -0,0 +1,154 @@
|
||||
--lua/syndrome-util.lua
|
||||
--author expwnent
|
||||
--some utilities for adding syndromes to units
|
||||
|
||||
local _ENV = mkmodule("syndrome-util")
|
||||
local utils = require("utils")
|
||||
|
||||
function findUnitSyndrome(unit,syn_id)
|
||||
for index,syndrome in ipairs(unit.syndromes.active) do
|
||||
if syndrome['type'] == syn_id then
|
||||
return syndrome
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
--usage: syndromeUtil.ResetPolicy.DoNothing, syndromeUtil.ResetPolicy.ResetDuration, etc
|
||||
ResetPolicy = ResetPolicy or utils.invert({
|
||||
"DoNothing",
|
||||
"ResetDuration",
|
||||
"AddDuration",
|
||||
"NewInstance"
|
||||
})
|
||||
|
||||
function eraseSyndrome(unit,syndromeId,oldestFirst)
|
||||
local i1
|
||||
local iN
|
||||
local d
|
||||
if oldestFirst then
|
||||
i1 = 0
|
||||
iN = #unit.syndromes.active-1
|
||||
d = 1
|
||||
else
|
||||
i1 = #unit.syndromes.active-1
|
||||
iN = 0
|
||||
d = -1
|
||||
end
|
||||
local syndromes = unit.syndromes.active
|
||||
for i=i1,iN,d do
|
||||
if syndromes[i]['type'] == syndromeId then
|
||||
syndromes:erase(i)
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function eraseSyndromes(unit,syndromeId)
|
||||
local count=0
|
||||
while eraseSyndrome(unit,syndromeId,true) do
|
||||
count = count+1
|
||||
end
|
||||
return count
|
||||
end
|
||||
|
||||
--target is a df.unit, syndrome is a df.syndrome, resetPolicy is one of syndromeUtil.ResetPolicy
|
||||
--if the target has an instance of the syndrome already, the reset policy takes effect
|
||||
--returns true if the unit did not have the syndrome before calling and false otherwise
|
||||
function infectWithSyndrome(target,syndrome,resetPolicy)
|
||||
local oldSyndrome = findUnitSyndrome(target,syndrome.id)
|
||||
if oldSyndrome == nil or resetPolicy == nil or resetPolicy == ResetPolicy.NewInstance then
|
||||
local unitSyndrome = df.unit_syndrome:new()
|
||||
unitSyndrome.type = syndrome.id
|
||||
unitSyndrome.year = df.global.cur_year
|
||||
unitSyndrome.year_time = df.global.cur_year_tick
|
||||
unitSyndrome.ticks = 0
|
||||
unitSyndrome.wound_id = -1
|
||||
for k,v in ipairs(syndrome.ce) do
|
||||
local symptom = df.unit_syndrome.T_symptoms:new()
|
||||
symptom.quantity = 0
|
||||
symptom.delay = 0
|
||||
symptom.ticks = 0
|
||||
symptom.flags.active = true
|
||||
unitSyndrome.symptoms:insert("#",symptom)
|
||||
end
|
||||
target.syndromes.active:insert("#",unitSyndrome)
|
||||
elseif resetPolicy == ResetPolicy.DoNothing then
|
||||
elseif resetPolicy == ResetPolicy.ResetDuration then
|
||||
for k,symptom in ipairs(oldSyndrome.symptoms) do
|
||||
symptom.ticks = 0
|
||||
end
|
||||
oldSyndrome.ticks = 0
|
||||
elseif resetPolicy == ResetPolicy.AddDuration then
|
||||
for k,symptom in ipairs(oldSyndrome.symptoms) do
|
||||
--really it's syndrome.ce[k].end, but lua doesn't like that because keywords
|
||||
if syndrome.ce[k]["end"] ~= -1 then
|
||||
symptom.ticks = symptom.ticks - syndrome.ce[k]["end"]
|
||||
end
|
||||
end
|
||||
else qerror("Bad reset policy: " .. resetPolicy)
|
||||
end
|
||||
return (oldSyndrome == nil)
|
||||
end
|
||||
|
||||
function isValidTarget(unit,syndrome)
|
||||
--mostly copied from itemsyndrome, which is based on autoSyndrome
|
||||
if
|
||||
#syndrome.syn_affected_class==0
|
||||
and #syndrome.syn_affected_creature==0
|
||||
and #syndrome.syn_affected_caste==0
|
||||
and #syndrome.syn_immune_class==0
|
||||
and #syndrome.syn_immune_creature==0
|
||||
and #syndrome.syn_immune_caste==0
|
||||
then
|
||||
return true
|
||||
end
|
||||
local affected = false
|
||||
local unitRaws = df.creature_raw.find(unit.race)
|
||||
local casteRaws = unitRaws.caste[unit.caste]
|
||||
local unitRaceName = unitRaws.creature_id
|
||||
local unitCasteName = casteRaws.caste_id
|
||||
local unitClasses = casteRaws.creature_class
|
||||
for _,unitClass in ipairs(unitClasses) do
|
||||
for _,syndromeClass in ipairs(syndrome.syn_affected_class) do
|
||||
if unitClass.value==syndromeClass.value then
|
||||
affected = true
|
||||
end
|
||||
end
|
||||
end
|
||||
for caste,creature in ipairs(syndrome.syn_affected_creature) do
|
||||
local affectedCreature = creature.value
|
||||
local affectedCaste = syndrome.syn_affectedCaste[caste].value
|
||||
if affectedCreature == unitRaceName and (affectedCaste == unitCasteName or affectedCaste == "ALL") then
|
||||
affected = true
|
||||
end
|
||||
end
|
||||
for _,unitClass in ipairs(unitClasses) do
|
||||
for _,syndromeClass in ipairs(syndrome.syn_immune_class) do
|
||||
if unitClass.value == syndromeClass.value then
|
||||
affected = false
|
||||
end
|
||||
end
|
||||
end
|
||||
for caste,creature in ipairs(syndrome.syn_immune_creature) do
|
||||
local immuneCreature = creature.value
|
||||
local immuneCaste = syndrome.syn_immune_caste[caste].value
|
||||
if immuneCreature == unitRaceName and (immuneCaste == unitCasteName or immuneCaste == "ALL") then
|
||||
affected = false
|
||||
end
|
||||
end
|
||||
return affected
|
||||
end
|
||||
|
||||
function infectWithSyndromeIfValidTarget(target,syndrome,resetPolicy)
|
||||
if isValidTarget(target,syndrome) then
|
||||
infectWithSyndrome(target,syndrome,resetPolicy)
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return _ENV
|
||||
|
@ -1,489 +0,0 @@
|
||||
#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;
|
||||
|
||||
namespace ResetPolicy {
|
||||
typedef enum {DoNothing, ResetDuration, AddDuration, NewInstance} ResetPolicy;
|
||||
}
|
||||
|
||||
DFHACK_PLUGIN_IS_ENABLED(enabled);
|
||||
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;
|
||||
}*/
|
||||
|
||||
DFhackCExport command_result plugin_enable(color_ostream& out, bool enable)
|
||||
{
|
||||
if (enabled == enable)
|
||||
return CR_OK;
|
||||
|
||||
enabled = enable;
|
||||
|
||||
if ( enabled ) {
|
||||
EventManager::EventHandler handle(processJob, 0);
|
||||
EventManager::registerListener(EventManager::EventType::JOB_COMPLETED, handle, plugin_self);
|
||||
} else {
|
||||
EventManager::unregisterAll(plugin_self);
|
||||
}
|
||||
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
command_result autoSyndrome(color_ostream& out, vector<string>& parameters) {
|
||||
if ( parameters.size() > 1 )
|
||||
return CR_WRONG_USAGE;
|
||||
|
||||
bool enable = false;
|
||||
if ( parameters.size() == 1 ) {
|
||||
if ( parameters[0] == "enable" ) {
|
||||
enable = true;
|
||||
} else if ( parameters[0] == "disable" ) {
|
||||
enable = false;
|
||||
} else {
|
||||
int32_t a = atoi(parameters[0].c_str());
|
||||
if ( a < 0 || a > 1 )
|
||||
return CR_WRONG_USAGE;
|
||||
|
||||
enable = (bool)a;
|
||||
}
|
||||
}
|
||||
|
||||
out.print("autoSyndrome is %s\n", enabled ? "enabled" : "disabled");
|
||||
return plugin_enable(out, enable);
|
||||
}
|
||||
|
||||
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->wound_id = -1;
|
||||
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->quantity = 0;
|
||||
symptom->delay = 0;
|
||||
symptom->ticks = 0;
|
||||
symptom->flags.bits.active = true; //TODO: ???
|
||||
unitSyndrome->symptoms.push_back(symptom);
|
||||
}
|
||||
unit->syndromes.active.push_back(unitSyndrome);
|
||||
return 0;
|
||||
}
|
||||
|
@ -1,32 +0,0 @@
|
||||
|
||||
#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;
|
||||
}
|
@ -1,202 +0,0 @@
|
||||
|
||||
#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 "modules/Once.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 <map>
|
||||
#include <string>
|
||||
//TODO: check if building becomes inside/outside later
|
||||
|
||||
using namespace DFHack;
|
||||
using namespace std;
|
||||
|
||||
DFHACK_PLUGIN_IS_ENABLED(enabled);
|
||||
DFHACK_PLUGIN("outsideOnly");
|
||||
|
||||
static map<std::string, int32_t> registeredBuildings;
|
||||
const int32_t OUTSIDE_ONLY = 1;
|
||||
const int32_t EITHER = 0;
|
||||
const int32_t INSIDE_ONLY = -1;
|
||||
int32_t checkEvery = -1;
|
||||
|
||||
void buildingCreated(color_ostream& out, void* data);
|
||||
void checkBuildings(color_ostream& out, void* data);
|
||||
command_result outsideOnly(color_ostream& out, vector<string>& parameters);
|
||||
|
||||
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
|
||||
{
|
||||
commands.push_back(PluginCommand("outsideOnly", "Register buildings as inside/outside only. If the player attempts to construct them in an inapproprate place, the building plan automatically deconstructs.\n", &outsideOnly, false,
|
||||
"outsideOnly:\n"
|
||||
" outsideOnly outside [custom building name]\n"
|
||||
" registers [custom building name] as outside-only\n"
|
||||
" outsideOnly inside [custom building name]\n"
|
||||
" registers [custom building name] as inside-only\n"
|
||||
" outsideOnly either [custom building name]\n"
|
||||
" unregisters [custom building name]\n"
|
||||
" outsideOnly checkEvery [n]\n"
|
||||
" checks for buildings that were previously in appropriate inside/outsideness but are not anymore every [n] ticks. If [n] is negative, disables checking.\n"
|
||||
" outsideOnly clear\n"
|
||||
" unregisters all custom buildings\n"
|
||||
" enable outsideOnly\n"
|
||||
" enables the plugin. Plugin must be enabled to function!\n"
|
||||
" disable outsideOnly\n"
|
||||
" disables the plugin\n"
|
||||
" outsideOnly clear outside BUILDING_1 BUILDING_2 inside BUILDING_3\n"
|
||||
" equivalent to:\n"
|
||||
" outsideOnly clear\n"
|
||||
" outsideOnly outside BUILDING_1\n"
|
||||
" outsideOnly outside BUILDING_2\n"
|
||||
" outsideOnly inside BUILDING_3\n"
|
||||
));
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event)
|
||||
{
|
||||
switch (event) {
|
||||
case SC_WORLD_UNLOADED:
|
||||
registeredBuildings.clear();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
DFhackCExport command_result plugin_enable(color_ostream& out, bool enable) {
|
||||
if ( enabled == enable )
|
||||
return CR_OK;
|
||||
enabled = enable;
|
||||
EventManager::unregisterAll(plugin_self);
|
||||
if ( enabled ) {
|
||||
EventManager::EventHandler handler(buildingCreated,1);
|
||||
EventManager::registerListener(EventManager::EventType::BUILDING, handler, plugin_self);
|
||||
checkBuildings(out, 0);
|
||||
}
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
void destroy(df::building* building) {
|
||||
if ( Buildings::deconstruct(building) )
|
||||
return;
|
||||
building->flags.bits.almost_deleted = 1;
|
||||
}
|
||||
|
||||
void checkBuildings(color_ostream& out, void* data) {
|
||||
if ( !enabled )
|
||||
return;
|
||||
|
||||
std::vector<df::building*>& buildings = df::global::world->buildings.all;
|
||||
for ( size_t a = 0; a < buildings.size(); a++ ) {
|
||||
df::building* building = buildings[a];
|
||||
if ( building == NULL )
|
||||
continue;
|
||||
if ( building->getCustomType() < 0 )
|
||||
continue;
|
||||
df::coord pos(building->centerx,building->centery,building->z);
|
||||
df::tile_designation* des = Maps::getTileDesignation(pos);
|
||||
bool outside = des->bits.outside;
|
||||
df::building_def* def = df::global::world->raws.buildings.all[building->getCustomType()];
|
||||
int32_t type = registeredBuildings[def->code];
|
||||
|
||||
if ( type == EITHER ) {
|
||||
registeredBuildings.erase(def->code);
|
||||
} else if ( type == OUTSIDE_ONLY ) {
|
||||
if ( outside )
|
||||
continue;
|
||||
destroy(building);
|
||||
} else if ( type == INSIDE_ONLY ) {
|
||||
if ( !outside )
|
||||
continue;
|
||||
destroy(building);
|
||||
} else {
|
||||
if ( DFHack::Once::doOnce("outsideOnly invalid setting") ) {
|
||||
out.print("Error: outsideOnly: building has invalid setting: %s %d\n", def->code.c_str(), type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( checkEvery < 0 )
|
||||
return;
|
||||
EventManager::EventHandler timeHandler(checkBuildings,-1);
|
||||
EventManager::registerTick(timeHandler, checkEvery, plugin_self);
|
||||
}
|
||||
|
||||
command_result outsideOnly(color_ostream& out, vector<string>& parameters) {
|
||||
int32_t status = 2;
|
||||
for ( size_t a = 0; a < parameters.size(); a++ ) {
|
||||
if ( parameters[a] == "clear" ) {
|
||||
registeredBuildings.clear();
|
||||
} else if ( parameters[a] == "outside" ) {
|
||||
status = OUTSIDE_ONLY;
|
||||
} else if ( parameters[a] == "inside" ) {
|
||||
status = INSIDE_ONLY;
|
||||
} else if ( parameters[a] == "either" ) {
|
||||
status = EITHER;
|
||||
} else if ( parameters[a] == "checkEvery" ) {
|
||||
if (a+1 >= parameters.size()) {
|
||||
out.printerr("You must specify how often to check.\n");
|
||||
return CR_WRONG_USAGE;
|
||||
}
|
||||
checkEvery = atoi(parameters[a].c_str());
|
||||
}
|
||||
else {
|
||||
if ( status == 2 ) {
|
||||
out.printerr("Error: you need to tell outsideOnly whether the building is inside only, outside-only or either.\n");
|
||||
return CR_WRONG_USAGE;
|
||||
}
|
||||
registeredBuildings[parameters[a]] = status;
|
||||
}
|
||||
}
|
||||
out.print("outsideOnly is %s\n", enabled ? "enabled" : "disabled");
|
||||
if ( enabled ) {
|
||||
|
||||
}
|
||||
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;
|
||||
//check if it was created inside or outside
|
||||
df::coord pos(building->centerx,building->centery,building->z);
|
||||
df::tile_designation* des = Maps::getTileDesignation(pos);
|
||||
bool outside = des->bits.outside;
|
||||
|
||||
df::building_def* def = df::global::world->raws.buildings.all[building->getCustomType()];
|
||||
int32_t type = registeredBuildings[def->code];
|
||||
if ( type == EITHER ) {
|
||||
registeredBuildings.erase(def->code);
|
||||
} else if ( type == OUTSIDE_ONLY ) {
|
||||
if ( outside )
|
||||
return;
|
||||
Buildings::deconstruct(building);
|
||||
} else if ( type == INSIDE_ONLY ) {
|
||||
if ( !outside )
|
||||
return;
|
||||
Buildings::deconstruct(building);
|
||||
} else {
|
||||
if ( DFHack::Once::doOnce("outsideOnly invalid setting") ) {
|
||||
out.print("Error: outsideOnly: building has invalid setting: %s %d\n", def->code.c_str(), type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,198 +0,0 @@
|
||||
|
||||
#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!
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,10 @@
|
||||
--print-args.lua
|
||||
--author expwnent
|
||||
--prints all the arguments on their own line with quotes around them. useful for debugging
|
||||
|
||||
local args = {...}
|
||||
print("print-args")
|
||||
for _,arg in ipairs(args) do
|
||||
print("'"..arg.."'")
|
||||
end
|
||||
|
@ -1,62 +0,0 @@
|
||||
# dig a mineral vein/layer, add tiles as they are discovered
|
||||
|
||||
# reuses the dig mode (upstairs etc) of the selected tile
|
||||
|
||||
if df.cursor.x < 0
|
||||
puts "Place the game cursor on a tile to dig"
|
||||
throw :script_finished
|
||||
end
|
||||
|
||||
tile = df.map_tile_at(df.cursor)
|
||||
if tile.shape_basic != :Wall or tile.designation.hidden
|
||||
puts "Place the game cursor on an unmined, discovered tile"
|
||||
throw :script_finished
|
||||
end
|
||||
|
||||
def digmat_watch(tile, digmode, tilelist)
|
||||
# watch the tile, expand mining operations when dug out
|
||||
tilelist << [tile.x, tile.y, tile.z]
|
||||
if tilelist.length == 1
|
||||
df.onupdate_register_once("digmat", 10) {
|
||||
tilelist.dup.each { |x, y, z|
|
||||
t = df.map_tile_at(x, y, z)
|
||||
if t.shape_basic != :Wall
|
||||
digmat_around(t, digmode, tilelist)
|
||||
tilelist.delete [x, y, z]
|
||||
end
|
||||
}
|
||||
tilelist.empty?
|
||||
}
|
||||
end
|
||||
tilelist.uniq!
|
||||
end
|
||||
|
||||
def digmat_around(tile, digmode=tile.designation.dig, tilelist=[])
|
||||
digmode = :Default if digmode == :No
|
||||
[-1, 0, 1].each { |dz|
|
||||
next if digmode == :Default and dz != 0
|
||||
next if tile.z+dz < 1 or tile.z+dz > df.world.map.z_count-2
|
||||
[-1, 0, 1].each { |dy|
|
||||
next if tile.y+dy < 1 or tile.y+dy > df.world.map.y_count-2
|
||||
[-1, 0, 1].each { |dx|
|
||||
next if tile.x+dx < 1 or tile.x+dx > df.world.map.x_count-2
|
||||
ntile = tile.offset(dx, dy, dz)
|
||||
next if not ntile
|
||||
next if ntile.designation.hidden
|
||||
next if ntile.designation.dig != :No
|
||||
next if ntile.shape_basic != :Wall
|
||||
next if not ntile.mat_info === tile.mat_info
|
||||
|
||||
# ignore damp/warm stone walls
|
||||
next if [-1, 0, 1].find { |ddy| [-1, 0, 1].find { |ddx|
|
||||
t = ntile.offset(ddx, ddy) and t.designation.flow_size > 1
|
||||
} }
|
||||
|
||||
ntile.dig(digmode)
|
||||
digmat_watch(ntile, digmode, tilelist)
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
digmat_around(tile)
|
@ -0,0 +1,48 @@
|
||||
--blood-del.lua
|
||||
--makes it so that civs won't come with barrels full of blood, ichor, or goo
|
||||
--author Urist Da Vinci
|
||||
--edited by expwnent
|
||||
|
||||
local my_entity=df.historical_entity.find(df.global.ui.civ_id)
|
||||
local sText=" "
|
||||
local k=0
|
||||
local v=1
|
||||
|
||||
for x,y in pairs(df.global.world.entities.all) do
|
||||
my_entity=y
|
||||
k=0
|
||||
while k < #my_entity.resources.misc_mat.extracts.mat_index do
|
||||
v=my_entity.resources.misc_mat.extracts.mat_type[k]
|
||||
sText=dfhack.matinfo.decode(v,my_entity.resources.misc_mat.extracts.mat_index[k])
|
||||
if (sText==nil) then
|
||||
--LIQUID barrels
|
||||
my_entity.resources.misc_mat.extracts.mat_type:erase(k)
|
||||
my_entity.resources.misc_mat.extracts.mat_index:erase(k)
|
||||
k=k-1
|
||||
else
|
||||
if(sText.material.id=="BLOOD") then
|
||||
my_entity.resources.misc_mat.extracts.mat_type:erase(k)
|
||||
my_entity.resources.misc_mat.extracts.mat_index:erase(k)
|
||||
k=k-1
|
||||
end
|
||||
if(sText.material.id=="ICHOR") then
|
||||
my_entity.resources.misc_mat.extracts.mat_type:erase(k)
|
||||
my_entity.resources.misc_mat.extracts.mat_index:erase(k)
|
||||
k=k-1
|
||||
end
|
||||
if(sText.material.id=="GOO") then
|
||||
my_entity.resources.misc_mat.extracts.mat_type:erase(k)
|
||||
my_entity.resources.misc_mat.extracts.mat_index:erase(k)
|
||||
k=k-1
|
||||
end
|
||||
--VENOM
|
||||
--POISON
|
||||
--FLUID
|
||||
--MILK
|
||||
--EXTRACT
|
||||
|
||||
end
|
||||
k=k+1
|
||||
end
|
||||
end
|
||||
|
@ -0,0 +1,37 @@
|
||||
-- feeding-timers.lua
|
||||
-- original author: tejón
|
||||
-- rewritten by expwnent
|
||||
-- see repeat.lua for how to run this every so often automatically
|
||||
|
||||
local args = {...}
|
||||
if args[1] ~= nil then
|
||||
print("fix/feeding-timers usage")
|
||||
print(" fix/feeding-timers")
|
||||
print(" reset the feeding timers of all units as appropriate")
|
||||
print(" fix/feeding-timers help")
|
||||
print(" print this help message")
|
||||
print(" repeat -time [n] [years/months/ticks/days/etc] -command fix/feeding-timers")
|
||||
print(" run this script every n time units")
|
||||
print(" repeat -cancel fix/feeding-timers")
|
||||
print(" stop automatically running this script")
|
||||
return
|
||||
end
|
||||
|
||||
local fixcount = 0
|
||||
for _,unit in ipairs(df.global.world.units.all) do
|
||||
if dfhack.units.isCitizen(unit) and not (unit.flags1.dead) then
|
||||
for _,v in pairs(unit.status.misc_traits) do
|
||||
local didfix = 0
|
||||
if v.id == 0 then -- I think this should have additional conditions...
|
||||
v.value = 0 -- GiveWater cooldown set to zero
|
||||
didfix = 1
|
||||
end
|
||||
if v.id == 1 then -- I think this should have additional conditions...
|
||||
v.value = 0 -- GiveFood cooldown set to zero
|
||||
didfix = 1
|
||||
end
|
||||
fixcount = fixcount + didfix
|
||||
end
|
||||
end
|
||||
end
|
||||
print("Fixed feeding timers for " .. fixcount .. " citizens.")
|
@ -0,0 +1,35 @@
|
||||
--growth-bug.lua: units only grow when the current tick is 0 mod 10, so only 1/10 units will grow naturally. this script periodically sets the birth time of each unit so that it will grow
|
||||
--to run periodically, use "repeat -time 2 months -command fix/growth-bug -now". see repeat.lua for details
|
||||
--author expwnent
|
||||
|
||||
local utils = require 'utils'
|
||||
|
||||
validArgs = validArgs or utils.invert({
|
||||
'help',
|
||||
'now'
|
||||
})
|
||||
|
||||
local args = utils.processArgs({...}, validArgs)
|
||||
|
||||
if args.help or not next(args) then
|
||||
print("fix/growth-bug usage")
|
||||
print(" fix/growth-bug")
|
||||
print(" fix the growth bug for all units on the map")
|
||||
print(" fix/growth-bug -help")
|
||||
print(" print this help message")
|
||||
print(" repeat -time [n] [years/months/ticks/days/etc] -command fix/growth-bug now")
|
||||
print(" run this script every n time units")
|
||||
print(" repeat -cancel fix/growth-bug")
|
||||
print(" stop automatically running this script")
|
||||
end
|
||||
|
||||
local count = 0
|
||||
for _,unit in ipairs(df.global.world.units.all) do
|
||||
local offset = unit.relations.birth_time % 10;
|
||||
if offset ~= 0 then
|
||||
unit.relations.birth_time = unit.relations.birth_time - offset
|
||||
count = count+1
|
||||
end
|
||||
end
|
||||
print("Fixed growth bug for "..count.." units.")
|
||||
|
@ -0,0 +1,126 @@
|
||||
-- scripts/forum-dwarves.lua
|
||||
-- Save a copy of a text screen for the DF forums. Use 'forumdwarves help' for more details.
|
||||
-- original author: Caldfir
|
||||
-- edited by expwnent
|
||||
|
||||
local args = {...}
|
||||
|
||||
if args[1] == 'help' then
|
||||
print([[
|
||||
description:
|
||||
This script will attempt to read the current df-screen, and if it is a
|
||||
text-viewscreen (such as the dwarf 'thoughts' screen or an item
|
||||
'description') then append a marked-up version of this text to the
|
||||
target file. Previous entries in the file are not overwritten, so you
|
||||
may use the 'forumdwarves' command multiple times to create a single
|
||||
document containing the text from multiple screens (eg: text screens
|
||||
from several dwarves, or text screens from multiple artifacts/items,
|
||||
or some combination).
|
||||
known screens:
|
||||
The screens which have been tested and known to function properly with
|
||||
this script are:
|
||||
1: dwarf/unit 'thoughts' screen
|
||||
2: item/art 'description' screen
|
||||
3: individual 'historical item/figure' screens
|
||||
There may be other screens to which the script applies. It should be
|
||||
safe to attempt running the script with any screen active, with an
|
||||
error message to inform you when the selected screen is not appropriate
|
||||
for this script.
|
||||
target file:
|
||||
The target file's name is 'forumdwarves.txt'. A remider to this effect
|
||||
will be displayed if the script is successful.
|
||||
character encoding:
|
||||
The text will likely be using system-default encoding, and as such
|
||||
will likely NOT display special characters (eg:é,õ,ç) correctly. To
|
||||
fix this, you need to modify the character set that you are reading
|
||||
the document with. 'Notepad++' is a freely available program which
|
||||
can do this using the following steps:
|
||||
1: open the document in Notepad++
|
||||
2: in the menu-bar, select
|
||||
Encoding->Character Sets->Western European->OEM-US
|
||||
3: copy the text normally to wherever you want to use it
|
||||
]])
|
||||
return
|
||||
end
|
||||
local utils = require 'utils'
|
||||
local gui = require 'gui'
|
||||
local dialog = require 'gui.dialogs'
|
||||
local colors_css = {
|
||||
[0] = 'black',
|
||||
[1] = 'navy',
|
||||
[2] = 'green',
|
||||
[3] = 'teal',
|
||||
[4] = 'maroon',
|
||||
[5] = 'purple',
|
||||
[6] = 'olive',
|
||||
[7] = 'silver',
|
||||
[8] = 'gray',
|
||||
[9] = 'blue',
|
||||
[10] = 'lime',
|
||||
[11] = 'cyan',
|
||||
[12] = 'red',
|
||||
[13] = 'magenta',
|
||||
[14] = 'yellow',
|
||||
[15] = 'white'
|
||||
}
|
||||
|
||||
local scrn = dfhack.gui.getCurViewscreen()
|
||||
local flerb = dfhack.gui.getFocusString(scrn)
|
||||
|
||||
local function format_for_forum(strin)
|
||||
local strout = strin
|
||||
|
||||
local newline_idx = string.find(strout, '[P]', 1, true)
|
||||
while newline_idx ~= nil do
|
||||
strout = string.sub(strout,1, newline_idx-1)..'\n'..string.sub(strout,newline_idx+3)
|
||||
newline_idx = string.find(strout, '[P]', 1, true)
|
||||
end
|
||||
|
||||
newline_idx = string.find(strout, '[B]', 1, true)
|
||||
while newline_idx ~= nil do
|
||||
strout = string.sub(strout,1, newline_idx-1)..'\n'..string.sub(strout,newline_idx+3)
|
||||
newline_idx = string.find(strout, '[B]', 1, true)
|
||||
end
|
||||
|
||||
newline_idx = string.find(strout, '[R]', 1, true)
|
||||
while newline_idx ~= nil do
|
||||
strout = string.sub(strout,1, newline_idx-1)..'\n'..string.sub(strout,newline_idx+3)
|
||||
newline_idx = string.find(strout, '[R]', 1, true)
|
||||
end
|
||||
|
||||
local color_idx = string.find(strout, '[C:', 1, true)
|
||||
while color_idx ~= nil do
|
||||
local colormatch = (string.byte(strout, color_idx+3)-48)+((string.byte(strout, color_idx+7)-48)*8)
|
||||
strout = string.sub(strout,1, color_idx-1)..'[/color][color='..colors_css[colormatch]..']'..string.sub(strout,color_idx+9)
|
||||
color_idx = string.find(strout, '[C:', 1, true)
|
||||
end
|
||||
|
||||
return strout
|
||||
end
|
||||
|
||||
if flerb == 'textviewer' then
|
||||
print(scrn)
|
||||
printall(scrn)
|
||||
local lines = scrn.formatted_text
|
||||
local line = ""
|
||||
|
||||
if lines ~= nil then
|
||||
local log = io.open('forumdwarves.txt', 'a')
|
||||
log:write("[color=silver]")
|
||||
for n,x in ipairs(lines) do
|
||||
print(x)
|
||||
printall(x)
|
||||
print(x.text)
|
||||
printall(x.text)
|
||||
if (x ~= nil) and (x.text ~= nil) then
|
||||
log:write(format_for_forum(x.text), ' ')
|
||||
--log:write(x[0],'\n')
|
||||
end
|
||||
end
|
||||
log:write("[/color]\n")
|
||||
log:close()
|
||||
end
|
||||
print 'data prepared for forum in \"forumdwarves.txt\"'
|
||||
else
|
||||
print 'this is not a textview screen'
|
||||
end
|
@ -0,0 +1,127 @@
|
||||
--full-heal.lua
|
||||
--author Kurik Amudnil, Urist DaVinci
|
||||
--edited by expwnent
|
||||
|
||||
-- attempt to fully heal a selected unit, option -r to attempt to resurrect the unit
|
||||
local args = {...}
|
||||
local resurrect = false
|
||||
local i=0
|
||||
for _,arg in ipairs(args) do
|
||||
if arg == '-r' or arg == '-R' then
|
||||
resurrect = true
|
||||
elseif tonumber(arg) then
|
||||
unit = df.unit.find(tonumber(arg))
|
||||
elseif arg == 'help' or arg == '-help' or arg == '-h' then
|
||||
print('full-heal: heal a unit completely from anything, optionally including death.')
|
||||
print(' full-heal [unitId]')
|
||||
print(' heal the unit with the given id')
|
||||
print(' full-heal -r [unitId]')
|
||||
print(' heal the unit with the given id and bring them back from death if they are dead')
|
||||
print(' full-heal')
|
||||
print(' heal the currently selected unit')
|
||||
print(' full-heal -r')
|
||||
print(' heal the currently selected unit and bring them back from death if they are dead')
|
||||
print(' full-heal help')
|
||||
print(' print this help message')
|
||||
return
|
||||
end
|
||||
end
|
||||
unit = unit or dfhack.gui.getSelectedUnit()
|
||||
if not unit then
|
||||
qerror('Error: please select a unit or pass its id as an argument.')
|
||||
end
|
||||
|
||||
if unit then
|
||||
if resurrect then
|
||||
if unit.flags1.dead then
|
||||
--print("Resurrecting...")
|
||||
unit.flags2.slaughter = false
|
||||
unit.flags3.scuttle = false
|
||||
end
|
||||
unit.flags1.dead = false
|
||||
unit.flags2.killed = false
|
||||
unit.flags3.ghostly = false
|
||||
--unit.unk_100 = 3
|
||||
end
|
||||
|
||||
--print("Erasing wounds...")
|
||||
while #unit.body.wounds > 0 do
|
||||
unit.body.wounds:erase(#unit.body.wounds-1)
|
||||
end
|
||||
unit.body.wound_next_id=1
|
||||
|
||||
--print("Refilling blood...")
|
||||
unit.body.blood_count=unit.body.blood_max
|
||||
|
||||
--print("Resetting grasp/stand status...")
|
||||
unit.status2.limbs_stand_count=unit.status2.limbs_stand_max
|
||||
unit.status2.limbs_grasp_count=unit.status2.limbs_grasp_max
|
||||
|
||||
--print("Resetting status flags...")
|
||||
unit.flags2.has_breaks=false
|
||||
unit.flags2.gutted=false
|
||||
unit.flags2.circulatory_spray=false
|
||||
unit.flags2.vision_good=true
|
||||
unit.flags2.vision_damaged=false
|
||||
unit.flags2.vision_missing=false
|
||||
unit.flags2.breathing_good=true
|
||||
unit.flags2.breathing_problem=false
|
||||
|
||||
unit.flags2.calculated_nerves=false
|
||||
unit.flags2.calculated_bodyparts=false
|
||||
unit.flags2.calculated_insulation=false
|
||||
unit.flags3.compute_health=true
|
||||
|
||||
--print("Resetting counters...")
|
||||
unit.counters.winded=0
|
||||
unit.counters.stunned=0
|
||||
unit.counters.unconscious=0
|
||||
unit.counters.webbed=0
|
||||
unit.counters.pain=0
|
||||
unit.counters.nausea=0
|
||||
unit.counters.dizziness=0
|
||||
|
||||
unit.counters2.paralysis=0
|
||||
unit.counters2.fever=0
|
||||
unit.counters2.exhaustion=0
|
||||
unit.counters2.hunger_timer=0
|
||||
unit.counters2.thirst_timer=0
|
||||
unit.counters2.sleepiness_timer=0
|
||||
unit.counters2.vomit_timeout=0
|
||||
|
||||
--print("Resetting body part status...")
|
||||
v=unit.body.components
|
||||
for i=0,#v.nonsolid_remaining - 1,1 do
|
||||
v.nonsolid_remaining[i] = 100 -- percent remaining of fluid layers (Urist Da Vinci)
|
||||
end
|
||||
|
||||
v=unit.body.components
|
||||
for i=0,#v.layer_wound_area - 1,1 do
|
||||
v.layer_status[i].whole = 0 -- severed, leaking layers (Urist Da Vinci)
|
||||
v.layer_wound_area[i] = 0 -- wound contact areas (Urist Da Vinci)
|
||||
v.layer_cut_fraction[i] = 0 -- 100*surface percentage of cuts/fractures on the body part layer (Urist Da Vinci)
|
||||
v.layer_dent_fraction[i] = 0 -- 100*surface percentage of dents on the body part layer (Urist Da Vinci)
|
||||
v.layer_effect_fraction[i] = 0 -- 100*surface percentage of "effects" on the body part layer (Urist Da Vinci)
|
||||
end
|
||||
|
||||
v=unit.body.components.body_part_status
|
||||
for i=0,#v-1,1 do
|
||||
v[i].on_fire = false
|
||||
v[i].missing = false
|
||||
v[i].organ_loss = false
|
||||
v[i].organ_damage = false
|
||||
v[i].muscle_loss = false
|
||||
v[i].muscle_damage = false
|
||||
v[i].bone_loss = false
|
||||
v[i].bone_damage = false
|
||||
v[i].skin_damage = false
|
||||
v[i].motor_nerve_severed = false
|
||||
v[i].sensory_nerve_severed = false
|
||||
end
|
||||
|
||||
if unit.job.current_job and unit.job.current_job.job_type == df.job_type.Rest then
|
||||
--print("Wake from rest -> clean self...")
|
||||
unit.job.current_job = df.job_type.CleanSelf
|
||||
end
|
||||
end
|
||||
|
@ -0,0 +1,252 @@
|
||||
-- hack-wish.lua
|
||||
-- Allows for script-based wishing.
|
||||
-- author Putnam
|
||||
-- edited by expwnent
|
||||
|
||||
function getGenderString(gender)
|
||||
local genderStr
|
||||
if gender==0 then
|
||||
genderStr=string.char(12)
|
||||
elseif gender==1 then
|
||||
genderStr=string.char(11)
|
||||
else
|
||||
return ""
|
||||
end
|
||||
return string.char(40)..genderStr..string.char(41)
|
||||
end
|
||||
|
||||
function getCreatureList()
|
||||
local crList={}
|
||||
for k,cr in ipairs(df.global.world.raws.creatures.alphabetic) do
|
||||
for kk,ca in ipairs(cr.caste) do
|
||||
local str=ca.caste_name[0]
|
||||
str=str..' '..getGenderString(ca.gender)
|
||||
table.insert(crList,{str,nil,ca})
|
||||
end
|
||||
end
|
||||
return crList
|
||||
end
|
||||
|
||||
function getMatFilter(itemtype)
|
||||
local itemTypes={
|
||||
SEEDS=function(mat,parent,typ,idx)
|
||||
return mat.flags.SEED_MAT
|
||||
end,
|
||||
PLANT=function(mat,parent,typ,idx)
|
||||
return mat.flags.STRUCTURAL_PLANT_MAT
|
||||
end,
|
||||
LEAVES=function(mat,parent,typ,idx)
|
||||
return mat.flags.LEAF_MAT
|
||||
end,
|
||||
MEAT=function(mat,parent,typ,idx)
|
||||
return mat.flags.MEAT
|
||||
end,
|
||||
CHEESE=function(mat,parent,typ,idx)
|
||||
return (mat.flags.CHEESE_PLANT or mat.flags.CHEESE_CREATURE)
|
||||
end,
|
||||
LIQUID_MISC=function(mat,parent,typ,idx)
|
||||
return (mat.flags.LIQUID_MISC_PLANT or mat.flags.LIQUID_MISC_CREATURE or mat.flags.LIQUID_MISC_OTHER)
|
||||
end,
|
||||
POWDER_MISC=function(mat,parent,typ,idx)
|
||||
return (mat.flags.POWDER_MISC_PLANT or mat.flags.POWDER_MISC_CREATURE)
|
||||
end,
|
||||
DRINK=function(mat,parent,typ,idx)
|
||||
return (mat.flags.ALCOHOL_PLANT or mat.flags.ALCOHOL_CREATURE)
|
||||
end,
|
||||
GLOB=function(mat,parent,typ,idx)
|
||||
return (mat.flags.STOCKPILE_GLOB)
|
||||
end,
|
||||
WOOD=function(mat,parent,typ,idx)
|
||||
return (mat.flags.WOOD)
|
||||
end,
|
||||
THREAD=function(mat,parent,typ,idx)
|
||||
return (mat.flags.THREAD_PLANT)
|
||||
end,
|
||||
LEATHER=function(mat,parent,typ,idx)
|
||||
return (mat.flags.LEATHER)
|
||||
end
|
||||
}
|
||||
return itemTypes[df.item_type[itemtype]] or getRestrictiveMatFilter(itemtype)
|
||||
end
|
||||
|
||||
function getRestrictiveMatFilter(itemType)
|
||||
if not args.veryRestrictive then return nil else
|
||||
local itemTypes={
|
||||
WEAPON=function(mat,parent,typ,idx)
|
||||
return (mat.flags.ITEMS_WEAPON or mat.flags.ITEMS_WEAPON_RANGED)
|
||||
end,
|
||||
AMMO=function(mat,parent,typ,idx)
|
||||
return (mat.flags.ITEMS_AMMO)
|
||||
end,
|
||||
ARMOR=function(mat,parent,typ,idx)
|
||||
return (mat.flags.ITEMS_ARMOR)
|
||||
end,
|
||||
SHOES,SHIELD,HELM,GLOVES=ARMOR,ARMOR,ARMOR,ARMOR,
|
||||
INSTRUMENT=function(mat,parent,typ,idx)
|
||||
return (mat.flags.ITEMS_HARD)
|
||||
end,
|
||||
GOBLET,FLASK,TOY,RING,CROWN,SCEPTER,FIGURINE,TOOL=INSTRUMENT,INSTRUMENT,INSTRUMENT,INSTRUMENT,INSTRUMENT,INSTRUMENT,INSTRUMENT,
|
||||
AMULET=function(mat,parent,typ,idx)
|
||||
return (mat.flags.ITEMS_SOFT or mat.flags.ITEMS_HARD)
|
||||
end,
|
||||
EARRING,BRACELET=AMULET,AMULET,
|
||||
ROCK=function(mat,parent,typ,idx)
|
||||
return (mat.flags.IS_STONE)
|
||||
end,
|
||||
BOULDER=ROCK,
|
||||
BAR=function(mat,parent,typ,idx)
|
||||
return (mat.flags.IS_METAL or mat.flags.SOAP or mat.id==COAL)
|
||||
end
|
||||
}
|
||||
return itemTypes[df.item_type[itemType]]
|
||||
end
|
||||
end
|
||||
|
||||
function createItem(mat,itemType,quality,pos,description)
|
||||
local item=df[df.item_type.attrs[itemType[1]].classname]:new()
|
||||
item.id=df.global.item_next_id
|
||||
df.global.world.items.all:insert('#',item)
|
||||
df.global.item_next_id=df.global.item_next_id+1
|
||||
item:setSubtype(itemType[2])
|
||||
item:setMaterial(mat[1])
|
||||
item:setMaterialIndex(mat[2])
|
||||
if df.item_type[itemType[1]]=='EGG' then
|
||||
local creature=df.creature_raw.find(mat[1])
|
||||
local eggMat={}
|
||||
eggMat[1]=dfhack.matinfo.find(creature.creature_id..':EGGSHELL')
|
||||
if eggMat[1] then
|
||||
eggMat[2]=dfhack.matinfo.find(creature.creature_id..':EGG_WHITE')
|
||||
eggMat[3]=dfhack.matinfo.find(creature.creature_id..'EGG_YOLK')
|
||||
for k,v in ipairs(eggMat) do
|
||||
item.egg_materials.mat_type:insert('#',v.type)
|
||||
item.egg_materials.mat_index:insert('#',v.index)
|
||||
end
|
||||
else
|
||||
eggMat=dfhack.matinfo.find(creature.creature_id..':MUSCLE')
|
||||
item.egg_materials.mat_type:insert('#',eggMat.type)
|
||||
item.egg_materials.mat_index:insert('#',eggMat.index)
|
||||
end
|
||||
end
|
||||
item:categorize(true)
|
||||
item.flags.removed=true
|
||||
item:setSharpness(1,0)
|
||||
item:setQuality(quality-1)
|
||||
if df.item_type[itemType[1]]=='SLAB' then
|
||||
item.description=description
|
||||
end
|
||||
dfhack.items.moveToGround(item,{x=pos.x,y=pos.y,z=pos.z})
|
||||
end
|
||||
|
||||
--TODO: should this be a function?
|
||||
function qualityTable()
|
||||
return {{'None'},
|
||||
{'-Well-crafted-'},
|
||||
{'+Finely-crafted+'},
|
||||
{'*Superior*'},
|
||||
{string.char(240)..'Exceptional'..string.char(240)},
|
||||
{string.char(15)..'Masterwork'..string.char(15)}
|
||||
}
|
||||
end
|
||||
|
||||
local script=require('gui/script')
|
||||
local guimaterials=require('gui.materials')
|
||||
|
||||
function showItemPrompt(text,item_filter,hide_none)
|
||||
guimaterials.ItemTypeDialog{
|
||||
prompt=text,
|
||||
item_filter=item_filter,
|
||||
hide_none=hide_none,
|
||||
on_select=script.mkresume(true),
|
||||
on_cancel=script.mkresume(false),
|
||||
on_close=script.qresume(nil)
|
||||
}:show()
|
||||
|
||||
return script.wait()
|
||||
end
|
||||
|
||||
function showMaterialPrompt(title, prompt, filter, inorganic, creature, plant) --the one included with DFHack doesn't have a filter or the inorganic, creature, plant things available
|
||||
guimaterials.MaterialDialog{
|
||||
frame_title = title,
|
||||
prompt = prompt,
|
||||
mat_filter = filter,
|
||||
use_inorganic = inorganic,
|
||||
use_creature = creature,
|
||||
use_plant = plant,
|
||||
on_select = script.mkresume(true),
|
||||
on_cancel = script.mkresume(false),
|
||||
on_close = script.qresume(nil)
|
||||
}:show()
|
||||
|
||||
return script.wait()
|
||||
end
|
||||
|
||||
function usesCreature(itemtype)
|
||||
typesThatUseCreatures={REMAINS=true,FISH=true,FISH_RAW=true,VERMIN=true,PET=true,EGG=true,CORPSE=true,CORPSEPIECE=true}
|
||||
return typesThatUseCreatures[df.item_type[itemtype]]
|
||||
end
|
||||
|
||||
function getCreatureRaceAndCaste(caste)
|
||||
return df.global.world.raws.creatures.list_creature[caste.index],df.global.world.raws.creatures.list_caste[caste.index]
|
||||
end
|
||||
|
||||
function hackWish(posOrUnit)
|
||||
local pos = df.unit:is_instance(posOrUnit) and posOrUnit.pos or posOrUnit
|
||||
script.start(function()
|
||||
--local amountok, amount
|
||||
local matok,mattype,matindex,matFilter
|
||||
local itemok,itemtype,itemsubtype=showItemPrompt('What item do you want?',function(itype) return df.item_type[itype]~='CORPSE' and df.item_type[itype]~='FOOD' end ,true)
|
||||
if not args.notRestrictive then
|
||||
matFilter=getMatFilter(itemtype)
|
||||
end
|
||||
if not usesCreature(itemtype) then
|
||||
matok,mattype,matindex=showMaterialPrompt('Wish','And what material should it be made of?',matFilter)
|
||||
else
|
||||
local creatureok,useless,creatureTable=script.showListPrompt('Wish','What creature should it be?',COLOR_LIGHTGREEN,getCreatureList())
|
||||
mattype,matindex=getCreatureRaceAndCaste(creatureTable[3])
|
||||
end
|
||||
local qualityok,quality=script.showListPrompt('Wish','What quality should it be?',COLOR_LIGHTGREEN,qualityTable())
|
||||
local description
|
||||
if df.item_type[itemtype]=='SLAB' then
|
||||
local descriptionok
|
||||
descriptionok,description=script.showInputPrompt('Slab','What should the slab say?',COLOR_WHITE)
|
||||
end
|
||||
--repeat amountok,amount=script.showInputPrompt('Wish','How many do you want? (numbers only!)',COLOR_LIGHTGREEN) until tonumber(amount)
|
||||
if mattype and itemtype then
|
||||
--for i=1,tonumber(amount) do
|
||||
createItem({mattype,matindex},{itemtype,itemsubtype},quality,pos,description)
|
||||
--end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
scriptArgs={...}
|
||||
|
||||
args={}
|
||||
|
||||
for k,v in ipairs(scriptArgs) do
|
||||
v=v:lower()
|
||||
if v=='startup' then args.startup=true end
|
||||
if v=='all' then args.notRestrictive=true end
|
||||
if v=='restrictive' then args.veryRestrictive=true end
|
||||
if v=='unit' then args.unitNum=args[k+1] end
|
||||
if v=='x' then args.x=args[k+1] end
|
||||
if v=='y' then args.y=args[k+1] end
|
||||
if v=='z' then args.z=args[k+1] end
|
||||
end
|
||||
|
||||
eventful=require('plugins.eventful')
|
||||
|
||||
function posIsValid(pos)
|
||||
return pos.x~=-30000 and pos or false
|
||||
end
|
||||
|
||||
if not args.startup then
|
||||
local posOrUnit=args.x and {x=args.x,y=args.y,z=args.z} or args.unitNum and df.unit.find(args.unitNum) or posIsValid(df.global.cursor) or dfhack.gui.getSelectedUnit(true)
|
||||
hackWish(posOrUnit)
|
||||
else
|
||||
eventful.onReactionComplete.hackWishP=function(reaction,unit,input_items,input_reagents,output_items, call_native)
|
||||
if not reaction.code:find('DFHACK_WISH') then return nil end
|
||||
hackWish(unit)
|
||||
end
|
||||
end
|
||||
|
@ -0,0 +1,793 @@
|
||||
-- unit-info-viewer.lua
|
||||
-- Displays age, birth, maxage, shearing, milking, grazing, egg laying, body size, and death info about a unit. Recommended keybinding Alt-I
|
||||
-- version 1.04
|
||||
-- original author: Kurik Amudnil
|
||||
-- edited by expwnent
|
||||
|
||||
local gui = require 'gui'
|
||||
local widgets =require 'gui.widgets'
|
||||
local utils = require 'utils'
|
||||
|
||||
local DEBUG = false
|
||||
if DEBUG then print('-----') end
|
||||
|
||||
local pens = {
|
||||
BLACK = dfhack.pen.parse{fg=COLOR_BLACK,bg=0},
|
||||
BLUE = dfhack.pen.parse{fg=COLOR_BLUE,bg=0},
|
||||
GREEN = dfhack.pen.parse{fg=COLOR_GREEN,bg=0},
|
||||
CYAN = dfhack.pen.parse{fg=COLOR_CYAN,bg=0},
|
||||
RED = dfhack.pen.parse{fg=COLOR_RED,bg=0},
|
||||
MAGENTA = dfhack.pen.parse{fg=COLOR_MAGENTA,bg=0},
|
||||
BROWN = dfhack.pen.parse{fg=COLOR_BROWN,bg=0},
|
||||
GREY = dfhack.pen.parse{fg=COLOR_GREY,bg=0},
|
||||
DARKGREY = dfhack.pen.parse{fg=COLOR_DARKGREY,bg=0},
|
||||
LIGHTBLUE = dfhack.pen.parse{fg=COLOR_LIGHTBLUE,bg=0},
|
||||
LIGHTGREEN = dfhack.pen.parse{fg=COLOR_LIGHTGREEN,bg=0},
|
||||
LIGHTCYAN = dfhack.pen.parse{fg=COLOR_LIGHTCYAN,bg=0},
|
||||
LIGHTRED = dfhack.pen.parse{fg=COLOR_LIGHTRED,bg=0},
|
||||
LIGHTMAGENTA = dfhack.pen.parse{fg=COLOR_LIGHTMAGENTA,bg=0},
|
||||
YELLOW = dfhack.pen.parse{fg=COLOR_YELLOW,bg=0},
|
||||
WHITE = dfhack.pen.parse{fg=COLOR_WHITE,bg=0},
|
||||
}
|
||||
|
||||
function getUnit_byID(id) -- get unit by id from units.all via binsearch
|
||||
if type(id) == 'number' then
|
||||
-- (vector,key,field,cmpfun,min,max) { item/nil , found true/false , idx/insert at }
|
||||
return utils.binsearch(df.global.world.units.all,id,'id')
|
||||
end
|
||||
end
|
||||
|
||||
function getUnit_byVS(silent) -- by view screen mode
|
||||
silent = silent or false
|
||||
-- if not world loaded, return nil ?
|
||||
local u,tmp -- u: the unit to return ; tmp: temporary for intermediate tests/return values
|
||||
local v = dfhack.gui.getCurViewscreen()
|
||||
u = dfhack.gui.getSelectedUnit(true) -- supports gui scripts/plugin that provide a hook for getSelectedUnit()
|
||||
if u then
|
||||
return u
|
||||
-- else: contexts not currently supported by dfhack.gui.getSelectedUnit()
|
||||
elseif df.viewscreen_dwarfmodest:is_instance(v) then
|
||||
tmp = df.global.ui.main.mode
|
||||
if tmp == 17 or tmp == 42 or tmp == 43 then
|
||||
-- context: @dwarfmode/QueryBuiding/Some/Cage -- (q)uery cage
|
||||
-- context: @dwarfmode/ZonesPenInfo/AssignUnit -- i (zone) -> pe(N)
|
||||
-- context: @dwarfmode/ZonesPitInfo -- i (zone) -> (P)it
|
||||
u = df.global.ui_building_assign_units[df.global.ui_building_item_cursor]
|
||||
elseif tmp == 49 and df.global.ui.burrows.in_add_units_mode then
|
||||
-- @dwarfmode/Burrows/AddUnits
|
||||
u = df.global.ui.burrows.list_units[ df.global.ui.burrows.unit_cursor_pos ]
|
||||
|
||||
elseif df.global.ui.follow_unit ~= -1 then
|
||||
-- context: follow unit mode
|
||||
u = getUnit_byID(df.global.ui.follow_unit)
|
||||
end -- end viewscreen_dwarfmodest
|
||||
elseif df.viewscreen_petst:is_instance(v) then
|
||||
-- context: @pet/List/Unit -- z (status) -> animals
|
||||
if v.mode == 0 then -- List
|
||||
if not v.is_vermin[v.cursor] then
|
||||
u = v.animal[v.cursor].unit
|
||||
end
|
||||
--elseif v.mode = 1 then -- training knowledge (no unit reference)
|
||||
elseif v.mode == 2 then -- select trainer
|
||||
u = v.trainer_unit[v.trainer_cursor]
|
||||
end
|
||||
elseif df.viewscreen_layer_workshop_profilest:is_instance(v) then
|
||||
-- context: @layer_workshop_profile/Unit -- (q)uery workshop -> (P)rofile -- df.global.ui.main.mode == 17
|
||||
u = v.workers[v.layer_objects[0].cursor]
|
||||
elseif df.viewscreen_layer_overall_healthst:is_instance(v) then
|
||||
-- context @layer_overall_health/Units -- z -> health
|
||||
u = v.unit[v.layer_objects[0].cursor]
|
||||
elseif df.viewscreen_layer_militaryst:is_instance(v) then
|
||||
local PG_ASSIGNMENTS = 0
|
||||
local PG_EQUIPMENT = 2
|
||||
local TB_POSITIONS = 1
|
||||
local TB_CANDIDATES = 2
|
||||
-- layer_objects[0: squads list; 1: positions list; 2: candidates list]
|
||||
-- page 0:positions/assignments 1:alerts 2:equipment 3:uniforms 4:supplies 5:ammunition
|
||||
if v.page == PG_ASSIGNMENTS and v.layer_objects[TB_CANDIDATES].enabled and v.layer_objects[TB_CANDIDATES].active then
|
||||
-- context: @layer_military/Positions/Position/Candidates -- m -> Candidates
|
||||
u = v.positions.candidates[v.layer_objects[TB_CANDIDATES].cursor]
|
||||
elseif v.page == PG_ASSIGNMENTS and v.layer_objects[TB_POSITIONS].enabled and v.layer_objects[TB_POSITIONS].active then
|
||||
-- context: @layer_military/Positions/Position -- m -> Positions
|
||||
u = v.positions.assigned[v.layer_objects[TB_POSITIONS].cursor]
|
||||
elseif v.page == PG_EQUIPMENT and v.layer_objects[TB_POSITIONS].enabled and v.layer_objects[TB_POSITIONS].active then
|
||||
-- context: @layer_military/Equip/Customize/View/Position -- m -> (e)quip -> Positions
|
||||
-- context: @layer_military/Equip/Uniform/Positions -- m -> (e)quip -> assign (U)niforms -> Positions
|
||||
u = v.equip.units[v.layer_objects[TB_POSITIONS].cursor]
|
||||
end
|
||||
elseif df.viewscreen_layer_noblelistst:is_instance(v) then
|
||||
if v.mode == 0 then
|
||||
-- context: @layer_noblelist/List -- (n)obles
|
||||
u = v.info[v.layer_objects[v.mode].cursor].unit
|
||||
elseif v.mode == 1 then
|
||||
-- context: @layer_noblelist/Appoint -- (n)obles -> (r)eplace
|
||||
u = v.candidates[v.layer_objects[v.mode].cursor].unit
|
||||
end
|
||||
elseif df.viewscreen_unitst:is_instance(v) then
|
||||
-- @unit -- (v)unit -> z ; loo(k) -> enter ; (n)obles -> enter ; others
|
||||
u = v.unit
|
||||
elseif df.viewscreen_customize_unitst:is_instance(v) then
|
||||
-- @customize_unit -- @unit -> y
|
||||
u = v.unit
|
||||
elseif df.viewscreen_layer_unit_healthst:is_instance(v) then
|
||||
-- @layer_unit_health -- @unit -> h ; @layer_overall_health/Units -> enter
|
||||
if df.viewscreen_layer_overall_healthst:is_instance(v.parent) then
|
||||
-- context @layer_overall_health/Units -- z (status)-> health
|
||||
u = v.parent.unit[v.parent.layer_objects[0].cursor]
|
||||
elseif df.viewscreen_unitst:is_instance(v.parent) then
|
||||
-- @unit -- (v)unit -> z ; loo(k) -> enter ; (n)obles -> enter ; others
|
||||
u = v.parent.unit
|
||||
end
|
||||
elseif df.viewscreen_textviewerst:is_instance(v) then
|
||||
-- @textviewer -- @unit -> enter (thoughts and preferences)
|
||||
if df.viewscreen_unitst:is_instance(v.parent) then
|
||||
-- @unit -- @unit -> enter (thoughts and preferences)
|
||||
u = v.parent.unit
|
||||
elseif df.viewscreen_itemst:is_instance(v.parent) then
|
||||
tmp = v.parent.entry_ref[v.parent.cursor_pos]
|
||||
if df.general_ref_unit:is_instance(tmp) then -- general_ref_unit and derived ; general_ref_contains_unitst ; others?
|
||||
u = getUnit_byID(tmp.unit_id)
|
||||
end
|
||||
elseif df.viewscreen_dwarfmodest:is_instance(v.parent) then
|
||||
tmp = df.global.ui.main.mode
|
||||
if tmp == 24 then -- (v)iew units {g,i,p,w} -> z (thoughts and preferences)
|
||||
-- context: @dwarfmode/ViewUnits/...
|
||||
--if df.global.ui_selected_unit > -1 then -- -1 = 'no units nearby'
|
||||
u = df.global.world.units.active[df.global.ui_selected_unit]
|
||||
--end
|
||||
elseif tmp == 25 then -- loo(k) unit -> enter (thoughs and preferences)
|
||||
-- context: @dwarfmode/LookAround/Unit
|
||||
tmp = df.global.ui_look_list.items[df.global.ui_look_cursor]
|
||||
if tmp.type == 2 then -- 0:item 1:terrain >>2: unit<< 3:building 4:colony/vermin 7:spatter
|
||||
u = tmp.unit
|
||||
end
|
||||
end
|
||||
elseif df.viewscreen_unitlistst:is_instance(v.parent) then -- (u)nit list -> (v)iew unit (not citizen)
|
||||
-- context: @unitlist/Citizens ; @unitlist/Livestock ; @unitlist/Others ; @unitlist/Dead
|
||||
u = v.parent.units[v.parent.page][ v.parent.cursor_pos[v.parent.page] ]
|
||||
end
|
||||
end -- switch viewscreen
|
||||
if not u and not silent then
|
||||
dfhack.printerr('No unit is selected in the UI or context not supported.')
|
||||
end
|
||||
return u
|
||||
end -- getUnit_byVS()
|
||||
|
||||
--http://lua-users.org/wiki/StringRecipes ----------
|
||||
function str2FirstUpper(str)
|
||||
return str:gsub("^%l", string.upper)
|
||||
end
|
||||
|
||||
--------------------------------------------------
|
||||
--http://lua-users.org/wiki/StringRecipes ----------
|
||||
local function tchelper(first, rest)
|
||||
return first:upper()..rest:lower()
|
||||
end
|
||||
|
||||
-- Add extra characters to the pattern if you need to. _ and ' are
|
||||
-- found in the middle of identifiers and English words.
|
||||
-- We must also put %w_' into [%w_'] to make it handle normal stuff
|
||||
-- and extra stuff the same.
|
||||
-- This also turns hex numbers into, eg. 0Xa7d4
|
||||
function str2TitleCase(str)
|
||||
return str:gsub("(%a)([%w_']*)", tchelper)
|
||||
end
|
||||
|
||||
--------------------------------------------------
|
||||
--isBlank suggestion by http://stackoverflow.com/a/10330861
|
||||
function isBlank(x)
|
||||
x = tostring(x) or ""
|
||||
-- returns (not not match_begin), _ = match_end => not not true , _ => true
|
||||
-- returns not not nil => false (no match)
|
||||
return not not x:find("^%s*$")
|
||||
end
|
||||
|
||||
--http://lua-users.org/wiki/StringRecipes (removed indents since I am not using them)
|
||||
function wrap(str, limit)--, indent, indent1)
|
||||
--indent = indent or ""
|
||||
--indent1 = indent1 or indent
|
||||
local limit = limit or 72
|
||||
local here = 1 ---#indent1
|
||||
return str:gsub("(%s+)()(%S+)()", --indent1..str:gsub(
|
||||
function(sp, st, word, fi)
|
||||
if fi-here > limit then
|
||||
here = st -- - #indent
|
||||
return "\n"..word --..indent..word
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
--------------------------------------------------
|
||||
---------------------- Time ----------------------
|
||||
--------------------------------------------------
|
||||
local TU_PER_DAY = 1200
|
||||
--[[
|
||||
if advmode then TU_PER_DAY = 86400 ? or only for cur_year_tick?
|
||||
advmod_TU / 72 = ticks
|
||||
--]]
|
||||
local TU_PER_MONTH = TU_PER_DAY * 28
|
||||
local TU_PER_YEAR = TU_PER_MONTH * 12
|
||||
|
||||
local MONTHS = {
|
||||
'Granite',
|
||||
'Slate',
|
||||
'Felsite',
|
||||
'Hematite',
|
||||
'Malachite',
|
||||
'Galena',
|
||||
'Limestone',
|
||||
'Sandstone',
|
||||
'Timber',
|
||||
'Moonstone',
|
||||
'Opal',
|
||||
'Obsidian',
|
||||
}
|
||||
Time = defclass(Time)
|
||||
function Time:init(args)
|
||||
self.year = args.year or 0
|
||||
self.ticks = args.ticks or 0
|
||||
end
|
||||
function Time:getDays() -- >>float<< Days as age (including years)
|
||||
return self.year * 336 + (self.ticks / TU_PER_DAY)
|
||||
end
|
||||
function Time:getMonths() -- >>int<< Months as age (not including years)
|
||||
return math.floor (self.ticks / TU_PER_MONTH)
|
||||
end
|
||||
function Time:getMonthStr() -- Month as date
|
||||
return MONTHS[self:getMonths()+1] or 'error'
|
||||
end
|
||||
function Time:getDayStr() -- Day as date
|
||||
local d = math.floor ( (self.ticks % TU_PER_MONTH) / TU_PER_DAY ) + 1
|
||||
if d == 11 or d == 12 or d == 13 then
|
||||
d = tostring(d)..'th'
|
||||
elseif d % 10 == 1 then
|
||||
d = tostring(d)..'st'
|
||||
elseif d % 10 == 2 then
|
||||
d = tostring(d)..'nd'
|
||||
elseif d % 10 == 3 then
|
||||
d = tostring(d)..'rd'
|
||||
else
|
||||
d = tostring(d)..'th'
|
||||
end
|
||||
return d
|
||||
end
|
||||
--function Time:__add()
|
||||
--end
|
||||
function Time:__sub(other)
|
||||
if DEBUG then print(self.year,self.ticks) end
|
||||
if DEBUG then print(other.year,other.ticks) end
|
||||
if self.ticks < other.ticks then
|
||||
return Time{ year = (self.year - other.year - 1) , ticks = (TU_PER_YEAR + self.ticks - other.ticks) }
|
||||
else
|
||||
return Time{ year = (self.year - other.year) , ticks = (self.ticks - other.ticks) }
|
||||
end
|
||||
end
|
||||
--------------------------------------------------
|
||||
--------------------------------------------------
|
||||
|
||||
--------------------------------------------------
|
||||
-------------------- Identity --------------------
|
||||
--------------------------------------------------
|
||||
local SINGULAR = 0
|
||||
local PLURAL = 1
|
||||
--local POSSESSIVE = 2
|
||||
|
||||
local PRONOUNS = {
|
||||
[0]='She',
|
||||
[1]='He',
|
||||
[2]='It',
|
||||
}
|
||||
local BABY = 0
|
||||
local CHILD = 1
|
||||
local ADULT = 2
|
||||
|
||||
local TRAINING_LEVELS = {
|
||||
[0] = ' (Semi-Wild)', -- Semi-wild
|
||||
' (Trained)', -- Trained
|
||||
' (-Trained-)', -- Well-trained
|
||||
' (+Trained+)', -- Skillfully trained
|
||||
' (*Trained*)', -- Expertly trained
|
||||
' ('..string.char(240)..'Trained'..string.char(240)..')', -- Exceptionally trained
|
||||
' ('..string.char(15)..'Trained'..string.char(15)..')', -- Masterully Trained
|
||||
' (Tame)', -- Domesticated
|
||||
'', -- undefined
|
||||
'', -- wild/untameable
|
||||
}
|
||||
|
||||
local DEATH_TYPES = {
|
||||
[0] = ' died of old age', -- OLD_AGE
|
||||
' starved to death', -- HUNGER
|
||||
' died of dehydration', -- THIRST
|
||||
' was shot and killed', -- SHOT
|
||||
' bled to death', -- BLEED
|
||||
' drowned', -- DROWN
|
||||
' suffocated', -- SUFFOCATE
|
||||
' was struck down', -- STRUCK_DOWN
|
||||
' was scuttled', -- SCUTTLE
|
||||
" didn't survive a collision", -- COLLISION
|
||||
' took a magma bath', -- MAGMA
|
||||
' took a magma shower', -- MAGMA_MIST
|
||||
' was incinerated by dragon fire', -- DRAGONFIRE
|
||||
' was killed by fire', -- FIRE
|
||||
' experienced death by SCALD', -- SCALD
|
||||
' was crushed by cavein', -- CAVEIN
|
||||
' was smashed by a drawbridge', -- DRAWBRIDGE
|
||||
' was killed by falling rocks', -- FALLING_ROCKS
|
||||
' experienced death by CHASM', -- CHASM
|
||||
' experienced death by CAGE', -- CAGE
|
||||
' was murdered', -- MURDER
|
||||
' was killed by a trap', -- TRAP
|
||||
' vanished', -- VANISH
|
||||
' experienced death by QUIT', -- QUIT
|
||||
' experienced death by ABANDON', -- ABANDON
|
||||
' suffered heat stroke', -- HEAT
|
||||
' died of hypothermia', -- COLD
|
||||
' experienced death by SPIKE', -- SPIKE
|
||||
' experienced death by ENCASE_LAVA', -- ENCASE_LAVA
|
||||
' experienced death by ENCASE_MAGMA', -- ENCASE_MAGMA
|
||||
' was preserved in ice', -- ENCASE_ICE
|
||||
' became headless', -- BEHEAD
|
||||
' was crucified', -- CRUCIFY
|
||||
' experienced death by BURY_ALIVE', -- BURY_ALIVE
|
||||
' experienced death by DROWN_ALT', -- DROWN_ALT
|
||||
' experienced death by BURN_ALIVE', -- BURN_ALIVE
|
||||
' experienced death by FEED_TO_BEASTS', -- FEED_TO_BEASTS
|
||||
' experienced death by HACK_TO_PIECES', -- HACK_TO_PIECES
|
||||
' choked on air', -- LEAVE_OUT_IN_AIR
|
||||
' experienced death by BOIL', -- BOIL
|
||||
' melted', -- MELT
|
||||
' experienced death by CONDENSE', -- CONDENSE
|
||||
' experienced death by SOLIDIFY', -- SOLIDIFY
|
||||
' succumbed to infection', -- INFECTION
|
||||
"'s ghost was put to rest with a memorial", -- MEMORIALIZE
|
||||
' scared to death', -- SCARE
|
||||
' experienced death by DARKNESS', -- DARKNESS
|
||||
' experienced death by COLLAPSE', -- COLLAPSE
|
||||
' was drained of blood', -- DRAIN_BLOOD
|
||||
' was slaughtered', -- SLAUGHTER
|
||||
' became roadkill', -- VEHICLE
|
||||
' killed by a falling object', -- FALLING_OBJECT
|
||||
}
|
||||
|
||||
--GHOST_TYPES[unit.relations.ghost_info.type].." This spirit has not been properly memorialized or buried."
|
||||
local GHOST_TYPES = {
|
||||
[0]="A murderous ghost.",
|
||||
"A sadistic ghost.",
|
||||
"A secretive ghost.",
|
||||
"An energetic poltergeist.",
|
||||
"An angry ghost.",
|
||||
"A violent ghost.",
|
||||
"A moaning spirit returned from the dead. It will generally trouble one unfortunate at a time.",
|
||||
"A howling spirit. The ceaseless noise is making sleep difficult.",
|
||||
"A troublesome poltergeist.",
|
||||
"A restless haunt, generally troubling past acquaintances and relatives.",
|
||||
"A forlorn haunt, seeking out known locations or drifting around the place of death.",
|
||||
}
|
||||
|
||||
|
||||
Identity = defclass(Identity)
|
||||
function Identity:init(args)
|
||||
local u = args.unit
|
||||
self.ident = dfhack.units.getIdentity(u)
|
||||
|
||||
self.unit = u
|
||||
self.name = dfhack.TranslateName( dfhack.units.getVisibleName(u) )
|
||||
self.name_en = dfhack.TranslateName( dfhack.units.getVisibleName(u) , true)
|
||||
self.raw_prof = dfhack.units.getProfessionName(u)
|
||||
self.pronoun = PRONOUNS[u.sex] or 'It'
|
||||
|
||||
if self.ident then
|
||||
self.birth_date = Time{year = self.ident.birth_year, ticks = self.ident.birth_second}
|
||||
self.race_id = self.ident.race
|
||||
self.caste_id = self.ident.caste
|
||||
if self.ident.histfig_id > -1 then
|
||||
self.hf_id = self.ident.histfig_id
|
||||
end
|
||||
else
|
||||
self.birth_date = Time{year = self.unit.relations.birth_year, ticks = self.unit.relations.birth_time}
|
||||
self.race_id = u.race
|
||||
self.caste_id = u.caste
|
||||
if u.hist_figure_id > -1 then
|
||||
self.hf_id = u.hist_figure_id
|
||||
end
|
||||
end
|
||||
self.race = df.global.world.raws.creatures.all[self.race_id]
|
||||
self.caste = self.race.caste[self.caste_id]
|
||||
|
||||
self.isCivCitizen = (df.global.ui.civ_id == u.civ_id)
|
||||
self.isStray = u.flags1.tame --self.isCivCitizen and not u.flags1.merchant
|
||||
self.cur_date = Time{year = df.global.cur_year, ticks = df.global.cur_year_tick}
|
||||
|
||||
|
||||
------------ death ------------
|
||||
self.dead = u.flags1.dead
|
||||
self.ghostly = u.flags3.ghostly
|
||||
self.undead = u.enemy.undead
|
||||
|
||||
if self.dead and self.hf_id then -- dead-dead not undead-dead
|
||||
local events = df.global.world.history.events2
|
||||
local e
|
||||
for idx = #events - 1,0,-1 do
|
||||
e = events[idx]
|
||||
if df.history_event_hist_figure_diedst:is_instance(e) and e.victim_hf == self.hf_id then
|
||||
self.death_event = e
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
if u.counters.death_id > -1 then -- if undead/ghostly dead or dead-dead
|
||||
self.death_info = df.global.world.deaths.all[u.counters.death_id]
|
||||
if not self.death_info.flags.discovered then
|
||||
self.missing = true
|
||||
end
|
||||
end
|
||||
-- slaughtered?
|
||||
if self.death_event then
|
||||
self.death_date = Time{year = self.death_event.year, ticks = self.death_event.seconds}
|
||||
elseif self.death_info then
|
||||
self.death_date = Time{year = self.death_info.event_year, ticks = self.death_info.event_time}
|
||||
end
|
||||
-- age now or age death?
|
||||
if self.dead and self.death_date then -- if cursed with no age? -- if hacked a ressurection, such that they aren't dead anymore, don't use the death date
|
||||
self.age_time = self.death_date - self.birth_date
|
||||
else
|
||||
self.age_time = self.cur_date - self.birth_date
|
||||
end
|
||||
if DEBUG then print( self.age_time.year,self.age_time.ticks,self.age_time:getMonths() ) end
|
||||
---------- ---------- ----------
|
||||
|
||||
|
||||
---------- caste_name ----------
|
||||
self.caste_name = {}
|
||||
if isBlank(self.caste.caste_name[SINGULAR]) then
|
||||
self.caste_name[SINGULAR] = self.race.name[SINGULAR]
|
||||
else
|
||||
self.caste_name[SINGULAR] = self.caste.caste_name[SINGULAR]
|
||||
end
|
||||
if isBlank(self.caste.caste_name[PLURAL]) then
|
||||
self.caste_name[PLURAL] = self.race.name[PLURAL]
|
||||
else
|
||||
self.caste_name[PLURAL] = self.caste.caste_name[PLURAL]
|
||||
end
|
||||
---------- ---------- ----------
|
||||
|
||||
--------- growth_status ---------
|
||||
-- 'baby_age' is the age the baby becomes a child
|
||||
-- 'child_age' is the age the child becomes an adult
|
||||
if self.age_time.year >= self.caste.misc.child_age then -- has child come of age becoming adult?
|
||||
self.growth_status = ADULT
|
||||
elseif self.age_time.year >= self.caste.misc.baby_age then -- has baby come of age becoming child?
|
||||
self.growth_status = CHILD
|
||||
else
|
||||
self.growth_status = BABY
|
||||
end
|
||||
---------- ---------- ----------
|
||||
|
||||
-------- aged_caste_name --------
|
||||
local caste_name, race_name
|
||||
if self.growth_status == ADULT then
|
||||
caste_name = self.caste.caste_name[SINGULAR]
|
||||
race_name = self.race.name[SINGULAR]
|
||||
elseif self.growth_status == CHILD then
|
||||
caste_name = self.caste.child_name[SINGULAR]
|
||||
race_name = self.race.general_child_name[SINGULAR]
|
||||
else --if self.growth_status == BABY then
|
||||
caste_name = self.caste.baby_name[SINGULAR]
|
||||
race_name = self.race.general_baby_name[SINGULAR]
|
||||
end
|
||||
self.aged_caste_name = {}
|
||||
if isBlank(caste_name[SINGULAR]) then
|
||||
self.aged_caste_name[SINGULAR] = race_name[SINGULAR]
|
||||
else
|
||||
self.aged_caste_name[SINGULAR] = caste_name[SINGULAR]
|
||||
end
|
||||
if isBlank(caste_name[PLURAL]) then
|
||||
self.aged_caste_name[PLURAL] = race_name[PLURAL]
|
||||
else
|
||||
self.aged_caste_name[PLURAL] = caste_name[PLURAL]
|
||||
end
|
||||
---------- ---------- ----------
|
||||
|
||||
----- Profession adjustment -----
|
||||
local prof = self.raw_prof
|
||||
if self.undead then
|
||||
prof = str2TitleCase( self.caste_name[SINGULAR] )
|
||||
if isBlank(u.enemy.undead.anon_7) then
|
||||
prof = prof..' Corpse'
|
||||
else
|
||||
prof = u.enemy.undead.anon_7 -- a reanimated body part will use this string instead
|
||||
end
|
||||
end
|
||||
--[[
|
||||
if self.ghostly then
|
||||
prof = 'Ghostly '..prof
|
||||
end
|
||||
--]]
|
||||
if u.curse.name_visible and not isBlank(u.curse.name) then
|
||||
prof = prof..' '..u.curse.name
|
||||
end
|
||||
if isBlank(self.name) then
|
||||
if self.isStray then
|
||||
prof = 'Stray '..prof --..TRAINING_LEVELS[u.training_level]
|
||||
end
|
||||
end
|
||||
self.prof = prof
|
||||
---------- ---------- ----------
|
||||
end
|
||||
--------------------------------------------------
|
||||
--------------------------------------------------
|
||||
--[[
|
||||
prof_id ?
|
||||
group_id ?
|
||||
fort_race_id
|
||||
fort_civ_id
|
||||
--fort_group_id?
|
||||
--]]
|
||||
|
||||
|
||||
UnitInfoViewer = defclass(UnitInfoViewer, gui.FramedScreen)
|
||||
UnitInfoViewer.focus_path = 'unitinfoviewer' -- -> dfhack/lua/unitinfoviewer
|
||||
UnitInfoViewer.ATTRS={
|
||||
frame_style = gui.GREY_LINE_FRAME,
|
||||
frame_inset = 2, -- used by init
|
||||
frame_outset = 1,--3, -- new, used by init; 0 = full screen, suggest 0, 1, or 3 or maybe 5
|
||||
--frame_title , -- not used
|
||||
--frame_width,frame_height calculated by frame inset and outset in init
|
||||
}
|
||||
function UnitInfoViewer:init(args) -- requires args.unit
|
||||
--if DEBUG then print('-----') end
|
||||
local x,y = dfhack.screen.getWindowSize()
|
||||
-- what if inset or outset are defined as {l,r,t,b}?
|
||||
x = x - 2*(self.frame_inset + 1 + self.frame_outset) -- 1=frame border thickness
|
||||
y = y - 2*(self.frame_inset + 1 + self.frame_outset) -- 1=frame border thickness
|
||||
self.frame_width = args.frame_width or x
|
||||
self.frame_height = args.frame_height or y
|
||||
self.text = {}
|
||||
if df.unit:is_instance(args.unit) then
|
||||
self.ident = Identity{ unit = args.unit }
|
||||
if not isBlank(self.ident.name_en) then
|
||||
self.frame_title = 'Unit: '..self.ident.name_en
|
||||
elseif not isBlank(self.ident.prof) then
|
||||
self.frame_title = 'Unit: '..self.ident.prof
|
||||
if self.ident.isStray then
|
||||
self.frame_title = self.frame_title..TRAINING_LEVELS[self.ident.unit.training_level]
|
||||
end
|
||||
end
|
||||
self:chunk_Name()
|
||||
self:chunk_Description()
|
||||
if not (self.ident.dead or self.ident.undead or self.ident.ghostly) then --not self.dead
|
||||
if self.ident.isCivCitizen then
|
||||
self:chunk_Age()
|
||||
self:chunk_MaxAge()
|
||||
end
|
||||
if self.ident.isStray then
|
||||
if self.ident.growth_status == ADULT then
|
||||
self:chunk_Milkable()
|
||||
end
|
||||
self:chunk_Grazer()
|
||||
if self.ident.growth_status == ADULT then
|
||||
self:chunk_Shearable()
|
||||
end
|
||||
if self.ident.growth_status == ADULT then
|
||||
self:chunk_EggLayer()
|
||||
end
|
||||
end
|
||||
self:chunk_BodySize()
|
||||
elseif self.ident.ghostly then
|
||||
self:chunk_Dead()
|
||||
self:chunk_Ghostly()
|
||||
elseif self.ident.undead then
|
||||
self:chunk_BodySize()
|
||||
self:chunk_Dead()
|
||||
else
|
||||
self:chunk_Dead()
|
||||
end
|
||||
else
|
||||
self:insert_chunk("No unit is selected in the UI or context not supported.",pens.LIGHTRED)
|
||||
end
|
||||
self:addviews{ widgets.Label{ frame={yalign=0}, text=self.text } }
|
||||
end
|
||||
function UnitInfoViewer:onInput(keys)
|
||||
if keys.LEAVESCREEN or keys.SELECT then
|
||||
self:dismiss()
|
||||
end
|
||||
end
|
||||
function UnitInfoViewer:onGetSelectedUnit()
|
||||
return self.ident.unit
|
||||
end
|
||||
function UnitInfoViewer:insert_chunk(str,pen)
|
||||
local lines = utils.split_string( wrap(str,self.frame_width) , NEWLINE )
|
||||
for i = 1,#lines do
|
||||
table.insert(self.text,{text=lines[i],pen=pen})
|
||||
table.insert(self.text,NEWLINE)
|
||||
end
|
||||
table.insert(self.text,NEWLINE)
|
||||
end
|
||||
function UnitInfoViewer:chunk_Name()
|
||||
local i = self.ident
|
||||
local u = i.unit
|
||||
local prof = i.prof
|
||||
local color = dfhack.units.getProfessionColor(u)
|
||||
local blurb
|
||||
if i.ghostly then
|
||||
prof = 'Ghostly '..prof
|
||||
end
|
||||
if i.isStray then
|
||||
prof = prof..TRAINING_LEVELS[u.training_level]
|
||||
end
|
||||
if isBlank(i.name) then
|
||||
if isBlank(prof) then
|
||||
blurb = 'I am a mystery'
|
||||
else
|
||||
blurb = prof
|
||||
end
|
||||
else
|
||||
if isBlank(prof) then
|
||||
blurb=i.name
|
||||
else
|
||||
blurb=i.name..', '..prof
|
||||
end
|
||||
end
|
||||
self:insert_chunk(blurb,dfhack.pen.parse{fg=color,bg=0})
|
||||
end
|
||||
function UnitInfoViewer:chunk_Description()
|
||||
local dsc = self.ident.caste.description
|
||||
if not isBlank(dsc) then
|
||||
self:insert_chunk(dsc,pens.WHITE)
|
||||
end
|
||||
end
|
||||
|
||||
function UnitInfoViewer:chunk_Age()
|
||||
local i = self.ident
|
||||
local age_str -- = ''
|
||||
if i.age_time.year > 1 then
|
||||
age_str = tostring(i.age_time.year)..' years old'
|
||||
elseif i.age_time.year > 0 then -- == 1
|
||||
age_str = '1 year old'
|
||||
else --if age_time.year == 0 then
|
||||
local age_m = i.age_time:getMonths() -- math.floor
|
||||
if age_m > 1 then
|
||||
age_str = tostring(age_m)..' months old'
|
||||
elseif age_m > 0 then -- age_m == 1
|
||||
age_str = '1 month old'
|
||||
else -- if age_m == 0 then -- and age_m < 0 which would be an error
|
||||
age_str = 'a newborn'
|
||||
end
|
||||
end
|
||||
local blurb = i.pronoun..' is '..age_str
|
||||
if i.race_id == df.global.ui.race_id then
|
||||
blurb = blurb..', born on the '..i.birth_date:getDayStr()..' of '..i.birth_date:getMonthStr()..' in the year '..tostring(i.birth_date.year)..PERIOD
|
||||
else
|
||||
blurb = blurb..PERIOD
|
||||
end
|
||||
self:insert_chunk(blurb,pens.YELLOW)
|
||||
end
|
||||
|
||||
function UnitInfoViewer:chunk_MaxAge()
|
||||
local i = self.ident
|
||||
local maxage = math.floor( (i.caste.misc.maxage_max + i.caste.misc.maxage_min)/2 )
|
||||
--or i.unit.curse.add_tags1.NO_AGING hidden ident?
|
||||
if i.caste.misc.maxage_min == -1 then
|
||||
maxage = ' die of unnatural causes.'
|
||||
elseif maxage == 0 then
|
||||
maxage = ' die at a very young age.'
|
||||
elseif maxage == 1 then
|
||||
maxage = ' live about '..tostring(maxage)..' year.'
|
||||
else
|
||||
maxage = ' live about '..tostring(maxage)..' years.'
|
||||
end
|
||||
--' is expected to '..
|
||||
local blurb = str2FirstUpper(i.caste_name[PLURAL])..maxage
|
||||
self:insert_chunk(blurb,pens.DARKGREY)
|
||||
end
|
||||
function UnitInfoViewer:chunk_Grazer()
|
||||
if self.ident.caste.flags.GRAZER then
|
||||
local blurb = 'Grazing satisfies '..tostring(self.ident.caste.misc.grazer)..' units of hunger.'
|
||||
self:insert_chunk(blurb,pens.LIGHTGREEN)
|
||||
end
|
||||
end
|
||||
function UnitInfoViewer:chunk_EggLayer()
|
||||
local caste = self.ident.caste
|
||||
if caste.flags.LAYS_EGGS then
|
||||
local clutch = math.floor( (caste.misc.clutch_size_max + caste.misc.clutch_size_min)/2 )
|
||||
local blurb = 'Lays clutches of about '..tostring(clutch)
|
||||
if clutch > 1 then
|
||||
blurb = blurb..' eggs.'
|
||||
else
|
||||
blurb = blurb..' egg.'
|
||||
end
|
||||
self:insert_chunk(blurb,pens.GREEN)
|
||||
end
|
||||
end
|
||||
function UnitInfoViewer:chunk_Milkable()
|
||||
local i = self.ident
|
||||
if i.caste.flags.MILKABLE then
|
||||
local milk = dfhack.matinfo.decode( i.caste.extracts.milkable_mat , i.caste.extracts.milkable_matidx )
|
||||
if milk then
|
||||
local days,seconds = math.modf ( i.caste.misc.milkable / TU_PER_DAY )
|
||||
days = (seconds > 0) and (tostring(days)..' to '..tostring(days + 1)) or tostring(days)
|
||||
--local blurb = pronoun..' produces '..milk:toString()..' every '..days..' days.'
|
||||
local blurb = (i.growth_status == ADULT) and (i.pronoun..' secretes ') or str2FirstUpper(i.caste_name[PLURAL])..' secrete '
|
||||
blurb = blurb..milk:toString()..' every '..days..' days.'
|
||||
self:insert_chunk(blurb,pens.LIGHTCYAN)
|
||||
end
|
||||
end
|
||||
end
|
||||
function UnitInfoViewer:chunk_Shearable()
|
||||
local i = self.ident
|
||||
local mat_types = i.caste.body_info.materials.mat_type
|
||||
local mat_idxs = i.caste.body_info.materials.mat_index
|
||||
local mat_info, blurb
|
||||
for idx,mat_type in ipairs(mat_types) do
|
||||
mat_info = dfhack.matinfo.decode(mat_type,mat_idxs[idx])
|
||||
if mat_info and mat_info.material.flags.YARN then
|
||||
blurb = (i.growth_status == ADULT) and (i.pronoun..' produces ') or str2FirstUpper(i.caste_name[PLURAL])..' produce '
|
||||
blurb = blurb..mat_info:toString()..PERIOD
|
||||
self:insert_chunk(blurb,pens.BROWN)
|
||||
end
|
||||
end
|
||||
end
|
||||
function UnitInfoViewer:chunk_BodySize()
|
||||
local i = self.ident
|
||||
local pat = i.unit.body.physical_attrs
|
||||
local blurb = i.pronoun..' appears to be about '..pat.STRENGTH.value..':'..pat.AGILITY.value..' cubic decimeters in size.'
|
||||
self:insert_chunk(blurb,pens.LIGHTBLUE)
|
||||
end
|
||||
function UnitInfoViewer:chunk_Ghostly()
|
||||
local blurb = GHOST_TYPES[self.ident.unit.relations.ghost_info.type].." This spirit has not been properly memorialized or buried."
|
||||
self:insert_chunk(blurb,pens.LIGHTMAGENTA)
|
||||
-- Arose in relations.curse_year curse_time
|
||||
end
|
||||
function UnitInfoViewer:chunk_Dead()
|
||||
local i = self.ident
|
||||
local blurb, str, pen
|
||||
if i.missing then --dfhack.units.isDead(unit)
|
||||
str = ' is missing.'
|
||||
pen = pens.WHITE
|
||||
elseif i.death_event then
|
||||
--str = "The Caste_name Unit_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
|
||||
str = DEATH_TYPES[i.death_event.death_cause]..PERIOD
|
||||
pen = pens.MAGENTA
|
||||
elseif i.death_info then
|
||||
--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
|
||||
str = DEATH_TYPES[i.death_info.death_cause]..PERIOD
|
||||
pen = pens.MAGENTA
|
||||
elseif i.unit.flags2.slaughter and i.unit.flags2.killed then
|
||||
str = ' was slaughtered.'
|
||||
pen = pens.MAGENTA
|
||||
else
|
||||
str = ' is dead.'
|
||||
pen = pens.MAGENTA
|
||||
end
|
||||
if i.undead or i.ghostly then
|
||||
str = ' is undead.'
|
||||
pen = pens.GREY
|
||||
end
|
||||
blurb = 'The '..i.prof -- assume prof is not blank
|
||||
if not isBlank(i.name) then
|
||||
blurb = blurb..', '..i.name
|
||||
end
|
||||
blurb = blurb..str
|
||||
self:insert_chunk(blurb,pen)
|
||||
end
|
||||
|
||||
-- only show if UnitInfoViewer isn't the current focus
|
||||
if dfhack.gui.getCurFocus() ~= 'dfhack/lua/'..UnitInfoViewer.focus_path then
|
||||
local gui_no_unit = false -- show if not found?
|
||||
local unit = getUnit_byVS(gui_no_unit) -- silent? or let the gui display
|
||||
if unit or gui_no_unit then
|
||||
local kan_viewscreen = UnitInfoViewer{unit = unit}
|
||||
kan_viewscreen:show()
|
||||
end
|
||||
end
|
||||
|
@ -1,29 +0,0 @@
|
||||
-- invasion-now civName start end : schedules an invasion in the near future, or several
|
||||
|
||||
args = {...}
|
||||
civName = args[1]
|
||||
|
||||
if ( civName ~= nil ) then
|
||||
--find the civ with that name
|
||||
evilEntity = nil;
|
||||
for _,candidate in ipairs(df.global.world.entities.all) do
|
||||
if candidate.entity_raw.code == civName then
|
||||
evilEntity = candidate
|
||||
break
|
||||
end
|
||||
end
|
||||
if ( evilEntity == nil ) then
|
||||
do return end
|
||||
end
|
||||
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 = evilEntity
|
||||
})
|
||||
end
|
||||
end
|
@ -0,0 +1,100 @@
|
||||
--modtools/add-syndrome.lua
|
||||
--author expwnent
|
||||
--add syndromes to a target, or remove them
|
||||
|
||||
local syndromeUtil = require 'syndrome-util'
|
||||
local utils = require 'utils'
|
||||
|
||||
validArgs = validArgs or utils.invert({
|
||||
'help',
|
||||
'syndrome',
|
||||
'resetPolicy',
|
||||
'erase',
|
||||
'eraseOldest',
|
||||
'eraseAll',
|
||||
'target',
|
||||
'skipImmunities'
|
||||
})
|
||||
|
||||
local args = utils.processArgs({...}, validArgs)
|
||||
|
||||
if args.help then
|
||||
print([[scripts/modtools/add-syndrome usage:
|
||||
arguments:
|
||||
-help
|
||||
print this help message
|
||||
-syndrome name
|
||||
the name of the syndrome to operate on
|
||||
examples:
|
||||
"gila monster bite"
|
||||
-resetPolicy policy
|
||||
specify a policy of what to do if the unit already has an instance of the syndrome
|
||||
examples:
|
||||
NewInstance
|
||||
default behavior: create a new instance of the syndrome
|
||||
DoNothing
|
||||
ResetDuration
|
||||
AddDuration
|
||||
-erase
|
||||
instead of adding an instance of the syndrome, erase one
|
||||
-eraseAll
|
||||
erase every instance of the syndrome
|
||||
-target id
|
||||
the unit id of the target unit
|
||||
examples:
|
||||
0
|
||||
28
|
||||
-skipImmunities
|
||||
add the syndrome to the target regardless of whether it is immune to the syndrome
|
||||
]])
|
||||
return
|
||||
end
|
||||
|
||||
if args.resetPolicy then
|
||||
args.resetPolicy = syndromeUtil.ResetPolicy[args.resetPolicy]
|
||||
if not args.resetPolicy then
|
||||
error ('Invalid reset policy.')
|
||||
end
|
||||
end
|
||||
|
||||
if not args.syndrome then
|
||||
error 'Specify a syndrome name.'
|
||||
end
|
||||
|
||||
local syndrome
|
||||
for _,syn in ipairs(df.global.world.raws.syndromes.all) do
|
||||
if syn.syn_name == args.syndrome then
|
||||
syndrome = syn
|
||||
break
|
||||
end
|
||||
end
|
||||
if not syndrome then
|
||||
error ('Invalid syndrome: ' .. args.syndrome)
|
||||
end
|
||||
args.syndrome = syndrome
|
||||
|
||||
if not args.target then
|
||||
error 'Specify a target.'
|
||||
end
|
||||
local targ = df.unit.find(tonumber(args.target))
|
||||
if not targ then
|
||||
error ('Could not find target: ' .. args.target)
|
||||
end
|
||||
args.target = targ
|
||||
|
||||
if args.erase then
|
||||
syndromeUtil.eraseSyndrome(args.target,args.syndrome.id,args.eraseOldest)
|
||||
return
|
||||
end
|
||||
|
||||
if args.eraseAll then
|
||||
syndromeUtil.eraseSyndromes(args.target,args.syndrome.id)
|
||||
return
|
||||
end
|
||||
|
||||
if skipImmunities then
|
||||
syndromeUtil.infectWithSyndrome(args.target,args.syndrome,args.resetPolicy)
|
||||
else
|
||||
syndromeUtil.infectWithSyndromeIfValidTarget(args.target,args.syndrome,args.resetPolicy)
|
||||
end
|
||||
|
@ -0,0 +1,17 @@
|
||||
--scripts/modtools/anonymous-script.lua
|
||||
--author expwnent
|
||||
--a tool for invoking simple lua scripts without putting them in a file first
|
||||
--anonymous-script "print(args[1])" arg1 arg2
|
||||
--# prints "arg1"
|
||||
|
||||
local args = {...}
|
||||
|
||||
--automatically collect arguments to make the anonymous script more succinct
|
||||
local f,err = load('local args = {...}; ' .. args[1], '=(anonymous lua script)') --,'=(lua command)', 't')
|
||||
if err then
|
||||
error(err)
|
||||
end
|
||||
|
||||
--we don't care about the result even if they return something for some reason: we just want to ensure its side-effects happen and print appropriate error messages
|
||||
dfhack.safecall(f,table.unpack(args,2))
|
||||
|
@ -0,0 +1,110 @@
|
||||
--scripts/modtools/create-item.lua
|
||||
--author expwnent
|
||||
--creates an item of a given type and material
|
||||
|
||||
local utils = require 'utils'
|
||||
|
||||
validArgs = validArgs or utils.invert({
|
||||
'help',
|
||||
'creator',
|
||||
'material',
|
||||
'item',
|
||||
-- 'creature',
|
||||
-- 'caste',
|
||||
'matchingGloves',
|
||||
'matchingShoes'
|
||||
})
|
||||
|
||||
local args = utils.processArgs({...}, validArgs)
|
||||
|
||||
if args.help then
|
||||
print(
|
||||
[[scripts/modtools/create-item.lua
|
||||
arguments:
|
||||
-help
|
||||
print this help message
|
||||
-creator id
|
||||
specify the id of the unit who will create the item
|
||||
examples:
|
||||
0
|
||||
2
|
||||
-material matstring
|
||||
specify the material of the item to be created
|
||||
examples:
|
||||
INORGANIC:IRON
|
||||
CREATURE_MAT:DWARF:BRAIN
|
||||
PLANT_MAT:MUSHROOM_HELMET_PLUMP:DRINK
|
||||
-item itemstr
|
||||
specify the itemdef of the item to be created
|
||||
examples:
|
||||
WEAPON:ITEM_WEAPON_PICK
|
||||
-matchingShoes
|
||||
create two of this item
|
||||
-matchingGloves
|
||||
create two of this item, and set handedness appropriately
|
||||
]])
|
||||
return
|
||||
end
|
||||
|
||||
if not args.creator or not tonumber(args.creator) or not df.unit.find(tonumber(args.creator)) then
|
||||
error 'Invalid creator.'
|
||||
end
|
||||
args.creator = df.unit.find(tonumber(args.creator))
|
||||
if not args.creator then
|
||||
error 'Invalid creator.'
|
||||
end
|
||||
|
||||
if not args.item then
|
||||
error 'Invalid item.'
|
||||
end
|
||||
local itemType = dfhack.items.findType(args.item)
|
||||
if itemType == -1 then
|
||||
error 'Invalid item.'
|
||||
end
|
||||
local itemSubtype = dfhack.items.findSubtype(args.item)
|
||||
|
||||
organicTypes = organicTypes or utils.invert({
|
||||
df.item_type.REMAINS,
|
||||
df.item_type.FISH,
|
||||
df.item_type.FISH_RAW,
|
||||
df.item_type.VERMIN,
|
||||
df.item_type.PET,
|
||||
df.item_type.EGG,
|
||||
})
|
||||
|
||||
if organicTypes[itemType] then
|
||||
--TODO: look up creature and caste
|
||||
error 'Not yet supported.'
|
||||
end
|
||||
|
||||
badTypes = badTypes or utils.invert({
|
||||
df.item_type.CORPSE,
|
||||
df.item_type.CORPSEPIECE,
|
||||
df.item_type.FOOD,
|
||||
})
|
||||
|
||||
if badTypes[itemType] then
|
||||
error 'Not supported.'
|
||||
end
|
||||
|
||||
if not args.material then
|
||||
error 'Invalid material.'
|
||||
end
|
||||
args.material = dfhack.matinfo.find(args.material)
|
||||
if not args.material then
|
||||
error 'Invalid material.'
|
||||
end
|
||||
|
||||
local item1 = dfhack.items.createItem(itemType, itemSubtype, args.material['type'], args.material.index, args.creator)
|
||||
if args.matchingGloves or args.matchingShoes then
|
||||
if args.matchingGloves then
|
||||
item1 = df.item.find(item1)
|
||||
item1:setGloveHandedness(1);
|
||||
end
|
||||
local item2 = dfhack.items.createItem(itemType, itemSubtype, args.material['type'], args.material.index, args.creator)
|
||||
if args.matchingGloves then
|
||||
item2 = df.item.find(item2)
|
||||
item2:setGloveHandedness(2);
|
||||
end
|
||||
end
|
||||
|
@ -0,0 +1,80 @@
|
||||
-- scripts/modtools/force.lua
|
||||
-- author Putnam
|
||||
-- edited by expwnent
|
||||
-- Forces an event.
|
||||
|
||||
local utils = require 'utils'
|
||||
|
||||
local function findCiv(arg)
|
||||
local entities = df.global.world.entities.all
|
||||
if tonumber(arg) then return arg end
|
||||
if arg then
|
||||
for eid,entity in ipairs(entities) do
|
||||
if entity.entity_raw.code == arg then return entity end
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
validArgs = validArgs or utils.invert({
|
||||
'eventType',
|
||||
'help',
|
||||
'civ'
|
||||
})
|
||||
|
||||
local args = utils.processArgs({...}, validArgs)
|
||||
if next(args) == nil or args.help then
|
||||
print([[force usage
|
||||
arguments:
|
||||
-help
|
||||
print this help message
|
||||
-eventType event
|
||||
specify the type of the event to trigger
|
||||
examples:
|
||||
MegaBeast
|
||||
Migrants
|
||||
Caravan
|
||||
Diplomat
|
||||
WildlifeCurious
|
||||
WildlifeMischievous
|
||||
WildlifeFlier
|
||||
CivAttack
|
||||
NightCreature
|
||||
-civ entity
|
||||
specify the civ of the event, if applicable
|
||||
examples:
|
||||
player
|
||||
MOUNTAIN
|
||||
EVIL
|
||||
28
|
||||
]])
|
||||
print('force: -eventType [Megabeast, Migrants, Caravan, Diplomat, WildlifeCurious, WildlifeMischievous, WildlifeFlier, CivAttack, NightCreature] -civ [player,ENTITY_ID]')
|
||||
return
|
||||
end
|
||||
|
||||
if not args.eventType then
|
||||
error 'Specify an eventType.'
|
||||
elseif not df.timed_event_type[args.eventType] then
|
||||
error('Invalid eventType: ' .. args.eventType)
|
||||
end
|
||||
|
||||
if args.civ == 'player' then
|
||||
args.civ = df.historical_entity.find(df.global.ui.civ_id)
|
||||
elseif args.civ then
|
||||
args.civ = findCiv(args.civ)
|
||||
end
|
||||
|
||||
if args.eventType == 'Migrants' then
|
||||
args.civ = df.historical_entity.find(df.global.ui.civ_id)
|
||||
end
|
||||
|
||||
local timedEvent = df.timed_event:new()
|
||||
timedEvent['type'] = df.timed_event_type[args.eventType]
|
||||
timedEvent.season = df.global.cur_season
|
||||
timedEvent.season_ticks = df.global.cur_season_tick
|
||||
if args.civ then
|
||||
timedEvent.entity = args.civ
|
||||
end
|
||||
|
||||
df.global.timed_events:insert('#', timedEvent)
|
||||
|
@ -0,0 +1,157 @@
|
||||
--scripts/modtools/interaction-trigger.lua
|
||||
--author expwnent
|
||||
--triggers scripts when a unit does a unit interaction on another
|
||||
|
||||
local eventful = require 'plugins.eventful'
|
||||
local utils = require 'utils'
|
||||
|
||||
attackStrTriggers = attackStrTriggers or {}
|
||||
defendStrTriggers = defendStrTriggers or {}
|
||||
|
||||
eventful.enableEvent(eventful.eventType.INTERACTION,1) --cheap, so every tick is fine
|
||||
eventful.enableEvent(eventful.eventType.UNLOAD,1)
|
||||
|
||||
eventful.onUnload.interactionTrigger = function()
|
||||
attackStrTriggers = {}
|
||||
defendStrTriggers = {}
|
||||
end
|
||||
|
||||
local function processTrigger(args)
|
||||
local command = {}
|
||||
for _,arg in ipairs(args.command) do
|
||||
if arg == '\\ATTACK_VERB' then
|
||||
table.insert(command,args.attackVerb)
|
||||
elseif arg == '\\DEFEND_VERB' then
|
||||
table.insert(command,args.defendVerb)
|
||||
elseif arg == '\\ATTACKER_ID' then
|
||||
table.insert(command,args.attackerId)
|
||||
elseif arg == '\\DEFENDER_ID' then
|
||||
table.insert(command,args.defenderId)
|
||||
elseif arg == '\\ATTACK_REPORT' then
|
||||
table.insert(command,args.attackReport)
|
||||
elseif arg == '\\DEFEND_REPORT' then
|
||||
table.insert(command,args.defendReport)
|
||||
elseif string.sub(arg,1,1) == '\\' then
|
||||
table.insert(command,string.sub(arg,2))
|
||||
else
|
||||
table.insert(command,arg)
|
||||
end
|
||||
end
|
||||
dfhack.run_command(table.unpack(command))
|
||||
end
|
||||
|
||||
eventful.onInteraction.interactionTrigger = function(attackVerb, defendVerb, attacker, defender, attackReport, defendReport)
|
||||
local extras = {}
|
||||
extras.attackVerb = attackVerb
|
||||
extras.defendVerb = defendVerb
|
||||
extras.attackReport = attackReport
|
||||
extras.defendReport = defendReport
|
||||
extras.attackerId = attacker
|
||||
extras.defenderId = defender
|
||||
local suppressAttack = false
|
||||
local suppressDefend = false
|
||||
for _,trigger in ipairs(attackStrTriggers[attackVerb] or {}) do
|
||||
suppressAttack = suppressAttack or trigger.suppressAttack
|
||||
suppressDefend = suppressDefend or trigger.suppressDefend
|
||||
utils.fillTable(trigger,extras)
|
||||
processTrigger(trigger)
|
||||
utils.unfillTable(trigger,extras)
|
||||
end
|
||||
for _,trigger in ipairs(defendStrTriggers[defendVerb] or {}) do
|
||||
suppressAttack = suppressAttack or trigger.suppressAttack
|
||||
suppressDefend = suppressDefend or trigger.suppressDefend
|
||||
utils.fillTable(trigger,extras)
|
||||
processTrigger(trigger)
|
||||
utils.unfillTable(trigger,extras)
|
||||
end
|
||||
|
||||
local eraseReport = function(unit,report)
|
||||
for i,v in ipairs(unit.reports.log.Combat) do
|
||||
if v == report then
|
||||
unit.reports.log.Combat:erase(i)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
if suppressAttack or suppressDefend then
|
||||
attacker = df.unit.find(tonumber(attacker))
|
||||
defender = df.unit.find(tonumber(defender))
|
||||
end
|
||||
if suppressAttack then
|
||||
eraseReport(attacker,attackReport)
|
||||
eraseReport(defender,attackReport)
|
||||
end
|
||||
if suppressDefend then
|
||||
eraseReport(attacker,defendReport)
|
||||
eraseReport(defender,defendReport)
|
||||
end
|
||||
end
|
||||
|
||||
----------------------------------------------------
|
||||
--argument processing
|
||||
|
||||
validArgs = validArgs or utils.invert({
|
||||
'clear',
|
||||
'help',
|
||||
'onAttackStr',
|
||||
'onDefendStr',
|
||||
'command',
|
||||
'suppressAttack',
|
||||
'suppressDefend',
|
||||
})
|
||||
|
||||
local args = utils.processArgs({...}, validArgs)
|
||||
|
||||
if args.help then
|
||||
print([[scripts/modtools/interaction-trigger.lua
|
||||
arguments:
|
||||
-help
|
||||
print this help message
|
||||
-clear
|
||||
unregisters all triggers
|
||||
-onAttackStr str
|
||||
trigger the command when the attack verb is "str"
|
||||
-onDefendStr str
|
||||
trigger the command when the defend verb is "str"
|
||||
-suppressAttack
|
||||
delete the attack announcement from the combat logs
|
||||
-suppressDefend
|
||||
delete the defend announcement from the combat logs
|
||||
-command [ commandStrs ]
|
||||
specify the command to be executed
|
||||
commandStrs
|
||||
\\ATTACK_VERB
|
||||
\\DEFEND_VERB
|
||||
\\ATTACKER_ID
|
||||
\\DEFENDER_ID
|
||||
\\ATTACK_REPORT
|
||||
\\DEFEND_REPORT
|
||||
\\anything -> anything
|
||||
anything -> anything
|
||||
]])
|
||||
return
|
||||
end
|
||||
|
||||
if args.clear then
|
||||
attackStrTriggers = {}
|
||||
defendStrTriggers = {}
|
||||
end
|
||||
|
||||
if not args.command then
|
||||
return
|
||||
end
|
||||
|
||||
if args.onAttackStr then
|
||||
if not attackStrTriggers[args.onAttackStr] then
|
||||
attackStrTriggers[args.onAttackStr] = {}
|
||||
end
|
||||
table.insert(attackStrTriggers[args.onAttackStr], args)
|
||||
end
|
||||
|
||||
if args.onDefendStr then
|
||||
if not defendStrTriggers[args.onDefendStr] then
|
||||
defendStrTriggers[args.onDefendStr] = {}
|
||||
end
|
||||
table.insert(defendStrTriggers[args.onDefendStr], args)
|
||||
end
|
||||
|
@ -0,0 +1,178 @@
|
||||
--scripts/modtools/invader-item-destroyer.lua
|
||||
--author expwnent
|
||||
--configurably deletes invader items when they die
|
||||
|
||||
local eventful = require 'plugins.eventful'
|
||||
local utils = require 'utils'
|
||||
|
||||
--invaders = invaders or {}
|
||||
entities = entities or {}
|
||||
items = items or {}
|
||||
|
||||
allEntities = allEntities or false
|
||||
allItems = allitems or true
|
||||
|
||||
eventful.enableEvent(eventful.eventType.UNLOAD,1)
|
||||
eventful.onUnload.invaderItemDestroyer = function()
|
||||
entities = {}
|
||||
items = {}
|
||||
allEntities = false
|
||||
allItems = true
|
||||
end
|
||||
|
||||
eventful.enableEvent(eventful.eventType.UNIT_DEATH, 1) --requires iterating through all units
|
||||
eventful.onUnitDeath.invaderItemDestroyer = function(unitId)
|
||||
local unit = df.unit.find(unitId)
|
||||
if not unit then
|
||||
return
|
||||
end
|
||||
|
||||
local entity = df.historical_entity.find(unit.civ_id)
|
||||
if not allEntities and not entity then
|
||||
return
|
||||
end
|
||||
|
||||
if not allEntities and not entities[entity.entity_raw.code] then
|
||||
return
|
||||
end
|
||||
|
||||
if dfhack.units.isCitizen(unit) then
|
||||
return
|
||||
end
|
||||
|
||||
local function forEach(item)
|
||||
if not allItems and not items[dfhack.items.getSubtypeDef(item:getType(),item:getSubtype()).id] then
|
||||
return
|
||||
end
|
||||
if not (item.flags.foreign and item.flags.forbid) then
|
||||
return
|
||||
end
|
||||
if item.pos.x ~= unit.pos.x then
|
||||
return
|
||||
end
|
||||
if item.pos.y ~= unit.pos.y then
|
||||
return
|
||||
end
|
||||
if item.pos.z ~= unit.pos.z then
|
||||
return
|
||||
end
|
||||
item.flags.garbage_collect = true
|
||||
item.flags.forbid = true
|
||||
item.flags.hidden = true
|
||||
end
|
||||
|
||||
for _,item in ipairs(unit.inventory) do
|
||||
local item2 = df.item.find(item.item)
|
||||
forEach(item2)
|
||||
end
|
||||
--for each item on the ground
|
||||
local block = dfhack.maps.getTileBlock(unit.pos.x, unit.pos.y, unit.pos.z)
|
||||
for _,item in ipairs(block.items) do
|
||||
local item2 = df.item.find(item)
|
||||
forEach(item2)
|
||||
end
|
||||
end
|
||||
--[[eventful.onUnitDeath.invaderItemDestroyer = function(unit)
|
||||
if invaders[unit] then
|
||||
print ('Invader ' .. unit .. ' dies.')
|
||||
end
|
||||
for _,item in ipairs(invaders[unit] or {}) do
|
||||
local item2 = df.item.find(item)
|
||||
if item2 then
|
||||
print ('deleting item ' .. item)
|
||||
item2.flags.garbage_collect = true
|
||||
item2.flags.forbid = true
|
||||
item2.flags.hidden = true
|
||||
item2.flags.encased = true
|
||||
end
|
||||
end
|
||||
invaders[unit] = nil
|
||||
--TODO: delete corpses?
|
||||
end]]
|
||||
|
||||
validArgs = validArgs or utils.invert({
|
||||
'clear',
|
||||
'help',
|
||||
'allRaces',
|
||||
'allEntities',
|
||||
'allItems',
|
||||
'item',
|
||||
'entity',
|
||||
'race',
|
||||
})
|
||||
|
||||
local args = utils.processArgs({...}, validArgs)
|
||||
|
||||
if args.clear then
|
||||
entities = {}
|
||||
items = {}
|
||||
allEntities = false
|
||||
allItems = true
|
||||
end
|
||||
|
||||
if args.help then
|
||||
print([[scripts/modtools/invader-item-destroyer.lua usage
|
||||
arguments:
|
||||
-help
|
||||
print this help message
|
||||
-clear
|
||||
reset all registered data
|
||||
-allEntities [true/false]
|
||||
set whether it should delete items from invaders from any civ
|
||||
-allItems [true/false]
|
||||
set whether it should delete all invader items regardless of type when an appropriate invader dies
|
||||
-item itemdef
|
||||
set a particular itemdef to be destroyed when an invader from an appropriate civ dies
|
||||
examples:
|
||||
ITEM_WEAPON_PICK
|
||||
-entity entityName
|
||||
set a particular entity up so that its invaders destroy their items shortly after death
|
||||
examples:
|
||||
MOUNTAIN
|
||||
EVIL
|
||||
]])
|
||||
return
|
||||
end
|
||||
|
||||
if args.allEntities then
|
||||
if args.allEntities == 'true' then
|
||||
allEntities = true
|
||||
else
|
||||
allEntities = false
|
||||
end
|
||||
end
|
||||
if args.allItems then
|
||||
if args.allItems == 'true' then
|
||||
allItems = true
|
||||
else
|
||||
allItems = false
|
||||
end
|
||||
end
|
||||
|
||||
if args.item then
|
||||
local itemType
|
||||
for _,itemdef in ipairs(df.global.world.raws.itemdefs.all) do
|
||||
if itemdef.id == args.item then
|
||||
itemType = itemdef.id
|
||||
break
|
||||
end
|
||||
end
|
||||
if not itemType then
|
||||
error ('Invalid item type: ' .. args.item)
|
||||
end
|
||||
items[itemType] = true
|
||||
end
|
||||
if args.entity then
|
||||
local success
|
||||
for _,entity in ipairs(df.global.world.entities.all) do
|
||||
if entity.entity_raw.code == args.entity then
|
||||
success = true
|
||||
break
|
||||
end
|
||||
end
|
||||
if not success then
|
||||
error 'Invalid entity'
|
||||
end
|
||||
entities[args.entity] = true
|
||||
end
|
||||
|
@ -0,0 +1,274 @@
|
||||
--scripts/modtools/attack-trigger.lua
|
||||
--author expwnent
|
||||
--based on itemsyndrome by Putnam
|
||||
--triggers scripts when a unit attacks another with a weapon type, a weapon of a particular material, or a weapon contaminated with a particular material, or when a unit equips/unequips a particular item type, an item of a particular material, or an item contaminated with a particular material
|
||||
|
||||
local eventful = require 'plugins.eventful'
|
||||
local utils = require 'utils'
|
||||
|
||||
itemTriggers = itemTriggers or {}
|
||||
materialTriggers = materialTriggers or {}
|
||||
contaminantTriggers = contaminantTriggers or {}
|
||||
|
||||
eventful.enableEvent(eventful.eventType.UNIT_ATTACK,1) -- this event type is cheap, so checking every tick is fine
|
||||
eventful.enableEvent(eventful.eventType.INVENTORY_CHANGE,5) --this is expensive, but you might still want to set it lower
|
||||
eventful.enableEvent(eventful.eventType.UNLOAD,1)
|
||||
|
||||
eventful.onUnload.itemTrigger = function()
|
||||
itemTriggers = {}
|
||||
materialTriggers = {}
|
||||
contaminantTriggers = {}
|
||||
end
|
||||
|
||||
function processTrigger(command)
|
||||
local command2 = {}
|
||||
for i,arg in ipairs(command.command) do
|
||||
if arg == '\\ATTACKER_ID' then
|
||||
command2[i] = '' .. command.attacker.id
|
||||
elseif arg == '\\DEFENDER_ID' then
|
||||
command2[i] = '' .. command.defender.id
|
||||
elseif arg == '\\ITEM_MATERIAL' then
|
||||
command2[i] = command.itemMat:getToken()
|
||||
elseif arg == '\\ITEM_MATERIAL_TYPE' then
|
||||
command2[i] = command.itemMat['type']
|
||||
elseif arg == '\\ITEM_MATERIAL_INDEX' then
|
||||
command2[i] = command.itemMat.index
|
||||
elseif arg == '\\ITEM_ID' then
|
||||
command2[i] = '' .. command.item.id
|
||||
elseif arg == '\\ITEM_TYPE' then
|
||||
command2[i] = command.itemType
|
||||
elseif arg == '\\CONTAMINANT_MATERIAL' then
|
||||
command2[i] = command.contaminantMat:getToken()
|
||||
elseif arg == '\\CONTAMINANT_MATERIAL_TYPE' then
|
||||
command2[i] = command.contaminantMat['type']
|
||||
elseif arg == '\\CONTAMINANT_MATERIAL_INDEX' then
|
||||
command2[i] = command.contaminantMat.index
|
||||
elseif arg == '\\MODE' then
|
||||
command2[i] = command.mode
|
||||
elseif arg == '\\UNIT_ID' then
|
||||
command2[i] = command.unit.id
|
||||
elseif string.sub(arg,1,1) == '\\' then
|
||||
command2[i] = string.sub(arg,2)
|
||||
else
|
||||
command2[i] = arg
|
||||
end
|
||||
end
|
||||
dfhack.run_command(table.unpack(command2))
|
||||
end
|
||||
|
||||
function handler(table)
|
||||
local itemMat = dfhack.matinfo.decode(table.item)
|
||||
local itemMatStr = itemMat:getToken()
|
||||
local itemType = dfhack.items.getSubtypeDef(table.item:getType(),table.item:getSubtype()).id
|
||||
table.itemMat = itemMat
|
||||
table.itemType = itemType
|
||||
|
||||
for _,command in ipairs(itemTriggers[itemType] or {}) do
|
||||
if command[table.mode] then
|
||||
utils.fillTable(command,table)
|
||||
processTrigger(command)
|
||||
utils.unfillTable(command,table)
|
||||
end
|
||||
end
|
||||
|
||||
for _,command in ipairs(materialTriggers[itemMatStr] or {}) do
|
||||
if command[table.mode] then
|
||||
utils.fillTable(command,table)
|
||||
processTrigger(command)
|
||||
utils.unfillTable(command,table)
|
||||
end
|
||||
end
|
||||
|
||||
for _,contaminant in ipairs(table.item.contaminants or {}) do
|
||||
local contaminantMat = dfhack.matinfo.decode(contaminant.mat_type, contaminant.mat_index)
|
||||
local contaminantStr = contaminantMat:getToken()
|
||||
table.contaminantMat = contaminantMat
|
||||
for _,command in ipairs(contaminantTriggers[contaminantStr] or {}) do
|
||||
utils.fillTable(command,table)
|
||||
processTrigger(command)
|
||||
utils.unfillTable(command,table)
|
||||
end
|
||||
table.contaminantMat = nil
|
||||
end
|
||||
end
|
||||
|
||||
function equipHandler(unit, item, isEquip)
|
||||
local mode = (isEquip and 'onEquip') or (not isEquip and 'onUnequip')
|
||||
|
||||
local table = {}
|
||||
table.mode = mode
|
||||
table.item = df.item.find(item)
|
||||
table.unit = unit
|
||||
handler(table)
|
||||
end
|
||||
|
||||
eventful.onInventoryChange.equipmentTrigger = function(unit, item, item_old, item_new)
|
||||
if item_old and item_new then
|
||||
return
|
||||
end
|
||||
|
||||
local isEquip = item_new and not item_old
|
||||
equipHandler(unit,item,isEquip)
|
||||
end
|
||||
|
||||
eventful.onUnitAttack.attackTrigger = function(attacker,defender,wound)
|
||||
attacker = df.unit.find(attacker)
|
||||
defender = df.unit.find(defender)
|
||||
|
||||
if not attacker then
|
||||
return
|
||||
end
|
||||
|
||||
local attackerWeapon
|
||||
for _,item in ipairs(attacker.inventory) do
|
||||
if item.mode == df.unit_inventory_item.T_mode.Weapon then
|
||||
attackerWeapon = item.item
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if not attackerWeapon then
|
||||
return
|
||||
end
|
||||
|
||||
local table = {}
|
||||
table.attacker = attacker
|
||||
table.defender = defender
|
||||
table.item = attackerWeapon
|
||||
table.mode = 'onStrike'
|
||||
handler(table)
|
||||
end
|
||||
|
||||
validArgs = validArgs or utils.invert({
|
||||
'clear',
|
||||
'help',
|
||||
'checkAttackEvery',
|
||||
'checkInventoryEvery',
|
||||
'command',
|
||||
'itemType',
|
||||
'onStrike',
|
||||
'onEquip',
|
||||
'onUnequip',
|
||||
'material',
|
||||
'contaminant',
|
||||
})
|
||||
local args = utils.processArgs({...}, validArgs)
|
||||
|
||||
if args.help then
|
||||
print([[scripts/modtools/item-trigger.lua usage
|
||||
arguments:
|
||||
-help
|
||||
print this help message
|
||||
-clear
|
||||
clear all registered triggers
|
||||
-checkAttackEvery n
|
||||
check the attack event at least every n ticks
|
||||
-checkInventoryEvery n
|
||||
check inventory event at least every n ticks
|
||||
-itemType type
|
||||
trigger the command for items of this type
|
||||
examples:
|
||||
ITEM_WEAPON_PICK
|
||||
-onStrike
|
||||
trigger the command when someone strikes someone with an appropriate weapon
|
||||
-onEquip
|
||||
trigger the command when someone equips an appropriate item
|
||||
-onUnequip
|
||||
trigger the command when someone unequips an appropriate item
|
||||
-material mat
|
||||
trigger the commmand on items with the given material
|
||||
examples
|
||||
INORGANIC:IRON
|
||||
CREATURE_MAT:DWARF:BRAIN
|
||||
PLANT_MAT:MUSHROOM_HELMET_PLUMP:DRINK
|
||||
-contaminant mat
|
||||
trigger the command on items with a given material contaminant
|
||||
examples
|
||||
INORGANIC:IRON
|
||||
CREATURE_MAT:DWARF:BRAIN
|
||||
PLANT_MAT:MUSHROOM_HELMET_PLUMP:DRINK
|
||||
-command [ commandStrs ]
|
||||
specify the command to be executed
|
||||
commandStrs
|
||||
\\ATTACKER_ID
|
||||
\\DEFENDER_ID
|
||||
\\ITEM_MATERIAL
|
||||
\\ITEM_MATERIAL_TYPE
|
||||
\\ITEM_ID
|
||||
\\ITEM_TYPE
|
||||
\\CONTAMINANT_MATERIAL
|
||||
\\CONTAMINANT_MATERIAL_TYPE
|
||||
\\CONTAMINANT_MATERIAL_INDEX
|
||||
\\MODE
|
||||
\\UNIT_ID
|
||||
\\anything -> anything
|
||||
anything -> anything
|
||||
]])
|
||||
return
|
||||
end
|
||||
|
||||
if args.clear then
|
||||
itemTriggers = {}
|
||||
materialTriggers = {}
|
||||
contaminantTriggers = {}
|
||||
end
|
||||
|
||||
if args.checkAttackEvery then
|
||||
if not tonumber(args.checkAttackEvery) then
|
||||
error('checkAttackEvery must be a number')
|
||||
end
|
||||
eventful.enableEvent(eventful.eventType.UNIT_ATTACK,tonumber(args.checkAttackEvery))
|
||||
end
|
||||
|
||||
if args.checkInventoryEvery then
|
||||
if not tonumber(args.checkInventoryEvery) then
|
||||
error('checkInventoryEvery must be a number')
|
||||
end
|
||||
eventful.enableEvent(eventful.eventType.INVENTORY_CHANGE,tonumber(args.checkInventoryEvery))
|
||||
end
|
||||
|
||||
if not args.command then
|
||||
if not args.clear then
|
||||
error 'specify a command'
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
if args.itemType then
|
||||
local temp
|
||||
for _,itemdef in ipairs(df.global.world.raws.itemdefs.all) do
|
||||
if itemdef.id == args.itemType then
|
||||
temp = itemdef.subtype
|
||||
break
|
||||
end
|
||||
end
|
||||
if not temp then
|
||||
error 'Could not find item type.'
|
||||
end
|
||||
args.itemType = temp
|
||||
end
|
||||
|
||||
local numConditions = (args.material and 1 or 0) + (args.itemType and 1 or 0) + (args.contaminant and 1 or 0)
|
||||
if numConditions > 1 then
|
||||
error 'too many conditions defined: not (yet) supported (pester expwnent if you want it)'
|
||||
elseif numConditions == 0 then
|
||||
error 'specify a material, weaponType, or contaminant'
|
||||
end
|
||||
|
||||
if args.material then
|
||||
if not materialTriggers[args.material] then
|
||||
materialTriggers[args.material] = {}
|
||||
end
|
||||
table.insert(materialTriggers[args.material],args)
|
||||
elseif args.itemType then
|
||||
if not itemTriggers[args.itemType] then
|
||||
itemTriggers[args.itemType] = {}
|
||||
end
|
||||
table.insert(itemTriggers[args.itemType],args)
|
||||
elseif args.contaminant then
|
||||
if not contaminantTriggers[args.contaminant] then
|
||||
contaminantTriggers[args.contaminant] = {}
|
||||
end
|
||||
table.insert(contaminantTriggers[args.contaminant],args)
|
||||
end
|
||||
|
@ -0,0 +1,95 @@
|
||||
--scripts/modtools/moddable-gods.lua
|
||||
--based on moddableGods by Putnam
|
||||
--edited by expwnent
|
||||
|
||||
local utils = require 'utils'
|
||||
|
||||
validArgs = validArgs or utils.invert({
|
||||
'help',
|
||||
'name',
|
||||
'spheres',
|
||||
'gender',
|
||||
'depictedAs',
|
||||
'domain',
|
||||
'description',
|
||||
-- 'entities',
|
||||
})
|
||||
local args = utils.processArgs({...})
|
||||
|
||||
if args.help then
|
||||
print([[scripts/modtools/moddable-gods.lua
|
||||
arguments:
|
||||
-help
|
||||
print this help message
|
||||
-name godName
|
||||
sets the name of the god to godName
|
||||
if there's already a god of that name, the script halts
|
||||
-spheres [ sphereList ]
|
||||
define a space-separated list of spheres of influence of the god
|
||||
-depictedAs str
|
||||
often depicted as a str
|
||||
-domain str
|
||||
set the domain of the god
|
||||
-description str
|
||||
set the description of the god
|
||||
]])
|
||||
return
|
||||
end
|
||||
|
||||
if not args.name or not args.depictedAs or not args.domain or not args.description or not args.spheres or not args.gender then
|
||||
error('All arguments must be specified.')
|
||||
end
|
||||
|
||||
local templateGod
|
||||
for _,fig in ipairs(df.global.world.history.figures) do
|
||||
if fig.flags.deity then
|
||||
templateGod = fig
|
||||
break
|
||||
end
|
||||
end
|
||||
if not templateGod then
|
||||
error 'Could not find template god.'
|
||||
end
|
||||
|
||||
if args.gender == 'male' then
|
||||
args.gender = 1
|
||||
elseif args.gender == 'female' then
|
||||
args.gender = 0
|
||||
else
|
||||
error 'invalid gender'
|
||||
end
|
||||
|
||||
for _,fig in ipairs(df.global.world.history.figures) do
|
||||
if fig.name.first_name == args.name then
|
||||
print('god ' .. args.name .. ' already exists. Skipping')
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
local godFig = df.historical_figure:new()
|
||||
godFig.appeared_year = -1
|
||||
godFig.born_year = -1
|
||||
godFig.born_seconds = -1
|
||||
godFig.curse_year = -1
|
||||
godFig.curse_seconds = -1
|
||||
godFig.old_year = -1
|
||||
godFig.old_seconds = -1
|
||||
godFig.died_year = -1
|
||||
godFig.died_seconds = -1
|
||||
godFig.name.has_name = true
|
||||
godFig.breed_id = -1
|
||||
godFig.flags:assign(templateGod.flags)
|
||||
godFig.id = df.global.hist_figure_next_id
|
||||
df.global.hist_figure_next_id = 1+df.global.hist_figure_next_id
|
||||
godFig.info = df.historical_figure_info:new()
|
||||
godFig.info.spheres = {new=true}
|
||||
godFig.info.secret = df.historical_figure_info.T_secret:new()
|
||||
|
||||
godFig.sex = args.gender
|
||||
godFig.name.first_name = args.name
|
||||
for _,sphere in ipairs(args.spheres) do
|
||||
godFig.info.spheres:insert('#',df.sphere_type[sphere])
|
||||
end
|
||||
df.global.world.history.figures:insert('#',godFig)
|
||||
|
||||
|
@ -0,0 +1,134 @@
|
||||
--scripts/modtools/outsideOnly.lua
|
||||
--author expwnent
|
||||
--enables outside only and inside only buildings
|
||||
|
||||
local eventful = require 'plugins.eventful'
|
||||
local utils = require 'utils'
|
||||
|
||||
buildingType = buildingType or utils.invert({'EITHER','OUTSIDE_ONLY','INSIDE_ONLY'})
|
||||
registeredBuildings = registeredBuildings or {}
|
||||
checkEvery = checkEvery or 100
|
||||
timeoutId = timeoutId or nil
|
||||
|
||||
eventful.enableEvent(eventful.eventType.UNLOAD,1)
|
||||
eventful.onUnload.outsideOnly = function()
|
||||
registeredBuildings = {}
|
||||
checkEvery = 100
|
||||
timeoutId = nil
|
||||
end
|
||||
|
||||
local function destroy(building)
|
||||
if #building.jobs > 0 and building.jobs[0] and building.jobs[0].job_type == df.job_type.DestroyBuilding then
|
||||
return
|
||||
end
|
||||
local b = dfhack.buildings.deconstruct(building)
|
||||
if b then
|
||||
--TODO: print an error message to the user so they know
|
||||
return
|
||||
end
|
||||
-- building.flags.almost_deleted = 1
|
||||
end
|
||||
|
||||
local function checkBuildings()
|
||||
local toDestroy = {}
|
||||
local function forEach(building)
|
||||
if building:getCustomType() < 0 then
|
||||
--TODO: support builtin building types if someone wants
|
||||
return
|
||||
end
|
||||
local pos = df.coord:new()
|
||||
pos.x = building.centerx
|
||||
pos.y = building.centery
|
||||
pos.z = building.z
|
||||
local outside = dfhack.maps.getTileBlock(pos).designation[pos.x%16][pos.y%16].outside
|
||||
local def = df.global.world.raws.buildings.all[building:getCustomType()]
|
||||
local btype = registeredBuildings[def.code]
|
||||
if btype then
|
||||
-- print('outside: ' .. outside==true .. ', type: ' .. btype)
|
||||
end
|
||||
|
||||
if not btype or btype == buildingType.EITHER then
|
||||
registeredBuildings[def.code] = nil
|
||||
return
|
||||
elseif btype == buildingType.OUTSIDE_ONLY then
|
||||
if outside then
|
||||
return
|
||||
end
|
||||
else
|
||||
if not outside then
|
||||
return
|
||||
end
|
||||
end
|
||||
table.insert(toDestroy,building)
|
||||
end
|
||||
for _,building in ipairs(df.global.world.buildings.all) do
|
||||
forEach(building)
|
||||
end
|
||||
for _,building in ipairs(toDestroy) do
|
||||
destroy(building)
|
||||
end
|
||||
if timeoutId then
|
||||
dfhack.timeout_active(timeoutId,nil)
|
||||
timeoutId = nil
|
||||
end
|
||||
timeoutId = dfhack.timeout(checkEvery, 'ticks', checkBuildings)
|
||||
end
|
||||
|
||||
eventful.enableEvent(eventful.eventType.BUILDING, 100)
|
||||
eventful.onBuildingCreatedDestroyed.outsideOnly = function(buildingId)
|
||||
checkBuildings()
|
||||
end
|
||||
|
||||
validArgs = validArgs or utils.invert({
|
||||
'help',
|
||||
'clear',
|
||||
'checkEvery',
|
||||
'building',
|
||||
'type'
|
||||
})
|
||||
local args = utils.processArgs({...}, validArgs)
|
||||
if args.help then
|
||||
print([[scripts/modtools/outside-only
|
||||
arguments
|
||||
-help
|
||||
print this help message
|
||||
-clear
|
||||
clears the list of registered buildings
|
||||
-checkEvery n
|
||||
set how often existing buildings are checked for whether they are in the appropriate location to n ticks
|
||||
-type [EITHER, OUTSIDE_ONLY, INSIDE_ONLY]
|
||||
specify what sort of restriction to put on the building
|
||||
-building name
|
||||
specify the id of the building
|
||||
]])
|
||||
return
|
||||
end
|
||||
|
||||
if args.clear then
|
||||
registeredBuildings = {}
|
||||
end
|
||||
|
||||
if args.checkEvery then
|
||||
if not tonumber(args.checkEvery) then
|
||||
error('Invalid checkEvery.')
|
||||
end
|
||||
checkEvery = tonumber(args.checkEvery)
|
||||
end
|
||||
|
||||
if not args.building then
|
||||
return
|
||||
end
|
||||
|
||||
if not args['type'] then
|
||||
print 'outside-only: please specify type'
|
||||
return
|
||||
end
|
||||
|
||||
if not buildingType[args['type']] then
|
||||
error('Invalid building type: ' .. args['type'])
|
||||
end
|
||||
|
||||
registeredBuildings[args.building] = buildingType[args['type']]
|
||||
|
||||
checkBuildings()
|
||||
|
@ -0,0 +1,102 @@
|
||||
--scripts/modtools/projectile-trigger.lua
|
||||
--author expwnent
|
||||
--based on Putnam's projectileExpansion
|
||||
--TODO: trigger based on contaminants
|
||||
|
||||
local eventful = require 'plugins.eventful'
|
||||
local utils = require 'utils'
|
||||
|
||||
materialTriggers = materialTriggers or {}
|
||||
|
||||
eventful.enableEvent(eventful.eventType.UNLOAD,1)
|
||||
eventful.onUnload.projectileTrigger = function()
|
||||
materialTriggers = {}
|
||||
end
|
||||
|
||||
function processTrigger(args)
|
||||
local command2 = {}
|
||||
for _,arg in ipairs(args.command) do
|
||||
if arg == '\\LOCATION' then
|
||||
table.insert(command2,args.pos.x)
|
||||
table.insert(command2,args.pos.y)
|
||||
table.insert(command2,args.pos.z)
|
||||
elseif arg == '\\PROJECTILE_ID' then
|
||||
table.insert(command2,args.projectile.id)
|
||||
elseif arg == '\\FIRER_ID' then
|
||||
table.insert(command2,args.projectile.firer.id)
|
||||
elseif string.sub(arg,1,1) == '\\' then
|
||||
table.insert(command2,string.sub(arg,2))
|
||||
else
|
||||
table.insert(command2,arg)
|
||||
end
|
||||
end
|
||||
dfhack.run_command(table.unpack(command2))
|
||||
end
|
||||
|
||||
eventful.onProjItemCheckImpact.expansion = function(projectile)
|
||||
local matStr = dfhack.matinfo.decode(projectile.item):getToken()
|
||||
local table = {}
|
||||
table.pos = projectile.cur_pos
|
||||
table.projectile = projectile
|
||||
table.item = projectile.item
|
||||
for _,args in ipairs(materialTriggers[matStr] or {}) do
|
||||
utils.fillTable(args,table)
|
||||
processTrigger(args)
|
||||
utils.unfillTable(args,table)
|
||||
end
|
||||
end
|
||||
|
||||
validArgs = validArgs or utils.invert({
|
||||
'help',
|
||||
'clear',
|
||||
'command',
|
||||
'material',
|
||||
})
|
||||
|
||||
local args = utils.processArgs({...}, validArgs)
|
||||
|
||||
if args.help then
|
||||
print([[scripts/modtools/projectile-trigger.lua
|
||||
arguments
|
||||
-help
|
||||
print this help message
|
||||
-clear
|
||||
unregister all triggers
|
||||
-material
|
||||
specify a material for projectiles that will trigger the command
|
||||
examples:
|
||||
INORGANIC:IRON
|
||||
CREATURE_MAT:DWARF:BRAIN
|
||||
PLANT_MAT:MUSHROOM_HELMET_PLUMP:DRINK
|
||||
-command [ commandList ]
|
||||
\\LOCATION
|
||||
\\PROJECTILE_ID
|
||||
\\FIRER_ID
|
||||
\\anything -> anything
|
||||
anything -> anything
|
||||
]])
|
||||
return
|
||||
end
|
||||
|
||||
if args.clear then
|
||||
materialTriggers = {}
|
||||
end
|
||||
|
||||
if not args.command then
|
||||
return
|
||||
end
|
||||
|
||||
if not args.material then
|
||||
error 'specify a material'
|
||||
end
|
||||
|
||||
if not dfhack.matinfo.find(args.material) then
|
||||
error ('invalid material: ' .. args.material)
|
||||
end
|
||||
|
||||
if not materialTriggers[args.material] then
|
||||
materialTriggers[args.material] = {}
|
||||
end
|
||||
table.insert(materialTriggers[args.material], args)
|
||||
|
||||
|
@ -0,0 +1,171 @@
|
||||
--scripts/modtools/random-trigger.lua
|
||||
--triggers random scripts
|
||||
--register a few scripts, then tell it to "go" and it will pick a random one based on the probability weights you specified. outcomes are mutually exclusive. To make independent random events, call the script multiple times.
|
||||
|
||||
local utils = require 'utils'
|
||||
local eventful = require 'plugins.eventful'
|
||||
|
||||
outcomeLists = outcomeLists or {}
|
||||
randomGen = randomGen or dfhack.random.new()
|
||||
|
||||
eventful.enableEvent(eventful.eventType.UNLOAD, 1)
|
||||
eventful.onUnload.randomTrigger = function()
|
||||
outcomeLists = {}
|
||||
end
|
||||
|
||||
validArgs = validArgs or utils.invert({
|
||||
'help',
|
||||
'command',
|
||||
'outcomeListName',
|
||||
'weight',
|
||||
'seed',
|
||||
'trigger',
|
||||
'preserveList',
|
||||
'withProbability',
|
||||
'listOutcomes',
|
||||
'clear',
|
||||
})
|
||||
|
||||
local function triggerEvent(outcomeListName)
|
||||
local outcomeList = outcomeLists[outcomeListName]
|
||||
local r = randomGen:random(outcomeList.total)
|
||||
local sum = 0
|
||||
--print ('r = ' .. r)
|
||||
for i,outcome in ipairs(outcomeList.outcomes) do
|
||||
sum = sum + outcome.weight
|
||||
if sum > r then
|
||||
local temp = outcome.command
|
||||
--print('triggering outcome ' .. i .. ': "' .. table.concat(temp, ' ') .. '"')
|
||||
--dfhack.run_command(table.unpack(temp))
|
||||
dfhack.run_script(table.unpack(temp))
|
||||
break
|
||||
else
|
||||
--print ('sum = ' .. sum .. ' <= r = ' .. r)
|
||||
end
|
||||
end
|
||||
--print('Done.')
|
||||
--dfhack.print('\n')
|
||||
end
|
||||
|
||||
local args = utils.processArgs({...}, validArgs)
|
||||
|
||||
if args.help then
|
||||
print([[scripts/modtools/random-trigger.lua
|
||||
Allows mutually-exclusive random events. Register a list of scripts along with positive integer relative weights, then tell the script to select one of them with the specified probabilities and run it.
|
||||
The weights must be positive integers, but they do NOT have to sum to 100 or any other particular number.
|
||||
The outcomes are mutually exclusive: only one will be triggered.
|
||||
If you want multiple independent random events, call the script multiple times.
|
||||
99% of the time, you won't need to worry about this, but just in case, you can specify a name of a list of outcomes to prevent interference from other scripts that call this one.
|
||||
That also permits situations where you don't know until runtime what outcomes you want.
|
||||
For example, you could make a reaction-trigger that registers the worker as a mayor candidate, then run this script to choose a random mayor from the list of units that did the mayor reaction.
|
||||
|
||||
arguments:
|
||||
-help
|
||||
print this help message
|
||||
-outcomeListName name
|
||||
specify the name of this list of outcomes to prevent interference if two scripts are registering outcomes at the same time
|
||||
if none is specified, the default outcome list is selected automatically
|
||||
-command [ commandStrs ]
|
||||
specify the command to be run if this outcome is selected
|
||||
must be specified unless the -trigger argument is given
|
||||
-weight n
|
||||
the relative probability weight of this outcome
|
||||
n must be a non-negative integer
|
||||
if not specified, n=1 is used by default
|
||||
-trigger
|
||||
selects a random script based on the specified outcomeList (or the default one if none is specified)
|
||||
-preserveList
|
||||
when combined with trigger, preserves the list of outcomes so you don't have to register them again
|
||||
it is extremely highly recommended that you always specify the outcome list name when you give this command to prevent almost certain interference
|
||||
if you want to trigger one of 5 outcomes three times, you might want this option even without -outcomeListName
|
||||
most of the time, you won't want this
|
||||
will NOT be preserved after the user saves/loads (ask expwnent if you want this: it's not that hard but if nobody wants it I won't bother)
|
||||
performance will be slightly faster if you preserve the outcome lists when possible and trigger them multiple times instead of reregistering each time, but the effect should be small
|
||||
-withProbability p
|
||||
p is a real number between 0 and 1 inclusive
|
||||
triggers the command immediately with this probability
|
||||
-seed s
|
||||
sets the random seed (guarantees the same sequence of random numbers will be produced internally)
|
||||
use for debugging purposes
|
||||
-listOutcomes
|
||||
lists the currently registered list of outcomes of the outcomeList along with their probability weights
|
||||
use for debugging purposes
|
||||
-clear
|
||||
unregister everything
|
||||
]])
|
||||
return
|
||||
end
|
||||
|
||||
if args.clear then
|
||||
outcomeLists = {}
|
||||
end
|
||||
|
||||
if args.weight and not tonumber(args.weight) then
|
||||
error ('Invalid weight: ' .. args.weight)
|
||||
end
|
||||
args.weight = (args.weight and tonumber(args.weight)) or 1
|
||||
if args.weight ~= math.floor(args.weight) then
|
||||
error 'Noninteger weight.'
|
||||
end
|
||||
if args.weight < 0 then
|
||||
error 'invalid weight: must be non-negative'
|
||||
end
|
||||
|
||||
if args.seed then
|
||||
randomGen:init(tonumber(args.seed), 37) --37 is probably excessive and definitely arbitrary
|
||||
end
|
||||
|
||||
args.outcomeListName = args.outcomeListName or ''
|
||||
args.outcomeListName = 'outcomeList ' .. args.outcomeListName
|
||||
|
||||
if args.withProbability then
|
||||
args.withProbability = tonumber(args.withProbability)
|
||||
if not args.withProbability or args.withProbability < 0 or args.withProbability > 1 then
|
||||
error('Invalid withProbability: ' .. (args.withProbability or 'nil'))
|
||||
end
|
||||
if randomGen:drandom() < args.withProbability then
|
||||
dfhack.run_command(table.unpack(args.command))
|
||||
end
|
||||
end
|
||||
|
||||
if args.trigger then
|
||||
triggerEvent(args.outcomeListName)
|
||||
if not args.preserveList then
|
||||
outcomeLists[args.outcomeListName] = nil
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
if args.listOutcomes then
|
||||
local outcomeList = outcomeLists[args.outcomeListName]
|
||||
if not outcomeList then
|
||||
print ('No outcomes registered.')
|
||||
return
|
||||
end
|
||||
print ('Total weight: ' .. outcomeList.total)
|
||||
for _,outcome in ipairs(outcomeList.outcomes) do
|
||||
print(' outcome weight ' .. outcome.weight .. ': ' .. table.concat(outcome.command, ' '))
|
||||
end
|
||||
print('\n')
|
||||
return
|
||||
end
|
||||
|
||||
if not args.command then
|
||||
return
|
||||
end
|
||||
|
||||
--actually register
|
||||
local outcomeList = outcomeLists[args.outcomeListName]
|
||||
if not outcomeList then
|
||||
outcomeLists[args.outcomeListName] = {}
|
||||
outcomeList = outcomeLists[args.outcomeListName]
|
||||
end
|
||||
|
||||
outcomeList.total = args.weight + (outcomeList.total or 0)
|
||||
local outcome = {}
|
||||
outcome.weight = args.weight
|
||||
outcome.command = args.command
|
||||
outcomeList.outcomes = outcomeList.outcomes or {}
|
||||
table.insert(outcomeList.outcomes, outcome)
|
||||
|
||||
|
@ -0,0 +1,137 @@
|
||||
-- scripts/modtools/reaction-trigger-transition.lua
|
||||
-- author expwnent
|
||||
-- prints useful things to the console and a file to help modders transition from autoSyndrome to reaction-trigger
|
||||
-- this script is basically an apology for breaking backward compatibiility
|
||||
|
||||
local function maybeQuote(str)
|
||||
if str == '' or string.find(str,' ') then
|
||||
return ('"' .. str .. '"')
|
||||
else
|
||||
return str
|
||||
end
|
||||
end
|
||||
|
||||
warnings = ''
|
||||
output = ''
|
||||
for _,reaction in ipairs(df.global.world.raws.reactions) do
|
||||
local function foreachProduct(product)
|
||||
local prodType = product:getType()
|
||||
if prodType ~= df.reaction_product_type.item then
|
||||
return
|
||||
end
|
||||
if product.item_type ~= df.item_type.BOULDER then
|
||||
return
|
||||
end
|
||||
if product.mat_index < 0 then
|
||||
return
|
||||
end
|
||||
local inorganic = df.global.world.raws.inorganics[product.mat_index]
|
||||
local didInorganicName
|
||||
for _,syndrome in ipairs(inorganic.material.syndrome) do
|
||||
local workerOnly = true
|
||||
local allowMultipleTargets = false;
|
||||
local command
|
||||
local commandStr
|
||||
local destroyRock = true;
|
||||
local foundAutoSyndrome = false;
|
||||
local resetPolicy;
|
||||
for i,synclass in ipairs(syndrome.syn_class) do
|
||||
synclass = synclass.value
|
||||
if false then
|
||||
elseif synclass == '\\AUTO_SYNDROME' then
|
||||
foundAutoSyndrome = true
|
||||
elseif synclass == '\\ALLOW_NONWORKER_TARGETS' then
|
||||
workerOnly = false
|
||||
elseif synclass == '\\ALLOW_MULTIPLE_TARGETS' then
|
||||
allowMultipleTargets = true
|
||||
elseif synclass == '\\PRESERVE_ROCK' then
|
||||
destroyRock = false
|
||||
elseif synclass == '\\RESET_POLICY DoNothing' then
|
||||
resetPolicy = 'DoNothing'
|
||||
elseif synclass == '\\RESET_POLICY ResetDuration' then
|
||||
resetPolicy = 'ResetDuration'
|
||||
elseif synclass == '\\RESET_POLICY AddDuration' then
|
||||
resetPolicy = 'AddDuration'
|
||||
elseif synclass == '\\RESET_POLICY NewInstance' then
|
||||
resetPolicy = 'NewInstance'
|
||||
elseif synclass == '\\COMMAND' then
|
||||
command = ''
|
||||
elseif command then
|
||||
if synclass == '\\LOCATION' then
|
||||
command = command .. '\\LOCATION '
|
||||
elseif synclass == '\\WORKER_ID' then
|
||||
command = command .. '\\WORKER_ID '
|
||||
elseif synclass == '\\REACTION_INDEX' then
|
||||
warnings = warnings .. ('Warning: \\REACTION_INDEX is deprecated. Use \\REACTION_NAME instead.\n')
|
||||
command = command .. '\\REACTION_NAME '
|
||||
else
|
||||
commandStr = true
|
||||
command = command .. maybeQuote(synclass) .. ' '
|
||||
end
|
||||
end
|
||||
end
|
||||
if foundAutoSyndrome then
|
||||
if destroyRock then
|
||||
warnings = warnings .. ('Warning: instead of destroying the rock, do not produce it in the first place.\n')
|
||||
end
|
||||
if workerOnly then
|
||||
workerOnly = 'true'
|
||||
else
|
||||
workerOnly = 'false'
|
||||
end
|
||||
if allowMultipleTargets then
|
||||
allowMultipleTargets = 'true'
|
||||
else
|
||||
allowMultipleTargets = 'false'
|
||||
end
|
||||
local reactionTriggerStr = 'modtools/reaction-trigger -reactionName ' .. maybeQuote(reaction.code) --.. '"'
|
||||
if workerOnly ~= 'true' then
|
||||
reactionTriggerStr = reactionTriggerStr .. ' -workerOnly ' .. workerOnly
|
||||
end
|
||||
if allowMultipleTargets ~= 'false' then
|
||||
reactionTriggerStr = reactionTriggerStr .. ' -allowMultipleTargets ' .. allowMultipleTargets
|
||||
end
|
||||
if resetPolicy and resetPolicy ~= 'NewInstance' then
|
||||
reactionTriggerStr = reactionTriggerStr .. ' -resetPolicy ' .. resetPolicy
|
||||
end
|
||||
if #syndrome.ce > 0 then
|
||||
if syndrome.syn_name == '' then
|
||||
warnings = warnings .. ('Warning: give this syndrome a name!\n')
|
||||
end
|
||||
reactionTriggerStr = reactionTriggerStr .. ' -syndrome ' .. maybeQuote(syndrome.syn_name) .. ''
|
||||
end
|
||||
if command and commandStr then
|
||||
reactionTriggerStr = reactionTriggerStr .. ' -command ' .. command
|
||||
end
|
||||
if (not command or command == '') and (not syndrome.syn_name or syndrome.syn_name == '') then
|
||||
--output = output .. '#'
|
||||
else
|
||||
if not didInorganicName then
|
||||
-- output = output .. '# ' .. (inorganic.id) .. '\n'
|
||||
didInorganicName = true
|
||||
end
|
||||
output = output .. (reactionTriggerStr) .. '\n'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
for _,product in ipairs(reaction.products) do
|
||||
foreachProduct(product)
|
||||
end
|
||||
end
|
||||
|
||||
print(warnings)
|
||||
print('\n\n\n\n')
|
||||
print(output)
|
||||
local file = io.open('reaction-trigger-transition.txt', 'w+')
|
||||
--io.output(file)
|
||||
--file:write(warnings)
|
||||
--file:write('\n\n\n\n')
|
||||
file:write(output)
|
||||
file:flush()
|
||||
--io.flush(file)
|
||||
io.close(file)
|
||||
--io.output()
|
||||
print('transition information written to reaction-trigger-transition.txt')
|
||||
|
||||
|
@ -0,0 +1,228 @@
|
||||
-- scripts/modtools/reaction-trigger.lua
|
||||
-- author expwnent
|
||||
-- replaces autoSyndrome: trigger commands when custom reactions are completed
|
||||
|
||||
local eventful = require 'plugins.eventful'
|
||||
local syndromeUtil = require 'syndromeUtil'
|
||||
local utils = require 'utils'
|
||||
|
||||
reactionHooks = reactionHooks or {}
|
||||
|
||||
eventful.enableEvent(eventful.eventType.UNLOAD,1)
|
||||
eventful.onUnload.reactionTrigger = function()
|
||||
reactionHooks = {}
|
||||
end
|
||||
|
||||
local function getWorkerAndBuilding(job)
|
||||
local workerId = -1
|
||||
local buildingId = -1
|
||||
for _,generalRef in ipairs(job.general_refs) do
|
||||
if generalRef:getType() == df.general_ref_type.UNIT_WORKER then
|
||||
if workerId ~= -1 then
|
||||
print(job)
|
||||
printall(job)
|
||||
error('reaction-trigger: two workers on same job: ' .. workerId .. ', ' .. generalRef.unit_id)
|
||||
else
|
||||
workerId = generalRef.unit_id
|
||||
if workerId == -1 then
|
||||
print(job)
|
||||
printall(job)
|
||||
error('reaction-trigger: invalid worker')
|
||||
end
|
||||
end
|
||||
elseif generalRef:getType() == df.general_ref_type.BUILDING_HOLDER then
|
||||
if buildingId ~= -1 then
|
||||
print(job)
|
||||
printall(job)
|
||||
error('reaction-trigger: two buildings same job: ' .. buildingId .. ', ' .. generalRef.building_id)
|
||||
else
|
||||
buildingId = generalRef.building_id
|
||||
if buildingId == -1 then
|
||||
print(job)
|
||||
printall(job)
|
||||
error('reaction-trigger: invalid building')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return workerId,buildingId
|
||||
end
|
||||
|
||||
local function processCommand(job, worker, target, building, command)
|
||||
local result = {}
|
||||
for _,arg in ipairs(command) do
|
||||
if arg == '\\WORKER_ID' then
|
||||
table.insert(result,''..worker.id)
|
||||
elseif arg == '\\TARGET_ID' then
|
||||
table.insert(restul,''..target.id)
|
||||
elseif arg == '\\BUILDING_ID' then
|
||||
table.insert(result,''..building.id)
|
||||
elseif arg == '\\LOCATION' then
|
||||
table.insert(result,''..job.pos.x)
|
||||
table.insert(result,''..job.pos.y)
|
||||
table.insert(result,''..job.pos.z)
|
||||
elseif arg == '\\REACTION_NAME' then
|
||||
table.insert(result,''..job.reaction_name)
|
||||
elseif string.sub(arg,1,1) == '\\' then
|
||||
table.insert(result,string.sub(arg,2))
|
||||
else
|
||||
table.insert(result,arg)
|
||||
end
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
eventful.onJobCompleted.reactionTrigger = function(job)
|
||||
if job.completion_timer > 0 then
|
||||
return
|
||||
end
|
||||
|
||||
-- if job.job_type ~= df.job_type.CustomReaction then
|
||||
-- --TODO: support builtin reaction triggers if someone asks
|
||||
-- return
|
||||
-- end
|
||||
|
||||
if not job.reaction_name or job.reaction_name == '' then
|
||||
return
|
||||
end
|
||||
-- print('reaction name: ' .. job.reaction_name)
|
||||
if not job.reaction_name or not reactionHooks[job.reaction_name] then
|
||||
return
|
||||
end
|
||||
|
||||
local worker,building = getWorkerAndBuilding(job)
|
||||
worker = df.unit.find(worker)
|
||||
building = df.building.find(building)
|
||||
if not worker or not building then
|
||||
--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
|
||||
end
|
||||
|
||||
local function doAction(action)
|
||||
local didSomething
|
||||
if action.command then
|
||||
local processed = processCommand(job, worker, worker, building, action.command)
|
||||
dfhack.run_command(table.unpack(processed))
|
||||
end
|
||||
if action.syndrome then
|
||||
didSomething = syndromeUtil.infectWithSyndromeIfValidTarget(worker, action.syndrome, action.resetPolicy) or didSomething
|
||||
end
|
||||
if action.workerOnly then
|
||||
return
|
||||
end
|
||||
if didSomething and not action.allowMultipleTargets then
|
||||
return
|
||||
end
|
||||
local function foreach(unit)
|
||||
if unit == worker then
|
||||
return false
|
||||
elseif unit.pos.z ~= building.z then
|
||||
return false
|
||||
elseif unit.pos.x < building.x1 or unit.pos.x > building.x2 then
|
||||
return false
|
||||
elseif unit.pos.y < building.y1 or unit.pos.y > building.y2 then
|
||||
return false
|
||||
else
|
||||
if action.command then
|
||||
processCommand(job, worker, unit, building, action.command)
|
||||
end
|
||||
if action.syndrome then
|
||||
didSomething = syndrome.infectWithSyndromeIfValidTarget(unit,action.syndrome,action.resetPolicy) or didSomething
|
||||
end
|
||||
if didSomething and not action.allowMultipleTargets then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
end
|
||||
for _,unit in ipairs(df.global.world.units.all) do
|
||||
if foreach(unit) then
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
for _,action in ipairs(reactionHooks[job.reaction_name]) do
|
||||
doAction(action)
|
||||
end
|
||||
end
|
||||
eventful.enableEvent(eventful.eventType.JOB_COMPLETED,0) --0 is necessary to catch cancelled jobs and not trigger them
|
||||
|
||||
validArgs = validArgs or utils.invert({
|
||||
'help',
|
||||
'clear',
|
||||
'reactionName',
|
||||
'syndrome',
|
||||
'command',
|
||||
'allowNonworkerTargets',
|
||||
'allowMultipleTargets'
|
||||
})
|
||||
local args = utils.processArgs({...}, validArgs)
|
||||
|
||||
if args.help then
|
||||
print([[scripts/modtools/reaction-trigger.lua
|
||||
arguments:
|
||||
-help
|
||||
print this help message
|
||||
-clear
|
||||
unregister all reaction hooks
|
||||
-reactionName name
|
||||
specify the name of the reaction
|
||||
-syndrome name
|
||||
specify the name of the syndrome to be applied to the targets
|
||||
-allowNonworkerTargets
|
||||
allow other units in the same building to be targetted by either the script or the syndrome
|
||||
-allowMultipleTargets
|
||||
allow multiple targets to the script or syndrome
|
||||
if absent:
|
||||
if running a script, only one target will be used
|
||||
if applying a syndrome, then only one target will be infected
|
||||
-resetPolicy policy
|
||||
set the reset policy in the case that the syndrome is already present
|
||||
policy
|
||||
NewInstance (default)
|
||||
DoNothing
|
||||
ResetDuration
|
||||
AddDuration
|
||||
-command [ commandStrs ]
|
||||
specify the command to be run on the target(s)
|
||||
special args
|
||||
\\WORKER_ID
|
||||
\\TARGET_ID
|
||||
\\BUILDING_ID
|
||||
\\LOCATION
|
||||
\\REACTION_NAME
|
||||
\\anything -> anything
|
||||
anything -> anything
|
||||
]])
|
||||
return
|
||||
end
|
||||
|
||||
if args.clear then
|
||||
reactionHooks = {}
|
||||
end
|
||||
|
||||
if not args.reactionName then
|
||||
return
|
||||
end
|
||||
|
||||
if not reactionHooks[args.reactionName] then
|
||||
reactionHooks[args.reactionName] = {}
|
||||
end
|
||||
|
||||
if args.syndrome then
|
||||
local foundIt
|
||||
for _,syndrome in ipairs(df.global.world.raws.syndromes.all) do
|
||||
if syndrome.syn_name == args.syndrome then
|
||||
args.syndrome = syndrome
|
||||
foundIt = true
|
||||
break
|
||||
end
|
||||
end
|
||||
if not foundIt then
|
||||
error('Could not find syndrome ' .. args.syndrome)
|
||||
end
|
||||
end
|
||||
|
||||
table.insert(reactionHooks[args.reactionName], args)
|
||||
|
@ -0,0 +1,101 @@
|
||||
--scripts/modtools/skill-change.lua
|
||||
--author expwnent
|
||||
--based on skillChange.lua by Putnam
|
||||
--TODO: update skill level once experience increases/decreases
|
||||
--TODO: skill rust?
|
||||
|
||||
local utils = require 'utils'
|
||||
|
||||
validArgs = validArgs or utils.invert({
|
||||
'help',
|
||||
'skill',
|
||||
'mode',
|
||||
'value',
|
||||
'granularity',
|
||||
'unit'
|
||||
})
|
||||
|
||||
mode = mode or utils.invert({
|
||||
'add',
|
||||
'set',
|
||||
})
|
||||
|
||||
granularity = granularity or utils.invert({
|
||||
'experience',
|
||||
'level',
|
||||
})
|
||||
|
||||
local args = utils.processArgs({...}, validArgs)
|
||||
|
||||
if args.help then
|
||||
print([[scripts/modtools/skill-change.lua
|
||||
arguments
|
||||
-help
|
||||
print this help message
|
||||
-skill skillName
|
||||
set the skill that we're talking about
|
||||
-mode (add/set)
|
||||
are we adding experience/levels or setting them?
|
||||
-granularity (experience/levels)
|
||||
direct experience, or experience levels?
|
||||
-unit id
|
||||
id of the target unit
|
||||
-value amount
|
||||
how much to set/add
|
||||
]])
|
||||
return
|
||||
end
|
||||
|
||||
if not args.unit or not tonumber(args.unit) or not df.unit.find(tonumber(args.unit)) then
|
||||
error 'Invalid unit.'
|
||||
end
|
||||
args.unit = df.unit.find(tonumber(args.unit))
|
||||
|
||||
args.skill = df.job_skill[args.skill]
|
||||
args.mode = mode[args.mode or 'set']
|
||||
args.granularity = granularity[args.granularity or 'level']
|
||||
args.value = tonumber(args.value)
|
||||
|
||||
if not args.skill then
|
||||
error('invalid skill')
|
||||
end
|
||||
if not args.value then
|
||||
error('invalid value')
|
||||
end
|
||||
|
||||
local skill
|
||||
for _,skill_c in ipairs(args.unit.status.current_soul.skills) do
|
||||
if skill_c.id == args.skill then
|
||||
skill = skill_c
|
||||
end
|
||||
end
|
||||
|
||||
if not skill then
|
||||
skill = df.unit_skill:new()
|
||||
skill.id = args.skill
|
||||
args.unit.status.current_soul.skills:insert('#', skill)
|
||||
end
|
||||
|
||||
print('old: ' .. skill.rating .. ': ' .. skill.experience)
|
||||
if args.granularity == granularity.experience then
|
||||
if args.mode == mode.set then
|
||||
skill.experience = args.value
|
||||
elseif args.mode == mode.add then
|
||||
skill.experience = skill.experience + args.value
|
||||
else
|
||||
error 'bad mode'
|
||||
end
|
||||
elseif args.granularity == granularity.level then
|
||||
if args.mode == mode.set then
|
||||
skill.rating = df.skill_rating[args.value]
|
||||
elseif args.mode == mode.add then
|
||||
skill.rating = df.skill_rating[args.value + df.skill_rating[skill.rating]]
|
||||
else
|
||||
error 'bad mode'
|
||||
end
|
||||
else
|
||||
error 'bad granularity'
|
||||
end
|
||||
|
||||
print('new: ' .. skill.rating .. ': ' .. skill.experience)
|
||||
|
@ -0,0 +1,86 @@
|
||||
--scripts/modtools/spawn-flow.lua
|
||||
--author expwnent
|
||||
--spawns flows at locations
|
||||
|
||||
local utils = require 'utils'
|
||||
|
||||
validArgs = validArgs or utils.invert({
|
||||
'help',
|
||||
'material',
|
||||
'flowType',
|
||||
'location',
|
||||
'flowSize',
|
||||
})
|
||||
local args = utils.processArgs({...}, validArgs)
|
||||
|
||||
if args.help then
|
||||
print([[scripts/modtools/spawn-flow.lua
|
||||
arguments:
|
||||
-help
|
||||
print this help message
|
||||
-material mat
|
||||
specify the material of the flow, if applicable
|
||||
examples:
|
||||
INORGANIC:IRON
|
||||
CREATURE_MAT:DWARF:BRAIN
|
||||
PLANT_MAT:MUSHROOM_HELMET_PLUMP:DRINK
|
||||
-location [ x y z]
|
||||
the location to spawn the flow
|
||||
-flowType type
|
||||
specify the flow type
|
||||
examples:
|
||||
Miasma
|
||||
Steam
|
||||
Mist
|
||||
MaterialDust
|
||||
MagmaMist
|
||||
Smoke
|
||||
Dragonfire
|
||||
Fire
|
||||
Web
|
||||
MaterialGas
|
||||
MaterialVapor
|
||||
OceanWave
|
||||
SeaFoam
|
||||
-flowSize size
|
||||
specify how big the flow is
|
||||
]])
|
||||
return
|
||||
end
|
||||
|
||||
local mat_index = -1;
|
||||
local mat_type = -1;
|
||||
if args.material then
|
||||
local mat = dfhack.matinfo.find(args.material)
|
||||
if not mat then
|
||||
error ('Invalid material: ' .. mat)
|
||||
end
|
||||
mat_index = mat.index
|
||||
mat_type = mat['type']
|
||||
end
|
||||
|
||||
if args.flowSize and not tonumber(args.flowSize) then
|
||||
error ('Invalid flow size: ' .. args.flowSize)
|
||||
end
|
||||
|
||||
args.flowSize = tonumber(args.flowSize or 'z') or 100
|
||||
|
||||
if not args.flowType or not df.flow_type[args.flowType] then
|
||||
error ('Invalid flow type: ' .. (args.flowType or 'none specified'))
|
||||
end
|
||||
args.flowType = df.flow_type[args.flowType]
|
||||
|
||||
if not args.location then
|
||||
error 'Specify a location.'
|
||||
end
|
||||
|
||||
local pos = df.coord:new();
|
||||
pos.x = tonumber(args.location[1] or 'a')
|
||||
pos.y = tonumber(args.location[2] or 'a')
|
||||
pos.z = tonumber(args.location[3] or 'a')
|
||||
if not pos.x or not pos.y or not pos.z then
|
||||
error ('Invalid pos.')
|
||||
end
|
||||
|
||||
dfhack.maps.spawnFlow(pos, args.flowType, mat_type, mat_index, args.flowSize)
|
||||
|
@ -0,0 +1,117 @@
|
||||
--scripts/modtools/syndrome-trigger.lua
|
||||
--author expwnent
|
||||
--triggers scripts when units are infected with syndromes
|
||||
|
||||
local eventful = require 'plugins.eventful'
|
||||
local utils = require 'utils'
|
||||
|
||||
onInfection = onInfection or {}
|
||||
|
||||
eventful.enableEvent(eventful.eventType.UNLOAD,1)
|
||||
eventful.onUnload.syndromeTrigger = function()
|
||||
onInfection = {}
|
||||
end
|
||||
|
||||
eventful.enableEvent(eventful.eventType.SYNDROME,5) --requires iterating through every unit, so not cheap, but not slow either
|
||||
|
||||
local function processTrigger(args)
|
||||
local command = {}
|
||||
for i,arg in ipairs(args.command) do
|
||||
if arg == '\\SYNDROME_ID' then
|
||||
table.insert(command, '' .. args.syndrome.id)
|
||||
elseif arg == '\\UNIT_ID' then
|
||||
table.insert(command, '' .. args.unit.id)
|
||||
elseif arg == '\\LOCATION' then
|
||||
table.insert(command, '' .. args.unit.pos.x)
|
||||
table.insert(command, '' .. args.unit.pos.y)
|
||||
table.insert(command, '' .. args.unit.pos.z)
|
||||
elseif string.sub(arg,1,1) == '\\' then
|
||||
table.insert(command, string.sub(arg,2))
|
||||
else
|
||||
table.insert(command, arg)
|
||||
end
|
||||
end
|
||||
dfhack.run_command(table.unpack(command))
|
||||
end
|
||||
|
||||
eventful.onSyndrome.syndromeTrigger = function(unitId, syndromeIndex)
|
||||
local unit = df.unit.find(unitId)
|
||||
local unit_syndrome = unit.syndromes.active[syndromeIndex]
|
||||
local syn_id = unit_syndrome['type']
|
||||
if not onInfection[syn_id] then
|
||||
return
|
||||
end
|
||||
local syndrome = df.syndrome.find(syn_id)
|
||||
local table = {}
|
||||
table.unit = unit
|
||||
table.unit_syndrome = unit_syndrome
|
||||
table.syndrome = syndrome
|
||||
for _,args in ipairs(onInfection[syn_id] or {}) do
|
||||
utils.fillTable(args,table)
|
||||
processTrigger(args)
|
||||
utils.unfillTable(args,table)
|
||||
end
|
||||
end
|
||||
|
||||
------------------------------
|
||||
--argument processing
|
||||
|
||||
validArgs = validArgs or utils.invert({
|
||||
'clear',
|
||||
'help',
|
||||
'command',
|
||||
'syndrome'
|
||||
})
|
||||
|
||||
local args = utils.processArgs({...}, validArgs)
|
||||
|
||||
if args.help then
|
||||
print([[scripts/modtools/syndrome-trigger.lua
|
||||
arguments
|
||||
-help
|
||||
print this help message
|
||||
-clear
|
||||
clear all triggers
|
||||
-syndrome name
|
||||
specify the name of a syndrome
|
||||
-command [ commandStrs ]
|
||||
specify the command to be executed after infection
|
||||
args
|
||||
\\SYNDROME_ID
|
||||
\\UNIT_ID
|
||||
\\LOCATION
|
||||
\anything -> anything
|
||||
anything -> anything
|
||||
]])
|
||||
return
|
||||
end
|
||||
|
||||
if args.clear then
|
||||
onInfection = {}
|
||||
end
|
||||
|
||||
if not args.command then
|
||||
return
|
||||
end
|
||||
|
||||
if not args.syndrome then
|
||||
error 'Select a syndrome.'
|
||||
end
|
||||
|
||||
local syndrome
|
||||
for _,syn in ipairs(df.global.world.raws.syndromes.all) do
|
||||
if syn.syn_name == args.syndrome then
|
||||
if syndrome then
|
||||
error ('Multiple syndromes with same name: ' .. syn.syn_name)
|
||||
end
|
||||
syndrome = syn.id
|
||||
end
|
||||
end
|
||||
|
||||
if not syndrome then
|
||||
error ('Could not find syndrome named ' .. syndrome)
|
||||
end
|
||||
|
||||
onInfection[syndrome] = onInfection[syndrome] or {}
|
||||
table.insert(onInfection[syndrome], args)
|
||||
|
@ -0,0 +1,158 @@
|
||||
--scripts/modtools/transform-unit.lua
|
||||
--author expwnent
|
||||
--based on shapechange by Putnam
|
||||
--warning: will crash arena mode if you view the unit on the same tick that it transforms
|
||||
--if you wait until later, it will be fine
|
||||
|
||||
local utils = require 'utils'
|
||||
|
||||
normalRace = normalRace or {}
|
||||
|
||||
local function transform(unit,race,caste)
|
||||
unit.enemy.normal_race = race
|
||||
unit.enemy.normal_caste = caste
|
||||
unit.enemy.were_race = race
|
||||
unit.enemy.were_caste = caste
|
||||
end
|
||||
|
||||
validArgs = validArgs or utils.invert({
|
||||
'clear',
|
||||
'help',
|
||||
'unit',
|
||||
'duration',
|
||||
'setPrevRace',
|
||||
'keepInventory',
|
||||
'race',
|
||||
'caste',
|
||||
'suppressAnnouncement',
|
||||
'untransform',
|
||||
})
|
||||
|
||||
local args = utils.processArgs({...}, validArgs)
|
||||
|
||||
if args.help then
|
||||
print([[scripts/modtools/transform-unit.lua
|
||||
arguments
|
||||
-help
|
||||
print this help message
|
||||
-clear
|
||||
clear records of normal races
|
||||
-unit id
|
||||
set the target unit
|
||||
-duration ticks
|
||||
how long it should last, or "forever"
|
||||
-setPrevRace
|
||||
make a record of the previous race so that you can change it back with -untransform
|
||||
-keepInventory
|
||||
move items back into inventory after transformation
|
||||
-race raceName
|
||||
-caste casteName
|
||||
-suppressAnnouncement
|
||||
don't show the Unit has transformed into a Blah! event
|
||||
-untransform
|
||||
turn the unit back into what it was before
|
||||
]])
|
||||
return
|
||||
end
|
||||
|
||||
if args.clear then
|
||||
normalRace = {}
|
||||
end
|
||||
|
||||
if not args.unit then
|
||||
error 'Specify a unit.'
|
||||
end
|
||||
|
||||
if not args.duration then
|
||||
args.duration = 'forever'
|
||||
end
|
||||
|
||||
local raceIndex
|
||||
local race
|
||||
local caste
|
||||
if args.untransform then
|
||||
local unit = df.unit.find(tonumber(args.unit))
|
||||
raceIndex = normalRace[args.unit].race
|
||||
race = df.creature_raw.find(raceIndex)
|
||||
caste = normalRace[args.unit].caste
|
||||
normalRace[args.unit] = nil
|
||||
else
|
||||
if not args.race or not args.caste then
|
||||
error 'Specficy a target form.'
|
||||
end
|
||||
|
||||
--find race
|
||||
for i,v in ipairs(df.global.world.raws.creatures.all) do
|
||||
if v.creature_id == args.race then
|
||||
raceIndex = i
|
||||
race = v
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if not race then
|
||||
error 'Invalid race.'
|
||||
end
|
||||
|
||||
for i,v in ipairs(race.caste) do
|
||||
if v.caste_id == args.caste then
|
||||
caste = i
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if not caste then
|
||||
error 'Invalid caste.'
|
||||
end
|
||||
end
|
||||
|
||||
local unit = df.unit.find(tonumber(args.unit))
|
||||
local oldRace = unit.enemy.normal_race
|
||||
local oldCaste = unit.enemy.normal_caste
|
||||
if args.setPrevRace then
|
||||
normalRace[args.unit] = {}
|
||||
normalRace[args.unit].race = oldRace
|
||||
normalRace[args.unit].caste = oldCaste
|
||||
end
|
||||
transform(unit,raceIndex,caste,args.setPrevRace)
|
||||
|
||||
local inventoryItems = {}
|
||||
|
||||
local function getInventory()
|
||||
local result = {}
|
||||
for _,item in ipairs(unit.inventory) do
|
||||
table.insert(result, item:new());
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
local function restoreInventory()
|
||||
dfhack.timeout(1, 'ticks', function()
|
||||
for _,item in ipairs(inventoryItems) do
|
||||
dfhack.items.moveToInventory(item.item, unit, item.mode, item.body_part_id)
|
||||
item:delete()
|
||||
end
|
||||
inventoryItems = {}
|
||||
end)
|
||||
end
|
||||
|
||||
if args.keepInventory then
|
||||
inventoryItems = getInventory()
|
||||
end
|
||||
|
||||
if args.keepInventory then
|
||||
restoreInventory()
|
||||
end
|
||||
if args.duration and args.duration ~= 'forever' then
|
||||
--when the timeout ticks down, transform them back
|
||||
dfhack.timeout(tonumber(args.duration), 'ticks', function()
|
||||
if args.keepInventory then
|
||||
inventoryItems = getInventory()
|
||||
end
|
||||
transform(unit,oldRace,oldCaste)
|
||||
if args.keepInventory then
|
||||
restoreInventory()
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
@ -0,0 +1,57 @@
|
||||
-- scripts/remove-wear.lua
|
||||
-- Resets all items in your fort to 0 wear
|
||||
-- original author: Laggy
|
||||
-- edited by expwnent
|
||||
|
||||
local args = {...}
|
||||
|
||||
if args[1] == 'help' then
|
||||
print([[remove-wear - this script removes wear from all items, or from individual ones
|
||||
|
||||
remove-wear all
|
||||
remove wear from all items
|
||||
remove-wear n1 n2 n3 ...
|
||||
remove wear from items with the given ids. order does not matter
|
||||
repeat -time 2 months -command remove-wear all
|
||||
remove wear from all items every 2 months. see repeat.lua for details
|
||||
]])
|
||||
do return end
|
||||
elseif args[1] == 'all' then
|
||||
local count = 0;
|
||||
for _,item in ipairs(df.global.world.items.all) do
|
||||
if (item.wear > 0) then
|
||||
item:setWear(0)
|
||||
count = count+1
|
||||
end
|
||||
end
|
||||
print('remove-wear removed wear from '..count..' objects')
|
||||
else
|
||||
local argIndex = 1
|
||||
local isCompleted = {}
|
||||
for i,x in ipairs(args) do
|
||||
args[i] = tonumber(x)
|
||||
end
|
||||
table.sort(args)
|
||||
for _,item in ipairs(df.global.world.items.all) do
|
||||
local function loop()
|
||||
if argIndex > #args then
|
||||
return
|
||||
elseif item.id > args[argIndex] then
|
||||
argIndex = argIndex+1
|
||||
loop()
|
||||
return
|
||||
elseif item.id == args[argIndex] then
|
||||
--print('removing wear from item with id ' .. args[argIndex])
|
||||
item:setWear(0)
|
||||
isCompleted[args[argIndex]] = true
|
||||
argIndex = argIndex+1
|
||||
end
|
||||
end
|
||||
loop()
|
||||
end
|
||||
for _,arg in ipairs(args) do
|
||||
if isCompleted[arg] ~= true then
|
||||
print('failed to remove wear from item ' .. arg .. ': could not find item with that id')
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,61 @@
|
||||
-- scripts/repeat.lua
|
||||
-- repeatedly calls a lua script, eg "repeat -time 1 months -command cleanowned"; to disable "repeat -cancel cleanowned"
|
||||
-- repeat -help for details
|
||||
-- author expwnent
|
||||
-- vaguely based on a script by Putnam
|
||||
|
||||
local repeatUtil = require 'repeat-util'
|
||||
local utils = require 'utils'
|
||||
|
||||
validArgs = validArgs or utils.invert({
|
||||
'help',
|
||||
'cancel',
|
||||
'name',
|
||||
'time',
|
||||
'timeUnits',
|
||||
'command'
|
||||
})
|
||||
|
||||
local args = utils.processArgs({...}, validArgs)
|
||||
|
||||
if args.help then
|
||||
print([[repeat.lua
|
||||
repeat -help
|
||||
print this help message
|
||||
repeat -cancel bob
|
||||
cancels the repetition with the name bob
|
||||
repeat -name jim -time delay -timeUnits units -printResult true -command printArgs 3 1 2
|
||||
-name sets the name for the purposes of cancelling and making sure you don't schedule the same repeating event twice
|
||||
if not specified, it's set to the first argument after -command
|
||||
-time delay -timeUnits units
|
||||
delay is some positive integer
|
||||
units is some valid time unit for dfhack.timeout(delay,timeUnits,function)
|
||||
-command ...
|
||||
specify the command to be run
|
||||
]])
|
||||
return
|
||||
end
|
||||
|
||||
if args.cancel then
|
||||
repeatUtil.cancel(args.cancel)
|
||||
if args.name then
|
||||
repeatUtil.cancel(args.name)
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
args.time = tonumber(args.time)
|
||||
if not args.name then
|
||||
args.name = args.command[1]
|
||||
end
|
||||
|
||||
if not args.timeUnits then
|
||||
args.timeUnits = 'ticks'
|
||||
end
|
||||
|
||||
local callCommand = function()
|
||||
dfhack.run_command(table.unpack(args.command))
|
||||
end
|
||||
|
||||
repeatUtil.scheduleEvery(args.name,args.time,args.timeUnits,callCommand)
|
||||
|
@ -0,0 +1,36 @@
|
||||
-- teleport.lua
|
||||
-- teleports a unit to a location
|
||||
-- author Putnam
|
||||
-- edited by expwnent
|
||||
|
||||
local function teleport(unit,pos)
|
||||
local unitoccupancy = dfhack.maps.getTileBlock(unit.pos).occupancy[unit.pos.x%16][unit.pos.y%16]
|
||||
unit.pos.x = pos.x
|
||||
unit.pos.y = pos.y
|
||||
unit.pos.z = pos.z
|
||||
if not unit.flags1.on_ground then unitoccupancy.unit = false else unitoccupancy.unit_grounded = false end
|
||||
end
|
||||
|
||||
local function getArgsTogether(args)
|
||||
local settings={pos={}}
|
||||
for k,v in ipairs(args) do
|
||||
v=string.lower(v)
|
||||
if v=="unit" then settings.unitID=tonumber(args[k+1]) end
|
||||
if v=="x" then settings.pos['x']=tonumber(args[k+1]) end
|
||||
if v=="y" then settings.pos['y']=tonumber(args[k+1]) end
|
||||
if v=="z" then settings.pos['z']=tonumber(args[k+1]) end
|
||||
if v=="showunitid" then print(dfhack.gui.getSelectedUnit(true).id) end
|
||||
if v=="showpos" then printall(df.global.cursor) end
|
||||
end
|
||||
if not settings.pos.x or not settings.pos.y or not settings.pos.z then settings.pos=nil end
|
||||
if not settings.unitID and not settings.pos.x then qerror("Needs a position, a unit ID or both, but not neither!") end
|
||||
return settings
|
||||
end
|
||||
|
||||
local args = {...}
|
||||
local teleportSettings=getArgsTogether(args)
|
||||
local unit = teleportSettings.unitID and df.unit.find(teleportSettings.unitID) or dfhack.gui.getSelectedUnit(true)
|
||||
local pos = teleportSettings.pos and teleportSettings.pos or df.global.cursor
|
||||
|
||||
teleport(unit,pos)
|
||||
|
Loading…
Reference in New Issue