summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlexander Gavrilov2012-09-17 14:45:22 +0400
committerAlexander Gavrilov2012-09-17 14:45:22 +0400
commitf2fde21b10c087fd95948a5f40ef972ac6688718 (patch)
tree832a68b12f1a2a54130515e320ea8115c4c7387b
parentc1e20c6f0565007c47cce9aaa06199a061dac20e (diff)
downloaddfhack-f2fde21b10c087fd95948a5f40ef972ac6688718.tar.gz
dfhack-f2fde21b10c087fd95948a5f40ef972ac6688718.tar.bz2
dfhack-f2fde21b10c087fd95948a5f40ef972ac6688718.tar.xz
Implement a slightly more sensible aiming AI in siege engine.
-rw-r--r--plugins/devel/siege-engine.cpp76
-rw-r--r--plugins/lua/siege-engine.lua208
2 files changed, 270 insertions, 14 deletions
diff --git a/plugins/devel/siege-engine.cpp b/plugins/devel/siege-engine.cpp
index 5e5cf5d7..3b95aba3 100644
--- a/plugins/devel/siege-engine.cpp
+++ b/plugins/devel/siege-engine.cpp
@@ -112,7 +112,7 @@ static bool is_in_range(const coord_range &target, df::coord pos)
static std::pair<int, int> get_engine_range(df::building_siegeenginest *bld)
{
if (bld->type == siegeengine_type::Ballista)
- return std::make_pair(0, 200);
+ return std::make_pair(1, 200);
else
return std::make_pair(30, 100);
}
@@ -291,7 +291,7 @@ static EngineInfo *find_engine(df::building *bld, bool create = false)
);
obj->is_catapult = (ebld->type == siegeengine_type::Catapult);
obj->proj_speed = 2;
- obj->hit_delay = 3;
+ obj->hit_delay = obj->is_catapult ? 2 : -1;
obj->fire_range = get_engine_range(ebld);
obj->ammo_vector_id = job_item_vector_id::BOULDER;
@@ -1107,6 +1107,9 @@ struct UnitPath {
float time = unit->counters.job_counter+0.5f;
float speed = Units::computeMovementSpeed(unit)/100.0f;
+ if (unit->counters.unconscious > 0)
+ time += unit->counters.unconscious;
+
for (size_t i = 0; i < upath.size(); i++)
{
df::coord new_pos = upath[i];
@@ -1282,6 +1285,74 @@ static int proposeUnitHits(lua_State *L)
return 1;
}
+static int computeNearbyWeight(lua_State *L)
+{
+ auto engine = find_engine(L, 1);
+ luaL_checktype(L, 2, LUA_TTABLE);
+ luaL_checktype(L, 3, LUA_TTABLE);
+ const char *fname = luaL_optstring(L, 4, "nearby_weight");
+
+ std::vector<UnitPath*> units;
+ std::vector<float> weights;
+
+ lua_pushnil(L);
+
+ while (lua_next(L, 3))
+ {
+ df::unit *unit;
+ if (lua_isnumber(L, -2))
+ unit = df::unit::find(lua_tointeger(L, -2));
+ else
+ unit = Lua::CheckDFObject<df::unit>(L, -2);
+ if (!unit)
+ continue;
+ units.push_back(UnitPath::get(unit));
+ weights.push_back(lua_tonumber(L, -1));
+ lua_pop(L, 1);
+ }
+
+ lua_pushnil(L);
+
+ while (lua_next(L, 2))
+ {
+ Lua::StackUnwinder frame(L, 1);
+
+ lua_getfield(L, frame[1], "unit");
+ df::unit *unit = Lua::CheckDFObject<df::unit>(L, -1);
+
+ lua_getfield(L, frame[1], "time");
+ float time = luaL_checknumber(L, lua_gettop(L));
+
+ df::coord pos;
+
+ lua_getfield(L, frame[1], "pos");
+ if (lua_isnil(L, -1))
+ {
+ if (!unit) luaL_error(L, "either unit or pos is required");
+ pos = UnitPath::get(unit)->posAtTime(time);
+ }
+ else
+ Lua::CheckDFAssign(L, &pos, -1);
+
+ float sum = 0.0f;
+
+ for (size_t i = 0; i < units.size(); i++)
+ {
+ if (units[i]->unit == unit)
+ continue;
+
+ auto diff = units[i]->posAtTime(time) - pos;
+ float dist = 1 + sqrtf(diff.x*diff.x + diff.y*diff.y + diff.z*diff.z);
+ sum += weights[i]/(dist*dist);
+ }
+
+ lua_pushnumber(L, sum);
+ lua_setfield(L, frame[1], fname);
+ }
+
+ return 0;
+}
+
/*
* Projectile hook
*/
@@ -1698,6 +1769,7 @@ DFHACK_PLUGIN_LUA_COMMANDS {
DFHACK_LUA_COMMAND(traceUnitPath),
DFHACK_LUA_COMMAND(unitPosAtTime),
DFHACK_LUA_COMMAND(proposeUnitHits),
+ DFHACK_LUA_COMMAND(computeNearbyWeight),
DFHACK_LUA_END
};
diff --git a/plugins/lua/siege-engine.lua b/plugins/lua/siege-engine.lua
index 89c47659..33e120fe 100644
--- a/plugins/lua/siege-engine.lua
+++ b/plugins/lua/siege-engine.lua
@@ -8,37 +8,221 @@ local _ENV = mkmodule('plugins.siege-engine')
* clearTargetArea(building)
* setTargetArea(building, point1, point2) -> true/false
---]]
+ * isLinkedToPile(building,pile) -> true/false
+ * getStockpileLinks(building) -> {pile}
+ * addStockpileLink(building,pile) -> true/false
+ * removeStockpileLink(building,pile) -> true/false
+
+ * saveWorkshopProfile(building) -> profile
+
+ * getAmmoItem(building) -> item_type
+ * setAmmoItem(building,item_type) -> true/false
+
+ * isPassableTile(pos) -> true/false
+ * isTreeTile(pos) -> true/false
+ * isTargetableTile(pos) -> true/false
+
+ * getTileStatus(building,pos) -> 'invalid/ok/out_of_range/blocked/semiblocked'
+ * paintAimScreen(building,view_pos_xyz,left_top_xy,size_xy)
+
+ * canTargetUnit(unit) -> true/false
+
+ proj_info = { target = pos, [delta = float/pos], [factor = int] }
+
+ * projPosAtStep(building,proj_info,step) -> pos
+ * projPathMetrics(building,proj_info) -> {
+ hit_type = 'wall/floor/ceiling/map_edge/tree',
+ collision_step = int,
+ collision_z_step = int,
+ goal_distance = int,
+ goal_step = int/nil,
+ goal_z_step = int/nil,
+ status = 'ok/out_of_range/blocked'
+ }
+
+ * adjustToTarget(building,pos) -> pos,ok=true/false
+
+ * traceUnitPath(unit) -> { {x=int,y=int,z=int[,from=time][,to=time]} }
+ * unitPosAtTime(unit, time) -> pos
+
+ * proposeUnitHits(building) -> { {
+ pos=pos, unit=unit, time=float, dist=int,
+ [lmargin=float,] [rmargin=float,]
+ } }
+
+ * computeNearbyWeight(building,hits,{[id/unit]=score}[,fname])
+
+]]
Z_STEP_COUNT = 15
Z_STEP = 1/31
+function getMetrics(engine, path)
+ path.metrics = path.metrics or projPathMetrics(engine, path)
+ return path.metrics
+end
+
function findShotHeight(engine, target)
local path = { target = target, delta = 0.0 }
- if projPathMetrics(engine, path).goal_step then
+ if getMetrics(engine, path).goal_step then
return path
end
- for i = 1,Z_STEP_COUNT do
- path.delta = i*Z_STEP
- if projPathMetrics(engine, path).goal_step then
- return path
+ local tpath = { target = target, delta = Z_STEP_COUNT*Z_STEP }
+
+ if getMetrics(engine, tpath).goal_step then
+ for i = 1,Z_STEP_COUNT-1 do
+ path = { target = target, delta = i*Z_STEP }
+ if getMetrics(engine, path).goal_step then
+ return path
+ end
+ end
+
+ return tpath
+ end
+
+ tpath = { target = target, delta = -Z_STEP_COUNT*Z_STEP }
+
+ if getMetrics(engine, tpath).goal_step then
+ for i = 1,Z_STEP_COUNT-1 do
+ path = { target = target, delta = -i*Z_STEP }
+ if getMetrics(engine, path).goal_step then
+ return path
+ end
+ end
+
+ return tpath
+ end
+end
+
+function findReachableTargets(engine, targets)
+ local reachable = {}
+ for _,tgt in ipairs(targets) do
+ tgt.path = findShotHeight(engine, tgt.pos)
+ if tgt.path then
+ table.insert(reachable, tgt)
+ end
+ end
+ return reachable
+end
+
+recent_targets = recent_targets or {}
+
+if dfhack.is_core_context then
+ dfhack.onStateChange[_ENV] = function(code)
+ if code == SC_MAP_LOADED then
+ recent_targets = {}
+ end
+ end
+end
+
+function saveRecent(unit)
+ local id = unit.id
+ local tgt = recent_targets
+ tgt[id] = (tgt[id] or 0) + 1
+ dfhack.timeout(3, 'days', function()
+ tgt[id] = math.max(0, tgt[id]-1)
+ end)
+end
+
+function getBaseUnitWeight(unit)
+ if dfhack.units.isCitizen(unit) then
+ return -10
+ elseif unit.flags1.diplomat or unit.flags1.merchant then
+ return -2
+ elseif unit.flags1.tame and unit.civ_id == df.global.ui.civ_id then
+ return -1
+ else
+ local rv = 1
+ if unit.flags1.marauder then rv = rv + 0.5 end
+ if unit.flags1.active_invader then rv = rv + 1 end
+ if unit.flags1.invader_origin then rv = rv + 1 end
+ if unit.flags1.invades then rv = rv + 1 end
+ if unit.flags1.hidden_ambusher then rv = rv + 1 end
+ return rv
+ end
+end
+
+function getUnitWeight(unit)
+ local base = getBaseUnitWeight(unit)
+ return base * math.pow(0.7, recent_targets[unit.id] or 0)
+end
+
+function unitWeightCache()
+ local cache = {}
+ return cache, function(unit)
+ local id = unit.id
+ cache[id] = cache[id] or getUnitWeight(unit)
+ return cache[id]
+ end
+end
+
+function scoreTargets(engine, reachable)
+ local ucache, get_weight = unitWeightCache()
+
+ for _,tgt in ipairs(reachable) do
+ tgt.score = get_weight(tgt.unit)
+ if tgt.lmargin and tgt.lmargin < 3 then
+ tgt.score = tgt.score * tgt.lmargin / 3
+ end
+ if tgt.rmargin and tgt.rmargin < 3 then
+ tgt.score = tgt.score * tgt.rmargin / 3
end
+ end
+
+ computeNearbyWeight(engine, reachable, ucache)
+
+ for _,tgt in ipairs(reachable) do
+ tgt.score = (tgt.score + tgt.nearby_weight*0.7) * math.pow(0.995, tgt.time/3)
+ end
+
+ table.sort(reachable, function(a,b)
+ return a.score > b.score or (a.score == b.score and a.time < b.time)
+ end)
+end
+
+function pickUniqueTargets(reachable)
+ local unique = {}
- path.delta = -i*Z_STEP
- if projPathMetrics(engine, path).goal_step then
- return path
+ if #reachable > 0 then
+ local pos_table = {}
+ local first_score = reachable[1].score
+
+ for i,tgt in ipairs(reachable) do
+ if tgt.score < 0 or tgt.score < 0.1*first_score then
+ break
+ end
+ local x,y,z = pos2xyz(tgt.pos)
+ local key = x..':'..y..':'..z
+ if pos_table[key] then
+ table.insert(pos_table[key].units, tgt.unit)
+ else
+ table.insert(unique, tgt)
+ pos_table[key] = tgt
+ tgt.units = { tgt.unit }
+ end
end
end
+
+ return unique
end
function doAimProjectile(engine, item, target_min, target_max, skill)
print(item, df.skill_rating[skill])
+
local targets = proposeUnitHits(engine)
- if #targets > 0 then
- local rnd = math.random(#targets)
- return findShotHeight(engine, targets[rnd].pos)
+ local reachable = findReachableTargets(engine, targets)
+ scoreTargets(engine, reachable)
+ local unique = pickUniqueTargets(reachable)
+
+ if #unique > 0 then
+ local cnt = math.max(math.min(#unique,5), math.min(10, math.floor(#unique/2)))
+ local rnd = math.random(cnt)
+ for _,u in ipairs(unique[rnd].units) do
+ saveRecent(u)
+ end
+ return unique[rnd].path
end
end