Allow specifying arbitrary items to use in catapults.

develop
Alexander Gavrilov 2012-09-11 22:46:17 +04:00
parent 3a075f4bc7
commit b0938d7e0d
2 changed files with 195 additions and 10 deletions

@ -8,6 +8,7 @@
#include <modules/Maps.h> #include <modules/Maps.h>
#include <modules/World.h> #include <modules/World.h>
#include <modules/Units.h> #include <modules/Units.h>
#include <modules/Job.h>
#include <LuaTools.h> #include <LuaTools.h>
#include <TileTypes.h> #include <TileTypes.h>
#include <vector> #include <vector>
@ -41,6 +42,10 @@
#include "df/assumed_identity.h" #include "df/assumed_identity.h"
#include "df/game_mode.h" #include "df/game_mode.h"
#include "df/unit_misc_trait.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" #include "MiscUtils.h"
@ -148,6 +153,11 @@ struct EngineInfo {
coord_range target; 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 hasTarget() { return is_range_valid(target); }
bool onTarget(df::coord pos) { return is_in_range(target, pos); } bool onTarget(df::coord pos) { return is_in_range(target, pos); }
df::coord getTargetSize() { return target.second - target.first; } 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->hit_delay = 3;
obj->fire_range = get_engine_range(ebld); 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; coord_engines[obj->center] = bld;
return obj; return obj;
} }
@ -198,7 +213,7 @@ static EngineInfo *find_engine(lua_State *L, int idx, bool create = false)
{ {
auto bld = Lua::CheckDFObject<df::building_siegeenginest>(L, idx); auto bld = Lua::CheckDFObject<df::building_siegeenginest>(L, idx);
auto engine = find_engine(bld); auto engine = find_engine(bld, create);
if (!engine) if (!engine)
luaL_error(L, "no such 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.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)); 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) static int getTargetArea(lua_State *L)
@ -309,6 +333,51 @@ static bool setTargetArea(df::building_siegeenginest *bld, df::coord target_min,
return true; 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) static int getShotSkill(df::building_siegeenginest *bld)
{ {
CHECK_NULL_POINTER(bld); CHECK_NULL_POINTER(bld);
@ -446,7 +515,7 @@ static bool adjustToTarget(EngineInfo *engine, df::coord *pos)
return true; return true;
for (df::coord fudge = *pos; for (df::coord fudge = *pos;
fudge.z < engine->target.second.z; fudge.z++) fudge.z <= engine->target.second.z; fudge.z++)
{ {
if (!isTargetableTile(fudge)) if (!isTargetableTile(fudge))
continue; continue;
@ -455,7 +524,7 @@ static bool adjustToTarget(EngineInfo *engine, df::coord *pos)
} }
for (df::coord fudge = *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)) if (!isTargetableTile(fudge))
continue; continue;
@ -1046,6 +1115,79 @@ struct projectile_hook : df::proj_itemst {
IMPLEMENT_VMETHOD_INTERPOSE(projectile_hook, checkMovement); 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 * Initialization
*/ */
@ -1064,6 +1206,8 @@ DFHACK_PLUGIN_LUA_FUNCTIONS {
DFHACK_PLUGIN_LUA_COMMANDS { DFHACK_PLUGIN_LUA_COMMANDS {
DFHACK_LUA_COMMAND(getTargetArea), DFHACK_LUA_COMMAND(getTargetArea),
DFHACK_LUA_COMMAND(getAmmoItem),
DFHACK_LUA_COMMAND(setAmmoItem),
DFHACK_LUA_COMMAND(projPosAtStep), DFHACK_LUA_COMMAND(projPosAtStep),
DFHACK_LUA_COMMAND(projPathMetrics), DFHACK_LUA_COMMAND(projPathMetrics),
DFHACK_LUA_COMMAND(adjustToTarget), DFHACK_LUA_COMMAND(adjustToTarget),
@ -1080,6 +1224,7 @@ static void enable_hooks(bool enable)
is_enabled = enable; is_enabled = enable;
INTERPOSE_HOOK(projectile_hook, checkMovement).apply(enable); INTERPOSE_HOOK(projectile_hook, checkMovement).apply(enable);
INTERPOSE_HOOK(building_hook, updateAction).apply(enable);
if (enable) if (enable)
load_engines(); load_engines();

@ -12,6 +12,21 @@ local wmap = df.global.world.map
last_target_min = last_target_min or nil last_target_min = last_target_min or nil
last_target_max = last_target_max 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 = defclass(SiegeEngine, guidm.MenuOverlay)
SiegeEngine.focus_path = 'siege-engine' SiegeEngine.focus_path = 'siege-engine'
@ -83,13 +98,8 @@ function SiegeEngine:zoomToTarget()
local cx = math.floor((target_min.x + target_max.x)/2) local cx = math.floor((target_min.x + target_max.x)/2)
local cy = math.floor((target_min.y + target_max.y)/2) local cy = math.floor((target_min.y + target_max.y)/2)
local cz = math.floor((target_min.z + target_max.z)/2) local cz = math.floor((target_min.z + target_max.z)/2)
for z = cz,target_max.z do local pos = plugin.adjustToTarget(self.building, xyz2pos(cx,cy,cz))
if plugin.getTileStatus(self.building, xyz2pos(cx,cy,z)) ~= 'blocked' then self:centerViewOn(pos)
cz = z
break
end
end
self:centerViewOn(xyz2pos(cx,cy,cz))
end end
end end
@ -171,6 +181,19 @@ function SiegeEngine:onRenderBody_main(dc)
dc:string("z",COLOR_LIGHTGREEN):string(": Zoom") dc:string("z",COLOR_LIGHTGREEN):string(": Zoom")
end 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 if self.target_select_first then
self:renderTargetView(self.target_select_first, guidm.getCursorPos()) self:renderTargetView(self.target_select_first, guidm.getCursorPos())
else else
@ -192,6 +215,19 @@ function SiegeEngine:setTargetArea(p1, p2)
end end
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) function SiegeEngine:onInput_main(keys)
if keys.CUSTOM_R then if keys.CUSTOM_R then
self:showCursor(true) self:showCursor(true)
@ -199,6 +235,10 @@ function SiegeEngine:onInput_main(keys)
self.mode = self.mode_aim self.mode = self.mode_aim
elseif keys.CUSTOM_P and last_target_min then elseif keys.CUSTOM_P and last_target_min then
self:setTargetArea(last_target_min, last_target_max) 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 elseif keys.CUSTOM_Z then
self:zoomToTarget() self:zoomToTarget()
elseif keys.CUSTOM_X then elseif keys.CUSTOM_X then