From 5a880d619c6e51093ff06bf7d8b99e672740bc76 Mon Sep 17 00:00:00 2001 From: "jj@jj" Date: Sun, 12 Aug 2012 22:13:01 +0200 Subject: [PATCH 01/26] ruby: add MapTile#dig, tweak unit_iscitizen --- plugins/ruby/README | 3 +-- plugins/ruby/map.rb | 5 ++++ plugins/ruby/unit.rb | 60 ++++++++++++++++++++++++++------------------ 3 files changed, 42 insertions(+), 26 deletions(-) diff --git a/plugins/ruby/README b/plugins/ruby/README index 8a473f332..c9a84fb37 100644 --- a/plugins/ruby/README +++ b/plugins/ruby/README @@ -213,8 +213,7 @@ Find the raws name of the plant under cursor p df.world.raws.plants.all[plant.mat_index].id Dig a channel under the cursor - df.map_designation_at(df.cursor).dig = :Channel - df.map_block_at(df.cursor).flags.designated = true + df.map_tile_at(df.cursor).dig(:Channel) Spawn 2/7 magma on the tile of the dwarf nicknamed 'hotfeet' hot = df.unit_citizens.find { |u| u.name.nickname == 'hotfeet' } diff --git a/plugins/ruby/map.rb b/plugins/ruby/map.rb index c99d5b88d..dccea7291 100644 --- a/plugins/ruby/map.rb +++ b/plugins/ruby/map.rb @@ -188,6 +188,11 @@ module DFHack "#" end + def dig(mode=:Default) + designation.dig = mode + mapblock.flags.designated = true + end + def spawn_liquid(quantity, is_magma=false, flowing=true) designation.flow_size = quantity designation.liquid_type = (is_magma ? :Magma : :Water) diff --git a/plugins/ruby/unit.rb b/plugins/ruby/unit.rb index ebcf249da..1a619c5ce 100644 --- a/plugins/ruby/unit.rb +++ b/plugins/ruby/unit.rb @@ -41,48 +41,60 @@ module DFHack # returns an Array of all units that are current fort citizen (dwarves, on map, not hostile) def unit_citizens - race = ui.race_id - civ = ui.civ_id world.units.active.find_all { |u| - u.race == race and u.civ_id == civ and !u.flags1.dead and !u.flags1.merchant and - !u.flags1.diplomat and !u.flags2.resident and !u.flags3.ghostly and - !u.curse.add_tags1.OPPOSED_TO_LIFE and !u.curse.add_tags1.CRAZED and - u.mood != :Berserk - # TODO check curse ; currently this should keep vampires, but may include werebeasts + unit_iscitizen(u) } end + def unit_iscitizen(u) + u.race == ui.race_id and u.civ_id == ui.civ_id and !u.flags1.dead and !u.flags1.merchant and + !u.flags1.diplomat and !u.flags2.resident and !u.flags3.ghostly and + !u.curse.add_tags1.OPPOSED_TO_LIFE and !u.curse.add_tags1.CRAZED and + u.mood != :Berserk + # TODO check curse ; currently this should keep vampires, but may include werebeasts + end + # list workers (citizen, not crazy / child / inmood / noble) def unit_workers - unit_citizens.find_all { |u| - u.mood == :None and - u.profession != :CHILD and - u.profession != :BABY and - # TODO MENIAL_WORK_EXEMPTION_SPOUSE - !unit_entitypositions(u).find { |pos| pos.flags[:MENIAL_WORK_EXEMPTION] } + world.units.active.find_all { |u| + unit_isworker(u) } end + def unit_isworker(u) + unit_iscitizen(u) and + u.mood == :None and + u.profession != :CHILD and + u.profession != :BABY and + # TODO MENIAL_WORK_EXEMPTION_SPOUSE + !unit_entitypositions(u).find { |pos| pos.flags[:MENIAL_WORK_EXEMPTION] } + end + # list currently idle workers def unit_idlers - unit_workers.find_all { |u| - # current_job includes eat/drink/sleep/pickupequip - !u.job.current_job and - # filter 'attend meeting' - not u.specific_refs.find { |s| s.type == :ACTIVITY } and - # filter soldiers (TODO check schedule) - u.military.squad_index == -1 and - # filter 'on break' - not u.status.misc_traits.find { |t| t.id == :OnBreak } + world.units.active.find_all { |u| + unit_isidler(u) } end + def unit_isidler(u) + unit_isworker(u) and + # current_job includes eat/drink/sleep/pickupequip + !u.job.current_job and + # filter 'attend meeting' + not u.specific_refs.find { |s| s.type == :ACTIVITY } and + # filter soldiers (TODO check schedule) + u.military.squad_index == -1 and + # filter 'on break' + not u.status.misc_traits.find { |t| t.id == :OnBreak } + end + def unit_entitypositions(unit) list = [] - return list if not hf = world.history.figures.binsearch(unit.hist_figure_id) + return list if not hf = unit.hist_figure_tg hf.entity_links.each { |el| next if el._rtti_classname != :histfig_entity_link_positionst - next if not ent = world.entities.all.binsearch(el.entity_id) + next if not ent = el.entity_tg next if not pa = ent.positions.assignments.binsearch(el.assignment_id) next if not pos = ent.positions.own.binsearch(pa.position_id) list << pos From 53a05365066ba47da2e047e4b8ef87d2a80405d4 Mon Sep 17 00:00:00 2001 From: jj Date: Mon, 13 Aug 2012 00:46:27 +0200 Subject: [PATCH 02/26] ruby: typo in building_deconstruct --- plugins/ruby/building.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/ruby/building.rb b/plugins/ruby/building.rb index ab029ac24..af152e198 100644 --- a/plugins/ruby/building.rb +++ b/plugins/ruby/building.rb @@ -279,9 +279,9 @@ module DFHack job = Job.cpp_new refbuildingholder = GeneralRefBuildingHolderst.cpp_new job.job_type = :DestroyBuilding - refbuildingholder.building_id = building.id + refbuildingholder.building_id = bld.id job.references << refbuildingholder - building.jobs << job + bld.jobs << job job_link job job end From e659d845ab63c1e354deafe351b9a86d5861168f Mon Sep 17 00:00:00 2001 From: jj Date: Mon, 13 Aug 2012 02:06:56 +0200 Subject: [PATCH 03/26] ruby: fix LinkList.next= --- plugins/ruby/ruby-autogen-defs.rb | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/plugins/ruby/ruby-autogen-defs.rb b/plugins/ruby/ruby-autogen-defs.rb index 3507508e1..0cee6426f 100644 --- a/plugins/ruby/ruby-autogen-defs.rb +++ b/plugins/ruby/ruby-autogen-defs.rb @@ -666,9 +666,9 @@ module DFHack @_tg = tg end - field(:_ptr, 0) { number 32, false } - field(:_prev, 4) { number 32, false } - field(:_next, 8) { number 32, false } + field(:_ptr, 0) { pointer } + field(:_prev, 4) { pointer } + field(:_next, 8) { pointer } def item # With the current xml structure, currently _tg designate @@ -682,22 +682,24 @@ module DFHack def item=(v) #addr = _ptr - #raise 'null pointer' if addr == 0 + #raise 'null pointer' if not addr #@_tg.at(addr)._set(v) raise 'null pointer' end def prev addr = _prev - return if addr == 0 + return if not addr @_tg._at(addr)._get end def next addr = _next - return if addr == 0 + return if not addr @_tg._at(addr)._get end + alias next= _next= + alias prev= _prev= include Enumerable def each From c339c7085a0ac2907449e63b7836803e07ed99d4 Mon Sep 17 00:00:00 2001 From: jj Date: Thu, 23 Aug 2012 18:44:03 +0200 Subject: [PATCH 04/26] ruby: add memory_check / memory_patch native methods --- plugins/ruby/ruby.cpp | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/plugins/ruby/ruby.cpp b/plugins/ruby/ruby.cpp index 1391faa44..482714d2a 100644 --- a/plugins/ruby/ruby.cpp +++ b/plugins/ruby/ruby.cpp @@ -4,6 +4,7 @@ #include "Export.h" #include "PluginManager.h" #include "VersionInfo.h" +#include "MemAccess.h" #include "DataDefs.h" #include "df/global_objects.h" @@ -597,6 +598,45 @@ static VALUE rb_dfmemory_write_float(VALUE self, VALUE addr, VALUE val) return Qtrue; } +// return memory permissions at address (eg "rx", nil if unmapped) +static VALUE rb_dfmemory_check(VALUE self, VALUE addr) +{ + void *ptr = (void*)rb_num2ulong(addr); + std::vector ranges; + Core::getInstance().p->getMemRanges(ranges); + + unsigned i = 0; + while (i < ranges.size() && ranges[i].end <= ptr) + i++; + + if (i >= ranges.size() || ranges[i].start > ptr || !ranges[i].valid) + return Qnil; + + std::string perm = ""; + if (ranges[i].read) + perm += "r"; + if (ranges[i].write) + perm += "w"; + if (ranges[i].execute) + perm += "x"; + if (ranges[i].shared) + perm += "s"; + + return rb_str_new(perm.c_str(), perm.length()); +} + +// memory write (tmp override page permissions, eg patch code) +static VALUE rb_dfmemory_patch(VALUE self, VALUE addr, VALUE raw) +{ + int strlen = FIX2INT(rb_funcall(raw, rb_intern("length"), 0)); + bool ret; + + ret = Core::getInstance().p->patchMemory((void*)rb_num2ulong(addr), + rb_string_value_ptr(&raw), strlen); + + return ret ? Qtrue : Qfalse; +} + // stl::string static VALUE rb_dfmemory_stlstring_new(VALUE self) @@ -875,6 +915,8 @@ static void ruby_bind_dfhack(void) { rb_define_singleton_method(rb_cDFHack, "memory_write_int16", RUBY_METHOD_FUNC(rb_dfmemory_write_int16), 2); rb_define_singleton_method(rb_cDFHack, "memory_write_int32", RUBY_METHOD_FUNC(rb_dfmemory_write_int32), 2); rb_define_singleton_method(rb_cDFHack, "memory_write_float", RUBY_METHOD_FUNC(rb_dfmemory_write_float), 2); + rb_define_singleton_method(rb_cDFHack, "memory_check", RUBY_METHOD_FUNC(rb_dfmemory_check), 1); + rb_define_singleton_method(rb_cDFHack, "memory_patch", RUBY_METHOD_FUNC(rb_dfmemory_patch), 2); rb_define_singleton_method(rb_cDFHack, "memory_stlstring_new", RUBY_METHOD_FUNC(rb_dfmemory_stlstring_new), 0); rb_define_singleton_method(rb_cDFHack, "memory_stlstring_delete", RUBY_METHOD_FUNC(rb_dfmemory_stlstring_delete), 1); From e825dc5ddb1f2c0798b571ebcb02a52e184fc689 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Fri, 24 Aug 2012 13:20:08 +0400 Subject: [PATCH 05/26] Tweak the API for current viewscreen, and dispatch show/dismiss from C++. --- LUA_API.rst | 24 +++++++++++++++++++++--- Lua API.html | 22 ++++++++++++++++++---- library/Core.cpp | 2 +- library/LuaApi.cpp | 1 + library/include/modules/Gui.h | 8 +++++--- library/include/modules/Screen.h | 8 +++++++- library/lua/gui.lua | 11 ++++------- library/modules/Gui.cpp | 16 +++++++++------- library/modules/Screen.cpp | 30 ++++++++++++++++++++++++++++-- scripts/gui/mechanisms.lua | 7 ++----- 10 files changed, 96 insertions(+), 33 deletions(-) diff --git a/LUA_API.rst b/LUA_API.rst index 76f6454f4..49e205235 100644 --- a/LUA_API.rst +++ b/LUA_API.rst @@ -724,15 +724,20 @@ can be omitted. Gui module ---------- -* ``dfhack.gui.getCurViewscreen()`` +* ``dfhack.gui.getCurViewscreen([skip_dismissed])`` - Returns the viewscreen that is current in the core. + Returns the topmost viewscreen. If ``skip_dismissed`` is *true*, + ignores screens already marked to be removed. * ``dfhack.gui.getFocusString(viewscreen)`` Returns a string representation of the current focus position in the ui. The string has a "screen/foo/bar/baz..." format. +* ``dfhack.gui.getCurFocus([skip_dismissed])`` + + Returns the focus string of the current viewscreen. + * ``dfhack.gui.getSelectedWorkshopJob([silent])`` When a job is selected in *'q'* mode, returns the job, else @@ -1291,9 +1296,10 @@ Screens are managed with the following functions: Displays the given screen, possibly placing it below a different one. The screen must not be already shown. Returns *true* if success. -* ``dfhack.screen.dismiss(screen)`` +* ``dfhack.screen.dismiss(screen[,to_first])`` Marks the screen to be removed when the game enters its event loop. + If ``to_first`` is *true*, all screens up to the first one will be deleted. * ``dfhack.screen.isDismissed(screen)`` @@ -1312,10 +1318,22 @@ Supported callbacks and fields are: Initialized by ``show`` with a reference to the backing viewscreen object, and removed again when the object is deleted. +* ``function screen:onShow()`` + + Called by ``dfhack.screen.show`` if successful. + +* ``function screen:onDismiss()`` + + Called by ``dfhack.screen.dismiss`` if successful. + * ``function screen:onDestroy()`` Called from the destructor when the viewscreen is deleted. +* ``function screen:onResize(w, h)`` + + Called before ``onRender`` or ``onIdle`` when the window size has changed. + * ``function screen:onRender()`` Called when the viewscreen should paint itself. This is the only context diff --git a/Lua API.html b/Lua API.html index c4ab9c8c4..15f1d89cb 100644 --- a/Lua API.html +++ b/Lua API.html @@ -987,13 +987,17 @@ can be omitted.

