From 407518defd6f8f5cb6aa413b5cce54a7f4127b7c Mon Sep 17 00:00:00 2001 From: Coolthulhu Date: Sun, 24 Jun 2018 03:36:53 +0200 Subject: [PATCH 1/2] Automatic fire refuelling! +better fire math --- data/json/construction.json | 8 +++ data/json/materials.json | 46 ++++++------- data/json/player_activities.json | 15 +++-- data/json/traps.json | 12 ++++ src/activity_handlers.h | 1 + src/activity_item_handling.cpp | 110 +++++++++++++++++++++++++++++++ src/activity_type.cpp | 1 + src/activity_type.h | 7 ++ src/construction.cpp | 15 +++++ src/field.cpp | 4 +- src/fire.h | 19 ++++-- src/item.cpp | 44 ++++++++----- src/item.h | 10 ++- src/material.cpp | 2 +- src/player.cpp | 4 +- src/player_activity.cpp | 5 ++ 16 files changed, 245 insertions(+), 58 deletions(-) diff --git a/data/json/construction.json b/data/json/construction.json index edfcdb34c014b..3e5a69e47f0a4 100644 --- a/data/json/construction.json +++ b/data/json/construction.json @@ -2343,5 +2343,13 @@ ], "pre_terrain" : "t_reinforced_glass", "post_terrain" : "t_reinforced_glass_shutter" + },{ + "type" : "construction", + "description" : "Mark firewood source", + "category" : "OTHER", + "required_skills" : [], + "time" : 0, + "pre_special" : "check_no_trap", + "post_special" : "done_mark_firewood" } ] diff --git a/data/json/materials.json b/data/json/materials.json index ea88577c590cf..771fa28eee5fe 100644 --- a/data/json/materials.json +++ b/data/json/materials.json @@ -51,7 +51,7 @@ "bash_dmg_verb": "cracked", "cut_dmg_verb": "chipped", "burn_data": [ - { "fuel": 0, "smoke": 1, "burn": 1, "chance": 5 }, + { "fuel": 0, "smoke": 1, "burn": 1, "volume_per_turn": "1250_ml" }, { "fuel": 1, "smoke": 3, "burn": 1 }, { "fuel": 1, "smoke": 5, "burn": 5 } ] @@ -74,7 +74,7 @@ "vitamins": [ [ "calcium", 2 ] ], "burn_data": [ { "fuel": 0, "smoke": 0, "burn": 0 }, - { "fuel": 0, "smoke": 1, "burn": 1, "chance": 1 }, + { "fuel": 0, "smoke": 1, "burn": 1, "volume_per_turn": "250_ml" }, { "fuel": 1, "smoke": 1, "burn": 2 } ] }, @@ -139,7 +139,7 @@ "bash_dmg_verb": "cracked", "cut_dmg_verb": "chipped", "burn_data": [ - { "fuel": 0, "smoke": 1, "burn": 1, "chance": 5 }, + { "fuel": 0, "smoke": 1, "burn": 1, "volume_per_turn": "1250_ml" }, { "fuel": 1, "smoke": 3, "burn": 1 }, { "fuel": 1, "smoke": 5, "burn": 5 } ] @@ -194,7 +194,7 @@ "bash_dmg_verb": "ripped", "cut_dmg_verb": "cut", "burn_data": [ - { "fuel": 1, "smoke": 1, "burn": 1, "chance": 5 }, + { "fuel": 1, "smoke": 1, "burn": 1, "volume_per_turn": "1250_ml" }, { "fuel": 1, "smoke": 1, "burn": 1 }, { "fuel": 1, "smoke": 1, "burn": 2 } ] @@ -249,8 +249,8 @@ "cut_dmg_verb": "sliced", "vitamins": [ [ "calcium", 0.1 ], [ "vitB", 1 ], [ "iron", 2 ] ], "burn_data": [ - { "fuel": 1, "smoke": 1, "burn": 1, "chance": 10 }, - { "fuel": 2, "smoke": 3, "burn": 2, "chance": 40 }, + { "fuel": 1, "smoke": 1, "burn": 1, "volume_per_turn": "2500_ml" }, + { "fuel": 2, "smoke": 3, "burn": 2, "volume_per_turn": "10000_ml" }, { "fuel": 3, "smoke": 10, "burn": 3 } ] }, @@ -286,7 +286,7 @@ "cut_dmg_verb": "cut", "vitamins": [ [ "vitC", 2 ] ], "burn_data": [ - { "fuel": 1, "smoke": 2, "burn": 1, "chance": 5 }, + { "fuel": 1, "smoke": 2, "burn": 1, "volume_per_turn": "1250_ml" }, { "fuel": 1, "smoke": 1, "burn": 2 }, { "fuel": 1, "smoke": 1, "burn": 3 } ] @@ -309,7 +309,7 @@ "bash_dmg_verb": "ripped", "cut_dmg_verb": "sliced", "burn_data": [ - { "fuel": 1, "smoke": 3, "burn": 1, "chance": 5 }, + { "fuel": 1, "smoke": 3, "burn": 1, "volume_per_turn": "1250_ml" }, { "fuel": 1, "smoke": 5, "burn": 2 }, { "fuel": 1, "smoke": 10, "burn": 4 } ] @@ -331,7 +331,7 @@ "burn_data": [ { "fuel": 0, "smoke": 0, "burn": 0 }, { "fuel": 0, "smoke": 0, "burn": 0 }, - { "fuel": 0, "smoke": 0, "burn": 1000, "chance": 20, "//": "More like shattering than melting" } + { "fuel": 0, "smoke": 0, "burn": 1000, "volume_per_turn": "5000_ml", "//": "More like shattering than melting" } ] }, { @@ -385,8 +385,8 @@ "cut_dmg_verb": "sliced", "vitamins": [ [ "calcium", 0.1 ], [ "vitB", 1 ], [ "iron", 2 ] ], "burn_data": [ - { "fuel": 1, "smoke": 1, "burn": 1, "chance": 10 }, - { "fuel": 2, "smoke": 3, "burn": 2, "chance": 40 }, + { "fuel": 1, "smoke": 1, "burn": 1, "volume_per_turn": "2500_ml" }, + { "fuel": 2, "smoke": 3, "burn": 2, "volume_per_turn": "10000_ml" }, { "fuel": 3, "smoke": 10, "burn": 3 } ] }, @@ -444,8 +444,8 @@ "cut_dmg_verb": "sliced", "vitamins": [ [ "calcium", 1 ] ], "burn_data": [ - { "fuel": 1, "smoke": 1, "burn": 1, "chance": 10 }, - { "fuel": 2, "smoke": 3, "burn": 2, "chance": 40 }, + { "fuel": 1, "smoke": 1, "burn": 1, "volume_per_turn": "2500_ml" }, + { "fuel": 2, "smoke": 3, "burn": 2, "volume_per_turn": "10000_ml" }, { "fuel": 3, "smoke": 10, "burn": 3 } ] }, @@ -535,7 +535,7 @@ "cut_dmg_verb": "sliced", "burn_data": [ { "fuel": 0, "smoke": 0, "burn": 0 }, - { "fuel": 1, "smoke": 3, "burn": 2, "chance": 2 }, + { "fuel": 1, "smoke": 3, "burn": 2, "volume_per_turn": "500_ml" }, { "fuel": 1, "smoke": 3, "burn": 2 } ] }, @@ -579,7 +579,7 @@ "bash_dmg_verb": "ripped", "cut_dmg_verb": "sliced", "burn_data": [ - { "fuel": 1, "smoke": 3, "burn": 1, "chance": 10 }, + { "fuel": 1, "smoke": 3, "burn": 1, "volume_per_turn": "2500_ml" }, { "fuel": 1, "smoke": 3, "burn": 2 }, { "fuel": 1, "smoke": 3, "burn": 3 } ] @@ -673,7 +673,7 @@ "bash_dmg_verb": "dented", "cut_dmg_verb": "gouged", "burn_data": [ - { "fuel": 1, "smoke": 2, "burn": 1, "chance": 3 }, + { "fuel": 1, "smoke": 2, "burn": 1, "volume_per_turn": "750_ml" }, { "fuel": 1, "smoke": 3, "burn": 2 }, { "fuel": 1, "smoke": 5, "burn": 5 } ] @@ -794,8 +794,8 @@ "cut_dmg_verb": "cut", "vitamins": [ [ "calcium", 0.1 ], [ "vitA", 1 ], [ "vitC", 0.5 ] ], "burn_data": [ - { "fuel": 1, "smoke": 1, "burn": 1, "chance": 5 }, - { "fuel": 2, "smoke": 1, "burn": 2, "chance": 10 }, + { "fuel": 1, "smoke": 1, "burn": 1, "volume_per_turn": "1250_ml" }, + { "fuel": 2, "smoke": 1, "burn": 2, "volume_per_turn": "2500_ml" }, { "fuel": 2, "smoke": 1, "burn": 3 } ] }, @@ -854,8 +854,8 @@ "bash_dmg_verb": "splintered", "cut_dmg_verb": "gouged", "burn_data": [ - { "fuel": 1, "smoke": 1, "burn": 0.02, "chance": 5 }, - { "fuel": 2, "smoke": 1, "burn": 0.04, "chance": 20 }, + { "fuel": 1, "smoke": 1, "burn": 0.02, "volume_per_turn": "1250_ml" }, + { "fuel": 2, "smoke": 1, "burn": 0.04, "volume_per_turn": "5000_ml" }, { "fuel": 3, "smoke": 1, "burn": 0.06 } ], "burn_products": [ [ "ash", 0.018 ] ] @@ -878,7 +878,7 @@ "bash_dmg_verb": "torn", "cut_dmg_verb": "cut", "burn_data": [ - { "fuel": 1, "smoke": 1, "burn": 1, "chance": 5 }, + { "fuel": 1, "smoke": 1, "burn": 1, "volume_per_turn": "1250_ml" }, { "fuel": 1, "smoke": 1, "burn": 2 }, { "fuel": 1, "smoke": 1, "burn": 5 } ] @@ -898,8 +898,8 @@ "bash_dmg_verb": "squished", "cut_dmg_verb": "squished", "burn_data": [ - { "fuel": 1, "smoke": 1, "burn": 1, "chance": 5 }, - { "fuel": 2, "smoke": 1, "burn": 2, "chance": 10 }, + { "fuel": 1, "smoke": 1, "burn": 1, "volume_per_turn": "1250_ml" }, + { "fuel": 2, "smoke": 1, "burn": 2, "volume_per_turn": "2500_ml" }, { "fuel": 2, "smoke": 1, "burn": 3 } ] } diff --git a/data/json/player_activities.json b/data/json/player_activities.json index cd1eab90f0d41..2a5ebd6153ccc 100644 --- a/data/json/player_activities.json +++ b/data/json/player_activities.json @@ -11,7 +11,8 @@ "type": "activity_type", "stop_phrase": "Stop reading?", "rooted": true, - "based_on": "speed" + "based_on": "speed", + "refuel_fires": true }, { "id": "ACT_GAME", @@ -32,20 +33,23 @@ "id": "ACT_CRAFT", "type": "activity_type", "stop_phrase": "Stop crafting?", - "based_on": "neither" + "based_on": "neither", + "refuel_fires": true }, { "id": "ACT_LONGCRAFT", "type": "activity_type", "stop_phrase": "Stop crafting?", - "based_on": "neither" + "based_on": "neither", + "refuel_fires": true }, { "id": "ACT_DISASSEMBLE", "type": "activity_type", "stop_phrase": "Stop disassembly?", "suspendable": false, - "based_on": "speed" + "based_on": "speed", + "refuel_fires": true }, { "id": "ACT_BUTCHER", @@ -58,7 +62,8 @@ "type": "activity_type", "stop_phrase": "Stop salvaging?", "based_on": "speed", - "no_resume": true + "no_resume": true, + "refuel_fires": true }, { "id": "ACT_FORAGE", diff --git a/data/json/traps.json b/data/json/traps.json index 88eafc921c717..14ab8c1482523 100644 --- a/data/json/traps.json +++ b/data/json/traps.json @@ -467,5 +467,17 @@ "drops" : ["metal_funnel"], "benign" : true, "funnel_radius": 400 + }, + { + "type" : "trap", + "id": "tr_firewood_source", + "name" : "firewood source", + "color" : "green", + "symbol" : "x", + "visibility" : -1, + "avoidance" : 0, + "difficulty" : 0, + "action" : "none", + "benign" : true } ] diff --git a/src/activity_handlers.h b/src/activity_handlers.h index e7a2e9dbfb154..d267fc9e74f36 100644 --- a/src/activity_handlers.h +++ b/src/activity_handlers.h @@ -14,6 +14,7 @@ void activity_on_turn_drop(); void activity_on_turn_move_items(); void activity_on_turn_pickup(); void activity_on_turn_stash(); +void try_refuel_fire( player &p ); // advanced_inv.cpp void advanced_inv(); diff --git a/src/activity_item_handling.cpp b/src/activity_item_handling.cpp index 1dff15e12bf7a..121ec7bc2ef05 100644 --- a/src/activity_item_handling.cpp +++ b/src/activity_item_handling.cpp @@ -2,16 +2,21 @@ #include "game.h" #include "map.h" +#include "mapdata.h" #include "item.h" #include "player_activity.h" #include "action.h" #include "enums.h" +#include "field.h" +#include "fire.h" #include "creature.h" #include "pickup.h" #include "translations.h" #include "messages.h" #include "monster.h" +#include "optional.h" #include "output.h" +#include "trap.h" #include "vehicle.h" #include "vpart_position.h" #include "vpart_reference.h" @@ -26,12 +31,15 @@ #include #include #include +#include void cancel_aim_processing(); const efftype_id effect_controlled( "controlled" ); const efftype_id effect_pet( "pet" ); +const trap_str_id tr_firewood_source( "tr_firewood_source" ); + /** Activity-associated item */ struct act_item { const item *it; /// Pointer to the inventory item @@ -640,3 +648,105 @@ void activity_on_turn_move_items() } } } + +cata::optional find_best_fire( const std::vector &from ) +{ + cata::optional best_fire; + time_duration best_fire_age = 1_days; + for( const tripoint &pt : from ) { + field_entry *fire = g->m.get_field( pt, fd_fire ); + if( fire == nullptr || fire->getFieldDensity() > 1 ) { + continue; + } + time_duration fire_age = fire->getFieldAge(); + // Refuel only the best fueled fire (if it needs it) + if( fire_age < best_fire_age ) { + best_fire = pt; + best_fire_age = fire_age; + } + // If a contained fire exists, ignore any other fires + if( g->m.has_flag_furn( TFLAG_FIRE_CONTAINER, pt ) ) { + return pt; + } + } + + return best_fire; +} + +void try_refuel_fire( player &p ) +{ + auto adjacent = closest_tripoints_first( 1, p.pos() ); + adjacent.erase( adjacent.begin() ); + cata::optional best_fire = find_best_fire( adjacent ); + + if( !best_fire || !g->m.accessible_items( *best_fire ) ) { + return; + } + + const auto refuel_spot = std::find_if( adjacent.begin(), adjacent.end(), + []( const tripoint & pt ) { + // Hacky - firewood spot is a trap and it's ID-checked + // @todo Something cleaner than ID-checking a trap + return g->m.tr_at( pt ).id == tr_firewood_source && g->m.has_items( pt ) && + g->m.accessible_items( pt ); + } ); + if( refuel_spot == adjacent.end() ) { + return; + } + + // Special case: fire containers allow burning logs, so use them as fuel iif fire is contained + bool contained = g->m.has_flag_furn( TFLAG_FIRE_CONTAINER, *best_fire ); + fire_data fd( 1, contained ); + + time_duration fire_age = g->m.get_field_age( *best_fire, fd_fire ); + // Maybe @todo - refuelling in the rain could use more fuel + // First, simulate expected burn per turn, to see if we need more fuel + const float low_cap = fire_age / 10_minutes; + // High cap is 10 fuel/turn for 10 minute old fire, 1 fuel/turn for 0 old + const float high_cap = 10.0f - 9.0f * ( 10_minutes - fire_age ) / 10_minutes; + auto fuel_on_fire = g->m.i_at( *best_fire ); + for( size_t i = 0; i < fuel_on_fire.size(); i++ ) { + fuel_on_fire[i].simulate_burn( fd ); + if( fd.fuel_produced > high_cap && !fuel_on_fire[i].made_of( LIQUID ) ) { + // Too much - we don't want a firestorm! + // Put first item back to refuelling pile + std::list indices_to_remove{ static_cast( i ) }; + std::list quantities_to_remove{ 0 }; + move_items( *best_fire - p.pos(), false, *refuel_spot - p.pos(), false, indices_to_remove, + quantities_to_remove ); + return; + } + } + + // Enough to sustain the fire + if( fd.fuel_produced >= low_cap ) { + return; + } + + std::list indices; + std::list quantities; + // We need to move fuel from stash to fire + auto potential_fuel = g->m.i_at( *refuel_spot ); + for( size_t i = 0; i < potential_fuel.size(); i++ ) { + if( potential_fuel[i].made_of( LIQUID ) ) { + continue; + } + + // item::flammable isn't good enough - we need "fuelable" + float last_fuel = fd.fuel_produced; + potential_fuel[i].simulate_burn( fd ); + if( fd.fuel_produced > last_fuel ) { + indices.push_back( static_cast( i ) ); + quantities.push_back( 0 ); + } + // After we move those items, we should have enough + if( fd.fuel_produced > low_cap ) { + break; + } + } + + if( !indices.empty() ) { + // Note: move_items handles messages (they're the generic "you drop x") + move_items( *refuel_spot - p.pos(), false, *best_fire - p.pos(), false, indices, quantities ); + } +} diff --git a/src/activity_type.cpp b/src/activity_type.cpp index 28cafe057f61b..8a401b3c375cc 100644 --- a/src/activity_type.cpp +++ b/src/activity_type.cpp @@ -39,6 +39,7 @@ void activity_type::load( JsonObject &jo ) result.stop_phrase_ = _( jo.get_string( "stop_phrase" ).c_str() ); assign( jo, "suspendable", result.suspendable_, true ); assign( jo, "no_resume", result.no_resume_, true ); + assign( jo, "refuel_fires", result.refuel_fires, false ); result.based_on_ = io::string_to_enum_look_up( based_on_type_values, jo.get_string( "based_on" ) ); diff --git a/src/activity_type.h b/src/activity_type.h index a618293ae0e20..2cf823f57a08d 100644 --- a/src/activity_type.h +++ b/src/activity_type.h @@ -33,6 +33,7 @@ class activity_type bool suspendable_ = true; based_on_type based_on_ = based_on_type::SPEED; bool no_resume_ = false; + bool refuel_fires = false; public: const activity_id &id() const { @@ -53,6 +54,12 @@ class activity_type bool no_resume() const { return no_resume_; } + /** + * If true, player will refuel one adjacent fire if there is firewood spot adjacent. + */ + bool will_refuel_fires() const { + return refuel_fires; + } void call_do_turn( player_activity *, player * ) const; /** Returns whether it had a finish function or not */ diff --git a/src/construction.cpp b/src/construction.cpp index ebb7f5dad0a26..a2474620d4693 100644 --- a/src/construction.cpp +++ b/src/construction.cpp @@ -46,6 +46,8 @@ static const trait_id trait_PAINRESIST_TROGLO( "PAINRESIST_TROGLO" ); static const trait_id trait_STOCKY_TROGLO( "STOCKY_TROGLO" ); static const trait_id trait_WEB_ROPE( "WEB_ROPE" ); +const trap_str_id tr_firewood_source( "tr_firewood_source" ); + // Construction functions. namespace construct { @@ -59,6 +61,7 @@ bool check_support( const tripoint & ); // at least two orthogonal supports bool check_deconstruct( const tripoint & ); // either terrain or furniture must be deconstructible bool check_up_OK( const tripoint & ); // tile is empty and you're not on the surface bool check_down_OK( const tripoint & ); // tile is empty and you're not on z-10 already +bool check_no_trap( const tripoint & ); // Special actions to be run post-terrain-mod void done_nothing( const tripoint & ) {} @@ -72,6 +75,7 @@ void done_mine_upstair( const tripoint & ); void done_window_curtains( const tripoint & ); void done_extract_sand( const tripoint & ); void done_extract_clay( const tripoint & ); +void done_mark_firewood( const tripoint & ); void failure_standard( const tripoint & ); }; @@ -900,6 +904,10 @@ bool construct::check_down_OK( const tripoint & ) return ( g->get_levz() > -OVERMAP_DEPTH ); } +bool construct::check_no_trap( const tripoint &p ) +{ + return g->m.tr_at( p ).is_null(); +} void construct::done_trunk_plank( const tripoint &p ) { @@ -1133,6 +1141,11 @@ void construct::done_extract_clay( const tripoint &p ) g->u.add_msg_if_player( _( "You gather some clay." ) ); } +void construct::done_mark_firewood( const tripoint &p ) +{ + g->m.trap_set( p, tr_firewood_source ); +} + void construct::failure_standard( const tripoint & ) { add_msg( m_info, _( "You cannot build there!" ) ); @@ -1214,6 +1227,7 @@ void load_construction( JsonObject &jo ) { "check_deconstruct", construct::check_deconstruct }, { "check_up_OK", construct::check_up_OK }, { "check_down_OK", construct::check_down_OK }, + { "check_no_trap", construct::check_no_trap } } }; static const std::map> post_special_map = {{ @@ -1227,6 +1241,7 @@ void load_construction( JsonObject &jo ) { "done_window_curtains", construct::done_window_curtains }, { "done_extract_sand", construct::done_extract_sand }, { "done_extract_clay", construct::done_extract_clay }, + { "done_mark_firewood", construct::done_mark_firewood } } }; static const std::map> explain_fail_map = {{ diff --git a/src/field.cpp b/src/field.cpp index f4df287f8d8d2..7df25d728fa5c 100644 --- a/src/field.cpp +++ b/src/field.cpp @@ -896,7 +896,7 @@ bool map::process_fields_in_submap( submap *const current_submap, } } - fire_data frd{ cur.getFieldDensity(), 0.0f, 0.0f }; + fire_data frd( cur.getFieldDensity(), !can_spread ); // The highest # of items this fire can remove in one turn int max_consume = cur.getFieldDensity() * 2; @@ -905,7 +905,7 @@ bool map::process_fields_in_submap( submap *const current_submap, // destroyed by the fire, this changes the item weight, but may not actually // destroy it. We need to spawn products anyway. const units::mass old_weight = fuel->weight( false ); - bool destroyed = fuel->burn( frd, can_spread); + bool destroyed = fuel->burn( frd ); // If the item is considered destroyed, it may have negative charge count, // see `item::burn?. This in turn means `item::weight` returns a negative value, // which we can not use, so only call `weight` when it's still an existing item. diff --git a/src/fire.h b/src/fire.h index 24f6116ce8800..2c464b8601704 100644 --- a/src/fire.h +++ b/src/fire.h @@ -2,6 +2,8 @@ #ifndef FIRE_H #define FIRE_H +#include "units.h" + /** * Contains the state of a fire in one tile on one turn * @@ -14,12 +16,19 @@ * this turn. */ struct fire_data { + fire_data() {} + fire_data( const fire_data & ) = default; + fire_data( int intensity, bool is_contained = false ) : fire_intensity( intensity ), + contained( is_contained ) + {} /** Current intensity of the fire. This is an input to the calculations */ - int fire_intensity; + int fire_intensity = 0; /** Smoke produced by each burning item this turn is summed here. */ - float smoke_produced; + float smoke_produced = 0.0f; /** Fuel contributed by each burning item this turn is summed here. */ - float fuel_produced; + float fuel_produced = 0.0f; + /** The fire is contained and burned for fuel intentionally. */ + bool contained = false; }; /** @@ -37,8 +46,8 @@ struct fire_data { struct mat_burn_data { /** If this is true, an object will not burn or be destroyed by fire. */ bool immune = false; - /** Chance of burning per unit volume of the object, per turn. 0 for 100% chance */ - int chance_in_volume = 0; + /** If non-zero and lower than item's volume, scale burning by `volume_penalty / volume`. */ + units::volume volume_per_turn = 0_ml; /** Fuel contributed per tick when this material burns. */ float fuel = 0.0f; /** Smoke produced per tick when this material burns. */ diff --git a/src/item.cpp b/src/item.cpp index f228fe83dea74..a9a3645c8a8e3 100644 --- a/src/item.cpp +++ b/src/item.cpp @@ -5079,36 +5079,37 @@ bool item::reload( player &u, item_location loc, long qty ) return true; } -bool item::burn( fire_data &frd, bool contained) +float item::simulate_burn( fire_data &frd ) const { const auto &mats = made_of(); float smoke_added = 0.0f; float time_added = 0.0f; float burn_added = 0.0f; - const int vol = base_volume() / units::legacy_volume_factor; + const units::volume vol = base_volume(); + const int effective_intensity = frd.contained ? 3 : frd.fire_intensity; for( const auto &m : mats ) { - const auto &bd = m.obj().burn_data( frd.fire_intensity ); + const auto &bd = m.obj().burn_data( effective_intensity ); if( bd.immune ) { // Made to protect from fire return false; } - // If fire is contained, burn all of it continuously - if( bd.chance_in_volume == 0 || !contained ) { - time_added += bd.fuel; - smoke_added += bd.smoke; - burn_added += bd.burn; - - } else if( bd.chance_in_volume >= vol || x_in_y( bd.chance_in_volume, vol ) ){ + // If fire is contained, burn rate is independent of volume + if( frd.contained || bd.volume_per_turn == 0_ml ) { time_added += bd.fuel; smoke_added += bd.smoke; burn_added += bd.burn; + } else { + double volume_burn_rate = to_liter( bd.volume_per_turn ) / to_liter( vol ); + time_added += bd.fuel * volume_burn_rate; + smoke_added += bd.smoke * volume_burn_rate; + burn_added += bd.burn * volume_burn_rate; } } // Liquids that don't burn well smother fire well instead if( made_of( LIQUID ) && time_added < 200 ) { - time_added -= rng( 100 * vol, 300 * vol ); + time_added -= rng( 400.0 * to_liter( vol ), 1200.0 * to_liter( vol ) ); } else if( mats.size() > 1 ) { // Average the materials time_added /= mats.size(); @@ -5121,6 +5122,12 @@ bool item::burn( fire_data &frd, bool contained) frd.fuel_produced += time_added; frd.smoke_produced += smoke_added; + return burn_added; +} + +bool item::burn( fire_data &frd ) +{ + float burn_added = simulate_burn( frd ); if( burn_added <= 0 ) { return false; @@ -5152,6 +5159,7 @@ bool item::burn( fire_data &frd, bool contained) burnt += roll_remainder( burn_added ); + const int vol = base_volume() / units::legacy_volume_factor; return burnt >= vol * 3; } @@ -5164,7 +5172,7 @@ bool item::flammable( int threshold ) const } int flammability = 0; - int chance = 0; + units::volume volume_per_turn = 0; for( const auto &m : mats ) { const auto &bd = m->burn_data( 1 ); if( bd.immune ) { @@ -5173,20 +5181,20 @@ bool item::flammable( int threshold ) const } flammability += bd.fuel; - chance += bd.chance_in_volume; + volume_per_turn += bd.volume_per_turn; } if( threshold == 0 || flammability <= 0 ) { return flammability > 0; } - chance /= mats.size(); - int vol = base_volume() / units::legacy_volume_factor; - if( chance > 0 && chance < vol ) { - flammability = flammability * chance / vol; + volume_per_turn /= mats.size(); + units::volume vol = base_volume(); + if( volume_per_turn > 0 && volume_per_turn < vol ) { + flammability = flammability * volume_per_turn / vol; } else { // If it burns well, it provides a bonus here - flammability *= vol; + flammability *= vol / units::legacy_volume_factor; } return flammability > threshold; diff --git a/src/item.h b/src/item.h index b0b40841229e5..f41e43f393b1f 100644 --- a/src/item.h +++ b/src/item.h @@ -362,8 +362,14 @@ class item : public visitable std::string info(std::vector &dump, const iteminfo_query *parts = nullptr, int batch = 1) const; - /** Burns the item. Returns true if the item was destroyed. */ - bool burn( fire_data &bd, bool contained ); + /** + * Calculate all burning calculations, but don't actually apply them to item. + * DO apply them to @ref fire_data argument, though. + * @return Amount of "burn" that would be applied to the item. + */ + float simulate_burn( fire_data &bd ) const; + /** Burns the item. Returns true if the item was destroyed. */ + bool burn( fire_data &bd ); // Returns the category of this item. const item_category &get_category() const; diff --git a/src/material.cpp b/src/material.cpp index 755b8b0fb52f5..87ab0c1cda997 100644 --- a/src/material.cpp +++ b/src/material.cpp @@ -43,7 +43,7 @@ mat_burn_data load_mat_burn_data( JsonObject &jsobj ) { mat_burn_data bd; assign( jsobj, "immune", bd.immune ); - assign( jsobj, "chance", bd.chance_in_volume ); + assign( jsobj, "volume_penalty", bd.volume_per_turn ); jsobj.read( "fuel", bd.fuel ); jsobj.read( "smoke", bd.smoke ); jsobj.read( "burn", bd.burn ); diff --git a/src/player.cpp b/src/player.cpp index e931b936b73c0..9b50e8d9f31bc 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -9933,8 +9933,8 @@ void player::absorb_hit(body_part bp, damage_instance &dam) { // Even though it doesn't cause direct physical damage to it if( outermost && elem.type == DT_HEAT && elem.amount >= 1.0f ) { // @todo: Different fire intensity values based on damage - fire_data frd{ 2, 0.0f, 0.0f }; - destroy = armor.burn( frd, true ); + fire_data frd{ 2 }; + destroy = armor.burn( frd ); int fuel = roll_remainder( frd.fuel_produced ); if( fuel > 0 ) { add_effect( effect_onfire, time_duration::from_turns( fuel + 1 ), bp, false, 0, false, true ); diff --git a/src/player_activity.cpp b/src/player_activity.cpp index 7703864ce3fdf..78a7c5b203c61 100644 --- a/src/player_activity.cpp +++ b/src/player_activity.cpp @@ -96,6 +96,11 @@ std::string player_activity::get_str_value( size_t index, std::string def ) cons void player_activity::do_turn( player &p ) { + // Should happen before activity or it may fail du to 0 moves + if( *this && type->will_refuel_fires() ) { + try_refuel_fire( p ); + } + if( type->based_on() == based_on_type::TIME ) { moves_left -= 100; } else if( type->based_on() == based_on_type::SPEED ) { From 3bcce39f45297842c7824926fb340d4cc6dd7af0 Mon Sep 17 00:00:00 2001 From: Coolthulhu Date: Thu, 28 Jun 2018 19:31:54 +0200 Subject: [PATCH 2/2] Allow refueling distant fires, rebalance refueling --- src/activity_item_handling.cpp | 47 +++++++++++++++------------------- 1 file changed, 20 insertions(+), 27 deletions(-) diff --git a/src/activity_item_handling.cpp b/src/activity_item_handling.cpp index 121ec7bc2ef05..af8e437462d29 100644 --- a/src/activity_item_handling.cpp +++ b/src/activity_item_handling.cpp @@ -649,13 +649,14 @@ void activity_on_turn_move_items() } } -cata::optional find_best_fire( const std::vector &from ) +cata::optional find_best_fire( const std::vector &from, const tripoint ¢er ) { cata::optional best_fire; time_duration best_fire_age = 1_days; for( const tripoint &pt : from ) { field_entry *fire = g->m.get_field( pt, fd_fire ); - if( fire == nullptr || fire->getFieldDensity() > 1 ) { + if( fire == nullptr || fire->getFieldDensity() > 1 || + !g->m.clear_path( center, pt, PICKUP_RANGE, 1, 100 ) ) { continue; } time_duration fire_age = fire->getFieldAge(); @@ -675,20 +676,21 @@ cata::optional find_best_fire( const std::vector &from ) void try_refuel_fire( player &p ) { - auto adjacent = closest_tripoints_first( 1, p.pos() ); + const tripoint pos = p.pos(); + auto adjacent = closest_tripoints_first( PICKUP_RANGE, pos ); adjacent.erase( adjacent.begin() ); - cata::optional best_fire = find_best_fire( adjacent ); + cata::optional best_fire = find_best_fire( adjacent, pos ); if( !best_fire || !g->m.accessible_items( *best_fire ) ) { return; } const auto refuel_spot = std::find_if( adjacent.begin(), adjacent.end(), - []( const tripoint & pt ) { + [pos]( const tripoint & pt ) { // Hacky - firewood spot is a trap and it's ID-checked // @todo Something cleaner than ID-checking a trap return g->m.tr_at( pt ).id == tr_firewood_source && g->m.has_items( pt ) && - g->m.accessible_items( pt ); + g->m.accessible_items( pt ) && g->m.clear_path( pos, pt, PICKUP_RANGE, 1, 100 ); } ); if( refuel_spot == adjacent.end() ) { return; @@ -697,34 +699,32 @@ void try_refuel_fire( player &p ) // Special case: fire containers allow burning logs, so use them as fuel iif fire is contained bool contained = g->m.has_flag_furn( TFLAG_FIRE_CONTAINER, *best_fire ); fire_data fd( 1, contained ); - time_duration fire_age = g->m.get_field_age( *best_fire, fd_fire ); + // Maybe @todo - refuelling in the rain could use more fuel // First, simulate expected burn per turn, to see if we need more fuel - const float low_cap = fire_age / 10_minutes; - // High cap is 10 fuel/turn for 10 minute old fire, 1 fuel/turn for 0 old - const float high_cap = 10.0f - 9.0f * ( 10_minutes - fire_age ) / 10_minutes; auto fuel_on_fire = g->m.i_at( *best_fire ); for( size_t i = 0; i < fuel_on_fire.size(); i++ ) { fuel_on_fire[i].simulate_burn( fd ); - if( fd.fuel_produced > high_cap && !fuel_on_fire[i].made_of( LIQUID ) ) { + // Uncontained fires grow below -50_minutes age + if( !contained && fire_age < -40_minutes && fd.fuel_produced > 1.0f && + !fuel_on_fire[i].made_of( LIQUID ) ) { // Too much - we don't want a firestorm! // Put first item back to refuelling pile std::list indices_to_remove{ static_cast( i ) }; std::list quantities_to_remove{ 0 }; - move_items( *best_fire - p.pos(), false, *refuel_spot - p.pos(), false, indices_to_remove, + move_items( *best_fire - pos, false, *refuel_spot - pos, false, indices_to_remove, quantities_to_remove ); return; } } // Enough to sustain the fire - if( fd.fuel_produced >= low_cap ) { + // @todo It's not enough in the rain + if( fd.fuel_produced >= 1.0f || fire_age < 10_minutes ) { return; } - std::list indices; - std::list quantities; // We need to move fuel from stash to fire auto potential_fuel = g->m.i_at( *refuel_spot ); for( size_t i = 0; i < potential_fuel.size(); i++ ) { @@ -732,21 +732,14 @@ void try_refuel_fire( player &p ) continue; } - // item::flammable isn't good enough - we need "fuelable" float last_fuel = fd.fuel_produced; potential_fuel[i].simulate_burn( fd ); if( fd.fuel_produced > last_fuel ) { - indices.push_back( static_cast( i ) ); - quantities.push_back( 0 ); - } - // After we move those items, we should have enough - if( fd.fuel_produced > low_cap ) { - break; + std::list indices{ static_cast( i ) }; + std::list quantities{ 0 }; + // Note: move_items handles messages (they're the generic "you drop x") + move_items( *refuel_spot - p.pos(), false, *best_fire - p.pos(), false, indices, quantities ); + return; } } - - if( !indices.empty() ) { - // Note: move_items handles messages (they're the generic "you drop x") - move_items( *refuel_spot - p.pos(), false, *best_fire - p.pos(), false, indices, quantities ); - } }