diff options
| author | Kelly Martin | 2012-09-04 12:05:05 -0500 |
|---|---|---|
| committer | Kelly Martin | 2012-09-04 12:05:05 -0500 |
| commit | 96fec768c73aee3fca8937017ec8ef59cf0d7adb (patch) | |
| tree | 09e821999fd94b96918fdf2a7cb2e885d03f4e8a /plugins/devel | |
| parent | a8158cb19ac25ec69db4cbb09c1eaf853a559e9a (diff) | |
| parent | aa449a2180144961308e1e357c013724921175b3 (diff) | |
| download | dfhack-96fec768c73aee3fca8937017ec8ef59cf0d7adb.tar.gz dfhack-96fec768c73aee3fca8937017ec8ef59cf0d7adb.tar.bz2 dfhack-96fec768c73aee3fca8937017ec8ef59cf0d7adb.tar.xz | |
Merge remote-tracking branch 'angavrilov/master'
Diffstat (limited to 'plugins/devel')
| -rw-r--r-- | plugins/devel/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | plugins/devel/building_steam_engine.txt | 92 | ||||
| -rw-r--r-- | plugins/devel/item_trapcomp_steam_engine.txt | 12 | ||||
| -rw-r--r-- | plugins/devel/reaction_steam_engine.txt | 14 | ||||
| -rw-r--r-- | plugins/devel/steam-engine.cpp | 929 |
5 files changed, 1048 insertions, 0 deletions
diff --git a/plugins/devel/CMakeLists.txt b/plugins/devel/CMakeLists.txt index 134d5cb6..f126ae53 100644 --- a/plugins/devel/CMakeLists.txt +++ b/plugins/devel/CMakeLists.txt @@ -18,6 +18,7 @@ DFHACK_PLUGIN(stripcaged stripcaged.cpp) DFHACK_PLUGIN(rprobe rprobe.cpp) DFHACK_PLUGIN(nestboxes nestboxes.cpp) DFHACK_PLUGIN(vshook vshook.cpp) +DFHACK_PLUGIN(steam-engine steam-engine.cpp) IF(UNIX) DFHACK_PLUGIN(ref-index ref-index.cpp) ENDIF() diff --git a/plugins/devel/building_steam_engine.txt b/plugins/devel/building_steam_engine.txt new file mode 100644 index 00000000..48657b0c --- /dev/null +++ b/plugins/devel/building_steam_engine.txt @@ -0,0 +1,92 @@ +building_steam_engine
+
+[OBJECT:BUILDING]
+
+[BUILDING_WORKSHOP:STEAM_ENGINE]
+ [NAME:Steam Engine]
+ [NAME_COLOR:4:0:1]
+ [DIM:3:3]
+ [WORK_LOCATION:2:3]
+ [BUILD_LABOR:MECHANIC]
+ [BUILD_KEY:CUSTOM_ALT_S]
+ [BLOCK:1:1:1:1]
+ [BLOCK:2:1:1:1]
+ [BLOCK:3:1:0:1]
+ [TILE:0:1:240:' ':254]
+ [TILE:0:2:' ':' ':128]
+ [TILE:0:3:246:' ':' ']
+ [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:MAT:0:0:0:0:0:0]
+ [TILE:1:1:246:128:' ']
+ [TILE:1:2:' ':' ':254]
+ [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: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: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 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: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:1:BLOCKS:NONE:NONE:NONE][BUILDMAT][FIRE_BUILD_SAFE]
+
+[BUILDING_WORKSHOP:MAGMA_STEAM_ENGINE]
+ [NAME:Magma Steam Engine]
+ [NAME_COLOR:4:0:1]
+ [DIM:3:3]
+ [WORK_LOCATION:2:3]
+ [BUILD_LABOR:MECHANIC]
+ [BUILD_KEY:CUSTOM_ALT_E]
+ [NEEDS_MAGMA]
+ [BLOCK:1:1:1:1]
+ [BLOCK:2:1:1:1]
+ [BLOCK:3:1:0:1]
+ [TILE:0:1:240:' ':254]
+ [TILE:0:2:' ':' ':128]
+ [TILE:0:3:246:' ':' ']
+ [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:MAT:0:0:0:0:0:0]
+ [TILE:1:1:246:128:' ']
+ [TILE:1:2:' ':' ':254]
+ [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: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: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 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: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: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 00000000..bae6f5b2 --- /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_steam_engine.txt b/plugins/devel/reaction_steam_engine.txt new file mode 100644 index 00000000..175ffdd5 --- /dev/null +++ b/plugins/devel/reaction_steam_engine.txt @@ -0,0 +1,14 @@ +reaction_steam_engine
+
+[OBJECT:REACTION]
+
+[REACTION:STOKE_BOILER]
+ [NAME:stoke the boiler]
+ [BUILDING:STEAM_ENGINE:CUSTOM_S]
+ [BUILDING:MAGMA_STEAM_ENGINE:CUSTOM_S]
+ [FUEL]
+ [SKILL:SMELT]
+ Dimension is the number of days it can produce 100 power * 100.
+ 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 new file mode 100644 index 00000000..5c344f78 --- /dev/null +++ b/plugins/devel/steam-engine.cpp @@ -0,0 +1,929 @@ +#include "Core.h" +#include <Console.h> +#include <Export.h> +#include <PluginManager.h> +#include <modules/Gui.h> +#include <modules/Screen.h> +#include <modules/Maps.h> +#include <TileTypes.h> +#include <vector> +#include <cstdio> +#include <stack> +#include <string> +#include <cmath> +#include <string.h> + +#include <VTableInterpose.h> +#include "df/graphic.h" +#include "df/building_workshopst.h" +#include "df/building_def_workshopst.h" +#include "df/item_liquid_miscst.h" +#include "df/power_info.h" +#include "df/workshop_type.h" +#include "df/builtin_mats.h" +#include "df/world.h" +#include "df/buildings_other_id.h" +#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 "df/flow_info.h" +#include "df/report.h" + +#include "MiscUtils.h" + +using std::vector; +using std::string; +using std::stack; +using namespace DFHack; +using namespace df::enums; + +using df::global::gps; +using df::global::world; +using df::global::ui; +using df::global::ui_build_selector; + +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<df::coord2d> gear_tiles; + df::coord2d hearth_tile; + df::coord2d water_tile; + df::coord2d magma_tile; +}; + +std::vector<steam_engine_workshop> 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; +} + +/* + * Misc utilities. + */ + +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 } +}; + +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, int(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); +} + +void make_explosion(df::coord center, int power) +{ + static const int bias[9] = { + 60, 30, 60, + 30, 0, 30, + 60, 30, 60 + }; + + 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; + +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++; + } + } + + 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; + + 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); + } + + 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); + +/* + * 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) + return find_steam_engine(custom_type); + + 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. + + int get_steam_amount() + { + return (flags.whole >> 28) & 15; + } + + void set_steam_amount(int count) + { + 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, int min_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 < min_level) + continue; + + if (pldes->bits.liquid_type == tile_liquid::Magma) + { + if (pmagma) + *pmagma = df::coord(x,y,z-1); + if (pwater->isValid()) + return true; + } + else + { + *pwater = df::coord(x,y,z-1); + if (!pmagma || pmagma->isValid()) + return true; + } + } + } + + 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, 1)) + { + // Destroy the item with enormous wear amount. + liquid->addWear(WEAR_TICKS*5, true, false); + return false; + } + + decrement_flow(water, 1); + 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; + liquid->temperature_fraction = 0; + + // 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 block temperature updates + enable_updates_at(liquid->pos, false, true); + return true; + } + + bool boil_unit(df::item_liquid_miscst *liquid) + { + 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; + } + + // 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; + *count = 0; + + for (int i = contained_items.size()-1; i >= 0; i--) + { + auto item = contained_items[i]; + if (item->use_mode != 0) + continue; + + auto liquid = strict_virtual_cast<df::item_liquid_miscst>(item->item); + if (!liquid) + continue; + + if (!liquid->flags.bits.in_building) + { + if (liquid->mat_type != builtin_mats::WATER || + liquid->age > 1 || + liquid->wear != 0) + continue; + + // This may destroy the item + if (!absorb_unit(engine, liquid)) + continue; + } + + if (*count < engine->max_capacity) + { + first = liquid; + ++*count; + } + else + { + // Overpressure valve + boil_unit(liquid); + suspend_jobs(true); + } + } + + 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<df::item_liquid_miscst>(item->item); + if (!liquid) + continue; + + if (cnt == 0 || rand() < RAND_MAX/2) + { + cnt++; + boil_unit(liquid); + } + } + } + + int classify_component(df::building_actual::T_contained_items *item) + { + if (item->use_mode != 2 || item->item->isBuildMat()) + return -1; + + 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--) + { + int type = classify_component(contained_items[i]); + if (type < 0) + continue; + + df::item *item = contained_items[i]->item; + int melt_temp = item->getMeltingPoint(); + if (coeffs[type] == 0 || melt_temp >= engine->wear_temp) + continue; + + // 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; + } + + 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 + 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; + } + // 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)); + } + + 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, ()) + { + if (get_steam_engine()) + return true; + + return INTERPOSE_NEXT(needsDesign)(); + } + + // Machine interface + DEFINE_VMETHOD_INTERPOSE(void, getPowerInfo, (df::power_info *info)) + { + if (auto engine = get_steam_engine()) + { + info->produced = std::min(engine->max_power, get_steam_amount())*100; + info->consumed = 10 - int(get_component_quality(0)); + return; + } + + INTERPOSE_NEXT(getPowerInfo)(info); + } + + DEFINE_VMETHOD_INTERPOSE(df::machine_info*, getMachineInfo, ()) + { + if (get_steam_engine()) + return &machine; + + return INTERPOSE_NEXT(getMachineInfo)(); + } + + DEFINE_VMETHOD_INTERPOSE(bool, isPowerSource, ()) + { + if (get_steam_engine()) + return true; + + return INTERPOSE_NEXT(isPowerSource)(); + } + + DEFINE_VMETHOD_INTERPOSE(void, categorize, (bool free)) + { + if (get_steam_engine()) + { + auto &vec = world->buildings.other[buildings_other_id::ANY_MACHINE]; + insert_into_vector(vec, &df::building::id, (df::building*)this); + } + + INTERPOSE_NEXT(categorize)(free); + } + + DEFINE_VMETHOD_INTERPOSE(void, uncategorize, ()) + { + if (get_steam_engine()) + { + auto &vec = world->buildings.other[buildings_other_id::ANY_MACHINE]; + erase_from_vector(vec, &df::building::id, id); + } + + INTERPOSE_NEXT(uncategorize)(); + } + + DEFINE_VMETHOD_INTERPOSE(bool, canConnectToMachine, (df::machine_tile_set *info)) + { + if (auto engine = get_steam_engine()) + { + int real_cx = centerx, real_cy = centery; + bool ok = false; + + 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; + + if (!INTERPOSE_NEXT(canConnectToMachine)(info)) + continue; + + ok = true; + break; + } + + centerx = real_cx; centery = real_cy; + return ok; + } + else + 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, 3); + } + + return INTERPOSE_NEXT(isUnpowered)(); + } + + DEFINE_VMETHOD_INTERPOSE(void, updateAction, ()) + { + if (auto engine = get_steam_engine()) + { + if (is_fully_built()) + update_working(engine); + else + update_under_construction(engine); + + if (flags.bits.almost_deleted) + return; + } + + 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 (!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) + { + 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]; + } + + // Set liquid indicator state + if (engine->water_tile.isValid() || engine->magma_tile.isValid()) + { + df::coord water, magma; + 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()) + { + 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; + } + } + } + } + + DEFINE_VMETHOD_INTERPOSE(void, deconstructItems, (bool noscatter, bool lost)) + { + if (get_steam_engine()) + { + // 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 + ); + + random_boil(); + } + } + + INTERPOSE_NEXT(deconstructItems)(noscatter, lost); + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE(workshop_hook, needsDesign); +IMPLEMENT_VMETHOD_INTERPOSE(workshop_hook, getPowerInfo); +IMPLEMENT_VMETHOD_INTERPOSE(workshop_hook, getMachineInfo); +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); + +/* + * 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; + + 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 - cover channels with down stairs."; + ui_build_selector->errors.push_back(new std::string(msg)); + } + } + + DEFINE_VMETHOD_INTERPOSE(void, feed, (set<df::interface_key> *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. 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(); + + auto &wslist = world->raws.buildings.workshops; + + for (size_t i = 0; i < wslist.size(); i++) + { + if (strstr(wslist[i]->code.c_str(), "STEAM_ENGINE") == NULL) + continue; + + steam_engine_workshop ws; + ws.def = wslist[i]; + ws.id = ws.def->id; + + int bs = ws.def->build_stages; + for (int x = 0; x < ws.def->dim_x; x++) + { + for (int y = 0; y < ws.def->dim_y; y++) + { + 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 1: + ws.water_tile = df::coord2d(x,y); + break; + case 4: + ws.magma_tile = df::coord2d(x,y); + break; + } + } + } + } + + 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); + } + + return !engines.empty(); +} + +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); + 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, 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) +{ + switch (event) { + case SC_MAP_LOADED: + if (find_engines()) + { + out.print("Detected steam engine workshops - enabling plugin.\n"); + enable_hooks(true); + } + else + enable_hooks(false); + break; + case SC_MAP_UNLOADED: + enable_hooks(false); + engines.clear(); + break; + default: + break; + } + + return CR_OK; +} + +DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands) +{ + if (Core::getInstance().isMapLoaded()) + plugin_onstatechange(out, SC_MAP_LOADED); + + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown ( color_ostream &out ) +{ + enable_hooks(false); + return CR_OK; +} |
