Updated interaction-trigger to work better.

develop
expwnent 2014-11-09 18:36:21 -05:00
parent 3f82230685
commit 779ac3fd50
4 changed files with 239 additions and 104 deletions

@ -1,4 +1,7 @@
DFHack Future DFHack Future
Internals:
EventManager should handle INTERACTION triggers a little better. It still can get confused about who did what but only rarely.
devel/all-bob.lua: renames everyone Bob to help test interaction-trigger
DFHack 0.40.15-r1 DFHack 0.40.15-r1
Fixes: Fixes:

@ -84,10 +84,10 @@ namespace DFHack {
struct InteractionData { struct InteractionData {
std::string attackVerb; std::string attackVerb;
std::string defendVerb; std::string defendVerb;
int32_t attackReport;
int32_t defendReport;
int32_t attacker; int32_t attacker;
int32_t defender; int32_t defender;
int32_t attackReport;
int32_t defendReport;
}; };
DFHACK_EXPORT void registerListener(EventType::EventType e, EventHandler handler, Plugin* plugin); DFHACK_EXPORT void registerListener(EventType::EventType e, EventHandler handler, Plugin* plugin);

@ -16,6 +16,7 @@
#include "df/general_ref_type.h" #include "df/general_ref_type.h"
#include "df/general_ref_unit_workerst.h" #include "df/general_ref_unit_workerst.h"
#include "df/global_objects.h" #include "df/global_objects.h"
#include "df/interaction.h"
#include "df/item.h" #include "df/item.h"
#include "df/item_actual.h" #include "df/item_actual.h"
#include "df/item_constructed.h" #include "df/item_constructed.h"
@ -956,20 +957,161 @@ static void manageUnitAttackEvent(color_ostream& out) {
static std::string getVerb(df::unit* unit, std::string reportStr) { static std::string getVerb(df::unit* unit, std::string reportStr) {
std::string result(reportStr); std::string result(reportStr);
std::string name = unit->name.first_name + " "; std::string name = unit->name.first_name + " ";
bool useName = strncmp(result.c_str(), name.c_str(), name.length()) == 0; bool match = strncmp(result.c_str(), name.c_str(), name.length()) == 0;
if ( useName ) { if ( match ) {
result = result.substr(name.length()); result = result.substr(name.length());
result = result.substr(0,result.length()-1); result = result.substr(0,result.length()-1);
return result; return result;
} }
//use profession name //use profession name
std::string profession = "The " + Units::getProfessionName(unit) + " "; name = "The " + Units::getProfessionName(unit) + " ";
bool match = strncmp(result.c_str(), profession.c_str(), profession.length()) == 0; match = strncmp(result.c_str(), name.c_str(), name.length()) == 0;
if ( !match ) if ( match ) {
result = result.substr(name.length());
result = result.substr(0,result.length()-1);
return result;
}
if ( unit->id != 0 ) {
return ""; return "";
result = result.substr(profession.length()); }
std::string you = "You ";
match = strncmp(result.c_str(), name.c_str(), name.length()) == 0;
if ( match ) {
result = result.substr(name.length());
result = result.substr(0,result.length()-1); result = result.substr(0,result.length()-1);
//TODO: special case for the player in adventure mode return result;
}
return "";
}
static InteractionData getAttacker(color_ostream& out, df::report* attackEvent, df::unit* lastAttacker, df::report* defendEvent, vector<df::unit*>& relevantUnits) {
vector<df::unit*> attackers = relevantUnits;
vector<df::unit*> defenders = relevantUnits;
//find valid interactions: TODO
/*map<int32_t,vector<df::interaction*> > validInteractions;
for ( size_t a = 0; a < relevantUnits.size(); a++ ) {
df::unit* unit = relevantUnits[a];
vector<df::interaction*>& interactions = validInteractions[unit->id];
for ( size_t b = 0; b < unit->body.
}*/
//if attackEvent
// attacker must be same location
// attacker name must start attack str
// attack verb must match valid interaction of this attacker
std::string attackVerb;
if ( attackEvent ) {
for ( size_t a = 0; a < attackers.size(); a++ ) {
if ( attackers[a]->pos != attackEvent->pos ) {
attackers.erase(attackers.begin()+a);
a--;
continue;
}
if ( lastAttacker && attackers[a] != lastAttacker ) {
attackers.erase(attackers.begin()+a);
a--;
continue;
}
std::string verbC = getVerb(attackers[a], attackEvent->text);
if ( verbC.length() == 0 ) {
attackers.erase(attackers.begin()+a);
a--;
continue;
}
attackVerb = verbC;
}
}
//if defendEvent
// defender must be same location
// defender name must start defend str
// defend verb must match valid interaction of some attacker
std::string defendVerb;
if ( defendEvent ) {
for ( size_t a = 0; a < defenders.size(); a++ ) {
if ( defenders[a]->pos != defendEvent->pos ) {
defenders.erase(defenders.begin()+a);
a--;
continue;
}
std::string verbC = getVerb(defenders[a], defendEvent->text);
if ( verbC.length() == 0 ) {
defenders.erase(defenders.begin()+a);
a--;
continue;
}
defendVerb = verbC;
}
}
//keep in mind one attacker zero defenders is perfectly valid for self-cast
if ( attackers.size() == 1 && defenders.size() == 1 && attackers[0] == defenders[0] ) {
} else {
if ( defenders.size() == 1 ) {
auto a = std::find(attackers.begin(),attackers.end(),defenders[0]);
if ( a != attackers.end() )
attackers.erase(a);
}
if ( attackers.size() == 1 ) {
auto a = std::find(defenders.begin(),defenders.end(),attackers[0]);
if ( a != defenders.end() )
defenders.erase(a);
}
}
//if trying attack-defend pair and it fails to find attacker, try defend only
InteractionData result = /*(InteractionData)*/ { std::string(), std::string(), -1, -1, -1, -1 };
if ( attackers.size() > 1 ) {
if ( Once::doOnce("EventManager interaction ambiguous attacker") ) {
out.print("%s,%d: ambiguous attacker on report\n \'%s\'\n '%s'\n", __FILE__, __LINE__, attackEvent ? attackEvent->text.c_str() : "", defendEvent ? defendEvent->text.c_str() : "");
}
} else if ( attackers.size() < 1 ) {
if ( defendEvent )
return getAttacker(out, NULL, NULL, defendEvent, relevantUnits);
} else {
//attackers.size() == 1
result.attacker = attackers[0]->id;
if ( defenders.size() > 0 )
result.defender = defenders[0]->id;
if ( defenders.size() > 1 ) {
if ( Once::doOnce("EventManager interaction ambiguous defender") ) {
out.print("%s,%d: ambiguous defender: shouldn't happen. On report\n \'%s\'\n '%s'\n", __FILE__, __LINE__, attackEvent ? attackEvent->text.c_str() : "", defendEvent ? defendEvent->text.c_str() : "");
}
}
result.attackVerb = attackVerb;
result.defendVerb = defendVerb;
if ( attackEvent )
result.attackReport = attackEvent->id;
if ( defendEvent )
result.defendReport = defendEvent->id;
}
return result;
}
static vector<df::unit*> gatherRelevantUnits(color_ostream& out, df::report* r1, df::report* r2) {
vector<df::report*> reports;
if ( r1 == r2 ) r2 = NULL;
if ( r1 ) reports.push_back(r1);
if ( r2 ) reports.push_back(r2);
vector<df::unit*> result;
unordered_set<int32_t> ids;
for ( size_t a = 0; a < reports.size(); a++ ) {
vector<int32_t>& units = reportToRelevantUnits[reports[a]->id];
if ( units.size() > 2 ) {
if ( Once::doOnce("EventManager interaction too many relevant units") ) {
out.print("%s,%d: too many relevant units. On report\n \'%s\'\n", __FILE__, __LINE__, reports[a]->text.c_str());
}
}
for ( size_t b = 0; b < units.size(); b++ )
if ( ids.find(units[b]) == ids.end() ) {
ids.insert(units[b]);
result.push_back(df::unit::find(units[b]));
}
}
return result; return result;
} }
@ -985,82 +1127,61 @@ static void manageInteractionEvent(color_ostream& out) {
if ( a < reports.size() ) if ( a < reports.size() )
updateReportToRelevantUnits(); updateReportToRelevantUnits();
int32_t attackerId = -1; df::report* lastAttackEvent = NULL;
int32_t lastTime = -1; df::unit* lastAttacker = NULL;
std::string attackVerb; df::unit* lastDefender = NULL;
int32_t attackReport = -1; unordered_map<int32_t,unordered_set<int32_t> > history;
for ( ; a < reports.size(); a++ ) { for ( ; a < reports.size(); a++ ) {
df::report* report = reports[a]; df::report* report = reports[a];
lastReportInteraction = report->id; lastReportInteraction = report->id;
if ( report->flags.bits.continuation )
continue;
df::announcement_type type = report->type; df::announcement_type type = report->type;
if ( type != df::announcement_type::INTERACTION_ACTOR && type != df::announcement_type::INTERACTION_TARGET ) if ( type != df::announcement_type::INTERACTION_ACTOR && type != df::announcement_type::INTERACTION_TARGET )
continue; continue;
int32_t unitId = -1; if ( report->flags.bits.continuation )
int32_t validCount = 0;
std::string verb;
//find relevant unit
for ( auto b = reportToRelevantUnits[report->id].begin(); b != reportToRelevantUnits[report->id].end(); b++ ) {
int32_t candidateId = *b;
df::unit* candidate = df::unit::find(candidateId);
if ( !candidate ) {
//TODO: error
continue;
}
if ( candidate->pos != report->pos )
continue;
std::string verbC = getVerb(candidate, report->text);
if ( verbC.length() == 0 )
continue;
verb = verbC;
validCount++;
unitId = candidateId;
}
if ( validCount > 1 ) {
if ( Once::doOnce("EventManager interaction too many actors") ) {
out.print("%s:%d: too many actors for report %d\n", __FILE__, __LINE__, report->id);
out.print("reportStr = \"%s\", pos = %d,%d,%d\n", report->text.c_str(), report->pos.x, report->pos.y, report->pos.z);
}
attackerId = -1;
continue; continue;
} bool attack = type == df::announcement_type::INTERACTION_ACTOR;
if ( validCount == 0 ) { if ( attack ) {
if ( Once::doOnce("EventManager interaction too few actors") ) { lastAttackEvent = report;
out.print("%s:%d: too few actors for report %d\n", __FILE__, __LINE__, report->id); lastAttacker = NULL;
out.print("reportStr = \"%s\", pos = %d,%d,%d\n", report->text.c_str(), report->pos.x, report->pos.y, report->pos.z); lastDefender = NULL;
} }
attackerId = -1; vector<df::unit*> relevantUnits = gatherRelevantUnits(out, lastAttackEvent, report);
InteractionData data = getAttacker(out, lastAttackEvent, lastAttacker, attack ? NULL : report, relevantUnits);
if ( data.attacker < 0 )
continue; continue;
//if ( !attack && lastAttacker && data.attacker == lastAttacker->id && lastDefender && data.defender == lastDefender->id )
// continue; //lazy way of preventing duplicates
if ( attack && a+1 < reports.size() && reports[a+1]->type == df::announcement_type::INTERACTION_TARGET ) {
InteractionData data2 = getAttacker(out, lastAttackEvent, lastAttacker, reports[a+1], gatherRelevantUnits(out, lastAttackEvent, reports[a+1]));
if ( data.attacker == data2.attacker && (data.defender == -1 || data.defender == data2.defender) ) {
data = data2;
a++;
} }
//int32_t unitId = reportToRelevantUnits[report->id][0];
bool isActor = attackerId == -1;//type == df::announcement_type::INTERACTION_ACTOR;
if ( isActor ) {
attackReport = report->id;
attackerId = unitId;
lastTime = report->year*ticksPerYear + report->time;
attackVerb = verb;
continue;
} }
{
if ( attackerId == -1 ) #define HISTORY_ITEM 1
#if HISTORY_ITEM
unordered_set<int32_t>& b = history[data.attacker];
if ( b.find(data.defender) != b.end() )
continue; continue;
if ( report->year*ticksPerYear + report->time != lastTime ) { history[data.attacker].insert(data.defender);
attackerId = -1; //b.insert(data.defender);
#else
unordered_set<int32_t>& b = history[data.attackReport];
if ( b.find(data.defendReport) != b.end() )
continue; continue;
history[data.attackReport].insert(data.defendReport);
//b.insert(data.defendReport);
#endif
} }
InteractionData data; lastAttacker = df::unit::find(data.attacker);
data.attacker = attackerId; lastDefender = df::unit::find(data.defender);
data.defender = unitId; //fire event
data.attackReport = attackReport;
data.defendReport = report->id;
data.attackVerb = attackVerb;
data.defendVerb = verb;
for ( auto b = copy.begin(); b != copy.end(); b++ ) { for ( auto b = copy.begin(); b != copy.end(); b++ ) {
EventHandler handle = (*b).second; EventHandler handle = (*b).second;
handle.eventHandler(out, (void*)&data); handle.eventHandler(out, (void*)&data);
} }
//TODO: deduce attacker from latest defend event first
} }
} }

@ -5,15 +5,19 @@
local eventful = require 'plugins.eventful' local eventful = require 'plugins.eventful'
local utils = require 'utils' local utils = require 'utils'
attackStrTriggers = attackStrTriggers or {} attackTriggers = attackTriggers or {}
defendStrTriggers = defendStrTriggers or {} defendTriggers = defendTriggers or {}
commands = commands or {}
commandCount = commandCount or 0
eventful.enableEvent(eventful.eventType.INTERACTION,1) --cheap, so every tick is fine eventful.enableEvent(eventful.eventType.INTERACTION,1) --cheap, so every tick is fine
eventful.enableEvent(eventful.eventType.UNLOAD,1) eventful.enableEvent(eventful.eventType.UNLOAD,1)
eventful.onUnload.interactionTrigger = function() eventful.onUnload.interactionTrigger = function()
attackStrTriggers = {} attackTriggers = {}
defendStrTriggers = {} defendTriggers = {}
commands = {}
commandCount = 0
end end
local function processTrigger(args) local function processTrigger(args)
@ -50,19 +54,20 @@ eventful.onInteraction.interactionTrigger = function(attackVerb, defendVerb, att
extras.defenderId = defender extras.defenderId = defender
local suppressAttack = false local suppressAttack = false
local suppressDefend = false local suppressDefend = false
for _,trigger in ipairs(attackStrTriggers[attackVerb] or {}) do local todo = {}
suppressAttack = suppressAttack or trigger.suppressAttack for _,trigger in ipairs(attackTriggers[attackVerb] or {}) do
suppressDefend = suppressDefend or trigger.suppressDefend todo[trigger] = true
utils.fillTable(trigger,extras)
processTrigger(trigger)
utils.unfillTable(trigger,extras)
end end
for _,trigger in ipairs(defendStrTriggers[defendVerb] or {}) do for _,trigger in ipairs(defendTriggers[defendVerb] or {}) do
suppressAttack = suppressAttack or trigger.suppressAttack todo[trigger] = true
suppressDefend = suppressDefend or trigger.suppressDefend end
utils.fillTable(trigger,extras) for k,v in pairs(todo) do
processTrigger(trigger) command = commands[k]
utils.unfillTable(trigger,extras) suppressAttack = suppressAttack or command.suppressAttack
suppressDefend = suppressDefend or command.suppressDefend
utils.fillTable(command,extras)
processTrigger(command)
utils.unfillTable(command,extras)
end end
local eraseReport = function(unit,report) local eraseReport = function(unit,report)
@ -85,6 +90,7 @@ eventful.onInteraction.interactionTrigger = function(attackVerb, defendVerb, att
eraseReport(attacker,defendReport) eraseReport(attacker,defendReport)
eraseReport(defender,defendReport) eraseReport(defender,defendReport)
end end
--TODO: get rid of combat report on LHS of screen
end end
---------------------------------------------------- ----------------------------------------------------
@ -110,9 +116,9 @@ arguments:
-clear -clear
unregisters all triggers unregisters all triggers
-onAttackStr str -onAttackStr str
trigger the command when the attack verb is "str" trigger the command when the attack verb is "str". both onAttackStr and onDefendStr MUST be specified
-onDefendStr str -onDefendStr str
trigger the command when the defend verb is "str" trigger the command when the defend verb is "str". both onAttackStr and onDefendStr MUST be specified
-suppressAttack -suppressAttack
delete the attack announcement from the combat logs delete the attack announcement from the combat logs
-suppressDefend -suppressDefend
@ -126,32 +132,37 @@ arguments:
\\DEFENDER_ID \\DEFENDER_ID
\\ATTACK_REPORT \\ATTACK_REPORT
\\DEFEND_REPORT \\DEFEND_REPORT
\anything -> anything \\anything -> \anything
anything -> anything anything -> anything
You must specify both an attack string and a defend string to guarantee correct performance. Either will trigger the script when it happens, but it will not be triggered twice in a row if both happen.
]]) ]])
return return
end end
if args.clear then if args.clear then
attackStrTriggers = {} triggers = {}
defendStrTriggers = {} commands = {}
commandCount = 0
end end
if not args.command then if not args.command then
return return
end end
if args.onAttackStr then if not args.onAttackStr or not args.onDefendStr then
if not attackStrTriggers[args.onAttackStr] then error 'You must specify both onAttackStr and onDefendStr.'
attackStrTriggers[args.onAttackStr] = {}
end
table.insert(attackStrTriggers[args.onAttackStr], args)
end end
if args.onDefendStr then commands[commandCount] = args
if not defendStrTriggers[args.onDefendStr] then
defendStrTriggers[args.onDefendStr] = {} if not attackTriggers[args.onAttackStr] then
attackTriggers[args.onAttackStr] = {}
end end
table.insert(defendStrTriggers[args.onDefendStr], args) table.insert(attackTriggers[args.onAttackStr],commandCount)
if not defendTriggers[args.onDefendStr] then
defendTriggers[args.onDefendStr] = {}
end end
table.insert(defendTriggers[args.onDefendStr],commandCount)
commandCount = commandCount+1