Gui module

    -
  • dfhack.gui.getCurViewscreen()

    -

    Returns the viewscreen that is current in the core.

    +
  • dfhack.gui.getCurViewscreen([skip_dismissed])

    +

    Returns the topmost viewscreen. If skip_dismissed is true, +ignores screens already marked to be removed.

  • dfhack.gui.getFocusString(viewscreen)

    Returns a string representation of the current focus position in the ui. The string has a "screen/foo/bar/baz..." format.

  • +
  • dfhack.gui.getCurFocus([skip_dismissed])

    +

    Returns the focus string of the current viewscreen.

    +
  • dfhack.gui.getSelectedWorkshopJob([silent])

    When a job is selected in 'q' mode, returns the job, else prints error unless silent and returns nil.

    @@ -1465,8 +1469,9 @@ interface screens added by dfhack should bear the "DFHack" signature.<

    Displays the given screen, possibly placing it below a different one. The screen must not be already shown. Returns true if success.

  • -
  • dfhack.screen.dismiss(screen)

    -

    Marks the screen to be removed when the game enters its event loop.

    +
  • dfhack.screen.dismiss(screen[,to_first])

    +

    Marks the screen to be removed when the game enters its event loop. +If to_first is true, all screens up to the first one will be deleted.

  • dfhack.screen.isDismissed(screen)

    Checks if the screen is already marked for removal.

    @@ -1482,9 +1487,18 @@ that delegates all processing to methods stored in that table.

    Initialized by show with a reference to the backing viewscreen object, and removed again when the object is deleted.

  • +
  • function screen:onShow()

    +

    Called by dfhack.screen.show if successful.

    +
  • +
  • function screen:onDismiss()

    +

    Called by dfhack.screen.dismiss if successful.

    +
  • function screen:onDestroy()

    Called from the destructor when the viewscreen is deleted.

  • +
  • function screen:onResize(w, h)

    +

    Called before onRender or onIdle when the window size has changed.

    +
  • function screen:onRender()

    Called when the viewscreen should paint itself. This is the only context where the above painting functions work correctly.

    diff --git a/library/Core.cpp b/library/Core.cpp index a61fef4e9..6a0dea7c2 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -1260,7 +1260,7 @@ bool Core::ncurses_wgetch(int in, int & out) // FIXME: copypasta, push into a method! if(df::global::ui && df::global::gview) { - df::viewscreen * ws = Gui::GetCurrentScreen(); + df::viewscreen * ws = Gui::getCurViewscreen(); if (strict_virtual_cast(ws) && df::global::ui->main.mode != ui_sidebar_mode::Hotkeys && df::global::ui->main.hotkeys[idx].cmd == df::ui_hotkey::T_cmd::None) diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index d25da8087..35cf1435b 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -749,6 +749,7 @@ static const LuaWrapper::FunctionReg dfhack_module[] = { static const LuaWrapper::FunctionReg dfhack_gui_module[] = { WRAPM(Gui, getCurViewscreen), WRAPM(Gui, getFocusString), + WRAPM(Gui, getCurFocus), WRAPM(Gui, getSelectedWorkshopJob), WRAPM(Gui, getSelectedJob), WRAPM(Gui, getSelectedUnit), diff --git a/library/include/modules/Gui.h b/library/include/modules/Gui.h index e7155c436..273d84cee 100644 --- a/library/include/modules/Gui.h +++ b/library/include/modules/Gui.h @@ -55,8 +55,6 @@ namespace DFHack */ namespace Gui { - inline df::viewscreen *getCurViewscreen() { return Core::getTopViewscreen(); } - DFHACK_EXPORT std::string getFocusString(df::viewscreen *top); // Full-screen item details view @@ -113,7 +111,11 @@ namespace DFHack * Gui screens */ /// Get the current top-level view-screen - DFHACK_EXPORT df::viewscreen * GetCurrentScreen(); + DFHACK_EXPORT df::viewscreen *getCurViewscreen(bool skip_dismissed = false); + + inline std::string getCurFocus(bool skip_dismissed = false) { + return getFocusString(getCurViewscreen(skip_dismissed)); + } /// get the size of the window buffer DFHACK_EXPORT bool getWindowSize(int32_t & width, int32_t & height); diff --git a/library/include/modules/Screen.h b/library/include/modules/Screen.h index ce3f32ed2..492e1eecc 100644 --- a/library/include/modules/Screen.h +++ b/library/include/modules/Screen.h @@ -112,7 +112,7 @@ namespace DFHack // Push and remove viewscreens DFHACK_EXPORT bool show(df::viewscreen *screen, df::viewscreen *before = NULL); - DFHACK_EXPORT void dismiss(df::viewscreen *screen); + DFHACK_EXPORT void dismiss(df::viewscreen *screen, bool to_first = false); DFHACK_EXPORT bool isDismissed(df::viewscreen *screen); } @@ -136,7 +136,10 @@ namespace DFHack virtual bool key_conflict(df::interface_key key); virtual bool is_lua_screen() { return false; } + virtual std::string getFocusString() = 0; + virtual void onShow() {}; + virtual void onDismiss() {}; }; class DFHACK_EXPORT dfhack_lua_viewscreen : public dfhack_viewscreen { @@ -166,5 +169,8 @@ namespace DFHack virtual void help(); virtual void resize(int w, int h); virtual void feed(std::set *keys); + + virtual void onShow(); + virtual void onDismiss(); }; } diff --git a/library/lua/gui.lua b/library/lua/gui.lua index f9a45548f..62e393f0b 100644 --- a/library/lua/gui.lua +++ b/library/lua/gui.lua @@ -232,9 +232,7 @@ function Screen:show(below) error("This screen is already on display") end self:onAboutToShow(below) - if dscreen.show(self, below) then - self:onShown() - else + if not dscreen.show(self, below) then error('Could not show screen') end end @@ -242,17 +240,16 @@ end function Screen:onAboutToShow() end -function Screen:onShown() +function Screen:onShow() end function Screen:dismiss() - if self._native and not dscreen.isDismissed(self) then + if self._native then dscreen.dismiss(self) - self:onDismissed() end end -function Screen:onDismissed() +function Screen:onDismiss() end ------------------------ diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index 9d3ee96eb..91a17e998 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -983,17 +983,19 @@ void Gui::showPopupAnnouncement(std::string message, int color, bool bright) world->status.popups.push_back(popup); } -df::viewscreen * Gui::GetCurrentScreen() +df::viewscreen *Gui::getCurViewscreen(bool skip_dismissed) { df::viewscreen * ws = &gview->view; - while(ws) + while (ws && ws->child) + ws = ws->child; + + if (skip_dismissed) { - if(ws->child) - ws = ws->child; - else - return ws; + while (ws && Screen::isDismissed(ws) && ws->parent) + ws = ws->parent; } - return 0; + + return ws; } bool Gui::getViewCoords (int32_t &x, int32_t &y, int32_t &z) diff --git a/library/modules/Screen.cpp b/library/modules/Screen.cpp index 9b6839a40..c2377f2ca 100644 --- a/library/modules/Screen.cpp +++ b/library/modules/Screen.cpp @@ -235,14 +235,26 @@ bool Screen::show(df::viewscreen *screen, df::viewscreen *before) if (screen->child) screen->child->parent = screen; + if (dfhack_viewscreen::is_instance(screen)) + static_cast(screen)->onShow(); + return true; } -void Screen::dismiss(df::viewscreen *screen) +void Screen::dismiss(df::viewscreen *screen, bool to_first) { CHECK_NULL_POINTER(screen); - screen->breakdown_level = interface_breakdown_types::STOPSCREEN; + if (screen->breakdown_level != interface_breakdown_types::NONE) + return; + + if (to_first) + screen->breakdown_level = interface_breakdown_types::TOFIRST; + else + screen->breakdown_level = interface_breakdown_types::STOPSCREEN; + + if (dfhack_viewscreen::is_instance(screen)) + static_cast(screen)->onDismiss(); } bool Screen::isDismissed(df::viewscreen *screen) @@ -261,6 +273,8 @@ static std::set dfhack_screens; dfhack_viewscreen::dfhack_viewscreen() : text_input_mode(false) { dfhack_screens.insert(this); + + last_size = Screen::getWindowSize(); } dfhack_viewscreen::~dfhack_viewscreen() @@ -576,3 +590,15 @@ void dfhack_lua_viewscreen::feed(std::set *keys) lua_pushlightuserdata(Lua::Core::State, keys); safe_call_lua(do_input, 1, 0); } + +void dfhack_lua_viewscreen::onShow() +{ + lua_pushstring(Lua::Core::State, "onShow"); + safe_call_lua(do_notify, 1, 0); +} + +void dfhack_lua_viewscreen::onDismiss() +{ + lua_pushstring(Lua::Core::State, "onDismiss"); + safe_call_lua(do_notify, 1, 0); +} diff --git a/scripts/gui/mechanisms.lua b/scripts/gui/mechanisms.lua index fe45d4acd..3b40ffbd7 100644 --- a/scripts/gui/mechanisms.lua +++ b/scripts/gui/mechanisms.lua @@ -142,11 +142,8 @@ function MechanismList:onInput(keys) end end -if not df.viewscreen_dwarfmodest:is_instance(dfhack.gui.getCurViewscreen()) then - qerror("This script requires the main dwarfmode view") -end -if df.global.ui.main.mode ~= df.ui_sidebar_mode.QueryBuilding then - qerror("This script requires the 'q' interface mode") +if dfhack.gui.getCurFocus() ~= 'dwarfmode/QueryBuilding/Some' then + qerror("This script requires the main dwarfmode view in 'q' mode") end local list = MechanismList.new(df.global.world.selected_building) From 296f82b02fcd0801d6614bb45da05c88ed7c88d2 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Fri, 24 Aug 2012 13:28:34 +0400 Subject: [PATCH 06/26] Try using the Objective-C 'alloc + init' idiom for lua screen objects. --- library/lua/dfhack.lua | 26 +++++++--------- library/lua/gui.lua | 57 ++++++++++++++++++++++++++++++++--- library/lua/gui/dwarfmode.lua | 17 +++++------ scripts/gui/hello-world.lua | 2 +- scripts/gui/mechanisms.lua | 18 +++++------ 5 files changed, 82 insertions(+), 38 deletions(-) diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua index 5699e8a20..2cbd019a6 100644 --- a/library/lua/dfhack.lua +++ b/library/lua/dfhack.lua @@ -104,11 +104,21 @@ end -- Trivial classes +function rawset_default(target,source) + for k,v in pairs(source) do + if rawget(target,k) == nil then + rawset(target,k,v) + end + end +end + function defclass(class,parent) class = class or {} - rawset(class, '__index', rawget(class, '__index') or class) + rawset_default(class, { __index = class }) if parent then setmetatable(class, parent) + else + rawset_default(class, { init_fields = rawset_default }) end return class end @@ -153,14 +163,6 @@ function xyz2pos(x,y,z) end end -function rawset_default(target,source) - for k,v in pairs(source) do - if rawget(target,k) == nil then - rawset(target,k,v) - end - end -end - function safe_index(obj,idx,...) if obj == nil or idx == nil then return nil @@ -202,12 +204,6 @@ function dfhack.buildings.getSize(bld) return bld.x2+1-x, bld.y2+1-y, bld.centerx-x, bld.centery-y end -dfhack.screen.__index = dfhack.screen - -function dfhack.screen:__tostring() - return "" -end - -- Interactive local print_banner = true diff --git a/library/lua/gui.lua b/library/lua/gui.lua index 62e393f0b..ac032166f 100644 --- a/library/lua/gui.lua +++ b/library/lua/gui.lua @@ -4,6 +4,8 @@ local _ENV = mkmodule('gui') local dscreen = dfhack.screen +USE_GRAPHICS = dscreen.inGraphicsMode() + CLEAR_PEN = {ch=32,fg=0,bg=0} function simulateInput(screen,...) @@ -52,6 +54,9 @@ function inset(rect,dx1,dy1,dx2,dy2) rect.x2-(dx2 or dx1), rect.y2-(dy2 or dy1) ) end +function is_in_rect(rect,x,y) + return x and y and x >= rect.x1 and x <= rect.x2 and y >= rect.y1 and y <= rect.y2 +end local function to_pen(default, pen, bg, bold) if pen == nil then @@ -201,10 +206,17 @@ end -- Base screen object -- ------------------------ -Screen = defclass(Screen, dfhack.screen) +Screen = defclass(Screen) Screen.text_input_mode = false +function Screen:init() + self:updateLayout() + return self +end + +Screen.isDismissed = dscreen.isDismissed + function Screen:isShown() return self._native ~= nil end @@ -213,6 +225,18 @@ function Screen:isActive() return self:isShown() and not self:isDismissed() end +function Screen:invalidate() + dscreen.invalidate() +end + +function Screen:getWindowSize() + return dscreen.getWindowSize() +end + +function Screen:getMousePos() + return dscreen.getMousePos() +end + function Screen:renderParent() if self._native and self._native.parent then self._native.parent:render() @@ -241,6 +265,7 @@ function Screen:onAboutToShow() end function Screen:onShow() + self:updateLayout() end function Screen:dismiss() @@ -252,6 +277,13 @@ end function Screen:onDismiss() end +function Screen:onResize(w,h) + self:updateLayout() +end + +function Screen:updateLayout() +end + ------------------------ -- Framed screen object -- ------------------------ @@ -318,7 +350,8 @@ local function hint_coord(gap,hint) end end -function FramedScreen:updateFrameSize(sw,sh) +function FramedScreen:updateFrameSize() + local sw, sh = dscreen.getWindowSize() local iw, ih = sw-2, sh-2 local width = math.min(self.frame_width or iw, iw) local height = math.min(self.frame_height or ih, ih) @@ -328,8 +361,21 @@ function FramedScreen:updateFrameSize(sw,sh) self.frame_opaque = (gw == 0 and gh == 0) end -function FramedScreen:onResize(w,h) - self:updateFrameSize(w,h) +function FramedScreen:updateLayout() + self:updateFrameSize() +end + +function FramedScreen:getWindowSize() + local rect = self.frame_rect + return rect.width, rect.height +end + +function FramedScreen:getMousePos() + local rect = self.frame_rect + local x,y = dscreen.getMousePos() + if is_in_rect(rect,x,y) then + return x-rect.x1, y-rect.y1 + end end function FramedScreen:onRender() @@ -348,4 +394,7 @@ function FramedScreen:onRender() self:onRenderBody(Painter.new(rect)) end +function FramedScreen:onRenderBody(dc) +end + return _ENV diff --git a/library/lua/gui/dwarfmode.lua b/library/lua/gui/dwarfmode.lua index dde5225a1..b5ffa7280 100644 --- a/library/lua/gui/dwarfmode.lua +++ b/library/lua/gui/dwarfmode.lua @@ -179,14 +179,6 @@ function DwarfOverlay:updateLayout() self.df_layout = getPanelLayout() end -function DwarfOverlay:onShown() - self:updateLayout() -end - -function DwarfOverlay:onResize(w,h) - self:updateLayout() -end - function DwarfOverlay:getViewport(old_vp) if old_vp then return old_vp:resize(self.df_layout) @@ -238,6 +230,14 @@ end MenuOverlay = defclass(MenuOverlay, DwarfOverlay) +function MenuOverlay:updateLayout() + DwarfOverlay.updateLayout(self) + self.frame_rect = self.df_layout.menu +end + +MenuOverlay.getWindowSize = gui.FramedScreen.getWindowSize +MenuOverlay.getMousePos = gui.FramedScreen.getMousePos + function MenuOverlay:onAboutToShow(below) DwarfOverlay.onAboutToShow(self,below) @@ -249,7 +249,6 @@ end function MenuOverlay:onRender() self:renderParent() - self:updateLayout() local menu = self.df_layout.menu if menu then diff --git a/scripts/gui/hello-world.lua b/scripts/gui/hello-world.lua index b67e50124..80986bbf6 100644 --- a/scripts/gui/hello-world.lua +++ b/scripts/gui/hello-world.lua @@ -17,6 +17,6 @@ local screen = mkinstance(gui.FramedScreen, { self:dismiss() end end -}) +}):init() screen:show() diff --git a/scripts/gui/mechanisms.lua b/scripts/gui/mechanisms.lua index 3b40ffbd7..d33d55a92 100644 --- a/scripts/gui/mechanisms.lua +++ b/scripts/gui/mechanisms.lua @@ -52,15 +52,16 @@ MechanismList = defclass(MechanismList, guidm.MenuOverlay) MechanismList.focus_path = 'mechanisms' -function MechanismList.new(building) - local self = { - links = {}, - selected = 1 +function MechanismList:init(building) + self:init_fields{ + links = {}, selected = 1 } - return mkinstance(MechanismList, self):init(building) + guidm.MenuOverlay.init(self) + self:fillList(building) + return self end -function MechanismList:init(building) +function MechanismList:fillList(building) local links = listMechanismLinks(building) links[1].viewport = self:getViewport() @@ -71,7 +72,6 @@ function MechanismList:init(building) self.links = links self.selected = 1 - return self end local colors = { @@ -133,7 +133,7 @@ function MechanismList:onInput(keys) end elseif keys.SELECT_ALL then if self.selected > 1 then - self:init(self.links[self.selected].obj) + self:fillList(self.links[self.selected].obj) end elseif keys.SELECT then self:dismiss() @@ -146,6 +146,6 @@ if dfhack.gui.getCurFocus() ~= 'dwarfmode/QueryBuilding/Some' then qerror("This script requires the main dwarfmode view in 'q' mode") end -local list = MechanismList.new(df.global.world.selected_building) +local list = mkinstance(MechanismList):init(df.global.world.selected_building) list:show() list:changeSelected(1) From d52c54cc762e4f525b4d1f3c339e9cf5a1230ee7 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Fri, 24 Aug 2012 13:49:22 +0400 Subject: [PATCH 07/26] Pull some minor stuff from mechanisms into main libs. --- library/lua/gui/dwarfmode.lua | 14 ++++++++++++++ library/lua/utils.lua | 8 ++++++++ scripts/gui/mechanisms.lua | 32 ++++++-------------------------- 3 files changed, 28 insertions(+), 26 deletions(-) diff --git a/library/lua/gui/dwarfmode.lua b/library/lua/gui/dwarfmode.lua index b5ffa7280..c1a8bcb95 100644 --- a/library/lua/gui/dwarfmode.lua +++ b/library/lua/gui/dwarfmode.lua @@ -3,6 +3,8 @@ local _ENV = mkmodule('gui.dwarfmode') local gui = require('gui') +local utils = require('utils') + local dscreen = dfhack.screen local world_map = df.global.world.map @@ -187,6 +189,18 @@ function DwarfOverlay:getViewport(old_vp) end end +function DwarfOverlay:moveCursorTo(cursor,viewport) + setCursorPos(cursor) + self:getViewport(viewport):reveal(cursor, 5, 0, 10):set() +end + +function DwarfOverlay:selectBuilding(building,cursor,viewport) + cursor = cursor or utils.getBuildingCenter(building) + + df.global.world.selected_building = building + self:moveCursorTo(cursor, viewport) +end + function DwarfOverlay:propagateMoveKeys(keys) for code,_ in pairs(MOVEMENT_KEYS) do if keys[code] then diff --git a/library/lua/utils.lua b/library/lua/utils.lua index 009bdf985..19a4e6f6a 100644 --- a/library/lua/utils.lua +++ b/library/lua/utils.lua @@ -373,6 +373,14 @@ function call_with_string(obj,methodname,...) ) end +function getBuildingName(building) + return call_with_string(building, 'getName') +end + +function getBuildingCenter(building) + return xyz2pos(building.centerx, building.centery, building.z) +end + -- Ask a yes-no question function prompt_yes_no(msg,default) local prompt = msg diff --git a/scripts/gui/mechanisms.lua b/scripts/gui/mechanisms.lua index d33d55a92..6b4b4042b 100644 --- a/scripts/gui/mechanisms.lua +++ b/scripts/gui/mechanisms.lua @@ -4,22 +4,13 @@ local utils = require 'utils' local gui = require 'gui' local guidm = require 'gui.dwarfmode' -function getBuildingName(building) - return utils.call_with_string(building, 'getName') -end - -function getBuildingCenter(building) - return xyz2pos(building.centerx, building.centery, building.z) -end - function listMechanismLinks(building) local lst = {} local function push(item, mode) if item then lst[#lst+1] = { obj = item, mode = mode, - name = getBuildingName(item), - center = getBuildingCenter(item) + name = utils.getBuildingName(item) } end end @@ -64,8 +55,9 @@ end function MechanismList:fillList(building) local links = listMechanismLinks(building) - links[1].viewport = self:getViewport() - links[1].cursor = guidm.getCursorPos() + self.old_viewport = self:getViewport() + self.old_cursor = guidm.getCursorPos() + if #links <= 1 then links[1].mode = 'none' end @@ -103,22 +95,10 @@ function MechanismList:onRenderBody(dc) dc:string("Enter", COLOR_LIGHTGREEN):string(": Switch") end -function MechanismList:zoomToLink(link,back) - df.global.world.selected_building = link.obj - - if back then - guidm.setCursorPos(link.cursor) - self:getViewport(link.viewport):set() - else - guidm.setCursorPos(link.center) - self:getViewport():reveal(link.center, 5, 0, 10):set() - end -end - function MechanismList:changeSelected(delta) if #self.links <= 1 then return end self.selected = 1 + (self.selected + delta - 1) % #self.links - self:zoomToLink(self.links[self.selected]) + self:selectBuilding(self.links[self.selected].obj) end function MechanismList:onInput(keys) @@ -129,7 +109,7 @@ function MechanismList:onInput(keys) elseif keys.LEAVESCREEN then self:dismiss() if self.selected ~= 1 then - self:zoomToLink(self.links[1], true) + self:selectBuilding(self.links[1].obj, self.old_cursor, self.old_view) end elseif keys.SELECT_ALL then if self.selected > 1 then From 8359e80b233675ee347ff1ac8ec7d37430d52a7a Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Fri, 24 Aug 2012 18:26:18 +0400 Subject: [PATCH 08/26] Expose a few API functions to lua, and implement a room browser overlay. --- LUA_API.rst | 14 ++ Lua API.html | 11 ++ dfhack.init-example | 3 + library/LuaApi.cpp | 3 + library/include/modules/Buildings.h | 5 + library/modules/Buildings.cpp | 39 +++++ scripts/gui/room-list.lua | 246 ++++++++++++++++++++++++++++ 7 files changed, 321 insertions(+) create mode 100644 scripts/gui/room-list.lua diff --git a/LUA_API.rst b/LUA_API.rst index 49e205235..a7dab21b0 100644 --- a/LUA_API.rst +++ b/LUA_API.rst @@ -880,6 +880,15 @@ Units module Retrieves the profession name for the given race/caste using raws. +* ``dfhack.units.getProfessionColor(unit[,ignore_noble])`` + + Retrieves the color associated with the profession, using noble assignments + or raws. The ``ignore_noble`` boolean disables the use of noble positions. + +* ``dfhack.units.getCasteProfessionColor(race,caste,prof_id)`` + + Retrieves the profession color for the given race/caste using raws. + Items module ------------ @@ -1032,6 +1041,11 @@ Burrows module Buildings module ---------------- +* ``dfhack.buildings.setOwner(item,unit)`` + + Replaces the owner of the building. If unit is *nil*, removes ownership. + Returns *false* in case of error. + * ``dfhack.buildings.getSize(building)`` Returns *width, height, centerx, centery*. diff --git a/Lua API.html b/Lua API.html index 15f1d89cb..b9f09cf96 100644 --- a/Lua API.html +++ b/Lua API.html @@ -1113,6 +1113,13 @@ or raws. The ignore_noble boolean disables the
  • dfhack.units.getCasteProfessionName(race,caste,prof_id[,plural])

    Retrieves the profession name for the given race/caste using raws.

  • +
  • dfhack.units.getProfessionColor(unit[,ignore_noble])

    +

    Retrieves the color associated with the profession, using noble assignments +or raws. The ignore_noble boolean disables the use of noble positions.

    +
  • +
  • dfhack.units.getCasteProfessionColor(race,caste,prof_id)

    +

    Retrieves the profession color for the given race/caste using raws.

    +
@@ -1237,6 +1244,10 @@ burrows, or the presence of invaders.

