diff options
| author | Alexander Gavrilov | 2012-09-07 19:54:32 +0400 |
|---|---|---|
| committer | Alexander Gavrilov | 2012-09-07 19:54:32 +0400 |
| commit | 325e294af2bbd2ba0381a476756eddbbfceb1b36 (patch) | |
| tree | 7a51e57401315a79584fc24348edb9d177d15493 | |
| parent | e925d8f4d999817d3ccf7f3180e7abee382e03b4 (diff) | |
| download | dfhack-325e294af2bbd2ba0381a476756eddbbfceb1b36.tar.gz dfhack-325e294af2bbd2ba0381a476756eddbbfceb1b36.tar.bz2 dfhack-325e294af2bbd2ba0381a476756eddbbfceb1b36.tar.xz | |
Start the siege engine plugin with code to highlight obstacles on screen.
| -rw-r--r-- | library/lua/dfhack.lua | 17 | ||||
| -rw-r--r-- | library/lua/gui/dwarfmode.lua | 53 | ||||
| -rw-r--r-- | plugins/devel/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | plugins/devel/siege-engine.cpp | 432 | ||||
| -rw-r--r-- | plugins/lua/siege-engine.lua | 13 | ||||
| -rw-r--r-- | scripts/gui/siege-engine.lua | 72 |
6 files changed, 582 insertions, 6 deletions
diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua index e96bb0f4..baf0d42e 100644 --- a/library/lua/dfhack.lua +++ b/library/lua/dfhack.lua @@ -169,6 +169,23 @@ function xyz2pos(x,y,z) end end +function pos2xy(pos) + if pos then + local x = pos.x + if x and x ~= -30000 then + return x, pos.y + end + end +end + +function xy2pos(x,y) + if x then + return {x=x,y=y} + else + return {x=-30000,y=-30000} + end +end + function safe_index(obj,idx,...) if obj == nil or idx == nil then return nil diff --git a/library/lua/gui/dwarfmode.lua b/library/lua/gui/dwarfmode.lua index 1f7ae1b0..21a94217 100644 --- a/library/lua/gui/dwarfmode.lua +++ b/library/lua/gui/dwarfmode.lua @@ -136,6 +136,14 @@ function Viewport:set() return vp end +function Viewport:getPos() + return xyz2pos(self.x1, self.y1, self.z) +end + +function Viewport:getSize() + return xy2pos(self.width, self.height) +end + function Viewport:clip(x,y,z) return self:make( math.max(0, math.min(x or self.x1, world_map.x_count-self.width)), @@ -207,16 +215,24 @@ MOVEMENT_KEYS = { CURSOR_UP_Z_AUX = { 0, 0, 1 }, CURSOR_DOWN_Z_AUX = { 0, 0, -1 }, } -function Viewport:scrollByKey(key) +local function get_movement_delta(key, delta, big_step) local info = MOVEMENT_KEYS[key] if info then - local delta = 10 - if info[4] then delta = 20 end + if info[4] then + delta = big_step + end + return delta*info[1], delta*info[2], info[3] + end +end + +function Viewport:scrollByKey(key) + local dx, dy, dz = get_movement_delta(key, 10, 20) + if dx then return self:clip( - self.x1 + delta*info[1], - self.y1 + delta*info[2], - self.z + info[3] + self.x1 + dx, + self.y1 + dy, + self.z + dz ) else return self @@ -282,6 +298,31 @@ function DwarfOverlay:simulateViewScroll(keys, anchor, no_clip_cursor) end end +function DwarfOverlay:simulateCursorMovement(keys, anchor) + local layout = self.df_layout + local cursor = getCursorPos() + local cx, cy, cz = pos2xyz(cursor) + + if anchor and keys.A_MOVE_SAME_SQUARE then + setCursorPos(anchor) + self:getViewport():centerOn(anchor):set() + return 'A_MOVE_SAME_SQUARE' + end + + for code,_ in pairs(MOVEMENT_KEYS) do + if keys[code] then + local dx, dy, dz = get_movement_delta(code, 1, 10) + local ncur = xyz2pos(cx+dx, cy+dy, cz+dz) + + if dfhack.maps.isValidTilePos(ncur) then + setCursorPos(ncur) + self:getViewport():reveal(ncur,4,10,6,true):set() + return code + end + end + end +end + function DwarfOverlay:onAboutToShow(below) local screen = dfhack.gui.getCurViewscreen() if below then screen = below.parent end diff --git a/plugins/devel/CMakeLists.txt b/plugins/devel/CMakeLists.txt index 134d5cb6..39e8f7b6 100644 --- a/plugins/devel/CMakeLists.txt +++ b/plugins/devel/CMakeLists.txt @@ -18,6 +18,7 @@ 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 new file mode 100644 index 00000000..189c43ad --- /dev/null +++ b/plugins/devel/siege-engine.cpp @@ -0,0 +1,432 @@ +#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/World.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 "MiscUtils.h" + +using std::vector; +using std::string; +using std::stack; +using namespace DFHack; +using namespace df::enums; + +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"); + +/* + * Configuration management + */ + +static bool enable_plugin(); + +struct EngineInfo { + int id; + df::coord target_min, target_max; + + bool hasTarget() { + return target_min.isValid() && target_max.isValid(); + } + bool onTarget(df::coord pos) { + return hasTarget() && + target_min.x <= pos.x && pos.x <= target_max.x && + target_min.y <= pos.y && pos.y <= target_max.y && + target_min.z <= pos.z && pos.z <= target_max.z; + } +}; + +static std::map<df::building*, EngineInfo> engines; + +static EngineInfo *find_engine(df::building *bld, bool create = false) +{ + if (!bld) + return NULL; + + auto it = engines.find(bld); + if (it != engines.end()) + return &it->second; + if (!create) + return NULL; + + auto *obj = &engines[bld]; + obj->id = bld->id; + return obj; +} + +static void load_engines() +{ + engines.clear(); + + 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_min = df::coord(it->ival(1), it->ival(2), it->ival(3)); + engine->target_max = df::coord(it->ival(4), it->ival(5), it->ival(6)); + } +} + +static int getTargetArea(lua_State *L) +{ + auto bld = Lua::CheckDFObject<df::building>(L, 1); + if (!bld) luaL_argerror(L, 1, "null building"); + auto engine = find_engine(bld); + + if (engine && engine->target_min.isValid()) + { + Lua::Push(L, engine->target_min); + Lua::Push(L, engine->target_max); + } + 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_min = engine->target_max = df::coord(); + + 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); + + entry.ival(0) = bld->id; + entry.ival(1) = engine->target_min.x = std::min(target_min.x, target_max.x); + entry.ival(2) = engine->target_min.y = std::min(target_min.y, target_max.y); + entry.ival(3) = engine->target_min.z = std::min(target_min.z, target_max.z); + entry.ival(4) = engine->target_max.x = std::max(target_min.x, target_max.x); + entry.ival(5) = engine->target_max.y = std::max(target_min.y, target_max.y); + entry.ival(6) = engine->target_max.z = std::max(target_min.z, target_max.z); + + return true; +} + +/* + * Trajectory + */ + +struct ProjectilePath { + df::coord origin, target; + int divisor; + df::coord speed, direction; + + ProjectilePath(df::coord origin, df::coord target) : + origin(origin), target(target) + { + speed = target - origin; + divisor = std::max(abs(speed.x), std::max(abs(speed.y), abs(speed.z))); + 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 bool isPassableTile(df::coord pos) +{ + auto ptile = Maps::getTileType(pos); + return !ptile || FlowPassable(*ptile); +} + +struct PathMetrics { + enum CollisionType { + Impassable, + Floor, + Ceiling, + MapEdge + } hit_type; + + int collision_step; + int goal_step, goal_z_step; + std::vector<df::coord> coords; + + bool hits() { return goal_step != -1 && collision_step > goal_step; } + + PathMetrics(const ProjectilePath &path, df::coord goal, bool list_coords = false) + { + coords.clear(); + collision_step = goal_step = goal_z_step = -1; + + 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 == goal.z) + { + if (goal_z_step == -1) + goal_z_step = step; + if (cur_pos == goal) + goal_step = step; + } + + if (!Maps::isValidTilePos(cur_pos)) + { + hit_type = PathMetrics::MapEdge; + break; + } + + if (!isPassableTile(cur_pos)) + { + 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; + } + } + + prev_pos = cur_pos; + } + + collision_step = step; + } +}; + +void paintAimScreen(df::building_siegeenginest *bld, df::coord view, df::coord2d ltop, df::coord2d size) +{ + CHECK_NULL_POINTER(bld); + + df::coord origin = df::coord(bld->centerx, bld->centery, bld->z); + + auto engine = find_engine(bld); + int min_distance, max_distance; + + if (bld->type == siegeengine_type::Ballista) + { + min_distance = 0; + max_distance = 200; + } + else + { + min_distance = 30; + max_distance = 100; + } + + df::coord cursor = Gui::getCursorPos(); + + 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 (tile_pos == cursor) + continue; + if (tile_pos.z == bld->z && + tile_pos.x >= bld->x1 && tile_pos.x <= bld->x2 && + tile_pos.y >= bld->y1 && tile_pos.y <= bld->y2) + continue; + + Pen cur_tile = Screen::readTile(ltop.x+x, ltop.y+y); + if (!cur_tile.valid()) + continue; + + ProjectilePath path(origin, tile_pos); + + if (path.speed.z != 0 && abs(path.speed.z) != path.divisor) { + path.divisor *= 20; + path.speed.x *= 20; + path.speed.y *= 20; + path.speed.z *= 20; + path.speed.z += 9; + } + + PathMetrics raytrace(path, tile_pos); + + int color; + if (raytrace.hits()) + { + if (raytrace.goal_step >= min_distance && + raytrace.goal_step <= max_distance) + color = COLOR_GREEN; + else + color = COLOR_CYAN; + } + else + color = COLOR_RED; + + 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 && engine->onTarget(tile_pos)); + + if (cur_tile.tile) + cur_tile.tile_mode = Pen::CharColor; + + Screen::paintTile(cur_tile, ltop.x+x, ltop.y+y); + } + } +} + +/* + * Initialization + */ + +DFHACK_PLUGIN_LUA_FUNCTIONS { + DFHACK_LUA_FUNCTION(clearTargetArea), + DFHACK_LUA_FUNCTION(setTargetArea), + DFHACK_LUA_FUNCTION(paintAimScreen), + DFHACK_LUA_END +}; + +DFHACK_PLUGIN_LUA_COMMANDS { + DFHACK_LUA_COMMAND(getTargetArea), + DFHACK_LUA_END +}; + +static bool is_enabled = false; + +static void enable_hooks(bool enable) +{ + is_enabled = enable; + + if (enable) + load_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; +} + +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; +} diff --git a/plugins/lua/siege-engine.lua b/plugins/lua/siege-engine.lua new file mode 100644 index 00000000..01b5d144 --- /dev/null +++ b/plugins/lua/siege-engine.lua @@ -0,0 +1,13 @@ +local _ENV = mkmodule('plugins.siege-engine') + +--[[ + + Native functions: + + * getTargetArea(building) -> point1, point2 + * clearTargetArea(building) + * setTargetArea(building, point1, point2) -> true/false + +--]] + +return _ENV
\ No newline at end of file diff --git a/scripts/gui/siege-engine.lua b/scripts/gui/siege-engine.lua new file mode 100644 index 00000000..466657e5 --- /dev/null +++ b/scripts/gui/siege-engine.lua @@ -0,0 +1,72 @@ +-- Front-end for the siege engine plugin. + +local utils = require 'utils' +local gui = require 'gui' +local guidm = require 'gui.dwarfmode' +local dlg = require 'gui.dialogs' + +local plugin = require 'plugins.siege-engine' + +SiegeEngine = defclass(SiegeEngine, guidm.MenuOverlay) + +SiegeEngine.focus_path = 'siege-engine' + +function SiegeEngine:init(building) + self:init_fields{ + building = building, + center = utils.getBuildingCenter(building), + links = {}, selected = 1 + } + guidm.MenuOverlay.init(self) + return self +end + +function SiegeEngine:onShow() + guidm.MenuOverlay.onShow(self) + + self.old_cursor = guidm.getCursorPos() + self.old_viewport = self:getViewport() +end + +function SiegeEngine:onDestroy() + guidm.setCursorPos(self.old_cursor) + self:getViewport(self.old_viewport):set() +end + +function SiegeEngine:onRenderBody(dc) + dc:clear() + dc:seek(1,1):string(utils.getBuildingName(self.building), COLOR_WHITE):newline() + + local view = self:getViewport() + local map = self.df_layout.map + + plugin.paintAimScreen( + self.building, view:getPos(), + xy2pos(map.x1, map.y1), view:getSize() + ) + + dc:newline():newline(1):pen(COLOR_WHITE) + dc:string("Esc", COLOR_LIGHTGREEN):string(": Back, ") + dc:string("Enter", COLOR_LIGHTGREEN):string(": Switch") +end + +function SiegeEngine:onInput(keys) + if keys.LEAVESCREEN then + self:dismiss() + elseif self:simulateCursorMovement(keys, self.center) then + return + end +end + +if not string.find(dfhack.gui.getCurFocus(), 'dwarfmode/QueryBuilding/Some') then + qerror("This script requires the main dwarfmode view in 'q' mode") +end + +local building = df.global.world.selected_building + +if not df.building_siegeenginest:is_instance(building) then + qerror("A siege engine must be selected") +end + +local list = mkinstance(SiegeEngine):init(df.global.world.selected_building) +list:show() |
