From c68afdaad23ccb9e1c33ec118514c78e5376a72e Mon Sep 17 00:00:00 2001 From: Quietust Date: Fri, 31 Aug 2012 20:35:35 -0500 Subject: [PATCH 01/19] Display command key helper for Manipulator on Unit List, various tweaks --- plugins/manipulator.cpp | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index 3410b69e3..1a90d2eea 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -39,8 +39,6 @@ using df::global::ui; using df::global::gps; using df::global::enabler; -DFHACK_PLUGIN("manipulator"); - struct SkillLevel { const char *name; @@ -668,7 +666,7 @@ void viewscreen_unitlaborsst::render() int col_offset = col + first_column; fg = 15; bg = 0; - char c = 0xFA; + uint8_t c = 0xFA; if ((col_offset == sel_column) && (row_offset == sel_row)) fg = 9; if (columns[col_offset].skill != job_skill::NONE) @@ -747,7 +745,7 @@ void viewscreen_unitlaborsst::render() canToggle = (cur->allowEdit) && (columns[sel_column].labor != unit_labor::NONE); } - int x = 1; + int x = 2; OutputString(10, x, gps->dimy - 3, "Enter"); // SELECT key OutputString(canToggle ? 15 : 8, x, gps->dimy - 3, ": Toggle labor, "); @@ -760,7 +758,7 @@ void viewscreen_unitlaborsst::render() OutputString(10, x, gps->dimy - 3, "c"); // UNITJOB_ZOOM_CRE key OutputString(15, x, gps->dimy - 3, ": Zoom-Cre"); - x = 1; + x = 2; OutputString(10, x, gps->dimy - 2, "Esc"); // LEAVESCREEN key OutputString(15, x, gps->dimy - 2, ": Done, "); @@ -803,23 +801,35 @@ struct unitlist_hook : df::viewscreen_unitlistst } INTERPOSE_NEXT(feed)(input); } + + DEFINE_VMETHOD_INTERPOSE(void, render, ()) + { + INTERPOSE_NEXT(render)(); + + if (units[page].size()) + { + int x = 2; + OutputString(12, x, gps->dimy - 2, "l"); // UNITVIEW_PRF_PROF key + OutputString(15, x, gps->dimy - 2, ": Manage labors"); + } + } }; IMPLEMENT_VMETHOD_INTERPOSE(unitlist_hook, feed); +IMPLEMENT_VMETHOD_INTERPOSE(unitlist_hook, render); + +DFHACK_PLUGIN("manipulator"); DFhackCExport command_result plugin_init ( color_ostream &out, vector &commands) { - if (gps) - { - if (!INTERPOSE_HOOK(unitlist_hook, feed).apply()) - out.printerr("Could not interpose viewscreen_unitlistst::feed\n"); - } - + if (!gps || !INTERPOSE_HOOK(unitlist_hook, feed).apply() || !INTERPOSE_HOOK(unitlist_hook, render).apply()) + out.printerr("Could not insert Dwarf Manipulator hooks!\n"); return CR_OK; } DFhackCExport command_result plugin_shutdown ( color_ostream &out ) { INTERPOSE_HOOK(unitlist_hook, feed).remove(); + INTERPOSE_HOOK(unitlist_hook, render).remove(); return CR_OK; } From e0097d8d43013d72d729e2ae71cdd6a73a110d8d Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 1 Sep 2012 11:25:24 +0400 Subject: [PATCH 02/19] Fix access to unnamed bits in bitfields, and allow hook.apply(false) --- library/DataDefs.cpp | 2 +- library/LuaTypes.cpp | 4 ++-- library/VTableInterpose.cpp | 8 +++++++- library/include/VTableInterpose.h | 2 +- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/library/DataDefs.cpp b/library/DataDefs.cpp index 341164441..fa2aacf78 100644 --- a/library/DataDefs.cpp +++ b/library/DataDefs.cpp @@ -374,7 +374,7 @@ void DFHack::bitfieldToString(std::vector *pvec, const void *p, unsigned size, const bitfield_item_info *items) { for (unsigned i = 0; i < size; i++) { - int value = getBitfieldField(p, i, std::min(1,items[i].size)); + int value = getBitfieldField(p, i, std::max(1,items[i].size)); if (value) { std::string name = format_key(items[i].name, i); diff --git a/library/LuaTypes.cpp b/library/LuaTypes.cpp index e71977960..9f2689fa9 100644 --- a/library/LuaTypes.cpp +++ b/library/LuaTypes.cpp @@ -894,7 +894,7 @@ static int meta_bitfield_len(lua_State *state) static void read_bitfield(lua_State *state, uint8_t *ptr, bitfield_identity *id, int idx) { - int size = id->getBits()[idx].size; + int size = std::max(1, id->getBits()[idx].size); int value = getBitfieldField(ptr, idx, size); if (size <= 1) @@ -951,7 +951,7 @@ static int meta_bitfield_newindex(lua_State *state) } int idx = check_container_index(state, id->getNumBits(), 2, iidx, "write"); - int size = id->getBits()[idx].size; + int size = std::max(1, id->getBits()[idx].size); if (lua_isboolean(state, 3) || lua_isnil(state, 3)) setBitfieldField(ptr, idx, size, lua_toboolean(state, 3)); diff --git a/library/VTableInterpose.cpp b/library/VTableInterpose.cpp index 079890fe4..583ef5184 100644 --- a/library/VTableInterpose.cpp +++ b/library/VTableInterpose.cpp @@ -335,8 +335,14 @@ void VMethodInterposeLinkBase::on_host_delete(virtual_identity *from) } } -bool VMethodInterposeLinkBase::apply() +bool VMethodInterposeLinkBase::apply(bool enable) { + if (!enable) + { + remove(); + return true; + } + if (is_applied()) return true; if (!host->vtable_ptr) diff --git a/library/include/VTableInterpose.h b/library/include/VTableInterpose.h index c9482f82c..7ba6b67aa 100644 --- a/library/include/VTableInterpose.h +++ b/library/include/VTableInterpose.h @@ -159,7 +159,7 @@ namespace DFHack ~VMethodInterposeLinkBase(); bool is_applied() { return applied; } - bool apply(); + bool apply(bool enable = true); void remove(); }; From f158e1894d15c2d835f8ee8b65ce3b094b71e5f6 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 1 Sep 2012 11:29:05 +0400 Subject: [PATCH 03/19] Further work on steam engine. - Display water inside as 'boiling' by hooking item_liquid_miscst. - Store current power in flags to avoid mess if items disappear etc. - Suspend/unsuspend stoke jobs depending on steam level. - Implement intelligent steam use rate and boiler capacity cap. - Modify appearance of special tiles to display status. --- plugins/devel/building_zsteam_engine.txt | 2 + plugins/devel/reaction_zsteam_engine.txt | 3 +- plugins/devel/steam-engine.cpp | 244 +++++++++++++++++------ 3 files changed, 192 insertions(+), 57 deletions(-) diff --git a/plugins/devel/building_zsteam_engine.txt b/plugins/devel/building_zsteam_engine.txt index f76b237d5..572eb4074 100644 --- a/plugins/devel/building_zsteam_engine.txt +++ b/plugins/devel/building_zsteam_engine.txt @@ -30,9 +30,11 @@ building_zsteam_engine [COLOR:2:1:6:0:0:0:0:0:7:0:0] [COLOR:2:2:7:0:0:0:0:0:6:0:0] [COLOR:2:3:7:0:0:MAT:7:0:0] + Tile 15 marks places where machines can connect: [TILE:3:1:15:246:15] [TILE:3:2:'\':19:'/'] [TILE:3:3:7:' ':7] + Color 0:?:1 marks hearth, 1:?:1 water indicator, 4:?:1 magma indicator: [COLOR:3:1:6:0:0:6:0:0:6:0:0] [COLOR:3:2:6:7:0:0:0:1:6:7:0] [COLOR:3:3:1:7:1:0:0:0:4:7:1] diff --git a/plugins/devel/reaction_zsteam_engine.txt b/plugins/devel/reaction_zsteam_engine.txt index b8267cf55..1018510f4 100644 --- a/plugins/devel/reaction_zsteam_engine.txt +++ b/plugins/devel/reaction_zsteam_engine.txt @@ -7,6 +7,7 @@ reaction_other [BUILDING:STEAM_ENGINE:CUSTOM_S] [BUILDING:MAGMA_STEAM_ENGINE:CUSTOM_S] [FUEL] - [PRODUCT:100:1:LIQUID_MISC:NONE:WATER][PRODUCT_DIMENSION:333] [SKILL:SMELT] + Dimension is the number of days it can produce 100 power * 100. + [PRODUCT:100:1:LIQUID_MISC:NONE:WATER][PRODUCT_DIMENSION:1500] diff --git a/plugins/devel/steam-engine.cpp b/plugins/devel/steam-engine.cpp index 23af12177..5a26fb246 100644 --- a/plugins/devel/steam-engine.cpp +++ b/plugins/devel/steam-engine.cpp @@ -22,6 +22,8 @@ #include "df/world.h" #include "df/buildings_other_id.h" #include "df/machine.h" +#include "df/job.h" +#include "df/building_drawbuffer.h" #include "MiscUtils.h" @@ -40,6 +42,8 @@ DFHACK_PLUGIN("steam-engine"); struct steam_engine_workshop { int id; df::building_def_workshopst *def; + bool is_magma; + int max_power, max_capacity; std::vector gear_tiles; df::coord2d hearth_tile; df::coord2d water_tile; @@ -48,42 +52,94 @@ struct steam_engine_workshop { std::vector engines; +steam_engine_workshop *find_steam_engine(int id) +{ + for (size_t i = 0; i < engines.size(); i++) + if (engines[i].id == id) + return &engines[i]; + + return NULL; +} + +static const int hearth_colors[6][2] = { + { COLOR_BLACK, 1 }, + { COLOR_BROWN, 0 }, + { COLOR_RED, 0 }, + { COLOR_RED, 1 }, + { COLOR_BROWN, 1 }, + { COLOR_GREY, 1 } +}; + +struct liquid_hook : df::item_liquid_miscst { + typedef df::item_liquid_miscst interpose_base; + + static const uint32_t BOILING_FLAG = 0x80000000U; + + DEFINE_VMETHOD_INTERPOSE(void, getItemDescription, (std::string *buf, int8_t mode)) + { + if (mat_state.whole & BOILING_FLAG) + buf->append("boiling "); + + INTERPOSE_NEXT(getItemDescription)(buf, mode); + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE(liquid_hook, getItemDescription); + struct workshop_hook : df::building_workshopst { typedef df::building_workshopst interpose_base; steam_engine_workshop *get_steam_engine() { if (type == workshop_type::Custom) - for (size_t i = 0; i < engines.size(); i++) - if (engines[i].id == custom_type) - return &engines[i]; + return find_steam_engine(custom_type); return NULL; } + // Use high bits of flags to store current steam amount. + // This is necessary for consistency if items disappear unexpectedly. + int get_steam_amount() { - int cnt = 0; + return (flags.whole >> 28) & 15; + } - for (size_t i = 0; i < contained_items.size(); i++) - { - if (contained_items[i]->use_mode == 0 && - contained_items[i]->item->flags.bits.in_building) - cnt++; - } + void set_steam_amount(int count) + { + flags.whole = (flags.whole & 0x0FFFFFFFU) | uint32_t((count & 15) << 28); + } + + void absorb_unit(steam_engine_workshop *engine, df::item_liquid_miscst *liquid) + { + liquid->flags.bits.in_building = true; + liquid->mat_state.whole |= liquid_hook::BOILING_FLAG; - return cnt; + // This affects where the steam appears to come from + if (engine->hearth_tile.isValid()) + liquid->pos = df::coord(x1+engine->hearth_tile.x, y1+engine->hearth_tile.y, z); } - int get_power_output(steam_engine_workshop *engine) + bool boil_unit(df::item_liquid_miscst *liquid) { - int maxv = engine->def->needs_magma ? 5 : 3; - return std::min(get_steam_amount(), maxv)*100; + liquid->wear = 4; + liquid->flags.bits.in_building = false; + liquid->temperature = liquid->getBoilingPoint() + 10; + + return liquid->checkMeltBoil(); + } + + void suspend_jobs(bool suspend) + { + for (size_t i = 0; i < jobs.size(); i++) + if (jobs[i]->job_type == job_type::CustomReaction) + jobs[i]->flags.bits.suspend = suspend; } - df::item_liquid_miscst *collect_steam() + df::item_liquid_miscst *collect_steam(steam_engine_workshop *engine, int *count) { df::item_liquid_miscst *first = NULL; + *count = 0; for (int i = contained_items.size()-1; i >= 0; i--) { @@ -98,19 +154,54 @@ struct workshop_hook : df::building_workshopst { if (!liquid->flags.bits.in_building) { if (liquid->mat_type != builtin_mats::WATER || - liquid->dimension != 333 || + liquid->age > 1 || liquid->wear != 0) continue; - liquid->flags.bits.in_building = true; + absorb_unit(engine, liquid); } - first = liquid; + if (*count < engine->max_capacity) + { + first = liquid; + ++*count; + } + else + { + // Overpressure valve + boil_unit(liquid); + } } return first; } + static const int WEAR_TICKS = 806400; + + int get_steam_use_rate(steam_engine_workshop *engine, int dimension, int power_level) + { + // total ticks to wear off completely + float ticks = WEAR_TICKS * 4.0f; + // dimension == days it lasts * 100 + ticks /= 1200.0f * dimension / 100.0f; + // true power use + float power_rate = 1.0f; + // check the actual load + if (auto mptr = df::machine::find(machine.machine_id)) + { + if (mptr->cur_power >= mptr->min_power) + power_rate = float(mptr->min_power) / mptr->cur_power; + else + power_rate = 0.0f; + } + // apply rate; 10% steam is wasted anyway + ticks *= (0.1f + 0.9f*power_rate)*power_level; + // end result + return std::max(1, int(ticks)); + } + + // Furnaces need architecture, and this is a workshop + // only because furnaces cannot connect to machines. DEFINE_VMETHOD_INTERPOSE(bool, needsDesign, ()) { if (get_steam_engine()) @@ -119,11 +210,12 @@ struct workshop_hook : df::building_workshopst { return INTERPOSE_NEXT(needsDesign)(); } + // Machine interface DEFINE_VMETHOD_INTERPOSE(void, getPowerInfo, (df::power_info *info)) { if (auto engine = get_steam_engine()) { - info->produced = get_power_output(engine); + info->produced = std::min(engine->max_power, get_steam_amount())*100; info->consumed = 10; return; } @@ -178,6 +270,7 @@ struct workshop_hook : df::building_workshopst { for (size_t i = 0; i < engine->gear_tiles.size(); i++) { + // the original function connects to the center tile centerx = x1 + engine->gear_tiles[i].x; centery = y1 + engine->gear_tiles[i].y; @@ -199,37 +292,76 @@ struct workshop_hook : df::building_workshopst { { if (auto engine = get_steam_engine()) { - int output = get_power_output(engine); + int old_count = get_steam_amount(); + int old_power = std::min(engine->max_power, old_count); + int cur_count = 0; - if (auto first = collect_steam()) + if (auto first = collect_steam(engine, &cur_count)) { - if (first->incWearTimer(output)) + int rate = get_steam_use_rate(engine, first->dimension, old_power); + + if (first->incWearTimer(rate)) { - while (first->wear_timer >= 806400) + while (first->wear_timer >= WEAR_TICKS) { - first->wear_timer -= 806400; + first->wear_timer -= WEAR_TICKS; first->wear++; } if (first->wear > 3) { - first->flags.bits.in_building = 0; - first->temperature = first->getBoilingPoint()+50; + boil_unit(first); + cur_count--; } } } - int new_out = get_power_output(engine); - if (new_out != output) + if (old_count < engine->max_capacity && cur_count == engine->max_capacity) + suspend_jobs(true); + else if (cur_count <= engine->max_power+1 && old_count > engine->max_power+1) + suspend_jobs(false); + + set_steam_amount(cur_count); + + int cur_power = std::min(engine->max_power, cur_count); + if (cur_power != old_power) { auto mptr = df::machine::find(machine.machine_id); if (mptr) - mptr->cur_power += (new_out - output); + mptr->cur_power += (cur_power - old_power)*100; } } INTERPOSE_NEXT(updateAction)(); } + + DEFINE_VMETHOD_INTERPOSE(void, drawBuilding, (df::building_drawbuffer *db, void *unk)) + { + INTERPOSE_NEXT(drawBuilding)(db, unk); + + if (auto engine = get_steam_engine()) + { + // If machine is running, tweak gear assemblies + auto mptr = df::machine::find(machine.machine_id); + if (mptr && (mptr->visual_phase & 1) != 0) + { + for (size_t i = 0; i < engine->gear_tiles.size(); i++) + { + auto pos = engine->gear_tiles[i]; + db->tile[pos.x][pos.y] = 42; + } + } + + // Use the hearth color to display power level + if (engine->hearth_tile.isValid()) + { + auto pos = engine->hearth_tile; + int power = std::min(engine->max_power, get_steam_amount()); + db->fore[pos.x][pos.y] = hearth_colors[power][0]; + db->bright[pos.x][pos.y] = hearth_colors[power][1]; + } + } + } }; IMPLEMENT_VMETHOD_INTERPOSE(workshop_hook, needsDesign); @@ -240,8 +372,9 @@ IMPLEMENT_VMETHOD_INTERPOSE(workshop_hook, categorize); IMPLEMENT_VMETHOD_INTERPOSE(workshop_hook, uncategorize); IMPLEMENT_VMETHOD_INTERPOSE(workshop_hook, canConnectToMachine); IMPLEMENT_VMETHOD_INTERPOSE(workshop_hook, updateAction); +IMPLEMENT_VMETHOD_INTERPOSE(workshop_hook, drawBuilding); -static void find_engines() +static bool find_engines() { engines.clear(); @@ -284,47 +417,46 @@ static void find_engines() } } - engines.push_back(ws); + ws.is_magma = ws.def->needs_magma; + ws.max_power = ws.is_magma ? 5 : 3; + ws.max_capacity = ws.is_magma ? 10 : 6; + + if (!ws.gear_tiles.empty()) + engines.push_back(ws); } -} -static void enable_hooks() -{ - INTERPOSE_HOOK(workshop_hook, needsDesign).apply(); - INTERPOSE_HOOK(workshop_hook, getPowerInfo).apply(); - INTERPOSE_HOOK(workshop_hook, getMachineInfo).apply(); - INTERPOSE_HOOK(workshop_hook, isPowerSource).apply(); - INTERPOSE_HOOK(workshop_hook, categorize).apply(); - INTERPOSE_HOOK(workshop_hook, uncategorize).apply(); - INTERPOSE_HOOK(workshop_hook, canConnectToMachine).apply(); - INTERPOSE_HOOK(workshop_hook, updateAction).apply(); + return !engines.empty(); } -static void disable_hooks() +static void enable_hooks(bool enable) { - INTERPOSE_HOOK(workshop_hook, needsDesign).remove(); - INTERPOSE_HOOK(workshop_hook, getPowerInfo).remove(); - INTERPOSE_HOOK(workshop_hook, getMachineInfo).remove(); - INTERPOSE_HOOK(workshop_hook, isPowerSource).remove(); - INTERPOSE_HOOK(workshop_hook, categorize).remove(); - INTERPOSE_HOOK(workshop_hook, uncategorize).remove(); - INTERPOSE_HOOK(workshop_hook, canConnectToMachine).remove(); - INTERPOSE_HOOK(workshop_hook, updateAction).remove(); + INTERPOSE_HOOK(liquid_hook, getItemDescription).apply(enable); + + INTERPOSE_HOOK(workshop_hook, needsDesign).apply(enable); + INTERPOSE_HOOK(workshop_hook, getPowerInfo).apply(enable); + INTERPOSE_HOOK(workshop_hook, getMachineInfo).apply(enable); + INTERPOSE_HOOK(workshop_hook, isPowerSource).apply(enable); + INTERPOSE_HOOK(workshop_hook, categorize).apply(enable); + INTERPOSE_HOOK(workshop_hook, uncategorize).apply(enable); + INTERPOSE_HOOK(workshop_hook, canConnectToMachine).apply(enable); + INTERPOSE_HOOK(workshop_hook, updateAction).apply(enable); + INTERPOSE_HOOK(workshop_hook, drawBuilding).apply(enable); } DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) { switch (event) { case SC_MAP_LOADED: - find_engines(); - if (!engines.empty()) + if (find_engines()) { out.print("Detected steam engine workshops - enabling plugin.\n"); - enable_hooks(); + enable_hooks(true); } + else + enable_hooks(false); break; case SC_MAP_UNLOADED: - disable_hooks(); + enable_hooks(false); engines.clear(); break; default: @@ -344,6 +476,6 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector Date: Sat, 1 Sep 2012 14:42:19 +0400 Subject: [PATCH 04/19] Try preventing "boiling water" from freezing, and dump steam on destroy. --- plugins/devel/steam-engine.cpp | 54 ++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/plugins/devel/steam-engine.cpp b/plugins/devel/steam-engine.cpp index 5a26fb246..6f83c41d9 100644 --- a/plugins/devel/steam-engine.cpp +++ b/plugins/devel/steam-engine.cpp @@ -82,9 +82,27 @@ struct liquid_hook : df::item_liquid_miscst { INTERPOSE_NEXT(getItemDescription)(buf, mode); } + + DEFINE_VMETHOD_INTERPOSE(bool, adjustTemperature, (uint16_t temp, int32_t unk)) + { + if (mat_state.whole & BOILING_FLAG) + temp = std::max(int(temp), getBoilingPoint()-1); + + return INTERPOSE_NEXT(adjustTemperature)(temp, unk); + } + + DEFINE_VMETHOD_INTERPOSE(bool, checkTemperatureDamage, ()) + { + if (mat_state.whole & BOILING_FLAG) + temperature = std::max(int(temperature), getBoilingPoint()-1); + + return INTERPOSE_NEXT(checkTemperatureDamage)(); + } }; IMPLEMENT_VMETHOD_INTERPOSE(liquid_hook, getItemDescription); +IMPLEMENT_VMETHOD_INTERPOSE(liquid_hook, adjustTemperature); +IMPLEMENT_VMETHOD_INTERPOSE(liquid_hook, checkTemperatureDamage); struct workshop_hook : df::building_workshopst { typedef df::building_workshopst interpose_base; @@ -114,6 +132,8 @@ struct workshop_hook : df::building_workshopst { { liquid->flags.bits.in_building = true; liquid->mat_state.whole |= liquid_hook::BOILING_FLAG; + liquid->temperature = liquid->getBoilingPoint()-1; + liquid->temperature_fraction = 0; // This affects where the steam appears to come from if (engine->hearth_tile.isValid()) @@ -176,6 +196,28 @@ struct workshop_hook : df::building_workshopst { return first; } + void random_boil() + { + int cnt = 0; + + for (int i = contained_items.size()-1; i >= 0; i--) + { + auto item = contained_items[i]; + if (item->use_mode != 0 || !item->item->flags.bits.in_building) + continue; + + auto liquid = strict_virtual_cast(item->item); + if (!liquid) + continue; + + if (cnt == 0 || random() < RAND_MAX/2) + { + cnt++; + boil_unit(liquid); + } + } + } + static const int WEAR_TICKS = 806400; int get_steam_use_rate(steam_engine_workshop *engine, int dimension, int power_level) @@ -362,6 +404,14 @@ struct workshop_hook : df::building_workshopst { } } } + + DEFINE_VMETHOD_INTERPOSE(void, deconstructItems, (bool noscatter, bool lost)) + { + if (get_steam_engine()) + random_boil(); + + INTERPOSE_NEXT(deconstructItems)(noscatter, lost); + } }; IMPLEMENT_VMETHOD_INTERPOSE(workshop_hook, needsDesign); @@ -373,6 +423,7 @@ IMPLEMENT_VMETHOD_INTERPOSE(workshop_hook, uncategorize); IMPLEMENT_VMETHOD_INTERPOSE(workshop_hook, canConnectToMachine); IMPLEMENT_VMETHOD_INTERPOSE(workshop_hook, updateAction); IMPLEMENT_VMETHOD_INTERPOSE(workshop_hook, drawBuilding); +IMPLEMENT_VMETHOD_INTERPOSE(workshop_hook, deconstructItems); static bool find_engines() { @@ -431,6 +482,8 @@ static bool find_engines() static void enable_hooks(bool enable) { INTERPOSE_HOOK(liquid_hook, getItemDescription).apply(enable); + INTERPOSE_HOOK(liquid_hook, adjustTemperature).apply(enable); + INTERPOSE_HOOK(liquid_hook, checkTemperatureDamage).apply(enable); INTERPOSE_HOOK(workshop_hook, needsDesign).apply(enable); INTERPOSE_HOOK(workshop_hook, getPowerInfo).apply(enable); @@ -441,6 +494,7 @@ static void enable_hooks(bool enable) INTERPOSE_HOOK(workshop_hook, canConnectToMachine).apply(enable); INTERPOSE_HOOK(workshop_hook, updateAction).apply(enable); INTERPOSE_HOOK(workshop_hook, drawBuilding).apply(enable); + INTERPOSE_HOOK(workshop_hook, deconstructItems).apply(enable); } DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) From bae85ac77d977ce543fc2e36f42aed3a10f79a5c Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 1 Sep 2012 17:52:51 +0400 Subject: [PATCH 05/19] Make the steam engine consume liquids from Z level below. --- plugins/devel/steam-engine.cpp | 195 ++++++++++++++++++++++++++++++++- 1 file changed, 193 insertions(+), 2 deletions(-) diff --git a/plugins/devel/steam-engine.cpp b/plugins/devel/steam-engine.cpp index 6f83c41d9..1f45e511a 100644 --- a/plugins/devel/steam-engine.cpp +++ b/plugins/devel/steam-engine.cpp @@ -4,6 +4,8 @@ #include #include #include +#include +#include #include #include #include @@ -24,6 +26,9 @@ #include "df/machine.h" #include "df/job.h" #include "df/building_drawbuffer.h" +#include "df/ui.h" +#include "df/viewscreen_dwarfmodest.h" +#include "df/ui_build_selector.h" #include "MiscUtils.h" @@ -35,6 +40,7 @@ using namespace df::enums; using df::global::gps; using df::global::world; +using df::global::ui; using df::global::ui_build_selector; DFHACK_PLUGIN("steam-engine"); @@ -70,6 +76,29 @@ static const int hearth_colors[6][2] = { { COLOR_GREY, 1 } }; +void enable_updates_at(df::coord pos, bool flow, bool temp) +{ + static const int delta[4][2] = { { -1, -1 }, { 1, -1 }, { -1, 1 }, { 1, 1 } }; + + for (int i = 0; i < 4; i++) + { + auto blk = Maps::getTileBlock(pos.x+delta[i][0], pos.y+delta[i][1], pos.z); + Maps::enableBlockUpdates(blk, flow, temp); + } +} + +void decrement_flow(df::coord pos, int amount) +{ + auto pldes = Maps::getTileDesignation(pos); + if (!pldes) return; + + int nsize = std::max(0, pldes->bits.flow_size - amount); + pldes->bits.flow_size = nsize; + pldes->bits.flow_forbid = (nsize > 3 || pldes->bits.liquid_type == tile_liquid::Magma); + + enable_updates_at(pos, true, false); +} + struct liquid_hook : df::item_liquid_miscst { typedef df::item_liquid_miscst interpose_base; @@ -128,8 +157,65 @@ struct workshop_hook : df::building_workshopst { flags.whole = (flags.whole & 0x0FFFFFFFU) | uint32_t((count & 15) << 28); } - void absorb_unit(steam_engine_workshop *engine, df::item_liquid_miscst *liquid) + bool find_liquids(df::coord *pwater, df::coord *pmagma, bool is_magma, bool any_level) + { + if (!is_magma) + pmagma = NULL; + + for (int x = x1; x <= x2; x++) + { + for (int y = y1; y <= y2; y++) + { + auto ptile = Maps::getTileType(x,y,z); + if (!ptile || !LowPassable(*ptile)) + continue; + + auto pltile = Maps::getTileType(x,y,z-1); + if (!pltile || !FlowPassable(*pltile)) + continue; + + auto pldes = Maps::getTileDesignation(x,y,z-1); + if (!pldes || pldes->bits.flow_size == 0) + continue; + + if (pldes->bits.liquid_type == tile_liquid::Magma) + { + if (!pmagma || (!any_level && pldes->bits.flow_size < 4)) + continue; + + *pmagma = df::coord(x,y,z-1); + if (pwater->isValid()) + return true; + } + else + { + if (!any_level && pldes->bits.flow_size < 3) + continue; + + *pwater = df::coord(x,y,z-1); + if (!pmagma || pmagma->isValid()) + return true; + } + } + } + + return false; + } + + bool absorb_unit(steam_engine_workshop *engine, df::item_liquid_miscst *liquid) { + df::coord water, magma; + + if (!find_liquids(&water, &magma, engine->is_magma, true)) + { + liquid->addWear(WEAR_TICKS*4+1, true, false); + return false; + } + + decrement_flow(water, 1); + if (engine->is_magma) + decrement_flow(magma, 1); + liquid->flags.bits.in_building = true; liquid->mat_state.whole |= liquid_hook::BOILING_FLAG; liquid->temperature = liquid->getBoilingPoint()-1; @@ -138,6 +224,9 @@ struct workshop_hook : df::building_workshopst { // This affects where the steam appears to come from if (engine->hearth_tile.isValid()) liquid->pos = df::coord(x1+engine->hearth_tile.x, y1+engine->hearth_tile.y, z); + + enable_updates_at(liquid->pos, false, true); + return true; } bool boil_unit(df::item_liquid_miscst *liquid) @@ -178,7 +267,8 @@ struct workshop_hook : df::building_workshopst { liquid->wear != 0) continue; - absorb_unit(engine, liquid); + if (!absorb_unit(engine, liquid)) + continue; } if (*count < engine->max_capacity) @@ -330,6 +420,18 @@ struct workshop_hook : df::building_workshopst { return INTERPOSE_NEXT(canConnectToMachine)(info); } + // Operation logic + DEFINE_VMETHOD_INTERPOSE(bool, isUnpowered, ()) + { + if (auto engine = get_steam_engine()) + { + df::coord water, magma; + return !find_liquids(&water, &magma, engine->is_magma, false); + } + + return INTERPOSE_NEXT(isUnpowered)(); + } + DEFINE_VMETHOD_INTERPOSE(void, updateAction, ()) { if (auto engine = get_steam_engine()) @@ -402,6 +504,18 @@ struct workshop_hook : df::building_workshopst { db->fore[pos.x][pos.y] = hearth_colors[power][0]; db->bright[pos.x][pos.y] = hearth_colors[power][1]; } + + // Set liquid indicator state + if (engine->water_tile.isValid() || engine->magma_tile.isValid()) + { + df::coord water, magma; + find_liquids(&water, &magma, engine->is_magma, false); + + if (engine->water_tile.isValid() && !water.isValid()) + db->fore[engine->water_tile.x][engine->water_tile.y] = 0; + if (engine->magma_tile.isValid() && engine->is_magma && !magma.isValid()) + db->fore[engine->magma_tile.x][engine->magma_tile.y] = 0; + } } } @@ -421,10 +535,84 @@ IMPLEMENT_VMETHOD_INTERPOSE(workshop_hook, isPowerSource); IMPLEMENT_VMETHOD_INTERPOSE(workshop_hook, categorize); IMPLEMENT_VMETHOD_INTERPOSE(workshop_hook, uncategorize); IMPLEMENT_VMETHOD_INTERPOSE(workshop_hook, canConnectToMachine); +IMPLEMENT_VMETHOD_INTERPOSE(workshop_hook, isUnpowered); IMPLEMENT_VMETHOD_INTERPOSE(workshop_hook, updateAction); IMPLEMENT_VMETHOD_INTERPOSE(workshop_hook, drawBuilding); IMPLEMENT_VMETHOD_INTERPOSE(workshop_hook, deconstructItems); +struct dwarfmode_hook : df::viewscreen_dwarfmodest +{ + typedef df::viewscreen_dwarfmodest interpose_base; + + steam_engine_workshop *get_steam_engine() + { + if (ui->main.mode == ui_sidebar_mode::Build && + ui_build_selector->stage == 1 && + ui_build_selector->building_type == building_type::Workshop && + ui_build_selector->building_subtype == workshop_type::Custom) + { + return find_steam_engine(ui_build_selector->custom_type); + } + + return NULL; + } + + void check_hanging_tiles(steam_engine_workshop *engine) + { + using df::global::cursor; + + if (!engine) return; + + bool error = false; + + int x1 = cursor->x - engine->def->workloc_x; + int y1 = cursor->y - engine->def->workloc_y; + + for (int x = 0; x < engine->def->dim_x; x++) + { + for (int y = 0; y < engine->def->dim_y; y++) + { + if (ui_build_selector->tiles[x][y] >= 5) + continue; + + auto ptile = Maps::getTileType(x1+x,y1+y,cursor->z); + if (ptile && !isOpenTerrain(*ptile)) + continue; + + ui_build_selector->tiles[x][y] = 6; + error = true; + } + } + + if (error) + { + const char *msg = "Hanging - use down stair."; + ui_build_selector->errors.push_back(new std::string(msg)); + } + } + + DEFINE_VMETHOD_INTERPOSE(void, feed, (set *input)) + { + steam_engine_workshop *engine = get_steam_engine(); + + // Selector insists that workshops cannot be placed hanging + // unless they require magma, so pretend we always do. + if (engine) + engine->def->needs_magma = true; + + INTERPOSE_NEXT(feed)(input); + + // Restore the flag + if (engine) + engine->def->needs_magma = engine->is_magma; + + // And now, check for open space + check_hanging_tiles(get_steam_engine()); + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE(dwarfmode_hook, feed); + static bool find_engines() { engines.clear(); @@ -492,9 +680,12 @@ static void enable_hooks(bool enable) INTERPOSE_HOOK(workshop_hook, categorize).apply(enable); INTERPOSE_HOOK(workshop_hook, uncategorize).apply(enable); INTERPOSE_HOOK(workshop_hook, canConnectToMachine).apply(enable); + INTERPOSE_HOOK(workshop_hook, isUnpowered).apply(enable); INTERPOSE_HOOK(workshop_hook, updateAction).apply(enable); INTERPOSE_HOOK(workshop_hook, drawBuilding).apply(enable); INTERPOSE_HOOK(workshop_hook, deconstructItems).apply(enable); + + INTERPOSE_HOOK(dwarfmode_hook, feed).apply(enable); } DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) From 8536785d1d8b1a87e34e35de36064dd5d9b32333 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 1 Sep 2012 20:46:34 +0400 Subject: [PATCH 06/19] Boilers made out of unsuitable materials should explode! --- library/xml | 2 +- plugins/devel/steam-engine.cpp | 84 ++++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 1 deletion(-) diff --git a/library/xml b/library/xml index 9b3ded158..df8178a98 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 9b3ded15848e830784ef2dc4dea6093175669bc9 +Subproject commit df8178a989373ec7868d9195d82ae5f85145ef81 diff --git a/plugins/devel/steam-engine.cpp b/plugins/devel/steam-engine.cpp index 1f45e511a..2ff12ad42 100644 --- a/plugins/devel/steam-engine.cpp +++ b/plugins/devel/steam-engine.cpp @@ -29,6 +29,8 @@ #include "df/ui.h" #include "df/viewscreen_dwarfmodest.h" #include "df/ui_build_selector.h" +#include "df/flow_info.h" +#include "df/report.h" #include "MiscUtils.h" @@ -50,6 +52,7 @@ struct steam_engine_workshop { df::building_def_workshopst *def; bool is_magma; int max_power, max_capacity; + int wear_temp; std::vector gear_tiles; df::coord2d hearth_tile; df::coord2d water_tile; @@ -99,6 +102,26 @@ void decrement_flow(df::coord pos, int amount) enable_updates_at(pos, true, false); } +bool make_explosion(df::coord pos, int mat_type, int mat_index, int density) +{ + using df::global::flows; + + auto block = Maps::getTileBlock(pos); + if (!flows || !block) + return false; + + auto flow = new df::flow_info(); + flow->type = flow_type::MaterialDust; + flow->mat_type = mat_type; + flow->mat_index = mat_index; + flow->density = std::min(100, density); + flow->pos = pos; + + block->flows.push_back(flow); + flows->push_back(flow); + return true; +} + struct liquid_hook : df::item_liquid_miscst { typedef df::item_liquid_miscst interpose_base; @@ -308,6 +331,60 @@ struct workshop_hook : df::building_workshopst { } } + void explode() + { + int mat_type = builtin_mats::ASH, mat_index = -1; + int cx = (x1+x2)/2, cy = (y1+y2)/2; + int power = std::min(240, get_steam_amount()*80); + + make_explosion(df::coord(cx, cy, z), mat_type, mat_index, power); + make_explosion(df::coord(cx-1, cy, z), mat_type, mat_index, power/3); + make_explosion(df::coord(cx, cy-1, z), mat_type, mat_index, power/3); + make_explosion(df::coord(cx+1, cy, z), mat_type, mat_index, power/3); + make_explosion(df::coord(cx, cy+1, z), mat_type, mat_index, power/3); + + *df::global::pause_state = true; + + Gui::showAnnouncement("A boiler has exploded!", COLOR_RED, true); + auto ann = world->status.announcements.back(); + ann->type = announcement_type::CAVE_COLLAPSE; + ann->pos = df::coord(cx, cy, z); + } + + bool check_component_wear(steam_engine_workshop *engine, int count, int power) + { + for (int i = contained_items.size()-1; i >= 0; i--) + { + auto item = contained_items[i]; + if (item->use_mode != 2) + continue; + + int melt_temp = item->item->getMeltingPoint(); + if (melt_temp >= engine->wear_temp) + continue; + if (item->item->isBuildMat()) + continue; + + auto type = item->item->getType(); + if (type == item_type::TRAPPARTS || item_type::CHAIN) + continue; + + int coeff = power; + if (type == item_type::BARREL) + coeff = count; + + int ticks = coeff*(engine->wear_temp - melt_temp); + + if (item->item->addWear(ticks, true, true)) + { + explode(); + return true; + } + } + + return false; + } + static const int WEAR_TICKS = 806400; int get_steam_use_rate(steam_engine_workshop *engine, int dimension, int power_level) @@ -458,6 +535,9 @@ struct workshop_hook : df::building_workshopst { cur_count--; } } + + if (check_component_wear(engine, old_count, old_power)) + return; } if (old_count < engine->max_capacity && cur_count == engine->max_capacity) @@ -524,6 +604,9 @@ struct workshop_hook : df::building_workshopst { if (get_steam_engine()) random_boil(); + if (lost) + explode(); + INTERPOSE_NEXT(deconstructItems)(noscatter, lost); } }; @@ -659,6 +742,7 @@ static bool find_engines() ws.is_magma = ws.def->needs_magma; ws.max_power = ws.is_magma ? 5 : 3; ws.max_capacity = ws.is_magma ? 10 : 6; + ws.wear_temp = ws.is_magma ? 12000 : 11000; if (!ws.gear_tiles.empty()) engines.push_back(ws); From febfc9aa5b87928432060c9c55f2710a2a6201cc Mon Sep 17 00:00:00 2001 From: warmist Date: Sat, 1 Sep 2012 23:33:49 +0300 Subject: [PATCH 07/19] Fixed bug with gui/mechanism Fixes script not allowing to ran on e.g. levers (focus string: dwarfmode/QueryBuilding/Some/Lever/Empty ) --- scripts/gui/mechanisms.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/gui/mechanisms.lua b/scripts/gui/mechanisms.lua index 6b4b4042b..4468e1dcb 100644 --- a/scripts/gui/mechanisms.lua +++ b/scripts/gui/mechanisms.lua @@ -122,7 +122,7 @@ function MechanismList:onInput(keys) end end -if dfhack.gui.getCurFocus() ~= 'dwarfmode/QueryBuilding/Some' then +if not string.find(dfhack.gui.getCurFocus(), 'dwarfmode/QueryBuilding/Some') then qerror("This script requires the main dwarfmode view in 'q' mode") end From 3713c5ea9eaab262be1d08a92781f62bbb6cf95a Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sun, 2 Sep 2012 14:10:58 +0400 Subject: [PATCH 08/19] Add some APIs required by steam engine to the core. --- LUA_API.rst | 19 +++++ Lua API.html | 14 +++ dfhack.init-example | 3 + library/LuaApi.cpp | 12 +++ library/include/modules/Gui.h | 19 +++++ library/include/modules/Maps.h | 6 ++ library/modules/Gui.cpp | 151 ++++++++++++++++++++++++++++++++- library/modules/Maps.cpp | 34 +++++++- plugins/tweak.cpp | 9 +- 9 files changed, 255 insertions(+), 12 deletions(-) diff --git a/LUA_API.rst b/LUA_API.rst index a7dab21b0..542034f40 100644 --- a/LUA_API.rst +++ b/LUA_API.rst @@ -764,10 +764,20 @@ Gui module Adds a regular announcement with given text, color, and brightness. The is_bright boolean actually seems to invert the brightness. +* ``dfhack.gui.showZoomAnnouncement(type,pos,text,color[,is_bright])`` + + Like above, but also specifies a position you can zoom to from the announcement menu. + * ``dfhack.gui.showPopupAnnouncement(text,color[,is_bright])`` Pops up a titan-style modal announcement window. +* ``dfhack.gui.showAutoAnnouncement(type,pos,text,color[,is_bright])`` + + Uses the type to look up options from announcements.txt, and calls the + above operations accordingly. If enabled, pauses and zooms to position. + + Job module ---------- @@ -959,6 +969,10 @@ Maps module Returns a map block object for given x,y,z in local block coordinates. +* ``dfhack.maps.isValidTilePos(coords)``, or isValidTilePos(x,y,z)`` + + Checks if the given df::coord or x,y,z in local tile coordinates are valid. + * ``dfhack.maps.getTileBlock(coords)``, or ``getTileBlock(x,y,z)`` Returns a map block object for given df::coord or x,y,z in local tile coordinates. @@ -971,6 +985,11 @@ Maps module Enables updates for liquid flow or temperature, unless already active. +* ``dfhack.maps.spawnFlow(pos,type,mat_type,mat_index,dimension)`` + + Spawns a new flow (i.e. steam/mist/dust/etc) at the given pos, and with + the given parameters. Returns it, or *nil* if unsuccessful. + * ``dfhack.maps.getGlobalInitFeature(index)`` Returns the global feature object with the given index. diff --git a/Lua API.html b/Lua API.html index b9f09cf96..63a4c8547 100644 --- a/Lua API.html +++ b/Lua API.html @@ -1019,9 +1019,16 @@ the container itself.

