diff --git a/plugins/devel/siege-engine.cpp b/plugins/devel/siege-engine.cpp index 6906f540c..86fe3ba5e 100644 --- a/plugins/devel/siege-engine.cpp +++ b/plugins/devel/siege-engine.cpp @@ -29,6 +29,7 @@ #include "df/ui_build_selector.h" #include "df/flow_info.h" #include "df/report.h" +#include "df/proj_itemst.h" #include "MiscUtils.h" @@ -106,6 +107,11 @@ static void orient_engine(df::building_siegeenginest *bld, df::coord target) df::building_siegeenginest::Up; } +static int random_int(int val) +{ + return int(int64_t(rand())*val/RAND_MAX); +} + /* * Configuration management */ @@ -115,12 +121,15 @@ static bool enable_plugin(); struct EngineInfo { int id; coord_range target; + df::coord center; 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; } }; static std::map engines; +static std::map coord_engines; static EngineInfo *find_engine(df::building *bld, bool create = false) { @@ -135,12 +144,26 @@ static EngineInfo *find_engine(df::building *bld, bool create = false) auto *obj = &engines[bld]; obj->id = bld->id; + obj->center = df::coord(bld->centerx, bld->centery, bld->z); + + coord_engines[obj->center] = bld; return obj; } -static void load_engines() +static EngineInfo *find_engine(df::coord pos) +{ + return find_engine(coord_engines[pos]); +} + +static void clear_engines() { engines.clear(); + coord_engines.clear(); +} + +static void load_engines() +{ + clear_engines(); auto pworld = Core::getInstance().getWorld(); std::vector vec; @@ -224,12 +247,30 @@ static bool setTargetArea(df::building_siegeenginest *bld, df::coord target_min, */ struct ProjectilePath { - df::coord origin, target; - int divisor; + df::coord origin, goal, target, fudge_delta; + int divisor, fudge_factor; df::coord speed, direction; - ProjectilePath(df::coord origin, df::coord target) : - origin(origin), target(target) + ProjectilePath(df::coord origin, df::coord goal) : + origin(origin), goal(goal), target(goal), fudge_factor(1) + { + fudge_delta = df::coord(0,0,0); + calc_line(); + } + + void fudge(int factor, df::coord delta) + { + 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; + calc_line(); + } + + void calc_line() { speed = target - origin; divisor = std::max(abs(speed.x), std::max(abs(speed.y), abs(speed.z))); @@ -237,7 +278,8 @@ struct ProjectilePath { direction = df::coord(speed.x>=0?1:-1,speed.y>=0?1:-1,speed.z>=0?1:-1); } - df::coord operator[] (int i) const { + df::coord operator[] (int i) const + { int div2 = divisor * 2; int bias = divisor-1; return origin + df::coord( @@ -266,12 +308,12 @@ struct PathMetrics { int goal_step, goal_z_step; std::vector coords; - bool hits() { return goal_step != -1 && collision_step > goal_step; } + bool hits() { return collision_step > goal_step; } - PathMetrics(const ProjectilePath &path, df::coord goal, bool list_coords = false) + PathMetrics(const ProjectilePath &path, bool list_coords = false) { coords.clear(); - collision_step = goal_step = goal_z_step = -1; + collision_step = goal_step = goal_z_step = 1000000; int step = 0; df::coord prev_pos = path.origin; @@ -285,11 +327,10 @@ struct PathMetrics { if (list_coords) coords.push_back(cur_pos); - if (cur_pos.z == goal.z) + if (cur_pos.z == path.goal.z) { - if (goal_z_step == -1) - goal_z_step = step; - if (cur_pos == goal) + goal_z_step = std::min(step, goal_z_step); + if (cur_pos == path.goal) goal_step = step; } @@ -324,18 +365,68 @@ struct PathMetrics { } }; +struct AimContext { + df::building_siegeenginest *bld; + df::coord origin; + coord_range building_rect; + EngineInfo *engine; + std::pair fire_range; + + AimContext(df::building_siegeenginest *bld, EngineInfo *engine) + : bld(bld), engine(engine) + { + 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); + } + + bool isInRange(const PathMetrics &raytrace) + { + return raytrace.goal_step >= fire_range.first && + raytrace.goal_step <= fire_range.second; + } + + bool adjustToPassable(df::coord *pos) + { + if (isPassableTile(*pos)) + return true; + + for (df::coord fudge = *pos; + fudge.z < engine->target.second.z; fudge.z++) + { + if (!isPassableTile(fudge)) + continue; + *pos = fudge; + return true; + } + + for (df::coord fudge = *pos; + fudge.z > engine->target.first.z; fudge.z--) + { + if (!isPassableTile(fudge)) + continue; + *pos = fudge; + return true; + } + + return false; + } + +}; + static std::string getTileStatus(df::building_siegeenginest *bld, df::coord tile_pos) { - df::coord origin(bld->centerx, bld->centery, bld->z); - auto fire_range = get_engine_range(bld); + AimContext context(bld, NULL); - ProjectilePath path(origin, tile_pos); - PathMetrics raytrace(path, tile_pos); + ProjectilePath path(context.origin, tile_pos); + PathMetrics raytrace(path); if (raytrace.hits()) { - if (raytrace.goal_step >= fire_range.first && - raytrace.goal_step <= fire_range.second) + if (context.isInRange(raytrace)) return "ok"; else return "out_of_range"; @@ -348,35 +439,27 @@ static void paintAimScreen(df::building_siegeenginest *bld, df::coord view, df:: { CHECK_NULL_POINTER(bld); - df::coord origin(bld->centerx, bld->centery, bld->z); - coord_range building_rect( - df::coord(bld->x1, bld->y1, bld->z), - df::coord(bld->x2, bld->y2, bld->z) - ); - - auto engine = find_engine(bld); - auto fire_range = get_engine_range(bld); + AimContext context(bld, find_engine(bld)); 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(building_rect, tile_pos)) + if (is_in_range(context.building_rect, tile_pos)) continue; Pen cur_tile = Screen::readTile(ltop.x+x, ltop.y+y); if (!cur_tile.valid()) continue; - ProjectilePath path(origin, tile_pos); - PathMetrics raytrace(path, tile_pos); + ProjectilePath path(context.origin, tile_pos); + PathMetrics raytrace(path); int color; if (raytrace.hits()) { - if (raytrace.goal_step >= fire_range.first && - raytrace.goal_step <= fire_range.second) + if (context.isInRange(raytrace)) color = COLOR_GREEN; else color = COLOR_CYAN; @@ -395,7 +478,7 @@ static void paintAimScreen(df::building_siegeenginest *bld, df::coord view, df:: cur_tile.bg = color; } - cur_tile.bold = (engine && engine->onTarget(tile_pos)); + cur_tile.bold = (context.engine && context.engine->onTarget(tile_pos)); if (cur_tile.tile) cur_tile.tile_mode = Pen::CharColor; @@ -405,6 +488,105 @@ static void paintAimScreen(df::building_siegeenginest *bld, df::coord view, df:: } } +/* + * Projectile hook + */ + +struct projectile_hook : df::proj_itemst { + typedef df::proj_itemst interpose_base; + + void aimAtPoint(AimContext &context, ProjectilePath &path, bool bad_shot = false) + { + target_pos = path.target; + + PathMetrics raytrace(path); + + // Materialize map blocks, or the projectile will crash into them + 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 + { + if (bad_shot) + fall_threshold = context.fire_range.second; + else + fall_threshold = raytrace.goal_step; + } + + fall_threshold = std::max(fall_threshold, context.fire_range.first); + fall_threshold = std::min(fall_threshold, context.fire_range.second); + } + + void aimAtArea(AimContext &context) + { + df::coord target, last_passable; + df::coord tbase = context.engine->target.first; + df::coord tsize = context.engine->getTargetSize(); + bool success = false; + + for (int i = 0; i < 50; i++) + { + target = tbase + df::coord( + random_int(tsize.x), random_int(tsize.y), random_int(tsize.z) + ); + + if (context.adjustToPassable(&target)) + last_passable = target; + else + continue; + + ProjectilePath path(context.origin, target); + PathMetrics raytrace(path); + + if (raytrace.hits() && context.isInRange(raytrace)) + { + aimAtPoint(context, path); + return; + } + } + + if (!last_passable.isValid()) + last_passable = target; + + ProjectilePath path(context.origin, last_passable); + aimAtPoint(context, path, true); + } + + void doCheckMovement() + { + if (distance_flown != 0 || fall_counter != fall_delay) + return; + + auto engine = find_engine(origin_pos); + if (!engine || !engine->hasTarget()) + return; + + auto bld0 = df::building::find(engine->id); + auto bld = strict_virtual_cast(bld0); + if (!bld) + return; + + AimContext context(bld, engine); + + aimAtArea(context); + } + + DEFINE_VMETHOD_INTERPOSE(bool, checkMovement, ()) + { + if (flags.bits.high_flying || flags.bits.piercing) + doCheckMovement(); + + return INTERPOSE_NEXT(checkMovement)(); + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE(projectile_hook, checkMovement); + /* * Initialization */ @@ -428,8 +610,12 @@ static void enable_hooks(bool enable) { is_enabled = enable; + INTERPOSE_HOOK(projectile_hook, checkMovement).apply(enable); + if (enable) load_engines(); + else + clear_engines(); } static bool enable_plugin()