@ -4,6 +4,8 @@
# include <PluginManager.h>
# include <modules/Gui.h>
# include <modules/Screen.h>
# include <modules/Maps.h>
# include <TileTypes.h>
# include <vector>
# include <cstdio>
# include <stack>
@ -22,9 +24,92 @@
# 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"
/*
* 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 ;
@ -33,13 +118,23 @@ 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 ;
@ -48,42 +143,268 @@ struct steam_engine_workshop {
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 )
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 ;
}
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 ( )
{
int cnt = 0 ;
return ( flags . whole > > 28 ) & 15 ;
}
for ( size_t i = 0 ; i < contained_items . size ( ) ; i + + )
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 + + )
{
if ( contained_items [ i ] - > use_mode = = 0 & &
contained_items [ i ] - > item - > flags . bits . in_building )
cnt + + ;
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 cnt ;
return false ;
}
int get_power_output ( steam_engine_workshop * engine )
// Absorbs a water item produced by stoke reaction into the engine.
bool absorb_unit ( steam_engine_workshop * engine , df : : item_liquid_miscst * liquid )
{
int maxv = engine - > def - > needs_magma ? 5 : 3 ;
return std : : min ( get_steam_amount ( ) , maxv ) * 100 ;
// 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 ;
}
df : : item_liquid_miscst * collect_steam ( )
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 - - )
{
@ -98,19 +419,194 @@ 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 ;
// This may destroy the item
if ( ! absorb_unit ( engine , liquid ) )
continue ;
}
first = liquid ;
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 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 * efficiency_coeff ;
// 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 ( ) )
@ -119,12 +615,13 @@ 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 - > consumed = 10 ;
info - > produced = std: : min ( engine - > max_power , get_steam_amount ( ) ) * 100 ;
info - > consumed = 10 - int ( get_component_quality ( 0 ) ) ;
return ;
}
@ -178,6 +675,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 ;
@ -195,40 +693,106 @@ 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 , 3 ) ;
}
return INTERPOSE_NEXT ( isUnpowered ) ( ) ;
}
DEFINE_VMETHOD_INTERPOSE ( void , updateAction , ( ) )
{
if ( auto engine = get_steam_engine ( ) )
{
int output = get_power_output ( 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 ( auto first = collect_steam ( ) )
// If machine is running, tweak gear assemblies
auto mptr = df : : machine : : find ( machine . machine_id ) ;
if ( mptr & & ( mptr - > visual_phase & 1 ) ! = 0 )
{
if ( first - > incWearTimer ( output ) )
for ( size_t i = 0 ; i < engine - > gear_tiles . size ( ) ; i + + )
{
while ( first - > wear_timer > = 806400 )
{
first - > wear_timer - = 806400 ;
first - > wear + + ;
}
auto pos = engine - > gear_tiles [ i ] ;
db - > tile [ pos . x ] [ pos . y ] = 42 ;
}
}
if ( first - > wear > 3 )
{
first - > flags . bits . in_building = 0 ;
first - > temperature = first - > getBoilingPoint ( ) + 50 ;
}
// 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 ;
}
}
}
}
int new_out = get_power_output ( engine ) ;
if ( new_out ! = output )
DEFINE_VMETHOD_INTERPOSE ( void , deconstructItems , ( bool noscatter , bool lost ) )
{
if ( get_steam_engine ( ) )
{
// Explode if any steam left
if ( int amount = get_steam_amount ( ) )
{
auto mptr = df : : machine : : find ( machine . machine_id ) ;
if ( mptr )
mptr - > cur_power + = ( new_out - output ) ;
make_explosion (
df : : coord ( ( x1 + x2 ) / 2 , ( y1 + y2 ) / 2 , z ) ,
40 + amount * 20
) ;
random_boil ( ) ;
}
}
INTERPOSE_NEXT ( updateAction ) ( ) ;
INTERPOSE_NEXT ( deconstructItems ) ( noscatter , lost ) ;
}
} ;
@ -239,9 +803,95 @@ 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 ;
static void find_engines ( )
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 ( ) ;
@ -261,70 +911,78 @@ static void 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 ;
}
}
}
}
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 ;
ws . wear_temp = ws . is_magma ? 12000 : 11000 ;
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 ( 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 :
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 +1002,6 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <Plug
DFhackCExport command_result plugin_shutdown ( color_ostream & out )
{
disable_hooks( ) ;
enable_hooks( false ) ;
return CR_OK ;
}