diff --git a/NEWS b/NEWS index a491c5235..2316f8a80 100644 --- a/NEWS +++ b/NEWS @@ -1,11 +1,12 @@ DFHack future Internals: - LuaApi - support queuing dfhack.timeout events before a world is loaded. + EventManager: added ON_REPORT and UNIT_ATTACK events and exposed them to Lua. New scripts: add-syndrome.lua add a syndrome to a unit. many configurable options + attackTrigger.lua + run commands when a unit attacks another with weapons of a given type or weapons of a given material blooddel.lua makes it so civs don't bring barrels full of blood ichor or goo feeding-timers.lua @@ -24,12 +25,6 @@ DFHack future adds and removes syndromes to units when they equip and unequip items moddableGods.lua makes raw moddable gods possible - onReport.lua - callbacks when a report happens - onReportExample.lua - onStrike.lua - callbacks when a unit injures another - onStrikeExample.lua printArgs.lua projectileExpansion.lua adds extra functionality to projectiles diff --git a/dfhack.init-example b/dfhack.init-example index f23aad233..2a5b18751 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -208,6 +208,12 @@ log-region # patch the material objects in memory to fix cloth stockpiles fix/cloth-stockpile enable +# civs don't bring blood +#:lua dfhack.onStateChange.onLoadBloodDel = function(state) if state == SC_WORLD_LOADED then dfhack.run_command('repeat -time 1 months -command fix/blooddel') end end + +# run growth bug regularly +#:lua dfhack.onStateChange.onLoadGrowthBug = function(state) if state == SC_WORLD_LOADED then dfhack.run_command('repeat -time 1 months -command fix/growthbug') end end + ####################################################### # Apply binary patches at runtime # ####################################################### diff --git a/library/LuaTools.cpp b/library/LuaTools.cpp index b70a33b2a..392659bc8 100644 --- a/library/LuaTools.cpp +++ b/library/LuaTools.cpp @@ -1694,6 +1694,12 @@ int dfhack_timeout(lua_State *L) luaL_checktype(L, 3, LUA_TFUNCTION); lua_settop(L, 3); + if (mode > 0 && !Core::getInstance().isWorldLoaded()) + { + lua_pushnil(L); + return 1; + } + // Compute timeout value switch (mode) { diff --git a/library/include/modules/EventManager.h b/library/include/modules/EventManager.h index f73a94253..f95a75e8d 100644 --- a/library/include/modules/EventManager.h +++ b/library/include/modules/EventManager.h @@ -9,8 +9,10 @@ #include "Console.h" #include "DataDefs.h" -#include -#include +#include "df/coord.h" +#include "df/unit.h" +#include "df/unit_inventory_item.h" +#include "df/unit_wound.h" namespace DFHack { namespace EventManager { @@ -26,6 +28,8 @@ namespace DFHack { SYNDROME, INVASION, INVENTORY_CHANGE, + REPORT, + UNIT_ATTACK, EVENT_MAX }; } @@ -69,6 +73,12 @@ namespace DFHack { InventoryChangeData(int32_t id_in, InventoryItem* old_in, InventoryItem* new_in): unitId(id_in), item_old(old_in), item_new(new_in) {} }; + struct UnitAttackData { + int32_t attacker; + int32_t defender; + int32_t wound; + }; + DFHACK_EXPORT void registerListener(EventType::EventType e, EventHandler handler, Plugin* plugin); DFHACK_EXPORT int32_t registerTick(EventHandler handler, int32_t when, Plugin* plugin, bool absolute=false); DFHACK_EXPORT void unregister(EventType::EventType e, EventHandler handler, Plugin* plugin); diff --git a/library/lua/onReport.lua b/library/lua/onReport.lua deleted file mode 100644 index edef7574b..000000000 --- a/library/lua/onReport.lua +++ /dev/null @@ -1,74 +0,0 @@ ---onReport.lua ---author expwnent ---contains the "ON_REPORT" event: triggered when there is a new report in df.global.world.status.reports - ---example ---local onReport = require 'onReport' ---onReport.triggers.someName = function (reportId) --- --do stuff with that id ---end - -local _ENV = mkmodule('onReport') -local utils = require 'utils' -local repeatUtil = require 'repeatUtil' - -lastReport = lastReport or -1 -triggers = triggers or {} - -monitorFrequency = monitorFrequency or nil -eventToDwarf = eventToDwarf or {} - -function updateEventToDwarf(reportId) - if not eventToDwarf[reportId] then - eventToDwarf[reportId] = {} - end - for _,unit in ipairs(df.global.world.units.all) do - for _,reportType in ipairs(unit.reports.log) do - for _,report in ipairs(reportType) do - if report == reportId then - eventToDwarf[reportId][unit.id] = true - end - end - end - end -end - -function monitor() - local reports = df.global.world.status.reports - if df.global.world.status.next_report_id-1 <= lastReport then - return - end --- if #reports == 0 or reports[#reports-1].id <= lastReport then --- return --- end - _,_,start = utils.binsearch(reports,lastReport,"id") - while start < #reports and reports[start].id <= lastReport do - start = start+1 - end - for i=start,#reports-1,1 do - updateEventToDwarf(reports[i].id) - for _,callBack in pairs(triggers) do - callBack(reports[i].id) - end - lastReport = reports[i].id - end -end - -monitorEvery = function(n) - if n <= 0 then - print('cannot monitor onReport every '..n..' ticks.') - return - end - if monitorFrequency and monitorFrequency < n then - print('NOT decreasing frequency of onReport monitoring from every '..monitorFrequency..' ticks to every '..n..' ticks') - return - end - print('monitor onReport every '..n..' ticks') - monitorFrequency = n - repeatUtil.scheduleEvery('onReportMonitoring', n, 'ticks', monitor) -end - -monitorEvery(1) - -return _ENV - diff --git a/library/lua/onStrike.lua b/library/lua/onStrike.lua deleted file mode 100644 index 58cdfe7cb..000000000 --- a/library/lua/onStrike.lua +++ /dev/null @@ -1,255 +0,0 @@ - -local _ENV = mkmodule('onStrike') - -local onReport = require 'onReport' -local utils = require 'utils' - -debug = debug or true - -triggers = triggers or {} - -function getReportString(reportId) - local report = df.report.find(reportId) - local result = report.text - local i = 1 - local report2 = df.report.find(reportId+i) - while report2 and report2.flags.continuation do - result = result .. ' ' .. report2.text - i = i+1 - report2 = df.report.find(reportId+i) - end - return result -end - -onReport.triggers.onStrike = function(reportId) - local report = df.report.find(reportId) - if report["type"] ~= df.announcement_type.COMBAT_STRIKE_DETAILS then - return - end - if report.flags.continuation then - return - end --- print('\n') - local fighters = {} - for unitId,_ in pairs(onReport.eventToDwarf[reportId]) do - table.insert(fighters,unitId) - end - local reportString = getReportString(reportId) - if #fighters ~= 2 then - if debug then - local ok = string.find(reportString,' skids along ') - if not ok then - print('onStrike: #fighters = ' .. #fighters) - print(reportString) - df.global.pause_state = true - end - end - return - end - local info = {} - local count = 0 - fighters[1] = df.unit.find(fighters[1]) - fighters[2] = df.unit.find(fighters[2]) - local function getWound(fighterA,fighterB) - local wound - for i=#fighterB.body.wounds-1,0,-1 do - local w = fighterB.body.wounds[i] - if w.unit_id == fighterA.id and w.age <= 1 then - wound = w - break - end - end - --[[name,_ = tryParse(reportString,getNames(fighterA)) - if not name then - wound = nil - end - --]] - return wound - end - local wound1 = getWound(fighters[1],fighters[2]) - local wound2 = getWound(fighters[2],fighters[1]) - local flying = string.find(reportString,'The flying') - local name1 - local name2 - if flying then - name1 = findAny(reportString,getNames(fighters[2])) - name2 = findAny(reportString,getNames(fighters[1])) - else - name1 = tryParse(reportString,getNames(fighters[1])) - name2 = tryParse(reportString,getNames(fighters[2])) - end - if name1 and wound1 and name2 and wound2 then - if debug then - print('ambiguous wounds: ' .. reportString) - print('fighter1 = ' .. fighters[1].id) - print('fighter2 = ' .. fighters[2].id) - df.global.pause_state = true - end - return - elseif not wound1 and not wound2 then - local ok = fighters[1].flags1.dead or fighters[2].flags1.dead or string.find(reportString,' grabs ') or string.find(reportString,'snatches at') or string.find(reportString,'glances away!') or string.find(reportString, ' shakes ') - if not ok and debug then - print('neither wound works: ' .. reportString) - print('fighter1 = ' .. fighters[1].id) - print('fighter2 = ' .. fighters[2].id) - df.global.pause_state = true - end - return - elseif not name1 and not name2 and not string.find(reportString,'The flying ') then - if debug then - print('WTF?') - print('fighter1 = ' .. fighters[1].id) - print('fighter2 = ' .. fighters[2].id) - df.global.pause_state = true - end - return - elseif name1 and wound1 then - else - local temp = fighters[1] - fighters[1] = fighters[2] - fighters[2] = temp - end - local wound = wound1 or wound2 - - --is it a weapon attack? - local isWeaponAttack - if getWeapon(fighters[1]) and string.find(reportString,getWeaponString(fighters[1])) then - isWeaponAttack = true - else - isWeaponAttack = false - end - isWeaponAttack = isWeaponAttack or flying - local weapon - if isWeaponAttack then - weapon = getWeapon(fighters[1]) - end --- print('triggers') - for _,trigger in pairs(triggers) do --- print('trigger') - trigger(fighters[1],fighters[2],weapon,wound) - end -end - -function myConcat(table1,table2) - local result = {} - for _,v in pairs(table1) do - table.insert(result,v) - end - for _,v in pairs(table2) do - table.insert(result,v) - end - return result -end - -function getUnitAttackStrings(unit) - local result = {} - for _,attack in ipairs(unit.body.body_plan.attacks) do - table.insert(result,attack.verb_3rd..' ') - end - return result -end - -function getUnitAttack(unit,parsedAttack) - for _,attack in ipairs(unit.body.body_plan.attacks) do - if attack.verb_3rd..' ' == parsedAttack then - return result - end - end - return nil -end - -function getWeapon(unit) - function dumb(item) --- print('\n') --- print(item) --- printall(item) - if item.mode ~= df.unit_inventory_item.T_mode.Weapon then - --print('item.mode ' .. item.mode .. ' /= Weapon ' .. df.unit_inventory_item.T_mode.Weapon) - return false - end - if item.item._type ~= df.item_weaponst then - --print('item.item._type ' .. item.item._type .. ' /= df.item_weaponst ' .. df.item_weaponst) - return false - end - return true - end - for _,item in ipairs(unit.inventory) do - if dumb(item) then - return item.item - end - end - return nil -end - -function getWeaponAttackStrings(unit) - local result = {} - local weapon = getWeapon(unit) - if not weapon then - print('no weapon') - return result - end - for _,attack in ipairs(weapon.subtype.attacks) do - table.insert(result,attack.verb_3rd..' ') - end - return result -end - -function getWeaponAttack(unit,parsedAttack) - local weapon = getWeapon(unit) - if not weapon then - return nil - end - for _,attack in ipairs(weapon.subtype.attacks) do - if attack.verb_3rd..' ' == parsedAttack then - return attack - end - end - return nil -end - -function getNames(unit) - local result = {} - table.insert(result,unit.name.first_name .. ' ') - table.insert(result,'The '..dfhack.units.getProfessionName(unit)..' ') - table.insert(result,'the '..dfhack.units.getProfessionName(unit)..' ') - table.insert(result,'The Stray '..dfhack.units.getProfessionName(unit)..' ') - table.insert(result,'the stray '..dfhack.units.getProfessionName(unit)..' ') - return result -end - -function getWeaponString(unit,suffix) - local weapon = getWeapon(unit) - if not weapon then - return '' - end - local material = getMaterialString(weapon) - return material .. ' ' .. weapon.subtype.name .. (suffix or '') -end - -function getMaterialString(item) - local material = dfhack.matinfo.decode(item.mat_type,item.mat_index) - return material.material.state_name[df.matter_state.Solid] -end - -function findAny(parseString,strs) - for _,str in ipairs(strs) do - if string.find(parseString,str) then - return str - end - end - return nil -end - -function tryParse(parseString,strs) - for _,str in ipairs(strs) do - if string.sub(parseString,1,#str) == str then - --print('\n"' .. str .. '" matches "' .. parseString .. '"\n') - return str,string.sub(parseString,#str+1,#parseString) - end - --print('\n"' .. str .. '" doesn\'t match "' .. parseString .. '"\n') - end - return nil,nil -end - -return _ENV - diff --git a/library/modules/EventManager.cpp b/library/modules/EventManager.cpp index 88f4d56d6..8c2670e17 100644 --- a/library/modules/EventManager.cpp +++ b/library/modules/EventManager.cpp @@ -1,5 +1,6 @@ #include "Core.h" #include "Console.h" +#include "VTableInterpose.h" #include "modules/Buildings.h" #include "modules/Constructions.h" #include "modules/EventManager.h" @@ -7,6 +8,7 @@ #include "modules/Job.h" #include "modules/World.h" +#include "df/announcement_type.h" #include "df/building.h" #include "df/construction.h" #include "df/general_ref.h" @@ -14,13 +16,20 @@ #include "df/general_ref_unit_workerst.h" #include "df/global_objects.h" #include "df/item.h" +#include "df/item_actual.h" +#include "df/item_constructed.h" +#include "df/item_crafted.h" +#include "df/item_weaponst.h" #include "df/job.h" #include "df/job_list_link.h" +#include "df/report.h" #include "df/ui.h" #include "df/unit.h" #include "df/unit_flags1.h" #include "df/unit_inventory_item.h" +#include "df/unit_report_type.h" #include "df/unit_syndrome.h" +#include "df/unit_wound.h" #include "df/world.h" #include @@ -116,6 +125,8 @@ static void manageConstructionEvent(color_ostream& out); static void manageSyndromeEvent(color_ostream& out); static void manageInvasionEvent(color_ostream& out); static void manageEquipmentEvent(color_ostream& out); +static void manageReportEvent(color_ostream& out); +static void manageUnitAttackEvent(color_ostream& out); typedef void (*eventManager_t)(color_ostream&); @@ -130,6 +141,8 @@ static const eventManager_t eventManager[] = { manageSyndromeEvent, manageInvasionEvent, manageEquipmentEvent, + manageReportEvent, + manageUnitAttackEvent, }; //job initiated @@ -162,6 +175,12 @@ static int32_t nextInvasion; //static unordered_map > equipmentLog; static unordered_map > equipmentLog; +//report +static int32_t lastReport; + +//unit attack +static int32_t lastReportUnitAttack; + void DFHack::EventManager::onStateChange(color_ostream& out, state_change_event event) { static bool doOnce = false; // const string eventNames[] = {"world loaded", "world unloaded", "map loaded", "map unloaded", "viewscreen changed", "core initialized", "begin unload", "paused", "unpaused"}; @@ -186,6 +205,8 @@ void DFHack::EventManager::onStateChange(color_ostream& out, state_change_event equipmentLog.clear(); Buildings::clearBuildings(out); + lastReport = -1; + lastReportUnitAttack = -1; gameLoaded = false; } else if ( event == DFHack::SC_MAP_LOADED ) { /* @@ -235,6 +256,8 @@ void DFHack::EventManager::onStateChange(color_ostream& out, state_change_event lastSyndromeTime = startTime; } } + lastReport = -1; + lastReportUnitAttack = -1; for ( size_t a = 0; a < EventType::EVENT_MAX; a++ ) { eventLastTick[a] = -1;//-1000000; } @@ -694,3 +717,140 @@ static void manageEquipmentEvent(color_ostream& out) { } } +static void manageReportEvent(color_ostream& out) { + multimap copy(handlers[EventType::REPORT].begin(), handlers[EventType::REPORT].end()); + std::vector& reports = df::global::world->status.reports; + if (reports.size() == 0) { + return; + } + size_t a = df::report::binsearch_index(reports, lastReport, false); + //this may or may not be needed: I don't know if binsearch_index goes earlier or later if it can't hit the target exactly + while (a < reports.size() && reports[a]->id <= lastReport) { + a++; + } + for ( ; a < reports.size(); a++ ) { + df::report* report = reports[a]; + for ( auto b = copy.begin(); b != copy.end(); b++ ) { + EventHandler handle = (*b).second; + handle.eventHandler(out, (void*)report->id); + } + lastReport = report->id; + } +} + +static df::unit_wound* getWound(df::unit* attacker, df::unit* defender) { + for ( size_t a = 0; a < defender->body.wounds.size(); a++ ) { + df::unit_wound* wound = defender->body.wounds[a]; + if ( wound->age <= 1 && wound->unit_id == attacker->id ) { + return wound; + } + } + return NULL; +} + +static void manageUnitAttackEvent(color_ostream& out) { + multimap copy(handlers[EventType::UNIT_ATTACK].begin(), handlers[EventType::UNIT_ATTACK].end()); + std::vector& reports = df::global::world->status.reports; + if (reports.size() == 0) { + return; + } + size_t a = df::report::binsearch_index(reports, lastReportUnitAttack, false); + //this may or may not be needed: I don't know if binsearch_index goes earlier or later if it can't hit the target exactly + while (a < reports.size() && reports[a]->id <= lastReportUnitAttack) { + a++; + } + std::set strikeReports; + for ( ; a < reports.size(); a++ ) { + df::report* report = reports[a]; + lastReportUnitAttack = report->id; + if ( report->flags.bits.continuation ) + continue; + df::announcement_type type = report->type; + if ( type == df::announcement_type::COMBAT_STRIKE_DETAILS ) { + strikeReports.insert(report->id); + } + } + + if ( strikeReports.empty() ) + return; + + //report id -> relevant units + std::map> reportToRelevantUnits; + for ( size_t a = 0; a < df::global::world->units.all.size(); a++ ) { + df::unit* unit = df::global::world->units.all[a]; + for ( int16_t b = df::enum_traits::first_item_value; b <= df::enum_traits::last_item_value; b++ ) { + if ( b == df::unit_report_type::Sparring ) + continue; + for ( size_t c = 0; c < unit->reports.log[b].size(); c++ ) { + reportToRelevantUnits[unit->reports.log[b][c]].push_back(unit->id); + } + } + } + + map > alreadyDone; + for ( auto a = strikeReports.begin(); a != strikeReports.end(); a++ ) { + int32_t reportId = *a; + df::report* report = df::report::find(reportId); + if ( !report ) + continue; //TODO: error + std::string reportStr = report->text; + for ( int32_t b = reportId+1; ; b++ ) { + df::report* report2 = df::report::find(b); + if ( !report2 ) + break; + if ( report2->type != df::announcement_type::COMBAT_STRIKE_DETAILS ) + break; + if ( !report2->flags.bits.continuation ) + break; + reportStr = reportStr + report2->text; + } + + std::vector& relevantUnits = reportToRelevantUnits[report->id]; + if ( relevantUnits.size() != 2 ) { + continue; + } + + df::unit* unit1 = df::unit::find(relevantUnits[0]); + df::unit* unit2 = df::unit::find(relevantUnits[1]); + + df::unit_wound* wound1 = getWound(unit1,unit2); + df::unit_wound* wound2 = getWound(unit2,unit1); + + if ( wound1 && !alreadyDone[unit1->id][unit2->id] ) { + UnitAttackData data; + data.attacker = unit1->id; + data.defender = unit2->id; + data.wound = wound1->id; + + alreadyDone[data.attacker][data.defender] = 1; + for ( auto b = copy.begin(); b != copy.end(); b++ ) { + EventHandler handle = (*b).second; + handle.eventHandler(out, (void*)&data); + } + } + + if ( wound2 && !alreadyDone[unit1->id][unit2->id] ) { + UnitAttackData data; + data.attacker = unit2->id; + data.defender = unit1->id; + data.wound = wound2->id; + + alreadyDone[data.attacker][data.defender] = 1; + for ( auto b = copy.begin(); b != copy.end(); b++ ) { + EventHandler handle = (*b).second; + handle.eventHandler(out, (void*)&data); + } + } + + if ( !wound1 && !wound2 ) { + if ( unit1->flags1.bits.dead || unit2->flags1.bits.dead ) + continue; + if ( reportStr.find("severed part") ) + continue; + if ( Once::doOnce("EventManager neither wound") ) { + out.print("%s, %d: neither wound: %s\n", __FILE__, __LINE__, reportStr.c_str()); + } + } + } +} + diff --git a/plugins/devel/eventExample.cpp b/plugins/devel/eventExample.cpp index 80570fc27..5a827f18e 100644 --- a/plugins/devel/eventExample.cpp +++ b/plugins/devel/eventExample.cpp @@ -3,16 +3,24 @@ #include "Core.h" #include "Export.h" #include "PluginManager.h" -#include "modules/EventManager.h" #include "DataDefs.h" +#include "VTableInterpose.h" + +#include "modules/EventManager.h" +#include "df/body_part_raw.h" +#include "df/caste_body_info.h" #include "df/construction.h" #include "df/coord.h" #include "df/item.h" +#include "df/item_actual.h" #include "df/job.h" +#include "df/unit.h" +#include "df/unit_wound.h" #include "df/world.h" #include +#include using namespace DFHack; using namespace std; @@ -28,6 +36,9 @@ void building(color_ostream& out, void* ptr); void construction(color_ostream& out, void* ptr); void syndrome(color_ostream& out, void* ptr); void invasion(color_ostream& out, void* ptr); +void unitAttack(color_ostream& out, void* ptr); + +//bool interposed = false; command_result eventExample(color_ostream& out, vector& parameters); @@ -36,7 +47,30 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vectorid, unit->id, wound->unit_id, (int32_t)unk1, (int32_t)unk2); + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE(my_contaminate, contaminateWound); +*/ + command_result eventExample(color_ostream& out, vector& parameters) { +/* + if ( !interposed ) { + interposed = true; + if ( !INTERPOSE_HOOK(my_contaminate, contaminateWound).apply() ) { + out.print("Error: could not interpose contaminateWound."); + } + } +*/ + EventManager::EventHandler initiateHandler(jobInitiated, 1); EventManager::EventHandler completeHandler(jobCompleted, 0); EventManager::EventHandler timeHandler(timePassed, 1); @@ -46,8 +80,9 @@ command_result eventExample(color_ostream& out, vector& parameters) { EventManager::EventHandler constructionHandler(construction, 100); EventManager::EventHandler syndromeHandler(syndrome, 1); EventManager::EventHandler invasionHandler(invasion, 1000); + EventManager::EventHandler unitAttackHandler(unitAttack, 1); EventManager::unregisterAll(plugin_self); - + EventManager::registerListener(EventManager::EventType::JOB_INITIATED, initiateHandler, plugin_self); EventManager::registerListener(EventManager::EventType::JOB_COMPLETED, completeHandler, plugin_self); EventManager::registerListener(EventManager::EventType::UNIT_DEATH, deathHandler, plugin_self); @@ -56,6 +91,7 @@ command_result eventExample(color_ostream& out, vector& parameters) { EventManager::registerListener(EventManager::EventType::CONSTRUCTION, constructionHandler, plugin_self); EventManager::registerListener(EventManager::EventType::SYNDROME, syndromeHandler, plugin_self); EventManager::registerListener(EventManager::EventType::INVASION, invasionHandler, plugin_self); + EventManager::registerListener(EventManager::EventType::UNIT_ATTACK, unitAttackHandler, plugin_self); EventManager::registerTick(timeHandler, 1, plugin_self); EventManager::registerTick(timeHandler, 2, plugin_self); EventManager::registerTick(timeHandler, 4, plugin_self); @@ -135,3 +171,20 @@ void invasion(color_ostream& out, void* ptr) { out.print("New invasion! %d\n", (int32_t)ptr); } +void unitAttack(color_ostream& out, void* ptr) { + EventManager::UnitAttackData* data = (EventManager::UnitAttackData*)ptr; + out.print("unit %d attacks unit %d\n", data->attacker, data->defender); + df::unit* defender = df::unit::find(data->defender); + int32_t woundIndex = df::unit_wound::binsearch_index(defender->body.wounds, data->wound); + df::unit_wound* wound = defender->body.wounds[woundIndex]; + set parts; + for ( auto a = wound->parts.begin(); a != wound->parts.end(); a++ ) { + parts.insert((*a)->body_part_id); + } + for ( auto a = parts.begin(); a != parts.end(); a++ ) { + int32_t body_part_id = (*a); + df::body_part_raw* part = defender->body.body_plan->body_parts[body_part_id]; + out.print(" %s\n", part->name_singular[0]->c_str()); + } +} + diff --git a/plugins/eventful.cpp b/plugins/eventful.cpp index 4bfed38d9..5dbed2a5d 100644 --- a/plugins/eventful.cpp +++ b/plugins/eventful.cpp @@ -1,36 +1,32 @@ #include "Core.h" #include "Error.h" -#include -#include -#include -#include -#include - -#include +#include "Console.h" +#include "Export.h" +#include "LuaTools.h" +#include "MiscUtils.h" +#include "PluginManager.h" +#include "VTableInterpose.h" +#include "df/building.h" #include "df/building_workshopst.h" - -#include "df/unit.h" -#include "df/unit_inventory_item.h" +#include "df/construction.h" #include "df/item.h" #include "df/item_actual.h" -#include "df/unit_wound.h" -#include "df/world.h" +#include "df/job.h" +#include "df/proj_itemst.h" +#include "df/proj_unitst.h" #include "df/reaction.h" #include "df/reaction_reagent_itemst.h" #include "df/reaction_product_itemst.h" - -#include "df/proj_itemst.h" -#include "df/proj_unitst.h" - -#include "MiscUtils.h" -#include "LuaTools.h" +#include "df/unit.h" +#include "df/unit_inventory_item.h" +#include "df/unit_wound.h" +#include "df/world.h" #include "modules/EventManager.h" -#include "df/job.h" -#include "df/building.h" -#include "df/construction.h" +#include +#include using std::vector; using std::string; @@ -124,6 +120,8 @@ static void handle_job_complete(color_ostream &out,df::job*){}; static void handle_constructions(color_ostream &out,df::construction*){}; static void handle_syndrome(color_ostream &out,int32_t,int32_t){}; static void handle_inventory_change(color_ostream& out,int32_t,int32_t,df::unit_inventory_item*,df::unit_inventory_item*){}; +static void handle_report(color_ostream& out,int32_t){}; +static void handle_unitAttack(color_ostream& out,int32_t,int32_t,int32_t){}; DEFINE_LUA_EVENT_1(onBuildingCreatedDestroyed, handle_int32t, int32_t); DEFINE_LUA_EVENT_1(onJobInitiated,handle_job_init,df::job*); DEFINE_LUA_EVENT_1(onJobCompleted,handle_job_complete,df::job*); @@ -133,6 +131,8 @@ DEFINE_LUA_EVENT_1(onConstructionCreatedDestroyed, handle_constructions, df::con DEFINE_LUA_EVENT_2(onSyndrome, handle_syndrome, int32_t,int32_t); DEFINE_LUA_EVENT_1(onInvasion,handle_int32t,int32_t); DEFINE_LUA_EVENT_4(onInventoryChange,handle_inventory_change,int32_t,int32_t,df::unit_inventory_item*,df::unit_inventory_item*); +DEFINE_LUA_EVENT_1(onReport,handle_report,int32_t); +DEFINE_LUA_EVENT_3(onUnitAttack,handle_unitAttack,int32_t,int32_t,int32_t) DFHACK_PLUGIN_LUA_EVENTS { DFHACK_LUA_EVENT(onWorkshopFillSidebarMenu), DFHACK_LUA_EVENT(postWorkshopFillSidebarMenu), @@ -152,6 +152,8 @@ DFHACK_PLUGIN_LUA_EVENTS { DFHACK_LUA_EVENT(onSyndrome), DFHACK_LUA_EVENT(onInvasion), DFHACK_LUA_EVENT(onInventoryChange), + DFHACK_LUA_EVENT(onReport), + DFHACK_LUA_EVENT(onUnitAttack), DFHACK_LUA_END }; @@ -212,6 +214,13 @@ static void ev_mng_inventory(color_ostream& out, void* ptr) } onInventoryChange(out,unitId,itemId,item_old,item_new); } +static void ev_mng_report(color_ostream& out, void* ptr) { + onReport(out,(int32_t)ptr); +} +static void ev_mng_unitAttack(color_ostream& out, void* ptr) { + EventManager::UnitAttackData* data = (EventManager::UnitAttackData*)ptr; + onUnitAttack(out,data->attacker,data->defender,data->wound); +} std::vector enabledEventManagerEvents(EventManager::EventType::EVENT_MAX,-1); typedef void (*handler_t) (color_ostream&,void*); static const handler_t eventHandlers[] = { @@ -225,6 +234,8 @@ static const handler_t eventHandlers[] = { ev_mng_syndrome, ev_mng_invasion, ev_mng_inventory, + ev_mng_report, + ev_mng_unitAttack, }; static void enableEvent(int evType,int freq) { diff --git a/plugins/lua/eventful.lua b/plugins/lua/eventful.lua index 2aa713583..3c3865ce8 100644 --- a/plugins/lua/eventful.lua +++ b/plugins/lua/eventful.lua @@ -133,5 +133,5 @@ local function invertTable(tbl) return ret end eventType=invertTable{[0]="TICK","JOB_INITIATED","JOB_COMPLETED","UNIT_DEATH","ITEM_CREATED", - "BUILDING","CONSTRUCTION","SYNDROME","INVASION","INVENTORY_CHANGE","EVENT_MAX"} + "BUILDING","CONSTRUCTION","SYNDROME","INVASION","INVENTORY_CHANGE","REPORT","UNIT_ATTACK","EVENT_MAX"} return _ENV diff --git a/plugins/syndromeTrigger.cpp b/plugins/syndromeTrigger.cpp index ee75efc36..475d1d56a 100644 --- a/plugins/syndromeTrigger.cpp +++ b/plugins/syndromeTrigger.cpp @@ -20,8 +20,7 @@ using namespace DFHack; using namespace std; -static bool enabled = false; - +DFHACK_PLUGIN_IS_ENABLED(enabled); DFHACK_PLUGIN("syndromeTrigger"); void syndromeHandler(color_ostream& out, void* ptr); @@ -43,34 +42,42 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector & parameters) { if ( parameters.size() > 1 ) return CR_WRONG_USAGE; - bool wasEnabled = enabled; + bool enable; if ( parameters.size() == 1 ) { if ( parameters[0] == "enable" ) { - enabled = true; + enable = true; } else if ( parameters[0] == "disable" ) { - enabled = false; + enable = false; } else { int32_t a = atoi(parameters[0].c_str()); if ( a < 0 || a > 1 ) return CR_WRONG_USAGE; - enabled = (bool)a; + enable = (bool)a; } } - + + plugin_enable(out, enable); 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; } diff --git a/scripts/attackTrigger.lua b/scripts/attackTrigger.lua new file mode 100644 index 000000000..1ba717c19 --- /dev/null +++ b/scripts/attackTrigger.lua @@ -0,0 +1,141 @@ +--attackTrigger.lua +--author expwnent +--triggers scripts when a unit attacks another with a weapon type, a weapon of a particular material + +local eventful = require 'plugins.eventful' +eventful.enableEvent(eventful.eventType.UNIT_ATTACK,1) + +itemTriggers = itemTriggers or {} +materialTriggers = materialTriggers or {} +itemMaterialTriggers = itemMaterialTriggers or {} + +local function processTrigger(command, attacker, defender) + local command2 = {} + for i,arg in ipairs(command) do + if arg == '\\ATTACKER' then + command2[i] = '' .. attacker.id + elseif arg == '\\DEFENDER' then + command2[i] = '' .. defender.id + else + command2[i] = arg + end + end + print(dfhack.run_command(table.unpack(command2))) +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 weaponType = attackerWeapon.subtype.subtype --attackerWeapon.subtype.id + + if itemTriggers[weaponType] then + for _,command in pairs(itemTriggers[weaponType]) do + processTrigger(command,attacker,defender) + end + end + +-- if materialTriggers[attackerWeapon.mat_type] and materialTriggers[attackerWeapon.mat_type][attackerWeapon.mat_index] then + if materialTriggers[attackerWeapon.mat_index] then + for _,command in pairs(materialTriggers[attackerWeapon.mat_index]) do + processTrigger(command,attacker,defender) + end + end + +-- if itemMaterialTriggers[weaponType] and itemMaterialTriggers[weaponType][attackerWeapon.mat_type] and itemMaterialTriggers[weaponType][attackerWeapon.mat_type][attackerWeapon.mat_index] then +-- for _,command in pairs(itemMaterialTriggers[weaponType][attackerWeapon.mat_type][attackerWeapon.mat_index]) do +-- processTrigger(command,attacker,defender) +-- end +-- end +end + +local args = {...} + +local i = 1 +local command +local weaponType +local material +while i <= #args do + if command then + table.insert(command, args[i]) + i = i+1 + else + if args[i] == '-weaponType' then + weaponType = args[i+1] + i = i+2 + elseif args[i] == '-material' then + material = args[i+1] + i = i+2 + elseif args[i] == '-command' then + command = {} + i = i+1 + elseif args[i] == '-clear' then + itemTriggers = {} + materialTriggers = {} + i = i+1 + else + error('Invalid arguments to attackTrigger.lua.') + end + end +end + +if weaponType then + local temp + for _,itemdef in ipairs(df.global.world.raws.itemdefs.weapons) do + if itemdef.id == weaponType then + temp = itemdef.subtype + break + end + end + if not temp then + error 'Could not find weapon type.' + end + weaponType = temp +end + +if material then + local i = 0 + while true do + local mat = dfhack.matinfo.decode(0,i) + if not mat then + error 'Could not find material.' + end + if mat.inorganic.id == material then + material = mat.index + break + end + i = i+1 + end +end + +if material then + if not materialTriggers[material] then + materialTriggers[material] = {} + end + table.insert(materialTriggers[material],command) +-- table.insert(materialTriggers[material],function() +-- print(dfhack.run_command(command)) +-- end) +elseif weaponType then + if not itemTriggers[weaponType] then + itemTriggers[weaponType] = {} + end + table.insert(itemTriggers[weaponType],command) +end + diff --git a/scripts/devel/onReportExample.lua b/scripts/devel/onReportExample.lua deleted file mode 100644 index 4459865fe..000000000 --- a/scripts/devel/onReportExample.lua +++ /dev/null @@ -1,18 +0,0 @@ - -local onReport = require 'onReport' - -onReport.triggers.onReportExample = function(reportId) --- print('report '..reportId..' happened!') - local report = df.report.find(reportId) - if not report then - return - end --- printall(report) --- print('\n') - print(reportId .. ': ' .. df.announcement_type[report["type"]]) - for unitId,_ in pairs(onReport.eventToDwarf[reportId]) do - print('relevant dwarf: ' .. unitId) - end -end - - diff --git a/scripts/devel/onStrikeExample.lua b/scripts/devel/onStrikeExample.lua deleted file mode 100644 index 4bc85e182..000000000 --- a/scripts/devel/onStrikeExample.lua +++ /dev/null @@ -1,16 +0,0 @@ - -local onStrike = require 'onStrike' -local eventful = require 'plugins.eventful' ---print(onStrike) - ---onStrike.triggers.onStrikeExample = function(information) --- print(information.attacker .. ' attacks ' .. information.defender .. ': ' .. information.announcement) -onStrike.triggers.onStrikeExample = function(attacker, defender, weapon, wound) - if weapon then - print(attacker.id..' weapon attacks '..defender.id .. ' with ' .. weapon.id) - --df.global.pause_state = true - else - print(attacker.id..' attacks '..defender.id) - end -end -