diff options
| -rw-r--r-- | library/lua/gui/dwarfmode.lua | 29 | ||||
| -rw-r--r-- | library/modules/Gui.cpp | 7 | ||||
| -rw-r--r-- | plugins/devel/siege-engine.cpp | 160 | ||||
| -rw-r--r-- | scripts/devel/pop-screen.lua | 3 | ||||
| -rw-r--r-- | scripts/gui/mechanisms.lua | 2 | ||||
| -rw-r--r-- | scripts/gui/siege-engine.lua | 264 |
6 files changed, 390 insertions, 75 deletions
diff --git a/library/lua/gui/dwarfmode.lua b/library/lua/gui/dwarfmode.lua index 21a94217..661e1559 100644 --- a/library/lua/gui/dwarfmode.lua +++ b/library/lua/gui/dwarfmode.lua @@ -46,7 +46,7 @@ function getPanelLayout() end function getCursorPos() - if g_cursor ~= -30000 then + if g_cursor.x ~= -30000 then return copyall(g_cursor) end end @@ -167,6 +167,18 @@ function Viewport:isVisible(target,gap) return self:isVisibleXY(target,gap) and target.z == self.z end +function Viewport:tileToScreen(coord) + return xyz2pos(coord.x - self.x1, coord.y - self.y1, coord.z - self.z) +end + +function Viewport:getCenter() + return xyz2pos( + math.floor((self.x2+self.x1)/2), + math.floor((self.y2+self.y1)/2), + self.z + ) +end + function Viewport:centerOn(target) return self:clip( target.x - math.floor(self.width/2), @@ -253,16 +265,23 @@ function DwarfOverlay:getViewport(old_vp) end end -function DwarfOverlay:moveCursorTo(cursor,viewport) +function DwarfOverlay:moveCursorTo(cursor,viewport,gap) setCursorPos(cursor) - self:getViewport(viewport):reveal(cursor, 5, 0, 10):set() + self:zoomViewportTo(cursor,viewport,gap) +end + +function DwarfOverlay:zoomViewportTo(target, viewport, gap) + if gap and self:getViewport():isVisible(target, gap) then + return + end + self:getViewport(viewport):reveal(target, 5, 0, 10):set() end -function DwarfOverlay:selectBuilding(building,cursor,viewport) +function DwarfOverlay:selectBuilding(building,cursor,viewport,gap) cursor = cursor or utils.getBuildingCenter(building) df.global.world.selected_building = building - self:moveCursorTo(cursor, viewport) + self:moveCursorTo(cursor, viewport, gap) end function DwarfOverlay:propagateMoveKeys(keys) diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index 8de90873..91df14ea 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -173,10 +173,9 @@ DEFINE_GET_FOCUS_STRING_HANDLER(dwarfmode) else if (id == &df::building_trapst::_identity) { auto trap = (df::building_trapst*)selected; - if (trap->trap_type == trap_type::Lever) { - focus += "/Lever"; + focus += "/" + enum_item_key(trap->trap_type); + if (trap->trap_type == trap_type::Lever) jobs = true; - } } else if (ui_building_in_assign && *ui_building_in_assign && ui_building_assign_type && ui_building_assign_units && @@ -189,6 +188,8 @@ DEFINE_GET_FOCUS_STRING_HANDLER(dwarfmode) focus += unit ? "/Unit" : "/None"; } } + else + focus += "/" + enum_item_key(selected->getType()); if (jobs) { diff --git a/plugins/devel/siege-engine.cpp b/plugins/devel/siege-engine.cpp index 189c43ad..6906f540 100644 --- a/plugins/devel/siege-engine.cpp +++ b/plugins/devel/siege-engine.cpp @@ -48,6 +48,65 @@ 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(0, 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; +} + +/* * Configuration management */ @@ -55,17 +114,10 @@ static bool enable_plugin(); struct EngineInfo { int id; - df::coord target_min, target_max; + coord_range target; - 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; - } + bool hasTarget() { return is_range_valid(target); } + bool onTarget(df::coord pos) { return is_in_range(target, pos); } }; static std::map<df::building*, EngineInfo> engines; @@ -98,8 +150,8 @@ static void load_engines() { 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)); + 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)); } } @@ -109,10 +161,10 @@ static int getTargetArea(lua_State *L) if (!bld) luaL_argerror(L, 1, "null building"); auto engine = find_engine(bld); - if (engine && engine->target_min.isValid()) + if (engine && engine->hasTarget()) { - Lua::Push(L, engine->target_min); - Lua::Push(L, engine->target_max); + Lua::Push(L, engine->target.first); + Lua::Push(L, engine->target.second); } else { @@ -128,7 +180,7 @@ 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(); + engine->target = coord_range(); auto pworld = Core::getInstance().getWorld(); auto key = stl_sprintf("siege-engine/target/%d", bld->id); @@ -151,13 +203,18 @@ static bool setTargetArea(df::building_siegeenginest *bld, df::coord target_min, auto engine = find_engine(bld, true); + set_range(&engine->target, target_min, target_max); + 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); + 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; } @@ -267,38 +324,45 @@ struct PathMetrics { } }; -void paintAimScreen(df::building_siegeenginest *bld, df::coord view, df::coord2d ltop, df::coord2d size) +static std::string getTileStatus(df::building_siegeenginest *bld, df::coord tile_pos) { - CHECK_NULL_POINTER(bld); + df::coord origin(bld->centerx, bld->centery, bld->z); + auto fire_range = get_engine_range(bld); - df::coord origin = df::coord(bld->centerx, bld->centery, bld->z); + ProjectilePath path(origin, tile_pos); + PathMetrics raytrace(path, tile_pos); - auto engine = find_engine(bld); - int min_distance, max_distance; - - if (bld->type == siegeengine_type::Ballista) + if (raytrace.hits()) { - min_distance = 0; - max_distance = 200; + if (raytrace.goal_step >= fire_range.first && + raytrace.goal_step <= fire_range.second) + return "ok"; + else + return "out_of_range"; } else - { - min_distance = 30; - max_distance = 100; - } + return "blocked"; +} + +static void paintAimScreen(df::building_siegeenginest *bld, df::coord view, df::coord2d ltop, df::coord2d size) +{ + CHECK_NULL_POINTER(bld); - df::coord cursor = Gui::getCursorPos(); + df::coord origin(bld->centerx, bld->centery, bld->z); + coord_range building_rect( + df::coord(bld->x1, bld->y1, bld->z), + df::coord(bld->x2, bld->y2, bld->z) + ); + + auto engine = find_engine(bld); + auto fire_range = get_engine_range(bld); 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) + if (is_in_range(building_rect, tile_pos)) continue; Pen cur_tile = Screen::readTile(ltop.x+x, ltop.y+y); @@ -306,22 +370,13 @@ void paintAimScreen(df::building_siegeenginest *bld, df::coord view, df::coord2d 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) + if (raytrace.goal_step >= fire_range.first && + raytrace.goal_step <= fire_range.second) color = COLOR_GREEN; else color = COLOR_CYAN; @@ -357,6 +412,7 @@ void paintAimScreen(df::building_siegeenginest *bld, df::coord view, df::coord2d DFHACK_PLUGIN_LUA_FUNCTIONS { DFHACK_LUA_FUNCTION(clearTargetArea), DFHACK_LUA_FUNCTION(setTargetArea), + DFHACK_LUA_FUNCTION(getTileStatus), DFHACK_LUA_FUNCTION(paintAimScreen), DFHACK_LUA_END }; diff --git a/scripts/devel/pop-screen.lua b/scripts/devel/pop-screen.lua new file mode 100644 index 00000000..f1ee072c --- /dev/null +++ b/scripts/devel/pop-screen.lua @@ -0,0 +1,3 @@ +-- For killing bugged out gui script screens. + +dfhack.screen.dismiss(dfhack.gui.getCurViewscreen()) diff --git a/scripts/gui/mechanisms.lua b/scripts/gui/mechanisms.lua index 4468e1dc..c14bfcbe 100644 --- a/scripts/gui/mechanisms.lua +++ b/scripts/gui/mechanisms.lua @@ -122,7 +122,7 @@ function MechanismList:onInput(keys) end end -if not string.find(dfhack.gui.getCurFocus(), 'dwarfmode/QueryBuilding/Some') then +if not string.match(dfhack.gui.getCurFocus(), '^dwarfmode/QueryBuilding/Some') then qerror("This script requires the main dwarfmode view in 'q' mode") end diff --git a/scripts/gui/siege-engine.lua b/scripts/gui/siege-engine.lua index 466657e5..bba6bce8 100644 --- a/scripts/gui/siege-engine.lua +++ b/scripts/gui/siege-engine.lua @@ -6,6 +6,11 @@ local guidm = require 'gui.dwarfmode' local dlg = require 'gui.dialogs' local plugin = require 'plugins.siege-engine' +local wmap = df.global.world.map + +-- Globals kept between script calls +last_target_min = last_target_min or nil +last_target_max = last_target_max or nil SiegeEngine = defclass(SiegeEngine, guidm.MenuOverlay) @@ -15,9 +20,17 @@ function SiegeEngine:init(building) self:init_fields{ building = building, center = utils.getBuildingCenter(building), - links = {}, selected = 1 + links = {}, selected = 1, } guidm.MenuOverlay.init(self) + self.mode_main = { + render = self:callback 'onRenderBody_main', + input = self:callback 'onInput_main', + } + self.mode_aim = { + render = self:callback 'onRenderBody_aim', + input = self:callback 'onInput_aim', + } return self end @@ -26,40 +39,263 @@ function SiegeEngine:onShow() self.old_cursor = guidm.getCursorPos() self.old_viewport = self:getViewport() + + self.mode = self.mode_main + self:showCursor(false) end function SiegeEngine:onDestroy() - guidm.setCursorPos(self.old_cursor) - self:getViewport(self.old_viewport):set() + self:selectBuilding(self.building, self.old_cursor, self.old_viewport, 10) end -function SiegeEngine:onRenderBody(dc) - dc:clear() - dc:seek(1,1):string(utils.getBuildingName(self.building), COLOR_WHITE):newline() +function SiegeEngine:showCursor(enable) + local cursor = guidm.getCursorPos() + if cursor and not enable then + self.cursor = cursor + self.target_select_first = nil + guidm.clearCursorPos() + elseif not cursor and enable then + local view = self:getViewport() + cursor = self.cursor + if not cursor or not view:isVisible(cursor) then + cursor = view:getCenter() + end + self.cursor = nil + guidm.setCursorPos(cursor) + end +end +function SiegeEngine:centerViewOn(pos) + local cursor = guidm.getCursorPos() + if cursor then + guidm.setCursorPos(pos) + else + self.cursor = pos + end + self:getViewport():centerOn(pos):set() +end + +function SiegeEngine:zoomToTarget() + local target_min, target_max = plugin.getTargetArea(self.building) + if target_min then + 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)) + end +end + +function paint_target_grid(dc, view, origin, p1, p2) + local r1, sz, r2 = guidm.getSelectionRange(p1, p2) + + if view.z < r1.z or view.z > r2.z then + return + end + + local p1 = view:tileToScreen(r1) + local p2 = view:tileToScreen(r2) + local org = view:tileToScreen(origin) + dc:pen{ fg = COLOR_CYAN, bg = COLOR_CYAN, ch = '+', bold = true } + + -- Frame + dc:fill(p1.x,p1.y,p1.x,p2.y) + dc:fill(p1.x,p1.y,p2.x,p1.y) + dc:fill(p2.x,p1.y,p2.x,p2.y) + dc:fill(p1.x,p2.y,p2.x,p2.y) + + -- Grid + local gxmin = org.x+10*math.ceil((p1.x-org.x)/10) + local gxmax = org.x+10*math.floor((p2.x-org.x)/10) + local gymin = org.y+10*math.ceil((p1.y-org.y)/10) + local gymax = org.y+10*math.floor((p2.y-org.y)/10) + for x = gxmin,gxmax,10 do + for y = gymin,gymax,10 do + dc:fill(p1.x,y,p2.x,y) + dc:fill(x,p1.y,x,p2.y) + end + end +end + +function SiegeEngine:renderTargetView(target_min, target_max) local view = self:getViewport() local map = self.df_layout.map + local map_dc = gui.Painter.new(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") + if target_min and math.floor(dfhack.getTickCount()/500) % 2 == 0 then + paint_target_grid(map_dc, view, self.center, target_min, target_max) + end + + local cursor = guidm.getCursorPos() + if cursor then + local cx, cy, cz = pos2xyz(view:tileToScreen(cursor)) + if cz == 0 then + map_dc:seek(cx,cy):char('X', COLOR_YELLOW) + end + end +end + +function SiegeEngine:onRenderBody_main(dc) + dc:newline(1):pen(COLOR_WHITE):string("Target: ") + + local target_min, target_max = plugin.getTargetArea(self.building) + if target_min then + dc:string( + (target_max.x-target_min.x+1).."x".. + (target_max.y-target_min.y+1).."x".. + (target_max.z-target_min.z+1).." Rect" + ) + else + dc:string("None (default)") + end + + dc:newline(3):string("r",COLOR_LIGHTGREEN):string(": Rectangle") + if last_target_min then + dc:string(", "):string("p",COLOR_LIGHTGREEN):string(": Paste") + end + dc:newline(3) + if target_min then + dc:string("x",COLOR_LIGHTGREEN):string(": Clear, ") + dc:string("z",COLOR_LIGHTGREEN):string(": Zoom") + end + + if self.target_select_first then + self:renderTargetView(self.target_select_first, guidm.getCursorPos()) + else + self:renderTargetView(target_min, target_max) + end +end + +function SiegeEngine:setTargetArea(p1, p2) + self.target_select_first = nil + + if not plugin.setTargetArea(self.building, p1, p2) then + dlg.showMessage( + 'Set Target Area', + 'Could not set the target area', COLOR_LIGHTRED + ) + else + last_target_min = p1 + last_target_max = p2 + end +end + +function SiegeEngine:onInput_main(keys) + if keys.CUSTOM_R then + self:showCursor(true) + self.target_select_first = nil + 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_Z then + self:zoomToTarget() + elseif keys.CUSTOM_X then + plugin.clearTargetArea(self.building) + elseif self:simulateViewScroll(keys) then + self.cursor = nil + else + return false + end + return true +end + +local status_table = { + ok = { pen = COLOR_GREEN, msg = "Target accessible" }, + out_of_range = { pen = COLOR_CYAN, msg = "Target out of range" }, + blocked = { pen = COLOR_RED, msg = "Target obstructed" }, +} + +function SiegeEngine:onRenderBody_aim(dc) + local cursor = guidm.getCursorPos() + local first = self.target_select_first + + dc:newline(1):string('Select target rectangle'):newline() + + local info = status_table[plugin.getTileStatus(self.building, cursor)] + if info then + dc:newline(2):string(info.msg, info.pen) + else + dc:newline(2):string('ERROR', COLOR_RED) + end + + dc:newline():newline(1):string("Enter",COLOR_LIGHTGREEN) + if first then + dc:string(": Finish rectangle") + else + dc:string(": Start rectangle") + end + dc:newline() + + local target_min, target_max = plugin.getTargetArea(self.building) + if target_min then + dc:newline(1):string("z",COLOR_LIGHTGREEN):string(": Zoom to current target") + end + + if first then + self:renderTargetView(first, cursor) + else + local target_min, target_max = plugin.getTargetArea(self.building) + self:renderTargetView(target_min, target_max) + end +end + +function SiegeEngine:onInput_aim(keys) + if keys.SELECT then + local cursor = guidm.getCursorPos() + if self.target_select_first then + self:setTargetArea(self.target_select_first, cursor) + + self.mode = self.mode_main + self:showCursor(false) + else + self.target_select_first = cursor + end + elseif keys.CUSTOM_Z then + self:zoomToTarget() + elseif keys.LEAVESCREEN then + self.mode = self.mode_main + self:showCursor(false) + elseif self:simulateCursorMovement(keys) then + self.cursor = nil + else + return false + end + return true +end + +function SiegeEngine:onRenderBody(dc) + dc:clear() + dc:seek(1,1):pen(COLOR_WHITE):string(utils.getBuildingName(self.building)):newline() + + self.mode.render(dc) + + dc:seek(1, math.max(dc:localY(), 21)):pen(COLOR_WHITE) + dc:string("ESC", COLOR_LIGHTGREEN):string(": Back, ") + dc:string("c", COLOR_LIGHTGREEN):string(": Recenter") end function SiegeEngine:onInput(keys) - if keys.LEAVESCREEN then + if self.mode.input(keys) then + -- + elseif keys.CUSTOM_C then + self:centerViewOn(self.center) + elseif 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") +if not string.match(dfhack.gui.getCurFocus(), '^dwarfmode/QueryBuilding/Some/SiegeEngine') then + qerror("This script requires a siege engine selected in 'q' mode") end local building = df.global.world.selected_building |
