diff --git a/data/json/items/generic.json b/data/json/items/generic.json index 8ac168f0eb8d4..b44fef2d75389 100644 --- a/data/json/items/generic.json +++ b/data/json/items/generic.json @@ -57,6 +57,21 @@ "flags": [ "PSEUDO", "PERPETUAL" ], "fuel": { "energy": 1 } }, + { + "type": "GENERIC", + "//": "pseudo item, used as fuel type for CBMs that are sun-powered", + "id": "sunlight", + "symbol": "?", + "color": "white", + "name": "sun light", + "name_plural": "none", + "description": "seeing this is a bug", + "stackable": true, + "price": 0, + "volume": 0, + "flags": [ "PSEUDO", "PERPETUAL" ], + "fuel": { "energy": 1 } + }, { "type": "GENERIC", "//": "pseudo item, used as fuel type for CBMs that are metabolism-powered", diff --git a/data/mods/Aftershock/items/afs_cbms.json b/data/mods/Aftershock/items/afs_cbms.json index c88da7db978d1..e15ab4e50818a 100644 --- a/data/mods/Aftershock/items/afs_cbms.json +++ b/data/mods/Aftershock/items/afs_cbms.json @@ -8,6 +8,15 @@ "price": 350000, "difficulty": 4 }, + { + "id": "afs_bio_wind_turbine", + "copy-from": "bionic_general", + "type": "BIONIC_ITEM", + "name": "Wind Turbine CBM", + "description": "Installed on your body is a set of small retractable wind turbines. When activated, they will deploy and slowly harvest wind power to recharge your power level.", + "price": 350000, + "difficulty": 4 + }, { "id": "afs_bio_precision_solderers", "copy-from": "bionic_general", diff --git a/data/mods/Aftershock/maps/afs_item_groups.json b/data/mods/Aftershock/maps/afs_item_groups.json index 72ecabacffbc9..3caff3d06f308 100644 --- a/data/mods/Aftershock/maps/afs_item_groups.json +++ b/data/mods/Aftershock/maps/afs_item_groups.json @@ -29,6 +29,7 @@ "items": [ [ "bio_furnace", 10 ], [ "bn_bio_solar", 10 ], + [ "afs_bio_wind_turbine", 5 ], [ "afs_bio_precision_solderers", 10 ], [ "afs_bio_missiles", 10 ], [ "afs_bio_linguistic_coprocessor", 10 ], @@ -42,6 +43,7 @@ "items": [ [ "bio_furnace", 10 ], [ "bn_bio_solar", 10 ], + [ "afs_bio_wind_turbine", 5 ], [ "afs_bio_precision_solderers", 5 ], [ "afs_bio_linguistic_coprocessor", 8 ] ] @@ -57,6 +59,7 @@ "items": [ [ "bio_furnace", 5 ], [ "bn_bio_solar", 5 ], + [ "afs_bio_wind_turbine", 2 ], [ "afs_bio_precision_solderers", 7 ], [ "afs_bio_linguistic_coprocessor", 5 ] ] @@ -64,7 +67,13 @@ { "id": "bionics_op", "type": "item_group", - "items": [ [ "bio_furnace", 15 ], [ "bn_bio_solar", 15 ], [ "afs_bio_missiles", 10 ], [ "afs_bio_dopamine_stimulators", 10 ] ] + "items": [ + [ "bio_furnace", 15 ], + [ "bn_bio_solar", 15 ], + [ "afs_bio_wind_turbine", 10 ], + [ "afs_bio_missiles", 10 ], + [ "afs_bio_dopamine_stimulators", 10 ] + ] }, { "id": "bionics_subs", diff --git a/data/mods/Aftershock/player/afs_bionics.json b/data/mods/Aftershock/player/afs_bionics.json index 1d9e4eb1b0882..17e0bc54e1fc7 100644 --- a/data/mods/Aftershock/player/afs_bionics.json +++ b/data/mods/Aftershock/player/afs_bionics.json @@ -1,12 +1,25 @@ [ { "id": "bn_bio_solar", - "//": "That's right, it's taken from Bright Nights. Sue me!", "type": "bionic", "name": "Solar Panels", "description": "Installed on your back is a set of retractable, reinforced solar panels resembling angular butterfly wings. When in direct sunlight, they will automatically deploy and slowly recharge your power level.", "occupied_bodyparts": [ [ "TORSO", 10 ] ], - "flags": [ "BIONIC_POWER_SOURCE" ] + "fuel_options": [ "sunlight" ], + "fuel_efficiency": 1.0, + "time": 1, + "flags": [ "BIONIC_POWER_SOURCE", "BIONIC_TOGGLED" ] + }, + { + "id": "afs_bio_wind_turbine", + "type": "bionic", + "name": "Wind Turbines", + "description": "Installed on your body is a set of small retractable wind turbines. When activated, they will deploy and slowly harvest wind power to recharge your power level.", + "occupied_bodyparts": [ [ "TORSO", 10 ] ], + "fuel_options": [ "wind" ], + "fuel_efficiency": 0.25, + "time": 1, + "flags": [ "BIONIC_POWER_SOURCE", "BIONIC_TOGGLED" ] }, { "id": "afs_bio_precision_solderers", diff --git a/doc/JSON_INFO.md b/doc/JSON_INFO.md index 6a0115d3504b8..5f8dc9b13df7a 100644 --- a/doc/JSON_INFO.md +++ b/doc/JSON_INFO.md @@ -353,34 +353,35 @@ This section describes each json file and their contents. Each json has their ow ### Bionics -| Identifier | Description -|--- |--- -| id | Unique ID. Must be one continuous word, use underscores if necessary. -| name | In-game name displayed. -| active | Whether the bionic is active or passive. (default: `passive`) -| power_source | Whether the bionic provides power. (default: `false`) -| faulty | Whether it is a faulty type. (default: `false`) -| act_cost | How many kJ it costs to activate the bionic. Strings can be used "1 kJ"/"1000 J"/"1000000 mJ" (default: `0`) -| deact_cost | How many kJ it costs to deactivate the bionic. Strings can be used "1 kJ"/"1000 J"/"1000000 mJ" (default: `0`) -| react_cost | How many kJ it costs over time to keep this bionic active, does nothing without a non-zero "time". Strings can be used "1 kJ"/"1000 J"/"1000000 mJ" (default: `0`) -| time | How long, when activated, between drawing cost. If 0, it draws power once. (default: `0`) -| description | In-game description. -| encumbrance | (_optional_) A list of body parts and how much this bionic encumber them. -| weight_capacity_bonus | (_optional_) Bonus to weight carrying capacity in grams, can be negative. Strings can be used - "5000 g" or "5 kg" (default: `0`) -| weight_capacity_modifier | (_optional_) Factor modifying base weight carrying capacity. (default: `1`) -| canceled_mutations | (_optional_) A list of mutations/traits that are removed when this bionic is installed (e.g. because it replaces the fault biological part). -| included_bionics | (_optional_) Additional bionics that are installed automatically when this bionic is installed. This can be used to install several bionics from one CBM item, which is useful as each of those can be activated independently. -| included | (_optional_) Whether this bionic is included with another. If true this bionic does not require a CBM item to be defined. (default: `false`) -| env_protec | (_optional_) How much environmental protection does this bionic provide on the specified body parts. -| occupied_bodyparts | (_optional_) A list of body parts occupied by this bionic, and the number of bionic slots it take on those parts. -| capacity | (_optional_) Amount of power storage added by this bionic. Strings can be used "1 kJ"/"1000 J"/"1000000 mJ" (default: `0`) -| fuel_options | (_optional_) A list of fuel that this bionic can use to produce bionic power. -| fuel_capacity | (_optional_) Volume of fuel this bionic can store. -| fuel_efficiency | (_optional_) Fraction of fuel energy converted into power. (default: `0`) -| passive_fuel_efficiency | (_optional_) Fraction of fuel energy passively converted into power. Only useful for muscle powered CBM (default: `0`) -| exothermic_power_gen | (_optional_) If true this bionic emits heat when producing power. (default: `false`) -| power_gen_emission | (_optional_) `emit_id` of the field emitted by this bionic when it produces energy. Emit_ids are defined in `emit.json`. -| stat_bonus | (_optional_) List of passive stat bonus. Stat are designated as follow: "DEX", "INT", "STR", "PER". +| Identifier | Description +|--- |--- +| id | Unique ID. Must be one continuous word, use underscores if necessary. +| name | In-game name displayed. +| active | Whether the bionic is active or passive. (default: `passive`) +| power_source | Whether the bionic provides power. (default: `false`) +| faulty | Whether it is a faulty type. (default: `false`) +| act_cost | How many kJ it costs to activate the bionic. Strings can be used "1 kJ"/"1000 J"/"1000000 mJ" (default: `0`) +| deact_cost | How many kJ it costs to deactivate the bionic. Strings can be used "1 kJ"/"1000 J"/"1000000 mJ" (default: `0`) +| react_cost | How many kJ it costs over time to keep this bionic active, does nothing without a non-zero "time". Strings can be used "1 kJ"/"1000 J"/"1000000 mJ" (default: `0`) +| time | How long, when activated, between drawing cost. If 0, it draws power once. (default: `0`) +| description | In-game description. +| encumbrance | (_optional_) A list of body parts and how much this bionic encumber them. +| weight_capacity_bonus | (_optional_) Bonus to weight carrying capacity in grams, can be negative. Strings can be used - "5000 g" or "5 kg" (default: `0`) +| weight_capacity_modifier | (_optional_) Factor modifying base weight carrying capacity. (default: `1`) +| canceled_mutations | (_optional_) A list of mutations/traits that are removed when this bionic is installed (e.g. because it replaces the fault biological part). +| included_bionics | (_optional_) Additional bionics that are installed automatically when this bionic is installed. This can be used to install several bionics from one CBM item, which is useful as each of those can be activated independently. +| included | (_optional_) Whether this bionic is included with another. If true this bionic does not require a CBM item to be defined. (default: `false`) +| env_protec | (_optional_) How much environmental protection does this bionic provide on the specified body parts. +| occupied_bodyparts | (_optional_) A list of body parts occupied by this bionic, and the number of bionic slots it take on those parts. +| capacity | (_optional_) Amount of power storage added by this bionic. Strings can be used "1 kJ"/"1000 J"/"1000000 mJ" (default: `0`) +| fuel_options | (_optional_) A list of fuel that this bionic can use to produce bionic power. +| fuel_capacity | (_optional_) Volume of fuel this bionic can store. +| fuel_efficiency | (_optional_) Fraction of fuel energy converted into power. (default: `0`) +| passive_fuel_efficiency | (_optional_) Fraction of fuel energy passively converted into power. Useful for CBM using PERPETUAL fuel like `muscle`, `wind` or `sun_light`. (default: `0`) +| exothermic_power_gen | (_optional_) If true this bionic emits heat when producing power. (default: `false`) +| coverage_power_gen_penalty | (_optional_) Fraction of coverage diminishing fuel_efficiency. Float between 0.0 and 1.0. (default: `nullopt`) +| power_gen_emission | (_optional_) `emit_id` of the field emitted by this bionic when it produces energy. Emit_ids are defined in `emit.json`. +| stat_bonus | (_optional_) List of passive stat bonus. Stat are designated as follow: "DEX", "INT", "STR", "PER". ```C++ { diff --git a/src/bionics.cpp b/src/bionics.cpp index f273797c0bac9..839567001798c 100644 --- a/src/bionics.cpp +++ b/src/bionics.cpp @@ -845,8 +845,8 @@ bool Character::burn_fuel( int b, bool start ) return true; } const bool is_metabolism_powered = bio.is_this_fuel_powered( "metabolism" ); - const std::vector fuel_available = get_fuel_available( bio.id ); - const float fuel_efficiency = bio.info().fuel_efficiency; + const std::vector &fuel_available = get_fuel_available( bio.id ); + const float effective_efficiency = get_effective_efficiency( b, bio.info().fuel_efficiency ); if( start && fuel_available.empty() ) { add_msg_player_or_npc( m_bad, _( "Your %s does not have enought fuel to start." ), @@ -859,22 +859,36 @@ bool Character::burn_fuel( int b, bool start ) //in the menu if( !start ) { for( const itype_id &fuel : fuel_available ) { - const int fuel_energy = item( fuel ).fuel_energy(); + const item &tmp_fuel = item( fuel ); + const int fuel_energy = tmp_fuel.fuel_energy(); + const bool is_perpetual_fuel = tmp_fuel.has_flag( "PERPETUAL" ); int current_fuel_stock; if( is_metabolism_powered ) { current_fuel_stock = std::max( 0.0f, get_stored_kcal() - 0.8f * get_healthy_kcal() ); + } else if( is_perpetual_fuel ) { + current_fuel_stock = 1; } else { current_fuel_stock = std::stoi( get_value( fuel ) ); } if( !bio.has_flag( "SAFE_FUEL_OFF" ) && - get_power_level() + units::from_kilojoule( fuel_energy ) * fuel_efficiency + get_power_level() + units::from_kilojoule( fuel_energy ) * effective_efficiency > get_max_power_level() ) { - add_msg_player_or_npc( m_info, _( "Your %s turns off to not waste fuel." ), - _( "'s %s turns off to not waste fuel." ), - bio.info().name ); + if( is_metabolism_powered ) { + add_msg_player_or_npc( m_info, _( "Your %s turns off to not waste calories." ), + _( "'s %s turns off to not waste calories." ), + bio.info().name ); + } else if( is_perpetual_fuel ) { + add_msg_player_or_npc( m_info, _( "Your %s turns off after filling your power banks." ), + _( "'s %s turns off after filling their power banks." ), + bio.info().name ); + } else { + add_msg_player_or_npc( m_info, _( "Your %s turns off to not waste fuel." ), + _( "'s %s turns off to not waste fuel." ), + bio.info().name ); + } bio.powered = false; deactivate_bionic( b, true ); return false; @@ -884,26 +898,32 @@ bool Character::burn_fuel( int b, bool start ) if( is_metabolism_powered ) { const int kcal_consumed = fuel_energy; // 1kcal = 4187 J - const units::energy power_gain = kcal_consumed * 4184_J * fuel_efficiency; + const units::energy power_gain = kcal_consumed * 4184_J * effective_efficiency; mod_stored_kcal( -kcal_consumed ); mod_power_level( power_gain ); + } else if( is_perpetual_fuel ) { + if( fuel == itype_id( "sunlight" ) ) { + const double modifier = g->natural_light_level( pos().z ) / default_daylight_level(); + mod_power_level( units::from_kilojoule( fuel_energy ) * modifier * effective_efficiency ); + } else if( fuel == itype_id( "wind" ) ) { + int vehwindspeed = 0; + const optional_vpart_position vp = g->m.veh_at( pos() ); + if( vp ) { + vehwindspeed = abs( vp->vehicle().velocity / 100 ); // vehicle velocity in mph + } + const double windpower = get_local_windpower( g->weather.windspeed + vehwindspeed, + overmap_buffer.ter( global_omt_location() ), pos(), g->weather.winddirection, + g->is_sheltered( pos() ) ); + mod_power_level( units::from_kilojoule( fuel_energy ) * windpower * effective_efficiency ); + } } else { current_fuel_stock -= 1; set_value( fuel, std::to_string( current_fuel_stock ) ); update_fuel_storage( fuel ); - mod_power_level( units::from_kilojoule( fuel_energy ) * fuel_efficiency ); + mod_power_level( units::from_kilojoule( fuel_energy ) * effective_efficiency ); } - if( bio.info().exothermic_power_gen ) { - const int heat_prod = fuel_energy * ( 1 - fuel_efficiency ); - const int heat_level = std::min( heat_prod / 10, 4 ); - const int heat_spread = std::max( heat_prod / 10 - heat_level, 1 ); - const emit_id hotness = emit_id( "emit_hot_air" + to_string( heat_level ) + "_cbm" ); - g->m.emit_field( pos(), hotness, heat_spread ); - for( const auto bp : bio.info().occupied_bodyparts ) { - add_effect( efftype_id( "heating_bionic" ), 2_seconds, bp.first, false, heat_prod ); - } - } + heat_emission( b, fuel_energy ); g->m.emit_field( pos(), bio.info().power_gen_emission ); } else { @@ -930,6 +950,89 @@ bool Character::burn_fuel( int b, bool start ) return true; } +void Character::passive_power_gen( int b ) +{ + const bionic &bio = ( *my_bionics )[b]; + const float passive_fuel_efficiency = bio.info().passive_fuel_efficiency; + if( bio.info().fuel_opts.empty() || bio.is_this_fuel_powered( "muscle" ) || + passive_fuel_efficiency == 0.0 ) { + return; + } + const float effective_passive_efficiency = get_effective_efficiency( b, passive_fuel_efficiency ); + const std::vector &fuel_available = get_fuel_available( bio.id ); + + for( const itype_id &fuel : fuel_available ) { + const item &tmp_fuel = item( fuel ); + const int fuel_energy = tmp_fuel.fuel_energy(); + if( !tmp_fuel.has_flag( "PERPETUAL" ) ) { + continue; + } + + if( fuel == itype_id( "sunlight" ) ) { + const double modifier = g->natural_light_level( pos().z ) / default_daylight_level(); + mod_power_level( units::from_kilojoule( fuel_energy ) * modifier * effective_passive_efficiency ); + } else if( fuel == itype_id( "wind" ) ) { + int vehwindspeed = 0; + const optional_vpart_position vp = g->m.veh_at( pos() ); + if( vp ) { + vehwindspeed = abs( vp->vehicle().velocity / 100 ); // vehicle velocity in mph + } + const double windpower = get_local_windpower( g->weather.windspeed + vehwindspeed, + overmap_buffer.ter( global_omt_location() ), pos(), g->weather.winddirection, + g->is_sheltered( pos() ) ); + mod_power_level( units::from_kilojoule( fuel_energy ) * windpower * effective_passive_efficiency ); + } else { + mod_power_level( units::from_kilojoule( fuel_energy ) * effective_passive_efficiency ); + } + + heat_emission( b, fuel_energy ); + g->m.emit_field( pos(), bio.info().power_gen_emission ); + + } +} + +void Character::heat_emission( int b, int fuel_energy ) +{ + const bionic &bio = ( *my_bionics )[b]; + if( !bio.info().exothermic_power_gen ) { + return; + } + const float efficiency = bio.info().fuel_efficiency; + + const int &heat_prod = fuel_energy * ( 1 - efficiency ); + const int &heat_level = std::min( heat_prod / 10, 4 ); + const int &heat_spread = std::max( heat_prod / 10 - heat_level, 1 ); + const emit_id hotness = emit_id( "emit_hot_air" + to_string( heat_level ) + "_cbm" ); + g->m.emit_field( pos(), hotness, heat_spread ); + for( const auto bp : bio.info().occupied_bodyparts ) { + add_effect( efftype_id( "heating_bionic" ), 2_seconds, bp.first, false, heat_prod ); + } +} + +float Character::get_effective_efficiency( int b, float fuel_efficiency ) +{ + const bionic &bio = ( *my_bionics )[b]; + const cata::optional &coverage_penalty = bio.info().coverage_power_gen_penalty; + float effective_efficiency = fuel_efficiency; + if( coverage_penalty ) { + int coverage = 0; + const std::map< body_part, size_t > &occupied_bodyparts = bio.info().occupied_bodyparts; + for( const std::pair< body_part, size_t > &elem : occupied_bodyparts ) { + for( const item &i : worn ) { + if( i.covers( elem.first ) && !i.has_flag( "ALLOWS_NATURAL_ATTACKS" ) && + !i.has_flag( "SEMITANGIBLE" ) && + !i.has_flag( "PERSONAL" ) && !i.has_flag( "AURA" ) ) { + coverage += i.get_coverage(); + } + } + } + effective_efficiency = fuel_efficiency * ( 1.0 - ( coverage / ( 100.0 * + occupied_bodyparts.size() ) ) + * coverage_penalty.value() ); + } + return effective_efficiency; +} + /** * @param p the player * @param bio the bionic that is meant to be recharged. @@ -974,6 +1077,7 @@ void Character::process_bionic( int b ) bionic &bio = ( *my_bionics )[b]; // Only powered bionics should be processed if( !bio.powered ) { + passive_power_gen( b ); return; } @@ -2187,6 +2291,7 @@ void load_bionic( JsonObject &jsobj ) assign( jsobj, "weight_capacity_bonus", new_bionic.weight_capacity_bonus, false, 0_gram ); assign( jsobj, "exothermic_power_gen", new_bionic.exothermic_power_gen ); assign( jsobj, "power_gen_emission", new_bionic.power_gen_emission ); + assign( jsobj, "coverage_power_gen_penalty", new_bionic.coverage_power_gen_penalty ); jsobj.read( "canceled_mutations", new_bionic.canceled_mutations ); jsobj.read( "included_bionics", new_bionic.included_bionics ); diff --git a/src/bionics.h b/src/bionics.h index 0a7b8297f06f0..db3ffa3b6cb93 100644 --- a/src/bionics.h +++ b/src/bionics.h @@ -91,6 +91,8 @@ struct bionic_data { float fuel_efficiency; /**Fraction of fuel energy passively converted to bionic power*/ float passive_fuel_efficiency; + /**Fraction of coverage diminishing fuel_efficiency*/ + cata::optional coverage_power_gen_penalty; /**If true this bionic emits heat when producing power*/ bool exothermic_power_gen = false; /**Type of field emitted by this bionic when it produces energy*/ diff --git a/src/character.h b/src/character.h index 413c76773f8aa..a96c0917a668a 100644 --- a/src/character.h +++ b/src/character.h @@ -787,6 +787,12 @@ class Character : public Creature, public visitable virtual bool deactivate_bionic( int b, bool eff_only = false ); /**Convert fuel to bionic power*/ bool burn_fuel( int b, bool start = false ); + /**Passively produce power from PERPETUAL fuel*/ + void passive_power_gen( int b ); + /**Handle heat from exothermic power generation*/ + void heat_emission( int b, int fuel_energy ); + /**Applies modifier to fuel_efficiency and returns the resulting efficiency*/ + float get_effective_efficiency( int b, float fuel_efficiency ); units::energy get_power_level() const; units::energy get_max_power_level() const; diff --git a/src/player.cpp b/src/player.cpp index bbd9d08718982..3e89b22a2894e 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -2871,12 +2871,6 @@ void player::update_needs( int rate_multiplier ) mod_painkiller( -std::min( get_painkiller(), rate_multiplier ) ); } - if( g->is_in_sunlight( pos() ) ) { - if( has_bionic( bn_bio_solar ) ) { - mod_power_level( units::from_kilojoule( rate_multiplier * 25 ) ); - } - } - // Huge folks take penalties for cramming themselves in vehicles if( in_vehicle && ( has_trait( trait_HUGE ) || has_trait( trait_HUGE_OK ) ) ) { vehicle *veh = veh_pointer_or_null( g->m.veh_at( pos() ) );