Use new API in steam engine; always explode if destroyed with steam inside.

develop
Alexander Gavrilov 2012-09-02 14:59:13 +04:00
parent 3713c5ea9e
commit 9c3843c1d4
1 changed files with 189 additions and 98 deletions

@ -47,12 +47,18 @@ using df::global::ui_build_selector;
DFHACK_PLUGIN("steam-engine"); DFHACK_PLUGIN("steam-engine");
/*
* List of known steam engine workshop raws.
*/
struct steam_engine_workshop { struct steam_engine_workshop {
int id; int id;
df::building_def_workshopst *def; df::building_def_workshopst *def;
// Cached properties
bool is_magma; bool is_magma;
int max_power, max_capacity; int max_power, max_capacity;
int wear_temp; int wear_temp;
// Special tiles (relative position)
std::vector<df::coord2d> gear_tiles; std::vector<df::coord2d> gear_tiles;
df::coord2d hearth_tile; df::coord2d hearth_tile;
df::coord2d water_tile; df::coord2d water_tile;
@ -70,6 +76,10 @@ steam_engine_workshop *find_steam_engine(int id)
return NULL; return NULL;
} }
/*
* Misc utilities.
*/
static const int hearth_colors[6][2] = { static const int hearth_colors[6][2] = {
{ COLOR_BLACK, 1 }, { COLOR_BLACK, 1 },
{ COLOR_BROWN, 0 }, { COLOR_BROWN, 0 },
@ -102,26 +112,57 @@ void decrement_flow(df::coord pos, int amount)
enable_updates_at(pos, true, false); 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); int mat_type = builtin_mats::WATER, mat_index = -1;
if (!flows || !block) int i = 0;
return false;
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(); bool add_wear_nodestroy(df::item_actual *item, int rate)
flow->type = flow_type::MaterialDust; {
flow->mat_type = mat_type; if (item->incWearTimer(rate))
flow->mat_index = mat_index; {
flow->density = std::min(100, density); while (item->wear_timer >= WEAR_TICKS)
flow->pos = pos; {
item->wear_timer -= WEAR_TICKS;
item->wear++;
}
}
block->flows.push_back(flow); return item->wear > 3;
flows->push_back(flow);
return true;
} }
/*
* 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 { struct liquid_hook : df::item_liquid_miscst {
typedef df::item_liquid_miscst interpose_base; 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, adjustTemperature);
IMPLEMENT_VMETHOD_INTERPOSE(liquid_hook, checkTemperatureDamage); IMPLEMENT_VMETHOD_INTERPOSE(liquid_hook, checkTemperatureDamage);
/*
* Hook for the workshop itself. Implements core logic.
*/
struct workshop_hook : df::building_workshopst { struct workshop_hook : df::building_workshopst {
typedef df::building_workshopst interpose_base; typedef df::building_workshopst interpose_base;
// Engine detection
steam_engine_workshop *get_steam_engine() steam_engine_workshop *get_steam_engine()
{ {
if (type == workshop_type::Custom) if (type == workshop_type::Custom)
@ -167,6 +214,11 @@ struct workshop_hook : df::building_workshopst {
return NULL; return NULL;
} }
inline bool is_fully_built()
{
return getBuildStage() >= getMaxBuildStage();
}
// Use high bits of flags to store current steam amount. // Use high bits of flags to store current steam amount.
// This is necessary for consistency if items disappear unexpectedly. // 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); 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) bool find_liquids(df::coord *pwater, df::coord *pmagma, bool is_magma, bool any_level)
{ {
if (!is_magma) if (!is_magma)
@ -225,13 +279,17 @@ struct workshop_hook : df::building_workshopst {
return false; 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) bool absorb_unit(steam_engine_workshop *engine, df::item_liquid_miscst *liquid)
{ {
// Consume liquid inputs
df::coord water, magma; df::coord water, magma;
if (!find_liquids(&water, &magma, engine->is_magma, true)) 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; return false;
} }
@ -239,6 +297,7 @@ struct workshop_hook : df::building_workshopst {
if (engine->is_magma) if (engine->is_magma)
decrement_flow(magma, 1); decrement_flow(magma, 1);
// Update flags
liquid->flags.bits.in_building = true; liquid->flags.bits.in_building = true;
liquid->mat_state.whole |= liquid_hook::BOILING_FLAG; liquid->mat_state.whole |= liquid_hook::BOILING_FLAG;
liquid->temperature = liquid->getBoilingPoint()-1; liquid->temperature = liquid->getBoilingPoint()-1;
@ -248,6 +307,7 @@ struct workshop_hook : df::building_workshopst {
if (engine->hearth_tile.isValid()) if (engine->hearth_tile.isValid())
liquid->pos = df::coord(x1+engine->hearth_tile.x, y1+engine->hearth_tile.y, z); 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); enable_updates_at(liquid->pos, false, true);
return true; return true;
} }
@ -268,6 +328,8 @@ struct workshop_hook : df::building_workshopst {
jobs[i]->flags.bits.suspend = suspend; 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 *collect_steam(steam_engine_workshop *engine, int *count)
{ {
df::item_liquid_miscst *first = NULL; df::item_liquid_miscst *first = NULL;
@ -290,6 +352,7 @@ struct workshop_hook : df::building_workshopst {
liquid->wear != 0) liquid->wear != 0)
continue; continue;
// This may destroy the item
if (!absorb_unit(engine, liquid)) if (!absorb_unit(engine, liquid))
continue; continue;
} }
@ -303,6 +366,7 @@ struct workshop_hook : df::building_workshopst {
{ {
// Overpressure valve // Overpressure valve
boil_unit(liquid); 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; if (item->use_mode != 2 || item->item->isBuildMat())
int cx = (x1+x2)/2, cy = (y1+y2)/2; return -1;
int power = std::min(240, get_steam_amount()*80);
make_explosion(df::coord(cx, cy, z), mat_type, mat_index, power); switch (item->item->getType())
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); case item_type::TRAPPARTS:
make_explosion(df::coord(cx+1, cy, z), mat_type, mat_index, power/3); case item_type::CHAIN:
make_explosion(df::coord(cx, cy+1, z), mat_type, mat_index, power/3); return 0;
case item_type::BARREL:
*df::global::pause_state = true; return 2;
default:
Gui::showAnnouncement("A boiler has exploded!", COLOR_RED, true); return 1;
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) 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--) for (int i = contained_items.size()-1; i >= 0; i--)
{ {
auto item = contained_items[i]; int type = classify_component(contained_items[i]);
if (item->use_mode != 2) if (type < 0)
continue;
int melt_temp = item->item->getMeltingPoint();
if (melt_temp >= engine->wear_temp)
continue;
if (item->item->isBuildMat())
continue; continue;
auto type = item->item->getType(); df::item *item = contained_items[i]->item;
if (type == item_type::TRAPPARTS || item_type::CHAIN) int melt_temp = item->getMeltingPoint();
if (coeffs[type] == 0 || melt_temp >= engine->wear_temp)
continue; continue;
int coeff = power; // let 500 degree delta at 4 pressure work 1 season
if (type == item_type::BARREL) float ticks = coeffs[type]*(engine->wear_temp - melt_temp)*3.0f/500.0f/4.0f;
coeff = count; if (item->addWear(int(8*(1 + ticks)), true, true))
int ticks = coeff*(engine->wear_temp - melt_temp);
if (item->item->addWear(ticks, true, true))
{
explode();
return true; return true;
}
} }
return false; return false;
} }
static const int WEAR_TICKS = 806400;
int get_steam_use_rate(steam_engine_workshop *engine, int dimension, int power_level) int get_steam_use_rate(steam_engine_workshop *engine, int dimension, int power_level)
{ {
// total ticks to wear off completely // total ticks to wear off completely
@ -409,6 +458,61 @@ struct workshop_hook : df::building_workshopst {
return std::max(1, int(ticks)); 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 // Furnaces need architecture, and this is a workshop
// only because furnaces cannot connect to machines. // only because furnaces cannot connect to machines.
DEFINE_VMETHOD_INTERPOSE(bool, needsDesign, ()) DEFINE_VMETHOD_INTERPOSE(bool, needsDesign, ())
@ -513,47 +617,13 @@ struct workshop_hook : df::building_workshopst {
{ {
if (auto engine = get_steam_engine()) if (auto engine = get_steam_engine())
{ {
int old_count = get_steam_amount(); if (is_fully_built())
int old_power = std::min(engine->max_power, old_count); update_working(engine);
int cur_count = 0; else
update_under_construction(engine);
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);
int cur_power = std::min(engine->max_power, cur_count); if (flags.bits.almost_deleted)
if (cur_power != old_power) return;
{
auto mptr = df::machine::find(machine.machine_id);
if (mptr)
mptr->cur_power += (cur_power - old_power)*100;
}
} }
INTERPOSE_NEXT(updateAction)(); INTERPOSE_NEXT(updateAction)();
@ -565,6 +635,9 @@ struct workshop_hook : df::building_workshopst {
if (auto engine = get_steam_engine()) if (auto engine = get_steam_engine())
{ {
if (!is_fully_built())
return;
// If machine is running, tweak gear assemblies // If machine is running, tweak gear assemblies
auto mptr = df::machine::find(machine.machine_id); auto mptr = df::machine::find(machine.machine_id);
if (mptr && (mptr->visual_phase & 1) != 0) 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)) DEFINE_VMETHOD_INTERPOSE(void, deconstructItems, (bool noscatter, bool lost))
{ {
if (get_steam_engine()) 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) random_boil();
explode(); }
}
INTERPOSE_NEXT(deconstructItems)(noscatter, lost); 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, drawBuilding);
IMPLEMENT_VMETHOD_INTERPOSE(workshop_hook, deconstructItems); 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 struct dwarfmode_hook : df::viewscreen_dwarfmodest
{ {
typedef df::viewscreen_dwarfmodest interpose_base; typedef df::viewscreen_dwarfmodest interpose_base;
@ -669,7 +755,7 @@ struct dwarfmode_hook : df::viewscreen_dwarfmodest
if (error) 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)); ui_build_selector->errors.push_back(new std::string(msg));
} }
} }
@ -689,13 +775,18 @@ struct dwarfmode_hook : df::viewscreen_dwarfmodest
if (engine) if (engine)
engine->def->needs_magma = engine->is_magma; 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()); check_hanging_tiles(get_steam_engine());
} }
}; };
IMPLEMENT_VMETHOD_INTERPOSE(dwarfmode_hook, feed); IMPLEMENT_VMETHOD_INTERPOSE(dwarfmode_hook, feed);
/*
* Scan raws for matching workshop buildings.
*/
static bool find_engines() static bool find_engines()
{ {
engines.clear(); engines.clear();