summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlexander Gavrilov2012-09-07 19:54:32 +0400
committerAlexander Gavrilov2012-09-07 19:54:32 +0400
commit325e294af2bbd2ba0381a476756eddbbfceb1b36 (patch)
tree7a51e57401315a79584fc24348edb9d177d15493
parente925d8f4d999817d3ccf7f3180e7abee382e03b4 (diff)
downloaddfhack-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.lua17
-rw-r--r--library/lua/gui/dwarfmode.lua53
-rw-r--r--plugins/devel/CMakeLists.txt1
-rw-r--r--plugins/devel/siege-engine.cpp432
-rw-r--r--plugins/lua/siege-engine.lua13
-rw-r--r--scripts/gui/siege-engine.lua72
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()