Buildings module

    +
  • dfhack.buildings.setOwner(item,unit)

    +

    Replaces the owner of the building. If unit is nil, removes ownership. +Returns false in case of error.

    +
  • dfhack.buildings.getSize(building)

    Returns width, height, centerx, centery.

  • diff --git a/dfhack.init-example b/dfhack.init-example index f5f40196c..c9408e375 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -45,3 +45,6 @@ keybinding add Shift-G "job-material GLASS_GREEN" # browse linked mechanisms keybinding add Ctrl-M@dwarfmode/QueryBuilding/Some gui/mechanisms + +# browse rooms of same owner +keybinding add Alt-R@dwarfmode/QueryBuilding/Some gui/room-list.work diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 35cf1435b..00d4c517d 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -815,6 +815,8 @@ static const LuaWrapper::FunctionReg dfhack_units_module[] = { WRAPM(Units, getAge), WRAPM(Units, getProfessionName), WRAPM(Units, getCasteProfessionName), + WRAPM(Units, getProfessionColor), + WRAPM(Units, getCasteProfessionColor), { NULL, NULL } }; @@ -985,6 +987,7 @@ static bool buildings_containsTile(df::building *bld, int x, int y, bool room) { } static const LuaWrapper::FunctionReg dfhack_buildings_module[] = { + WRAPM(Buildings, setOwner), WRAPM(Buildings, allocInstance), WRAPM(Buildings, checkFreeTiles), WRAPM(Buildings, countExtentTiles), diff --git a/library/include/modules/Buildings.h b/library/include/modules/Buildings.h index 6e0a22052..639df6865 100644 --- a/library/include/modules/Buildings.h +++ b/library/include/modules/Buildings.h @@ -92,6 +92,11 @@ DFHACK_EXPORT bool Read (const uint32_t index, t_building & building); */ DFHACK_EXPORT bool ReadCustomWorkshopTypes(std::map & btypes); +/** + * Sets the owner unit for the building. + */ +DFHACK_EXPORT bool setOwner(df::building *building, df::unit *owner); + /** * Find the building located at the specified tile. * Does not work on civzones. diff --git a/library/modules/Buildings.cpp b/library/modules/Buildings.cpp index 8ec60e55b..d1aed8979 100644 --- a/library/modules/Buildings.cpp +++ b/library/modules/Buildings.cpp @@ -49,6 +49,7 @@ using namespace DFHack; #include "df/ui_look_list.h" #include "df/d_init.h" #include "df/item.h" +#include "df/unit.h" #include "df/job.h" #include "df/job_item.h" #include "df/general_ref_building_holderst.h" @@ -177,6 +178,44 @@ bool Buildings::ReadCustomWorkshopTypes(map & btypes) return true; } +bool Buildings::setOwner(df::building *bld, df::unit *unit) +{ + CHECK_NULL_POINTER(bld); + + if (!bld->is_room) + return false; + if (bld->owner == unit) + return true; + + if (bld->owner) + { + auto &blist = bld->owner->owned_buildings; + vector_erase_at(blist, linear_index(blist, bld)); + + if (auto spouse = df::unit::find(bld->owner->relations.spouse_id)) + { + auto &blist = spouse->owned_buildings; + vector_erase_at(blist, linear_index(blist, bld)); + } + } + + bld->owner = unit; + + if (unit) + { + unit->owned_buildings.push_back(bld); + + if (auto spouse = df::unit::find(unit->relations.spouse_id)) + { + auto &blist = spouse->owned_buildings; + if (bld->canUseSpouseRoom() && linear_index(blist, bld) < 0) + blist.push_back(bld); + } + } + + return true; +} + df::building *Buildings::findAtTile(df::coord pos) { auto occ = Maps::getTileOccupancy(pos); diff --git a/scripts/gui/room-list.lua b/scripts/gui/room-list.lua new file mode 100644 index 000000000..a4507466f --- /dev/null +++ b/scripts/gui/room-list.lua @@ -0,0 +1,246 @@ +-- Browses rooms owned by a unit. + +local utils = require 'utils' +local gui = require 'gui' +local guidm = require 'gui.dwarfmode' + +local room_type_table = { + [df.building_bedst] = { token = 'bed', qidx = 2, tile = 233 }, + [df.building_tablest] = { token = 'table', qidx = 3, tile = 209 }, + [df.building_chairst] = { token = 'chair', qidx = 4, tile = 210 }, + [df.building_coffinst] = { token = 'coffin', qidx = 5, tile = 48 }, +} + +local room_quality_table = { + { 1, 'Meager Quarters', 'Meager Dining Room', 'Meager Office', 'Grave' }, + { 100, 'Modest Quarters', 'Modest Dining Room', 'Modest Office', "Servant's Burial Chamber" }, + { 250, 'Quarters', 'Dining Room', 'Office', 'Burial Chamber' }, + { 500, 'Decent Quarters', 'Decent Dining Room', 'Decent Office', 'Tomb' }, + { 1000, 'Fine Quarters', 'Fine Dining Room', 'Splendid Office', 'Fine Tomb' }, + { 1500, 'Great Bedroom', 'Great Dining Room', 'Throne Room', 'Mausoleum' }, + { 2500, 'Grand Bedroom', 'Grand Dining Room', 'Opulent Throne Room', 'Grand Mausoleum' }, + { 10000, 'Royal Bedroom', 'Royal Dining Room', 'Royal Throne Room', 'Royal Mausoleum' } +} + +function getRoomName(building, unit) + local info = room_type_table[building._type] + if not info or not building.is_room then + return utils.getBuildingName(building) + end + + local quality = building:getRoomValue(unit) + local row = room_quality_table[1] + for _,v in ipairs(room_quality_table) do + if v[1] <= quality then + row = v + else + break + end + end + return row[info.qidx] +end + +function makeRoomEntry(bld, unit, is_spouse) + local info = room_type_table[bld._type] or {} + + return { + obj = bld, + token = info.token or '?', + tile = info.tile or '?', + caption = getRoomName(bld, unit), + can_use = (not is_spouse or bld:canUseSpouseRoom()), + owner = unit + } +end + +function listRooms(unit, spouse) + local rv = {} + for _,v in pairs(unit.owned_buildings) do + if v.owner == unit then + rv[#rv+1] = makeRoomEntry(v, unit, spouse) + end + end + return rv +end + +function concat_lists(...) + local rv = {} + for i = 1,select('#',...) do + local v = select(i,...) + if v then + for _,x in ipairs(v) do rv[#rv+1] = x end + end + end + return rv +end + +RoomList = defclass(RoomList, guidm.MenuOverlay) + +RoomList.focus_path = 'room-list' + +function RoomList:init(unit) + local base_bld = df.global.world.selected_building + + self:init_fields{ + unit = unit, base_building = base_bld, + items = {}, selected = 1, + own_rooms = {}, spouse_rooms = {} + } + guidm.MenuOverlay.init(self) + + self.old_viewport = self:getViewport() + self.old_cursor = guidm.getCursorPos() + + if unit then + self.own_rooms = listRooms(unit) + self.spouse = df.unit.find(unit.relations.spouse_id) + if self.spouse then + self.spouse_rooms = listRooms(self.spouse, unit) + end + self.items = concat_lists(self.own_rooms, self.spouse_rooms) + end + + if base_bld then + for i,v in ipairs(self.items) do + if v.obj == base_bld then + self.selected = i + v.tile = 26 + goto found + end + end + self.base_item = makeRoomEntry(base_bld, unit) + self.base_item.owner = unit + self.base_item.old_owner = base_bld.owner + self.base_item.tile = 26 + self.items = concat_lists({self.base_item}, self.items) + ::found:: + end + + return self +end + +local sex_char = { [0] = 12, [1] = 11 } + +function drawUnitName(dc, unit) + dc:pen(COLOR_GREY) + if unit then + local color = dfhack.units.getProfessionColor(unit) + dc:char(sex_char[unit.sex] or '?'):advance(1):pen(color) + + local vname = dfhack.units.getVisibleName(unit) + if vname and vname.has_name then + dc:string(dfhack.TranslateName(vname)..', ') + end + dc:string(dfhack.units.getProfessionName(unit)) + else + dc:string("No Owner Assigned") + end +end + +function drawRoomEntry(dc, entry, selected) + local color = COLOR_GREEN + if not entry.can_use then + color = COLOR_RED + elseif entry.obj.owner ~= entry.owner or not entry.owner then + color = COLOR_CYAN + end + dc:pen{fg = color, bold = (selected == entry)} + dc:char(entry.tile):advance(1):string(entry.caption) +end + +function can_modify(sel_item) + return sel_item and sel_item.owner + and sel_item.can_use and not sel_item.owner.flags1.dead +end + +function RoomList:onRenderBody(dc) + local sel_item = self.items[self.selected] + + dc:clear():seek(1,1) + drawUnitName(dc, self.unit) + + if self.base_item then + dc:newline():newline(2) + drawRoomEntry(dc, self.base_item, sel_item) + end + if #self.own_rooms > 0 then + dc:newline() + for _,v in ipairs(self.own_rooms) do + dc:newline(2) + drawRoomEntry(dc, v, sel_item) + end + end + if #self.spouse_rooms > 0 then + dc:newline():newline(1) + drawUnitName(dc, self.spouse) + + dc:newline() + for _,v in ipairs(self.spouse_rooms) do + dc:newline(2) + drawRoomEntry(dc, v, sel_item) + end + end + if self.unit and #self.own_rooms == 0 and #self.spouse_rooms == 0 then + dc:newline():newline(2):string("No already assigned rooms.", COLOR_LIGHTRED) + end + + dc:newline():newline(1):pen(COLOR_WHITE) + dc:string("Esc", COLOR_LIGHTGREEN):string(": Back") + + if can_modify(sel_item) then + dc:string(", "):string("Enter", COLOR_LIGHTGREEN) + if sel_item.obj.owner == sel_item.owner then + dc:string(": Unassign") + else + dc:string(": Assign") + end + end +end + +function RoomList:changeSelected(delta) + if #self.items <= 1 then return end + self.selected = 1 + (self.selected + delta - 1) % #self.items + self:selectBuilding(self.items[self.selected].obj) +end + +function RoomList:onInput(keys) + local sel_item = self.items[self.selected] + + if keys.SECONDSCROLL_UP then + self:changeSelected(-1) + elseif keys.SECONDSCROLL_DOWN then + self:changeSelected(1) + elseif keys.LEAVESCREEN then + self:dismiss() + + if self.base_building then + if not sel_item or self.base_building ~= sel_item.obj then + self:selectBuilding(self.base_building, self.old_cursor, self.old_view) + end + if self.unit and self.base_building.owner == self.unit then + df.global.ui_building_in_assign = false + end + end + elseif keys.SELECT then + if can_modify(sel_item) then + local owner = sel_item.owner + if sel_item.obj.owner == owner then + owner = sel_item.old_owner + end + dfhack.buildings.setOwner(sel_item.obj, owner) + end + elseif self:simulateViewScroll(keys) then + return + end +end + +local focus = dfhack.gui.getCurFocus() +if focus == 'dwarfmode/QueryBuilding/Some' then + local base = df.global.world.selected_building + mkinstance(RoomList):init(base.owner):show() +elseif focus == 'dwarfmode/QueryBuilding/Some/Assign/Unit' then + local unit = df.global.ui_building_assign_units[df.global.ui_building_item_cursor] + mkinstance(RoomList):init(unit):show() +else + qerror("This script requires the main dwarfmode view in 'q' mode") +end From 9154564e71caa378dcc3266e3e974b7b51f296f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Fri, 24 Aug 2012 21:56:16 +0200 Subject: [PATCH 09/26] Sync up submodules --- library/xml | 2 +- plugins/stonesense | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/xml b/library/xml index 1eeaa0836..abcb667bc 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 1eeaa08360c39a9a2d811544c2443309adc1a8f1 +Subproject commit abcb667bc832048552d8cbc8f4830936f8b63399 diff --git a/plugins/stonesense b/plugins/stonesense index 5d4f06d78..2a62ba5ed 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit 5d4f06d785f8a9933679fe3caa12c18215e9674d +Subproject commit 2a62ba5ed2607f4dbf0473e77502d4e19c19678e From 41ad42d0fdeae7d387186e66d9eb4117a3cf9e7d Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 25 Aug 2012 10:37:03 +0400 Subject: [PATCH 10/26] Expose the liquids plugin engine to lua, and make a wrapper gui script. --- dfhack.init-example | 3 + library/include/modules/Gui.h | 3 + library/lua/gui.lua | 5 +- library/lua/gui/dwarfmode.lua | 52 +++++- library/modules/Gui.cpp | 17 ++ plugins/liquids.cpp | 328 +++++++++++++++++++++------------- plugins/lua/liquids.lua | 11 ++ scripts/gui/liquids.lua | 201 +++++++++++++++++++++ 8 files changed, 491 insertions(+), 129 deletions(-) create mode 100644 plugins/lua/liquids.lua create mode 100644 scripts/gui/liquids.lua diff --git a/dfhack.init-example b/dfhack.init-example index c9408e375..552b2b3a1 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -48,3 +48,6 @@ keybinding add Ctrl-M@dwarfmode/QueryBuilding/Some gui/mechanisms # browse rooms of same owner keybinding add Alt-R@dwarfmode/QueryBuilding/Some gui/room-list.work + +# interface for the liquids plugin +keybinding add Alt-L@dwarfmode/LookAround gui/liquids diff --git a/library/include/modules/Gui.h b/library/include/modules/Gui.h index 273d84cee..58f222419 100644 --- a/library/include/modules/Gui.h +++ b/library/include/modules/Gui.h @@ -97,6 +97,9 @@ namespace DFHack /* * Cursor and window coords */ + DFHACK_EXPORT df::coord getViewportPos(); + DFHACK_EXPORT df::coord getCursorPos(); + DFHACK_EXPORT bool getViewCoords (int32_t &x, int32_t &y, int32_t &z); DFHACK_EXPORT bool setViewCoords (const int32_t x, const int32_t y, const int32_t z); diff --git a/library/lua/gui.lua b/library/lua/gui.lua index ac032166f..9e189ea13 100644 --- a/library/lua/gui.lua +++ b/library/lua/gui.lua @@ -18,7 +18,7 @@ function simulateInput(screen,...) error('Invalid keycode: '..arg) end end - if type(arg) == 'number' then + if type(kv) == 'number' then keys[#keys+1] = kv end end @@ -277,6 +277,9 @@ end function Screen:onDismiss() end +function Screen:onDestroy() +end + function Screen:onResize(w,h) self:updateLayout() end diff --git a/library/lua/gui/dwarfmode.lua b/library/lua/gui/dwarfmode.lua index c1a8bcb95..1f7ae1b03 100644 --- a/library/lua/gui/dwarfmode.lua +++ b/library/lua/gui/dwarfmode.lua @@ -6,6 +6,9 @@ local gui = require('gui') local utils = require('utils') local dscreen = dfhack.screen + +local g_cursor = df.global.cursor +local g_sel_rect = df.global.selection_rect local world_map = df.global.world.map AREA_MAP_WIDTH = 23 @@ -43,8 +46,8 @@ function getPanelLayout() end function getCursorPos() - if df.global.cursor.x ~= -30000 then - return copyall(df.global.cursor) + if g_cursor ~= -30000 then + return copyall(g_cursor) end end @@ -56,6 +59,51 @@ function clearCursorPos() df.global.cursor = xyz2pos(nil) end +function getSelection() + local p1, p2 + if g_sel_rect.start_x ~= -30000 then + p1 = xyz2pos(g_sel_rect.start_x, g_sel_rect.start_y, g_sel_rect.start_z) + end + if g_sel_rect.end_x ~= -30000 then + p2 = xyz2pos(g_sel_rect.end_x, g_sel_rect.end_y, g_sel_rect.end_z) + end + return p1, p2 +end + +function setSelectionStart(pos) + g_sel_rect.start_x = pos.x + g_sel_rect.start_y = pos.y + g_sel_rect.start_z = pos.z +end + +function setSelectionEnd(pos) + g_sel_rect.end_x = pos.x + g_sel_rect.end_y = pos.y + g_sel_rect.end_z = pos.z +end + +function clearSelection() + g_sel_rect.start_x = -30000 + g_sel_rect.start_y = -30000 + g_sel_rect.start_z = -30000 + g_sel_rect.end_x = -30000 + g_sel_rect.end_y = -30000 + g_sel_rect.end_z = -30000 +end + +function getSelectionRange(p1, p2) + local r1 = xyz2pos( + math.min(p1.x, p2.x), math.min(p1.y, p2.y), math.min(p1.z, p2.z) + ) + local r2 = xyz2pos( + math.max(p1.x, p2.x), math.max(p1.y, p2.y), math.max(p1.z, p2.z) + ) + local sz = xyz2pos( + r2.x - r1.x + 1, r2.y - r1.y + 1, r2.z - r1.z + 1 + ) + return r1, sz, r2 +end + Viewport = defclass(Viewport) function Viewport.make(map,x,y,z) diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index 91a17e998..0f28860bf 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -998,6 +998,23 @@ df::viewscreen *Gui::getCurViewscreen(bool skip_dismissed) return ws; } +df::coord Gui::getViewportPos() +{ + if (!df::global::window_x || !df::global::window_y || !df::global::window_z) + return df::coord(0,0,0); + + return df::coord(*df::global::window_x, *df::global::window_y, *df::global::window_z); +} + +df::coord Gui::getCursorPos() +{ + using df::global::cursor; + if (!cursor) + return df::coord(); + + return df::coord(cursor->x, cursor->y, cursor->z); +} + bool Gui::getViewCoords (int32_t &x, int32_t &y, int32_t &z) { x = *df::global::window_x; diff --git a/plugins/liquids.cpp b/plugins/liquids.cpp index b036e4fa8..b078b48fd 100644 --- a/plugins/liquids.cpp +++ b/plugins/liquids.cpp @@ -27,6 +27,7 @@ #include #include #include +#include using std::vector; using std::string; using std::endl; @@ -41,6 +42,7 @@ using std::set; #include "modules/Gui.h" #include "TileTypes.h" #include "modules/MapCache.h" +#include "LuaTools.h" #include "Brushes.h" using namespace MapExtras; using namespace DFHack; @@ -50,7 +52,6 @@ CommandHistory liquids_hist; command_result df_liquids (color_ostream &out, vector & parameters); command_result df_liquids_here (color_ostream &out, vector & parameters); -command_result df_liquids_execute (color_ostream &out); DFHACK_PLUGIN("liquids"); @@ -74,13 +75,49 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out ) return CR_OK; } -// static stuff to be remembered between sessions -static string brushname = "point"; -static string mode="magma"; -static string flowmode="f+"; -static string _setmode ="s."; -static unsigned int amount = 7; -static int width = 1, height = 1, z_levels = 1; +enum BrushType { + B_POINT, B_RANGE, B_BLOCK, B_COLUMN, B_FLOOD +}; + +static const char *brush_name[] = { + "point", "range", "block", "column", "flood", NULL +}; + +enum PaintMode { + P_WATER, P_MAGMA, P_OBSIDIAN, P_OBSIDIAN_FLOOR, + P_RIVER_SOURCE, P_FLOW_BITS, P_WCLEAN +}; + +static const char *paint_mode_name[] = { + "water", "magma", "obsidian", "obsidian_floor", + "riversource", "flowbits", "wclean", NULL +}; + +enum ModifyMode { + M_INC, M_KEEP, M_DEC +}; + +static const char *modify_mode_name[] = { + "+", ".", "-", NULL +}; + +struct OperationMode { + BrushType brush; + PaintMode paint; + ModifyMode flowmode; + ModifyMode setmode; + unsigned int amount; + df::coord size; + + OperationMode() : + brush(B_POINT), paint(P_MAGMA), + flowmode(M_INC), setmode(M_KEEP), amount(7), + size(1,1,1) + {} +} cur_mode; + +command_result df_liquids_execute(color_ostream &out); +command_result df_liquids_execute(color_ostream &out, OperationMode &mode, df::coord pos); command_result df_liquids (color_ostream &out_, vector & parameters) { @@ -117,10 +154,11 @@ command_result df_liquids (color_ostream &out_, vector & parameters) string input = ""; std::stringstream str; - str <<"[" << mode << ":" << brushname; - if (brushname == "range") - str << "(w" << width << ":h" << height << ":z" << z_levels << ")"; - str << ":" << amount << ":" << flowmode << ":" << _setmode << "]#"; + str <<"[" << paint_mode_name[cur_mode.paint] << ":" << brush_name[cur_mode.brush]; + if (cur_mode.brush == B_RANGE) + str << "(w" << cur_mode.size.x << ":h" << cur_mode.size.y << ":z" << cur_mode.size.z << ")"; + str << ":" << cur_mode.amount << ":f" << modify_mode_name[cur_mode.flowmode] + << ":s" << modify_mode_name[cur_mode.setmode] << "]#"; if(out.lineedit(str.str(),input,liquids_hist) == -1) return CR_FAILURE; liquids_hist.add(input); @@ -168,38 +206,39 @@ command_result df_liquids (color_ostream &out_, vector & parameters) } else if(command == "m") { - mode = "magma"; + cur_mode.paint = P_MAGMA; } else if(command == "o") { - mode = "obsidian"; + cur_mode.paint = P_OBSIDIAN; } else if(command == "of") { - mode = "obsidian_floor"; + cur_mode.paint = P_OBSIDIAN_FLOOR; } else if(command == "w") { - mode = "water"; + cur_mode.paint = P_WATER; } else if(command == "f") { - mode = "flowbits"; + cur_mode.paint = P_FLOW_BITS; } else if(command == "rs") { - mode = "riversource"; + cur_mode.paint = P_RIVER_SOURCE; } else if(command == "wclean") { - mode = "wclean"; + cur_mode.paint = P_WCLEAN; } else if(command == "point" || command == "p") { - brushname = "point"; + cur_mode.brush = B_POINT; } else if(command == "range" || command == "r") { + int width, height, z_levels; command_result res = parseRectangle(out, commands, 1, commands.size(), width, height, z_levels); if (res != CR_OK) @@ -209,24 +248,26 @@ command_result df_liquids (color_ostream &out_, vector & parameters) if (width == 1 && height == 1 && z_levels == 1) { - brushname = "point"; + cur_mode.brush = B_POINT; + cur_mode.size = df::coord(1, 1, 1); } else { - brushname = "range"; + cur_mode.brush = B_RANGE; + cur_mode.size = df::coord(width, height, z_levels); } } else if(command == "block") { - brushname = "block"; + cur_mode.brush = B_BLOCK; } else if(command == "column") { - brushname = "column"; + cur_mode.brush = B_COLUMN; } else if(command == "flood") { - brushname = "flood"; + cur_mode.brush = B_FLOOD; } else if(command == "q") { @@ -234,45 +275,45 @@ command_result df_liquids (color_ostream &out_, vector & parameters) } else if(command == "f+") { - flowmode = "f+"; + cur_mode.flowmode = M_INC; } else if(command == "f-") { - flowmode = "f-"; + cur_mode.flowmode = M_DEC; } else if(command == "f.") { - flowmode = "f."; + cur_mode.flowmode = M_KEEP; } else if(command == "s+") { - _setmode = "s+"; + cur_mode.setmode = M_INC; } else if(command == "s-") { - _setmode = "s-"; + cur_mode.setmode = M_DEC; } else if(command == "s.") { - _setmode = "s."; + cur_mode.setmode = M_KEEP; } // blah blah, bad code, bite me. else if(command == "0") - amount = 0; + cur_mode.amount = 0; else if(command == "1") - amount = 1; + cur_mode.amount = 1; else if(command == "2") - amount = 2; + cur_mode.amount = 2; else if(command == "3") - amount = 3; + cur_mode.amount = 3; else if(command == "4") - amount = 4; + cur_mode.amount = 4; else if(command == "5") - amount = 5; + cur_mode.amount = 5; else if(command == "6") - amount = 6; + cur_mode.amount = 6; else if(command == "7") - amount = 7; + cur_mode.amount = 7; else if(command.empty()) { df_liquids_execute(out); @@ -298,78 +339,78 @@ command_result df_liquids_here (color_ostream &out, vector & parameters } out.print("Run liquids-here with these parameters: "); - out << "[" << mode << ":" << brushname; - if (brushname == "range") - out << "(w" << width << ":h" << height << ":z" << z_levels << ")"; - out << ":" << amount << ":" << flowmode << ":" << _setmode << "]\n"; + out << "[" << paint_mode_name[cur_mode.paint] << ":" << brush_name[cur_mode.brush]; + if (cur_mode.brush == B_RANGE) + out << "(w" << cur_mode.size.x << ":h" << cur_mode.size.y << ":z" << cur_mode.size.z << ")"; + out << ":" << cur_mode.amount << ":f" << modify_mode_name[cur_mode.flowmode] + << ":" << modify_mode_name[cur_mode.setmode] << "]\n"; return df_liquids_execute(out); } command_result df_liquids_execute(color_ostream &out) { - // create brush type depending on old parameters - Brush * brush; + CoreSuspender suspend; - if (brushname == "point") - { - brush = new RectangleBrush(1,1,1,0,0,0); - //width = 1; - //height = 1; - //z_levels = 1; - } - else if (brushname == "range") + auto cursor = Gui::getCursorPos(); + if (!cursor.isValid()) { - brush = new RectangleBrush(width,height,z_levels,0,0,0); + out.printerr("Can't get cursor coords! Make sure you have a cursor active in DF.\n"); + return CR_WRONG_USAGE; } - else if(brushname == "block") + + auto rv = df_liquids_execute(out, cur_mode, cursor); + if (rv == CR_OK) + out << "OK" << endl; + return rv; +} + +command_result df_liquids_execute(color_ostream &out, OperationMode &cur_mode, df::coord cursor) +{ + // create brush type depending on old parameters + Brush *brush; + + switch (cur_mode.brush) { + case B_POINT: + brush = new RectangleBrush(1,1,1,0,0,0); + break; + case B_RANGE: + brush = new RectangleBrush(cur_mode.size.x,cur_mode.size.y,cur_mode.size.z,0,0,0); + break; + case B_BLOCK: brush = new BlockBrush(); - } - else if(brushname == "column") - { + break; + case B_COLUMN: brush = new ColumnBrush(); - } - else if(brushname == "flood") - { + break; + case B_FLOOD: brush = new FloodBrush(&Core::getInstance()); - } - else - { + break; + default: // this should never happen! out << "Old brushtype is invalid! Resetting to point brush.\n"; - brushname = "point"; - width = 1; - height = 1; - z_levels = 1; - brush = new RectangleBrush(width,height,z_levels,0,0,0); + cur_mode.brush = B_POINT; + brush = new RectangleBrush(1,1,1,0,0,0); } - CoreSuspender suspend; + std::auto_ptr brush_ref(brush); - do + if (!Maps::IsValid()) { - if (!Maps::IsValid()) - { - out << "Can't see any DF map loaded." << endl; - break;; - } - int32_t x,y,z; - if(!Gui::getCursorCoords(x,y,z)) - { - out << "Can't get cursor coords! Make sure you have a cursor active in DF." << endl; - break; - } - out << "cursor coords: " << x << "/" << y << "/" << z << endl; - MapCache mcache; - DFHack::DFCoord cursor(x,y,z); - coord_vec all_tiles = brush->points(mcache,cursor); - out << "working..." << endl; + out << "Can't see any DF map loaded." << endl; + return CR_FAILURE; + } + + MapCache mcache; + coord_vec all_tiles = brush->points(mcache,cursor); - // Force the game to recompute its walkability cache - df::global::world->reindex_pathfinding = true; + // Force the game to recompute its walkability cache + df::global::world->reindex_pathfinding = true; - if(mode == "obsidian") + switch (cur_mode.paint) + { + case P_OBSIDIAN: { coord_vec::iterator iter = all_tiles.begin(); while (iter != all_tiles.end()) @@ -383,8 +424,9 @@ command_result df_liquids_execute(color_ostream &out) mcache.setDesignationAt(*iter, des); iter ++; } + break; } - if(mode == "obsidian_floor") + case P_OBSIDIAN_FLOOR: { coord_vec::iterator iter = all_tiles.begin(); while (iter != all_tiles.end()) @@ -392,8 +434,9 @@ command_result df_liquids_execute(color_ostream &out) mcache.setTiletypeAt(*iter, findRandomVariant(tiletype::LavaFloor1)); iter ++; } + break; } - else if(mode == "riversource") + case P_RIVER_SOURCE: { coord_vec::iterator iter = all_tiles.begin(); while (iter != all_tiles.end()) @@ -413,8 +456,9 @@ command_result df_liquids_execute(color_ostream &out) iter++; } + break; } - else if(mode=="wclean") + case P_WCLEAN: { coord_vec::iterator iter = all_tiles.begin(); while (iter != all_tiles.end()) @@ -426,8 +470,11 @@ command_result df_liquids_execute(color_ostream &out) mcache.setDesignationAt(current,des); iter++; } + break; } - else if(mode== "magma" || mode== "water" || mode == "flowbits") + case P_MAGMA: + case P_WATER: + case P_FLOW_BITS: { set seen_blocks; coord_vec::iterator iter = all_tiles.begin(); @@ -450,30 +497,29 @@ command_result df_liquids_execute(color_ostream &out) iter++; continue; } - if(mode != "flowbits") + if(cur_mode.paint != P_FLOW_BITS) { unsigned old_amount = des.bits.flow_size; unsigned new_amount = old_amount; df::tile_liquid old_liquid = des.bits.liquid_type; df::tile_liquid new_liquid = old_liquid; // Compute new liquid type and amount - if(_setmode == "s.") - { - new_amount = amount; - } - else if(_setmode == "s+") - { - if(old_amount < amount) - new_amount = amount; - } - else if(_setmode == "s-") + switch (cur_mode.setmode) { - if (old_amount > amount) - new_amount = amount; + case M_KEEP: + new_amount = cur_mode.amount; + break; + case M_INC: + if(old_amount < cur_mode.amount) + new_amount = cur_mode.amount; + break; + case M_DEC: + if (old_amount > cur_mode.amount) + new_amount = cur_mode.amount; } - if (mode == "magma") + if (cur_mode.paint == P_MAGMA) new_liquid = tile_liquid::Magma; - else if (mode == "water") + else if (cur_mode.paint == P_WATER) new_liquid = tile_liquid::Water; // Store new amount and type des.bits.flow_size = new_amount; @@ -508,34 +554,64 @@ command_result df_liquids_execute(color_ostream &out) set ::iterator biter = seen_blocks.begin(); while (biter != seen_blocks.end()) { - if(flowmode == "f+") + switch (cur_mode.flowmode) { + case M_INC: (*biter)->enableBlockUpdates(true); - } - else if(flowmode == "f-") - { + break; + case M_DEC: if (auto block = (*biter)->getRaw()) { block->flags.bits.update_liquid = false; block->flags.bits.update_liquid_twice = false; } - } - else - { - auto bflags = (*biter)->BlockFlags(); - out << "flow bit 1 = " << bflags.bits.update_liquid << endl; - out << "flow bit 2 = " << bflags.bits.update_liquid_twice << endl; + break; + case M_KEEP: + { + auto bflags = (*biter)->BlockFlags(); + out << "flow bit 1 = " << bflags.bits.update_liquid << endl; + out << "flow bit 2 = " << bflags.bits.update_liquid_twice << endl; + } } biter ++; } + break; } - if(mcache.WriteAll()) - out << "OK" << endl; - else - out << "Something failed horribly! RUN!" << endl; - } while (0); + } + + if(!mcache.WriteAll()) + { + out << "Something failed horribly! RUN!" << endl; + return CR_FAILURE; + } - // cleanup - delete brush; return CR_OK; } + +static int paint(lua_State *L) +{ + df::coord pos; + OperationMode mode; + + lua_settop(L, 7); + Lua::CheckDFAssign(L, &pos, 1); + if (!pos.isValid()) + luaL_argerror(L, 1, "invalid cursor position"); + mode.brush = (BrushType)luaL_checkoption(L, 2, NULL, brush_name); + mode.paint = (PaintMode)luaL_checkoption(L, 3, NULL, paint_mode_name); + mode.amount = luaL_optint(L, 4, 7); + if (mode.amount < 0 || mode.amount > 7) + luaL_argerror(L, 4, "invalid liquid amount"); + if (!lua_isnil(L, 5)) + Lua::CheckDFAssign(L, &mode.size, 5); + mode.setmode = (ModifyMode)luaL_checkoption(L, 6, ".", modify_mode_name); + mode.flowmode = (ModifyMode)luaL_checkoption(L, 7, "+", modify_mode_name); + + lua_pushboolean(L, df_liquids_execute(*Lua::GetOutput(L), mode, pos)); + return 1; +} + +DFHACK_PLUGIN_LUA_COMMANDS { + DFHACK_LUA_COMMAND(paint), + DFHACK_LUA_END +}; diff --git a/plugins/lua/liquids.lua b/plugins/lua/liquids.lua new file mode 100644 index 000000000..22ce4da35 --- /dev/null +++ b/plugins/lua/liquids.lua @@ -0,0 +1,11 @@ +local _ENV = mkmodule('plugins.liquids') + +--[[ + + Native functions: + + * paint(pos,brush,paint,amount,size,setmode,flowmode) + +--]] + +return _ENV \ No newline at end of file diff --git a/scripts/gui/liquids.lua b/scripts/gui/liquids.lua new file mode 100644 index 000000000..27df49e9a --- /dev/null +++ b/scripts/gui/liquids.lua @@ -0,0 +1,201 @@ +-- Interface front-end for liquids plugin. + +local utils = require 'utils' +local gui = require 'gui' +local guidm = require 'gui.dwarfmode' + +local liquids = require('plugins.liquids') + +local sel_rect = df.global.selection_rect + +local brushes = { + { tag = 'range', caption = 'Rectangle', range = true }, + { tag = 'block', caption = '16x16 block' }, + { tag = 'column', caption = 'Column' }, + { tag = 'flood', caption = 'Flood' }, +} + +local paints = { + { tag = 'water', caption = 'Water', liquid = true, key = 'w' }, + { tag = 'magma', caption = 'Magma', liquid = true, key = 'l' }, + { tag = 'obsidian', caption = 'Obsidian Wall' }, + { tag = 'obsidian_floor', caption = 'Obsidian Floor' }, + { tag = 'riversource', caption = 'River Source' }, + { tag = 'flowbits', caption = 'Flow Updates' }, + { tag = 'wclean', caption = 'Clean Salt/Stagnant' }, +} + +local flowbits = { + { tag = '+', caption = 'Enable Updates' }, + { tag = '-', caption = 'Disable Updates' }, + { tag = '.', caption = 'Keep Updates' }, +} + +local setmode = { + { tag = '.', caption = 'Set Exactly' }, + { tag = '+', caption = 'Only Increase' }, + { tag = '-', caption = 'Only Decrease' }, +} + +Toggle = defclass(Toggle) + +function Toggle:init(items) + self:init_fields{ + items = items, + selected = 1 + } + return self +end + +function Toggle:get() + return self.items[self.selected] +end + +function Toggle:render(dc) + local item = self:get() + if item then + dc:string(item.caption) + if item.key then + dc:string(" ("):string(item.key, COLOR_LIGHTGREEN):string(")") + end + else + dc:string('NONE', COLOR_RED) + end +end + +function Toggle:step(delta) + if #self.items > 1 then + delta = delta or 1 + self.selected = 1 + (self.selected + delta - 1) % #self.items + end +end + +LiquidsUI = defclass(LiquidsUI, guidm.MenuOverlay) + +LiquidsUI.focus_path = 'liquids' + +function LiquidsUI:init() + self:init_fields{ + brush = mkinstance(Toggle):init(brushes), + paint = mkinstance(Toggle):init(paints), + flow = mkinstance(Toggle):init(flowbits), + set = mkinstance(Toggle):init(setmode), + amount = 7, + } + guidm.MenuOverlay.init(self) + return self +end + +function LiquidsUI:onDestroy() + guidm.clearSelection() +end + +function LiquidsUI:onRenderBody(dc) + dc:clear():seek(1,1):string("Paint Liquids Cheat", COLOR_WHITE) + + local cursor = guidm.getCursorPos() + local block = dfhack.maps.getTileBlock(cursor) + local tile = block.tiletype[cursor.x%16][cursor.y%16] + local dsgn = block.designation[cursor.x%16][cursor.y%16] + + dc:seek(2,3):string(df.tiletype.attrs[tile].caption, COLOR_CYAN):newline(2) + + if dsgn.flow_size > 0 then + if dsgn.liquid_type == df.tile_liquid.Magma then + dc:pen(COLOR_RED):string("Magma") + else + dc:pen(COLOR_BLUE) + if dsgn.water_stagnant then dc:string("Stagnant ") end + if dsgn.water_salt then dc:string("Salty ") end + dc:string("Water") + end + dc:string(" ["..dsgn.flow_size.."/7]") + else + dc:string('No Liquid', COLOR_DARKGREY) + end + + dc:newline():pen(COLOR_GREY) + + dc:newline(1):string("b", COLOR_LIGHTGREEN):string(": ") + self.brush:render(dc) + dc:newline(1):string("p", COLOR_LIGHTGREEN):string(": ") + self.paint:render(dc) + + local liquid = self.paint:get().liquid + + dc:newline() + if liquid then + dc:newline(1):string("Amount: "..self.amount) + dc:advance(1):string("("):string("-+", COLOR_LIGHTGREEN):string(")") + dc:newline(3):string("s", COLOR_LIGHTGREEN):string(": ") + self.set:render(dc) + else + dc:advance(0,2) + end + + dc:newline():newline(1):string("f", COLOR_LIGHTGREEN):string(": ") + self.flow:render(dc) + + dc:newline():newline(1):pen(COLOR_WHITE) + dc:string("Esc", COLOR_LIGHTGREEN):string(": Back, ") + dc:string("Enter", COLOR_LIGHTGREEN):string(": Paint") +end + +function LiquidsUI:onInput(keys) + local liquid = self.paint:get().liquid + if keys.CUSTOM_B then + self.brush:step() + elseif keys.CUSTOM_P then + self.paint:step() + elseif liquid and keys.SECONDSCROLL_UP then + self.amount = math.max(0, self.amount-1) + elseif liquid and keys.SECONDSCROLL_DOWN then + self.amount = math.min(7, self.amount+1) + elseif liquid and keys.CUSTOM_S then + self.set:step() + elseif keys.CUSTOM_F then + self.flow:step() + elseif keys.LEAVESCREEN then + if guidm.getSelection() then + guidm.clearSelection() + return + end + self:dismiss() + self:sendInputToParent('CURSOR_DOWN_Z') + self:sendInputToParent('CURSOR_UP_Z') + elseif keys.SELECT then + local cursor = guidm.getCursorPos() + local sp = guidm.getSelection() + local size = nil + if self.brush:get().range then + if not sp then + guidm.setSelectionStart(cursor) + return + else + guidm.clearSelection() + cursor, size = guidm.getSelectionRange(cursor, sp) + end + else + guidm.clearSelection() + end + liquids.paint( + cursor, + self.brush:get().tag, self.paint:get().tag, + self.amount, size, + self.set:get().tag, self.flow:get().tag + ) + elseif self:propagateMoveKeys(keys) then + return + elseif keys.D_LOOK_ARENA_WATER then + self.paint.selected = 1 + elseif keys.D_LOOK_ARENA_MAGMA then + self.paint.selected = 2 + end +end + +if not string.match(dfhack.gui.getCurFocus(), '^dwarfmode/LookAround') then + qerror("This script requires the main dwarfmode view in 'k' mode") +end + +local list = mkinstance(LiquidsUI):init() +list:show() From dcdff40c853dcef2d351635d43b4592affe95317 Mon Sep 17 00:00:00 2001 From: Quietust Date: Sat, 25 Aug 2012 10:57:50 -0500 Subject: [PATCH 11/26] Add sorting, command help; replace filtering with just using the current page of the UnitList viewscreen (and don't close it) --- plugins/manipulator.cpp | 295 ++++++++++++++++++++++++++-------------- 1 file changed, 191 insertions(+), 104 deletions(-) diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index 434ba08c8..9ebf093f1 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include "df/world.h" @@ -41,36 +42,36 @@ DFHACK_PLUGIN("manipulator"); struct SkillLevel { - const char *name; - int points; - char abbrev; + const char *name; + int points; + char abbrev; }; -#define NUM_SKILL_LEVELS (sizeof(skill_levels) / sizeof(SkillLevel)) +#define NUM_SKILL_LEVELS (sizeof(skill_levels) / sizeof(SkillLevel)) // The various skill rankings. Zero skill is hardcoded to "Not" and '-'. const SkillLevel skill_levels[] = { - {"Dabbling", 500, '0'}, - {"Novice", 600, '1'}, - {"Adequate", 700, '2'}, - {"Competent", 800, '3'}, - {"Skilled", 900, '4'}, - {"Proficient", 1000, '5'}, - {"Talented", 1100, '6'}, - {"Adept", 1200, '7'}, - {"Expert", 1300, '8'}, - {"Professional",1400, '9'}, - {"Accomplished",1500, 'A'}, - {"Great", 1600, 'B'}, - {"Master", 1700, 'C'}, - {"High Master", 1800, 'D'}, - {"Grand Master",1900, 'E'}, - {"Legendary", 2000, 'U'}, - {"Legendary+1", 2100, 'V'}, - {"Legendary+2", 2200, 'W'}, - {"Legendary+3", 2300, 'X'}, - {"Legendary+4", 2400, 'Y'}, - {"Legendary+5", 0, 'Z'} + {"Dabbling", 500, '0'}, + {"Novice", 600, '1'}, + {"Adequate", 700, '2'}, + {"Competent", 800, '3'}, + {"Skilled", 900, '4'}, + {"Proficient", 1000, '5'}, + {"Talented", 1100, '6'}, + {"Adept", 1200, '7'}, + {"Expert", 1300, '8'}, + {"Professional",1400, '9'}, + {"Accomplished",1500, 'A'}, + {"Great", 1600, 'B'}, + {"Master", 1700, 'C'}, + {"High Master", 1800, 'D'}, + {"Grand Master",1900, 'E'}, + {"Legendary", 2000, 'U'}, + {"Legendary+1", 2100, 'V'}, + {"Legendary+2", 2200, 'W'}, + {"Legendary+3", 2300, 'X'}, + {"Legendary+4", 2400, 'Y'}, + {"Legendary+5", 0, 'Z'} }; struct SkillColumn @@ -82,7 +83,7 @@ struct SkillColumn bool special; // specified labor is mutually exclusive with all other special labors }; -#define NUM_COLUMNS (sizeof(columns) / sizeof(SkillColumn)) +#define NUM_COLUMNS (sizeof(columns) / sizeof(SkillColumn)) // All of the skill/labor columns we want to display. Includes profession (for color), labor, skill, and 2 character label const SkillColumn columns[] = { @@ -247,17 +248,69 @@ struct UnitInfo int8_t color; }; -#define FILTER_NONWORKERS 0x0001 -#define FILTER_NONDWARVES 0x0002 -#define FILTER_NONCIV 0x0004 -#define FILTER_ANIMALS 0x0008 -#define FILTER_LIVING 0x0010 -#define FILTER_DEAD 0x0020 +enum altsort_mode { + ALTSORT_NAME, + ALTSORT_PROFESSION, + ALTSORT_MAX +}; + +bool descending; +df::job_skill sort_skill; +df::unit_labor sort_labor; + +bool sortByName (const UnitInfo *d1, const UnitInfo *d2) +{ + if (descending) + return (d1->name > d2->name); + else + return (d1->name < d2->name); +} + +bool sortByProfession (const UnitInfo *d1, const UnitInfo *d2) +{ + if (descending) + return (d1->profession > d2->profession); + else + return (d1->profession < d2->profession); +} + +bool sortBySkill (const UnitInfo *d1, const UnitInfo *d2) +{ + if (sort_skill != job_skill::NONE) + { + df::unit_skill *s1 = binsearch_in_vector>(d1->unit->status.current_soul->skills, &df::unit_skill::id, sort_skill); + df::unit_skill *s2 = binsearch_in_vector>(d1->unit->status.current_soul->skills, &df::unit_skill::id, sort_skill); + int l1 = s1 ? s1->rating : 0; + int l2 = s2 ? s2->rating : 0; + int e1 = s1 ? s1->experience : 0; + int e2 = s2 ? s2->experience : 0; + if (descending) + { + if (l1 != l2) + return l1 > l2; + if (e1 != e2) + return e1 > e2; + } + else + { + if (l1 != l2) + return l1 < l2; + if (e1 != e2) + return e1 < e2; + } + } + if (sort_labor != unit_labor::NONE) + { + if (descending) + return d1->unit->status.labors[sort_labor] > d2->unit->status.labors[sort_labor]; + else + return d1->unit->status.labors[sort_labor] < d2->unit->status.labors[sort_labor]; + } + return sortByName(d1, d2); +} class viewscreen_unitlaborsst : public dfhack_viewscreen { public: - static viewscreen_unitlaborsst *create (char pushtype, df::viewscreen *scr = NULL); - void feed(set *events); void render(); @@ -267,86 +320,41 @@ public: std::string getFocusString() { return "unitlabors"; } - viewscreen_unitlaborsst(); + viewscreen_unitlaborsst(vector &src); ~viewscreen_unitlaborsst() { }; protected: vector units; - int filter; + altsort_mode altsort; int first_row, sel_row; int first_column, sel_column; int height, name_width, prof_width, labors_width; -// bool descending; -// int sort_skill; -// int sort_labor; - - void readUnits (); void calcSize (); }; -viewscreen_unitlaborsst::viewscreen_unitlaborsst() -{ - filter = FILTER_LIVING; - first_row = sel_row = 0; - first_column = sel_column = 0; - calcSize(); - readUnits(); -} - -void viewscreen_unitlaborsst::readUnits () +viewscreen_unitlaborsst::viewscreen_unitlaborsst(vector &src) { - for (size_t i = 0; i < units.size(); i++) - delete units[i]; - units.clear(); - - UnitInfo *cur = new UnitInfo; - for (size_t i = 0; i < world->units.active.size(); i++) + for (size_t i = 0; i < src.size(); i++) { - df::unit *unit = world->units.active[i]; + UnitInfo *cur = new UnitInfo; + df::unit *unit = src[i]; cur->unit = unit; cur->allowEdit = true; if (unit->race != ui->race_id) - { cur->allowEdit = false; - if (!(filter & FILTER_NONDWARVES)) - continue; - } if (unit->civ_id != ui->civ_id) - { cur->allowEdit = false; - if (!(filter & FILTER_NONCIV)) - continue; - } if (unit->flags1.bits.dead) - { cur->allowEdit = false; - if (!(filter & FILTER_DEAD)) - continue; - } - else - { - if (!(filter & FILTER_LIVING)) - continue; - } if (!ENUM_ATTR(profession, can_assign_labor, unit->profession)) - { cur->allowEdit = false; - if (!(filter & FILTER_NONWORKERS)) - continue; - } - - if (!unit->name.first_name.length()) - { - if (!(filter & FILTER_ANIMALS)) - continue; - } cur->name = Translation::TranslateName(&unit->name, false); cur->transname = Translation::TranslateName(&unit->name, true); @@ -354,9 +362,13 @@ void viewscreen_unitlaborsst::readUnits () cur->color = Units::getProfessionColor(unit); units.push_back(cur); - cur = new UnitInfo; } - delete cur; + std::sort(units.begin(), units.end(), sortByName); + + altsort = ALTSORT_NAME; + first_row = sel_row = 0; + first_column = sel_column = 0; + calcSize(); } void viewscreen_unitlaborsst::calcSize() @@ -421,8 +433,6 @@ void viewscreen_unitlaborsst::feed(set *events) return; } - // TODO - allow modifying filters - if (!units.size()) return; @@ -501,9 +511,46 @@ void viewscreen_unitlaborsst::feed(set *events) unit->status.labors[col.labor] = !unit->status.labors[col.labor]; } - // TODO: add sorting + if (events->count(interface_key::SECONDSCROLL_UP) || events->count(interface_key::SECONDSCROLL_DOWN)) + { + descending = events->count(interface_key::SECONDSCROLL_UP); + sort_skill = columns[sel_column].skill; + sort_labor = columns[sel_column].labor; + std::sort(units.begin(), units.end(), sortBySkill); + } + + if (events->count(interface_key::SECONDSCROLL_PAGEUP) || events->count(interface_key::SECONDSCROLL_PAGEDOWN)) + { + descending = events->count(interface_key::SECONDSCROLL_PAGEUP); + switch (altsort) + { + case ALTSORT_NAME: + std::sort(units.begin(), units.end(), sortByName); + break; + case ALTSORT_PROFESSION: + std::sort(units.begin(), units.end(), sortByProfession); + break; + } + } + if (events->count(interface_key::CHANGETAB)) + { + switch (altsort) + { + case ALTSORT_NAME: + altsort = ALTSORT_PROFESSION; + break; + case ALTSORT_PROFESSION: + altsort = ALTSORT_NAME; + break; + } + } } +void OutputString(int8_t color, int &x, int y, const std::string &text) +{ + Screen::paintString(Screen::Pen(' ', color, 0), x, y, text); + x += text.length(); +} void viewscreen_unitlaborsst::render() { if (Screen::isDismissed(this)) @@ -528,6 +575,7 @@ void viewscreen_unitlaborsst::render() fg = 0; bg = 7; } + Screen::paintTile(Screen::Pen(columns[col_offset].label[0], fg, bg), 1 + name_width + 1 + prof_width + 1 + col, 1); Screen::paintTile(Screen::Pen(columns[col_offset].label[1], fg, bg), 1 + name_width + 1 + prof_width + 1 + col, 2); } @@ -537,6 +585,7 @@ void viewscreen_unitlaborsst::render() int row_offset = row + first_row; if (row_offset >= units.size()) break; + UnitInfo *cur = units[row_offset]; df::unit *unit = cur->unit; int8_t fg = 15, bg = 0; @@ -554,7 +603,8 @@ void viewscreen_unitlaborsst::render() profession.resize(prof_width); fg = cur->color; bg = 0; - Screen::paintString(Screen::Pen(' ', fg, bg), 1 + prof_width + 1, 3 + row, profession); + + Screen::paintString(Screen::Pen(' ', fg, bg), 1 + name_width + 1, 3 + row, profession); // Print unit's skills and labor assignments for (int col = 0; col < labors_width; col++) @@ -586,15 +636,21 @@ void viewscreen_unitlaborsst::render() if (cur != NULL) { df::unit *unit = cur->unit; - string str = cur->transname; - if (str.length()) - str += ", "; - str += cur->profession; - str += ":"; + int x = 1; + Screen::paintString(Screen::Pen(' ', 15, 0), x, 3 + height + 2, cur->transname); + x += cur->transname.length(); - Screen::paintString(Screen::Pen(' ', 15, 0), 1, 3 + height + 2, str); - int y = 1 + str.length() + 1; + if (cur->transname.length()) + { + Screen::paintString(Screen::Pen(' ', 15, 0), x, 3 + height + 2, ", "); + x += 2; + } + Screen::paintString(Screen::Pen(' ', 15, 0), x, 3 + height + 2, cur->profession); + x += cur->profession.length(); + Screen::paintString(Screen::Pen(' ', 15, 0), x, 3 + height + 2, ": "); + x += 2; + string str; if (columns[sel_column].skill == job_skill::NONE) { str = ENUM_ATTR_STR(unit_labor, caption, columns[sel_column].labor); @@ -602,7 +658,6 @@ void viewscreen_unitlaborsst::render() str += " Enabled"; else str += " Not Enabled"; - Screen::paintString(Screen::Pen(' ', 9, 0), y, 3 + height + 2, str); } else { @@ -618,11 +673,41 @@ void viewscreen_unitlaborsst::render() } else str = stl_sprintf("Not %s (0/500)", ENUM_ATTR_STR(job_skill, caption_noun, columns[sel_column].skill)); - Screen::paintString(Screen::Pen(' ', 9, 0), y, 3 + height + 2, str); } + Screen::paintString(Screen::Pen(' ', 9, 0), x, 3 + height + 2, str); } - // TODO - print command help info + int x = 1; + OutputString( 2, x, gps->dimy - 3, "Enter"); // SELECT key + OutputString(15, x, gps->dimy - 3, ": Toggle labor"); + x += 2; + + OutputString( 2, x, gps->dimy - 3, "Esc"); // LEAVESCREEN key + OutputString(15, x, gps->dimy - 3, ": Done"); + + x = 1; + OutputString( 2, x, gps->dimy - 2, "+"); // SECONDSCROLL_DOWN key + OutputString( 2, x, gps->dimy - 2, "-"); // SECONDSCROLL_UP key + OutputString(15, x, gps->dimy - 2, ": Sort by Skill"); + x += 2; + + OutputString( 2, x, gps->dimy - 2, "*"); // SECONDSCROLL_PAGEDOWN key + OutputString( 2, x, gps->dimy - 2, "/"); // SECONDSCROLL_PAGEUP key + OutputString(15, x, gps->dimy - 2, ": Sort by ("); + OutputString( 2, x, gps->dimy - 2, "Tab"); // CHANGETAB key + OutputString(15, x, gps->dimy - 2, ") "); + switch (altsort) + { + case ALTSORT_NAME: + OutputString(15, x, gps->dimy - 2, "Name"); + break; + case ALTSORT_PROFESSION: + OutputString(15, x, gps->dimy - 2, "Profession"); + break; + default: + OutputString(15, x, gps->dimy - 2, "Unknown"); + break; + } } struct unitlist_hook : df::viewscreen_unitlistst @@ -633,9 +718,11 @@ struct unitlist_hook : df::viewscreen_unitlistst { if (input->count(interface_key::UNITVIEW_PRF_PROF)) { - Screen::dismiss(this); - Screen::show(new viewscreen_unitlaborsst()); - return; + if (units[page].size()) + { + Screen::show(new viewscreen_unitlaborsst(units[page])); + return; + } } INTERPOSE_NEXT(feed)(input); } From bd9800055dd0c6d346d9741f537dd87993920f2e Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 25 Aug 2012 20:01:03 +0400 Subject: [PATCH 12/26] Link to the lua library in the liquids plugin. --- plugins/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 023cd6e83..a2e520178 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -81,7 +81,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(weather weather.cpp) DFHACK_PLUGIN(colonies colonies.cpp) DFHACK_PLUGIN(mode mode.cpp) - DFHACK_PLUGIN(liquids liquids.cpp Brushes.h) + DFHACK_PLUGIN(liquids liquids.cpp Brushes.h LINK_LIBRARIES lua) DFHACK_PLUGIN(tiletypes tiletypes.cpp Brushes.h) DFHACK_PLUGIN(tubefill tubefill.cpp) DFHACK_PLUGIN(autodump autodump.cpp) From f6e4969e1988422f854989fdde85a8b691d64c73 Mon Sep 17 00:00:00 2001 From: Quietust Date: Sat, 25 Aug 2012 11:07:42 -0500 Subject: [PATCH 13/26] Key names are bright green, not dark green --- plugins/manipulator.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index 9ebf093f1..3895b0650 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -678,23 +678,23 @@ void viewscreen_unitlaborsst::render() } int x = 1; - OutputString( 2, x, gps->dimy - 3, "Enter"); // SELECT key + OutputString(10, x, gps->dimy - 3, "Enter"); // SELECT key OutputString(15, x, gps->dimy - 3, ": Toggle labor"); x += 2; - OutputString( 2, x, gps->dimy - 3, "Esc"); // LEAVESCREEN key + OutputString(10, x, gps->dimy - 3, "Esc"); // LEAVESCREEN key OutputString(15, x, gps->dimy - 3, ": Done"); x = 1; - OutputString( 2, x, gps->dimy - 2, "+"); // SECONDSCROLL_DOWN key - OutputString( 2, x, gps->dimy - 2, "-"); // SECONDSCROLL_UP key + OutputString(10, x, gps->dimy - 2, "+"); // SECONDSCROLL_DOWN key + OutputString(10, x, gps->dimy - 2, "-"); // SECONDSCROLL_UP key OutputString(15, x, gps->dimy - 2, ": Sort by Skill"); x += 2; - OutputString( 2, x, gps->dimy - 2, "*"); // SECONDSCROLL_PAGEDOWN key - OutputString( 2, x, gps->dimy - 2, "/"); // SECONDSCROLL_PAGEUP key + OutputString(10, x, gps->dimy - 2, "*"); // SECONDSCROLL_PAGEDOWN key + OutputString(10, x, gps->dimy - 2, "/"); // SECONDSCROLL_PAGEUP key OutputString(15, x, gps->dimy - 2, ": Sort by ("); - OutputString( 2, x, gps->dimy - 2, "Tab"); // CHANGETAB key + OutputString(10, x, gps->dimy - 2, "Tab"); // CHANGETAB key OutputString(15, x, gps->dimy - 2, ") "); switch (altsort) { From 7f1e4b46bc102014533c015f09a20eef38aab13c Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sun, 26 Aug 2012 13:23:59 +0400 Subject: [PATCH 14/26] Implement inheritance-aware vmethod interposing. I.e. overwriting the vmethod in all vtables that use it, not only one. --- library/DataDefs.cpp | 4 +- library/VTableInterpose.cpp | 260 +++++++++++++++++++++++++++--- library/include/DataDefs.h | 2 +- library/include/VTableInterpose.h | 12 +- 4 files changed, 248 insertions(+), 30 deletions(-) diff --git a/library/DataDefs.cpp b/library/DataDefs.cpp index d6604cdb3..4428a2f76 100644 --- a/library/DataDefs.cpp +++ b/library/DataDefs.cpp @@ -218,8 +218,8 @@ virtual_identity::virtual_identity(size_t size, TAllocateFn alloc, virtual_identity::~virtual_identity() { // Remove interpose entries, so that they don't try accessing this object later - for (int i = interpose_list.size()-1; i >= 0; i--) - interpose_list[i]->remove(); + while (!interpose_list.empty()) + interpose_list.begin()->second->on_host_delete(this); } /* Vtable name to identity lookup. */ diff --git a/library/VTableInterpose.cpp b/library/VTableInterpose.cpp index 3725ccba7..47110cece 100644 --- a/library/VTableInterpose.cpp +++ b/library/VTableInterpose.cpp @@ -154,6 +154,73 @@ bool virtual_identity::set_vmethod_ptr(int idx, void *ptr) return Core::getInstance().p->patchMemory(&vtable[idx], &ptr, sizeof(void*)); } +/* + VMethod interposing data structures. + + In order to properly support adding and removing hooks, + it is necessary to track them. This is what this class + is for. The task is further complicated by propagating + hooks to child classes that use exactly the same original + vmethod implementation. + + Every applied link contains in the saved_chain field a + pointer to the next vmethod body that should be called + by the hook the link represents. This is the actual + control flow structure that needs to be maintained. + + There also are connections between link objects themselves, + which constitute the bookkeeping for doing that. Finally, + every link is associated with a fixed virtual_identity host, + which represents the point in the class hierarchy where + the hook is applied. + + When there are no subclasses (i.e. only one host), the + structures look like this: + + +--------------+ +------------+ + | link1 |-next------->| link2 |-next=NULL + |s_c: original |<-------prev-|s_c: $link1 |<--+ + +--------------+ +------------+ | + | + host->interpose_list[vmethod_idx] ------+ + vtable: $link2 + + The original vtable entry is stored in the saved_chain of the + first link. The interpose_list map points to the last one. + The hooks are called in order: link2 -> link1 -> original. + + When there are subclasses that use the same vmethod, but don't + hook it, the topmost link gets a set of the child_hosts, and + the hosts have the link added to their interpose_list: + + +--------------+ +----------------+ + | link0 @host0 |<--+-interpose_list-| host1 | + | |-child_hosts-+----->| vtable: $link | + +--------------+ | | +----------------+ + | | + | | +----------------+ + +-interpose_list-| host2 | + +----->| vtable: $link | + +----------------+ + + When a child defines its own hook, the child_hosts link is + severed and replaced with a child_next pointer to the new + hook. The hook still points back the chain with prev. + All child links to subclasses of host2 are migrated from + link1 to link2. + + +--------------+-next=NULL +--------------+-next=NULL + | link1 @host1 |-child_next------->| link2 @host2 |-child_*--->subclasses + | |<-------------prev-|s_c: $link1 | + +--------------+<-------+ +--------------+<-------+ + | | + +--------------+ | +--------------+ | + | host1 |-i_list-+ | host2 |-i_list-+ + |vtable: $link1| |vtable: $link2| + +--------------+ +--------------+ + + */ + void VMethodInterposeLinkBase::set_chain(void *chain) { saved_chain = chain; @@ -162,7 +229,7 @@ void VMethodInterposeLinkBase::set_chain(void *chain) VMethodInterposeLinkBase::VMethodInterposeLinkBase(virtual_identity *host, int vmethod_idx, void *interpose_method, void *chain_mptr) : host(host), vmethod_idx(vmethod_idx), interpose_method(interpose_method), chain_mptr(chain_mptr), - saved_chain(NULL), next(NULL), prev(NULL) + applied(false), saved_chain(NULL), next(NULL), prev(NULL) { if (vmethod_idx < 0 || interpose_method == NULL) { @@ -179,6 +246,75 @@ VMethodInterposeLinkBase::~VMethodInterposeLinkBase() remove(); } +VMethodInterposeLinkBase *VMethodInterposeLinkBase::get_first_interpose(virtual_identity *id) +{ + auto item = id->interpose_list[vmethod_idx]; + if (!item) + return NULL; + + if (item->host != id) + return NULL; + while (item->prev && item->prev->host == id) + item = item->prev; + + return item; +} + +void VMethodInterposeLinkBase::find_child_hosts(virtual_identity *cur, void *vmptr) +{ + auto &children = cur->getChildren(); + + for (size_t i = 0; i < children.size(); i++) + { + auto child = static_cast(children[i]); + auto base = get_first_interpose(child); + + if (base) + { + assert(base->prev == NULL); + + if (base->saved_chain != vmptr) + continue; + + child_next.insert(base); + } + else + { + void *cptr = child->get_vmethod_ptr(vmethod_idx); + if (cptr != vmptr) + continue; + + child_hosts.insert(child); + find_child_hosts(child, vmptr); + } + } +} + +void VMethodInterposeLinkBase::on_host_delete(virtual_identity *from) +{ + if (from == host) + { + // When in own host, fully delete + remove(); + } + else + { + // Otherwise, drop the link to that child: + assert(child_hosts.count(from) != 0 && + from->interpose_list[vmethod_idx] == this); + + // Find and restore the original vmethod ptr + auto last = this; + while (last->prev) last = last->prev; + + from->set_vmethod_ptr(vmethod_idx, last->saved_chain); + + // Unlink the chains + child_hosts.erase(from); + from->interpose_list.erase(vmethod_idx); + } +} + bool VMethodInterposeLinkBase::apply() { if (is_applied()) @@ -188,33 +324,73 @@ bool VMethodInterposeLinkBase::apply() // Retrieve the current vtable entry void *old_ptr = host->get_vmethod_ptr(vmethod_idx); - assert(old_ptr != NULL); + VMethodInterposeLinkBase *old_link = host->interpose_list[vmethod_idx]; - // Check if there are other interpose entries for the same slot - VMethodInterposeLinkBase *old_link = NULL; - - for (int i = host->interpose_list.size()-1; i >= 0; i--) - { - if (host->interpose_list[i]->vmethod_idx != vmethod_idx) - continue; - - old_link = host->interpose_list[i]; - assert(old_link->next == NULL && old_ptr == old_link->interpose_method); - break; - } + assert(old_ptr != NULL && (!old_link || old_link->interpose_method == old_ptr)); // Apply the new method ptr + set_chain(old_ptr); + if (!host->set_vmethod_ptr(vmethod_idx, interpose_method)) + { + set_chain(NULL); return false; + } - set_chain(old_ptr); - host->interpose_list.push_back(this); + // Push the current link into the home host + applied = true; + host->interpose_list[vmethod_idx] = this; + prev = old_link; - // Link into the chain if any - if (old_link) + child_hosts.clear(); + child_next.clear(); + + if (old_link && old_link->host == host) { + // If the old link is home, just push into the plain chain + assert(old_link->next == NULL); old_link->next = this; - prev = old_link; + + // Child links belong to the topmost local entry + child_hosts.swap(old_link->child_hosts); + child_next.swap(old_link->child_next); + } + else + { + // If creating a new local chain, find children with same vmethod + find_child_hosts(host, old_ptr); + + if (old_link) + { + // Enter the child chain set + assert(old_link->child_hosts.count(host)); + old_link->child_hosts.erase(host); + old_link->child_next.insert(this); + + // Subtract our own children from the parent's sets + for (auto it = child_next.begin(); it != child_next.end(); ++it) + old_link->child_next.erase(*it); + for (auto it = child_hosts.begin(); it != child_hosts.end(); ++it) + old_link->child_hosts.erase(*it); + } + } + + // Chain subclass hooks + for (auto it = child_next.begin(); it != child_next.end(); ++it) + { + auto nlink = *it; + assert(nlink->saved_chain == old_ptr && nlink->prev == old_link); + nlink->set_chain(interpose_method); + nlink->prev = this; + } + + // Chain passive subclass hosts + for (auto it = child_hosts.begin(); it != child_hosts.end(); ++it) + { + auto nhost = *it; + assert(nhost->interpose_list[vmethod_idx] == old_link); + nhost->set_vmethod_ptr(vmethod_idx, interpose_method); + nhost->interpose_list[vmethod_idx] = this; } return true; @@ -225,25 +401,57 @@ void VMethodInterposeLinkBase::remove() if (!is_applied()) return; - // Remove from the list in the identity - for (int i = host->interpose_list.size()-1; i >= 0; i--) - if (host->interpose_list[i] == this) - vector_erase_at(host->interpose_list, i); - - // Remove from the chain + // Remove the link from prev to this if (prev) - prev->next = next; + { + if (prev->host == host) + prev->next = next; + else + { + prev->child_next.erase(this); + + if (next) + prev->child_next.insert(next); + } + } if (next) { next->set_chain(saved_chain); next->prev = prev; + + assert(child_next.empty() && child_hosts.empty()); } else { + // Remove from the list in the identity and vtable + host->interpose_list[vmethod_idx] = prev; host->set_vmethod_ptr(vmethod_idx, saved_chain); + + for (auto it = child_next.begin(); it != child_next.end(); ++it) + { + auto nlink = *it; + assert(nlink->saved_chain == interpose_method && nlink->prev == this); + nlink->set_chain(saved_chain); + nlink->prev = prev; + if (prev) + prev->child_next.insert(nlink); + } + + for (auto it = child_hosts.begin(); it != child_hosts.end(); ++it) + { + auto nhost = *it; + assert(nhost->interpose_list[vmethod_idx] == this); + nhost->interpose_list[vmethod_idx] = prev; + nhost->set_vmethod_ptr(vmethod_idx, saved_chain); + if (prev) + prev->child_hosts.insert(nhost); + } } + applied = false; prev = next = NULL; + child_next.clear(); + child_hosts.clear(); set_chain(NULL); } diff --git a/library/include/DataDefs.h b/library/include/DataDefs.h index ccb29b0e7..591a0c3ff 100644 --- a/library/include/DataDefs.h +++ b/library/include/DataDefs.h @@ -303,7 +303,7 @@ namespace DFHack void *vtable_ptr; friend class VMethodInterposeLinkBase; - std::vector interpose_list; + std::map interpose_list; protected: virtual void doInit(Core *core); diff --git a/library/include/VTableInterpose.h b/library/include/VTableInterpose.h index bb7a37ce8..c9482f82c 100644 --- a/library/include/VTableInterpose.h +++ b/library/include/VTableInterpose.h @@ -134,21 +134,31 @@ namespace DFHack 1) Allow multiple hooks into the same vmethod 2) Auto-remove hooks when a plugin is unloaded. */ + friend class virtual_identity; virtual_identity *host; // Class with the vtable int vmethod_idx; void *interpose_method; // Pointer to the code of the interposing method void *chain_mptr; // Pointer to the chain field below + bool applied; void *saved_chain; // Previous pointer to the code VMethodInterposeLinkBase *next, *prev; // Other hooks for the same method + // inherited vtable members + std::set child_hosts; + std::set child_next; + void set_chain(void *chain); + void on_host_delete(virtual_identity *host); + + VMethodInterposeLinkBase *get_first_interpose(virtual_identity *id); + void find_child_hosts(virtual_identity *cur, void *vmptr); public: VMethodInterposeLinkBase(virtual_identity *host, int vmethod_idx, void *interpose_method, void *chain_mptr); ~VMethodInterposeLinkBase(); - bool is_applied() { return saved_chain != NULL; } + bool is_applied() { return applied; } bool apply(); void remove(); }; From 3402a3cd5d473d26c115145e19f4113db51e8435 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sun, 26 Aug 2012 13:24:37 +0400 Subject: [PATCH 15/26] Fix a deadlock problem between suspend in (un)load, and onupdate. --- library/PluginManager.cpp | 24 +++++++++++++++++------- library/include/PluginManager.h | 4 +++- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/library/PluginManager.cpp b/library/PluginManager.cpp index d8b9ff27d..ceb644e60 100644 --- a/library/PluginManager.cpp +++ b/library/PluginManager.cpp @@ -186,14 +186,17 @@ Plugin::~Plugin() bool Plugin::load(color_ostream &con) { - RefAutolock lock(access); - if(state == PS_BROKEN) - { - return false; - } - else if(state == PS_LOADED) { - return true; + RefAutolock lock(access); + if(state == PS_LOADED) + { + return true; + } + else if(state != PS_UNLOADED) + { + return false; + } + state = PS_LOADING; } // enter suspend CoreSuspender suspend; @@ -202,6 +205,7 @@ bool Plugin::load(color_ostream &con) if(!plug) { con.printerr("Can't load plugin %s\n", filename.c_str()); + RefAutolock lock(access); state = PS_BROKEN; return false; } @@ -211,6 +215,7 @@ bool Plugin::load(color_ostream &con) { con.printerr("Plugin %s has no name or version.\n", filename.c_str()); ClosePlugin(plug); + RefAutolock lock(access); state = PS_BROKEN; return false; } @@ -219,9 +224,11 @@ bool Plugin::load(color_ostream &con) con.printerr("Plugin %s was not built for this version of DFHack.\n" "Plugin: %s, DFHack: %s\n", *plug_name, *plug_version, DFHACK_VERSION); ClosePlugin(plug); + RefAutolock lock(access); state = PS_BROKEN; return false; } + RefAutolock lock(access); plugin_init = (command_result (*)(color_ostream &, std::vector &)) LookupPlugin(plug, "plugin_init"); if(!plugin_init) { @@ -273,8 +280,11 @@ bool Plugin::unload(color_ostream &con) } // wait for all calls to finish access->wait(); + state = PS_UNLOADING; + access->unlock(); // enter suspend CoreSuspender suspend; + access->lock(); // notify plugin about shutdown, if it has a shutdown function command_result cr = CR_OK; if(plugin_shutdown) diff --git a/library/include/PluginManager.h b/library/include/PluginManager.h index 25b05ad40..38f0e2e50 100644 --- a/library/include/PluginManager.h +++ b/library/include/PluginManager.h @@ -128,7 +128,9 @@ namespace DFHack { PS_UNLOADED, PS_LOADED, - PS_BROKEN + PS_BROKEN, + PS_LOADING, + PS_UNLOADING }; friend class PluginManager; friend class RPCService; From bee33fd486b6eeb09926a781a29d0a0e7b278bfd Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sun, 26 Aug 2012 14:42:36 +0400 Subject: [PATCH 16/26] Add a performance test for location caching in general refs. --- plugins/devel/CMakeLists.txt | 3 + plugins/devel/ref-index.cpp | 149 +++++++++++++++++++++++++++++++++++ 2 files changed, 152 insertions(+) create mode 100644 plugins/devel/ref-index.cpp diff --git a/plugins/devel/CMakeLists.txt b/plugins/devel/CMakeLists.txt index 8274accfb..134d5cb67 100644 --- a/plugins/devel/CMakeLists.txt +++ b/plugins/devel/CMakeLists.txt @@ -18,3 +18,6 @@ DFHACK_PLUGIN(stripcaged stripcaged.cpp) DFHACK_PLUGIN(rprobe rprobe.cpp) DFHACK_PLUGIN(nestboxes nestboxes.cpp) DFHACK_PLUGIN(vshook vshook.cpp) +IF(UNIX) +DFHACK_PLUGIN(ref-index ref-index.cpp) +ENDIF() diff --git a/plugins/devel/ref-index.cpp b/plugins/devel/ref-index.cpp new file mode 100644 index 000000000..686f6918b --- /dev/null +++ b/plugins/devel/ref-index.cpp @@ -0,0 +1,149 @@ +#include "Core.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "df/item.h" +#include "df/unit.h" +#include "df/world.h" +#include "df/general_ref_item.h" +#include "df/general_ref_unit.h" + +using std::vector; +using std::string; +using std::stack; +using namespace DFHack; + +using df::global::gps; + +DFHACK_PLUGIN("ref-index"); + +#define global_id id + +template +T get_from_global_id_vector(int32_t id, const std::vector &vect, int32_t *cache) +{ + size_t size = vect.size(); + int32_t start=0; + int32_t end=(int32_t)size-1; + + // Check the cached location. If it is a match, this provides O(1) lookup. + // Otherwise it works like one binsearch iteration. + if (size_t(*cache) < size) + { + T cptr = vect[*cache]; + if (cptr->global_id == id) + return cptr; + if (cptr->global_id < id) + start = *cache+1; + else + end = *cache-1; + } + + // Regular binsearch. The end check provides O(1) caching for missing item. + if (start <= end && vect[end]->global_id >= id) + { + do { + int32_t mid=(start+end)>>1; + + T cptr=vect[mid]; + if(cptr->global_id==id) + { + *cache = mid; + return cptr; + } + else if(cptr->global_id>id)end=mid-1; + else start=mid+1; + } while(start<=end); + } + + *cache = end+1; + return NULL; +} + +template T *find_object(int32_t id, int32_t *cache); +template<> df::item *find_object(int32_t id, int32_t *cache) { + return get_from_global_id_vector(id, df::global::world->items.all, cache); +} +template<> df::unit *find_object(int32_t id, int32_t *cache) { + return get_from_global_id_vector(id, df::global::world->units.all, cache); +} + +template +struct CachedRef { + int32_t id; + int32_t cache; + CachedRef(int32_t id = -1) : id(id), cache(-1) {} + T *target() { return find_object(id, &cache); } +}; + +#ifdef LINUX_BUILD +struct item_hook : df::general_ref_item { + typedef df::general_ref_item interpose_base; + + DEFINE_VMETHOD_INTERPOSE(df::item*, getItem, ()) + { + // HUGE HACK: ASSUMES THERE ARE 4 USABLE BYTES AFTER THE OBJECT + // This actually is true with glibc allocator due to granularity. + return find_object(item_id, 1+&item_id); + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE(item_hook, getItem); + +struct unit_hook : df::general_ref_unit { + typedef df::general_ref_unit interpose_base; + + DEFINE_VMETHOD_INTERPOSE(df::unit*, getUnit, ()) + { + // HUGE HACK: ASSUMES THERE ARE 4 USABLE BYTES AFTER THE OBJECT + // This actually is true with glibc allocator due to granularity. + return find_object(unit_id, 1+&unit_id); + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE(unit_hook, getUnit); + +command_result hook_refs(color_ostream &out, vector & parameters) +{ + auto &hook = INTERPOSE_HOOK(item_hook, getItem); + if (hook.is_applied()) + { + hook.remove(); + INTERPOSE_HOOK(unit_hook, getUnit).remove(); + } + else + { + hook.apply(); + INTERPOSE_HOOK(unit_hook, getUnit).apply(); + } + + if (hook.is_applied()) + out.print("Hook is applied.\n"); + else + out.print("Hook is not applied.\n"); + return CR_OK; +} +#endif + +DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) +{ +#ifdef LINUX_BUILD + commands.push_back(PluginCommand("hook-refs","Inject O(1) cached lookup into general refs.",hook_refs)); +#endif + + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown ( color_ostream &out ) +{ + return CR_OK; +} From 81716523238823625d09e186204801a413e41210 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sun, 26 Aug 2012 20:08:28 +0400 Subject: [PATCH 17/26] Support permaflow in liquids, including the gui. --- plugins/liquids.cpp | 74 ++++++++++++++++++++++++----- scripts/gui/liquids.lua | 102 ++++++++++++++++++++++++++++++++-------- 2 files changed, 144 insertions(+), 32 deletions(-) diff --git a/plugins/liquids.cpp b/plugins/liquids.cpp index b078b48fd..6df530a92 100644 --- a/plugins/liquids.cpp +++ b/plugins/liquids.cpp @@ -101,17 +101,36 @@ static const char *modify_mode_name[] = { "+", ".", "-", NULL }; +enum PermaflowMode { + PF_KEEP, PF_NONE, + PF_NORTH, PF_SOUTH, PF_EAST, PF_WEST, + PF_NORTHEAST, PF_NORTHWEST, PF_SOUTHEAST, PF_SOUTHWEST +}; + +static const char *permaflow_name[] = { + ".", "-", "N", "S", "E", "W", + "NE", "NW", "SE", "SW", NULL +}; + +#define X(name) tile_liquid_flow_dir::name +static const df::tile_liquid_flow_dir permaflow_id[] = { + X(none), X(none), X(north), X(south), X(east), X(west), + X(northeast), X(northwest), X(southeast), X(southwest) +}; +#undef X + struct OperationMode { BrushType brush; PaintMode paint; ModifyMode flowmode; ModifyMode setmode; + PermaflowMode permaflow; unsigned int amount; df::coord size; OperationMode() : brush(B_POINT), paint(P_MAGMA), - flowmode(M_INC), setmode(M_KEEP), amount(7), + flowmode(M_INC), setmode(M_KEEP), permaflow(PF_KEEP), amount(7), size(1,1,1) {} } cur_mode; @@ -119,6 +138,17 @@ struct OperationMode { command_result df_liquids_execute(color_ostream &out); command_result df_liquids_execute(color_ostream &out, OperationMode &mode, df::coord pos); +static void print_prompt(std::ostream &str, OperationMode &cur_mode) +{ + str <<"[" << paint_mode_name[cur_mode.paint] << ":" << brush_name[cur_mode.brush]; + if (cur_mode.brush == B_RANGE) + str << "(w" << cur_mode.size.x << ":h" << cur_mode.size.y << ":z" << cur_mode.size.z << ")"; + str << ":" << cur_mode.amount << ":f" << modify_mode_name[cur_mode.flowmode] + << ":s" << modify_mode_name[cur_mode.setmode] + << ":pf" << permaflow_name[cur_mode.permaflow] + << "]"; +} + command_result df_liquids (color_ostream &out_, vector & parameters) { if(!out_.is_console()) @@ -154,11 +184,8 @@ command_result df_liquids (color_ostream &out_, vector & parameters) string input = ""; std::stringstream str; - str <<"[" << paint_mode_name[cur_mode.paint] << ":" << brush_name[cur_mode.brush]; - if (cur_mode.brush == B_RANGE) - str << "(w" << cur_mode.size.x << ":h" << cur_mode.size.y << ":z" << cur_mode.size.z << ")"; - str << ":" << cur_mode.amount << ":f" << modify_mode_name[cur_mode.flowmode] - << ":s" << modify_mode_name[cur_mode.setmode] << "]#"; + print_prompt(str, cur_mode); + str << "# "; if(out.lineedit(str.str(),input,liquids_hist) == -1) return CR_FAILURE; liquids_hist.add(input); @@ -185,6 +212,10 @@ command_result df_liquids (color_ostream &out_, vector & parameters) << "f+ - make the spawned liquid flow" << endl << "f. - don't change flow state (read state in flow mode)" << endl << "f- - make the spawned liquid static" << endl + << "Permaflow (only for water):" << endl + << "pf. - don't change permaflow state" << endl + << "pf- - make the spawned liquid static" << endl + << "pf[NS][EW] - make the spawned liquid permanently flow" << endl << "0-7 - set liquid amount" << endl << "Brush:" << endl << "point - single tile [p]" << endl @@ -297,6 +328,20 @@ command_result df_liquids (color_ostream &out_, vector & parameters) { cur_mode.setmode = M_KEEP; } + else if (command.size() > 2 && memcmp(command.c_str(), "pf", 2) == 0) + { + auto *tail = command.c_str()+2; + for (int pm = PF_KEEP; pm <= PF_SOUTHWEST; pm++) + { + if (strcmp(tail, permaflow_name[pm]) != 0) + continue; + cur_mode.permaflow = PermaflowMode(pm); + tail = NULL; + break; + } + if (tail) + out << command << " : invalid permaflow mode" << endl; + } // blah blah, bad code, bite me. else if(command == "0") cur_mode.amount = 0; @@ -339,11 +384,8 @@ command_result df_liquids_here (color_ostream &out, vector & parameters } out.print("Run liquids-here with these parameters: "); - out << "[" << paint_mode_name[cur_mode.paint] << ":" << brush_name[cur_mode.brush]; - if (cur_mode.brush == B_RANGE) - out << "(w" << cur_mode.size.x << ":h" << cur_mode.size.y << ":z" << cur_mode.size.z << ")"; - out << ":" << cur_mode.amount << ":f" << modify_mode_name[cur_mode.flowmode] - << ":" << modify_mode_name[cur_mode.setmode] << "]\n"; + print_prompt(out, cur_mode); + out << endl; return df_liquids_execute(out); } @@ -489,6 +531,7 @@ command_result df_liquids_execute(color_ostream &out, OperationMode &cur_mode, d iter ++; continue; } + auto raw_block = block->getRaw(); df::tile_designation des = mcache.designationAt(current); df::tiletype tt = mcache.tiletypeAt(current); // don't put liquids into places where they don't belong... @@ -548,6 +591,12 @@ command_result df_liquids_execute(color_ostream &out, OperationMode &cur_mode, d // request flow engine updates block->enableBlockUpdates(new_amount != old_amount, new_liquid != old_liquid); } + if (cur_mode.permaflow != PF_KEEP && raw_block) + { + auto &flow = raw_block->liquid_flow[current.x&15][current.y&15]; + flow.bits.perm_flow_dir = permaflow_id[cur_mode.permaflow]; + flow.bits.temp_flow_timer = 0; + } seen_blocks.insert(block); iter++; } @@ -593,7 +642,7 @@ static int paint(lua_State *L) df::coord pos; OperationMode mode; - lua_settop(L, 7); + lua_settop(L, 8); Lua::CheckDFAssign(L, &pos, 1); if (!pos.isValid()) luaL_argerror(L, 1, "invalid cursor position"); @@ -606,6 +655,7 @@ static int paint(lua_State *L) Lua::CheckDFAssign(L, &mode.size, 5); mode.setmode = (ModifyMode)luaL_checkoption(L, 6, ".", modify_mode_name); mode.flowmode = (ModifyMode)luaL_checkoption(L, 7, "+", modify_mode_name); + mode.permaflow = (PermaflowMode)luaL_checkoption(L, 8, ".", permaflow_name); lua_pushboolean(L, df_liquids_execute(*Lua::GetOutput(L), mode, pos)); return 1; diff --git a/scripts/gui/liquids.lua b/scripts/gui/liquids.lua index 27df49e9a..869cac908 100644 --- a/scripts/gui/liquids.lua +++ b/scripts/gui/liquids.lua @@ -16,12 +16,12 @@ local brushes = { } local paints = { - { tag = 'water', caption = 'Water', liquid = true, key = 'w' }, - { tag = 'magma', caption = 'Magma', liquid = true, key = 'l' }, + { tag = 'water', caption = 'Water', liquid = true, flow = true, key = 'w' }, + { tag = 'magma', caption = 'Magma', liquid = true, flow = true, key = 'l' }, { tag = 'obsidian', caption = 'Obsidian Wall' }, { tag = 'obsidian_floor', caption = 'Obsidian Floor' }, { tag = 'riversource', caption = 'River Source' }, - { tag = 'flowbits', caption = 'Flow Updates' }, + { tag = 'flowbits', caption = 'Flow Updates', flow = true }, { tag = 'wclean', caption = 'Clean Salt/Stagnant' }, } @@ -37,6 +37,19 @@ local setmode = { { tag = '-', caption = 'Only Decrease' }, } +local permaflows = { + { tag = '.', caption = "Keep Permaflow" }, + { tag = '-', caption = 'Remove Permaflow' }, + { tag = 'N', caption = 'Set Permaflow N' }, + { tag = 'S', caption = 'Set Permaflow S' }, + { tag = 'E', caption = 'Set Permaflow E' }, + { tag = 'W', caption = 'Set Permaflow W' }, + { tag = 'NE', caption = 'Set Permaflow NE' }, + { tag = 'NW', caption = 'Set Permaflow NW' }, + { tag = 'SE', caption = 'Set Permaflow SE' }, + { tag = 'SW', caption = 'Set Permaflow SW' }, +} + Toggle = defclass(Toggle) function Toggle:init(items) @@ -80,6 +93,7 @@ function LiquidsUI:init() paint = mkinstance(Toggle):init(paints), flow = mkinstance(Toggle):init(flowbits), set = mkinstance(Toggle):init(setmode), + permaflow = mkinstance(Toggle):init(permaflows), amount = 7, } guidm.MenuOverlay.init(self) @@ -90,15 +104,8 @@ function LiquidsUI:onDestroy() guidm.clearSelection() end -function LiquidsUI:onRenderBody(dc) - dc:clear():seek(1,1):string("Paint Liquids Cheat", COLOR_WHITE) - - local cursor = guidm.getCursorPos() - local block = dfhack.maps.getTileBlock(cursor) - local tile = block.tiletype[cursor.x%16][cursor.y%16] - local dsgn = block.designation[cursor.x%16][cursor.y%16] - - dc:seek(2,3):string(df.tiletype.attrs[tile].caption, COLOR_CYAN):newline(2) +function render_liquid(dc, block, x, y) + local dsgn = block.designation[x%16][y%16] if dsgn.flow_size > 0 then if dsgn.liquid_type == df.tile_liquid.Magma then @@ -111,7 +118,51 @@ function LiquidsUI:onRenderBody(dc) end dc:string(" ["..dsgn.flow_size.."/7]") else - dc:string('No Liquid', COLOR_DARKGREY) + dc:string('No Liquid') + end +end + +local permaflow_abbr = { + north = 'N', south = 'S', east = 'E', west = 'W', + northeast = 'NE', northwest = 'NW', southeast = 'SE', southwest = 'SW' +} + +function render_flow_state(dc, block, x, y) + local flow = block.liquid_flow[x%16][y%16] + + if block.flags.update_liquid then + dc:string("Updating", COLOR_GREEN) + else + dc:string("Static") + end + dc:string(", ") + if flow.perm_flow_dir ~= 0 then + local tag = df.tile_liquid_flow_dir[flow.perm_flow_dir] + dc:string("Permaflow "..(permaflow_abbr[tag] or tag), COLOR_CYAN) + elseif flow.temp_flow_timer > 0 then + dc:string("Flowing "..flow.temp_flow_timer, COLOR_GREEN) + else + dc:string("No Flow") + end +end + +function LiquidsUI:onRenderBody(dc) + dc:clear():seek(1,1):string("Paint Liquids Cheat", COLOR_WHITE) + + local cursor = guidm.getCursorPos() + local block = dfhack.maps.getTileBlock(cursor) + + if block then + local x, y = pos2xyz(cursor) + local tile = block.tiletype[x%16][y%16] + + dc:seek(2,3):string(df.tiletype.attrs[tile].caption, COLOR_CYAN) + dc:newline(2):pen(COLOR_DARKGREY) + render_liquid(dc, block, x, y) + dc:newline(2):pen(COLOR_DARKGREY) + render_flow_state(dc, block, x, y) + else + dc:seek(2,3):string("No map data", COLOR_RED):advance(0,2) end dc:newline():pen(COLOR_GREY) @@ -121,10 +172,10 @@ function LiquidsUI:onRenderBody(dc) dc:newline(1):string("p", COLOR_LIGHTGREEN):string(": ") self.paint:render(dc) - local liquid = self.paint:get().liquid + local paint = self.paint:get() dc:newline() - if liquid then + if paint.liquid then dc:newline(1):string("Amount: "..self.amount) dc:advance(1):string("("):string("-+", COLOR_LIGHTGREEN):string(")") dc:newline(3):string("s", COLOR_LIGHTGREEN):string(": ") @@ -133,8 +184,15 @@ function LiquidsUI:onRenderBody(dc) dc:advance(0,2) end - dc:newline():newline(1):string("f", COLOR_LIGHTGREEN):string(": ") - self.flow:render(dc) + dc:newline() + if paint.flow then + dc:newline(1):string("f", COLOR_LIGHTGREEN):string(": ") + self.flow:render(dc) + dc:newline(1):string("r", COLOR_LIGHTGREEN):string(": ") + self.permaflow:render(dc) + else + dc:advance(0,2) + end dc:newline():newline(1):pen(COLOR_WHITE) dc:string("Esc", COLOR_LIGHTGREEN):string(": Back, ") @@ -142,7 +200,8 @@ function LiquidsUI:onRenderBody(dc) end function LiquidsUI:onInput(keys) - local liquid = self.paint:get().liquid + local paint = self.paint:get() + local liquid = paint.liquid if keys.CUSTOM_B then self.brush:step() elseif keys.CUSTOM_P then @@ -153,8 +212,10 @@ function LiquidsUI:onInput(keys) self.amount = math.min(7, self.amount+1) elseif liquid and keys.CUSTOM_S then self.set:step() - elseif keys.CUSTOM_F then + elseif paint.flow and keys.CUSTOM_F then self.flow:step() + elseif paint.flow and keys.CUSTOM_R then + self.permaflow:step() elseif keys.LEAVESCREEN then if guidm.getSelection() then guidm.clearSelection() @@ -182,7 +243,8 @@ function LiquidsUI:onInput(keys) cursor, self.brush:get().tag, self.paint:get().tag, self.amount, size, - self.set:get().tag, self.flow:get().tag + self.set:get().tag, self.flow:get().tag, + self.permaflow:get().tag ) elseif self:propagateMoveKeys(keys) then return From 84f6663a078ee8907fb1287fc25672c5f226f420 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sun, 26 Aug 2012 21:19:56 +0400 Subject: [PATCH 18/26] Add a tweak to save the cursor position of dwarfmode between menus. --- dfhack.init-example | 7 +++++ plugins/tweak.cpp | 64 ++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 68 insertions(+), 3 deletions(-) diff --git a/dfhack.init-example b/dfhack.init-example index 552b2b3a1..380bdd04f 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -51,3 +51,10 @@ keybinding add Alt-R@dwarfmode/QueryBuilding/Some gui/room-list.work # interface for the liquids plugin keybinding add Alt-L@dwarfmode/LookAround gui/liquids + +################### +# UI logic tweaks # +################### + +# stabilize the cursor of dwarfmode when switching menus +tweak stable-cursor diff --git a/plugins/tweak.cpp b/plugins/tweak.cpp index 2daa9063b..70acf7607 100644 --- a/plugins/tweak.cpp +++ b/plugins/tweak.cpp @@ -13,6 +13,7 @@ #include "MiscUtils.h" #include "DataDefs.h" +#include #include "df/ui.h" #include "df/world.h" #include "df/squad.h" @@ -26,6 +27,7 @@ #include "df/death_info.h" #include "df/criminal_case.h" #include "df/unit_inventory_item.h" +#include "df/viewscreen_dwarfmodest.h" #include @@ -67,6 +69,9 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector & params); - - // to be called by tweak-fixmigrant // units forced into the fort by removing the flags do not own their clothes // which has the result that they drop all their clothes and become unhappy because they are naked @@ -136,6 +138,54 @@ command_result fix_clothing_ownership(color_ostream &out, df::unit* unit) return CR_OK; } +/* + * Save or restore cursor position on change to/from main dwarfmode menu. + */ + +static df::coord last_view, last_cursor; + +struct stable_cursor_hook : df::viewscreen_dwarfmodest +{ + typedef df::viewscreen_dwarfmodest interpose_base; + + DEFINE_VMETHOD_INTERPOSE(void, feed, (set *input)) + { + bool was_default = (ui->main.mode == df::ui_sidebar_mode::Default); + df::coord view = Gui::getViewportPos(); + df::coord cursor = Gui::getCursorPos(); + + INTERPOSE_NEXT(feed)(input); + + bool is_default = (ui->main.mode == df::ui_sidebar_mode::Default); + df::coord cur_cursor = Gui::getCursorPos(); + + if (is_default && !was_default) + { + last_view = view; last_cursor = cursor; + } + else if (!is_default && was_default && + Gui::getViewportPos() == last_view && + last_cursor.isValid() && cur_cursor.isValid()) + { + Gui::setCursorCoords(last_cursor.x, last_cursor.y, last_cursor.z); + + // Force update of ui state + set tmp; + tmp.insert(interface_key::CURSOR_DOWN_Z); + INTERPOSE_NEXT(feed)(&tmp); + tmp.clear(); + tmp.insert(interface_key::CURSOR_UP_Z); + INTERPOSE_NEXT(feed)(&tmp); + } + else if (cur_cursor.isValid()) + { + last_cursor = df::coord(); + } + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE(stable_cursor_hook, feed); + static command_result tweak(color_ostream &out, vector ¶meters) { CoreSuspender suspend; @@ -234,6 +284,14 @@ static command_result tweak(color_ostream &out, vector ¶meters) unit->profession2 = df::profession::TRADER; return fix_clothing_ownership(out, unit); } + else if (cmd == "stable-cursor") + { + auto &hook = INTERPOSE_HOOK(stable_cursor_hook, feed); + if (vector_get(parameters, 1) == "disable") + hook.remove(); + else + hook.apply(); + } else return CR_WRONG_USAGE; From b2bdc199cb6aec64034e672f7590aa2a7a27194c Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sun, 26 Aug 2012 22:43:18 +0400 Subject: [PATCH 19/26] Fix NULL pointer access in ~virtual_identity. --- library/DataDefs.cpp | 6 ++++-- library/VTableInterpose.cpp | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/library/DataDefs.cpp b/library/DataDefs.cpp index 4428a2f76..341164441 100644 --- a/library/DataDefs.cpp +++ b/library/DataDefs.cpp @@ -218,8 +218,10 @@ virtual_identity::virtual_identity(size_t size, TAllocateFn alloc, virtual_identity::~virtual_identity() { // Remove interpose entries, so that they don't try accessing this object later - while (!interpose_list.empty()) - interpose_list.begin()->second->on_host_delete(this); + for (auto it = interpose_list.begin(); it != interpose_list.end(); ++it) + if (it->second) + it->second->on_host_delete(this); + interpose_list.clear(); } /* Vtable name to identity lookup. */ diff --git a/library/VTableInterpose.cpp b/library/VTableInterpose.cpp index 47110cece..04c436ba7 100644 --- a/library/VTableInterpose.cpp +++ b/library/VTableInterpose.cpp @@ -311,7 +311,7 @@ void VMethodInterposeLinkBase::on_host_delete(virtual_identity *from) // Unlink the chains child_hosts.erase(from); - from->interpose_list.erase(vmethod_idx); + from->interpose_list[vmethod_idx] = NULL; } } From f56287186754ac90056ea79581a30a5c45828961 Mon Sep 17 00:00:00 2001 From: Quietust Date: Sun, 26 Aug 2012 13:58:37 -0500 Subject: [PATCH 20/26] Add ViewCre and Zoom-Cre to Manipulator (by forwarding them to Unitlist) --- plugins/manipulator.cpp | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index 3895b0650..55fee5db0 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -544,6 +544,24 @@ void viewscreen_unitlaborsst::feed(set *events) break; } } + + if (VIRTUAL_CAST_VAR(unitlist, df::viewscreen_unitlistst, parent)) + { + if (events->count(interface_key::UNITJOB_VIEW) || events->count(interface_key::UNITJOB_ZOOM_CRE)) + { + for (int i = 0; i < unitlist->units[unitlist->page].size(); i++) + { + if (unitlist->units[unitlist->page][i] == units[sel_row]->unit) + { + unitlist->cursor_pos[unitlist->page] = i; + unitlist->feed(events); + if (Screen::isDismissed(unitlist)) + Screen::dismiss(this); + break; + } + } + } + } } void OutputString(int8_t color, int &x, int y, const std::string &text) @@ -679,8 +697,13 @@ void viewscreen_unitlaborsst::render() int x = 1; OutputString(10, x, gps->dimy - 3, "Enter"); // SELECT key - OutputString(15, x, gps->dimy - 3, ": Toggle labor"); - x += 2; + OutputString(15, x, gps->dimy - 3, ": Toggle labor, "); + + OutputString(10, x, gps->dimy - 3, "v"); // UNITJOB_VIEW key + OutputString(15, x, gps->dimy - 3, ": ViewCre, "); + + OutputString(10, x, gps->dimy - 3, "c"); // UNITJOB_ZOOM_CRE key + OutputString(15, x, gps->dimy - 3, ": Zoom-Cre, "); OutputString(10, x, gps->dimy - 3, "Esc"); // LEAVESCREEN key OutputString(15, x, gps->dimy - 3, ": Done"); @@ -688,8 +711,7 @@ void viewscreen_unitlaborsst::render() x = 1; OutputString(10, x, gps->dimy - 2, "+"); // SECONDSCROLL_DOWN key OutputString(10, x, gps->dimy - 2, "-"); // SECONDSCROLL_UP key - OutputString(15, x, gps->dimy - 2, ": Sort by Skill"); - x += 2; + OutputString(15, x, gps->dimy - 2, ": Sort by Skill, "); OutputString(10, x, gps->dimy - 2, "*"); // SECONDSCROLL_PAGEDOWN key OutputString(10, x, gps->dimy - 2, "/"); // SECONDSCROLL_PAGEUP key From 5fed060d7dbedd3014eee2675c172d086dbf53a4 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Mon, 27 Aug 2012 16:01:11 +0400 Subject: [PATCH 21/26] Follow field rename in xml. --- library/xml | 2 +- plugins/devel/rprobe.cpp | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/library/xml b/library/xml index abcb667bc..3fc2e1569 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit abcb667bc832048552d8cbc8f4830936f8b63399 +Subproject commit 3fc2e1569ff19953d11b6ea651bc9b8ca832b8a3 diff --git a/plugins/devel/rprobe.cpp b/plugins/devel/rprobe.cpp index 7a091a962..e19dbf9ba 100644 --- a/plugins/devel/rprobe.cpp +++ b/plugins/devel/rprobe.cpp @@ -79,7 +79,7 @@ command_result rprobe (color_ostream &out, vector & parameters) if (parameters.size() == 2) { - if (parameters[0] == "wet") + if (parameters[0] == "rai") set_field = 0; else if (parameters[0] == "veg") set_field = 1; @@ -87,7 +87,7 @@ command_result rprobe (color_ostream &out, vector & parameters) set_field = 2; else if (parameters[0] == "evi") set_field = 3; - else if (parameters[0] == "hil") + else if (parameters[0] == "dra") set_field = 4; else if (parameters[0] == "sav") set_field = 5; @@ -117,7 +117,7 @@ command_result rprobe (color_ostream &out, vector & parameters) if (set && i == to_set) { if (set_field == 0) - rd->wetness = set_val; + rd->rainfall = set_val; else if (set_field == 1) rd->vegetation = set_val; else if (set_field == 2) @@ -125,11 +125,11 @@ command_result rprobe (color_ostream &out, vector & parameters) else if (set_field == 3) rd->evilness = set_val; else if (set_field == 4) - rd->hilliness = set_val; + rd->drainage = set_val; else if (set_field == 5) rd->savagery = set_val; else if (set_field == 6) - rd->saltiness = set_val; + rd->salinity = set_val; } out << i << ": x = " << rg.x << ", y = " << rg.y; @@ -140,13 +140,13 @@ command_result rprobe (color_ostream &out, vector & parameters) " landmass_id: " << rd->landmass_id << " flags: " << hex << rd->flags.as_int() << dec << endl; out << - "wet: " << rd->wetness << " " << + "rai: " << rd->rainfall << " " << "veg: " << rd->vegetation << " " << "tem: " << rd->temperature << " " << "evi: " << rd->evilness << " " << - "hil: " << rd->hilliness << " " << + "dra: " << rd->drainage << " " << "sav: " << rd->savagery << " " << - "sal: " << rd->saltiness; + "sal: " << rd->salinity; int32_t *p = (int32_t *)rd; int c = sizeof(*rd) / sizeof(int32_t); From faf3bdf2b7e7b4d01de2c746397cf6be12cde702 Mon Sep 17 00:00:00 2001 From: Quietust Date: Mon, 27 Aug 2012 09:04:32 -0500 Subject: [PATCH 22/26] Adjust grid display - labors without skills use different tiles, and skills without labors have a red background --- plugins/manipulator.cpp | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index 55fee5db0..71b1fc907 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -630,11 +630,9 @@ void viewscreen_unitlaborsst::render() int col_offset = col + first_column; fg = 15; bg = 0; + char c = 0xFA; if ((col_offset == sel_column) && (row_offset == sel_row)) fg = 9; - if ((columns[col_offset].labor != unit_labor::NONE) && (unit->status.labors[columns[col_offset].labor])) - bg = 7; - char c = '-'; if (columns[col_offset].skill != job_skill::NONE) { df::unit_skill *skill = binsearch_in_vector>(unit->status.current_soul->skills, &df::unit_skill::id, columns[col_offset].skill); @@ -645,7 +643,20 @@ void viewscreen_unitlaborsst::render() level = NUM_SKILL_LEVELS - 1; c = skill_levels[level].abbrev; } + else + c = '-'; } + if (columns[col_offset].labor != unit_labor::NONE) + { + if (unit->status.labors[columns[col_offset].labor]) + { + bg = 7; + if (columns[col_offset].skill == job_skill::NONE) + c = 0xF9; + } + } + else + bg = 4; Screen::paintTile(Screen::Pen(c, fg, bg), 1 + name_width + 1 + prof_width + 1 + col, 3 + row); } } From f1915915b48395e9180c03dc8bf26f303eff877f Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Mon, 27 Aug 2012 23:03:02 +0400 Subject: [PATCH 23/26] Follow change in xml again. --- library/LuaApi.cpp | 4 ++-- library/include/modules/Maps.h | 2 +- library/modules/Maps.cpp | 3 ++- library/xml | 2 +- plugins/changelayer.cpp | 1 + plugins/devel/rprobe.cpp | 3 ++- plugins/probe.cpp | 4 ++-- plugins/prospector.cpp | 3 +++ 8 files changed, 14 insertions(+), 8 deletions(-) diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 00d4c517d..6dfb2f354 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -77,6 +77,7 @@ distribution. #include "df/job_material_category.h" #include "df/burrow.h" #include "df/building_civzonest.h" +#include "df/region_map_entry.h" #include #include @@ -931,8 +932,7 @@ static int maps_getRegionBiome(lua_State *L) static int maps_getTileBiomeRgn(lua_State *L) { auto pos = CheckCoordXYZ(L, 1, true); - Lua::PushPosXY(L, Maps::getTileBiomeRgn(pos)); - return 1; + return Lua::PushPosXY(L, Maps::getTileBiomeRgn(pos)); } static const luaL_Reg dfhack_maps_funcs[] = { diff --git a/library/include/modules/Maps.h b/library/include/modules/Maps.h index e63eef733..e6e9682eb 100644 --- a/library/include/modules/Maps.h +++ b/library/include/modules/Maps.h @@ -258,7 +258,7 @@ inline df::tile_occupancy *getTileOccupancy(df::coord pos) { /** * Returns biome info about the specified world region. */ -DFHACK_EXPORT df::world_data::T_region_map *getRegionBiome(df::coord2d rgn_pos); +DFHACK_EXPORT df::region_map_entry *getRegionBiome(df::coord2d rgn_pos); /** * Returns biome world region coordinates for the given tile within given block. diff --git a/library/modules/Maps.cpp b/library/modules/Maps.cpp index 3ab156d77..4107680b0 100644 --- a/library/modules/Maps.cpp +++ b/library/modules/Maps.cpp @@ -57,6 +57,7 @@ using namespace std; #include "df/builtin_mats.h" #include "df/block_square_event_grassst.h" #include "df/z_level_flags.h" +#include "df/region_map_entry.h" using namespace DFHack; using namespace df::enums; @@ -166,7 +167,7 @@ df::tile_occupancy *Maps::getTileOccupancy(int32_t x, int32_t y, int32_t z) return block ? &block->occupancy[x&15][y&15] : NULL; } -df::world_data::T_region_map *Maps::getRegionBiome(df::coord2d rgn_pos) +df::region_map_entry *Maps::getRegionBiome(df::coord2d rgn_pos) { auto data = world->world_data; if (!data) diff --git a/library/xml b/library/xml index 3fc2e1569..328a8dbdc 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 3fc2e1569ff19953d11b6ea651bc9b8ca832b8a3 +Subproject commit 328a8dbdc7d9e1e838798abf79861cc18a387e3f diff --git a/plugins/changelayer.cpp b/plugins/changelayer.cpp index 317a0fa36..3ab1899af 100644 --- a/plugins/changelayer.cpp +++ b/plugins/changelayer.cpp @@ -19,6 +19,7 @@ #include "df/world_data.h" #include "df/world_geo_biome.h" #include "df/world_geo_layer.h" +#include "df/region_map_entry.h" using namespace DFHack; using namespace df::enums; diff --git a/plugins/devel/rprobe.cpp b/plugins/devel/rprobe.cpp index e19dbf9ba..805489d5e 100644 --- a/plugins/devel/rprobe.cpp +++ b/plugins/devel/rprobe.cpp @@ -27,6 +27,7 @@ using namespace std; #include "df/world_region_details.h" #include "df/world_geo_biome.h" #include "df/world_geo_layer.h" +#include "df/region_map_entry.h" #include "df/inclusion_type.h" #include "df/viewscreen_choose_start_sitest.h" @@ -113,7 +114,7 @@ command_result rprobe (color_ostream &out, vector & parameters) { coord2d rg = screen->biome_rgn[i]; - df::world_data::T_region_map* rd = &data->region_map[rg.x][rg.y]; + auto rd = &data->region_map[rg.x][rg.y]; if (set && i == to_set) { if (set_field == 0) diff --git a/plugins/probe.cpp b/plugins/probe.cpp index 2ae6846d5..45ef1bbfb 100644 --- a/plugins/probe.cpp +++ b/plugins/probe.cpp @@ -27,6 +27,7 @@ using namespace std; #include "df/world.h" #include "df/world_raws.h" #include "df/building_def.h" +#include "df/region_map_entry.h" using std::vector; using std::string; @@ -224,8 +225,7 @@ command_result df_probe (color_ostream &out, vector & parameters) int bx = clip_range(block.region_pos.x + (offset % 3) - 1, 0, world->world_data->world_width-1); int by = clip_range(block.region_pos.y + (offset / 3) - 1, 0, world->world_data->world_height-1); - df::world_data::T_region_map* biome = - &world->world_data->region_map[bx][by]; + auto biome = &world->world_data->region_map[bx][by]; int sav = biome->savagery; int evi = biome->evilness; diff --git a/plugins/prospector.cpp b/plugins/prospector.cpp index e2f1e9534..6836b38c2 100644 --- a/plugins/prospector.cpp +++ b/plugins/prospector.cpp @@ -27,6 +27,7 @@ using namespace std; #include "df/world_region_details.h" #include "df/world_geo_biome.h" #include "df/world_geo_layer.h" +#include "df/region_map_entry.h" #include "df/inclusion_type.h" #include "df/viewscreen_choose_start_sitest.h" @@ -536,6 +537,8 @@ command_result prospector (color_ostream &con, vector & parameters) case tiletype_material::LAVA_STONE: // TODO ? break; + default: + break; } } } From f73cebff68922801e4eaeffbd2b0487d868a3b56 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Mon, 27 Aug 2012 23:03:17 +0400 Subject: [PATCH 24/26] Account for caves and magma sea in pre-embark prospector. --- plugins/prospector.cpp | 292 +++++++++++++++++++++++++++++------------ 1 file changed, 211 insertions(+), 81 deletions(-) diff --git a/plugins/prospector.cpp b/plugins/prospector.cpp index 6836b38c2..dce44e35a 100644 --- a/plugins/prospector.cpp +++ b/plugins/prospector.cpp @@ -25,8 +25,11 @@ using namespace std; #include "df/world.h" #include "df/world_data.h" #include "df/world_region_details.h" +#include "df/world_region_feature.h" #include "df/world_geo_biome.h" #include "df/world_geo_layer.h" +#include "df/world_underground_region.h" +#include "df/feature_init.h" #include "df/region_map_entry.h" #include "df/inclusion_type.h" #include "df/viewscreen_choose_start_sitest.h" @@ -109,9 +112,10 @@ struct compare_pair_second } }; -static void printMatdata(color_ostream &con, const matdata &data) +static void printMatdata(color_ostream &con, const matdata &data, bool only_z = false) { - con << std::setw(9) << data.count; + if (!only_z) + con << std::setw(9) << data.count; if(data.lower_z != data.upper_z) con <<" Z:" << std::setw(4) << data.lower_z << ".." << data.upper_z << std::endl; @@ -226,116 +230,239 @@ static coord2d biome_delta[] = { coord2d(-1,-1), coord2d(0,-1), coord2d(1,-1) }; -static command_result embark_prospector(color_ostream &out, df::viewscreen_choose_start_sitest *screen, - bool showHidden, bool showValue) +struct EmbarkTileLayout { + int elevation; + int min_z, base_z; + std::map penalty; +}; + +bool estimate_underground(color_ostream &out, EmbarkTileLayout &tile, df::world_region_details *details, int x, int y) { - if (!world || !world->world_data) + tile.elevation = ( + details->elevation[x][y] + details->elevation[x][y+1] + + details->elevation[x+1][y] + details->elevation[x+1][y+1] + ) / 4; + tile.base_z = tile.elevation; + tile.penalty.clear(); + + auto &features = details->features[x][y]; + + // Collect global feature layer depths and apply penalties + std::map layer_bottom, layer_top; + bool sea_found = false; + + for (size_t i = 0; i < features.size(); i++) { - out.printerr("World data is not available.\n"); - return CR_FAILURE; + auto feature = features[i]; + auto layer = df::world_underground_region::find(feature->layer); + if (!layer || feature->min_z == -30000) continue; + + layer_bottom[layer->layer_depth] = feature->min_z; + layer_top[layer->layer_depth] = feature->max_z; + tile.base_z = std::min(tile.base_z, (int)feature->min_z); + + float penalty = 1.0f; + switch (layer->type) { + case df::world_underground_region::Cavern: + penalty = 0.75f; + break; + case df::world_underground_region::MagmaSea: + sea_found = true; + tile.min_z = feature->min_z; + for (int i = feature->min_z; i <= feature->max_z; i++) + tile.penalty[i] = 0.2 + 0.6f*(i-feature->min_z)/(feature->max_z-feature->min_z+1); + break; + case df::world_underground_region::Underworld: + penalty = 0.0f; + break; + } + + if (penalty != 1.0f) + { + for (int i = feature->min_z; i <= feature->max_z; i++) + tile.penalty[i] = penalty; + } } - df::world_data *data = world->world_data; - coord2d cur_region = screen->region_pos; - int d_idx = linear_index(data->region_details, &df::world_region_details::pos, cur_region); - auto cur_details = vector_get(data->region_details, d_idx); + if (!sea_found) + { + out.printerr("Could not find magma sea.\n"); + return false; + } - if (!cur_details) + // Scan for big local features and apply their penalties + for (size_t i = 0; i < features.size(); i++) { - out.printerr("Current region details are not available.\n"); - return CR_FAILURE; + auto feature = features[i]; + auto lfeature = Maps::getLocalInitFeature(details->pos, feature->feature_idx); + if (!lfeature) + continue; + + switch (lfeature->getType()) + { + case feature_type::pit: + case feature_type::magma_pool: + case feature_type::volcano: + for (int i = layer_bottom[lfeature->end_depth]; + i <= layer_top[lfeature->start_depth]; i++) + tile.penalty[i] = std::min(0.4f, map_find(tile.penalty, i, 1.0f)); + break; + default: + break; + } } - // Compute biomes - std::map biomes; + return true; +} + +void add_materials(EmbarkTileLayout &tile, matdata &data, float amount, int min_z, int max_z) +{ + for (int z = min_z; z <= max_z; z++) + data.add(z, int(map_find(tile.penalty, z, 1)*amount)); +} + +bool estimate_materials(color_ostream &out, EmbarkTileLayout &tile, MatMap &layerMats, MatMap &veinMats, df::coord2d biome) +{ + using namespace geo_layer_type; - if (screen->biome_highlighted) + df::world_data *data = world->world_data; + int bx = clip_range(biome.x, 0, data->world_width-1); + int by = clip_range(biome.y, 0, data->world_height-1); + auto ®ion = data->region_map[bx][by]; + df::world_geo_biome *geo_biome = df::world_geo_biome::find(region.geo_index); + + if (!geo_biome) { - out.print("Processing one embark tile of biome F%d.\n\n", screen->biome_idx+1); - biomes[screen->biome_rgn[screen->biome_idx]]++; + out.printerr("Region geo-biome not found: (%d,%d)\n", bx, by); + return false; } - else + + // soil depth increases by 1 every 5 levels below 150 + int top_z_level = tile.elevation - std::max((154-tile.elevation)/5,0); + + for (unsigned i = 0; i < geo_biome->layers.size(); i++) { - for (int x = screen->embark_pos_min.x; x <= screen->embark_pos_max.x; x++) + auto layer = geo_biome->layers[i]; + switch (layer->type) { - for (int y = screen->embark_pos_min.y; y <= screen->embark_pos_max.y; y++) - { - int bv = clip_range(cur_details->biome[x][y], 1, 9); - biomes[cur_region + biome_delta[bv-1]]++; - } + case SOIL: + case SOIL_OCEAN: + case SOIL_SAND: + top_z_level += layer->top_height - layer->bottom_height + 1; + break; + default:; } } - // Compute material maps - MatMap layerMats; - MatMap veinMats; + top_z_level = std::max(top_z_level, tile.elevation)-1; - for (auto biome_it = biomes.begin(); biome_it != biomes.end(); ++biome_it) + for (unsigned i = 0; i < geo_biome->layers.size(); i++) { - int bx = clip_range(biome_it->first.x, 0, data->world_width-1); - int by = clip_range(biome_it->first.y, 0, data->world_height-1); - auto ®ion = data->region_map[bx][by]; - df::world_geo_biome *geo_biome = df::world_geo_biome::find(region.geo_index); + auto layer = geo_biome->layers[i]; + + int top_z = std::min(layer->top_height + top_z_level, tile.elevation-1); + int bottom_z = std::max(layer->bottom_height + top_z_level, tile.min_z); + if (i+1 == geo_biome->layers.size()) // stretch layer if needed + bottom_z = tile.min_z; + if (top_z < bottom_z) + continue; + + float layer_size = 48*48; + + int sums[ENUM_LAST_ITEM(inclusion_type)+1] = { 0 }; + + for (unsigned j = 0; j < layer->vein_mat.size(); j++) + if (is_valid_enum_item(layer->vein_type[j])) + sums[layer->vein_type[j]] += layer->vein_unk_38[j]; - if (!geo_biome) + for (unsigned j = 0; j < layer->vein_mat.size(); j++) { - out.printerr("Region geo-biome not found: (%d,%d)\n", bx, by); - return CR_FAILURE; + // TODO: find out how to estimate the real density + // this code assumes that vein_unk_38 is the weight + // used when choosing the vein material + float size = float(layer->vein_unk_38[j]); + df::inclusion_type type = layer->vein_type[j]; + + switch (type) + { + case inclusion_type::VEIN: + // 3 veins of 80 tiles avg + size = size * 80 * 3 / sums[type]; + break; + case inclusion_type::CLUSTER: + // 1 cluster of 700 tiles avg + size = size * 700 * 1 / sums[type]; + break; + case inclusion_type::CLUSTER_SMALL: + size = size * 6 * 7 / sums[type]; + break; + case inclusion_type::CLUSTER_ONE: + size = size * 1 * 5 / sums[type]; + break; + default: + // shouldn't actually happen + size = 1; + } + + layer_size -= size; + + add_materials(tile, veinMats[layer->vein_mat[j]], size, bottom_z, top_z); } - int cnt = biome_it->second; + add_materials(tile, layerMats[layer->mat_index], layer_size, bottom_z, top_z); + } - for (unsigned i = 0; i < geo_biome->layers.size(); i++) - { - auto layer = geo_biome->layers[i]; + return true; +} - layerMats[layer->mat_index].add(layer->bottom_height, 0); +static command_result embark_prospector(color_ostream &out, df::viewscreen_choose_start_sitest *screen, + bool showHidden, bool showValue) +{ + if (!world || !world->world_data) + { + out.printerr("World data is not available.\n"); + return CR_FAILURE; + } - int level_cnt = layer->top_height - layer->bottom_height + 1; - int layer_size = 48*48*cnt*level_cnt; + df::world_data *data = world->world_data; + coord2d cur_region = screen->region_pos; + int d_idx = linear_index(data->region_details, &df::world_region_details::pos, cur_region); + auto cur_details = vector_get(data->region_details, d_idx); - int sums[ENUM_LAST_ITEM(inclusion_type)+1] = { 0 }; + if (!cur_details) + { + out.printerr("Current region details are not available.\n"); + return CR_FAILURE; + } - for (unsigned j = 0; j < layer->vein_mat.size(); j++) - if (is_valid_enum_item(layer->vein_type[j])) - sums[layer->vein_type[j]] += layer->vein_unk_38[j]; + // Compute material maps + MatMap layerMats; + MatMap veinMats; + matdata world_bottom; - for (unsigned j = 0; j < layer->vein_mat.size(); j++) - { - // TODO: find out how to estimate the real density - // this code assumes that vein_unk_38 is the weight - // used when choosing the vein material - int size = layer->vein_unk_38[j]*cnt*level_cnt; - df::inclusion_type type = layer->vein_type[j]; + // Compute biomes + std::map biomes; - switch (type) - { - case inclusion_type::VEIN: - // 3 veins of 80 tiles avg - size = size * 80 * 3 / sums[type]; - break; - case inclusion_type::CLUSTER: - // 1 cluster of 700 tiles avg - size = size * 700 * 1 / sums[type]; - break; - case inclusion_type::CLUSTER_SMALL: - size = size * 6 * 7 / sums[type]; - break; - case inclusion_type::CLUSTER_ONE: - size = size * 1 * 5 / sums[type]; - break; - default: - // shouldn't actually happen - size = cnt*level_cnt; - } + /*if (screen->biome_highlighted) + { + out.print("Processing one embark tile of biome F%d.\n\n", screen->biome_idx+1); + biomes[screen->biome_rgn[screen->biome_idx]]++; + }*/ - veinMats[layer->vein_mat[j]].add(layer->bottom_height, 0); - veinMats[layer->vein_mat[j]].add(layer->top_height, size); + for (int x = screen->embark_pos_min.x; x <= screen->embark_pos_max.x; x++) + { + for (int y = screen->embark_pos_min.y; y <= screen->embark_pos_max.y; y++) + { + int bv = clip_range(cur_details->biome[x][y] & 15, 1, 9); + df::coord2d rgn = cur_region + biome_delta[bv-1]; - layer_size -= size; - } + EmbarkTileLayout tile; + if (!estimate_underground(out, tile, cur_details, x, y) || + !estimate_materials(out, tile, layerMats, veinMats, rgn)) + return CR_FAILURE; - layerMats[layer->mat_index].add(layer->top_height, std::max(0,layer_size)); + world_bottom.add(tile.base_z, 0); + world_bottom.add(tile.elevation-1, 0); } } @@ -349,7 +476,10 @@ static command_result embark_prospector(color_ostream &out, df::viewscreen_choos mats->Finish(); } - out << "Warning: the above data is only a very rough estimate." << std::endl; + out << "Embark depth: " << (world_bottom.upper_z-world_bottom.lower_z+1) << " "; + printMatdata(out, world_bottom, true); + + out << std::endl << "Warning: the above data is only a very rough estimate." << std::endl; return CR_OK; } From c587ea2c74056b392d004f8e5186c076fe0ed783 Mon Sep 17 00:00:00 2001 From: Quietust Date: Mon, 27 Aug 2012 14:06:10 -0500 Subject: [PATCH 25/26] Add new tweak command to make Train orders no longer count as patrol duty --- plugins/tweak.cpp | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/plugins/tweak.cpp b/plugins/tweak.cpp index 70acf7607..591125c5e 100644 --- a/plugins/tweak.cpp +++ b/plugins/tweak.cpp @@ -28,6 +28,7 @@ #include "df/criminal_case.h" #include "df/unit_inventory_item.h" #include "df/viewscreen_dwarfmodest.h" +#include "df/squad_order_trainst.h" #include @@ -72,6 +73,10 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector ¶meters) { @@ -292,6 +308,14 @@ static command_result tweak(color_ostream &out, vector ¶meters) else hook.apply(); } + else if (cmd == "patrol-duty") + { + auto &hook = INTERPOSE_HOOK(patrol_duty_hook, isPatrol); + if (vector_get(parameters, 1) == "disable") + hook.remove(); + else + hook.apply(); + } else return CR_WRONG_USAGE; From 834d7fa1faa09f1c6586b8d3994e05ae29943f15 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Tue, 28 Aug 2012 11:52:54 +0400 Subject: [PATCH 26/26] Tweak prospector: try using the biome elevation for soil depth. Until a better idea presents itself (or maybe it is the right way). Soil depth computation affects which soil layers are reported, and Z level alignment of the layer stack. --- plugins/prospector.cpp | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/plugins/prospector.cpp b/plugins/prospector.cpp index dce44e35a..5eab897c0 100644 --- a/plugins/prospector.cpp +++ b/plugins/prospector.cpp @@ -231,17 +231,31 @@ static coord2d biome_delta[] = { }; struct EmbarkTileLayout { - int elevation; + coord2d biome_off, biome_pos; + df::region_map_entry *biome; + int elevation, max_soil_depth; int min_z, base_z; std::map penalty; }; bool estimate_underground(color_ostream &out, EmbarkTileLayout &tile, df::world_region_details *details, int x, int y) { + // Find actual biome + int bv = clip_range(details->biome[x][y] & 15, 1, 9); + tile.biome_off = biome_delta[bv-1]; + + df::world_data *data = world->world_data; + int bx = clip_range(details->pos.x + tile.biome_off.x, 0, data->world_width-1); + int by = clip_range(details->pos.y + tile.biome_off.y, 0, data->world_height-1); + tile.biome_pos = coord2d(bx, by); + tile.biome = &data->region_map[bx][by]; + + // Compute surface elevation tile.elevation = ( details->elevation[x][y] + details->elevation[x][y+1] + details->elevation[x+1][y] + details->elevation[x+1][y+1] ) / 4; + tile.max_soil_depth = std::max((154-tile.biome->elevation)/5,0); tile.base_z = tile.elevation; tile.penalty.clear(); @@ -321,24 +335,21 @@ void add_materials(EmbarkTileLayout &tile, matdata &data, float amount, int min_ data.add(z, int(map_find(tile.penalty, z, 1)*amount)); } -bool estimate_materials(color_ostream &out, EmbarkTileLayout &tile, MatMap &layerMats, MatMap &veinMats, df::coord2d biome) +bool estimate_materials(color_ostream &out, EmbarkTileLayout &tile, MatMap &layerMats, MatMap &veinMats) { using namespace geo_layer_type; - df::world_data *data = world->world_data; - int bx = clip_range(biome.x, 0, data->world_width-1); - int by = clip_range(biome.y, 0, data->world_height-1); - auto ®ion = data->region_map[bx][by]; - df::world_geo_biome *geo_biome = df::world_geo_biome::find(region.geo_index); + df::world_geo_biome *geo_biome = df::world_geo_biome::find(tile.biome->geo_index); if (!geo_biome) { - out.printerr("Region geo-biome not found: (%d,%d)\n", bx, by); + out.printerr("Region geo-biome not found: (%d,%d)\n", + tile.biome_pos.x, tile.biome_pos.y); return false; } // soil depth increases by 1 every 5 levels below 150 - int top_z_level = tile.elevation - std::max((154-tile.elevation)/5,0); + int top_z_level = tile.elevation - tile.max_soil_depth; for (unsigned i = 0; i < geo_biome->layers.size(); i++) { @@ -453,12 +464,9 @@ static command_result embark_prospector(color_ostream &out, df::viewscreen_choos { for (int y = screen->embark_pos_min.y; y <= screen->embark_pos_max.y; y++) { - int bv = clip_range(cur_details->biome[x][y] & 15, 1, 9); - df::coord2d rgn = cur_region + biome_delta[bv-1]; - EmbarkTileLayout tile; if (!estimate_underground(out, tile, cur_details, x, y) || - !estimate_materials(out, tile, layerMats, veinMats, rgn)) + !estimate_materials(out, tile, layerMats, veinMats)) return CR_FAILURE; world_bottom.add(tile.base_z, 0);