Adds a regular announcement with given text, color, and brightness. The is_bright boolean actually seems to invert the brightness.

+
  • dfhack.gui.showZoomAnnouncement(type,pos,text,color[,is_bright])

    +

    Like above, but also specifies a position you can zoom to from the announcement menu.

    +
  • dfhack.gui.showPopupAnnouncement(text,color[,is_bright])

    Pops up a titan-style modal announcement window.

  • +
  • dfhack.gui.showAutoAnnouncement(type,pos,text,color[,is_bright])

    +

    Uses the type to look up options from announcements.txt, and calls the +above operations accordingly. If enabled, pauses and zooms to position.

    +
  • @@ -1177,6 +1184,9 @@ Returns false in case of error.

  • dfhack.maps.getBlock(x,y,z)

    Returns a map block object for given x,y,z in local block coordinates.

  • +
  • dfhack.maps.isValidTilePos(coords), or isValidTilePos(x,y,z)``

    +

    Checks if the given df::coord or x,y,z in local tile coordinates are valid.

    +
  • dfhack.maps.getTileBlock(coords), or getTileBlock(x,y,z)

    Returns a map block object for given df::coord or x,y,z in local tile coordinates.

  • @@ -1186,6 +1196,10 @@ Returns false in case of error.

  • dfhack.maps.enableBlockUpdates(block[,flow,temperature])

    Enables updates for liquid flow or temperature, unless already active.

  • +
  • dfhack.maps.spawnFlow(pos,type,mat_type,mat_index,dimension)

    +

    Spawns a new flow (i.e. steam/mist/dust/etc) at the given pos, and with +the given parameters. Returns it, or nil if unsuccessful.

    +
  • dfhack.maps.getGlobalInitFeature(index)

    Returns the global feature object with the given index.

  • diff --git a/dfhack.init-example b/dfhack.init-example index d3a28b9b0..9ee5ecc46 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -61,3 +61,6 @@ tweak stable-cursor # stop military from considering training as 'patrol duty' tweak patrol-duty + +# display creature weight in build plate menu as ??K, instead of (???df: Max +tweak readable-build-plate diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 6dfb2f354..4e57b113f 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -78,6 +78,7 @@ distribution. #include "df/burrow.h" #include "df/building_civzonest.h" #include "df/region_map_entry.h" +#include "df/flow_info.h" #include #include @@ -756,7 +757,9 @@ static const LuaWrapper::FunctionReg dfhack_gui_module[] = { WRAPM(Gui, getSelectedUnit), WRAPM(Gui, getSelectedItem), WRAPM(Gui, showAnnouncement), + WRAPM(Gui, showZoomAnnouncement), WRAPM(Gui, showPopupAnnouncement), + WRAPM(Gui, showAutoAnnouncement), { NULL, NULL } }; @@ -912,9 +915,17 @@ static const LuaWrapper::FunctionReg dfhack_maps_module[] = { WRAPM(Maps, getGlobalInitFeature), WRAPM(Maps, getLocalInitFeature), WRAPM(Maps, canWalkBetween), + WRAPM(Maps, spawnFlow), { NULL, NULL } }; +static int maps_isValidTilePos(lua_State *L) +{ + auto pos = CheckCoordXYZ(L, 1, true); + lua_pushboolean(L, Maps::isValidTilePos(pos)); + return 1; +} + static int maps_getTileBlock(lua_State *L) { auto pos = CheckCoordXYZ(L, 1, true); @@ -936,6 +947,7 @@ static int maps_getTileBiomeRgn(lua_State *L) } static const luaL_Reg dfhack_maps_funcs[] = { + { "isValidTilePos", maps_isValidTilePos }, { "getTileBlock", maps_getTileBlock }, { "getRegionBiome", maps_getRegionBiome }, { "getTileBiomeRgn", maps_getTileBiomeRgn }, diff --git a/library/include/modules/Gui.h b/library/include/modules/Gui.h index 58f222419..97e8bd422 100644 --- a/library/include/modules/Gui.h +++ b/library/include/modules/Gui.h @@ -32,6 +32,7 @@ distribution. #include "DataDefs.h" #include "df/init.h" #include "df/ui.h" +#include "df/announcement_type.h" namespace df { struct viewscreen; @@ -92,14 +93,32 @@ namespace DFHack // Show a plain announcement, or a titan-style popup message DFHACK_EXPORT void showAnnouncement(std::string message, int color = 7, bool bright = true); + DFHACK_EXPORT void showZoomAnnouncement(df::announcement_type type, df::coord pos, std::string message, int color = 7, bool bright = true); DFHACK_EXPORT void showPopupAnnouncement(std::string message, int color = 7, bool bright = true); + // Show an announcement with effects determined by announcements.txt + DFHACK_EXPORT void showAutoAnnouncement(df::announcement_type type, df::coord pos, std::string message, int color = 7, bool bright = true); + /* * Cursor and window coords */ DFHACK_EXPORT df::coord getViewportPos(); DFHACK_EXPORT df::coord getCursorPos(); + static const int AREA_MAP_WIDTH = 23; + static const int MENU_WIDTH = 30; + + struct DwarfmodeDims { + int map_x1, map_x2, menu_x1, menu_x2, area_x1, area_x2; + int y1, y2; + bool menu_on, area_on, menu_forced; + }; + + DFHACK_EXPORT DwarfmodeDims getDwarfmodeViewDims(); + + DFHACK_EXPORT void resetDwarfmodeView(bool pause = false); + DFHACK_EXPORT bool revealInDwarfmodeMap(df::coord pos, bool center = false); + 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/include/modules/Maps.h b/library/include/modules/Maps.h index e6e9682eb..984cf16cf 100644 --- a/library/include/modules/Maps.h +++ b/library/include/modules/Maps.h @@ -50,6 +50,7 @@ distribution. #include "df/tile_dig_designation.h" #include "df/tile_traffic.h" #include "df/feature_init.h" +#include "df/flow_type.h" /** * \defgroup grp_maps Maps module and its types @@ -232,6 +233,9 @@ extern DFHACK_EXPORT void getSize(uint32_t& x, uint32_t& y, uint32_t& z); /// get the position of the map on world map extern DFHACK_EXPORT void getPosition(int32_t& x, int32_t& y, int32_t& z); +extern DFHACK_EXPORT bool isValidTilePos(int32_t x, int32_t y, int32_t z); +inline bool isValidTilePos(df::coord pos) { return isValidTilePos(pos.x, pos.y, pos.z); } + /** * Get the map block or NULL if block is not valid */ @@ -272,6 +276,8 @@ inline df::coord2d getTileBiomeRgn(df::coord pos) { // Enables per-frame updates for liquid flow and/or temperature. DFHACK_EXPORT void enableBlockUpdates(df::map_block *blk, bool flow = false, bool temperature = false); +DFHACK_EXPORT df::flow_info *spawnFlow(df::coord pos, df::flow_type type, int mat_type = 0, int mat_index = -1, int density = 100); + /// sorts the block event vector into multiple vectors by type /// mineral veins, what's under ice, blood smears and mud extern DFHACK_EXPORT bool SortBlockEvents(df::map_block *block, diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index 0f28860bf..1ea4bf687 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -43,6 +43,7 @@ using namespace DFHack; #include "modules/Job.h" #include "modules/Screen.h" +#include "modules/Maps.h" #include "DataDefs.h" #include "df/world.h" @@ -81,6 +82,8 @@ using namespace DFHack; #include "df/graphic.h" #include "df/layer_object_listst.h" #include "df/assign_trade_status.h" +#include "df/announcement_flags.h" +#include "df/announcements.h" using namespace df::enums; using df::global::gview; @@ -88,6 +91,9 @@ using df::global::init; using df::global::gps; using df::global::ui; using df::global::world; +using df::global::selection_rect; +using df::global::ui_menu_width; +using df::global::ui_area_map_width; static df::layer_object_listst *getLayerList(df::viewscreen_layerst *layer, int idx) { @@ -921,8 +927,9 @@ df::item *Gui::getSelectedItem(color_ostream &out, bool quiet) // -void Gui::showAnnouncement(std::string message, int color, bool bright) -{ +static void doShowAnnouncement( + df::announcement_type type, df::coord pos, std::string message, int color, bool bright +) { using df::global::world; using df::global::cur_year; using df::global::cur_year_tick; @@ -948,6 +955,9 @@ void Gui::showAnnouncement(std::string message, int color, bool bright) { df::report *new_rep = new df::report(); + new_rep->type = type; + new_rep->pos = pos; + new_rep->color = color; new_rep->bright = bright; new_rep->year = year; @@ -969,7 +979,17 @@ void Gui::showAnnouncement(std::string message, int color, bool bright) world->status.announcements.push_back(new_rep); world->status.display_timer = 2000; } +} + +void Gui::showAnnouncement(std::string message, int color, bool bright) +{ + doShowAnnouncement(df::announcement_type(0), df::coord(), message, color, bright); +} +void Gui::showZoomAnnouncement( + df::announcement_type type, df::coord pos, std::string message, int color, bool bright +) { + doShowAnnouncement(type, pos, message, color, bright); } void Gui::showPopupAnnouncement(std::string message, int color, bool bright) @@ -983,6 +1003,29 @@ void Gui::showPopupAnnouncement(std::string message, int color, bool bright) world->status.popups.push_back(popup); } +void Gui::showAutoAnnouncement( + df::announcement_type type, df::coord pos, std::string message, int color, bool bright +) { + using df::global::announcements; + + df::announcement_flags flags; + if (is_valid_enum_item(type) && announcements) + flags = announcements->flags[type]; + + doShowAnnouncement(type, pos, message, color, bright); + + if (flags.bits.DO_MEGA || flags.bits.PAUSE || flags.bits.RECENTER) + { + resetDwarfmodeView(flags.bits.DO_MEGA || flags.bits.PAUSE); + + if (flags.bits.RECENTER && pos.isValid()) + revealInDwarfmodeMap(pos, true); + } + + if (flags.bits.DO_MEGA) + showPopupAnnouncement(message, color, bright); +} + df::viewscreen *Gui::getCurViewscreen(bool skip_dismissed) { df::viewscreen * ws = &gview->view; @@ -1015,6 +1058,110 @@ df::coord Gui::getCursorPos() return df::coord(cursor->x, cursor->y, cursor->z); } +Gui::DwarfmodeDims Gui::getDwarfmodeViewDims() +{ + DwarfmodeDims dims; + + auto ws = Screen::getWindowSize(); + dims.y1 = 1; + dims.y2 = ws.y-2; + dims.map_x1 = 1; + dims.map_x2 = ws.x-2; + dims.area_x1 = dims.area_x2 = dims.menu_x1 = dims.menu_x2 = -1; + dims.menu_forced = false; + + int menu_pos = (ui_menu_width ? *ui_menu_width : 2); + int area_pos = (ui_area_map_width ? *ui_area_map_width : 3); + + if (ui && ui->main.mode && menu_pos >= area_pos) + { + dims.menu_forced = true; + menu_pos = area_pos-1; + } + + dims.area_on = (area_pos < 3); + dims.menu_on = (menu_pos < area_pos); + + if (dims.menu_on) + { + dims.menu_x2 = ws.x - 2; + dims.menu_x1 = dims.menu_x2 - Gui::MENU_WIDTH + 1; + if (menu_pos == 1) + dims.menu_x1 -= Gui::AREA_MAP_WIDTH + 1; + dims.map_x2 = dims.menu_x1 - 2; + } + if (dims.area_on) + { + dims.area_x2 = ws.x-2; + dims.area_x1 = dims.area_x2 - Gui::AREA_MAP_WIDTH + 1; + if (dims.menu_on) + dims.menu_x2 = dims.area_x1 - 2; + else + dims.map_x2 = dims.area_x1 - 2; + } + + return dims; +} + +void Gui::resetDwarfmodeView(bool pause) +{ + using df::global::cursor; + + if (ui) + { + ui->follow_unit = -1; + ui->follow_item = -1; + ui->main.mode = ui_sidebar_mode::Default; + } + + if (selection_rect) + { + selection_rect->start_x = -30000; + selection_rect->end_x = -30000; + } + + if (cursor) + cursor->x = cursor->y = cursor->z = -30000; + + if (pause && df::global::pause_state) + *df::global::pause_state = true; +} + +bool Gui::revealInDwarfmodeMap(df::coord pos, bool center) +{ + using df::global::window_x; + using df::global::window_y; + using df::global::window_z; + + if (!window_x || !window_y || !window_z || !world) + return false; + if (!Maps::isValidTilePos(pos)) + return false; + + auto dims = getDwarfmodeViewDims(); + int w = dims.map_x2 - dims.map_x1 + 1; + int h = dims.y2 - dims.y1 + 1; + + *window_z = pos.z; + + if (center) + { + *window_x = pos.x - w/2; + *window_y = pos.y - h/2; + } + else + { + while (*window_x + w < pos.x+5) *window_x += 10; + while (*window_y + h < pos.y+5) *window_y += 10; + while (*window_x + 5 > pos.x) *window_x -= 10; + while (*window_y + 5 > pos.y) *window_y -= 10; + } + + *window_x = std::max(0, std::min(*window_x, world->map.x_count-w)); + *window_y = std::max(0, std::min(*window_y, world->map.y_count-h)); + return true; +} + bool Gui::getViewCoords (int32_t &x, int32_t &y, int32_t &z) { x = *df::global::window_x; diff --git a/library/modules/Maps.cpp b/library/modules/Maps.cpp index 4107680b0..305f1296d 100644 --- a/library/modules/Maps.cpp +++ b/library/modules/Maps.cpp @@ -58,6 +58,7 @@ using namespace std; #include "df/block_square_event_grassst.h" #include "df/z_level_flags.h" #include "df/region_map_entry.h" +#include "df/flow_info.h" using namespace DFHack; using namespace df::enums; @@ -138,13 +139,20 @@ df::map_block *Maps::getBlock (int32_t blockx, int32_t blocky, int32_t blockz) return world->map.block_index[blockx][blocky][blockz]; } -df::map_block *Maps::getTileBlock (int32_t x, int32_t y, int32_t z) +bool Maps::isValidTilePos(int32_t x, int32_t y, int32_t z) { if (!IsValid()) - return NULL; + return false; if ((x < 0) || (y < 0) || (z < 0)) - return NULL; + return false; if ((x >= world->map.x_count) || (y >= world->map.y_count) || (z >= world->map.z_count)) + return false; + return true; +} + +df::map_block *Maps::getTileBlock (int32_t x, int32_t y, int32_t z) +{ + if (!isValidTilePos(x,y,z)) return NULL; return world->map.block_index[x >> 4][y >> 4][z]; } @@ -204,6 +212,26 @@ void Maps::enableBlockUpdates(df::map_block *blk, bool flow, bool temperature) } } +df::flow_info *Maps::spawnFlow(df::coord pos, df::flow_type type, int mat_type, int mat_index, int density) +{ + using df::global::flows; + + auto block = getTileBlock(pos); + if (!flows || !block) + return NULL; + + auto flow = new df::flow_info(); + flow->type = type; + flow->mat_type = mat_type; + flow->mat_index = mat_index; + flow->density = std::min(100, density); + flow->pos = pos; + + block->flows.push_back(flow); + flows->push_back(flow); + return flow; +} + df::feature_init *Maps::getGlobalInitFeature(int32_t index) { auto data = world->world_data; diff --git a/plugins/tweak.cpp b/plugins/tweak.cpp index fa99f39e5..9853f7f95 100644 --- a/plugins/tweak.cpp +++ b/plugins/tweak.cpp @@ -211,9 +211,6 @@ struct patrol_duty_hook : df::squad_order_trainst IMPLEMENT_VMETHOD_INTERPOSE(patrol_duty_hook, isPatrol); -static const int AREA_MAP_WIDTH = 23; -static const int MENU_WIDTH = 30; - struct readable_build_plate_hook : df::viewscreen_dwarfmodest { typedef df::viewscreen_dwarfmodest interpose_base; @@ -228,10 +225,8 @@ struct readable_build_plate_hook : df::viewscreen_dwarfmodest ui_build_selector->building_subtype == trap_type::PressurePlate && ui_build_selector->plate_info.flags.bits.units) { - auto wsize = Screen::getWindowSize(); - int x = wsize.x - MENU_WIDTH - 1; - if (*ui_menu_width == 1 || *ui_area_map_width == 2) - x -= AREA_MAP_WIDTH + 1; + auto dims = Gui::getDwarfmodeViewDims(); + int x = dims.menu_x1; Screen::Pen pen(' ',COLOR_WHITE); From 9c3843c1d48c55b718fb1a770340f05170590b38 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sun, 2 Sep 2012 14:59:13 +0400 Subject: [PATCH 09/19] Use new API in steam engine; always explode if destroyed with steam inside. --- plugins/devel/steam-engine.cpp | 287 ++++++++++++++++++++++----------- 1 file changed, 189 insertions(+), 98 deletions(-) diff --git a/plugins/devel/steam-engine.cpp b/plugins/devel/steam-engine.cpp index 2ff12ad42..ac317687e 100644 --- a/plugins/devel/steam-engine.cpp +++ b/plugins/devel/steam-engine.cpp @@ -47,12 +47,18 @@ using df::global::ui_build_selector; DFHACK_PLUGIN("steam-engine"); +/* + * List of known steam engine workshop raws. + */ + struct steam_engine_workshop { int id; df::building_def_workshopst *def; + // Cached properties bool is_magma; int max_power, max_capacity; int wear_temp; + // Special tiles (relative position) std::vector gear_tiles; df::coord2d hearth_tile; df::coord2d water_tile; @@ -70,6 +76,10 @@ steam_engine_workshop *find_steam_engine(int id) return NULL; } +/* + * Misc utilities. + */ + static const int hearth_colors[6][2] = { { COLOR_BLACK, 1 }, { COLOR_BROWN, 0 }, @@ -102,26 +112,57 @@ void decrement_flow(df::coord pos, int amount) enable_updates_at(pos, true, false); } -bool make_explosion(df::coord pos, int mat_type, int mat_index, int density) +void make_explosion(df::coord center, int power) { - using df::global::flows; + static const int bias[9] = { + 60, 30, 60, + 30, 0, 30, + 60, 30, 60 + }; - auto block = Maps::getTileBlock(pos); - if (!flows || !block) - return false; + int mat_type = builtin_mats::WATER, mat_index = -1; + int i = 0; + + for (int dx = -1; dx <= 1; dx++) + { + for (int dy = -1; dy <= 1; dy++) + { + int size = power - bias[i++]; + auto pos = center + df::coord(dx,dy,0); + + if (size > 0) + Maps::spawnFlow(pos, flow_type::MaterialDust, mat_type, mat_index, size); + } + } + + Gui::showAutoAnnouncement( + announcement_type::CAVE_COLLAPSE, center, + "A boiler has exploded!", COLOR_RED, true + ); +} + +static const int WEAR_TICKS = 806400; - auto flow = new df::flow_info(); - flow->type = flow_type::MaterialDust; - flow->mat_type = mat_type; - flow->mat_index = mat_index; - flow->density = std::min(100, density); - flow->pos = pos; +bool add_wear_nodestroy(df::item_actual *item, int rate) +{ + if (item->incWearTimer(rate)) + { + while (item->wear_timer >= WEAR_TICKS) + { + item->wear_timer -= WEAR_TICKS; + item->wear++; + } + } - block->flows.push_back(flow); - flows->push_back(flow); - return true; + return item->wear > 3; } +/* + * Hook for the liquid item. Implements a special 'boiling' + * matter state with a modified description and temperature + * locked at boiling-1. + */ + struct liquid_hook : df::item_liquid_miscst { typedef df::item_liquid_miscst interpose_base; @@ -156,9 +197,15 @@ IMPLEMENT_VMETHOD_INTERPOSE(liquid_hook, getItemDescription); IMPLEMENT_VMETHOD_INTERPOSE(liquid_hook, adjustTemperature); IMPLEMENT_VMETHOD_INTERPOSE(liquid_hook, checkTemperatureDamage); +/* + * Hook for the workshop itself. Implements core logic. + */ + struct workshop_hook : df::building_workshopst { typedef df::building_workshopst interpose_base; + // Engine detection + steam_engine_workshop *get_steam_engine() { if (type == workshop_type::Custom) @@ -167,6 +214,11 @@ struct workshop_hook : df::building_workshopst { return NULL; } + inline bool is_fully_built() + { + return getBuildStage() >= getMaxBuildStage(); + } + // Use high bits of flags to store current steam amount. // This is necessary for consistency if items disappear unexpectedly. @@ -180,6 +232,8 @@ struct workshop_hook : df::building_workshopst { flags.whole = (flags.whole & 0x0FFFFFFFU) | uint32_t((count & 15) << 28); } + // Find liquids to consume below the engine. + bool find_liquids(df::coord *pwater, df::coord *pmagma, bool is_magma, bool any_level) { if (!is_magma) @@ -225,13 +279,17 @@ struct workshop_hook : df::building_workshopst { return false; } + // Absorbs a water item produced by stoke reaction into the engine. + bool absorb_unit(steam_engine_workshop *engine, df::item_liquid_miscst *liquid) { + // Consume liquid inputs df::coord water, magma; if (!find_liquids(&water, &magma, engine->is_magma, true)) { - liquid->addWear(WEAR_TICKS*4+1, true, false); + // Destroy the item with enormous wear amount. + liquid->addWear(WEAR_TICKS*5, true, false); return false; } @@ -239,6 +297,7 @@ struct workshop_hook : df::building_workshopst { if (engine->is_magma) decrement_flow(magma, 1); + // Update flags liquid->flags.bits.in_building = true; liquid->mat_state.whole |= liquid_hook::BOILING_FLAG; liquid->temperature = liquid->getBoilingPoint()-1; @@ -248,6 +307,7 @@ struct workshop_hook : df::building_workshopst { if (engine->hearth_tile.isValid()) liquid->pos = df::coord(x1+engine->hearth_tile.x, y1+engine->hearth_tile.y, z); + // Enable block temperature updates enable_updates_at(liquid->pos, false, true); return true; } @@ -268,6 +328,8 @@ struct workshop_hook : df::building_workshopst { jobs[i]->flags.bits.suspend = suspend; } + // Scan contained items for boiled steam to absorb. + df::item_liquid_miscst *collect_steam(steam_engine_workshop *engine, int *count) { df::item_liquid_miscst *first = NULL; @@ -290,6 +352,7 @@ struct workshop_hook : df::building_workshopst { liquid->wear != 0) continue; + // This may destroy the item if (!absorb_unit(engine, liquid)) continue; } @@ -303,6 +366,7 @@ struct workshop_hook : df::building_workshopst { { // Overpressure valve boil_unit(liquid); + suspend_jobs(true); } } @@ -331,62 +395,47 @@ struct workshop_hook : df::building_workshopst { } } - void explode() + int classify_component(df::building_actual::T_contained_items *item) { - int mat_type = builtin_mats::ASH, mat_index = -1; - int cx = (x1+x2)/2, cy = (y1+y2)/2; - int power = std::min(240, get_steam_amount()*80); + if (item->use_mode != 2 || item->item->isBuildMat()) + return -1; - make_explosion(df::coord(cx, cy, z), mat_type, mat_index, power); - make_explosion(df::coord(cx-1, cy, z), mat_type, mat_index, power/3); - make_explosion(df::coord(cx, cy-1, z), mat_type, mat_index, power/3); - make_explosion(df::coord(cx+1, cy, z), mat_type, mat_index, power/3); - make_explosion(df::coord(cx, cy+1, z), mat_type, mat_index, power/3); - - *df::global::pause_state = true; - - Gui::showAnnouncement("A boiler has exploded!", COLOR_RED, true); - auto ann = world->status.announcements.back(); - ann->type = announcement_type::CAVE_COLLAPSE; - ann->pos = df::coord(cx, cy, z); + switch (item->item->getType()) + { + case item_type::TRAPPARTS: + case item_type::CHAIN: + return 0; + case item_type::BARREL: + return 2; + default: + return 1; + } } bool check_component_wear(steam_engine_workshop *engine, int count, int power) { + int coeffs[3] = { 0, power, count }; + for (int i = contained_items.size()-1; i >= 0; i--) { - auto item = contained_items[i]; - if (item->use_mode != 2) - continue; - - int melt_temp = item->item->getMeltingPoint(); - if (melt_temp >= engine->wear_temp) - continue; - if (item->item->isBuildMat()) + int type = classify_component(contained_items[i]); + if (type < 0) continue; - auto type = item->item->getType(); - if (type == item_type::TRAPPARTS || item_type::CHAIN) + df::item *item = contained_items[i]->item; + int melt_temp = item->getMeltingPoint(); + if (coeffs[type] == 0 || melt_temp >= engine->wear_temp) continue; - int coeff = power; - if (type == item_type::BARREL) - coeff = count; - - int ticks = coeff*(engine->wear_temp - melt_temp); - - if (item->item->addWear(ticks, true, true)) - { - explode(); + // let 500 degree delta at 4 pressure work 1 season + float ticks = coeffs[type]*(engine->wear_temp - melt_temp)*3.0f/500.0f/4.0f; + if (item->addWear(int(8*(1 + ticks)), true, true)) return true; - } } return false; } - static const int WEAR_TICKS = 806400; - int get_steam_use_rate(steam_engine_workshop *engine, int dimension, int power_level) { // total ticks to wear off completely @@ -409,6 +458,61 @@ struct workshop_hook : df::building_workshopst { return std::max(1, int(ticks)); } + void update_under_construction(steam_engine_workshop *engine) + { + if (machine.machine_id != -1) + return; + + int cur_count = 0; + + if (auto first = collect_steam(engine, &cur_count)) + { + if (add_wear_nodestroy(first, WEAR_TICKS*4/10)) + { + boil_unit(first); + cur_count--; + } + } + + set_steam_amount(cur_count); + } + + void update_working(steam_engine_workshop *engine) + { + int old_count = get_steam_amount(); + int old_power = std::min(engine->max_power, old_count); + int cur_count = 0; + + if (auto first = collect_steam(engine, &cur_count)) + { + int rate = get_steam_use_rate(engine, first->dimension, old_power); + + if (add_wear_nodestroy(first, rate)) + { + boil_unit(first); + cur_count--; + } + + if (check_component_wear(engine, old_count, old_power)) + return; + } + + if (old_count < engine->max_capacity && cur_count == engine->max_capacity) + suspend_jobs(true); + else if (cur_count <= engine->max_power+1 && old_count > engine->max_power+1) + suspend_jobs(false); + + set_steam_amount(cur_count); + + int cur_power = std::min(engine->max_power, cur_count); + if (cur_power != old_power) + { + auto mptr = df::machine::find(machine.machine_id); + if (mptr) + mptr->cur_power += (cur_power - old_power)*100; + } + } + // Furnaces need architecture, and this is a workshop // only because furnaces cannot connect to machines. DEFINE_VMETHOD_INTERPOSE(bool, needsDesign, ()) @@ -513,47 +617,13 @@ struct workshop_hook : df::building_workshopst { { if (auto engine = get_steam_engine()) { - int old_count = get_steam_amount(); - int old_power = std::min(engine->max_power, old_count); - int cur_count = 0; - - if (auto first = collect_steam(engine, &cur_count)) - { - int rate = get_steam_use_rate(engine, first->dimension, old_power); - - if (first->incWearTimer(rate)) - { - while (first->wear_timer >= WEAR_TICKS) - { - first->wear_timer -= WEAR_TICKS; - first->wear++; - } - - if (first->wear > 3) - { - boil_unit(first); - cur_count--; - } - } - - if (check_component_wear(engine, old_count, old_power)) - return; - } - - if (old_count < engine->max_capacity && cur_count == engine->max_capacity) - suspend_jobs(true); - else if (cur_count <= engine->max_power+1 && old_count > engine->max_power+1) - suspend_jobs(false); - - set_steam_amount(cur_count); + if (is_fully_built()) + update_working(engine); + else + update_under_construction(engine); - int cur_power = std::min(engine->max_power, cur_count); - if (cur_power != old_power) - { - auto mptr = df::machine::find(machine.machine_id); - if (mptr) - mptr->cur_power += (cur_power - old_power)*100; - } + if (flags.bits.almost_deleted) + return; } INTERPOSE_NEXT(updateAction)(); @@ -565,6 +635,9 @@ struct workshop_hook : df::building_workshopst { if (auto engine = get_steam_engine()) { + if (!is_fully_built()) + return; + // If machine is running, tweak gear assemblies auto mptr = df::machine::find(machine.machine_id); if (mptr && (mptr->visual_phase & 1) != 0) @@ -602,10 +675,18 @@ struct workshop_hook : df::building_workshopst { DEFINE_VMETHOD_INTERPOSE(void, deconstructItems, (bool noscatter, bool lost)) { if (get_steam_engine()) - random_boil(); + { + // Explode if any steam left + if (int amount = get_steam_amount()) + { + make_explosion( + df::coord((x1+x2)/2, (y1+y2)/2, z), + 40 + amount * 20 + ); - if (lost) - explode(); + random_boil(); + } + } INTERPOSE_NEXT(deconstructItems)(noscatter, lost); } @@ -623,6 +704,11 @@ IMPLEMENT_VMETHOD_INTERPOSE(workshop_hook, updateAction); IMPLEMENT_VMETHOD_INTERPOSE(workshop_hook, drawBuilding); IMPLEMENT_VMETHOD_INTERPOSE(workshop_hook, deconstructItems); +/* + * Hook for the dwarfmode screen. Tweaks the build menu + * behavior to suit the steam engine building more. + */ + struct dwarfmode_hook : df::viewscreen_dwarfmodest { typedef df::viewscreen_dwarfmodest interpose_base; @@ -669,7 +755,7 @@ struct dwarfmode_hook : df::viewscreen_dwarfmodest if (error) { - const char *msg = "Hanging - use down stair."; + const char *msg = "Hanging - cover channels with down stairs."; ui_build_selector->errors.push_back(new std::string(msg)); } } @@ -689,13 +775,18 @@ struct dwarfmode_hook : df::viewscreen_dwarfmodest if (engine) engine->def->needs_magma = engine->is_magma; - // And now, check for open space + // And now, check for open space. Since these workshops + // are machines, they will collapse over true open space. check_hanging_tiles(get_steam_engine()); } }; IMPLEMENT_VMETHOD_INTERPOSE(dwarfmode_hook, feed); +/* + * Scan raws for matching workshop buildings. + */ + static bool find_engines() { engines.clear(); From 67630776eedc096f4386b515a6d856602d070f93 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sun, 2 Sep 2012 17:17:18 +0400 Subject: [PATCH 10/19] Tweak steam engine raws. --- ...m_engine.txt => building_steam_engine.txt} | 54 ++++++++++--------- plugins/devel/item_trapcomp_steam_engine.txt | 12 +++++ ...m_engine.txt => reaction_steam_engine.txt} | 5 +- plugins/devel/steam-engine.cpp | 14 ++--- 4 files changed, 52 insertions(+), 33 deletions(-) rename plugins/devel/{building_zsteam_engine.txt => building_steam_engine.txt} (57%) create mode 100644 plugins/devel/item_trapcomp_steam_engine.txt rename plugins/devel/{reaction_zsteam_engine.txt => reaction_steam_engine.txt} (57%) diff --git a/plugins/devel/building_zsteam_engine.txt b/plugins/devel/building_steam_engine.txt similarity index 57% rename from plugins/devel/building_zsteam_engine.txt rename to plugins/devel/building_steam_engine.txt index 572eb4074..48657b0c1 100644 --- a/plugins/devel/building_zsteam_engine.txt +++ b/plugins/devel/building_steam_engine.txt @@ -1,4 +1,4 @@ -building_zsteam_engine +building_steam_engine [OBJECT:BUILDING] @@ -15,35 +15,36 @@ building_zsteam_engine [TILE:0:1:240:' ':254] [TILE:0:2:' ':' ':128] [TILE:0:3:246:' ':' '] - [COLOR:0:1:MAT:0:0:0:7:0:0] + [COLOR:0:1:6:0:0:0:0:0:7:0:0] [COLOR:0:2:0:0:0:0:0:0:7:0:0] - [COLOR:0:3:6:0:0:0:0:0:0:0:0] + [COLOR:0:3:MAT:0:0:0:0:0:0] [TILE:1:1:246:128:' '] [TILE:1:2:' ':' ':254] - [TILE:1:3:254:240:240] - [COLOR:1:1:6:0:0:7:0:0:0:0:0] + [TILE:1:3:254:'/':240] + [COLOR:1:1:MAT:7:0:0:0:0:0] [COLOR:1:2:0:0:0:0:0:0:7:0:0] - [COLOR:1:3:7:0:0:MAT:MAT] + [COLOR:1:3:7:0:0:6:0:0:6:0:0] [TILE:2:1:21:' ':128] [TILE:2:2:128:' ':246] [TILE:2:3:177:19:177] [COLOR:2:1:6:0:0:0:0:0:7:0:0] - [COLOR:2:2:7:0:0:0:0:0:6:0:0] - [COLOR:2:3:7:0:0:MAT:7:0:0] - Tile 15 marks places where machines can connect: + [COLOR:2:2:7:0:0:0:0:0:MAT] + [COLOR:2:3:7:0:0:6:0:0:7:0:0] + Tile 15 marks places where machines can connect. + Tile 19 marks the hearth (color changed to reflect power). [TILE:3:1:15:246:15] [TILE:3:2:'\':19:'/'] [TILE:3:3:7:' ':7] - Color 0:?:1 marks hearth, 1:?:1 water indicator, 4:?:1 magma indicator: - [COLOR:3:1:6:0:0:6:0:0:6:0:0] - [COLOR:3:2:6:7:0:0:0:1:6:7:0] + Color 1:?:1 water indicator, 4:?:1 magma indicator: + [COLOR:3:1:7:0:0:MAT:7:0:0] + [COLOR:3:2:6:0:0:0:0:1:6:0:0] [COLOR:3:3:1:7:1:0:0:0:4:7:1] [BUILD_ITEM:1:BARREL:NONE:INORGANIC:NONE][EMPTY][CAN_USE_ARTIFACT] [BUILD_ITEM:1:PIPE_SECTION:NONE:INORGANIC:NONE][CAN_USE_ARTIFACT] - [BUILD_ITEM:1:WEAPON:WEAPON_MACE:INORGANIC:NONE][CAN_USE_ARTIFACT] + [BUILD_ITEM:1:TRAPCOMP:ITEM_TRAPCOMP_STEAM_PISTON:INORGANIC:NONE][CAN_USE_ARTIFACT] [BUILD_ITEM:1:CHAIN:NONE:INORGANIC:NONE][CAN_USE_ARTIFACT] [BUILD_ITEM:1:TRAPPARTS:NONE:NONE:NONE][CAN_USE_ARTIFACT] - [BUILD_ITEM:2:BLOCKS:NONE:NONE:NONE][BUILDMAT][FIRE_BUILD_SAFE] + [BUILD_ITEM:1:BLOCKS:NONE:NONE:NONE][BUILDMAT][FIRE_BUILD_SAFE] [BUILDING_WORKSHOP:MAGMA_STEAM_ENGINE] [NAME:Magma Steam Engine] @@ -59,30 +60,33 @@ building_zsteam_engine [TILE:0:1:240:' ':254] [TILE:0:2:' ':' ':128] [TILE:0:3:246:' ':' '] - [COLOR:0:1:MAT:0:0:0:7:0:0] + [COLOR:0:1:6:0:0:0:0:0:7:0:0] [COLOR:0:2:0:0:0:0:0:0:7:0:0] - [COLOR:0:3:6:0:0:0:0:0:0:0:0] + [COLOR:0:3:MAT:0:0:0:0:0:0] [TILE:1:1:246:128:' '] [TILE:1:2:' ':' ':254] - [TILE:1:3:254:240:240] - [COLOR:1:1:6:0:0:7:0:0:0:0:0] + [TILE:1:3:254:'/':240] + [COLOR:1:1:MAT:7:0:0:0:0:0] [COLOR:1:2:0:0:0:0:0:0:7:0:0] - [COLOR:1:3:7:0:0:MAT:MAT] + [COLOR:1:3:7:0:0:6:0:0:6:0:0] [TILE:2:1:21:' ':128] [TILE:2:2:128:' ':246] [TILE:2:3:177:19:177] [COLOR:2:1:6:0:0:0:0:0:7:0:0] - [COLOR:2:2:7:0:0:0:0:0:6:0:0] - [COLOR:2:3:7:0:0:MAT:7:0:0] + [COLOR:2:2:7:0:0:0:0:0:MAT] + [COLOR:2:3:7:0:0:6:0:0:7:0:0] + Tile 15 marks places where machines can connect. + Tile 19 marks the hearth (color changed to reflect power). [TILE:3:1:15:246:15] [TILE:3:2:'\':19:'/'] [TILE:3:3:7:' ':7] - [COLOR:3:1:6:0:0:6:0:0:6:0:0] - [COLOR:3:2:6:7:0:0:0:1:6:7:0] + Color 1:?:1 water indicator, 4:?:1 magma indicator: + [COLOR:3:1:7:0:0:MAT:7:0:0] + [COLOR:3:2:6:0:0:0:0:1:6:0:0] [COLOR:3:3:1:7:1:0:0:0:4:7:1] [BUILD_ITEM:1:BARREL:NONE:INORGANIC:NONE][EMPTY][CAN_USE_ARTIFACT] [BUILD_ITEM:1:PIPE_SECTION:NONE:INORGANIC:NONE][CAN_USE_ARTIFACT] - [BUILD_ITEM:1:WEAPON:WEAPON_MACE:INORGANIC:NONE][CAN_USE_ARTIFACT] + [BUILD_ITEM:1:TRAPCOMP:ITEM_TRAPCOMP_STEAM_PISTON:INORGANIC:NONE][CAN_USE_ARTIFACT] [BUILD_ITEM:1:CHAIN:NONE:INORGANIC:NONE][CAN_USE_ARTIFACT] [BUILD_ITEM:1:TRAPPARTS:NONE:NONE:NONE][CAN_USE_ARTIFACT] - [BUILD_ITEM:2:BLOCKS:NONE:NONE:NONE][BUILDMAT][MAGMA_BUILD_SAFE] + [BUILD_ITEM:1:BLOCKS:NONE:NONE:NONE][BUILDMAT][MAGMA_BUILD_SAFE] diff --git a/plugins/devel/item_trapcomp_steam_engine.txt b/plugins/devel/item_trapcomp_steam_engine.txt new file mode 100644 index 000000000..bae6f5b22 --- /dev/null +++ b/plugins/devel/item_trapcomp_steam_engine.txt @@ -0,0 +1,12 @@ +item_trapcomp_steam_engine + +[OBJECT:ITEM] + +[ITEM_TRAPCOMP:ITEM_TRAPCOMP_STEAM_PISTON] +[NAME:piston:pistons] +[ADJECTIVE:huge] +[SIZE:1600] +[HITS:1] +[MATERIAL_SIZE:6] +[METAL] +[ATTACK:BLUNT:40:200:bash:bashes:NO_SUB:2000] diff --git a/plugins/devel/reaction_zsteam_engine.txt b/plugins/devel/reaction_steam_engine.txt similarity index 57% rename from plugins/devel/reaction_zsteam_engine.txt rename to plugins/devel/reaction_steam_engine.txt index 1018510f4..175ffdd50 100644 --- a/plugins/devel/reaction_zsteam_engine.txt +++ b/plugins/devel/reaction_steam_engine.txt @@ -1,4 +1,4 @@ -reaction_other +reaction_steam_engine [OBJECT:REACTION] @@ -9,5 +9,6 @@ reaction_other [FUEL] [SKILL:SMELT] Dimension is the number of days it can produce 100 power * 100. - [PRODUCT:100:1:LIQUID_MISC:NONE:WATER][PRODUCT_DIMENSION:1500] + I.e. with 2000 it means energy of 1 job = 1 water wheel for 20 days. + [PRODUCT:100:1:LIQUID_MISC:NONE:WATER][PRODUCT_DIMENSION:2000] diff --git a/plugins/devel/steam-engine.cpp b/plugins/devel/steam-engine.cpp index ac317687e..edaba0e82 100644 --- a/plugins/devel/steam-engine.cpp +++ b/plugins/devel/steam-engine.cpp @@ -807,24 +807,26 @@ static bool find_engines() { for (int y = 0; y < ws.def->dim_y; y++) { - if (ws.def->tile[bs][x][y] == 15) + switch (ws.def->tile[bs][x][y]) + { + case 15: ws.gear_tiles.push_back(df::coord2d(x,y)); + break; + case 19: + ws.hearth_tile = df::coord2d(x,y); + break; + } if (ws.def->tile_color[2][bs][x][y]) { switch (ws.def->tile_color[0][bs][x][y]) { - case 0: - ws.hearth_tile = df::coord2d(x,y); - break; case 1: ws.water_tile = df::coord2d(x,y); break; case 4: ws.magma_tile = df::coord2d(x,y); break; - default: - break; } } } From 3b8e3d1459edb9fbf8df4b6055023b0d0905a15f Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sun, 2 Sep 2012 17:18:01 +0400 Subject: [PATCH 11/19] Fix wrong assumptions in lua wrapper for BitArray. --- library/include/BitArray.h | 4 ++-- library/include/DataIdentity.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/library/include/BitArray.h b/library/include/BitArray.h index fd9bd98fc..ff68ea1d1 100644 --- a/library/include/BitArray.h +++ b/library/include/BitArray.h @@ -64,7 +64,7 @@ namespace DFHack if (newsize == size) return; uint8_t* mem = (uint8_t *) realloc(bits, newsize); - if(!mem) + if(!mem && newsize != 0) throw std::bad_alloc(); bits = mem; if (newsize > size) @@ -207,7 +207,7 @@ namespace DFHack else { T* mem = (T*) realloc(m_data, sizeof(T)*new_size); - if(!mem) + if(!mem && new_size != 0) throw std::bad_alloc(); m_data = mem; } diff --git a/library/include/DataIdentity.h b/library/include/DataIdentity.h index 0f5fd9e7c..21dc68d1a 100644 --- a/library/include/DataIdentity.h +++ b/library/include/DataIdentity.h @@ -390,7 +390,7 @@ namespace df } virtual bool resize(void *ptr, int size) { - ((container*)ptr)->resize(size); + ((container*)ptr)->resize(size*8); return true; } From 3b08ee44d1560f7610187fb1d428fefaa37ed9ab Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sun, 2 Sep 2012 17:32:44 +0400 Subject: [PATCH 12/19] Vary the internal power consumption of the engine depending on quality. --- plugins/devel/steam-engine.cpp | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/plugins/devel/steam-engine.cpp b/plugins/devel/steam-engine.cpp index edaba0e82..7a67eadd7 100644 --- a/plugins/devel/steam-engine.cpp +++ b/plugins/devel/steam-engine.cpp @@ -436,6 +436,23 @@ struct workshop_hook : df::building_workshopst { return false; } + float get_component_quality(int use_type) + { + float sum = 0, cnt = 0; + + for (size_t i = 0; i < contained_items.size(); i++) + { + int type = classify_component(contained_items[i]); + if (type != use_type) + continue; + + sum += contained_items[i]->item->getQuality(); + cnt += 1; + } + + return (cnt > 0 ? sum/cnt : 0); + } + int get_steam_use_rate(steam_engine_workshop *engine, int dimension, int power_level) { // total ticks to wear off completely @@ -452,8 +469,10 @@ struct workshop_hook : df::building_workshopst { else power_rate = 0.0f; } - // apply rate; 10% steam is wasted anyway - ticks *= (0.1f + 0.9f*power_rate)*power_level; + // waste rate: 1-10% depending on piston assembly quality + float waste = 0.1f - 0.015f * get_component_quality(1); + // apply rate and waste factor + ticks *= (waste + 0.9f*power_rate)*power_level; // end result return std::max(1, int(ticks)); } @@ -529,7 +548,7 @@ struct workshop_hook : df::building_workshopst { if (auto engine = get_steam_engine()) { info->produced = std::min(engine->max_power, get_steam_amount())*100; - info->consumed = 10; + info->consumed = 10 - int(get_component_quality(0)); return; } From 2249cb14fab6fd778ef8e6502cbd749d2f4bfc14 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sun, 2 Sep 2012 18:57:10 +0400 Subject: [PATCH 13/19] Require level 3 both for water & magma, and indicate level 5 by brightness. --- plugins/devel/steam-engine.cpp | 39 ++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/plugins/devel/steam-engine.cpp b/plugins/devel/steam-engine.cpp index 7a67eadd7..ef708d11f 100644 --- a/plugins/devel/steam-engine.cpp +++ b/plugins/devel/steam-engine.cpp @@ -234,7 +234,7 @@ struct workshop_hook : df::building_workshopst { // Find liquids to consume below the engine. - bool find_liquids(df::coord *pwater, df::coord *pmagma, bool is_magma, bool any_level) + bool find_liquids(df::coord *pwater, df::coord *pmagma, bool is_magma, int min_level) { if (!is_magma) pmagma = NULL; @@ -252,23 +252,18 @@ struct workshop_hook : df::building_workshopst { continue; auto pldes = Maps::getTileDesignation(x,y,z-1); - if (!pldes || pldes->bits.flow_size == 0) + if (!pldes || pldes->bits.flow_size < min_level) continue; if (pldes->bits.liquid_type == tile_liquid::Magma) { - if (!pmagma || (!any_level && pldes->bits.flow_size < 4)) - continue; - - *pmagma = df::coord(x,y,z-1); + if (pmagma) + *pmagma = df::coord(x,y,z-1); if (pwater->isValid()) return true; } else { - if (!any_level && pldes->bits.flow_size < 3) - continue; - *pwater = df::coord(x,y,z-1); if (!pmagma || pmagma->isValid()) return true; @@ -286,7 +281,7 @@ struct workshop_hook : df::building_workshopst { // Consume liquid inputs df::coord water, magma; - if (!find_liquids(&water, &magma, engine->is_magma, true)) + if (!find_liquids(&water, &magma, engine->is_magma, 1)) { // Destroy the item with enormous wear amount. liquid->addWear(WEAR_TICKS*5, true, false); @@ -626,7 +621,7 @@ struct workshop_hook : df::building_workshopst { if (auto engine = get_steam_engine()) { df::coord water, magma; - return !find_liquids(&water, &magma, engine->is_magma, false); + return !find_liquids(&water, &magma, engine->is_magma, 3); } return INTERPOSE_NEXT(isUnpowered)(); @@ -681,12 +676,24 @@ struct workshop_hook : df::building_workshopst { if (engine->water_tile.isValid() || engine->magma_tile.isValid()) { df::coord water, magma; - find_liquids(&water, &magma, engine->is_magma, false); + find_liquids(&water, &magma, engine->is_magma, 3); + df::coord dwater, dmagma; + find_liquids(&dwater, &dmagma, engine->is_magma, 5); - if (engine->water_tile.isValid() && !water.isValid()) - db->fore[engine->water_tile.x][engine->water_tile.y] = 0; - if (engine->magma_tile.isValid() && engine->is_magma && !magma.isValid()) - db->fore[engine->magma_tile.x][engine->magma_tile.y] = 0; + if (engine->water_tile.isValid()) + { + if (!water.isValid()) + db->fore[engine->water_tile.x][engine->water_tile.y] = 0; + else if (!dwater.isValid()) + db->bright[engine->water_tile.x][engine->water_tile.y] = 0; + } + if (engine->magma_tile.isValid() && engine->is_magma) + { + if (!magma.isValid()) + db->fore[engine->magma_tile.x][engine->magma_tile.y] = 0; + else if (!dmagma.isValid()) + db->bright[engine->magma_tile.x][engine->magma_tile.y] = 0; + } } } } From 1618ccf5bb4044cdde97d45ef9e6173150cdddbc Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Mon, 3 Sep 2012 10:28:17 +0400 Subject: [PATCH 14/19] Fix steam-engine build on msvc, and add a script for listing mem ranges. --- plugins/devel/steam-engine.cpp | 4 ++-- scripts/devel/lsmem.lua | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 scripts/devel/lsmem.lua diff --git a/plugins/devel/steam-engine.cpp b/plugins/devel/steam-engine.cpp index ef708d11f..5c344f788 100644 --- a/plugins/devel/steam-engine.cpp +++ b/plugins/devel/steam-engine.cpp @@ -105,7 +105,7 @@ void decrement_flow(df::coord pos, int amount) auto pldes = Maps::getTileDesignation(pos); if (!pldes) return; - int nsize = std::max(0, pldes->bits.flow_size - amount); + int nsize = std::max(0, int(pldes->bits.flow_size - amount)); pldes->bits.flow_size = nsize; pldes->bits.flow_forbid = (nsize > 3 || pldes->bits.liquid_type == tile_liquid::Magma); @@ -382,7 +382,7 @@ struct workshop_hook : df::building_workshopst { if (!liquid) continue; - if (cnt == 0 || random() < RAND_MAX/2) + if (cnt == 0 || rand() < RAND_MAX/2) { cnt++; boil_unit(liquid); diff --git a/scripts/devel/lsmem.lua b/scripts/devel/lsmem.lua new file mode 100644 index 000000000..75586324d --- /dev/null +++ b/scripts/devel/lsmem.lua @@ -0,0 +1,14 @@ +-- Prints memory ranges of the process. + +for _,v in ipairs(dfhack.internal.getMemRanges()) do + local access = { '-', '-', '-', 'p' } + if v.read then access[1] = 'r' end + if v.write then access[2] = 'w' end + if v.execute then access[3] = 'x' end + if not v.valid then + access[4] = '?' + elseif v.shared then + access[4] = 's' + end + print(string.format('%08x-%08x %s %s', v.start_addr, v.end_addr, table.concat(access), v.name)) +end From aa449a2180144961308e1e357c013724921175b3 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Mon, 3 Sep 2012 21:11:35 +0400 Subject: [PATCH 15/19] Add a tweak to work around the endless temperature update bug. This obsoletes fix/stable-temp.lua, which only fixes items once. --- dfhack.init-example | 3 +++ plugins/tweak.cpp | 55 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/dfhack.init-example b/dfhack.init-example index 9ee5ecc46..39c0e61df 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -64,3 +64,6 @@ tweak patrol-duty # display creature weight in build plate menu as ??K, instead of (???df: Max tweak readable-build-plate + +# improve FPS by squashing endless item temperature update loops +tweak stable-temp diff --git a/plugins/tweak.cpp b/plugins/tweak.cpp index 9853f7f95..fbea30231 100644 --- a/plugins/tweak.cpp +++ b/plugins/tweak.cpp @@ -32,6 +32,8 @@ #include "df/squad_order_trainst.h" #include "df/ui_build_selector.h" #include "df/building_trapst.h" +#include "df/item_actual.h" +#include "df/contaminant.h" #include @@ -85,6 +87,8 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector size(); i++) + { + auto obj = (*contaminants)[i]; + + if (abs(obj->temperature - temperature) == 1) + { + obj->temperature = temperature; + obj->temperature_fraction = temperature_fraction; + } + } + } + + return INTERPOSE_NEXT(updateContaminants)(); + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE(stable_temp_hook, adjustTemperature); +IMPLEMENT_VMETHOD_INTERPOSE(stable_temp_hook, updateContaminants); + static void enable_hook(color_ostream &out, VMethodInterposeLinkBase &hook, vector ¶meters) { if (vector_get(parameters, 1) == "disable") @@ -375,6 +425,11 @@ static command_result tweak(color_ostream &out, vector ¶meters) enable_hook(out, INTERPOSE_HOOK(readable_build_plate_hook, render), parameters); } + else if (cmd == "stable-temp") + { + enable_hook(out, INTERPOSE_HOOK(stable_temp_hook, adjustTemperature), parameters); + enable_hook(out, INTERPOSE_HOOK(stable_temp_hook, updateContaminants), parameters); + } else return CR_WRONG_USAGE; From 9c6fcee9a9b03b42648152a549fc019f05fff468 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 5 Sep 2012 11:23:00 +0400 Subject: [PATCH 16/19] Add steam engine documentation, and use barrel quality in efficiency calc. --- plugins/devel/item_trapcomp_steam_engine.txt | 4 +- plugins/devel/steam-engine.cpp | 82 +++++++++++++++++++- 2 files changed, 82 insertions(+), 4 deletions(-) diff --git a/plugins/devel/item_trapcomp_steam_engine.txt b/plugins/devel/item_trapcomp_steam_engine.txt index bae6f5b22..c35f6ef45 100644 --- a/plugins/devel/item_trapcomp_steam_engine.txt +++ b/plugins/devel/item_trapcomp_steam_engine.txt @@ -4,8 +4,8 @@ item_trapcomp_steam_engine [ITEM_TRAPCOMP:ITEM_TRAPCOMP_STEAM_PISTON] [NAME:piston:pistons] -[ADJECTIVE:huge] -[SIZE:1600] +[ADJECTIVE:heavy] +[SIZE:1800] [HITS:1] [MATERIAL_SIZE:6] [METAL] diff --git a/plugins/devel/steam-engine.cpp b/plugins/devel/steam-engine.cpp index 5c344f788..cacfc6e16 100644 --- a/plugins/devel/steam-engine.cpp +++ b/plugins/devel/steam-engine.cpp @@ -34,6 +34,82 @@ #include "MiscUtils.h" +/* + * This plugin implements a steam engine workshop. It activates + * if there are any workshops in the raws with STEAM_ENGINE in + * their token, and provides the necessary behavior. + * + * Construction: + * + * The workshop needs water as its input, which it takes via a + * passable floor tile below it, like usual magma workshops do. + * The magma version also needs magma. + * + * ISSUE: Since this building is a machine, and machine collapse + * code cannot be modified, it would collapse over true open space. + * As a loophole, down stair provides support to machines, while + * being passable, so use them. + * + * After constructing the building itself, machines can be connected + * to the edge tiles that look like gear boxes. Their exact position + * is extracted from the workshop raws. + * + * ISSUE: Like with collapse above, part of the code involved in + * machine connection cannot be modified. As a result, the workshop + * can only immediately connect to machine components built AFTER it. + * This also means that engines cannot be chained without intermediate + * short axles that can be built later. + * + * Operation: + * + * In order to operate the engine, queue the Stoke Boiler job. + * A furnace operator will come, possibly bringing a bar of fuel, + * and perform it. As a result, a "boiling water" item will appear + * in the 't' view of the workshop. + * + * Note: The completion of the job will actually consume one unit + * of appropriate liquids from below the workshop. + * + * Every such item gives 100 power, up to a limit of 300 for coal, + * and 500 for a magma engine. The building can host twice that + * amount of items to provide longer autonomous running. When the + * boiler gets filled to capacity, all queued jobs are suspended; + * once it drops back to 3+1 or 5+1 items, they are re-enabled. + * + * While the engine is providing power, steam is being consumed. + * The consumption speed includes a fixed 10% waste rate, and + * the remaining 90% are applied proportionally to the actual + * load in the machine. With the engine at nominal 300 power with + * 150 load in the system, it will consume steam for actual + * 300*(10% + 90%*150/300) = 165 power. + * + * Masterpiece mechanism and chain will decrease the mechanical + * power drawn by the engine itself from 10 to 5. Masterpiece + * barrel decreases waste rate by 4%. Masterpiece piston and pipe + * decrease it by further 4%, and also decrease the whole steam + * use rate by 10%. + * + * Explosions: + * + * The engine must be constructed using barrel, pipe and piston + * from fire-safe, or in the magma version magma-safe metals. + * + * During operation weak parts get gradually worn out, and + * eventually the engine explodes. It should also explode if + * toppled during operation by a building destroyer, or a + * tantruming dwarf. + * + * Save files: + * + * It should be safe to load and view fortresses using engines + * from a DF version without DFHack installed, except that in such + * case the engines won't work. However actually making modifications + * to them, or machines they connect to (including by pulling levers), + * can easily result in inconsistent state once this plugin is + * available again. The effects may be as weird as negative power + * being generated. + */ + using std::vector; using std::string; using std::stack; @@ -465,9 +541,11 @@ struct workshop_hook : df::building_workshopst { power_rate = 0.0f; } // waste rate: 1-10% depending on piston assembly quality - float waste = 0.1f - 0.015f * get_component_quality(1); + float piston_qual = get_component_quality(1); + float waste = 0.1f - 0.016f * 0.5f * (piston_qual + get_component_quality(2)); + float efficiency_coeff = 1.0f - 0.02f * piston_qual; // apply rate and waste factor - ticks *= (waste + 0.9f*power_rate)*power_level; + ticks *= (waste + 0.9f*power_rate)*power_level*efficiency_coeff; // end result return std::max(1, int(ticks)); } From 27f169e298e658f3957aa2db1f76fe8aa20caef7 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 5 Sep 2012 17:37:36 +0400 Subject: [PATCH 17/19] Provide a partial application utility function to lua. Implemented in C++ for efficiency. --- LUA_API.rst | 10 ++++ Lua API.html | 107 +++++++++++++++++++++++------------------ library/LuaTools.cpp | 34 +++++++++++++ library/lua/dfhack.lua | 8 ++- 4 files changed, 110 insertions(+), 49 deletions(-) diff --git a/LUA_API.rst b/LUA_API.rst index 542034f40..22130efd6 100644 --- a/LUA_API.rst +++ b/LUA_API.rst @@ -550,6 +550,16 @@ Exception handling The default value of the ``verbose`` argument of ``err:tostring()``. +Miscellaneous +------------- + +* ``dfhack.curry(func,args...)``, or ``curry(func,args...)`` + + Returns a closure that invokes the function with args combined + both from the curry call and the closure call itself. I.e. + ``curry(func,a,b)(c,d)`` equals ``func(a,b,c,d)``. + + Locking and finalization ------------------------ diff --git a/Lua API.html b/Lua API.html index 63a4c8547..f6f2d42b3 100644 --- a/Lua API.html +++ b/Lua API.html @@ -337,42 +337,43 @@ ul.auto-toc {
  • Native utilities
  • -
  • C++ function wrappers
  • The current version of DFHack has extensive support for @@ -842,8 +843,18 @@ following properties:

    +
    +

    Miscellaneous

    +
      +
    • dfhack.curry(func,args...), or curry(func,args...)

      +

      Returns a closure that invokes the function with args combined +both from the curry call and the closure call itself. I.e. +curry(func,a,b)(c,d) equals func(a,b,c,d).

      +
    • +
    +
    -

    Locking and finalization

    +

    Locking and finalization

    • dfhack.with_suspend(f[,args...])

      Calls f with arguments after grabbing the DF core suspend lock. @@ -876,7 +887,7 @@ Implemented using call_with_final

    -

    Persistent configuration storage

    +

    Persistent configuration storage

    This api is intended for storing configuration options in the world itself. It probably should be restricted to data that is world-dependent.

    Entries are identified by a string key, but it is also possible to manage @@ -911,7 +922,7 @@ functions can just copy values in memory without doing any actual I/O. However, currently every entry has a 180+-byte dead-weight overhead.

    -

    Material info lookup

    +

    Material info lookup

    A material info record has fields:

    • type, index, material

      @@ -956,7 +967,7 @@ Accept dfhack_material_category auto-assign table.

    -

    C++ function wrappers

    +

    C++ function wrappers

    Thin wrappers around C++ functions, similar to the ones for virtual methods. One notable difference is that these explicit wrappers allow argument count adjustment according to the usual lua rules, so trailing false/nil arguments @@ -985,7 +996,7 @@ can be omitted.

    -

    Gui module

    +

    Gui module

    • dfhack.gui.getCurViewscreen([skip_dismissed])

      Returns the topmost viewscreen. If skip_dismissed is true, @@ -1032,7 +1043,7 @@ above operations accordingly. If enabled, pauses and zooms to position.

    -

    Job module

    +

    Job module

    • dfhack.job.cloneJobStruct(job)

      Creates a deep copy of the given job.

      @@ -1069,7 +1080,7 @@ a lua list containing them.

    -

    Units module

    +

    Units module

    • dfhack.units.getPosition(unit)

      Returns true x,y,z of the unit, or nil if invalid; may be not equal to unit.pos if caged.

      @@ -1130,7 +1141,7 @@ or raws. The ignore_noble boolean disables the
    -

    Items module

    +

    Items module

    • dfhack.items.getPosition(item)

      Returns true x,y,z of the item, or nil if invalid; may be not equal to item.pos if in inventory.

      @@ -1173,7 +1184,7 @@ Returns false in case of error.

    -

    Maps module

    +

    Maps module

    • dfhack.maps.getSize()

      Returns map size in blocks: x, y, z

      @@ -1221,7 +1232,7 @@ burrows, or the presence of invaders.

    -

    Burrows module

    +

    Burrows module

    • dfhack.burrows.findByName(name)

      Returns the burrow pointer or nil.

      @@ -1256,7 +1267,7 @@ burrows, or the presence of invaders.

    -

    Buildings module

    +

    Buildings module

    • dfhack.buildings.setOwner(item,unit)

      Replaces the owner of the building. If unit is nil, removes ownership. @@ -1400,7 +1411,7 @@ can be determined this way, constructBuilding

    -

    Constructions module

    +

    Constructions module

    • dfhack.constructions.designateNew(pos,type,item_type,mat_index)

      Designates a new construction at given position. If there already is @@ -1416,7 +1427,7 @@ Returns true, was_only_planned if removed; or false if none fo

    -

    Screen API

    +

    Screen API

    The screen module implements support for drawing to the tiled screen of the game. Note that drawing only has any effect when done from callbacks, so it can only be feasibly used in the core context.

    @@ -1555,7 +1566,7 @@ options; if multiple interpretations exist, the table will contain multiple keys
    -

    Internal API

    +

    Internal API

    These functions are intended for the use by dfhack developers, and are only documented here for completeness:

      @@ -1603,7 +1614,7 @@ Returns: found_index, or nil if end reached.

    -

    Core interpreter context

    +

    Core interpreter context

    While plugins can create any number of interpreter instances, there is one special context managed by dfhack core. It is the only context that can receive events from DF and plugins.

    @@ -1634,7 +1645,7 @@ Using timeout_active(id,nil) cancels the timer
    -

    Event type

    +

    Event type

    An event is a native object transparently wrapping a lua table, and implementing a __call metamethod. When it is invoked, it loops through the table with next and calls all contained values. @@ -1666,7 +1677,7 @@ order using dfhack.safecall.

    -

    Lua Modules

    +

    Lua Modules

    DFHack sets up the lua interpreter so that the built-in require function can be used to load shared lua code from hack/lua/. The dfhack namespace reference itself may be obtained via @@ -1695,7 +1706,7 @@ in this document.

    -

    Global environment

    +

    Global environment

    A number of variables and functions are provided in the base global environment by the mandatory init file dfhack.lua:

      @@ -1736,7 +1747,7 @@ Returns nil if any of obj or indices is nil, or a numeric inde
    -

    utils

    +

    utils

    • utils.compare(a,b)

      Comparator function; returns -1 if a<b, 1 if a>b, 0 otherwise.

      @@ -1849,7 +1860,7 @@ throws an error.

    -

    dumper

    +

    dumper

    A third-party lua table dumper module from http://lua-users.org/wiki/DataDumper. Defines one function:

    @@ -1863,14 +1874,14 @@ the other arguments see the original documentation link above.

    -

    Plugins

    +

    Plugins

    DFHack plugins may export native functions and events to lua contexts. They are automatically imported by mkmodule('plugins.<name>'); this means that a lua module file is still necessary for require to read.

    The following plugins have lua support.

    -

    burrows

    +

    burrows

    Implements extended burrow manipulations.

    Events:

      @@ -1908,13 +1919,13 @@ set is the same as used by the command line.

      The lua module file also re-exports functions from dfhack.burrows.

    -

    sort

    +

    sort

    Does not export any native functions as of now. Instead, it calls lua code to perform the actual ordering of list items.

    -

    Scripts

    +

    Scripts

    Any files with the .lua extension placed into hack/scripts/* are automatically used by the DFHack core as commands. The matching command name consists of the name of the file sans diff --git a/library/LuaTools.cpp b/library/LuaTools.cpp index 7c2c8f8d6..9f0477538 100644 --- a/library/LuaTools.cpp +++ b/library/LuaTools.cpp @@ -1211,6 +1211,39 @@ static int dfhack_open_plugin(lua_State *L) return 0; } +static int dfhack_curry_wrap(lua_State *L) +{ + int nargs = lua_gettop(L); + int ncurry = lua_tointeger(L, lua_upvalueindex(1)); + int scount = nargs + ncurry; + + luaL_checkstack(L, ncurry, "stack overflow in curry"); + + // Insert values in O(N+M) by first shifting the existing data + lua_settop(L, scount); + for (int i = 0; i < nargs; i++) + lua_copy(L, nargs-i, scount-i); + for (int i = 1; i <= ncurry; i++) + lua_copy(L, lua_upvalueindex(i+1), i); + + lua_callk(L, scount-1, LUA_MULTRET, 0, lua_gettop); + + return lua_gettop(L); +} + +static int dfhack_curry(lua_State *L) +{ + luaL_checkany(L, 1); + if (lua_isnil(L, 1)) + luaL_argerror(L, 1, "nil function in curry"); + if (lua_gettop(L) == 1) + return 1; + lua_pushinteger(L, lua_gettop(L)); + lua_insert(L, 1); + lua_pushcclosure(L, dfhack_curry_wrap, lua_gettop(L)); + return 1; +} + bool Lua::IsCoreContext(lua_State *state) { // This uses a private field of the lua state to @@ -1234,6 +1267,7 @@ static const luaL_Reg dfhack_funcs[] = { { "call_with_finalizer", dfhack_call_with_finalizer }, { "with_suspend", lua_dfhack_with_suspend }, { "open_plugin", dfhack_open_plugin }, + { "curry", dfhack_curry }, { NULL, NULL } }; diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua index 2cbd019a6..a1e899761 100644 --- a/library/lua/dfhack.lua +++ b/library/lua/dfhack.lua @@ -46,6 +46,7 @@ end -- Error handling safecall = dfhack.safecall +curry = dfhack.curry function dfhack.pcall(f, ...) return xpcall(f, dfhack.onerror, ...) @@ -118,7 +119,12 @@ function defclass(class,parent) if parent then setmetatable(class, parent) else - rawset_default(class, { init_fields = rawset_default }) + rawset_default(class, { + init_fields = rawset_default, + callback = function(self, name, ...) + return dfhack.curry(self[name], self, ...) + end + }) end return class end From 57086ac56eb489abd0c7759aed084020edc71148 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 5 Sep 2012 19:45:45 +0400 Subject: [PATCH 18/19] Add stock MessageBox and InputBox dialog screens for lua scripts. --- LUA_API.rst | 8 ++ Lua API.html | 6 ++ library/LuaApi.cpp | 2 + library/LuaTools.cpp | 3 + library/Process-darwin.cpp | 8 ++ library/Process-linux.cpp | 8 ++ library/Process-windows.cpp | 5 ++ library/include/MemAccess.h | 3 + library/lua/gui.lua | 12 ++- library/lua/gui/dialogs.lua | 175 ++++++++++++++++++++++++++++++++++++ library/lua/utils.lua | 13 +++ 11 files changed, 241 insertions(+), 2 deletions(-) create mode 100644 library/lua/gui/dialogs.lua diff --git a/LUA_API.rst b/LUA_API.rst index 22130efd6..799f623eb 100644 --- a/LUA_API.rst +++ b/LUA_API.rst @@ -553,6 +553,10 @@ Exception handling Miscellaneous ------------- +* ``dfhack.VERSION`` + + DFHack version string constant. + * ``dfhack.curry(func,args...)``, or ``curry(func,args...)`` Returns a closure that invokes the function with args combined @@ -719,6 +723,10 @@ can be omitted. Returns the dfhack directory path, i.e. ``".../df/hack/"``. +* ``dfhack.getTickCount()`` + + Returns the tick count in ms, exactly as DF ui uses. + * ``dfhack.isWorldLoaded()`` Checks if the world is loaded. diff --git a/Lua API.html b/Lua API.html index f6f2d42b3..f05ee5511 100644 --- a/Lua API.html +++ b/Lua API.html @@ -846,6 +846,9 @@ following properties:

    Miscellaneous

      +
    • dfhack.VERSION

      +

      DFHack version string constant.

      +
    • dfhack.curry(func,args...), or curry(func,args...)

      Returns a closure that invokes the function with args combined both from the curry call and the closure call itself. I.e. @@ -985,6 +988,9 @@ can be omitted.

    • dfhack.getHackPath()

      Returns the dfhack directory path, i.e. ".../df/hack/".

    • +
    • dfhack.getTickCount()

      +

      Returns the tick count in ms, exactly as DF ui uses.

      +
    • dfhack.isWorldLoaded()

      Checks if the world is loaded.

    • diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 4e57b113f..1dcb001f1 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -728,6 +728,7 @@ static std::string getOSType() } static std::string getDFVersion() { return Core::getInstance().vinfo->getVersion(); } +static uint32_t getTickCount() { return Core::getInstance().p->getTickCount(); } static std::string getDFPath() { return Core::getInstance().p->getPath(); } static std::string getHackPath() { return Core::getInstance().getHackPath(); } @@ -739,6 +740,7 @@ static const LuaWrapper::FunctionReg dfhack_module[] = { WRAP(getOSType), WRAP(getDFVersion), WRAP(getDFPath), + WRAP(getTickCount), WRAP(getHackPath), WRAP(isWorldLoaded), WRAP(isMapLoaded), diff --git a/library/LuaTools.cpp b/library/LuaTools.cpp index 9f0477538..a283d215c 100644 --- a/library/LuaTools.cpp +++ b/library/LuaTools.cpp @@ -1580,6 +1580,9 @@ lua_State *DFHack::Lua::Open(color_ostream &out, lua_State *state) lua_rawsetp(state, LUA_REGISTRYINDEX, &DFHACK_BASE_G_TOKEN); lua_setfield(state, -2, "BASE_G"); + lua_pushstring(state, DFHACK_VERSION); + lua_setfield(state, -2, "VERSION"); + lua_pushboolean(state, IsCoreContext(state)); lua_setfield(state, -2, "is_core_context"); diff --git a/library/Process-darwin.cpp b/library/Process-darwin.cpp index 5a97d9e00..3893cfc5f 100644 --- a/library/Process-darwin.cpp +++ b/library/Process-darwin.cpp @@ -27,6 +27,7 @@ distribution. #include #include #include +#include #include @@ -262,6 +263,13 @@ bool Process::getThreadIDs(vector & threads ) return true; } +uint32_t Process::getTickCount() +{ + struct timeval tp; + gettimeofday(&tp, NULL); + return (tp.tv_sec * 1000) + (tp.tv_usec / 1000); +} + string Process::getPath() { char path[1024]; diff --git a/library/Process-linux.cpp b/library/Process-linux.cpp index fe8647845..4a66470f9 100644 --- a/library/Process-linux.cpp +++ b/library/Process-linux.cpp @@ -27,6 +27,7 @@ distribution. #include #include #include +#include #include #include @@ -192,6 +193,13 @@ bool Process::getThreadIDs(vector & threads ) return true; } +uint32_t Process::getTickCount() +{ + struct timeval tp; + gettimeofday(&tp, NULL); + return (tp.tv_sec * 1000) + (tp.tv_usec / 1000); +} + string Process::getPath() { const char * cwd_name = "/proc/self/cwd"; diff --git a/library/Process-windows.cpp b/library/Process-windows.cpp index 7eb6ff5f7..db58c4d33 100644 --- a/library/Process-windows.cpp +++ b/library/Process-windows.cpp @@ -410,6 +410,11 @@ string Process::doReadClassName (void * vptr) return raw; } +uint32_t Process::getTickCount() +{ + return GetTickCount(); +} + string Process::getPath() { HMODULE hmod; diff --git a/library/include/MemAccess.h b/library/include/MemAccess.h index 0e5f618e2..a226018a6 100644 --- a/library/include/MemAccess.h +++ b/library/include/MemAccess.h @@ -281,6 +281,9 @@ namespace DFHack /// get the DF Process FilePath std::string getPath(); + /// millisecond tick count, exactly as DF uses + uint32_t getTickCount(); + /// modify permisions of memory range bool setPermisions(const t_memrange & range,const t_memrange &trgrange); diff --git a/library/lua/gui.lua b/library/lua/gui.lua index 9e189ea13..23904c14f 100644 --- a/library/lua/gui.lua +++ b/library/lua/gui.lua @@ -94,6 +94,9 @@ function Painter:isValidPos() end function Painter:viewport(x,y,w,h) + if type(x) == 'table' then + x,y,w,h = x.x1, x.y1, x.width, x.height + end local x1,y1 = self.x1+x, self.y1+y local x2,y2 = x1+w-1, y1+h-1 local vp = { @@ -353,11 +356,16 @@ local function hint_coord(gap,hint) end end +function FramedScreen:getWantedFrameSize() + return self.frame_width, self.frame_height +end + 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) + local fw, fh = self:getWantedFrameSize() + local width = math.min(fw or iw, iw) + local height = math.min(fh or ih, ih) local gw, gh = iw-width, ih-height local x1, y1 = hint_coord(gw,self.frame_xhint), hint_coord(gh,self.frame_yhint) self.frame_rect = mkdims_wh(x1+1,y1+1,width,height) diff --git a/library/lua/gui/dialogs.lua b/library/lua/gui/dialogs.lua new file mode 100644 index 000000000..e6d30c970 --- /dev/null +++ b/library/lua/gui/dialogs.lua @@ -0,0 +1,175 @@ +-- Some simple dialog screens + +local _ENV = mkmodule('gui.dialogs') + +local gui = require('gui') +local utils = require('utils') + +local dscreen = dfhack.screen + +MessageBox = defclass(MessageBox, gui.FramedScreen) + +MessageBox.frame_style = gui.GREY_LINE_FRAME + +function MessageBox:init(info) + info = info or {} + self:init_fields{ + text = info.text or {}, + frame_title = info.title, + frame_width = info.frame_width, + on_accept = info.on_accept, + on_cancel = info.on_cancel, + on_close = info.on_close, + text_pen = info.text_pen + } + if type(self.text) == 'string' then + self.text = utils.split_string(self.text, "\n") + end + gui.FramedScreen.init(self, info) + return self +end + +function MessageBox:getWantedFrameSize() + local text = self.text + local w = #(self.frame_title or '') + 2 + w = math.max(w, 20) + w = math.max(self.frame_width or w, w) + for _, l in ipairs(text) do + w = math.max(w, #l) + end + local h = #text+1 + if h > 1 then + h = h+1 + end + return w, #text+2 +end + +function MessageBox:onRenderBody(dc) + if #self.text > 0 then + dc:newline(1):pen(self.text_pen or COLOR_GREY) + for _, l in ipairs(self.text or {}) do + dc:string(l):newline(1) + end + end + + if self.on_accept then + local x,y = self.frame_rect.x1+1, self.frame_rect.y2+1 + dscreen.paintString({fg=COLOR_LIGHTGREEN},x,y,'ESC') + dscreen.paintString({fg=COLOR_GREY},x+3,y,'/') + dscreen.paintString({fg=COLOR_LIGHTGREEN},x+4,y,'y') + end +end + +function MessageBox:onDestroy() + if self.on_close then + self.on_close() + end +end + +function MessageBox:onInput(keys) + if keys.MENU_CONFIRM then + self:dismiss() + if self.on_accept then + self.on_accept() + end + elseif keys.LEAVESCREEN or (keys.SELECT and not self.on_accept) then + self:dismiss() + if self.on_cancel then + self.on_cancel() + end + end +end + +function showMessage(title, text, tcolor, on_close) + mkinstance(MessageBox):init{ + text = text, + title = title, + text = text, + text_pen = tcolor, + on_close = on_close + }:show() +end + +function showYesNoPrompt(title, text, tcolor, on_accept, on_cancel) + mkinstance(MessageBox):init{ + title = title, + text = text, + text_pen = tcolor, + on_accept = on_accept, + on_cancel = on_cancel, + }:show() +end + +InputBox = defclass(InputBox, MessageBox) + +function InputBox:init(info) + info = info or {} + self:init_fields{ + input = info.input or '', + input_pen = info.input_pen, + on_input = info.on_input, + } + MessageBox.init(self, info) + self.on_accept = nil + return self +end + +function InputBox:getWantedFrameSize() + local mw, mh = MessageBox.getWantedFrameSize(self) + return mw, mh+2 +end + +function InputBox:onRenderBody(dc) + MessageBox.onRenderBody(self, dc) + + dc:newline(1) + dc:pen(self.input_pen or COLOR_LIGHTCYAN) + dc:fill(dc.x1+1,dc.y,dc.x2-1,dc.y) + + local cursor = '_' + if math.floor(dfhack.getTickCount()/500) % 2 == 0 then + cursor = ' ' + end + local txt = self.input .. cursor + if #txt > dc.width-2 then + txt = string.sub(txt, #txt-dc.width+3) + -- Add prefix arrow + dc:advance(-1):char(27) + end + dc:string(txt) +end + +function InputBox:onInput(keys) + if keys.SELECT then + self:dismiss() + if self.on_input then + self.on_input(self.input) + end + elseif keys.LEAVESCREEN then + self:dismiss() + if self.on_cancel then + self.on_cancel() + end + elseif keys._STRING then + if keys._STRING == 0 then + self.input = string.sub(self.input, 1, #self.input-1) + else + self.input = self.input .. string.char(keys._STRING) + end + end +end + +function showInputPrompt(title, text, tcolor, input, on_input, on_cancel, min_width) + mkinstance(InputBox):init{ + title = title, + text = text, + text_pen = tcolor, + input = input, + on_input = on_input, + on_cancel = on_cancel, + frame_width = min_width, + }:show() +end + + +return _ENV diff --git a/library/lua/utils.lua b/library/lua/utils.lua index 19a4e6f6a..9fa473ed8 100644 --- a/library/lua/utils.lua +++ b/library/lua/utils.lua @@ -381,6 +381,19 @@ function getBuildingCenter(building) return xyz2pos(building.centerx, building.centery, building.z) end +function split_string(self, delimiter) + local result = { } + local from = 1 + local delim_from, delim_to = string.find( self, delimiter, from ) + while delim_from do + table.insert( result, string.sub( self, from , delim_from-1 ) ) + from = delim_to + 1 + delim_from, delim_to = string.find( self, delimiter, from ) + end + table.insert( result, string.sub( self, from ) ) + return result +end + -- Ask a yes-no question function prompt_yes_no(msg,default) local prompt = msg From 8d876cc7d92faf1616d914e03c890772256ebb83 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 5 Sep 2012 21:27:42 +0400 Subject: [PATCH 19/19] Support renaming some buildings, and arbitrary units, via gui script. --- dfhack.init-example | 4 + library/lua/gui/dialogs.lua | 9 +- library/modules/World.cpp | 4 +- plugins/CMakeLists.txt | 2 +- plugins/lua/rename.lua | 13 +++ plugins/proto/rename.proto | 7 ++ plugins/rename.cpp | 201 +++++++++++++++++++++++++++++++++++- scripts/gui/rename.lua | 63 +++++++++++ 8 files changed, 296 insertions(+), 7 deletions(-) create mode 100644 plugins/lua/rename.lua create mode 100644 scripts/gui/rename.lua diff --git a/dfhack.init-example b/dfhack.init-example index 39c0e61df..af8b17f0f 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -16,6 +16,10 @@ keybinding add Ctrl-K autodump-destroy-item # quicksave, only in main dwarfmode screen and menu page keybinding add Ctrl-Alt-S@dwarfmode/Default quicksave +# gui/rename script +keybinding add Ctrl-Shift-N gui/rename +keybinding add Ctrl-Shift-P "gui/rename unit-profession" + ############################## # Generic adv mode bindings # ############################## diff --git a/library/lua/gui/dialogs.lua b/library/lua/gui/dialogs.lua index e6d30c970..35720f871 100644 --- a/library/lua/gui/dialogs.lua +++ b/library/lua/gui/dialogs.lua @@ -9,6 +9,7 @@ local dscreen = dfhack.screen MessageBox = defclass(MessageBox, gui.FramedScreen) +MessageBox.focus_path = 'MessageBox' MessageBox.frame_style = gui.GREY_LINE_FRAME function MessageBox:init(info) @@ -31,7 +32,7 @@ end function MessageBox:getWantedFrameSize() local text = self.text - local w = #(self.frame_title or '') + 2 + local w = #(self.frame_title or '') + 4 w = math.max(w, 20) w = math.max(self.frame_width or w, w) for _, l in ipairs(text) do @@ -41,7 +42,7 @@ function MessageBox:getWantedFrameSize() if h > 1 then h = h+1 end - return w, #text+2 + return w+2, #text+2 end function MessageBox:onRenderBody(dc) @@ -102,6 +103,8 @@ end InputBox = defclass(InputBox, MessageBox) +InputBox.focus_path = 'InputBox' + function InputBox:init(info) info = info or {} self:init_fields{ @@ -127,7 +130,7 @@ function InputBox:onRenderBody(dc) dc:fill(dc.x1+1,dc.y,dc.x2-1,dc.y) local cursor = '_' - if math.floor(dfhack.getTickCount()/500) % 2 == 0 then + if math.floor(dfhack.getTickCount()/300) % 2 == 0 then cursor = ' ' end local txt = self.input .. cursor diff --git a/library/modules/World.cpp b/library/modules/World.cpp index 393e7cbfe..e14aa02a0 100644 --- a/library/modules/World.cpp +++ b/library/modules/World.cpp @@ -285,13 +285,13 @@ PersistentDataItem World::GetPersistentData(int entry_id) PersistentDataItem World::GetPersistentData(const std::string &key, bool *added) { - *added = false; + if (added) *added = false; PersistentDataItem rv = GetPersistentData(key); if (!rv.isValid()) { - *added = true; + if (added) *added = true; rv = AddPersistentData(key); } diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index a2e520178..04da3e6c8 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -92,7 +92,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(seedwatch seedwatch.cpp) DFHACK_PLUGIN(initflags initflags.cpp) DFHACK_PLUGIN(stockpiles stockpiles.cpp) - DFHACK_PLUGIN(rename rename.cpp PROTOBUFS rename) + DFHACK_PLUGIN(rename rename.cpp LINK_LIBRARIES lua PROTOBUFS rename) DFHACK_PLUGIN(jobutils jobutils.cpp) DFHACK_PLUGIN(workflow workflow.cpp) DFHACK_PLUGIN(showmood showmood.cpp) diff --git a/plugins/lua/rename.lua b/plugins/lua/rename.lua new file mode 100644 index 000000000..0e7128f57 --- /dev/null +++ b/plugins/lua/rename.lua @@ -0,0 +1,13 @@ +local _ENV = mkmodule('plugins.rename') + +--[[ + + Native functions: + + * canRenameBuilding(building) + * isRenamingBuilding(building) + * renameBuilding(building, name) + +--]] + +return _ENV \ No newline at end of file diff --git a/plugins/proto/rename.proto b/plugins/proto/rename.proto index aa1e95f48..810091fc7 100644 --- a/plugins/proto/rename.proto +++ b/plugins/proto/rename.proto @@ -17,3 +17,10 @@ message RenameUnitIn { optional string nickname = 2; optional string profession = 3; } + +// RPC RenameBuilding : RenameBuildingIn -> EmptyMessage +message RenameBuildingIn { + required int32 building_id = 1; + + optional string name = 2; +} diff --git a/plugins/rename.cpp b/plugins/rename.cpp index 1871d0f73..99dc6848a 100644 --- a/plugins/rename.cpp +++ b/plugins/rename.cpp @@ -3,11 +3,15 @@ #include "Export.h" #include "PluginManager.h" +#include +#include + #include "modules/Gui.h" #include "modules/Translation.h" #include "modules/Units.h" +#include "modules/World.h" -#include "DataDefs.h" +#include #include "df/ui.h" #include "df/world.h" #include "df/squad.h" @@ -18,6 +22,11 @@ #include "df/historical_figure_info.h" #include "df/assumed_identity.h" #include "df/language_name.h" +#include "df/building_stockpilest.h" +#include "df/building_workshopst.h" +#include "df/building_furnacest.h" +#include "df/building_trapst.h" +#include "df/building_siegeenginest.h" #include "RemoteServer.h" #include "rename.pb.h" @@ -36,6 +45,8 @@ using namespace dfproto; using df::global::ui; using df::global::world; +DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event); + static command_result rename(color_ostream &out, vector & parameters); DFHACK_PLUGIN("rename"); @@ -51,8 +62,32 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector clear(); \ + *buf += name; \ + *buf += " ("; \ + if (tag) *buf += (const char*)tag; \ + else { std::string tmp; INTERPOSE_NEXT(getName)(&tmp); *buf += tmp; } \ + *buf += ")"; \ + return; \ + } \ + else \ + INTERPOSE_NEXT(getName)(buf); \ + } \ + }; \ + IMPLEMENT_VMETHOD_INTERPOSE(cname##_hook, getName); +KNOWN_BUILDINGS +#undef BUILDING + +static char getBuildingCode(df::building *bld) +{ + CHECK_NULL_POINTER(bld); + +#define BUILDING(code, cname, tag) \ + if (strict_virtual_cast(bld)) return code; +KNOWN_BUILDINGS +#undef BUILDING + + return 0; +} + +static bool enable_building_rename(char code, bool enable) +{ + switch (code) { +#define BUILDING(code, cname, tag) \ + case code: return INTERPOSE_HOOK(cname##_hook, getName).apply(enable); +KNOWN_BUILDINGS +#undef BUILDING + default: + return false; + } +} + +static void disable_building_rename() +{ +#define BUILDING(code, cname, tag) \ + INTERPOSE_HOOK(cname##_hook, getName).remove(); +KNOWN_BUILDINGS +#undef BUILDING +} + +static bool is_enabled_building(char code) +{ + switch (code) { +#define BUILDING(code, cname, tag) \ + case code: return INTERPOSE_HOOK(cname##_hook, getName).is_applied(); +KNOWN_BUILDINGS +#undef BUILDING + default: + return false; + } +} + +static void init_buildings(bool enable) +{ + disable_building_rename(); + + if (enable) + { + auto pworld = Core::getInstance().getWorld(); + auto entry = pworld->GetPersistentData("rename/building_types"); + + if (entry.isValid()) + { + std::string val = entry.val(); + for (size_t i = 0; i < val.size(); i++) + enable_building_rename(val[i], true); + } + } +} + +static bool canRenameBuilding(df::building *bld) +{ + return getBuildingCode(bld) != 0; +} + +static bool isRenamingBuilding(df::building *bld) +{ + return is_enabled_building(getBuildingCode(bld)); +} + +static bool renameBuilding(df::building *bld, std::string name) +{ + char code = getBuildingCode(bld); + if (code == 0 && !name.empty()) + return false; + + if (!name.empty() && !is_enabled_building(code)) + { + auto pworld = Core::getInstance().getWorld(); + auto entry = pworld->GetPersistentData("rename/building_types", NULL); + if (!entry.isValid()) + return false; + + if (!enable_building_rename(code, true)) + return false; + + entry.val().push_back(code); + } + + bld->name = name; + return true; +} + static df::squad *getSquadByIndex(unsigned idx) { auto entity = df::historical_entity::find(ui->group_id); @@ -101,14 +263,37 @@ static command_result RenameUnit(color_ostream &stream, const RenameUnitIn *in) return CR_OK; } +static command_result RenameBuilding(color_ostream &stream, const RenameBuildingIn *in) +{ + auto building = df::building::find(in->building_id()); + if (!building) + return CR_NOT_FOUND; + + if (in->has_name()) + { + if (!renameBuilding(building, in->name())) + return CR_FAILURE; + } + + return CR_OK; +} + DFhackCExport RPCService *plugin_rpcconnect(color_ostream &) { RPCService *svc = new RPCService(); svc->addFunction("RenameSquad", RenameSquad); svc->addFunction("RenameUnit", RenameUnit); + svc->addFunction("RenameBuilding", RenameBuilding); return svc; } +DFHACK_PLUGIN_LUA_FUNCTIONS { + DFHACK_LUA_FUNCTION(canRenameBuilding), + DFHACK_LUA_FUNCTION(isRenamingBuilding), + DFHACK_LUA_FUNCTION(renameBuilding), + DFHACK_LUA_END +}; + static command_result rename(color_ostream &out, vector ¶meters) { CoreSuspender suspend; @@ -167,6 +352,20 @@ static command_result rename(color_ostream &out, vector ¶meters) unit->custom_profession = parameters[1]; } + else if (cmd == "building") + { + if (parameters.size() != 2) + return CR_WRONG_USAGE; + + if (ui->main.mode != ui_sidebar_mode::QueryBuilding) + return CR_WRONG_USAGE; + + if (!renameBuilding(world->selected_building, parameters[1])) + { + out.printerr("This type of building is not supported.\n"); + return CR_FAILURE; + } + } else { if (!parameters.empty() && cmd != "?") diff --git a/scripts/gui/rename.lua b/scripts/gui/rename.lua new file mode 100644 index 000000000..a457a0bfd --- /dev/null +++ b/scripts/gui/rename.lua @@ -0,0 +1,63 @@ +-- Rename various objects via gui. + +local gui = require 'gui' +local dlg = require 'gui.dialogs' +local plugin = require 'plugins.rename' + +local mode = ... +local focus = dfhack.gui.getCurFocus() + +local function verify_mode(expected) + if mode ~= nil and mode ~= expected then + qerror('Invalid UI state for mode '..mode) + end +end + +if string.match(focus, '^dwarfmode/QueryBuilding/Some') then + verify_mode('building') + + local building = df.global.world.selected_building + if plugin.canRenameBuilding(building) then + dlg.showInputPrompt( + 'Rename Building', + 'Enter a new name for the building:', COLOR_GREEN, + building.name, + curry(plugin.renameBuilding, building) + ) + else + dlg.showMessage( + 'Rename Building', + 'Cannot rename this type of building.', COLOR_LIGHTRED + ) + end +elseif dfhack.gui.getSelectedUnit(true) then + local unit = dfhack.gui.getSelectedUnit(true) + + if mode == 'unit-profession' then + dlg.showInputPrompt( + 'Rename Unit', + 'Enter a new profession for the unit:', COLOR_GREEN, + unit.custom_profession, + function(newval) + unit.custom_profession = newval + end + ) + else + verify_mode('unit') + + local vname = dfhack.units.getVisibleName(unit) + local vnick = '' + if vname and vname.has_name then + vnick = vname.nickname + end + + dlg.showInputPrompt( + 'Rename Unit', + 'Enter a new nickname for the unit:', COLOR_GREEN, + vnick, + curry(dfhack.units.setNickname, unit) + ) + end +elseif mode then + verify_mode(nil) +end