From b0938d7e0d80720ef1a6fd264418ba20c375d9ba Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Tue, 11 Sep 2012 22:46:17 +0400 Subject: [PATCH] Allow specifying arbitrary items to use in catapults. --- plugins/devel/siege-engine.cpp | 151 ++++++++++++++++++++++++++++++++- scripts/gui/siege-engine.lua | 54 ++++++++++-- 2 files changed, 195 insertions(+), 10 deletions(-) diff --git a/plugins/devel/siege-engine.cpp b/plugins/devel/siege-engine.cpp index ce835c6d4..2720c62f8 100644 --- a/plugins/devel/siege-engine.cpp +++ b/plugins/devel/siege-engine.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -41,6 +42,10 @@ #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.h" +#include "df/items_other_id.h" #include "MiscUtils.h" @@ -148,6 +153,11 @@ struct EngineInfo { coord_range target; + df::job_item_vector_id ammo_vector_id; + df::item_type ammo_item_type; + + int operator_id, operator_frame; + 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; } @@ -190,6 +200,11 @@ static EngineInfo *find_engine(df::building *bld, bool create = false) obj->hit_delay = 3; 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; } @@ -198,7 +213,7 @@ static EngineInfo *find_engine(lua_State *L, int idx, bool create = false) { auto bld = Lua::CheckDFObject(L, idx); - auto engine = find_engine(bld); + auto engine = find_engine(bld, create); if (!engine) luaL_error(L, "no such engine"); @@ -243,6 +258,15 @@ static void load_engines() 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); + } } static int getTargetArea(lua_State *L) @@ -309,6 +333,51 @@ static bool setTargetArea(df::building_siegeenginest *bld, df::coord target_min, return true; } +static int getAmmoItem(lua_State *L) +{ + auto engine = find_engine(L, 1, true); + Lua::Push(L, engine->ammo_item_type); + return 1; +} + +static int setAmmoItem(lua_State *L) +{ + 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"); + + if (!enable_plugin()) + return 0; + + 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 int getShotSkill(df::building_siegeenginest *bld) { CHECK_NULL_POINTER(bld); @@ -446,7 +515,7 @@ static bool adjustToTarget(EngineInfo *engine, df::coord *pos) return true; for (df::coord fudge = *pos; - fudge.z < engine->target.second.z; fudge.z++) + fudge.z <= engine->target.second.z; fudge.z++) { if (!isTargetableTile(fudge)) continue; @@ -455,7 +524,7 @@ static bool adjustToTarget(EngineInfo *engine, df::coord *pos) } for (df::coord fudge = *pos; - fudge.z > engine->target.first.z; fudge.z--) + fudge.z >= engine->target.first.z; fudge.z--) { if (!isTargetableTile(fudge)) continue; @@ -1046,6 +1115,79 @@ struct projectile_hook : df::proj_itemst { IMPLEMENT_VMETHOD_INTERPOSE(projectile_hook, checkMovement); +/* + * Building hook + */ + +struct building_hook : df::building_siegeenginest { + typedef df::building_siegeenginest interpose_base; + + DEFINE_VMETHOD_INTERPOSE(void, updateAction, ()) + { + INTERPOSE_NEXT(updateAction)(); + + if (jobs.empty()) + return; + + if (auto engine = find_engine(this)) + { + auto job = jobs[0]; + + 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; + } + } + break; + + case job_type::FireCatapult: + case job_type::FireBallista: + if (auto worker = Job::getWorker(job)) + { + color_ostream_proxy out(Core::getInstance().getConsole()); + out.print("operator %d\n", worker->id); + + engine->operator_id = worker->id; + engine->operator_frame = world->frame_counter; + } + else + engine->operator_id = -1; + break; + + default: + break; + } + } + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE(building_hook, updateAction); + /* * Initialization */ @@ -1064,6 +1206,8 @@ DFHACK_PLUGIN_LUA_FUNCTIONS { DFHACK_PLUGIN_LUA_COMMANDS { DFHACK_LUA_COMMAND(getTargetArea), + DFHACK_LUA_COMMAND(getAmmoItem), + DFHACK_LUA_COMMAND(setAmmoItem), DFHACK_LUA_COMMAND(projPosAtStep), DFHACK_LUA_COMMAND(projPathMetrics), DFHACK_LUA_COMMAND(adjustToTarget), @@ -1080,6 +1224,7 @@ static void enable_hooks(bool enable) is_enabled = enable; INTERPOSE_HOOK(projectile_hook, checkMovement).apply(enable); + INTERPOSE_HOOK(building_hook, updateAction).apply(enable); if (enable) load_engines(); diff --git a/scripts/gui/siege-engine.lua b/scripts/gui/siege-engine.lua index 7ad828c90..d10a9df69 100644 --- a/scripts/gui/siege-engine.lua +++ b/scripts/gui/siege-engine.lua @@ -12,6 +12,21 @@ local wmap = df.global.world.map last_target_min = last_target_min or nil last_target_max = last_target_max or nil +local item_choices = { + { caption = 'boulders (default)', item_type = df.item_type.BOULDER }, + { caption = 'blocks', item_type = df.item_type.BLOCKS }, + { caption = 'weapons', item_type = df.item_type.WEAPON }, + { caption = 'trap components', item_type = df.item_type.TRAPCOMP }, + { caption = 'bins', item_type = df.item_type.BIN }, + { caption = 'barrels', item_type = df.item_type.BARREL }, + { caption = 'anything', item_type = -1 }, +} + +local item_choice_idx = {} +for i,v in ipairs(item_choices) do + item_choice_idx[v.item_type] = i +end + SiegeEngine = defclass(SiegeEngine, guidm.MenuOverlay) SiegeEngine.focus_path = 'siege-engine' @@ -83,13 +98,8 @@ function SiegeEngine:zoomToTarget() local cx = math.floor((target_min.x + target_max.x)/2) local cy = math.floor((target_min.y + target_max.y)/2) local cz = math.floor((target_min.z + target_max.z)/2) - for z = cz,target_max.z do - if plugin.getTileStatus(self.building, xyz2pos(cx,cy,z)) ~= 'blocked' then - cz = z - break - end - end - self:centerViewOn(xyz2pos(cx,cy,cz)) + local pos = plugin.adjustToTarget(self.building, xyz2pos(cx,cy,cz)) + self:centerViewOn(pos) end end @@ -171,6 +181,19 @@ function SiegeEngine:onRenderBody_main(dc) dc:string("z",COLOR_LIGHTGREEN):string(": Zoom") end + dc:newline():newline(1) + if self.building.type == df.siegeengine_type.Ballista then + dc:string("Uses ballista arrows") + else + local item = plugin.getAmmoItem(self.building) + dc:string("u",COLOR_LIGHTGREEN):string(": Use ") + if item_choice_idx[item] then + dc:string(item_choices[item_choice_idx[item]].caption) + else + dc:string(df.item_type[item]) + end + end + if self.target_select_first then self:renderTargetView(self.target_select_first, guidm.getCursorPos()) else @@ -192,6 +215,19 @@ function SiegeEngine:setTargetArea(p1, p2) end end +function SiegeEngine:setAmmoItem(choice) + if self.building.type == df.siegeengine_type.Ballista then + return + end + + if not plugin.setAmmoItem(self.building, choice.item_type) then + dlg.showMessage( + 'Set Ammo Item', + 'Could not set the ammo item', COLOR_LIGHTRED + ) + end +end + function SiegeEngine:onInput_main(keys) if keys.CUSTOM_R then self:showCursor(true) @@ -199,6 +235,10 @@ function SiegeEngine:onInput_main(keys) self.mode = self.mode_aim elseif keys.CUSTOM_P and last_target_min then self:setTargetArea(last_target_min, last_target_max) + elseif keys.CUSTOM_U then + local item = plugin.getAmmoItem(self.building) + local idx = 1 + (item_choice_idx[item] or 0) % #item_choices + self:setAmmoItem(item_choices[idx]) elseif keys.CUSTOM_Z then self:zoomToTarget() elseif keys.CUSTOM_X then