diff --git a/library/include/LuaTools.h b/library/include/LuaTools.h index 6b1afb88b..3330e23e7 100644 --- a/library/include/LuaTools.h +++ b/library/include/LuaTools.h @@ -287,6 +287,11 @@ namespace DFHack {namespace Lua { PushDFObject(state, ptr); } + template inline void SetField(lua_State *L, T val, int idx, const char *name) { + if (idx < 0) idx = lua_absindex(L, idx); + Push(L, val); lua_setfield(L, idx, name); + } + template void PushVector(lua_State *state, const T &pvec, bool addn = false) { diff --git a/plugins/devel/siege-engine.cpp b/plugins/devel/siege-engine.cpp index c36e9fb32..ce835c6d4 100644 --- a/plugins/devel/siege-engine.cpp +++ b/plugins/devel/siege-engine.cpp @@ -137,13 +137,24 @@ static bool enable_plugin(); struct EngineInfo { int id; - coord_range target; + df::building_siegeenginest *bld; + df::coord center; + coord_range building_rect; + + bool is_catapult; int proj_speed, hit_delay; + std::pair fire_range; + + coord_range target; bool hasTarget() { return is_range_valid(target); } bool onTarget(df::coord pos) { return is_in_range(target, pos); } df::coord getTargetSize() { return target.second - target.first; } + + bool isInRange(int dist) { + return dist >= fire_range.first && dist <= fire_range.second; + } }; static std::map engines; @@ -151,29 +162,64 @@ static std::map coord_engines; static EngineInfo *find_engine(df::building *bld, bool create = false) { - if (!bld) + auto ebld = strict_virtual_cast(bld); + if (!ebld) return NULL; auto it = engines.find(bld); if (it != engines.end()) + { + it->second.bld = ebld; return &it->second; + } + if (!create) return NULL; auto *obj = &engines[bld]; obj->id = bld->id; + obj->bld = ebld; obj->center = df::coord(bld->centerx, bld->centery, bld->z); + obj->building_rect = coord_range( + df::coord(bld->x1, bld->y1, bld->z), + df::coord(bld->x2, bld->y2, bld->z) + ); + obj->is_catapult = (ebld->type == siegeengine_type::Catapult); obj->proj_speed = 2; obj->hit_delay = 3; + obj->fire_range = get_engine_range(ebld); coord_engines[obj->center] = bld; return obj; } +static EngineInfo *find_engine(lua_State *L, int idx, bool create = false) +{ + auto bld = Lua::CheckDFObject(L, idx); + + auto engine = find_engine(bld); + if (!engine) + luaL_error(L, "no such engine"); + + return engine; +} + static EngineInfo *find_engine(df::coord pos) { - return find_engine(coord_engines[pos]); + auto engine = find_engine(coord_engines[pos]); + + if (engine) + { + auto bld0 = df::building::find(engine->id); + auto bld = strict_virtual_cast(bld0); + if (!bld) + return NULL; + + engine->bld = bld; + } + + return engine; } static void clear_engines() @@ -263,37 +309,61 @@ static bool setTargetArea(df::building_siegeenginest *bld, df::coord target_min, return true; } +static int getShotSkill(df::building_siegeenginest *bld) +{ + CHECK_NULL_POINTER(bld); + + auto engine = find_engine(bld); + if (!engine) + return 0; + + auto &active = world->units.active; + for (size_t i = 0; i < active.size(); i++) + if (active[i]->pos == engine->center && Units::isCitizen(active[i])) + return Units::getEffectiveSkill(active[i], job_skill::SIEGEOPERATE); + + return 0; +} + /* * Trajectory */ struct ProjectilePath { + static const int DEFAULT_FUDGE = 31; + df::coord origin, goal, target, fudge_delta; int divisor, fudge_factor; df::coord speed, direction; ProjectilePath(df::coord origin, df::coord goal) : - origin(origin), goal(goal), target(goal), fudge_factor(1) + origin(origin), goal(goal), fudge_factor(1) { fudge_delta = df::coord(0,0,0); calc_line(); } - void fudge(int factor, df::coord delta) + ProjectilePath(df::coord origin, df::coord goal, df::coord delta, int factor) : + origin(origin), goal(goal), fudge_delta(delta), fudge_factor(factor) + { + calc_line(); + } + + ProjectilePath(df::coord origin, df::coord goal, float zdelta, int factor = DEFAULT_FUDGE) : + origin(origin), goal(goal), fudge_factor(factor) { - fudge_factor = factor; - fudge_delta = delta; - auto diff = goal - origin; - diff.x *= fudge_factor; - diff.y *= fudge_factor; - diff.z *= fudge_factor; - target = origin + diff + fudge_delta; + fudge_delta = df::coord(0,0,int(factor * zdelta)); calc_line(); } void calc_line() { - speed = target - origin; + speed = goal - origin; + speed.x *= fudge_factor; + speed.y *= fudge_factor; + speed.z *= fudge_factor; + speed = speed + fudge_delta; + target = origin + speed; divisor = point_distance(speed); if (divisor <= 0) divisor = 1; direction = df::coord(speed.x>=0?1:-1,speed.y>=0?1:-1,speed.z>=0?1:-1); @@ -311,42 +381,139 @@ struct ProjectilePath { } }; +static ProjectilePath decode_path(lua_State *L, int idx, df::coord origin) +{ + idx = lua_absindex(L, idx); + + Lua::StackUnwinder frame(L); + df::coord goal; + + lua_getfield(L, idx, "target"); + Lua::CheckDFAssign(L, &goal, frame[1]); + + lua_getfield(L, idx, "delta"); + + if (!lua_isnil(L, frame[2])) + { + lua_getfield(L, idx, "factor"); + int factor = luaL_optnumber(L, frame[3], ProjectilePath::DEFAULT_FUDGE); + + if (lua_isnumber(L, frame[2])) + return ProjectilePath(origin, goal, lua_tonumber(L, frame[2]), factor); + + df::coord delta; + Lua::CheckDFAssign(L, &delta, frame[2]); + + return ProjectilePath(origin, goal, delta, factor); + } + + return ProjectilePath(origin, goal); +} + +static int projPosAtStep(lua_State *L) +{ + auto engine = find_engine(L, 1); + auto path = decode_path(L, 2, engine->center); + int step = luaL_checkint(L, 3); + Lua::Push(L, path[step]); + return 1; +} + static bool isPassableTile(df::coord pos) { auto ptile = Maps::getTileType(pos); + return !ptile || FlowPassable(*ptile); } +static bool isTargetableTile(df::coord pos) +{ + auto ptile = Maps::getTileType(pos); + + return ptile && FlowPassable(*ptile) && !isOpenTerrain(*ptile); +} + +static bool isTreeTile(df::coord pos) +{ + auto ptile = Maps::getTileType(pos); + + return ptile && tileShape(*ptile) == tiletype_shape::TREE; +} + +static bool adjustToTarget(EngineInfo *engine, df::coord *pos) +{ + if (isTargetableTile(*pos)) + return true; + + for (df::coord fudge = *pos; + fudge.z < engine->target.second.z; fudge.z++) + { + if (!isTargetableTile(fudge)) + continue; + *pos = fudge; + return true; + } + + for (df::coord fudge = *pos; + fudge.z > engine->target.first.z; fudge.z--) + { + if (!isTargetableTile(fudge)) + continue; + *pos = fudge; + return true; + } + + return false; +} + +static int adjustToTarget(lua_State *L) +{ + auto engine = find_engine(L, 1, true); + df::coord pos; + Lua::CheckDFAssign(L, &pos, 2); + bool ok = adjustToTarget(engine, &pos); + Lua::Push(L, pos); + Lua::Push(L, ok); + return 2; +} + +static const char* const hit_type_names[] = { + "wall", "floor", "ceiling", "map_edge", "tree" +}; + struct PathMetrics { enum CollisionType { Impassable, Floor, Ceiling, - MapEdge + MapEdge, + Tree } hit_type; - int collision_step; - int goal_step, goal_z_step; - std::vector coords; + int collision_step, collision_z_step; + int goal_step, goal_z_step, goal_distance; + + bool hits() const { return collision_step > goal_step; } - bool hits() { return collision_step > goal_step; } + PathMetrics(const ProjectilePath &path) + { + compute(path); + } - PathMetrics(const ProjectilePath &path, bool list_coords = false) + void compute(const ProjectilePath &path) { - coords.clear(); collision_step = goal_step = goal_z_step = 1000000; + collision_z_step = 0; + + goal_distance = point_distance(path.origin - path.goal); int step = 0; df::coord prev_pos = path.origin; - if (list_coords) - coords.push_back(prev_pos); for (;;) { df::coord cur_pos = path[++step]; if (cur_pos == prev_pos) break; - if (list_coords) - coords.push_back(cur_pos); if (cur_pos.z == path.goal.z) { @@ -363,8 +530,21 @@ struct PathMetrics { if (!isPassableTile(cur_pos)) { - hit_type = Impassable; - break; + if (isTreeTile(cur_pos)) + { + // The projectile code has a bug where it will + // hit a tree on the same tick as a Z level change. + if (cur_pos.z != prev_pos.z) + { + hit_type = Tree; + break; + } + } + else + { + hit_type = Impassable; + break; + } } if (cur_pos.z != prev_pos.z) @@ -377,6 +557,8 @@ struct PathMetrics { hit_type = (cur_pos.z > prev_pos.z ? Ceiling : Floor); break; } + + collision_z_step = step; } prev_pos = cur_pos; @@ -386,107 +568,112 @@ struct PathMetrics { } }; -struct AimContext { - df::building_siegeenginest *bld; - df::coord origin; - coord_range building_rect; - EngineInfo *engine; - std::pair fire_range; +enum TargetTileStatus { + TARGET_OK, TARGET_RANGE, TARGET_BLOCKED, TARGET_SEMIBLOCKED +}; +static const char* const target_tile_type_names[] = { + "ok", "out_of_range", "blocked", "semi_blocked" +}; - AimContext(df::building_siegeenginest *bld, EngineInfo *engine) - : bld(bld), engine(engine) +static TargetTileStatus calcTileStatus(EngineInfo *engine, const PathMetrics &raytrace) +{ + if (raytrace.hits()) { - origin = df::coord(bld->centerx, bld->centery, bld->z); - building_rect = coord_range( - df::coord(bld->x1, bld->y1, bld->z), - df::coord(bld->x2, bld->y2, bld->z) - ); - fire_range = get_engine_range(bld); + if (engine->isInRange(raytrace.goal_step)) + return TARGET_OK; + else + return TARGET_RANGE; } + else + return TARGET_BLOCKED; +} - bool isInRange(const PathMetrics &raytrace) - { - return raytrace.goal_step >= fire_range.first && - raytrace.goal_step <= fire_range.second; - } +static int projPathMetrics(lua_State *L) +{ + auto engine = find_engine(L, 1); + auto path = decode_path(L, 2, engine->center); + + PathMetrics info(path); + + lua_createtable(L, 0, 7); + Lua::SetField(L, hit_type_names[info.hit_type], -1, "hit_type"); + Lua::SetField(L, info.collision_step, -1, "collision_step"); + Lua::SetField(L, info.collision_z_step, -1, "collision_z_step"); + Lua::SetField(L, info.goal_distance, -1, "goal_distance"); + if (info.goal_step < info.collision_step) + Lua::SetField(L, info.goal_step, -1, "goal_step"); + if (info.goal_z_step < info.collision_step) + Lua::SetField(L, info.goal_z_step, -1, "goal_z_step"); + Lua::SetField(L, target_tile_type_names[calcTileStatus(engine, info)], -1, "status"); + return 1; +} - bool adjustToPassable(df::coord *pos) - { - if (isPassableTile(*pos)) - return true; +static TargetTileStatus calcTileStatus(EngineInfo *engine, df::coord target, float zdelta) +{ + ProjectilePath path(engine->center, target, zdelta); + PathMetrics raytrace(path); + return calcTileStatus(engine, raytrace); +} - for (df::coord fudge = *pos; - fudge.z < engine->target.second.z; fudge.z++) - { - if (!isPassableTile(fudge)) - continue; - *pos = fudge; - return true; - } +static TargetTileStatus calcTileStatus(EngineInfo *engine, df::coord target) +{ + auto status = calcTileStatus(engine, target, 0.0f); - for (df::coord fudge = *pos; - fudge.z > engine->target.first.z; fudge.z--) - { - if (!isPassableTile(fudge)) - continue; - *pos = fudge; - return true; - } + if (status == TARGET_BLOCKED) + { + if (calcTileStatus(engine, target, 0.5f) < TARGET_BLOCKED) + return TARGET_SEMIBLOCKED; - return false; + if (calcTileStatus(engine, target, -0.5f) < TARGET_BLOCKED) + return TARGET_SEMIBLOCKED; } -}; + return status; +} static std::string getTileStatus(df::building_siegeenginest *bld, df::coord tile_pos) { - AimContext context(bld, NULL); - - ProjectilePath path(context.origin, tile_pos); - PathMetrics raytrace(path); + auto engine = find_engine(bld, true); + if (!engine) + return "invalid"; - if (raytrace.hits()) - { - if (context.isInRange(raytrace)) - return "ok"; - else - return "out_of_range"; - } - else - return "blocked"; + return target_tile_type_names[calcTileStatus(engine, tile_pos)]; } static void paintAimScreen(df::building_siegeenginest *bld, df::coord view, df::coord2d ltop, df::coord2d size) { - CHECK_NULL_POINTER(bld); - - AimContext context(bld, find_engine(bld)); + auto engine = find_engine(bld, true); + CHECK_NULL_POINTER(engine); for (int x = 0; x < size.x; x++) { for (int y = 0; y < size.y; y++) { df::coord tile_pos = view + df::coord(x,y,0); - if (is_in_range(context.building_rect, tile_pos)) + if (is_in_range(engine->building_rect, tile_pos)) continue; Pen cur_tile = Screen::readTile(ltop.x+x, ltop.y+y); if (!cur_tile.valid()) continue; - ProjectilePath path(context.origin, tile_pos); - PathMetrics raytrace(path); - int color; - if (raytrace.hits()) + + switch (calcTileStatus(engine, tile_pos)) { - if (context.isInRange(raytrace)) + case TARGET_OK: color = COLOR_GREEN; - else + break; + case TARGET_RANGE: color = COLOR_CYAN; + break; + case TARGET_BLOCKED: + color = COLOR_RED; + break; + case TARGET_SEMIBLOCKED: + color = COLOR_BROWN; + break; } - else - color = COLOR_RED; if (cur_tile.fg && cur_tile.ch != ' ') { @@ -499,7 +686,7 @@ static void paintAimScreen(df::building_siegeenginest *bld, df::coord view, df:: cur_tile.bg = color; } - cur_tile.bold = (context.engine && context.engine->onTarget(tile_pos)); + cur_tile.bold = engine->onTarget(tile_pos); if (cur_tile.tile) cur_tile.tile_mode = Pen::CharColor; @@ -537,6 +724,17 @@ struct UnitPath { UnitPath(df::unit *unit) : unit(unit) { + if (unit->flags1.bits.rider) + { + auto mount = df::unit::find(unit->relations.rider_mount_id); + + if (mount) + { + path = get(mount)->path; + return; + } + } + df::coord pos = unit->pos; df::coord dest = unit->path.dest; auto &upath = unit->path.path; @@ -598,7 +796,7 @@ struct UnitPath { if (info.lmargin > 0 && info.rmargin > 0) { - if (engine->onTarget(info.pos)) + if (engine->onTarget(info.pos) && engine->isInRange(info.dist)) hit_points->push_back(info); } } @@ -664,6 +862,19 @@ static int unitPosAtTime(lua_State *L) return 3; } +static bool canTargetUnit(df::unit *unit) +{ + CHECK_NULL_POINTER(unit); + + if (unit->flags1.bits.dead || + unit->flags3.bits.ghostly || + unit->flags1.bits.caged || + unit->flags1.bits.hidden_in_ambush) + return false; + + return true; +} + static void proposeUnitHits(EngineInfo *engine, std::vector *hits, float bias) { auto &active = world->units.active; @@ -672,12 +883,7 @@ static void proposeUnitHits(EngineInfo *engine, std::vector *hits { auto unit = active[i]; - if (unit->flags1.bits.dead || - unit->flags3.bits.ghostly || - unit->flags1.bits.caged || - unit->flags1.bits.chained || - unit->flags1.bits.rider || - unit->flags1.bits.hidden_in_ambush) + if (!canTargetUnit(unit)) continue; UnitPath::get(unit)->findHits(engine, hits, bias); @@ -686,12 +892,9 @@ static void proposeUnitHits(EngineInfo *engine, std::vector *hits static int proposeUnitHits(lua_State *L) { - auto bld = Lua::CheckDFObject(L, 1); + auto engine = find_engine(L, 1); float bias = luaL_optnumber(L, 2, 0); - auto engine = find_engine(bld); - if (!engine) - luaL_error(L, "no such engine"); if (!engine->hasTarget()) luaL_error(L, "target not set"); @@ -704,10 +907,10 @@ static int proposeUnitHits(lua_State *L) { auto &hit = hits[i]; lua_createtable(L, 0, 6); - Lua::PushDFObject(L, hit.path->unit); lua_setfield(L, -2, "unit"); - Lua::Push(L, hit.pos); lua_setfield(L, -2, "pos"); - lua_pushnumber(L, hit.dist); lua_setfield(L, -2, "dist"); - lua_pushnumber(L, hit.time); lua_setfield(L, -2, "time"); + Lua::SetField(L, hit.path->unit, -1, "unit"); + Lua::SetField(L, hit.pos, -1, "pos"); + Lua::SetField(L, hit.dist, -1, "dist"); + Lua::SetField(L, hit.time, -1, "time"); push_margin(L, hit.lmargin); lua_setfield(L, -2, "lmargin"); push_margin(L, hit.rmargin); lua_setfield(L, -2, "rmargin"); lua_rawseti(L, -2, i+1); @@ -716,89 +919,6 @@ static int proposeUnitHits(lua_State *L) return 1; } -#if 0 -struct UnitContext { - AimContext &ctx; - - struct UnitInfo { - df::unit *unit; - - UnitPath path; - float score; - - UnitInfo(df::unit *unit) : unit(unit), path(unit) {} - }; - - std::map units; - - UnitContext(AimContext &ctx) : ctx(ctx) {} - - ~UnitContext() - { - for (auto it = units.begin(); it != units.end(); ++it) - delete it->second; - } - - float unit_score(df::unit *unit) - { - float score = 1.0f; - - if (unit->flags1.bits.tame && unit->civ_id == ui->civ_id) - score = -1.0f; - if (unit->flags1.bits.diplomat || unit->flags1.bits.merchant) - score = -2.0f; - else if (Units::isCitizen(unit)) - score = -10.0f; - else - { - if (unit->flags1.bits.marauder) - score += 0.5f; - if (unit->flags1.bits.active_invader) - score += 1.0f; - if (unit->flags1.bits.invader_origin) - score += 1.0f; - if (unit->flags1.bits.invades) - score += 1.0f; - if (unit->flags1.bits.hidden_ambusher) - score += 1.0f; - } - - if (unit->flags1.bits.ridden) - { - for (size_t i = 0; i < unit->refs.size(); i++) - { - if (!unit->refs[i]->getType() == general_ref_type::UNIT_RIDER) - continue; - if (auto rider = unit->refs[i]->getUnit()) - score += unit_score(rider); - } - } - } - - void select_units() - { - auto &active = world->units.active; - - for (size_t i = 0; i < active.size(); i++) - { - auto unit = active[i]; - if (unit->flags1.bits.dead || - unit->flags3.bits.ghostly || - unit->flags1.bits.caged || - unit->flags1.bits.chained || - unit->flags1.bits.rider || - unit->flags1.bits.hidden_in_ambush) - continue; - - auto info = units[unit] = new UnitInfo(unit); - - info->findHits(ctx, ctx.proj_hit_delay); - info->score = unit_score(unit); - } - } -}; -#endif - /* * Projectile hook */ @@ -806,7 +926,7 @@ struct UnitContext { struct projectile_hook : df::proj_itemst { typedef df::proj_itemst interpose_base; - void aimAtPoint(AimContext &context, ProjectilePath &path, bool bad_shot = false) + void aimAtPoint(EngineInfo *engine, const ProjectilePath &path) { target_pos = path.target; @@ -816,28 +936,32 @@ struct projectile_hook : df::proj_itemst { for (int i = 0; i < raytrace.collision_step; i++) Maps::ensureTileBlock(path[i]); - if (flags.bits.piercing) - { - if (bad_shot) - fall_threshold = std::min(raytrace.goal_z_step, raytrace.collision_step); - } - else + // Find valid hit point for catapult stones + if (flags.bits.high_flying) { - if (bad_shot) - fall_threshold = context.fire_range.second; - else + if (raytrace.hits()) fall_threshold = raytrace.goal_step; + else + fall_threshold = (raytrace.collision_z_step+raytrace.collision_step-1)/2; + + while (fall_threshold < raytrace.collision_step-1) + { + if (isTargetableTile(path[fall_threshold])) + break; + + fall_threshold++; + } } - fall_threshold = std::max(fall_threshold, context.fire_range.first); - fall_threshold = std::min(fall_threshold, context.fire_range.second); + fall_threshold = std::max(fall_threshold, engine->fire_range.first); + fall_threshold = std::min(fall_threshold, engine->fire_range.second); } - void aimAtArea(AimContext &context) + void aimAtArea(EngineInfo *engine) { df::coord target, last_passable; - df::coord tbase = context.engine->target.first; - df::coord tsize = context.engine->getTargetSize(); + df::coord tbase = engine->target.first; + df::coord tsize = engine->getTargetSize(); bool success = false; for (int i = 0; i < 50; i++) @@ -846,17 +970,17 @@ struct projectile_hook : df::proj_itemst { random_int(tsize.x), random_int(tsize.y), random_int(tsize.z) ); - if (context.adjustToPassable(&target)) + if (adjustToTarget(engine, &target)) last_passable = target; else continue; - ProjectilePath path(context.origin, target); + ProjectilePath path(engine->center, target, engine->is_catapult ? 0.5f : 0.0f); PathMetrics raytrace(path); - if (raytrace.hits() && context.isInRange(raytrace)) + if (raytrace.hits() && engine->isInRange(raytrace.goal_step)) { - aimAtPoint(context, path); + aimAtPoint(engine, path); return; } } @@ -864,8 +988,30 @@ struct projectile_hook : df::proj_itemst { if (!last_passable.isValid()) last_passable = target; - ProjectilePath path(context.origin, last_passable); - aimAtPoint(context, path, true); + aimAtPoint(engine, ProjectilePath(engine->center, last_passable)); + } + + static int safeAimProjectile(lua_State *L) + { + color_ostream &out = *Lua::GetOutput(L); + auto proj = (projectile_hook*)lua_touserdata(L, 1); + auto engine = (EngineInfo*)lua_touserdata(L, 2); + + if (!Lua::PushModulePublic(out, L, "plugins.siege-engine", "doAimProjectile")) + luaL_error(L, "Projectile aiming AI not available"); + + Lua::PushDFObject(L, engine->bld); + Lua::Push(L, engine->target.first); + Lua::Push(L, engine->target.second); + + lua_call(L, 3, 1); + + if (lua_isnil(L, -1)) + proj->aimAtArea(engine); + else + proj->aimAtPoint(engine, decode_path(L, -1, engine->center)); + + return 0; } void doCheckMovement() @@ -877,14 +1023,16 @@ struct projectile_hook : df::proj_itemst { if (!engine || !engine->hasTarget()) return; - auto bld0 = df::building::find(engine->id); - auto bld = strict_virtual_cast(bld0); - if (!bld) - return; + auto L = Lua::Core::State; + CoreSuspendClaimer suspend; + color_ostream_proxy out(Core::getInstance().getConsole()); - AimContext context(bld, engine); + lua_pushcfunction(L, safeAimProjectile); + lua_pushlightuserdata(L, this); + lua_pushlightuserdata(L, engine); - aimAtArea(context); + if (!Lua::Core::SafeCall(out, 2, 0)) + aimAtArea(engine); } DEFINE_VMETHOD_INTERPOSE(bool, checkMovement, ()) @@ -907,11 +1055,18 @@ DFHACK_PLUGIN_LUA_FUNCTIONS { DFHACK_LUA_FUNCTION(setTargetArea), DFHACK_LUA_FUNCTION(getTileStatus), DFHACK_LUA_FUNCTION(paintAimScreen), + DFHACK_LUA_FUNCTION(canTargetUnit), + DFHACK_LUA_FUNCTION(isPassableTile), + DFHACK_LUA_FUNCTION(isTreeTile), + DFHACK_LUA_FUNCTION(isTargetableTile), DFHACK_LUA_END }; DFHACK_PLUGIN_LUA_COMMANDS { DFHACK_LUA_COMMAND(getTargetArea), + DFHACK_LUA_COMMAND(projPosAtStep), + DFHACK_LUA_COMMAND(projPathMetrics), + DFHACK_LUA_COMMAND(adjustToTarget), DFHACK_LUA_COMMAND(traceUnitPath), DFHACK_LUA_COMMAND(unitPosAtTime), DFHACK_LUA_COMMAND(proposeUnitHits), diff --git a/plugins/lua/siege-engine.lua b/plugins/lua/siege-engine.lua index 01b5d1447..d078ce1d7 100644 --- a/plugins/lua/siege-engine.lua +++ b/plugins/lua/siege-engine.lua @@ -10,4 +10,35 @@ local _ENV = mkmodule('plugins.siege-engine') --]] +Z_STEP_COUNT = 15 +Z_STEP = 1/31 + +function findShotHeight(engine, target) + local path = { target = target, delta = 0.0 } + + if projPathMetrics(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 + end + + path.delta = -i*Z_STEP + if projPathMetrics(engine, path).goal_step then + return path + end + end +end + +function doAimProjectile(engine, target_min, target_max) + local targets = proposeUnitHits(engine) + if #targets > 0 then + local rnd = math.random(#targets) + return findShotHeight(engine, targets[rnd].pos) + end +end + return _ENV \ No newline at end of file diff --git a/scripts/gui/siege-engine.lua b/scripts/gui/siege-engine.lua index bba6bce89..7ad828c90 100644 --- a/scripts/gui/siege-engine.lua +++ b/scripts/gui/siege-engine.lua @@ -45,7 +45,9 @@ function SiegeEngine:onShow() end function SiegeEngine:onDestroy() - self:selectBuilding(self.building, self.old_cursor, self.old_viewport, 10) + if self.building then + self:selectBuilding(self.building, self.old_cursor, self.old_viewport, 10) + end end function SiegeEngine:showCursor(enable) @@ -213,6 +215,7 @@ local status_table = { ok = { pen = COLOR_GREEN, msg = "Target accessible" }, out_of_range = { pen = COLOR_CYAN, msg = "Target out of range" }, blocked = { pen = COLOR_RED, msg = "Target obstructed" }, + semi_blocked = { pen = COLOR_BROWN, msg = "Partially obstructed" }, } function SiegeEngine:onRenderBody_aim(dc) @@ -291,6 +294,12 @@ function SiegeEngine:onInput(keys) self:centerViewOn(self.center) elseif keys.LEAVESCREEN then self:dismiss() + elseif keys.LEAVESCREEN_ALL then + self:dismiss() + self.building = nil + guidm.clearCursorPos() + df.global.ui.main.mode = df.ui_sidebar_mode.Default + df.global.world.selected_building = nil end end