From 3cb401594a474a88008e4a12ad4a9bb85fa87d96 Mon Sep 17 00:00:00 2001 From: Eric <52087122+Ramza13@users.noreply.github.com> Date: Tue, 14 Jul 2020 03:21:51 -0400 Subject: [PATCH] Add json controlled weather effects (#41905) * Move more weather pieces into weather datum and json * Move existing effects into new framework * Add radiation, health, effects, and spawns * Add translations, damage and target parts * Add traits to weather effects --- data/json/weather_type.json | 51 ++++++- doc/WEATHER_TYPE.md | 108 +++++++++++++-- src/game.cpp | 30 ++++- src/game.h | 5 + src/weather.cpp | 257 +++++++++++++++++++++--------------- src/weather.h | 16 +-- src/weather_type.cpp | 90 ++++++++++--- src/weather_type.h | 47 ++++++- 8 files changed, 451 insertions(+), 153 deletions(-) diff --git a/data/json/weather_type.json b/data/json/weather_type.json index 468798782d984..72e79533d3b44 100644 --- a/data/json/weather_type.json +++ b/data/json/weather_type.json @@ -152,7 +152,14 @@ "precip": "heavy", "rains": true, "acidic": false, - "effects": [ { "name": "thunder", "intensity": 50 } ], + "effects": [ + { + "one_in_chance": 50, + "must_be_outside": false, + "sound_message": "You hear a distant rumble of thunder.", + "sound_effect": "thunder_far" + } + ], "tiles_animation": "weather_rain_drop", "weather_animation": { "factor": 0.02, "color": "c_light_blue", "glyph": "." }, "sound_category": "thunder", @@ -174,7 +181,21 @@ "precip": "heavy", "rains": true, "acidic": false, - "effects": [ { "name": "thunder", "intensity": 50 }, { "name": "lightning", "intensity": 600 } ], + "effects": [ + { + "one_in_chance": 50, + "must_be_outside": false, + "sound_message": "You hear a distant rumble of thunder.", + "sound_effect": "thunder_far" + }, + { + "one_in_chance": 600, + "must_be_outside": false, + "message": "A flash of lightning illuminates your surroundings!.", + "sound_effect": "thunder_near", + "lightning": true + } + ], "tiles_animation": "weather_rain_drop", "weather_animation": { "factor": 0.04, "color": "c_light_blue", "glyph": "," }, "sound_category": "thunder", @@ -196,7 +217,16 @@ "precip": "light", "rains": true, "acidic": true, - "effects": [ { "name": "light_acid", "intensity": 180 } ], + "effects": [ + { + "time_between": "3 minutes", + "must_be_outside": true, + "message": "The acid rain stings, but is mostly harmless for now…", + "rain_proof": true, + "pain_max": 10, + "pain": 1 + } + ], "tiles_animation": "weather_acid_drop", "weather_animation": { "factor": 0.01, "color": "c_light_green", "glyph": "." }, "sound_category": "drizzle", @@ -218,7 +248,16 @@ "precip": "heavy", "rains": true, "acidic": true, - "effects": [ { "name": "acid", "intensity": 2 } ], + "effects": [ + { + "time_between": "2 seconds", + "must_be_outside": true, + "message": "The acid rain burns!", + "rain_proof": true, + "pain_max": 100, + "pain": 3 + } + ], "tiles_animation": "weather_acid_drop", "weather_animation": { "factor": 0.02, "color": "c_light_green", "glyph": "," }, "sound_category": "rainy", @@ -261,7 +300,7 @@ "precip": "heavy", "rains": false, "acidic": false, - "effects": [ { "name": "wet", "intensity": 10 } ], + "effects": [ { "must_be_outside": true, "wet": 10 } ], "tiles_animation": "weather_snowflake", "weather_animation": { "factor": 0.02, "color": "c_white", "glyph": "," }, "sound_category": "snow", @@ -283,7 +322,7 @@ "precip": "heavy", "rains": false, "acidic": false, - "effects": [ { "name": "wet", "intensity": 40 } ], + "effects": [ { "must_be_outside": true, "wet": 40 } ], "tiles_animation": "weather_snowflake", "weather_animation": { "factor": 0.04, "color": "c_white", "glyph": "*" }, "sound_category": "snowstorm", diff --git a/doc/WEATHER_TYPE.md b/doc/WEATHER_TYPE.md index 1cb25b36fad84..84af9e821ac1c 100644 --- a/doc/WEATHER_TYPE.md +++ b/doc/WEATHER_TYPE.md @@ -24,13 +24,7 @@ Each weather type is a type of weather that occurs, its effects and what causes snowstorm and snow. | | `sun_intensity` | Strength of the sun. Valid values are: none, light, normal, and high | | `weather_animation` | Optional, Information controlling weather animations. Members: factor, color and glyph | -| `effects` | String, int pair array for the effects the weather has. At present wet, thunder, lightning, light_acid, and acid are supported. | - `wet` | wets player by int amount - `thunder` | thunder sound with chance 1 in int - `lightening` | 1 in int chance of sound plus message and possible super charging electric fields - `light_acid` | causes pain unless waterproof - `acid_rain` | causes more pain unless waterproof - +| `effects` | Array for the effects the weather has. Descibed in detail below | `requirements` | Optional, is what determines what weather it is. All members are optional. When checking what weather it is it loops through the entries in order and uses the last one to succeed. | @@ -67,11 +61,107 @@ Each weather type is a type of weather that occurs, its effects and what causes "precip": "heavy", "rains": true, "acidic": false, - "effects": [ { "name": "thunder", "intensity": 50 }, { "name": "lightning", "intensity": 600 } ], + "effects": [ + { + "one_in_chance": 50, + "must_be_outside":false, + "sound_message": "You hear a distant rumble of thunder.", + "sound_effect": "thunder_far" + }, + { + "one_in_chance": 600, + "must_be_outside":false, + "message": "A flash of lightning illuminates your surroundings!.", + "sound_effect": "thunder_near", + "lightning":true + } + ], "tiles_animation": "weather_rain_drop", "weather_animation": { "factor": 0.04, "color": "c_light_blue", "glyph": "," }, "sound_category": "thunder", "sun_intensity": "none", "requirements": { "pressure_max": 990, "required_weathers": [ "thunder" ] } -} + }, ``` +### Weather_effects + +Things that weather can cause to happen. + +##Fields + +| Identifier | Description | +| ------------------------------ | --------------------------------------------------------------------- | +| `message` | Optional: Message displayed when this effect happens. | +| `sound_message` | Optional: Message describing what you hear, will not display if deaf | +| `sound_effect` | Optional: Name of sound effect to play | +| `sound_message` | Optional: Message describing what you hear for this, will not display if deaf. | +| `must_be_outside` | Whether the effect only happens while you are outside. | +| `one_in_chance` | Optional: The chance of the event occuring is 1 in this value, if blank will always happen. | +| `time_between` | Optional: The time between instances of this effect occuring. If both this and one_in_chance are set will only happen when both are true. | +| `lightning` | Optional: Causes the world be bright at night and supercharge monster electric fields. | +| `rain_proof` | Optional: If rainproof, resistant gear will help against this | +| `pain_max` | Optional: If there is a threshold of pain at which this will stop happening. | +| `pain` | Optional: How much pain this causes. | +| `wet` | Optional: How much wet this causes. | +| `radiation` | Optional: How much radiation this causes. | +| `healthy` | Optional: How much healthy this adds or removes. | +| `effect_id` | Optional: String id of an effect to add. | +| `effect_duration` | Optional: How long the above effect will be added for, defaults to 1 second. | +| `target_part` | Optional: Bodypart that above effect or damage are applied to, if blank affects whole body. | +| `damage` | Optional: Hp bashing damage applied. | +| `spawns` | Optional: Array of spawns to cause. If spawns are selected but are unable to spawn the effect is cancelled. | +| `fields` | Optional: Array of fields to cause. Elements are discussed below | + + optional( weather_effect_jo, was_loaded, "", effect.lightning, false ); + ### Example + +```json + { + "must_be_outside":true, + "radiation":10, + "healthy":1, + "message":"Suddenly a something", + "add_effect":"bite", + "effect_duration":"10 minutes", + "target_part": "arm_l", + "damage":5, + "spawns": + [{ + "max_radius":10, + "min_radius":10, + "target":"mon_zombie_survivor_elite", + "hallucination_count":1, + "real_count":0 +}] +``` + ### spawn_type + +How many and what spawns + +##Fields + +| Identifier | Description | +| ------------------------------ | --------------------------------------------------------------------- | +| `max_radius` | Optional: The furthest away a spawn will happen. | +| `min_radius` | Optional: The closest a spawn will happen. | +| `hallucination_count` | Optional: Number of hallucinations of the target to spawn. | +| `real_count` | Optional: Number of real copies to spawn. | +| `target` | Optional: Monster id of target to spawn. If left blank a nearby monster will be used. | +| `target_range` | Optional: If target is left blank how far away to look for something to copy. | + + ### fields + +Fields to create what kind and where + +##Fields + +| Identifier | Description | +| ------------------------------ | --------------------------------------------------------------------- | +| `type` | The string id of the field. | +| `intensity` | Intensity of the field. | +| `age` | Age of the field. | +| `outdoor_only` | Optional: Defaults to true. If true field will only spawn outdoors. | +| `radius` | Optional: Radius around player the effect will spread, defaults to everywhere. | + + + diff --git a/src/game.cpp b/src/game.cpp index bee1931498b45..29729dfcab6b0 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -5133,6 +5133,25 @@ void game::clear_zombies() critter_tracker->clear(); } +bool game::find_nearby_spawn_point( const Character &target, const mtype_id &mt, int min_radius, + int max_radius, tripoint &point ) +{ + tripoint target_point; + //find a legal outdoor place to spawn based on the specified radius, + //we just try a bunch of random points and use the first one that works, it none do then no spawn + for( int attempts = 0; attempts < 15; attempts++ ) { + target_point = target.pos() + tripoint( rng( -max_radius, max_radius ), + rng( -max_radius, max_radius ), 0 ); + if( can_place_monster( mt->id, target_point ) && + get_map().is_outside( target_point ) && + rl_dist( target_point, get_player_character().pos() ) > min_radius ) { + point = target_point; + return true; + } + } + return false; +} + /** * Attempts to spawn a hallucination at given location. * Returns false if the hallucination couldn't be spawned for whatever reason, such as @@ -5155,7 +5174,16 @@ bool game::spawn_hallucination( const tripoint &p ) } } - const mtype_id &mt = MonsterGenerator::generator().get_valid_hallucination(); + return spawn_hallucination( p, MonsterGenerator::generator().get_valid_hallucination() ); +} +/** + * Attempts to spawn a hallucination at given location. + * Returns false if the hallucination couldn't be spawned for whatever reason, such as + * a monster already in the target square. + * @return Whether or not a hallucination was successfully spawned. + */ +bool game::spawn_hallucination( const tripoint &p, const mtype_id &mt ) +{ const shared_ptr_fast phantasm = make_shared_fast( mt ); phantasm->hallucination = true; phantasm->spawn( p ); diff --git a/src/game.h b/src/game.h index c4498bd1a6b08..44d0eaa7f41e3 100644 --- a/src/game.h +++ b/src/game.h @@ -356,6 +356,11 @@ class game void clear_zombies(); /** Spawns a hallucination at a determined position. */ bool spawn_hallucination( const tripoint &p ); + /** Spawns a hallucination at a determined position of a given monster. */ + bool spawn_hallucination( const tripoint &p, const mtype_id &mt ); + /** Finds somewhere to spawn a monster. */ + bool find_nearby_spawn_point( const Character &target, const mtype_id &mt, int min_radius, + int max_radius, tripoint &point ); /** Swaps positions of two creatures */ bool swap_critters( Creature &, Creature & ); diff --git a/src/weather.cpp b/src/weather.cpp index a1cc7bba60db6..e4415a2be3fc6 100644 --- a/src/weather.cpp +++ b/src/weather.cpp @@ -21,6 +21,7 @@ #include "item_contents.h" #include "line.h" #include "map.h" +#include "map_iterator.h" #include "math_defines.h" #include "messages.h" #include "options.h" @@ -62,10 +63,10 @@ weather_manager &get_weather() return g->weather; } -static bool is_player_outside() +static bool is_creature_outside( const Creature &target ) { - return get_map().is_outside( point( get_player_character().posx(), - get_player_character().posy() ) ) && g->get_levz() >= 0; + return get_map().is_outside( point( target.posx(), + target.posy() ) ) && g->get_levz() >= 0; } weather_type_id get_bad_weather() @@ -88,8 +89,10 @@ weather_type_id get_bad_weather() */ void glare( weather_type_id w ) { + Character &target_character = g->u;//todo npcs, also //General prepequisites for glare - if( !is_player_outside() || !g->is_in_sunlight( g->u.pos() ) || g->u.in_sleep_state() || + if( !is_creature_outside( target_character ) || !g->is_in_sunlight( g->u.pos() ) || + g->u.in_sleep_state() || g->u.worn_with_flag( flag_SUN_GLASSES ) || g->u.has_bionic( bio_sunglasses ) || g->u.is_blind() ) { @@ -102,19 +105,19 @@ void glare( weather_type_id w ) if( season == WINTER ) { //Winter snow glare: for both clear & sunny weather effect = &effect_snow_glare; - dur = g->u.has_effect( *effect ) ? 1_turns : 2_turns; + dur = target_character.has_effect( *effect ) ? 1_turns : 2_turns; } else if( w->sun_intensity == sun_intensity_type::high ) { //Sun glare: only for bright sunny weather effect = &effect_glare; - dur = g->u.has_effect( *effect ) ? 1_turns : 2_turns; + dur = target_character.has_effect( *effect ) ? 1_turns : 2_turns; } //apply final glare effect if( dur > 0_turns && effect != nullptr ) { //enhance/reduce by some traits - if( g->u.has_trait( trait_CEPH_VISION ) ) { + if( target_character.has_trait( trait_CEPH_VISION ) ) { dur = dur * 2; } - g->u.add_env_effect( *effect, bp_eyes, 2, dur ); + target_character.add_env_effect( *effect, bp_eyes, 2, dur ); } } @@ -395,124 +398,50 @@ static void fill_water_collectors( int mmPerHour, bool acid ) * @see map::decay_fields_and_scent * @see player::drench */ -void weather_effect::wet_player( int amount ) +void wet( Character &target, int amount ) { - if( !is_player_outside() || - g->u.has_trait( trait_FEATHERS ) || - g->u.weapon.has_flag( "RAIN_PROTECT" ) || - ( !one_in( 50 ) && g->u.worn_with_flag( "RAINPROOF" ) ) ) { + if( !is_creature_outside( target ) || + target.has_trait( trait_FEATHERS ) || + target.weapon.has_flag( "RAIN_PROTECT" ) || + ( !one_in( 50 ) && target.worn_with_flag( "RAINPROOF" ) ) ) { return; } // Coarse correction to get us back to previously intended soaking rate. if( !calendar::once_every( 6_seconds ) ) { return; } - const int warmth_delay = g->u.warmth( bodypart_id( "torso" ) ) * 4 / 5 + g->u.warmth( + const int warmth_delay = target.warmth( bodypart_id( "torso" ) ) * 4 / 5 + target.warmth( bodypart_id( "head" ) ) / 5; if( rng( 0, 100 - amount + warmth_delay ) > 10 ) { // Thick clothing slows down (but doesn't cap) soaking return; } - const auto &wet = g->u.body_wetness; - const auto &capacity = g->u.drench_capacity; + const auto &wet = target.body_wetness; + const auto &capacity = target.drench_capacity; body_part_set drenched_parts{ { bodypart_str_id( "torso" ), bodypart_str_id( "arm_l" ), bodypart_str_id( "arm_r" ), bodypart_str_id( "head" ) } }; if( wet[bp_torso] * 100 >= capacity[bp_torso] * 50 ) { // Once upper body is 50%+ drenched, start soaking the legs too drenched_parts.unify_set( { { bodypart_str_id( "leg_l" ), bodypart_str_id( "leg_r" ) } } ); } - g->u.drench( amount, drenched_parts, false ); + target.drench( amount, drenched_parts, false ); } -/** - * Thunder. - * Flavor messages. Very wet. - */ -void weather_effect::thunder( int intensity ) +void weather_sound( translation sound_message, std::string sound_effect ) { - if( !g->u.has_effect( effect_sleep ) && !g->u.is_deaf() && one_in( intensity ) ) { + if( !g->u.has_effect( effect_sleep ) && !g->u.is_deaf() ) { if( g->get_levz() >= 0 ) { - add_msg( _( "You hear a distant rumble of thunder." ) ); - sfx::play_variant_sound( "environment", "thunder_far", 80, rng( 0, 359 ) ); + add_msg( sound_message ); + if( !sound_effect.empty() ) { + sfx::play_variant_sound( "environment", sound_effect, 80, rng( 0, 359 ) ); + } } else if( one_in( std::max( roll_remainder( 2.0f * g->get_levz() / g->u.mutation_value( "hearing_modifier" ) ), 1 ) ) ) { - add_msg( _( "You hear a rumble of thunder from above." ) ); - sfx::play_variant_sound( "environment", "thunder_far", - ( 80 * g->u.mutation_value( "hearing_modifier" ) ), rng( 0, 359 ) ); - } - } -} - -/** - * Lightning. - * Chance of lightning illumination for the current turn when aboveground. Thunder. - * - * This used to manifest actual lightning on the map, causing fires and such, but since such effects - * only manifest properly near the player due to the "reality bubble", this was causing undesired metagame tactics - * such as players leaving their shelter for a more "expendable" area during lightning storms. - */ -void weather_effect::lightning( int intensity ) -{ - if( one_in( intensity ) ) { - if( g->get_levz() >= 0 ) { - add_msg( _( "A flash of lightning illuminates your surroundings!" ) ); - sfx::play_variant_sound( "environment", "thunder_near", 100, rng( 0, 359 ) ); - g->weather.lightning_active = true; - } - } else { - g->weather.lightning_active = false; - } -} -/** - * Acid drizzle. - * Causes minor pain only. - */ -void weather_effect::light_acid( int intensity ) -{ - if( calendar::once_every( time_duration::from_seconds( intensity ) ) && is_player_outside() ) { - if( g->u.weapon.has_flag( "RAIN_PROTECT" ) && !one_in( 3 ) ) { - add_msg( _( "Your %s protects you from the acidic drizzle." ), g->u.weapon.tname() ); - } else { - if( g->u.worn_with_flag( "RAINPROOF" ) && !one_in( 4 ) ) { - add_msg( _( "Your clothing protects you from the acidic drizzle." ) ); - } else { - bool has_helmet = false; - if( g->u.is_wearing_power_armor( &has_helmet ) && ( has_helmet || !one_in( 4 ) ) ) { - add_msg( _( "Your power armor protects you from the acidic drizzle." ) ); - } else { - add_msg( m_warning, _( "The acid rain stings, but is mostly harmless for now…" ) ); - if( one_in( 10 ) && ( g->u.get_pain() < 10 ) ) { - g->u.mod_pain( 1 ); - } - } - } - } - } -} - -/** - * Acid rain. - * Causes major pain. Damages non acid-proof mobs. Very wet (acid). - */ -void weather_effect::acid( int intensity ) -{ - if( calendar::once_every( time_duration::from_seconds( intensity ) ) && is_player_outside() ) { - if( g->u.weapon.has_flag( "RAIN_PROTECT" ) && one_in( 4 ) ) { - add_msg( _( "Your umbrella protects you from the acid rain." ) ); - } else { - if( g->u.worn_with_flag( "RAINPROOF" ) && one_in( 2 ) ) { - add_msg( _( "Your clothing protects you from the acid rain." ) ); - } else { - bool has_helmet = false; - if( g->u.is_wearing_power_armor( &has_helmet ) && ( has_helmet || !one_in( 2 ) ) ) { - add_msg( _( "Your power armor protects you from the acid rain." ) ); - } else { - add_msg( m_bad, _( "The acid rain burns!" ) ); - if( one_in( 2 ) && ( g->u.get_pain() < 100 ) ) { - g->u.mod_pain( rng( 1, 5 ) ); - } - } + add_msg( sound_message ); + if( !sound_effect.empty() ) { + sfx::play_variant_sound( "environment", sound_effect, + ( 80 * g->u.mutation_value( "hearing_modifier" ) ), rng( 0, 359 ) ); } } } @@ -531,6 +460,8 @@ double precip_mm_per_hour( precip_class const p ) void handle_weather_effects( weather_type_id const w ) { + //Possible TODO, make npc/monsters affected + Character &target_character = get_player_character(); if( w->rains && w->precip != precip_class::none ) { fill_water_collectors( precip_mm_per_hour( w->precip ), w->acidic ); @@ -547,13 +478,129 @@ void handle_weather_effects( weather_type_id const w ) wetness = 60; } get_map().decay_fields_and_scent( decay_time ); - weather_effect::wet_player( wetness ); + wet( target_character, wetness ); } glare( w ); - std::vector> weather_effects = w->effects; + g->weather.lightning_active = false; - for( const std::pair &effect : weather_effects ) { - effect.first( effect.second ); + + for( const weather_effect ¤t_effect : w->effects ) { + if( current_effect.must_be_outside && !is_creature_outside( target_character ) ) { + continue; + } + if( current_effect.time_between > 0_seconds && + !calendar::once_every( current_effect.time_between ) ) { + continue; + } + if( !one_in( current_effect.one_in_chance ) ) { + continue; + } + if( current_effect.lightning && g->get_levz() >= 0 ) { + g->weather.lightning_active = true; + } + if( current_effect.rain_proof ) { + int chance = 0; + if( w->precip <= precip_class::light ) { + chance = 2; + } else if( w->precip >= precip_class::heavy ) { + chance = 4; + } + if( target_character.weapon.has_flag( "RAIN_PROTECT" ) && one_in( chance ) ) { + add_msg( _( "Your %s protects you from the weather." ), target_character.weapon.tname() ); + continue; + } else { + if( target_character.worn_with_flag( "RAINPROOF" ) && one_in( chance * 2 ) ) { + add_msg( _( "Your clothing protects you from the weather." ) ); + continue; + } else { + bool has_helmet = false; + if( target_character.is_wearing_power_armor( &has_helmet ) && ( has_helmet || + one_in( chance * 2 ) ) ) { + add_msg( _( "Your power armor protects you from the weather." ) ); + continue; + } + } + } + } + if( target_character.get_pain() >= current_effect.pain_max ) { + continue; + } + + bool spawned = current_effect.spawns.empty(); + for( const spawn_type &spawn : current_effect.spawns ) { + monster target_monster; + if( spawn.target.is_empty() ) { + //grab a random nearby hostile creature to create a hallucination or copy of + Creature *copy = g->get_creature_if( [&spawn]( const Creature & critter ) -> bool { + bool not_self = get_player_character().pos() != critter.pos(); + bool in_range = std::round( rl_dist_exact( get_player_character().pos(), critter.pos() ) ) <= spawn.target_range; + bool valid_target = get_player_character().attitude_to( critter ) == Creature::Attitude::HOSTILE; + return not_self && in_range && valid_target; + } ); + if( copy == nullptr ) { + continue; + } + target_monster = *dynamic_cast( copy ); + } else { + target_monster = spawn.target; + } + + for( int i = 0; i < spawn.hallucination_count; i++ ) { + tripoint point; + if( g->find_nearby_spawn_point( target_character, target_monster.type->id, spawn.min_radius, + spawn.max_radius, point ) ) { + g->spawn_hallucination( point, target_monster.type->id ); + spawned = true; + } + } + for( int i = 0; i < spawn.real_count; i++ ) { + tripoint point; + if( g->find_nearby_spawn_point( target_character, target_monster.type->id, spawn.min_radius, + spawn.max_radius, point ) ) { + g->place_critter_at( target_monster.type->id, point ); + spawned = true; + } + } + } + if( !spawned ) { + continue; + } + for( const weather_field &field : current_effect.fields ) { + for( const tripoint &dest : get_map().points_in_radius( target_character.pos(), field.radius ) ) { + if( !field.outdoor_only || get_map().is_outside( dest ) ) { + get_map().add_field( dest, field.type, field.intensity, field.age ); + } + } + } + if( current_effect.effect_id.is_valid() ) { + if( current_effect.target_part.is_valid() ) { + target_character.add_effect( current_effect.effect_id, current_effect.effect_duration, + current_effect.target_part->token ); + } else { + target_character.add_effect( current_effect.effect_id, current_effect.effect_duration ); + } + } + if( current_effect.trait_id_to_add.is_valid() ) { + target_character.set_mutation( current_effect.trait_id_to_add ); + } + if( current_effect.trait_id_to_remove.is_valid() ) { + target_character.unset_mutation( current_effect.trait_id_to_remove ); + } + + if( current_effect.target_part.is_valid() ) { + target_character.deal_damage( nullptr, current_effect.target_part, damage_instance( DT_BASH, + current_effect.damage ) ); + } else { + for( const bodypart_id &bp : target_character.get_all_body_parts() ) { + target_character.deal_damage( nullptr, bp, damage_instance( DT_BASH, current_effect.damage ) ); + } + } + target_character.mod_healthy( current_effect.healthy ); + target_character.mod_rad( current_effect.radiation ); + wet( target_character, current_effect.wet ); + target_character.mod_pain( current_effect.pain ); + weather_sound( current_effect.sound_message, current_effect.sound_effect ); + target_character.add_msg_if_player( current_effect.message ); } } diff --git a/src/weather.h b/src/weather.h index 903feabbbe66b..dd93db6bfede6 100644 --- a/src/weather.h +++ b/src/weather.h @@ -63,19 +63,6 @@ struct weather_printable { char cGlyph; }; -/** - * Environmental effects and ramifications of weather. - * Visibility range changes are done elsewhere. - */ -namespace weather_effect -{ -void thunder( int intensity ); -void lightning( int intensity ); -void light_acid( int intensity ); -void acid( int intensity ); -void wet_player( int amount ); -} // namespace weather_effect - struct weather_sum { int rain_amount = 0; int acid_amount = 0; @@ -152,6 +139,9 @@ void glare( weather_type_id w ); int incident_sunlight( weather_type_id wtype, const time_point &t = calendar::turn ); +void weather_sound( translation sound_message, std::string sound_effect ); +void wet( Character &target, int amount ); + class weather_manager { public: diff --git a/src/weather_type.cpp b/src/weather_type.cpp index f87bf7f8e2638..9bee3b8096247 100644 --- a/src/weather_type.cpp +++ b/src/weather_type.cpp @@ -118,6 +118,36 @@ void weather_type::check() const abort(); } } + for( const weather_effect &effect : effects ) { + if( !effect.effect_id.is_empty() && !effect.effect_id.is_valid() ) { + debugmsg( "Effect type %s does not exist.", effect.effect_id.c_str() ); + abort(); + } + if( !effect.trait_id_to_add.is_empty() && !effect.trait_id_to_add.is_valid() ) { + debugmsg( "Trait %s does not exist.", effect.trait_id_to_add.c_str() ); + abort(); + } + if( !effect.trait_id_to_remove.is_empty() && !effect.trait_id_to_remove.is_valid() ) { + debugmsg( "Trait %s does not exist.", effect.trait_id_to_remove.c_str() ); + abort(); + } + if( !effect.target_part.is_empty() && !effect.target_part.is_valid() ) { + debugmsg( "Target part %s does not exist.", effect.target_part.c_str() ); + abort(); + } + for( const spawn_type &spawn : effect.spawns ) { + if( !spawn.target.is_empty() && !spawn.target.is_valid() ) { + debugmsg( "Spawn target %s does not exist.", spawn.target.c_str() ); + abort(); + } + } + for( const weather_field &field : effect.fields ) { + if( !field.type.is_valid() ) { + debugmsg( "field type %s does not exist.", field.type.c_str() ); + abort(); + } + } + } } void weather_type::load( const JsonObject &jo, const std::string & ) @@ -148,23 +178,51 @@ void weather_type::load( const JsonObject &jo, const std::string & ) optional( jo, was_loaded, "sound_category", sound_category, weather_sound_category::silent ); mandatory( jo, was_loaded, "sun_intensity", sun_intensity ); - for( const JsonObject weather_effect : jo.get_array( "effects" ) ) { - - std::pair pair = std::make_pair( weather_effect.get_string( "name" ), - weather_effect.get_int( "intensity" ) ); - - static const std::map all_weather_effects = { - { "wet", &weather_effect::wet_player }, - { "thunder", &weather_effect::thunder }, - { "lightning", &weather_effect::lightning }, - { "light_acid", &weather_effect::light_acid }, - { "acid", &weather_effect::acid } - }; - const auto iter = all_weather_effects.find( pair.first ); - if( iter == all_weather_effects.end() ) { - weather_effect.throw_error( "Invalid weather effect", "name" ); + for( const JsonObject weather_effect_jo : jo.get_array( "effects" ) ) { + + weather_effect effect; + + optional( weather_effect_jo, was_loaded, "message", effect.message ); + optional( weather_effect_jo, was_loaded, "sound_message", effect.sound_message ); + optional( weather_effect_jo, was_loaded, "sound_effect", effect.sound_effect, "" ); + mandatory( weather_effect_jo, was_loaded, "must_be_outside", effect.must_be_outside ); + optional( weather_effect_jo, was_loaded, "one_in_chance", effect.one_in_chance, -1 ); + optional( weather_effect_jo, was_loaded, "time_between", effect.time_between ); + optional( weather_effect_jo, was_loaded, "lightning", effect.lightning, false ); + optional( weather_effect_jo, was_loaded, "rain_proof", effect.rain_proof, false ); + optional( weather_effect_jo, was_loaded, "pain_max", effect.pain_max, INT_MAX ); + optional( weather_effect_jo, was_loaded, "pain", effect.pain, 0 ); + optional( weather_effect_jo, was_loaded, "wet", effect.wet, 0 ); + optional( weather_effect_jo, was_loaded, "radiation", effect.radiation, 0 ); + optional( weather_effect_jo, was_loaded, "healthy", effect.healthy, 0 ); + optional( weather_effect_jo, was_loaded, "effect_id", effect.effect_id ); + optional( weather_effect_jo, was_loaded, "effect_duration", effect.effect_duration ); + optional( weather_effect_jo, was_loaded, "trait_id_to_add", effect.trait_id_to_add ); + optional( weather_effect_jo, was_loaded, "trait_id_to_remove", effect.trait_id_to_remove ); + optional( weather_effect_jo, was_loaded, "target_part", effect.target_part ); + optional( weather_effect_jo, was_loaded, "damage", effect.damage, 0 ); + for( const JsonObject field_jo : weather_effect_jo.get_array( "fields" ) ) { + weather_field new_field; + mandatory( field_jo, was_loaded, "type", new_field.type ); + mandatory( field_jo, was_loaded, "intensity", new_field.intensity ); + mandatory( field_jo, was_loaded, "age", new_field.age ); + optional( field_jo, was_loaded, "outdoor_only", new_field.outdoor_only, true ); + optional( field_jo, was_loaded, "radius", new_field.radius, 10000000 ); + + effect.fields.emplace_back( new_field ); + } + for( const JsonObject spawn_jo : weather_effect_jo.get_array( "spawns" ) ) { + spawn_type spawn; + mandatory( spawn_jo, was_loaded, "max_radius", spawn.max_radius ); + mandatory( spawn_jo, was_loaded, "min_radius", spawn.min_radius ); + optional( spawn_jo, was_loaded, "hallucination_count", spawn.hallucination_count, 0 ); + optional( spawn_jo, was_loaded, "real_count", spawn.real_count, 0 ); + optional( spawn_jo, was_loaded, "target", spawn.target ); + optional( spawn_jo, was_loaded, "target_range", spawn.target_range, 30 ); + + effect.spawns.emplace_back( spawn ); } - effects.emplace_back( iter->second, pair.second ); + effects.emplace_back( effect ); } weather_animation = { 0.0f, c_white, '?' }; if( jo.has_member( "weather_animation" ) ) { diff --git a/src/weather_type.h b/src/weather_type.h index 084a0110648e2..bc81fedc2668b 100644 --- a/src/weather_type.h +++ b/src/weather_type.h @@ -4,12 +4,12 @@ #include +#include "bodypart.h" +#include "field.h" #include "generic_factory.h" #include "translations.h" #include "type_id.h" -using weather_effect_fn = void ( * )( int intensity ); - const weather_type_id WEATHER_NULL( "null" ); const weather_type_id WEATHER_CLEAR( "clear" ); @@ -89,6 +89,47 @@ struct weather_requirements { std::vector required_weathers; }; +struct weather_field { + field_type_str_id type; + int intensity; + time_duration age; + int radius; + bool outdoor_only; +}; + +struct spawn_type { + mtype_id target; + int target_range; + int hallucination_count; + int real_count; + int min_radius; + int max_radius; +}; + +struct weather_effect { + int one_in_chance; + time_duration time_between; + translation message; + bool must_be_outside; + translation sound_message; + std::string sound_effect; + bool lightning; + bool rain_proof; + int pain; + int pain_max; + int wet; + int radiation; + int healthy; + efftype_id effect_id; + time_duration effect_duration; + trait_id trait_id_to_add; + trait_id trait_id_to_remove; + bodypart_str_id target_part; + int damage; + std::vector spawns; + std::vector fields; +}; + struct weather_type { public: friend class generic_factory; @@ -106,7 +147,7 @@ struct weather_type { precip_class precip; //!< Amount of associated precipitation. bool rains; //!< Whether said precipitation falls as rain. bool acidic; //!< Whether said precipitation is acidic. - std::vector < std::pair < weather_effect_fn, int >> effects; //!< vector for weather effects. + std::vector effects; //!< vector for weather effects. std::string tiles_animation; //!< string for tiles animation weather_animation_t weather_animation; //!< Information for weather animations weather_sound_category sound_category; //!< if playing sound effects what to use