diff options
| author | Alexander Gavrilov | 2012-09-17 14:59:59 +0400 |
|---|---|---|
| committer | Alexander Gavrilov | 2012-09-17 14:59:59 +0400 |
| commit | 82e870c8ddc9b64370c3828d1d4807306ac72887 (patch) | |
| tree | 25477841b4e451aba3f2f52a4d6010befdf76b6e /plugins/devel | |
| parent | f2fde21b10c087fd95948a5f40ef972ac6688718 (diff) | |
| download | dfhack-82e870c8ddc9b64370c3828d1d4807306ac72887.tar.gz dfhack-82e870c8ddc9b64370c3828d1d4807306ac72887.tar.bz2 dfhack-82e870c8ddc9b64370c3828d1d4807306ac72887.tar.xz | |
Move siege engine out of devel.
Diffstat (limited to 'plugins/devel')
| -rw-r--r-- | plugins/devel/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | plugins/devel/siege-engine.cpp | 1865 |
2 files changed, 0 insertions, 1866 deletions
diff --git a/plugins/devel/CMakeLists.txt b/plugins/devel/CMakeLists.txt index 39e8f7b6..134d5cb6 100644 --- a/plugins/devel/CMakeLists.txt +++ b/plugins/devel/CMakeLists.txt @@ -18,7 +18,6 @@ DFHACK_PLUGIN(stripcaged stripcaged.cpp) DFHACK_PLUGIN(rprobe rprobe.cpp) DFHACK_PLUGIN(nestboxes nestboxes.cpp) DFHACK_PLUGIN(vshook vshook.cpp) -DFHACK_PLUGIN(siege-engine siege-engine.cpp LINK_LIBRARIES lua) IF(UNIX) DFHACK_PLUGIN(ref-index ref-index.cpp) ENDIF() diff --git a/plugins/devel/siege-engine.cpp b/plugins/devel/siege-engine.cpp deleted file mode 100644 index 3b95aba3..00000000 --- a/plugins/devel/siege-engine.cpp +++ /dev/null @@ -1,1865 +0,0 @@ -#include "Core.h" -#include <Console.h> -#include <Export.h> -#include <Error.h> -#include <PluginManager.h> -#include <modules/Gui.h> -#include <modules/Screen.h> -#include <modules/Maps.h> -#include <modules/MapCache.h> -#include <modules/World.h> -#include <modules/Units.h> -#include <modules/Job.h> -#include <modules/Materials.h> -#include <LuaTools.h> -#include <TileTypes.h> -#include <vector> -#include <cstdio> -#include <stack> -#include <string> -#include <cmath> -#include <string.h> - -#include <VTableInterpose.h> -#include "df/graphic.h" -#include "df/building_siegeenginest.h" -#include "df/builtin_mats.h" -#include "df/world.h" -#include "df/buildings_other_id.h" -#include "df/job.h" -#include "df/building_drawbuffer.h" -#include "df/ui.h" -#include "df/viewscreen_dwarfmodest.h" -#include "df/ui_build_selector.h" -#include "df/flow_info.h" -#include "df/report.h" -#include "df/proj_itemst.h" -#include "df/unit.h" -#include "df/unit_soul.h" -#include "df/unit_skill.h" -#include "df/physical_attribute_type.h" -#include "df/creature_raw.h" -#include "df/caste_raw.h" -#include "df/caste_raw_flags.h" -#include "df/assumed_identity.h" -#include "df/game_mode.h" -#include "df/unit_misc_trait.h" -#include "df/job.h" -#include "df/job_item.h" -#include "df/item_actual.h" -#include "df/items_other_id.h" -#include "df/building_stockpilest.h" -#include "df/stockpile_links.h" -#include "df/workshop_profile.h" -#include "df/strain_type.h" -#include "df/material.h" -#include "df/flow_type.h" - -#include "MiscUtils.h" - -using std::vector; -using std::string; -using std::stack; -using namespace DFHack; -using namespace df::enums; - -using df::global::gamemode; -using df::global::gps; -using df::global::world; -using df::global::ui; -using df::global::ui_build_selector; - -using Screen::Pen; - -DFHACK_PLUGIN("siege-engine"); - -/* - * Misc. utils - */ - -typedef std::pair<df::coord, df::coord> coord_range; - -static void set_range(coord_range *target, df::coord p1, df::coord p2) -{ - if (!p1.isValid() || !p2.isValid()) - { - *target = coord_range(); - } - else - { - target->first.x = std::min(p1.x, p2.x); - target->first.y = std::min(p1.y, p2.y); - target->first.z = std::min(p1.z, p2.z); - target->second.x = std::max(p1.x, p2.x); - target->second.y = std::max(p1.y, p2.y); - target->second.z = std::max(p1.z, p2.z); - } -} - -static bool is_range_valid(const coord_range &target) -{ - return target.first.isValid() && target.second.isValid(); -} - -static bool is_in_range(const coord_range &target, df::coord pos) -{ - return target.first.isValid() && target.second.isValid() && - target.first.x <= pos.x && pos.x <= target.second.x && - target.first.y <= pos.y && pos.y <= target.second.y && - target.first.z <= pos.z && pos.z <= target.second.z; -} - -static std::pair<int, int> get_engine_range(df::building_siegeenginest *bld) -{ - if (bld->type == siegeengine_type::Ballista) - return std::make_pair(1, 200); - else - return std::make_pair(30, 100); -} - -static void orient_engine(df::building_siegeenginest *bld, df::coord target) -{ - int dx = target.x - bld->centerx; - int dy = target.y - bld->centery; - - if (abs(dx) > abs(dy)) - bld->facing = (dx > 0) ? - df::building_siegeenginest::Right : - df::building_siegeenginest::Left; - else - bld->facing = (dy > 0) ? - df::building_siegeenginest::Down : - df::building_siegeenginest::Up; -} - -static int random_int(int val) -{ - return int(int64_t(rand())*val/RAND_MAX); -} - -static int point_distance(df::coord speed) -{ - return std::max(abs(speed.x), std::max(abs(speed.y), abs(speed.z))); -} - -inline void normalize(float &x, float &y, float &z) -{ - float dist = sqrtf(x*x + y*y + z*z); - if (dist == 0.0f) return; - x /= dist; y /= dist; z /= dist; -} - -static void random_direction(float &x, float &y, float &z) -{ - float a, b, d; - for (;;) { - a = (rand() + 0.5f)*2.0f/RAND_MAX - 1.0f; - b = (rand() + 0.5f)*2.0f/RAND_MAX - 1.0f; - d = a*a + b*b; - if (d < 1.0f) - break; - } - - float sq = sqrtf(1-d); - x = 2.0f*a*sq; - y = 2.0f*b*sq; - z = 1.0f - 2.0f*d; -} - -static const int WEAR_TICKS = 806400; - -static bool apply_impact_damage(df::item *item, int minv, int maxv) -{ - MaterialInfo info(item); - if (!info.isValid()) - { - item->setWear(3); - return false; - } - - auto &strength = info.material->strength; - - // Use random strain type excluding COMPRESSIVE (conveniently last) - int type = random_int(strain_type::COMPRESSIVE); - int power = minv + random_int(maxv-minv+1); - - // High elasticity materials just bend - if (strength.strain_at_yield[type] >= 5000) - return true; - - // Instant fracture? - int fracture = strength.fracture[type]; - if (fracture <= power) - { - item->setWear(3); - return false; - } - - // Impact within elastic strain range? - int yield = strength.yield[type]; - if (yield > power) - return true; - - // Can wear? - auto actual = virtual_cast<df::item_actual>(item); - if (!actual) - return false; - - // Transform plastic deformation to wear - int max_wear = WEAR_TICKS * 4; - int cur_wear = WEAR_TICKS * actual->wear + actual->wear_timer; - cur_wear += int64_t(power - yield)*max_wear/(fracture - yield); - - if (cur_wear >= max_wear) - { - actual->wear = 3; - return false; - } - else - { - actual->wear = cur_wear / WEAR_TICKS; - actual->wear_timer = cur_wear % WEAR_TICKS; - return true; - } -} - -/* - * Configuration object - */ - -static bool enable_plugin(); - -struct EngineInfo { - int id; - df::building_siegeenginest *bld; - - df::coord center; - coord_range building_rect; - - bool is_catapult; - int proj_speed, hit_delay; - std::pair<int, int> fire_range; - - coord_range target; - - df::job_item_vector_id ammo_vector_id; - df::item_type ammo_item_type; - - int operator_id, operator_frame; - - std::set<int> stockpiles; - df::stockpile_links links; - df::workshop_profile profile; - - 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<df::building*, EngineInfo*> engines; -static std::map<df::coord, df::building*> coord_engines; - -static EngineInfo *find_engine(df::building *bld, bool create = false) -{ - auto ebld = strict_virtual_cast<df::building_siegeenginest>(bld); - if (!ebld) - return NULL; - - auto &obj = engines[bld]; - - if (obj) - { - obj->bld = ebld; - return obj; - } - - if (!create) - return NULL; - - obj = new EngineInfo(); - - 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 = obj->is_catapult ? 2 : -1; - obj->fire_range = get_engine_range(ebld); - - obj->ammo_vector_id = job_item_vector_id::BOULDER; - obj->ammo_item_type = item_type::BOULDER; - - obj->operator_id = obj->operator_frame = -1; - - coord_engines[obj->center] = bld; - return obj; -} - -static EngineInfo *find_engine(lua_State *L, int idx, bool create = false, bool silent = false) -{ - auto bld = Lua::CheckDFObject<df::building_siegeenginest>(L, idx); - - auto engine = find_engine(bld, create); - if (!engine && !silent) - luaL_error(L, "no such engine"); - - return engine; -} - -static EngineInfo *find_engine(df::coord pos) -{ - auto engine = find_engine(coord_engines[pos]); - - if (engine) - { - auto bld0 = df::building::find(engine->id); - auto bld = strict_virtual_cast<df::building_siegeenginest>(bld0); - if (!bld) - return NULL; - - engine->bld = bld; - } - - return engine; -} - -/* - * Configuration management - */ - -static void clear_engines() -{ - for (auto it = engines.begin(); it != engines.end(); ++it) - delete it->second; - engines.clear(); - coord_engines.clear(); -} - -static void load_engines() -{ - clear_engines(); - - auto pworld = Core::getInstance().getWorld(); - std::vector<PersistentDataItem> vec; - - pworld->GetPersistentData(&vec, "siege-engine/target/", true); - for (auto it = vec.begin(); it != vec.end(); ++it) - { - auto engine = find_engine(df::building::find(it->ival(0)), true); - if (!engine) continue; - engine->target.first = df::coord(it->ival(1), it->ival(2), it->ival(3)); - engine->target.second = df::coord(it->ival(4), it->ival(5), it->ival(6)); - } - - pworld->GetPersistentData(&vec, "siege-engine/ammo/", true); - for (auto it = vec.begin(); it != vec.end(); ++it) - { - auto engine = find_engine(df::building::find(it->ival(0)), true); - if (!engine) continue; - engine->ammo_vector_id = (df::job_item_vector_id)it->ival(1); - engine->ammo_item_type = (df::item_type)it->ival(2); - } - - pworld->GetPersistentData(&vec, "siege-engine/stockpiles/", true); - for (auto it = vec.begin(); it != vec.end(); ++it) - { - auto engine = find_engine(df::building::find(it->ival(0)), true); - if (!engine) - continue; - auto pile = df::building::find(it->ival(1)); - if (!pile || pile->getType() != building_type::Stockpile) - { - pworld->DeletePersistentData(*it); - continue;; - } - - engine->stockpiles.insert(it->ival(1)); - } - - pworld->GetPersistentData(&vec, "siege-engine/profiles/", true); - for (auto it = vec.begin(); it != vec.end(); ++it) - { - auto engine = find_engine(df::building::find(it->ival(0)), true); - if (!engine) continue; - engine->profile.min_level = it->ival(1); - engine->profile.max_level = it->ival(2); - } - - pworld->GetPersistentData(&vec, "siege-engine/profile-workers/", true); - for (auto it = vec.begin(); it != vec.end(); ++it) - { - auto engine = find_engine(df::building::find(it->ival(0)), true); - if (!engine) - continue; - auto unit = df::unit::find(it->ival(1)); - if (!unit || !Units::isCitizen(unit)) - { - pworld->DeletePersistentData(*it); - continue; - } - engine->profile.permitted_workers.push_back(it->ival(1)); - } -} - -static int getTargetArea(lua_State *L) -{ - auto engine = find_engine(L, 1, false, true); - - if (engine && engine->hasTarget()) - { - Lua::Push(L, engine->target.first); - Lua::Push(L, engine->target.second); - } - else - { - lua_pushnil(L); - lua_pushnil(L); - } - - return 2; -} - -static void clearTargetArea(df::building_siegeenginest *bld) -{ - CHECK_NULL_POINTER(bld); - - if (auto engine = find_engine(bld)) - engine->target = coord_range(); - - auto pworld = Core::getInstance().getWorld(); - auto key = stl_sprintf("siege-engine/target/%d", bld->id); - pworld->DeletePersistentData(pworld->GetPersistentData(key)); -} - -static bool setTargetArea(df::building_siegeenginest *bld, df::coord target_min, df::coord target_max) -{ - CHECK_NULL_POINTER(bld); - CHECK_INVALID_ARGUMENT(target_min.isValid() && target_max.isValid()); - - if (!enable_plugin()) - return false; - - auto pworld = Core::getInstance().getWorld(); - auto key = stl_sprintf("siege-engine/target/%d", bld->id); - auto entry = pworld->GetPersistentData(key, NULL); - if (!entry.isValid()) - return false; - - auto engine = find_engine(bld, true); - - set_range(&engine->target, target_min, target_max); - - entry.ival(0) = bld->id; - entry.ival(1) = engine->target.first.x; - entry.ival(2) = engine->target.first.y; - entry.ival(3) = engine->target.first.z; - entry.ival(4) = engine->target.second.x; - entry.ival(5) = engine->target.second.y; - entry.ival(6) = engine->target.second.z; - - df::coord sum = target_min + target_max; - orient_engine(bld, df::coord(sum.x/2, sum.y/2, sum.z/2)); - - return true; -} - -static int getAmmoItem(lua_State *L) -{ - auto engine = find_engine(L, 1, false, true); - if (!engine) - Lua::Push(L, item_type::BOULDER); - else - Lua::Push(L, engine->ammo_item_type); - return 1; -} - -static int setAmmoItem(lua_State *L) -{ - if (!enable_plugin()) - return 0; - - auto engine = find_engine(L, 1, true); - auto item_type = (df::item_type)luaL_optint(L, 2, item_type::BOULDER); - if (!is_valid_enum_item(item_type)) - luaL_argerror(L, 2, "invalid item type"); - - auto pworld = Core::getInstance().getWorld(); - auto key = stl_sprintf("siege-engine/ammo/%d", engine->id); - auto entry = pworld->GetPersistentData(key, NULL); - if (!entry.isValid()) - return 0; - - engine->ammo_vector_id = job_item_vector_id::ANY_FREE; - engine->ammo_item_type = item_type; - - FOR_ENUM_ITEMS(job_item_vector_id, id) - { - auto other = ENUM_ATTR(job_item_vector_id, other, id); - auto type = ENUM_ATTR(items_other_id, item, other); - if (type == item_type) - { - engine->ammo_vector_id = id; - break; - } - } - - entry.ival(0) = engine->id; - entry.ival(1) = engine->ammo_vector_id; - entry.ival(2) = engine->ammo_item_type; - - lua_pushboolean(L, true); - return 1; -} - -static void forgetStockpileLink(EngineInfo *engine, int pile_id) -{ - engine->stockpiles.erase(pile_id); - - auto pworld = Core::getInstance().getWorld(); - auto key = stl_sprintf("siege-engine/stockpiles/%d/%d", engine->id, pile_id); - pworld->DeletePersistentData(pworld->GetPersistentData(key)); -} - -static void update_stockpile_links(EngineInfo *engine) -{ - engine->links.take_from_pile.clear(); - - for (auto it = engine->stockpiles.begin(); it != engine->stockpiles.end(); ) - { - int id = *it; ++it; - auto pile = df::building::find(id); - - if (!pile || pile->getType() != building_type::Stockpile) - forgetStockpileLink(engine, id); - else - // The vector is sorted, but we are iterating through a sorted set - engine->links.take_from_pile.push_back(pile); - } -} - -static int getStockpileLinks(lua_State *L) -{ - auto engine = find_engine(L, 1, false, true); - if (!engine || engine->stockpiles.empty()) - return 0; - - update_stockpile_links(engine); - - auto &links = engine->links.take_from_pile; - lua_createtable(L, links.size(), 0); - - for (size_t i = 0; i < links.size(); i++) - { - Lua::Push(L, links[i]); - lua_rawseti(L, -2, i+1); - } - - return 1; -} - -static bool isLinkedToPile(df::building_siegeenginest *bld, df::building_stockpilest *pile) -{ - CHECK_NULL_POINTER(bld); - CHECK_NULL_POINTER(pile); - - auto engine = find_engine(bld); - - return engine && engine->stockpiles.count(pile->id); -} - -static bool addStockpileLink(df::building_siegeenginest *bld, df::building_stockpilest *pile) -{ - CHECK_NULL_POINTER(bld); - CHECK_NULL_POINTER(pile); - - if (!enable_plugin()) - return false; - - auto pworld = Core::getInstance().getWorld(); - auto key = stl_sprintf("siege-engine/stockpiles/%d/%d", bld->id, pile->id); - auto entry = pworld->GetPersistentData(key, NULL); - if (!entry.isValid()) - return false; - - auto engine = find_engine(bld, true); - - entry.ival(0) = bld->id; - entry.ival(1) = pile->id; - - engine->stockpiles.insert(pile->id); - return true; -} - -static bool removeStockpileLink(df::building_siegeenginest *bld, df::building_stockpilest *pile) -{ - CHECK_NULL_POINTER(bld); - CHECK_NULL_POINTER(pile); - - if (auto engine = find_engine(bld)) - { - forgetStockpileLink(engine, pile->id); - return true; - } - - return false; -} - -static df::workshop_profile *saveWorkshopProfile(df::building_siegeenginest *bld) -{ - CHECK_NULL_POINTER(bld); - - if (!enable_plugin()) - return NULL; - - // Save skill limits - auto pworld = Core::getInstance().getWorld(); - auto key = stl_sprintf("siege-engine/profiles/%d", bld->id); - auto entry = pworld->GetPersistentData(key, NULL); - if (!entry.isValid()) - return NULL; - - auto engine = find_engine(bld, true); - - entry.ival(0) = engine->id; - entry.ival(1) = engine->profile.min_level; - entry.ival(2) = engine->profile.max_level; - - // Save worker list - std::vector<PersistentDataItem> vec; - auto &workers = engine->profile.permitted_workers; - - key = stl_sprintf("siege-engine/profile-workers/%d", bld->id); - pworld->GetPersistentData(&vec, key, true); - - for (auto it = vec.begin(); it != vec.end(); ++it) - { - if (linear_index(workers, it->ival(1)) < 0) - pworld->DeletePersistentData(*it); - } - - for (size_t i = 0; i < workers.size(); i++) - { - key = stl_sprintf("siege-engine/profile-workers/%d/%d", bld->id, workers[i]); - entry = pworld->GetPersistentData(key, NULL); - if (!entry.isValid()) - continue; - entry.ival(0) = engine->id; - entry.ival(1) = workers[i]; - } - - return &engine->profile; -} - -static int getOperatorSkill(df::building_siegeenginest *bld, bool force = false) -{ - CHECK_NULL_POINTER(bld); - - auto engine = find_engine(bld); - if (!engine) - return 0; - - if (engine->operator_id != -1 && - (world->frame_counter - engine->operator_frame) <= 5) - { - auto op_unit = df::unit::find(engine->operator_id); - if (op_unit) - return Units::getEffectiveSkill(op_unit, job_skill::SIEGEOPERATE); - } - - if (force) - { - color_ostream_proxy out(Core::getInstance().getConsole()); - out.print("Forced siege operator search\n"); - - 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 raytracing - */ - -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), fudge_factor(1) - { - fudge_delta = df::coord(0,0,0); - calc_line(); - } - - 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_delta = df::coord(0,0,int(factor * zdelta)); - calc_line(); - } - - void calc_line() - { - 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); - } - - df::coord operator[] (int i) const - { - int div2 = divisor * 2; - int bias = divisor-1; - return origin + df::coord( - (2*speed.x*i + direction.x*bias)/div2, - (2*speed.y*i + direction.y*bias)/div2, - (2*speed.z*i + direction.z*bias)/div2 - ); - } -}; - -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, - Tree - } hit_type; - - int collision_step, collision_z_step; - int goal_step, goal_z_step, goal_distance; - - bool hits() const { return collision_step > goal_step; } - - PathMetrics(const ProjectilePath &path) - { - compute(path); - } - - void compute(const ProjectilePath &path) - { - 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; - - for (;;) { - df::coord cur_pos = path[++step]; - if (cur_pos == prev_pos) - break; - - if (cur_pos.z == path.goal.z) - { - goal_z_step = std::min(step, goal_z_step); - if (cur_pos == path.goal) - goal_step = step; - } - - if (!Maps::isValidTilePos(cur_pos)) - { - hit_type = PathMetrics::MapEdge; - break; - } - - if (!isPassableTile(cur_pos)) - { - 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) - { - int top_z = std::max(prev_pos.z, cur_pos.z); - auto ptile = Maps::getTileType(cur_pos.x, cur_pos.y, top_z); - - if (ptile && !LowPassable(*ptile)) - { - hit_type = (cur_pos.z > prev_pos.z ? Ceiling : Floor); - break; - } - - collision_z_step = step; - } - - prev_pos = cur_pos; - } - - collision_step = step; - } -}; - -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" -}; - -static TargetTileStatus calcTileStatus(EngineInfo *engine, const PathMetrics &raytrace) -{ - if (raytrace.hits()) - { - if (engine->isInRange(raytrace.goal_step)) - return TARGET_OK; - else - return TARGET_RANGE; - } - else - return TARGET_BLOCKED; -} - -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; -} - -static TargetTileStatus calcTileStatus(EngineInfo *engine, df::coord target, float zdelta) -{ - ProjectilePath path(engine->center, target, zdelta); - PathMetrics raytrace(path); - return calcTileStatus(engine, raytrace); -} - -static TargetTileStatus calcTileStatus(EngineInfo *engine, df::coord target) -{ - auto status = calcTileStatus(engine, target, 0.0f); - - if (status == TARGET_BLOCKED) - { - if (calcTileStatus(engine, target, 0.5f) < TARGET_BLOCKED) - return TARGET_SEMIBLOCKED; - - 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) -{ - auto engine = find_engine(bld, true); - if (!engine) - return "invalid"; - - 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) -{ - 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(engine->building_rect, tile_pos)) - continue; - - Pen cur_tile = Screen::readTile(ltop.x+x, ltop.y+y); - if (!cur_tile.valid()) - continue; - - int color; - - switch (calcTileStatus(engine, tile_pos)) - { - case TARGET_OK: - color = COLOR_GREEN; - break; - case TARGET_RANGE: - color = COLOR_CYAN; - break; - case TARGET_BLOCKED: - color = COLOR_RED; - break; - case TARGET_SEMIBLOCKED: - color = COLOR_BROWN; - break; - } - - if (cur_tile.fg && cur_tile.ch != ' ') - { - cur_tile.fg = color; - cur_tile.bg = 0; - } - else - { - cur_tile.fg = 0; - cur_tile.bg = color; - } - - cur_tile.bold = engine->onTarget(tile_pos); - - if (cur_tile.tile) - cur_tile.tile_mode = Pen::CharColor; - - Screen::paintTile(cur_tile, ltop.x+x, ltop.y+y); - } - } -} - -/* - * Unit tracking - */ - -static const float MAX_TIME = 1000000.0f; - -struct UnitPath { - df::unit *unit; - std::map<float, df::coord> path; - - struct Hit { - UnitPath *path; - df::coord pos; - int dist; - float time, lmargin, rmargin; - }; - - static std::map<df::unit*, UnitPath*> cache; - - static UnitPath *get(df::unit *unit) - { - auto &cv = cache[unit]; - if (!cv) cv = new UnitPath(unit); - return cv; - }; - - 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; - - if (dest.isValid() && !upath.x.empty()) - { - 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]; - if (new_pos == pos) - continue; - - float delay = speed; - if (new_pos.x != pos.x && new_pos.y != pos.y) - delay *= 362.0/256.0; - - path[time] = pos; - pos = new_pos; - time += delay + 1; - } - } - - path[MAX_TIME] = pos; - } - - void get_margin(std::map<float,df::coord>::iterator &it, float time, float *lmargin, float *rmargin) - { - auto it2 = it; - *lmargin = (it == path.begin()) ? MAX_TIME : time - (--it2)->first; - *rmargin = (it->first == MAX_TIME) ? MAX_TIME : it->first - time; - } - - df::coord posAtTime(float time, float *lmargin = NULL, float *rmargin = NULL) - { - CHECK_INVALID_ARGUMENT(time < MAX_TIME); - - auto it = path.upper_bound(time); - if (lmargin) - get_margin(it, time, lmargin, rmargin); - return it->second; - } - - bool findHits(EngineInfo *engine, std::vector<Hit> *hit_points, float bias) - { - df::coord origin = engine->center; - - Hit info; - info.path = this; - - for (auto it = path.begin(); it != path.end(); ++it) - { - info.pos = it->second; - info.dist = point_distance(origin - info.pos); - info.time = float(info.dist)*(engine->proj_speed+1) + engine->hit_delay + bias; - get_margin(it, info.time, &info.lmargin, &info.rmargin); - - if (info.lmargin > 0 && info.rmargin > 0) - { - if (engine->onTarget(info.pos) && engine->isInRange(info.dist)) - hit_points->push_back(info); - } - } - - return !hit_points->empty(); - } -}; - -std::map<df::unit*, UnitPath*> UnitPath::cache; - -static void push_margin(lua_State *L, float margin) -{ - if (margin == MAX_TIME) - lua_pushnil(L); - else - lua_pushnumber(L, margin); -} - -static int traceUnitPath(lua_State *L) -{ - auto unit = Lua::CheckDFObject<df::unit>(L, 1); - - CHECK_NULL_POINTER(unit); - - size_t idx = 1; - auto info = UnitPath::get(unit); - lua_createtable(L, info->path.size(), 0); - - float last_time = 0.0f; - for (auto it = info->path.begin(); it != info->path.end(); ++it) - { - Lua::Push(L, it->second); - if (idx > 1) - { - lua_pushnumber(L, last_time); - lua_setfield(L, -2, "from"); - } - if (idx < info->path.size()) - { - lua_pushnumber(L, it->first); - lua_setfield(L, -2, "to"); - } - lua_rawseti(L, -2, idx++); - last_time = it->first; - } - - return 1; -} - -static int unitPosAtTime(lua_State *L) -{ - auto unit = Lua::CheckDFObject<df::unit>(L, 1); - float time = luaL_checknumber(L, 2); - - CHECK_NULL_POINTER(unit); - - float lmargin, rmargin; - auto info = UnitPath::get(unit); - - Lua::Push(L, info->posAtTime(time, &lmargin, &rmargin)); - push_margin(L, lmargin); - push_margin(L, rmargin); - 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<UnitPath::Hit> *hits, float bias) -{ - auto &active = world->units.active; - - for (size_t i = 0; i < active.size(); i++) - { - auto unit = active[i]; - - if (!canTargetUnit(unit)) - continue; - - UnitPath::get(unit)->findHits(engine, hits, bias); - } -} - -static int proposeUnitHits(lua_State *L) -{ - auto engine = find_engine(L, 1); - float bias = luaL_optnumber(L, 2, 0); - - if (!engine->hasTarget()) - luaL_error(L, "target not set"); - - std::vector<UnitPath::Hit> hits; - proposeUnitHits(engine, &hits, bias); - - lua_createtable(L, hits.size(), 0); - - for (size_t i = 0; i < hits.size(); i++) - { - auto &hit = hits[i]; - lua_createtable(L, 0, 6); - 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); - } - - 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 - */ - -static const int offsets[8][2] = { - { -1, -1 }, { 0, -1 }, { 1, -1 }, - { -1, 0 }, { 1, 0 }, - { -1, 1 }, { 0, 1 }, { 1, 1 } -}; - -struct projectile_hook : df::proj_itemst { - typedef df::proj_itemst interpose_base; - - void aimAtPoint(EngineInfo *engine, const ProjectilePath &path) - { - target_pos = path.target; - - // Debug - Maps::getTileOccupancy(path.goal)->bits.arrow_color = COLOR_LIGHTMAGENTA; - - 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]); - - // Find valid hit point for catapult stones - if (flags.bits.high_flying) - { - 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, engine->fire_range.first); - fall_threshold = std::min(fall_threshold, engine->fire_range.second); - } - - void aimAtPoint(EngineInfo *engine, int skill, const ProjectilePath &path) - { - df::coord fail_target = path.goal; - - orient_engine(engine->bld, path.goal); - - // Debug - Maps::getTileOccupancy(path.goal)->bits.arrow_color = COLOR_LIGHTRED; - - // Dabbling always hit in 7x7 area - if (skill < skill_rating::Novice) - { - fail_target.x += random_int(7)-3; - fail_target.y += random_int(7)-3; - aimAtPoint(engine, ProjectilePath(path.origin, fail_target)); - return; - } - - // Exact hit chance - float hit_chance = 1.04f - powf(0.8f, skill); - - if (float(rand())/RAND_MAX < hit_chance) - { - aimAtPoint(engine, path); - return; - } - - // Otherwise perturb - if (skill <= skill_rating::Proficient) - { - // 5x5 - fail_target.x += random_int(5)-2; - fail_target.y += random_int(5)-2; - } - else - { - // 3x3 - int idx = random_int(8); - fail_target.x += offsets[idx][0]; - fail_target.y += offsets[idx][1]; - } - - ProjectilePath fail(path.origin, fail_target, path.fudge_delta, path.fudge_factor); - aimAtPoint(engine, fail); - } - - void aimAtArea(EngineInfo *engine, int skill) - { - df::coord target, last_passable; - df::coord tbase = engine->target.first; - df::coord tsize = 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 (adjustToTarget(engine, &target)) - last_passable = target; - else - continue; - - ProjectilePath path(engine->center, target, engine->is_catapult ? 0.5f : 0.0f); - PathMetrics raytrace(path); - - if (raytrace.hits() && engine->isInRange(raytrace.goal_step)) - { - aimAtPoint(engine, skill, path); - return; - } - } - - if (!last_passable.isValid()) - last_passable = target; - - aimAtPoint(engine, skill, 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); - int skill = lua_tointeger(L, 3); - - 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, proj->item); - Lua::Push(L, engine->target.first); - Lua::Push(L, engine->target.second); - Lua::Push(L, skill); - - lua_call(L, 5, 1); - - if (lua_isnil(L, -1)) - proj->aimAtArea(engine, skill); - else - proj->aimAtPoint(engine, skill, decode_path(L, -1, engine->center)); - - return 0; - } - - void doCheckMovement() - { - if (flags.bits.parabolic || distance_flown != 0 || - fall_counter != fall_delay || item == NULL) - return; - - auto engine = find_engine(origin_pos); - if (!engine || !engine->hasTarget()) - return; - - auto L = Lua::Core::State; - CoreSuspendClaimer suspend; - color_ostream_proxy out(Core::getInstance().getConsole()); - - int skill = getOperatorSkill(engine->bld, true); - - // Dabbling can't aim - if (skill < skill_rating::Novice) - aimAtArea(engine, skill); - else - { - lua_pushcfunction(L, safeAimProjectile); - lua_pushlightuserdata(L, this); - lua_pushlightuserdata(L, engine); - lua_pushinteger(L, skill); - - if (!Lua::Core::SafeCall(out, 3, 0)) - aimAtArea(engine, skill); - } - - switch (item->getType()) - { - case item_type::CAGE: - flags.bits.bouncing = false; - break; - case item_type::BIN: - case item_type::BARREL: - flags.bits.bouncing = false; - break; - default: - break; - } - } - - void doLaunchContents() - { - // Translate cartoon flight speed to parabolic - float speed = 100000.0f / (fall_delay + 1); - int min_zspeed = (fall_delay+1)*4900; - - float bonus = 1.0f + 0.1f*(origin_pos.z -cur_pos.z); - bonus *= 1.0f + (distance_flown - 60) / 200.0f; - speed *= bonus; - - // Flight direction vector - df::coord dist = target_pos - origin_pos; - float vx = dist.x, vy = dist.y, vz = fabs((float)dist.z); - normalize(vx, vy, vz); - - int start_z = 0; - - // Start at tile top, if hit a wall - ProjectilePath path(origin_pos, target_pos); - auto next_pos = path[distance_flown+1]; - if (next_pos.z == cur_pos.z && !isPassableTile(next_pos)) - start_z = 49000; - - MapExtras::MapCache mc; - std::vector<df::item*> contents; - Items::getContainedItems(item, &contents); - - for (size_t i = 0; i < contents.size(); i++) - { - auto child = contents[i]; - - // Liquids are vaporized so that they cover nearby units - if (child->isLiquid()) - { - auto flow = Maps::spawnFlow( - cur_pos, - flow_type::MaterialVapor, - child->getMaterial(), child->getMaterialIndex(), - 100 - ); - - // should it leave a puddle too?.. - if (flow && Items::remove(mc, child)) - continue; - } - - auto proj = Items::makeProjectile(mc, child); - if (!proj) continue; - - bool keep = apply_impact_damage(child, 50000, int(250000*bonus)); - - proj->flags.bits.no_impact_destroy = keep; - //proj->flags.bits.bouncing = true; - proj->flags.bits.piercing = true; - proj->flags.bits.parabolic = true; - proj->flags.bits.unk9 = true; - proj->flags.bits.no_collide = true; - - proj->pos_z = start_z; - - float sx, sy, sz; - random_direction(sx, sy, sz); - sx += vx*0.7; sy += vy*0.7; sz += vz*0.7; - if (sz < 0) sz = -sz; - normalize(sx, sy, sz); - - proj->speed_x = int(speed * sx); - proj->speed_y = int(speed * sy); - proj->speed_z = std::max(min_zspeed, int(speed * sz)); - } - } - - DEFINE_VMETHOD_INTERPOSE(bool, checkMovement, ()) - { - if (flags.bits.high_flying || flags.bits.piercing) - doCheckMovement(); - - return INTERPOSE_NEXT(checkMovement)(); - } - - DEFINE_VMETHOD_INTERPOSE(bool, checkImpact, (bool no_damage_floor)) - { - if (!flags.bits.has_hit_ground && !flags.bits.parabolic && - flags.bits.high_flying && !flags.bits.bouncing && - !flags.bits.no_impact_destroy && target_pos != origin_pos && - item && item->flags.bits.container) - { - doLaunchContents(); - } - - return INTERPOSE_NEXT(checkImpact)(no_damage_floor); - } -}; - -IMPLEMENT_VMETHOD_INTERPOSE(projectile_hook, checkMovement); -IMPLEMENT_VMETHOD_INTERPOSE(projectile_hook, checkImpact); - -/* - * Building hook - */ - -struct building_hook : df::building_siegeenginest { - typedef df::building_siegeenginest interpose_base; - - DEFINE_VMETHOD_INTERPOSE(df::workshop_profile*, getWorkshopProfile, ()) - { - if (auto engine = find_engine(this)) - return &engine->profile; - - return INTERPOSE_NEXT(getWorkshopProfile)(); - } - - DEFINE_VMETHOD_INTERPOSE(df::stockpile_links*, getStockpileLinks, ()) - { - if (auto engine = find_engine(this)) - { - update_stockpile_links(engine); - return &engine->links; - } - - return INTERPOSE_NEXT(getStockpileLinks)(); - } - - DEFINE_VMETHOD_INTERPOSE(void, updateAction, ()) - { - INTERPOSE_NEXT(updateAction)(); - - if (jobs.empty()) - return; - - if (auto engine = find_engine(this)) - { - auto job = jobs[0]; - bool save_op = false; - - switch (job->job_type) - { - case job_type::LoadCatapult: - if (!job->job_items.empty()) - { - auto item = job->job_items[0]; - item->item_type = engine->ammo_item_type; - item->vector_id = engine->ammo_vector_id; - - switch (item->item_type) - { - case item_type::NONE: - case item_type::BOULDER: - case item_type::BLOCKS: - item->mat_type = 0; - break; - - case item_type::BIN: - case item_type::BARREL: - item->mat_type = -1; - // A hack to make it take objects assigned to stockpiles. - // Since reaction_id is not set, the actual value is not used. - item->contains.resize(1); - break; - - default: - item->mat_type = -1; - break; - } - } - // fallthrough - - case job_type::LoadBallista: - case job_type::FireCatapult: - case job_type::FireBallista: - if (auto worker = Job::getWorker(job)) - { - engine->operator_id = worker->id; - engine->operator_frame = world->frame_counter; - } - break; - - default: - break; - } - } - } -}; - -IMPLEMENT_VMETHOD_INTERPOSE(building_hook, getWorkshopProfile); -IMPLEMENT_VMETHOD_INTERPOSE(building_hook, getStockpileLinks); -IMPLEMENT_VMETHOD_INTERPOSE(building_hook, updateAction); - -/* - * Initialization - */ - -DFHACK_PLUGIN_LUA_FUNCTIONS { - DFHACK_LUA_FUNCTION(clearTargetArea), - DFHACK_LUA_FUNCTION(setTargetArea), - DFHACK_LUA_FUNCTION(isLinkedToPile), - DFHACK_LUA_FUNCTION(addStockpileLink), - DFHACK_LUA_FUNCTION(removeStockpileLink), - DFHACK_LUA_FUNCTION(saveWorkshopProfile), - 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(getAmmoItem), - DFHACK_LUA_COMMAND(setAmmoItem), - DFHACK_LUA_COMMAND(getStockpileLinks), - DFHACK_LUA_COMMAND(projPosAtStep), - DFHACK_LUA_COMMAND(projPathMetrics), - DFHACK_LUA_COMMAND(adjustToTarget), - DFHACK_LUA_COMMAND(traceUnitPath), - DFHACK_LUA_COMMAND(unitPosAtTime), - DFHACK_LUA_COMMAND(proposeUnitHits), - DFHACK_LUA_COMMAND(computeNearbyWeight), - DFHACK_LUA_END -}; - -static bool is_enabled = false; - -static void enable_hooks(bool enable) -{ - is_enabled = enable; - - INTERPOSE_HOOK(projectile_hook, checkMovement).apply(enable); - INTERPOSE_HOOK(projectile_hook, checkImpact).apply(enable); - - INTERPOSE_HOOK(building_hook, getWorkshopProfile).apply(enable); - INTERPOSE_HOOK(building_hook, getStockpileLinks).apply(enable); - INTERPOSE_HOOK(building_hook, updateAction).apply(enable); - - if (enable) - load_engines(); - else - clear_engines(); -} - -static bool enable_plugin() -{ - if (is_enabled) - return true; - - auto pworld = Core::getInstance().getWorld(); - auto entry = pworld->GetPersistentData("siege-engine/enabled", NULL); - if (!entry.isValid()) - return false; - - enable_hooks(true); - return true; -} - -static void clear_caches(color_ostream &out) -{ - if (!UnitPath::cache.empty()) - { - for (auto it = UnitPath::cache.begin(); it != UnitPath::cache.end(); ++it) - delete it->second; - - UnitPath::cache.clear(); - } -} - -DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) -{ - switch (event) { - case SC_MAP_LOADED: - { - auto pworld = Core::getInstance().getWorld(); - bool enable = pworld->GetPersistentData("siege-engine/enabled").isValid(); - - if (enable) - { - out.print("Enabling the siege engine plugin.\n"); - enable_hooks(true); - } - else - enable_hooks(false); - } - break; - case SC_MAP_UNLOADED: - enable_hooks(false); - break; - default: - break; - } - - return CR_OK; -} - -DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands) -{ - if (Core::getInstance().isMapLoaded()) - plugin_onstatechange(out, SC_MAP_LOADED); - - return CR_OK; -} - -DFhackCExport command_result plugin_shutdown ( color_ostream &out ) -{ - enable_hooks(false); - return CR_OK; -} - -DFhackCExport command_result plugin_onupdate ( color_ostream &out ) -{ - clear_caches(out); - return CR_OK; -} |
