diff --git a/data/json/field_type.json b/data/json/field_type.json index 01b118599a9e6..343319a0c1a82 100644 --- a/data/json/field_type.json +++ b/data/json/field_type.json @@ -185,34 +185,48 @@ "sym": "8", "dangerous": true, "translucency": 1, - "effect_id": "smoke", - "effect_body_part": "MOUTH", - "effect_intensity": 1, - "effect_min_duration": "2 seconds", - "effect_max_duration": "2 seconds", - "inside_immune": true + "effects": [ + { + "effect_id": "smoke", + "body_part": "MOUTH", + "intensity": 1, + "min_duration": "2 seconds", + "max_duration": "2 seconds", + "immune_inside_vehicle": true + } + ] }, { "name": "smoke", "color": "light_gray", "transparent": false, "translucency": 10, - "effect_id": "smoke", - "effect_body_part": "MOUTH", - "effect_intensity": 2, - "effect_min_duration": "7 seconds", - "effect_max_duration": "7 seconds", + "effects": [ + { + "effect_id": "smoke", + "body_part": "MOUTH", + "intensity": 2, + "min_duration": "7 seconds", + "max_duration": "7 seconds", + "immune_inside_vehicle": true + } + ], "scent_neutralization": 1 }, { "name": "thick smoke", "color": "dark_gray", "translucency": 0, - "effect_id": "smoke", - "effect_body_part": "MOUTH", - "effect_intensity": 4, - "effect_min_duration": "15 seconds", - "effect_max_duration": "15 seconds", + "effects": [ + { + "effect_id": "smoke", + "body_part": "MOUTH", + "intensity": 4, + "min_duration": "15 seconds", + "max_duration": "15 seconds", + "immune_inside_vehicle": true + } + ], "scent_neutralization": 5 } ], diff --git a/src/enums.h b/src/enums.h index 4440085106f38..537df3f6746cc 100644 --- a/src/enums.h +++ b/src/enums.h @@ -236,6 +236,12 @@ enum game_message_type : int { m_headshot, m_critical, m_grazing, + num_game_message_type +}; + +template<> +struct enum_traits { + static constexpr game_message_type last = game_message_type::num_game_message_type; }; enum game_message_flags { diff --git a/src/field.cpp b/src/field.cpp index 55debbf266c58..bf0f5a5bbbe66 100644 --- a/src/field.cpp +++ b/src/field.cpp @@ -265,18 +265,7 @@ int field::total_move_cost() const return current_cost; } -effect field_entry::field_effect() const +std::vector field_entry::field_effects() const { - const field_effect_data &field_effect = type->get_intensity_level( intensity - 1 ).field_effect; - const efftype_id fx_id = field_effect.id; - if( fx_id.is_empty() || fx_id.is_null() ) { - return effect(); - } - return effect( &field_effect.id.obj(), rng( field_effect.min_duration, field_effect.max_duration ), - field_effect.bp, false, field_effect.intensity, calendar::turn ); -} - -bool field_entry::inside_immune() const -{ - return type->get_intensity_level( intensity - 1 ).field_effect.inside_immune; + return type->get_intensity_level( intensity - 1 ).field_effects; } diff --git a/src/field.h b/src/field.h index 43f2e5c5f5655..3ccd136ac1797 100644 --- a/src/field.h +++ b/src/field.h @@ -104,8 +104,7 @@ class field_entry return type.obj().accelerated_decay; } - effect field_effect() const; - bool inside_immune() const; + std::vector field_effects() const; private: // The field identifier. diff --git a/src/field_type.cpp b/src/field_type.cpp index 384a91a8bf109..dfc6663aaf551 100644 --- a/src/field_type.cpp +++ b/src/field_type.cpp @@ -1,7 +1,5 @@ #include "field_type.h" -#include - #include "bodypart.h" #include "debug.h" #include "enums.h" @@ -9,6 +7,34 @@ #include "json.h" #include "int_id.h" +namespace io +{ + +template<> +std::string enum_to_string( game_message_type data ) +{ + switch( data ) { + // *INDENT-OFF* + case game_message_type::m_good: return "good"; + case game_message_type::m_bad: return "bad"; + case game_message_type::m_mixed: return "mixed"; + case game_message_type::m_warning: return "warning"; + case game_message_type::m_info: return "info"; + case game_message_type::m_neutral: return "neutral"; + case game_message_type::m_debug: return "debug"; + case game_message_type::m_headshot: return "headshot"; + case game_message_type::m_critical: return "critical"; + case game_message_type::m_grazing: return "grazing"; + // *INDENT-ON* + case game_message_type::num_game_message_type: + break; + } + debugmsg( "Invalid side" ); + abort(); +} + +} // namespace io + namespace { @@ -124,18 +150,33 @@ void field_type::load( JsonObject &jo, const std::string & ) fallback_intensity_level.translucency ); optional( jao, was_loaded, "convection_temperature_mod", intensity_level.convection_temperature_mod, fallback_intensity_level.convection_temperature_mod ); - optional( jao, was_loaded, "effect_id", intensity_level.field_effect.id, - fallback_intensity_level.field_effect.id ); - optional( jao, was_loaded, "effect_min_duration", intensity_level.field_effect.min_duration, - fallback_intensity_level.field_effect.min_duration ); - optional( jao, was_loaded, "effect_max_duration", intensity_level.field_effect.max_duration, - fallback_intensity_level.field_effect.max_duration ); - optional( jao, was_loaded, "effect_intensity", intensity_level.field_effect.intensity, - fallback_intensity_level.field_effect.intensity ); - optional( jao, was_loaded, "effect_body_part", intensity_level.field_effect.bp, - fallback_intensity_level.field_effect.bp ); - optional( jao, was_loaded, "inside_immune", intensity_level.field_effect.inside_immune, - fallback_intensity_level.field_effect.inside_immune ); + if( jao.has_array( "effects" ) ) { + JsonArray jae = jao.get_array( "effects" ); + for( size_t j = 0; j < jae.size(); ++j ) { + JsonObject joe = jae.next_object(); + field_effect fe; + mandatory( joe, was_loaded, "effect_id", fe.id ); + optional( joe, was_loaded, "min_duration", fe.min_duration ); + optional( joe, was_loaded, "max_duration", fe.max_duration ); + optional( joe, was_loaded, "intensity", fe.intensity ); + optional( joe, was_loaded, "body_part", fe.bp ); + optional( joe, was_loaded, "is_environmental", fe.is_environmental ); + optional( joe, was_loaded, "immune_in_vehicle", fe.immune_in_vehicle ); + optional( joe, was_loaded, "immune_inside_vehicle", fe.immune_inside_vehicle ); + optional( joe, was_loaded, "immune_outside_vehicle", fe.immune_outside_vehicle ); + optional( joe, was_loaded, "chance_in_vehicle", fe.chance_in_vehicle ); + optional( joe, was_loaded, "chance_inside_vehicle", fe.chance_inside_vehicle ); + optional( joe, was_loaded, "chance_outside_vehicle", fe.chance_outside_vehicle ); + optional( joe, was_loaded, "message", fe.message ); + optional( joe, was_loaded, "message_npc", fe.message_npc ); + const auto game_message_type_reader = enum_flags_reader { "game message types" }; + optional( jo, was_loaded, "message_type", fe.env_message_type, game_message_type_reader ); + intensity_level.field_effects.emplace_back( fe ); + } + } else { + // Use effects from previous intensity level + intensity_level.field_effects = fallback_intensity_level.field_effects; + } optional( jao, was_loaded, "scent_neutralization", intensity_level.scent_neutralization, fallback_intensity_level.scent_neutralization ); intensity_levels.emplace_back( intensity_level ); diff --git a/src/field_type.h b/src/field_type.h index d0d11e220f0c2..54f1a8322b737 100644 --- a/src/field_type.h +++ b/src/field_type.h @@ -13,6 +13,7 @@ #include "calendar.h" #include "catacharset.h" #include "color.h" +#include "effect.h" #include "enums.h" #include "type_id.h" #include "string_id.h" @@ -23,13 +24,34 @@ class JsonObject; enum phase_id : int; enum body_part : int; -struct field_effect_data { +struct field_effect { efftype_id id; - time_duration min_duration; - time_duration max_duration; - int intensity; - body_part bp; - bool inside_immune; + time_duration min_duration = 0_seconds; + time_duration max_duration = 0_seconds; + int intensity = 0; + body_part bp = num_bp; + bool is_environmental = true; + bool immune_in_vehicle = false; + bool immune_inside_vehicle = false; + bool immune_outside_vehicle = false; + int chance_in_vehicle = 0; + int chance_inside_vehicle = 0; + int chance_outside_vehicle = 0; + game_message_type env_message_type = m_neutral; + translation message; + translation message_npc; + time_duration get_duration() const { + return rng( min_duration, max_duration ); + } + std::string get_message() const { + return message.translated(); + } + std::string get_message_npc() const { + return message_npc.translated(); + } + effect get_effect( const time_point &start_time = calendar::turn ) const { + return effect( &id.obj(), get_duration(), bp, false, intensity, start_time ); + } }; struct field_intensity_level { @@ -54,7 +76,7 @@ struct field_intensity_level { float translucency = 0.0f; int convection_temperature_mod = 0; int scent_neutralization = 0; - field_effect_data field_effect; + std::vector field_effects; }; struct field_type { diff --git a/src/map_field.cpp b/src/map_field.cpp index 645a01dc15f48..1235faffd23c2 100644 --- a/src/map_field.cpp +++ b/src/map_field.cpp @@ -1694,10 +1694,25 @@ void map::player_in_field( player &u ) void map::creature_in_field( Creature &critter ) { + bool in_vehicle = false; + bool inside_vehicle = false; + player *u = critter.as_player(); if( critter.is_monster() ) { monster_in_field( *static_cast( &critter ) ); - } else if( player *p = critter.as_player() ) { - player_in_field( *p ); + } else { + if( u ) { + in_vehicle = u->in_vehicle; + // If we are in a vehicle figure out if we are inside (reduces effects usually) + // and what part of the vehicle we need to deal with. + if( in_vehicle ) { + if( const optional_vpart_position vp = veh_at( u->pos() ) ) { + if( vp->is_inside() ) { + inside_vehicle = true; + } + } + } + player_in_field( *u ); + } } field &curfield = get_field( critter.pos() ); @@ -1707,25 +1722,44 @@ void map::creature_in_field( Creature &critter ) continue; } const field_type_id cur_field_id = cur_field_entry.get_field_type(); - const effect field_fx = cur_field_entry.field_effect(); - if( field_fx.is_null() || - critter.is_immune_field( cur_field_id ) || field_fx.get_id().is_empty() || - critter.is_immune_effect( field_fx.get_id() ) ) { - continue; - } - player *u = critter.as_player(); - if( u && cur_field_entry.inside_immune() ) { - // If we are in a vehicle figure out if we are inside (reduces effects usually) - // and what part of the vehicle we need to deal with. - if( u->in_vehicle ) { - if( const optional_vpart_position vp = veh_at( u->pos() ) ) { - if( vp->is_inside() ) { - continue; - } - } + + for( const auto &fe : cur_field_entry.field_effects() ) { + if( in_vehicle && fe.immune_in_vehicle ) { + continue; + } + if( inside_vehicle && fe.immune_inside_vehicle ) { + continue; + } + if( !inside_vehicle && fe.immune_outside_vehicle ) { + continue; + } + if( in_vehicle && !one_in( fe.chance_in_vehicle ) ) { + continue; + } + if( inside_vehicle && !one_in( fe.chance_inside_vehicle ) ) { + continue; + } + if( !inside_vehicle && !one_in( fe.chance_outside_vehicle ) ) { + continue; + } + + const effect field_fx = fe.get_effect(); + if( field_fx.is_null() || + critter.is_immune_field( cur_field_id ) || field_fx.get_id().is_empty() || + critter.is_immune_effect( field_fx.get_id() ) ) { + continue; + } + bool effect_added = false; + if( fe.is_environmental ) { + effect_added = critter.add_env_effect( fe.id, fe.bp, fe.intensity, fe.get_duration() ); + } else { + effect_added = true; + critter.add_effect( field_fx ); + } + if( effect_added ) { + critter.add_msg_player_or_npc( fe.env_message_type, fe.get_message(), fe.get_message_npc() ); } } - critter.add_effect( field_fx ); } }