From 89657123e0124f1e75fd5d7fd6a1a85b74b5a98b Mon Sep 17 00:00:00 2001 From: ShnitzelX2 <65314588+ShnitzelX2@users.noreply.github.com> Date: Wed, 6 Nov 2024 19:29:59 -0500 Subject: [PATCH] ter/furn code deduplication, improved "copy-from" support Co-Authored-By: Procyonae <45432782+Procyonae@users.noreply.github.com> --- data/json/connect_groups.json | 8 +- src/construction.cpp | 65 ++--- src/faction_camp.cpp | 13 +- src/field_type.cpp | 8 +- src/field_type.h | 2 +- src/handle_action.cpp | 23 +- src/iexamine.cpp | 3 +- src/map.cpp | 112 +++++---- src/mapdata.cpp | 455 +++++++++++++++++----------------- src/mapdata.h | 151 ++++++----- src/mapgen.cpp | 21 +- src/mission_companion.cpp | 55 ++-- src/vehicle_autodrive.cpp | 2 +- tests/map_bash_test.cpp | 8 +- 14 files changed, 492 insertions(+), 434 deletions(-) diff --git a/data/json/connect_groups.json b/data/json/connect_groups.json index f5683b4e85677..81fa1b86359e1 100644 --- a/data/json/connect_groups.json +++ b/data/json/connect_groups.json @@ -1,9 +1,7 @@ [ { "type": "connect_group", - "id": "WALL", - "group_flags": [ "WALL", "CONNECT_WITH_WALL" ], - "connects_to_flags": [ "WALL", "CONNECT_WITH_WALL" ] + "id": "WALL" }, { "type": "connect_group", @@ -135,8 +133,6 @@ }, { "type": "connect_group", - "id": "INDOORFLOOR", - "group_flags": [ "INDOORS" ], - "rotates_to_flags": [ "WINDOW", "DOOR" ] + "id": "INDOORFLOOR" } ] diff --git a/src/construction.cpp b/src/construction.cpp index e84fd5ab76598..0bdcbb24901ce 100644 --- a/src/construction.cpp +++ b/src/construction.cpp @@ -1572,10 +1572,10 @@ bool construct::check_deconstruct( const tripoint_bub_ms &p ) if( here.has_flag_furn( ter_furn_flag::TFLAG_EASY_DECONSTRUCT, p ) ) { return false; } - return here.furn( p ).obj().deconstruct.can_do; + return !!here.furn( p ).obj().deconstruct; } // terrain can only be deconstructed when there is no furniture in the way - return here.ter( p ).obj().deconstruct.can_do; + return !!here.ter( p ).obj().deconstruct; } bool construct::check_up_OK( const tripoint_bub_ms & ) @@ -1788,21 +1788,21 @@ void construct::done_deconstruct( const tripoint_bub_ms &p, Character &player_ch // TODO: Make this the argument if( here.has_furn( p ) ) { const furn_t &f = here.furn( p ).obj(); - if( !f.deconstruct.can_do ) { + if( !f.deconstruct ) { add_msg( m_info, _( "That %s can not be disassembled!" ), f.name() ); return; } - if( f.deconstruct.furn_set.str().empty() ) { + if( f.deconstruct->furn_set.str().empty() ) { here.furn_set( p, furn_str_id::NULL_ID() ); } else { - here.furn_set( p, f.deconstruct.furn_set ); + here.furn_set( p, f.deconstruct->furn_set ); } add_msg( _( "The %s is disassembled." ), f.name() ); item &item_here = here.i_at( p ).size() != 1 ? null_item_reference() : here.i_at( p ).only_item(); const std::vector drop = here.spawn_items( p, - item_group::items_from( f.deconstruct.drop_group, calendar::turn ) ); - if( f.deconstruct.skill.has_value() ) { - deconstruction_practice_skill( f.deconstruct.skill.value() ); + item_group::items_from( f.deconstruct->drop_group, calendar::turn ) ); + if( f.deconstruct->skill.has_value() ) { + deconstruction_practice_skill( f.deconstruct->skill.value() ); } // if furniture has liquid in it and deconstructs into watertight containers then fill them if( f.has_flag( "LIQUIDCONT" ) && item_here.made_of( phase_id::LIQUID ) ) { @@ -1825,11 +1825,11 @@ void construct::done_deconstruct( const tripoint_bub_ms &p, Character &player_ch here.delete_signage( p ); } else { const ter_t &t = here.ter( p ).obj(); - if( !t.deconstruct.can_do ) { + if( !t.deconstruct ) { add_msg( _( "That %s can not be disassembled!" ), t.name() ); return; } - if( t.deconstruct.deconstruct_above ) { + if( t.deconstruct->deconstruct_above ) { const tripoint_bub_ms top = p + tripoint_above; if( here.has_furn( top ) ) { add_msg( _( "That %s can not be disassembled, since there is furniture above it." ), t.name() ); @@ -1837,11 +1837,11 @@ void construct::done_deconstruct( const tripoint_bub_ms &p, Character &player_ch } done_deconstruct( top, player_character ); } - here.ter_set( p, t.deconstruct.ter_set ); + here.ter_set( p, t.deconstruct->ter_set ); add_msg( _( "The %s is disassembled." ), t.name() ); - here.spawn_items( p, item_group::items_from( t.deconstruct.drop_group, calendar::turn ) ); - if( t.deconstruct.skill.has_value() ) { - deconstruction_practice_skill( t.deconstruct.skill.value() ); + here.spawn_items( p, item_group::items_from( t.deconstruct->drop_group, calendar::turn ) ); + if( t.deconstruct->skill.has_value() ) { + deconstruction_practice_skill( t.deconstruct->skill.value() ); } } } @@ -2090,34 +2090,39 @@ void construct::do_turn_deconstruct( const tripoint_bub_ms &p, Character &who ) } return ret; }; - auto deconstruction_will_practice_skill = [ &who ]( auto & skill ) { + auto deconstruction_will_practice_skill = [&who]( auto & skill ) { return who.get_skill_level( skill.id ) >= skill.min && who.get_skill_level( skill.id ) < skill.max; }; - if( here.has_furn( p ) ) { - const furn_t &f = here.furn( p ).obj(); - if( !!f.deconstruct.skill && - deconstruction_will_practice_skill( *f.deconstruct.skill ) ) { + auto deconstruct_query = [&who, &cancel_construction, &deconstruction_will_practice_skill, + &deconstruct_items]( map_common_deconstruct_info & deconstruct, std::string & name ) { + if( !!deconstruct.skill && + deconstruction_will_practice_skill( deconstruct.skill.value() ) ) { cancel_construction = !who.query_yn( _( "Deconstructing the %s will yield:\n%s\nYou feel you might also learn something about %s.\nReally deconstruct?" ), - f.name(), deconstruct_items( f.deconstruct.drop_group ), f.deconstruct.skill->id.obj().name() ); + name, deconstruct_items( deconstruct.drop_group ), deconstruct.skill->id.obj().name() ); } else { cancel_construction = !who.query_yn( _( "Deconstructing the %s will yield:\n%s\nReally deconstruct?" ), - f.name(), deconstruct_items( f.deconstruct.drop_group ) ); + name, deconstruct_items( deconstruct.drop_group ) ); + } + }; + + std::string tname; + if( here.has_furn( p ) ) { + const furn_t &f = here.furn( p ).obj(); + if( f.deconstruct ) { + map_furn_deconstruct_info deconstruct = f.deconstruct.value(); + tname = f.name(); + deconstruct_query( deconstruct, tname ); } } else { const ter_t &t = here.ter( p ).obj(); - if( !!t.deconstruct.skill && - deconstruction_will_practice_skill( *t.deconstruct.skill ) ) { - cancel_construction = !who.query_yn( - _( "Deconstructing the %s will yield:\n%s\nYou feel you might also learn something about %s.\nReally deconstruct?" ), - t.name(), deconstruct_items( t.deconstruct.drop_group ), t.deconstruct.skill->id.obj().name() ); - } else { - cancel_construction = !who.query_yn( - _( "Deconstructing the %s will yield:\n%s\nReally deconstruct?" ), - t.name(), deconstruct_items( t.deconstruct.drop_group ) ); + if( t.deconstruct ) { + map_ter_deconstruct_info deconstruct = t.deconstruct.value(); + tname = t.name(); + deconstruct_query( deconstruct, tname ); } } if( cancel_construction ) { diff --git a/src/faction_camp.cpp b/src/faction_camp.cpp index 5e1a30d770a42..83517a81eafca 100644 --- a/src/faction_camp.cpp +++ b/src/faction_camp.cpp @@ -4863,12 +4863,15 @@ int om_harvest_ter( npc &comp, const tripoint_abs_omt &omt_tgt, const ter_id &t, continue; } if( bring_back ) { - for( const item &itm : item_group::items_from( ter_tgt.bash.drop_group, - calendar::turn ) ) { - comp.companion_mission_inv.push_back( itm ); + const std::optional &bash = ter_tgt.bash; + if( bash ) { + for( const item &itm : item_group::items_from( ter_tgt.bash->drop_group, + calendar::turn ) ) { + comp.companion_mission_inv.push_back( itm ); + } + harvested++; + target_bay.ter_set( p, ter_tgt.bash->ter_set ); } - harvested++; - target_bay.ter_set( p, ter_tgt.bash.ter_set ); } } } diff --git a/src/field_type.cpp b/src/field_type.cpp index b9e0c45efa294..ed4cb77957342 100644 --- a/src/field_type.cpp +++ b/src/field_type.cpp @@ -309,8 +309,12 @@ void field_type::load( const JsonObject &jo, const std::string_view ) optional( jo, was_loaded, "mopsafe", mopsafe, false ); optional( jo, was_loaded, "decrease_intensity_on_contact", decrease_intensity_on_contact, false ); - - bash_info.load( jo, "bash", map_bash_info::field, "field " + id.str() ); + if( jo.has_object( "bash" ) ) { + if( !bash_info ) { + bash_info.emplace(); + } + bash_info->load( jo.get_object( "bash" ), was_loaded, "field " + id.str() ); + } if( was_loaded && jo.has_member( "copy-from" ) && looks_like.empty() ) { looks_like = jo.get_string( "copy-from" ); } diff --git a/src/field_type.h b/src/field_type.h index 863e3d696c5f1..0be98c145fa19 100644 --- a/src/field_type.h +++ b/src/field_type.h @@ -207,7 +207,7 @@ struct field_type { bool has_elec = false; bool has_fume = false; description_affix desc_affix = description_affix::DESCRIPTION_AFFIX_NUM; - map_bash_info bash_info; + std::optional bash_info; // chance, issue, duration, speech std::tuple npc_complain_data; diff --git a/src/handle_action.cpp b/src/handle_action.cpp index 5876255e5dcca..555153e8751b2 100644 --- a/src/handle_action.cpp +++ b/src/handle_action.cpp @@ -993,30 +993,31 @@ avatar::smash_result avatar::smash( tripoint_bub_ms &smashp ) } } for( std::pair &fd_to_smsh : here.field_at( smashp ) ) { - const map_bash_info &bash_info = fd_to_smsh.first->bash_info; - if( bash_info.str_min == -1 ) { + const std::optional &bash_info = fd_to_smsh.first->bash_info; + if( !bash_info ) { continue; } - if( smashskill < bash_info.str_min && one_in( 10 ) ) { + if( smashskill < bash_info->str_min && one_in( 10 ) ) { add_msg( m_neutral, _( "You don't seem to be damaging the %s." ), fd_to_smsh.first->get_name() ); ret.did_smash = true; return ret; - } else if( smashskill >= rng( bash_info.str_min, bash_info.str_max ) ) { - sounds::sound( smashp, bash_info.sound_vol, sounds::sound_t::combat, bash_info.sound, true, "smash", + } else if( smashskill >= rng( bash_info->str_min, bash_info->str_max ) ) { + sounds::sound( smashp, bash_info->sound_vol, sounds::sound_t::combat, bash_info->sound, true, + "smash", "field" ); here.remove_field( smashp, fd_to_smsh.first ); - here.spawn_items( smashp, item_group::items_from( bash_info.drop_group, calendar::turn ) ); - mod_moves( - bash_info.fd_bash_move_cost ); - add_msg( m_info, bash_info.field_bash_msg_success.translated() ); + here.spawn_items( smashp, item_group::items_from( bash_info->drop_group, calendar::turn ) ); + mod_moves( - bash_info->fd_bash_move_cost ); + add_msg( m_info, bash_info->field_bash_msg_success.translated() ); ret.did_smash = true; ret.success = true; return ret; } else { - sounds::sound( smashp, bash_info.sound_fail_vol, sounds::sound_t::combat, bash_info.sound_fail, + sounds::sound( smashp, bash_info->sound_fail_vol, sounds::sound_t::combat, bash_info->sound_fail, true, "smash", "field" ); - ret.resistance = bash_info.str_min; + ret.resistance = bash_info->str_min; ret.did_smash = true; return ret; } @@ -1123,7 +1124,7 @@ avatar::smash_result avatar::smash( tripoint_bub_ms &smashp ) // Bash not effective g->draw_async_anim( smashp, "bash_ineffective" ); if( one_in( 10 ) ) { - if( here.has_furn( smashp ) && here.furn( smashp ).obj().bash.str_min != -1 ) { + if( here.has_furn( smashp ) && here.furn( smashp ).obj().bash ) { // %s is the smashed furniture add_msg( m_neutral, _( "You don't seem to be damaging the %s." ), here.furnname( smashp ) ); } else { diff --git a/src/iexamine.cpp b/src/iexamine.cpp index ebfdd2a7e1be0..8db1d010ba701 100644 --- a/src/iexamine.cpp +++ b/src/iexamine.cpp @@ -1713,7 +1713,8 @@ void iexamine::portable_structure( Character &you, const tripoint_bub_ms &examp } radius = actor.radius; } else { - radius = std::max( 1, fid->bash.collapse_radius ); + const std::optional &furn_bash = fid.obj().bash; + radius = std::max( 1, furn_bash ? furn_bash->collapse_radius : 0 ); } if( !query_yn( _( "Take down the %s?" ), name ) ) { diff --git a/src/map.cpp b/src/map.cpp index 9af98479cd722..63ce477a182f7 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -3323,10 +3323,10 @@ int map::bash_rating_internal( const int str, const furn_t &furniture, bool furn_smash = false; bool ter_smash = false; ///\EFFECT_STR determines what furniture can be smashed - if( furniture.id && furniture.bash.str_max != -1 ) { + if( furniture.id && furniture.bash ) { furn_smash = true; ///\EFFECT_STR determines what terrain can be smashed - } else if( terrain.bash.str_max != -1 && ( !terrain.bash.bash_below || allow_floor ) ) { + } else if( terrain.bash && ( !terrain.bash->bash_below || allow_floor ) ) { ter_smash = true; } @@ -3338,11 +3338,11 @@ int map::bash_rating_internal( const int str, const furn_t &furniture, int bash_min = 0; int bash_max = 0; if( furn_smash ) { - bash_min = furniture.bash.str_min; - bash_max = furniture.bash.str_max; + bash_min = furniture.bash->str_min; + bash_max = furniture.bash->str_max; } else if( ter_smash ) { - bash_min = terrain.bash.str_min; - bash_max = terrain.bash.str_max; + bash_min = terrain.bash->str_min; + bash_max = terrain.bash->str_max; } else { return -1; } @@ -3378,12 +3378,7 @@ bool map::is_bashable( const tripoint_bub_ms &p, const bool allow_floor ) const return true; } - if( has_furn( p ) && furn( p ).obj().bash.str_max != -1 ) { - return true; - } - - const map_bash_info &ter_bash = ter( p ).obj().bash; - return ter_bash.str_max != -1 && ( !ter_bash.bash_below || allow_floor ); + return is_bashable_ter_furn( p, allow_floor ); } bool map::is_bashable_ter( const tripoint &p, const bool allow_floor ) const @@ -3393,8 +3388,8 @@ bool map::is_bashable_ter( const tripoint &p, const bool allow_floor ) const bool map::is_bashable_ter( const tripoint_bub_ms &p, const bool allow_floor ) const { - const map_bash_info &ter_bash = ter( p ).obj().bash; - return ter_bash.str_max != -1 && ( !ter_bash.bash_below || allow_floor ); + const std::optional &ter_bash = ter( p ).obj().bash; + return ter_bash && ( !ter_bash->bash_below || allow_floor ); } bool map::is_bashable_furn( const tripoint &p ) const @@ -3404,7 +3399,7 @@ bool map::is_bashable_furn( const tripoint &p ) const bool map::is_bashable_furn( const tripoint_bub_ms &p ) const { - return has_furn( p ) && furn( p ).obj().bash.str_max != -1; + return has_furn( p ) && furn( p ).obj().bash; } bool map::is_bashable_ter_furn( const tripoint &p, const bool allow_floor ) const @@ -3424,13 +3419,12 @@ int map::bash_strength( const tripoint &p, const bool allow_floor ) const int map::bash_strength( const tripoint_bub_ms &p, const bool allow_floor ) const { - if( has_furn( p ) && furn( p ).obj().bash.str_max != -1 ) { - return furn( p ).obj().bash.str_max; + if( is_bashable_furn( p ) ) { + return furn( p ).obj().bash->str_max; } - const map_bash_info &ter_bash = ter( p ).obj().bash; - if( ter_bash.str_max != -1 && ( !ter_bash.bash_below || allow_floor ) ) { - return ter_bash.str_max; + if( is_bashable_ter( p, allow_floor ) ) { + return ter( p ).obj().bash->str_max; } return -1; @@ -3443,13 +3437,12 @@ int map::bash_resistance( const tripoint &p, const bool allow_floor ) const int map::bash_resistance( const tripoint_bub_ms &p, const bool allow_floor ) const { - if( has_furn( p ) && furn( p ).obj().bash.str_min != -1 ) { - return furn( p ).obj().bash.str_min; + if( is_bashable_furn( p ) ) { + return furn( p ).obj().bash->str_min; } - const map_bash_info &ter_bash = ter( p ).obj().bash; - if( ter_bash.str_min != -1 && ( !ter_bash.bash_below || allow_floor ) ) { - return ter_bash.str_min; + if( is_bashable_ter( p, allow_floor ) ) { + return ter( p ).obj().bash->str_min; } return -1; @@ -4215,7 +4208,9 @@ void map::bash_ter_furn( const tripoint_bub_ms &p, bash_params ¶ms ) const furn_t &furnid = furn( p ).obj(); bool smash_furn = false; bool smash_ter = false; - const map_bash_info *bash = nullptr; + std::optional bash; + map_ter_bash_info ter_bash; + map_furn_bash_info furn_bash; tripoint_bub_ms below = p + tripoint_rel_ms_below; ter_str_id below_tile_roof = ter_t_dirt; @@ -4228,11 +4223,13 @@ void map::bash_ter_furn( const tripoint_bub_ms &p, bash_params ¶ms ) bool success = false; - if( has_furn( p ) && furnid.bash.str_max != -1 ) { - bash = &furnid.bash; + if( has_furn( p ) && furnid.bash ) { + furn_bash = *furnid.bash; + bash = static_cast( furn_bash ); smash_furn = true; - } else if( ter( p ).obj().bash.str_max != -1 ) { - bash = &ter( p ).obj().bash; + } else if( ter( p ).obj().bash ) { + ter_bash = *ter( p ).obj().bash; + bash = static_cast( ter_bash ); smash_ter = true; } @@ -4242,18 +4239,18 @@ void map::bash_ter_furn( const tripoint_bub_ms &p, bash_params ¶ms ) if( smash_ter && bash->bash_below && ( !zlevels || !params.bash_floor ) ) { if( !params.destroy ) { // NOLINT(bugprone-branch-clone) smash_ter = false; - bash = nullptr; - } else if( !bash->ter_set && zlevels ) { + bash.reset(); + } else if( !ter_bash.ter_set && zlevels ) { // HACK: A hack for destroy && !bash_floor // We have to check what would we create and cancel if it is what we have now if( roof_of_below_tile ) { smash_ter = false; - bash = nullptr; + bash.reset(); } - } else if( !bash->ter_set && ter( p ) == ter_t_dirt ) { + } else if( !ter_bash.ter_set && ter( p ) == ter_t_dirt ) { // As above, except for no-z-levels case smash_ter = false; - bash = nullptr; + bash.reset(); } } @@ -4262,7 +4259,7 @@ void map::bash_ter_furn( const tripoint_bub_ms &p, bash_params ¶ms ) false, "environment", "alarm" ); } - if( bash == nullptr || ( bash->destroy_only && !params.destroy ) ) { + if( !bash || ( bash->destroy_only && !params.destroy ) ) { // Nothing bashable here if( impassable( p ) ) { if( !params.silent ) { @@ -4400,32 +4397,36 @@ void map::bash_ter_furn( const tripoint_bub_ms &p, bash_params ¶ms ) } // Didn't find any tent center, wreck the current tile if( !tentp ) { - spawn_items( p, item_group::items_from( bash->drop_group, calendar::turn ) ); - furn_set( p, bash->furn_set ); + if( bash ) { + spawn_items( p, item_group::items_from( bash->drop_group, calendar::turn ) ); + furn_set( p, furn_bash.furn_set ); + } } else { // Take the tent down - const int rad = tentp->second.obj().bash.collapse_radius; + const std::optional &tent_bash = tentp->second.obj().bash; + const int rad = tent_bash ? tent_bash->collapse_radius : 0; for( const tripoint_bub_ms &pt : points_in_radius( tentp->first, rad ) ) { const furn_id &frn = furn( pt ); if( frn == furn_str_id::NULL_ID() ) { continue; } - - const map_bash_info *recur_bash = &frn.obj().bash; + const std::optional &tent_recur_bash = frn.obj().bash; // Check if we share a center type and thus a "tent type" - for( const auto &cur_id : recur_bash->tent_centers ) { - if( centers.count( cur_id.id() ) > 0 ) { - // Found same center, wreck current tile - spawn_items( p, item_group::items_from( recur_bash->drop_group, calendar::turn ) ); - furn_set( pt, recur_bash->furn_set ); - break; + if( tent_recur_bash ) { + for( const auto &cur_id : tent_recur_bash->tent_centers ) { + if( centers.count( cur_id.id() ) > 0 ) { + // Found same center, wreck current tile + spawn_items( p, item_group::items_from( tent_recur_bash->drop_group, calendar::turn ) ); + furn_set( pt, tent_recur_bash->furn_set ); + break; + } } } } } soundfxvariant = "smash_cloth"; } else if( smash_furn ) { - furn_set( p, bash->furn_set ); + furn_set( p, furn_bash.furn_set ); for( item &it : i_at( p ) ) { it.on_drop( p, *this ); } @@ -4439,13 +4440,13 @@ void map::bash_ter_furn( const tripoint_bub_ms &p, bash_params ¶ms ) // Handle error earlier so that we can assume smash_ter is true below debugmsg( "data/json/terrain.json does not have %s.bash.ter_set set!", ter( p ).obj().id.c_str() ); - } else if( params.bashing_from_above && bash->ter_set_bashed_from_above ) { + } else if( params.bashing_from_above && ter_bash.ter_set_bashed_from_above ) { // If this terrain is being bashed from above and this terrain // has a valid post-destroy bashed-from-above terrain, set it - ter_set( p, bash->ter_set_bashed_from_above ); - } else if( bash->ter_set && !roof_of_below_tile ) { + ter_set( p, ter_bash.ter_set_bashed_from_above ); + } else if( ter_bash.ter_set && !roof_of_below_tile ) { // If the terrain has a valid post-destroy terrain, set it - ter_set( p, bash->ter_set ); + ter_set( p, ter_bash.ter_set ); } else { const ter_t &ter_below = ter( below ).obj(); if( bash->bash_below && ter_below.has_flag( ter_furn_flag::TFLAG_SUPPORTS_ROOF ) ) { @@ -4466,7 +4467,7 @@ void map::bash_ter_furn( const tripoint_bub_ms &p, bash_params ¶ms ) spawn_items( p, item_group::items_from( bash->drop_group, calendar::turn ) ); } //regenerates roofs for tiles that should be walkable from above - if( zlevels && smash_ter && !set_to_air && ter( p )->has_flag( "EMPTY_SPACE" ) && + if( zlevels && smash_ter && !set_to_air && ter( p )->has_flag( "EMPTY_SPACE" ) && ter( below )->has_flag( "WALL" ) ) { const ter_str_id roof = get_roof( below, params.bash_floor && ter( below ).obj().movecost != 0 ); ter_set( p, roof ); @@ -4587,7 +4588,7 @@ void map::bash_field( const tripoint_bub_ms &p, bash_params ¶ms ) { std::vector to_remove; for( const std::pair &fd : field_at( p ) ) { - if( fd.first->bash_info.str_min > -1 ) { + if( fd.first->bash_info ) { params.did_bash = true; params.bashed_solid = true; // To prevent bashing furniture/vehicles to_remove.push_back( fd.first ); @@ -4799,7 +4800,8 @@ void map::shoot( const tripoint_bub_ms &p, projectile &proj, const bool hit_item // Need to make a copy since 'remove_field' modifies the value field fields_copy = fields_there; for( const std::pair &fd : fields_copy ) { - if( fd.first->bash_info.str_min > 0 ) { + const std::optional &bash_info = fd.first->bash_info; + if( bash_info && bash_info->str_min > 0 ) { if( incendiary ) { add_field( p, fd_fire, fd.second.get_field_intensity() - 1 ); } else if( dam > 5 + fd.second.get_field_intensity() * 5 && diff --git a/src/mapdata.cpp b/src/mapdata.cpp index c386281cf84ab..bad2d1b2b1c9e 100644 --- a/src/mapdata.cpp +++ b/src/mapdata.cpp @@ -30,6 +30,7 @@ #include "type_id.h" static furn_id f_null; +static const furn_str_id furn_f_null( "f_null" ); static const item_group_id Item_spawn_data_EMPTY_GROUP( "EMPTY_GROUP" ); @@ -306,30 +307,6 @@ void connect_group::load( const JsonObject &jo ) return; } - if( jo.has_string( "group_flags" ) || jo.has_array( "group_flags" ) ) { - const std::vector str_flags = jo.get_as_string_array( "group_flags" ); - for( const std::string &flag : str_flags ) { - const ter_furn_flag f = io::string_to_enum( flag ); - result.group_flags.insert( f ); - } - } - - if( jo.has_string( "connects_to_flags" ) || jo.has_array( "connects_to_flags" ) ) { - const std::vector str_flags = jo.get_as_string_array( "connects_to_flags" ); - for( const std::string &flag : str_flags ) { - const ter_furn_flag f = io::string_to_enum( flag ); - result.connects_to_flags.insert( f ); - } - } - - if( jo.has_string( "rotates_to_flags" ) || jo.has_array( "rotates_to_flags" ) ) { - const std::vector str_flags = jo.get_as_string_array( "rotates_to_flags" ); - for( const std::string &flag : str_flags ) { - const ter_furn_flag f = io::string_to_enum( flag ); - result.rotates_to_flags.insert( f ); - } - } - ter_connects_map[ result.id.str() ] = result; } @@ -340,106 +317,98 @@ void connect_group::reset() static void load_map_bash_tent_centers( const JsonArray &ja, std::vector ¢ers ) { + centers.clear(); for( const std::string line : ja ) { centers.emplace_back( line ); } } -map_bash_info::map_bash_info() : str_min( -1 ), str_max( -1 ), - str_min_blocked( -1 ), str_max_blocked( -1 ), - str_min_supported( -1 ), str_max_supported( -1 ), - explosive( 0 ), sound_vol( -1 ), sound_fail_vol( -1 ), - collapse_radius( 1 ), destroy_only( false ), bash_below( false ), - drop_group( Item_spawn_data_EMPTY_GROUP ), - ter_set( ter_str_id::NULL_ID() ), furn_set( furn_str_id::NULL_ID() ) {} - -bool map_bash_info::load( const JsonObject &jsobj, const std::string_view member, - map_object_type obj_type, const std::string &context ) +void map_common_bash_info::load( const JsonObject &jo, const bool was_loaded, + const std::string &context ) { - if( !jsobj.has_object( member ) ) { - return false; - } + optional( jo, was_loaded, "str_min", str_min, -1 ); + optional( jo, was_loaded, "str_max", str_max, -1 ); - JsonObject j = jsobj.get_object( member ); - str_min = j.get_int( "str_min", 0 ); - str_max = j.get_int( "str_max", 0 ); - - str_min_blocked = j.get_int( "str_min_blocked", -1 ); - str_max_blocked = j.get_int( "str_max_blocked", -1 ); + optional( jo, was_loaded, "str_min_blocked", str_min_blocked, -1 ); + optional( jo, was_loaded, "str_max_blocked", str_max_blocked, -1 ); - str_min_supported = j.get_int( "str_min_supported", -1 ); - str_max_supported = j.get_int( "str_max_supported", -1 ); + optional( jo, was_loaded, "str_min_supported", str_min_supported, -1 ); + optional( jo, was_loaded, "str_max_supported", str_max_supported, -1 ); - explosive = j.get_int( "explosive", -1 ); + optional( jo, was_loaded, "explosive", explosive, -1 ); - sound_vol = j.get_int( "sound_vol", -1 ); - sound_fail_vol = j.get_int( "sound_fail_vol", -1 ); + optional( jo, was_loaded, "sound_vol", sound_vol, -1 ); + optional( jo, was_loaded, "sound_fail_vol", sound_fail_vol, -1 ); - collapse_radius = j.get_int( "collapse_radius", 1 ); + optional( jo, was_loaded, "collapse_radius", collapse_radius, 1 ); - destroy_only = j.get_bool( "destroy_only", false ); + optional( jo, was_loaded, "destroy_only", destroy_only, false ); - bash_below = j.get_bool( "bash_below", false ); + optional( jo, was_loaded, "bash_below", bash_below, false ); - sound = to_translation( "smash!" ); - sound_fail = to_translation( "thump!" ); - j.read( "sound", sound ); - j.read( "sound_fail", sound_fail ); + optional( jo, was_loaded, "sound", sound, to_translation( "smash!" ) ); + optional( jo, was_loaded, "sound_fail", sound_fail, to_translation( "thump!" ) ); - switch( obj_type ) { - case map_bash_info::furniture: - furn_set = furn_str_id( j.get_string( "furn_set", "f_null" ) ); - break; - case map_bash_info::terrain: - ter_set = ter_str_id( j.get_string( "ter_set" ) ); - ter_set_bashed_from_above = ter_str_id( j.get_string( "ter_set_bashed_from_above", - ter_set.c_str() ) ); - break; - case map_bash_info::field: - fd_bash_move_cost = j.get_int( "move_cost", 100 ); - j.read( "msg_success", field_bash_msg_success ); - break; - } - - if( j.has_member( "items" ) ) { - drop_group = item_group::load_item_group( j.get_member( "items" ), "collection", + if( jo.has_member( "items" ) ) { + drop_group = item_group::load_item_group( jo.get_member( "items" ), "collection", "map_bash_info for " + context ); - } else { + } else if( !was_loaded ) { drop_group = Item_spawn_data_EMPTY_GROUP; } - if( j.has_array( "tent_centers" ) ) { - load_map_bash_tent_centers( j.get_array( "tent_centers" ), tent_centers ); + if( jo.has_array( "tent_centers" ) ) { + load_map_bash_tent_centers( jo.get_array( "tent_centers" ), tent_centers ); } - - return true; +} +void map_ter_bash_info::load( const JsonObject &jo, const bool was_loaded, + const std::string &context ) +{ + map_common_bash_info::load( jo, was_loaded, context ); + mandatory( jo, was_loaded, "ter_set", ter_set ); + optional( jo, was_loaded, "ter_set_bashed_from_above", ter_set_bashed_from_above, ter_set ); +} +void map_furn_bash_info::load( const JsonObject &jo, const bool was_loaded, + const std::string &context ) +{ + map_common_bash_info::load( jo, was_loaded, context ); + optional( jo, was_loaded, "furn_set", furn_set, furn_f_null ); +} +void map_fd_bash_info::load( const JsonObject &jo, const bool was_loaded, + const std::string &context ) +{ + map_common_bash_info::load( jo, was_loaded, context ); + optional( jo, was_loaded, "move_cost", fd_bash_move_cost, 100 ); + optional( jo, was_loaded, "msg_success", field_bash_msg_success ); } -map_deconstruct_info::map_deconstruct_info() : can_do( false ), deconstruct_above( false ), - ter_set( ter_str_id::NULL_ID() ), furn_set( furn_str_id::NULL_ID() ) {} -bool map_deconstruct_info::load( const JsonObject &jsobj, const std::string_view member, - bool is_furniture, const std::string &context ) +void map_common_deconstruct_info::load( const JsonObject &jo, const bool was_loaded, + const std::string &context ) { - if( !jsobj.has_object( member ) ) { - return false; + if( jo.has_object( "skill" ) ) { + JsonObject jos = jo.get_object( "skill" ); + skill = { skill_id( jos.get_string( "skill" ) ), jos.get_int( "min", 0 ), jos.get_int( "max", 10 ), jos.get_float( "multiplier", 1.0 ) }; } - JsonObject j = jsobj.get_object( member ); - furn_set = furn_str_id( j.get_string( "furn_set", "f_null" ) ); - - if( !is_furniture ) { - ter_set = ter_str_id( j.get_string( "ter_set" ) ); - } - if( j.has_object( "skill" ) ) { - JsonObject jo = j.get_object( "skill" ); - skill = { skill_id( jo.get_string( "skill" ) ), jo.get_int( "min", 0 ), jo.get_int( "max", 10 ), jo.get_float( "multiplier", 1.0 ) }; + optional( jo, was_loaded, "deconstruct_above", deconstruct_above, false ); + if( jo.has_member( "items" ) ) { + drop_group = item_group::load_item_group( jo.get_member( "items" ), "collection", + "map_deconstruct_info for " + context ); + } else if( !was_loaded ) { + drop_group = Item_spawn_data_EMPTY_GROUP; } - can_do = true; - deconstruct_above = j.get_bool( "deconstruct_above", false ); +} - drop_group = item_group::load_item_group( j.get_member( "items" ), "collection", - "map_deconstruct_info for " + context ); - return true; +void map_ter_deconstruct_info::load( const JsonObject &jo, const bool was_loaded, + const std::string &context ) +{ + mandatory( jo, was_loaded, "ter_set", ter_set ); + map_common_deconstruct_info::load( jo, was_loaded, context ); +} +void map_furn_deconstruct_info::load( const JsonObject &jo, const bool was_loaded, + const std::string &context ) +{ + optional( jo, was_loaded, "furn_set", furn_set, furn_f_null ); + map_common_deconstruct_info::load( jo, was_loaded, context ); } bool map_shoot_info::load( const JsonObject &jsobj, const std::string_view member, bool was_loaded ) @@ -771,7 +740,7 @@ std::vector map_data_common_t::extended_description() const add( text ); } }; - add_if( bash.str_max != -1 && !bash.bash_below, _( "Smashable." ) ); + add_if( is_smashable(), _( "Smashable." ) ); add_if( has_flag( ter_furn_flag::TFLAG_DIGGABLE ), _( "Diggable." ) ); add_if( has_flag( ter_furn_flag::TFLAG_PLOWABLE ), _( "Plowable." ) ); add_if( has_flag( ter_furn_flag::TFLAG_ROUGH ), _( "Rough." ) ); @@ -780,9 +749,7 @@ std::vector map_data_common_t::extended_description() const add_if( has_flag( ter_furn_flag::TFLAG_FLAT ), _( "Flat." ) ); add_if( has_flag( ter_furn_flag::TFLAG_EASY_DECONSTRUCT ), _( "Simple." ) ); add_if( has_flag( ter_furn_flag::TFLAG_MOUNTABLE ), _( "Mountable." ) ); - add_if( has_flag( ter_furn_flag::TFLAG_FLAMMABLE ) || - has_flag( ter_furn_flag::TFLAG_FLAMMABLE_ASH ) || - has_flag( ter_furn_flag::TFLAG_FLAMMABLE_HARD ), _( "Flammable." ) ); + add_if( is_flammable(), _( "Flammable." ) ); tmp.emplace_back( result ); std::vector ret; @@ -810,33 +777,14 @@ void load_terrain( const JsonObject &jo, const std::string &src ) terrain_data.load( jo, src ); } -void map_data_common_t::extraprocess_flags( const ter_furn_flag flag ) -{ - if( !transparent && ( flag == ter_furn_flag::TFLAG_TRANSPARENT || - flag == ter_furn_flag::TFLAG_TRANSLUCENT ) ) { - transparent = true; - } - - for( std::pair &item : ter_connects_map ) { - if( item.second.group_flags.find( flag ) != item.second.group_flags.end() ) { - set_connect_groups( { item.second.id.str() } ); - } - if( item.second.connects_to_flags.find( flag ) != item.second.connects_to_flags.end() ) { - set_connects_to( { item.second.id.str() } ); - } - if( item.second.rotates_to_flags.find( flag ) != item.second.rotates_to_flags.end() ) { - set_rotates_to( { item.second.id.str() } ); - } - } -} - void map_data_common_t::set_flag( const std::string &flag ) { flags.insert( flag ); std::optional f = io::string_to_enum_optional( flag ); if( f.has_value() ) { bitflags.set( f.value() ); - extraprocess_flags( f.value() ); + transparent |= f.value() == ter_furn_flag::TFLAG_TRANSPARENT || + f.value() == ter_furn_flag::TFLAG_TRANSLUCENT; } } @@ -844,7 +792,28 @@ void map_data_common_t::set_flag( const ter_furn_flag flag ) { flags.insert( io::enum_to_string( flag ) ); bitflags.set( flag ); - extraprocess_flags( flag ); + transparent |= flag == ter_furn_flag::TFLAG_TRANSPARENT || flag == ter_furn_flag::TFLAG_TRANSLUCENT; +} + +void map_data_common_t::unset_flag( const std::string &flag ) +{ + if( auto it_flag = flags.find( flag ); it_flag != flags.end() ) { + flags.erase( it_flag ); + } //else return false? + std::optional f = io::string_to_enum_optional( flag ); + if( f.has_value() ) { + bitflags.reset( f.value() ); + if( transparent ) { + transparent = f.value() != ter_furn_flag::TFLAG_TRANSPARENT; + } + } +} + +void map_data_common_t::unset_flags() +{ + flags.clear(); + bitflags.reset(); + transparent = false; //? } void map_data_common_t::set_connect_groups( const std::vector @@ -968,6 +937,27 @@ void init_mapdata() void map_data_common_t::load( const JsonObject &jo, const std::string &src ) { + mandatory( jo, was_loaded, "name", name_ ); + mandatory( jo, was_loaded, "description", description ); + + optional( jo, was_loaded, "comfort", comfort, 0 ); + + if( jo.has_member( "connect_groups" ) ) { + connect_groups.reset(); + set_connect_groups( jo.get_as_string_array( "connect_groups" ) ); + } + if( jo.has_member( "connects_to" ) ) { + connect_to_groups.reset(); + set_connects_to( jo.get_as_string_array( "connects_to" ) ); + } + if( jo.has_member( "rotates_to" ) ) { + rotate_to_groups.reset(); + set_rotates_to( jo.get_as_string_array( "rotates_to" ) ); + } + optional( jo, was_loaded, "coverage", coverage ); + optional( jo, was_loaded, "curtain_transform", curtain_transform ); + optional( jo, was_loaded, "emissions", emissions ); + if( jo.has_string( "examine_action" ) ) { examine_actor = nullptr; examine_func = iexamine_functions_from_string( jo.get_string( "examine_action" ) ); @@ -981,6 +971,25 @@ void map_data_common_t::load( const JsonObject &jo, const std::string &src ) examine_func = iexamine_functions_from_string( "none" ); } + if( was_loaded && jo.has_member( "flags" ) ) { + unset_flags(); + } + for( auto &flag : jo.get_string_array( "flags" ) ) { + set_flag( flag ); + } + if( was_loaded && jo.has_member( "extend" ) ) { + JsonObject joe = jo.get_object( "extend" ); + for( auto &flag : joe.get_string_array( "flags" ) ) { + set_flag( flag ); + } + } + if( was_loaded && jo.has_member( "delete" ) ) { + JsonObject jod = jo.get_object( "delete" ); + for( auto &flag : jod.get_string_array( "flags" ) ) { + unset_flag( flag ); + } + } + if( jo.has_array( "harvest_by_season" ) ) { for( JsonObject harvest_jo : jo.get_array( "harvest_by_season" ) ) { auto season_strings = harvest_jo.get_tags( "seasons" ); @@ -1010,13 +1019,20 @@ void map_data_common_t::load( const JsonObject &jo, const std::string &src ) } } - mandatory( jo, was_loaded, "description", description ); optional( jo, was_loaded, "curtain_transform", curtain_transform ); + int legacy_floor_bedding_warmth = units::to_legacy_bodypart_temp_delta( + floor_bedding_warmth ); //TODO: Should be in map_data_common_t::load? + optional( jo, was_loaded, "floor_bedding_warmth", legacy_floor_bedding_warmth, 0 ); + floor_bedding_warmth = units::from_legacy_bodypart_temp_delta( legacy_floor_bedding_warmth ); + + optional( jo, was_loaded, "lockpick_message", lockpick_message, translation() ); + optional( jo, was_loaded, "light_emitted", light_emitted ); if( jo.has_object( "shoot" ) ) { shoot = cata::make_value(); shoot->load( jo, "shoot", was_loaded ); } + } bool ter_t::is_null() const @@ -1027,41 +1043,14 @@ bool ter_t::is_null() const void ter_t::load( const JsonObject &jo, const std::string &src ) { map_data_common_t::load( jo, src ); - mandatory( jo, was_loaded, "name", name_ ); mandatory( jo, was_loaded, "move_cost", movecost ); - optional( jo, was_loaded, "coverage", coverage ); assign( jo, "max_volume", max_volume, src == "dda" ); optional( jo, was_loaded, "trap", trap_id_str ); optional( jo, was_loaded, "heat_radiation", heat_radiation ); - optional( jo, was_loaded, "light_emitted", light_emitted ); - int legacy_floor_bedding_warmth = units::to_legacy_bodypart_temp_delta( floor_bedding_warmth ); - optional( jo, was_loaded, "floor_bedding_warmth", legacy_floor_bedding_warmth, 0 ); - floor_bedding_warmth = units::from_legacy_bodypart_temp_delta( legacy_floor_bedding_warmth ); - optional( jo, was_loaded, "comfort", comfort, 0 ); load_symbol( jo, "terrain " + id.str() ); trap = tr_null; - transparent = false; - connect_groups.reset(); - connect_to_groups.reset(); - rotate_to_groups.reset(); - - for( auto &flag : jo.get_string_array( "flags" ) ) { - set_flag( flag ); - } - // connect_to_groups is initialized to none, then terrain flags are set, then finally - // connections from JSON are set. This is so that wall flags can set wall connections - // but can be overridden by explicit connections in JSON. - if( jo.has_member( "connect_groups" ) ) { - set_connect_groups( jo.get_as_string_array( "connect_groups" ) ); - } - if( jo.has_member( "connects_to" ) ) { - set_connects_to( jo.get_as_string_array( "connects_to" ) ); - } - if( jo.has_member( "rotates_to" ) ) { - set_rotates_to( jo.get_as_string_array( "rotates_to" ) ); - } optional( jo, was_loaded, "allowed_template_ids", allowed_template_id ); @@ -1071,10 +1060,9 @@ void ter_t::load( const JsonObject &jo, const std::string &src ) optional( jo, was_loaded, "roof", roof, ter_str_id::NULL_ID() ); optional( jo, was_loaded, "lockpick_result", lockpick_result, ter_str_id::NULL_ID() ); - optional( jo, was_loaded, "lockpick_message", lockpick_message, translation() ); oxytorch = cata::make_value(); - if( jo.has_object( "oxytorch" ) ) { + if( jo.has_object( "oxytorch" ) ) { //TODO: Make overwriting these with eg "oxytorch": { } work while still allowing overwriting single values oxytorch->load( jo.get_object( "oxytorch" ) ); } @@ -1093,57 +1081,93 @@ void ter_t::load( const JsonObject &jo, const std::string &src ) prying->load( jo.get_object( "prying" ) ); } - optional( jo, was_loaded, "emissions", emissions ); - - bash.load( jo, "bash", map_bash_info::terrain, "terrain " + id.str() ); - deconstruct.load( jo, "deconstruct", false, "terrain " + id.str() ); + if( jo.has_object( "bash" ) ) { + if( !bash ) { + bash.emplace(); + } + bash->load( jo.get_object( "bash" ), was_loaded, + "terrain " + + id.str() ); //TODO: Make overwriting these with "bash": { } works while still allowing overwriting single values ie for "ter_set" + } + if( jo.has_object( "deconstruct" ) ) { + if( !deconstruct ) { + deconstruct.emplace(); + } + deconstruct->load( jo.get_object( "deconstruct" ), was_loaded, "terrain " + id.str() ); + } } -static void check_bash_items( const map_bash_info &mbi, const std::string &id, bool is_terrain ) +void map_common_bash_info::check( const std::string &id ) const { - if( !item_group::group_is_defined( mbi.drop_group ) ) { - debugmsg( "%s: bash result item group %s does not exist", id.c_str(), mbi.drop_group.c_str() ); + if( !drop_group.is_empty() ) { + if( !item_group::group_is_defined( drop_group ) ) { + debugmsg( "%s: bash result item group %s does not exist", id, drop_group.c_str() ); + } } - if( mbi.str_max != -1 ) { - if( is_terrain && mbi.ter_set.is_empty() ) { // Some tiles specify t_null explicitly - debugmsg( "bash result terrain of %s is undefined/empty", id.c_str() ); +} +void map_ter_bash_info::check( const std::string &id ) const +{ + map_common_bash_info::check( id ); + if( str_max != -1 ) { + if( ter_set.is_empty() ) { // Some tiles specify t_null explicitly + debugmsg( "bash result terrain of %s is undefined/empty", id ); } - if( !mbi.ter_set.is_valid() ) { - debugmsg( "bash result terrain %s of %s does not exist", mbi.ter_set.c_str(), id.c_str() ); + if( !ter_set.is_valid() ) { + debugmsg( "bash result terrain %s of %s does not exist", ter_set.c_str(), id ); } - if( !mbi.furn_set.is_valid() ) { - debugmsg( "bash result furniture %s of %s does not exist", mbi.furn_set.c_str(), id.c_str() ); + } +} +void map_furn_bash_info::check( const std::string &id ) const +{ + map_common_bash_info::check( id ); + if( str_max != -1 ) { + if( !furn_set.is_valid() ) { + debugmsg( "bash result furniture %s of %s does not exist", furn_set.c_str(), id ); } } } +void map_fd_bash_info::check( const std::string &id ) const +{ + map_common_bash_info::check( id ); +} -static void check_decon_items( const map_deconstruct_info &mbi, const std::string &id, - bool is_terrain ) +void map_common_deconstruct_info::check( const std::string &id ) const { - if( !mbi.can_do ) { - return; + if( !item_group::group_is_defined( drop_group ) ) { + debugmsg( "%s: deconstruct result item group %s does not exist", id, drop_group.c_str() ); } - if( !item_group::group_is_defined( mbi.drop_group ) ) { - debugmsg( "%s: deconstruct result item group %s does not exist", id.c_str(), - mbi.drop_group.c_str() ); - } - if( is_terrain && mbi.ter_set.is_empty() ) { // Some tiles specify t_null explicitly - debugmsg( "deconstruct result terrain of %s is undefined/empty", id.c_str() ); - } - if( !mbi.ter_set.is_valid() ) { - debugmsg( "deconstruct result terrain %s of %s does not exist", mbi.ter_set.c_str(), id.c_str() ); +} + +void map_ter_deconstruct_info::check( const std::string &id ) const +{ + if( !ter_set.is_valid() ) { + debugmsg( "deconstruct result terrain %s of %s does not exist", ter_set.c_str(), id ); } - if( !mbi.furn_set.is_valid() ) { - debugmsg( "deconstruct result furniture %s of %s does not exist", mbi.furn_set.c_str(), - id.c_str() ); + map_common_deconstruct_info::check( id ); +} + +void map_furn_deconstruct_info::check( const std::string &id ) const +{ + if( !furn_set.is_valid() ) { + debugmsg( "deconstruct result furniture %s of %s does not exist", furn_set.c_str(), id ); } + map_common_deconstruct_info::check( id ); +} + +bool ter_t::is_smashable() const +{ + return bash && !bash->bash_below; } void ter_t::check() const { map_data_common_t::check(); - check_bash_items( bash, id.str(), true ); - check_decon_items( deconstruct, id.str(), true ); + if( bash ) { + bash->check( id.c_str() ); + } + if( deconstruct ) { + deconstruct->check( id.c_str() ); + } if( !transforms_into.is_valid() ) { debugmsg( "invalid transforms_into %s for %s", transforms_into.c_str(), id.c_str() ); @@ -1186,9 +1210,9 @@ void ter_t::check() const debugmsg( "ter %s has invalid emission %s set", id.c_str(), e.str().c_str() ); } } - if( has_flag( ter_furn_flag::TFLAG_EASY_DECONSTRUCT ) && !deconstruct.can_do ) { + if( has_flag( ter_furn_flag::TFLAG_EASY_DECONSTRUCT ) && !deconstruct ) { debugmsg( "ter %s has EASY_DECONSTRUCT flag but cannot be deconstructed", - id.c_str(), deconstruct.drop_group.c_str() ); + id.c_str() ); } } @@ -1207,15 +1231,8 @@ bool furn_t::is_movable() const void furn_t::load( const JsonObject &jo, const std::string &src ) { map_data_common_t::load( jo, src ); - mandatory( jo, was_loaded, "name", name_ ); mandatory( jo, was_loaded, "move_cost_mod", movecost ); - optional( jo, was_loaded, "coverage", coverage ); - optional( jo, was_loaded, "comfort", comfort, 0 ); optional( jo, was_loaded, "fall_damage_reduction", fall_damage_reduction, 0 ); - int legacy_floor_bedding_warmth = units::to_legacy_bodypart_temp_delta( floor_bedding_warmth ); - optional( jo, was_loaded, "floor_bedding_warmth", legacy_floor_bedding_warmth, 0 ); - floor_bedding_warmth = units::from_legacy_bodypart_temp_delta( legacy_floor_bedding_warmth ); - optional( jo, was_loaded, "emissions", emissions ); int legacy_bonus_fire_warmth_feet = units::to_legacy_bodypart_temp_delta( bonus_fire_warmth_feet ); optional( jo, was_loaded, "bonus_fire_warmth_feet", legacy_bonus_fire_warmth_feet, 300 ); bonus_fire_warmth_feet = units::from_legacy_bodypart_temp_delta( legacy_bonus_fire_warmth_feet ); @@ -1225,35 +1242,12 @@ void furn_t::load( const JsonObject &jo, const std::string &src ) optional( jo, was_loaded, "crafting_pseudo_item", crafting_pseudo_item, itype_id() ); optional( jo, was_loaded, "deployed_item", deployed_item ); load_symbol( jo, "furniture " + id.str() ); - transparent = false; - - optional( jo, was_loaded, "light_emitted", light_emitted ); - - // see the comment in ter_id::load for connect_group handling - connect_groups.reset(); - connect_to_groups.reset(); - rotate_to_groups.reset(); - - for( auto &flag : jo.get_string_array( "flags" ) ) { - set_flag( flag ); - } - - if( jo.has_member( "connect_groups" ) ) { - set_connect_groups( jo.get_as_string_array( "connect_groups" ) ); - } - if( jo.has_member( "connects_to" ) ) { - set_connects_to( jo.get_as_string_array( "connects_to" ) ); - } - if( jo.has_member( "rotates_to" ) ) { - set_rotates_to( jo.get_as_string_array( "rotates_to" ) ); - } optional( jo, was_loaded, "open", open, string_id_reader {}, furn_str_id::NULL_ID() ); optional( jo, was_loaded, "close", close, string_id_reader {}, furn_str_id::NULL_ID() ); optional( jo, was_loaded, "lockpick_result", lockpick_result, string_id_reader {}, furn_str_id::NULL_ID() ); - optional( jo, was_loaded, "lockpick_message", lockpick_message, translation() ); oxytorch = cata::make_value(); if( jo.has_object( "oxytorch" ) ) { @@ -1275,8 +1269,18 @@ void furn_t::load( const JsonObject &jo, const std::string &src ) prying->load( jo.get_object( "prying" ) ); } - bash.load( jo, "bash", map_bash_info::furniture, "furniture " + id.str() ); - deconstruct.load( jo, "deconstruct", true, "furniture " + id.str() ); + if( jo.has_object( "bash" ) ) { + if( !bash ) { + bash.emplace(); + } + bash->load( jo.get_object( "bash" ), was_loaded, "furniture " + id.str() ); + } + if( jo.has_object( "deconstruct" ) ) { + if( !deconstruct ) { + deconstruct.emplace(); + } + deconstruct->load( jo.get_object( "deconstruct" ), was_loaded, "furniture " + id.str() ); + } if( jo.has_object( "workbench" ) ) { workbench = cata::make_value(); @@ -1291,11 +1295,20 @@ void furn_t::load( const JsonObject &jo, const std::string &src ) } } +bool furn_t::is_smashable() const +{ + return bash && !bash->bash_below; +} + void furn_t::check() const { map_data_common_t::check(); - check_bash_items( bash, id.str(), false ); - check_decon_items( deconstruct, id.str(), false ); + if( bash ) { + bash->check( id.c_str() ); + } + if( deconstruct ) { + deconstruct->check( id.c_str() ); + } if( !open.is_valid() ) { debugmsg( "invalid furniture %s for opening %s", open.c_str(), id.c_str() ); diff --git a/src/mapdata.h b/src/mapdata.h index e93239bfafe54..433c55f07ec10 100644 --- a/src/mapdata.h +++ b/src/mapdata.h @@ -38,37 +38,47 @@ connect_group get_connect_group( const std::string &name ); template struct enum_traits; -struct map_bash_info { - int str_min; // min str(*) required to bash - int str_max; // max str required: bash succeeds if str >= random # between str_min & str_max - int str_min_blocked; // same as above; alternate values for has_adjacent_furniture(...) == true - int str_max_blocked; - int str_min_supported; // Alternative values for floor supported by something from below - int str_max_supported; - int explosive; // Explosion on destruction - int sound_vol; // sound volume of breaking terrain/furniture - int sound_fail_vol; // sound volume on fail - int collapse_radius; // Radius of the tent supported by this tile - int fd_bash_move_cost = 100; // cost to bash a field - bool destroy_only; // Only used for destroying, not normally bashable - bool bash_below; // This terrain is the roof of the tile below it, try to destroy that too - item_group_id drop_group; // item group of items that are dropped when the object is bashed - translation sound; // sound made on success ('You hear a "smash!"') - translation sound_fail; // sound made on fail - translation field_bash_msg_success; // message upon successfully bashing a field - ter_str_id ter_set; // terrain to set (REQUIRED for terrain)) +struct map_common_bash_info { //TODO: Half of this shouldn't be common + int str_min; // min str(*) required to bash + int str_max; // max str required: bash succeeds if str >= random # between str_min & str_max + int str_min_blocked; // same as above; alternate values for has_adjacent_furniture(...) == true + int str_max_blocked; + int str_min_supported; // Alternative values for floor supported by something from below + int str_max_supported; + int explosive; // Explosion on destruction + int sound_vol; // sound volume of breaking terrain/furniture + int sound_fail_vol; // sound volume on fail + int collapse_radius; // Radius of the tent supported by this tile + bool destroy_only; // Only used for destroying, not normally bashable + bool bash_below; // This terrain is the roof of the tile below it, try to destroy that too + item_group_id drop_group; // item group of items that are dropped when the object is bashed + translation sound; // sound made on success ('You hear a "smash!"') + translation sound_fail; // sound made on fail + std::vector tent_centers; + void load( const JsonObject &jo, bool was_loaded, const std::string &context ); + void check( const std::string &id ) const; + public: + virtual ~map_common_bash_info() = default; +}; +struct map_ter_bash_info : map_common_bash_info { + ter_str_id ter_set; // terrain to set ter_str_id ter_set_bashed_from_above; // terrain to set if bashed from above (defaults to ter_set) + map_ter_bash_info() = default; + void load( const JsonObject &jo, bool was_loaded, const std::string &context ); + void check( const std::string &id ) const; +}; +struct map_furn_bash_info : map_common_bash_info { furn_str_id furn_set; // furniture to set (only used by furniture, not terrain) - // ids used for the special handling of tents - std::vector tent_centers; - map_bash_info(); - enum map_object_type { - furniture = 0, - terrain, - field - }; - bool load( const JsonObject &jsobj, std::string_view member, map_object_type obj_type, - const std::string &context ); + map_furn_bash_info() = default;; + void load( const JsonObject &jo, bool was_loaded, const std::string &context ); + void check( const std::string &id ) const; +}; +struct map_fd_bash_info : map_common_bash_info { + int fd_bash_move_cost; // cost to bash a field + translation field_bash_msg_success; // message upon successfully bashing a field + map_fd_bash_info() = default; + void load( const JsonObject &jo, bool was_loaded, const std::string &context ); + void check( const std::string &id ) const; }; struct map_deconstruct_skill { skill_id id; // Id of skill to increase on successful deconstruction @@ -76,19 +86,28 @@ struct map_deconstruct_skill { int max; // Level cap after which no xp is recieved but practise still occurs delaying rust double multiplier; // Multiplier of the base xp given that's calced using the mean of the min and max }; -struct map_deconstruct_info { - // Only if true, the terrain/furniture can be deconstructed - bool can_do; - // This terrain provided a roof, we need to tear it down now - bool deconstruct_above; - // items you get when deconstructing. - item_group_id drop_group; - ter_str_id ter_set; // terrain to set (REQUIRED for terrain)) - furn_str_id furn_set; // furniture to set (only used by furniture, not terrain) - map_deconstruct_info(); - std::optional skill; - bool load( const JsonObject &jsobj, std::string_view member, bool is_furniture, - const std::string &context ); +struct map_common_deconstruct_info { + // This terrain provided a roof, we need to tear it down now + bool deconstruct_above = false; + // items you get when deconstructing. + item_group_id drop_group; + std::optional skill; + virtual void load( const JsonObject &jo, bool was_loaded, const std::string &context ); + virtual void check( const std::string &id ) const; + public: + virtual ~map_common_deconstruct_info() = default; +}; +struct map_ter_deconstruct_info : map_common_deconstruct_info { + ter_str_id ter_set = ter_str_id::NULL_ID(); + void load( const JsonObject &jo, bool was_loaded, const std::string &context ) override; + void check( const std::string &id ) const override; + map_ter_deconstruct_info() = default; +}; +struct map_furn_deconstruct_info : map_common_deconstruct_info { + furn_str_id furn_set = furn_str_id::NULL_ID(); + void load( const JsonObject &jo, bool was_loaded, const std::string &context ) override; + void check( const std::string &id ) const override; + map_furn_deconstruct_info() = default; }; struct map_shoot_info { // Base chance to hit the object at all (defaults to 100%) @@ -451,9 +470,10 @@ class activity_data_furn : public activity_data_common void init_mapdata(); +//handles data common between terrain and furniture struct map_data_common_t { - map_bash_info bash; - map_deconstruct_info deconstruct; + std::set emissions; + translation lockpick_message; // Lockpick action: message when successfully lockpicked cata::value_ptr shoot; public: @@ -462,17 +482,17 @@ struct map_data_common_t { protected: friend furn_t null_furniture_t(); friend ter_t null_terrain_t(); - // The (untranslated) plaintext name of the terrain type the user would see (i.e. dirt) + // The (untranslated) plaintext name of the terrain/furniture type the user would see (i.e. dirt) translation name_; - // Hardcoded examination function - iexamine_functions examine_func; // What happens when the terrain/furniture is examined + // Hardcoded examination function; what happens when the terrain/furniture is examined + iexamine_functions examine_func; // Data-driven examine actor cata::clone_ptr examine_actor; private: - std::set flags; // string flags which possibly refer to what's documented above. + std::set flags; // string flags which possibly refer to what's documented above. enum_bitset bitflags; // bitfield of -certain- string flags which are heavily checked public: @@ -485,7 +505,7 @@ struct map_data_common_t { std::string name() const; /* - * The symbol drawn on the screen for the terrain. Please note that + * The symbol drawn on the screen for the terrain/furniture. Please note that * there are extensive rules as to which possible object/field/entity in * a single square gets drawn and that some symbols are "reserved" such * as * and % to do programmatic behavior. @@ -502,14 +522,14 @@ struct map_data_common_t { // The amount of movement points required to pass this terrain by default. int movecost = 0; int heat_radiation = 0; - // The coverage percentage of a furniture piece of terrain. <30 won't cover from sight. + // The coverage percentage of furniture/terrain. coverage < 30 won't cover from sight. int coverage = 0; - // Warmth provided by the terrain (for sleeping, etc.) + // Warmth provided by the furniture/terrain (for sleeping, etc.) units::temperature_delta floor_bedding_warmth = 0_C_delta; int comfort = 0; //flat damage reduction (increase if negative) on fall (some logic may apply) int fall_damage_reduction = 0; - // Maximal volume of items that can be stored in/on this furniture + // Maximal volume of items that can be stored in/on this furniture/terrain units::volume max_volume = DEFAULT_TILE_VOLUME; std::string liquid_source_item_id; // id of a liquid this tile provides @@ -525,7 +545,7 @@ struct map_data_common_t { std::string looks_like; /** - * When will this terrain/furniture get harvested and what will drop? + * When will this furniture/terrain get harvested and what will drop? * Note: This excludes items that take extra tools to harvest. */ std::array harvest_by_season = {{ @@ -547,11 +567,10 @@ struct map_data_common_t { return bitflags[flag]; } - void extraprocess_flags( ter_furn_flag flag ); - void set_flag( const std::string &flag ); - void set_flag( ter_furn_flag flag ); + void unset_flag( const std::string &flag ); + void unset_flags(); // Terrain groups of this type, for others to connect or rotate to; not symmetric, passive part std::bitset connect_groups; @@ -588,6 +607,9 @@ struct map_data_common_t { virtual std::vector extended_description() const; bool was_loaded = false; + virtual bool is_smashable() const { + return false; + } bool is_flammable() const { return has_flag( ter_furn_flag::TFLAG_FLAMMABLE ) || @@ -611,8 +633,10 @@ struct ter_t : map_data_common_t { ter_str_id open; // Open action: transform into terrain with matching id ter_str_id close; // Close action: transform into terrain with matching id + std::optional bash; + std::optional deconstruct; + ter_str_id lockpick_result; // Lockpick action: transform when successfully lockpicked - translation lockpick_message; // Lockpick action: message when successfully lockpicked cata::value_ptr boltcut; // Bolt cutting action data cata::value_ptr hacksaw; // Hacksaw action data @@ -625,7 +649,6 @@ struct ter_t : map_data_common_t { trap_id trap; // The id of the trap located at this terrain. Limit one trap per tile currently. - std::set emissions; std::set allowed_template_id; ter_t(); @@ -635,6 +658,7 @@ struct ter_t : map_data_common_t { bool is_null() const; std::vector extended_description() const override; + bool is_smashable() const override; void load( const JsonObject &jo, const std::string &src ) override; void check() const override; @@ -656,11 +680,10 @@ struct furn_t : map_data_common_t { furn_str_id open; // Open action: transform into furniture with matching id furn_str_id close; // Close action: transform into furniture with matching id furn_str_id lockpick_result; // Lockpick action: transform when successfully lockpicked - translation lockpick_message; // Lockpick action: message when successfully lockpicked + std::optional bash; + std::optional deconstruct; itype_id crafting_pseudo_item; units::volume keg_capacity = 0_ml; - /** Emissions of furniture */ - std::set emissions; units::temperature_delta bonus_fire_warmth_feet = 0.6_C_delta; itype_id deployed_item; // item id string used to create furniture @@ -690,6 +713,7 @@ struct furn_t : map_data_common_t { bool is_movable() const; std::vector extended_description() const override; + bool is_smashable() const override; void load( const JsonObject &jo, const std::string &src ) override; void check() const override; @@ -698,9 +722,6 @@ struct furn_t : map_data_common_t { void load_furniture( const JsonObject &jo, const std::string &src ); void load_terrain( const JsonObject &jo, const std::string &src ); -void verify_furniture(); -void verify_terrain(); - class ter_furn_migrations { public: @@ -745,6 +766,4 @@ extern ter_id t_null; // consistency checking of terlist & furnlist. void check_furniture_and_terrain(); -void finalize_furniture_and_terrain(); - #endif // CATA_SRC_MAPDATA_H diff --git a/src/mapgen.cpp b/src/mapgen.cpp index 771693034354c..954121560c96f 100644 --- a/src/mapgen.cpp +++ b/src/mapgen.cpp @@ -3112,20 +3112,23 @@ class jmapgen_terrain : public jmapgen_piece_with_has_vehicle_collision p ).id().str() : ""; while( dat.m.has_furn( p ) && max_recurse-- > 0 ) { const furn_t &f = dat.m.furn( p ).obj(); - if( f.deconstruct.can_do ) { - if( f.deconstruct.furn_set.str().empty() ) { + if( f.deconstruct ) { + if( f.deconstruct->furn_set.str().empty() ) { dat.m.furn_clear( p ); } else { - dat.m.furn_set( p, f.deconstruct.furn_set ); + dat.m.furn_set( p, f.deconstruct->furn_set ); } - dat.m.spawn_items( p, item_group::items_from( f.deconstruct.drop_group, calendar::turn ) ); + dat.m.spawn_items( p, item_group::items_from( f.deconstruct->drop_group, calendar::turn ) ); } else { - if( f.bash.furn_set.str().empty() ) { - dat.m.furn_clear( p ); - } else { - dat.m.furn_set( p, f.bash.furn_set ); + const std::optional &furn_bash = f.bash; + if( furn_bash ) { + if( furn_bash->furn_set.str().empty() ) { + dat.m.furn_clear( p ); + } else { + dat.m.furn_set( p, furn_bash->furn_set ); + } + dat.m.spawn_items( p, item_group::items_from( furn_bash->drop_group, calendar::turn ) ); } - dat.m.spawn_items( p, item_group::items_from( f.bash.drop_group, calendar::turn ) ); } } if( !max_recurse ) { diff --git a/src/mission_companion.cpp b/src/mission_companion.cpp index 30197c6ddb57d..a64cc2b4e2f44 100644 --- a/src/mission_companion.cpp +++ b/src/mission_companion.cpp @@ -2800,45 +2800,56 @@ std::set talk_function::loot_building( const tripoint_abs_omt &site, std::set return_items; for( const tripoint_omt_ms &p : bay.points_on_zlevel() ) { const ter_id &t = bay.ter( p ); - const ter_t &to = t.obj(); //Open all the doors, doesn't need to be exhaustive const std::unordered_set openable_doors = {ter_t_door_c, ter_t_door_c_peep, ter_t_door_b, ter_t_door_boarded, ter_t_door_boarded_damaged, ter_t_rdoor_boarded, ter_t_rdoor_boarded_damaged, ter_t_door_boarded_peep, ter_t_door_boarded_damaged_peep }; if( openable_doors.find( t.id() ) != openable_doors.end() ) { bay.ter_set( p, ter_t_door_o ); } else if( t == ter_t_door_locked || t == ter_t_door_locked_peep || t == ter_t_door_locked_alarm ) { - const map_bash_info &bash = to.bash; - bay.ter_set( p, bash.ter_set ); + const std::optional &bash = bay.ter( p ).obj().bash; + if( bash ) { + bay.ter_set( p, bash->ter_set ); + } // Bash doors twice - const map_bash_info &bash_again = to.bash; - bay.ter_set( p, bash_again.ter_set ); - bay.spawn_items( p, item_group::items_from( bash.drop_group, calendar::turn ) ); - bay.spawn_items( p, item_group::items_from( bash_again.drop_group, calendar::turn ) ); + const std::optional &bash_again = bay.ter( p ).obj().bash; + if( bash_again ) { + bay.ter_set( p, bash_again->ter_set ); + bay.spawn_items( p, item_group::items_from( bash->drop_group, calendar::turn ) ); + bay.spawn_items( p, item_group::items_from( bash_again->drop_group, calendar::turn ) ); + } } else if( t == ter_t_door_metal_c || t == ter_t_door_metal_locked || t == ter_t_door_metal_pickable ) { bay.ter_set( p, ter_t_door_metal_o ); } else if( t == ter_t_door_glass_c ) { bay.ter_set( p, ter_t_door_glass_o ); } else if( t == ter_t_wall && one_in( 25 ) ) { - const map_bash_info &bash = to.bash; - bay.ter_set( p, bash.ter_set ); - bay.spawn_items( p, item_group::items_from( bash.drop_group, calendar::turn ) ); - bay.collapse_at( p, false ); + const std::optional &bash = bay.ter( p ).obj().bash; + if( bash ) { + bay.ter_set( p, bash->ter_set ); + bay.spawn_items( p, item_group::items_from( bash->drop_group, calendar::turn ) ); + bay.collapse_at( p, false ); + } } //Smash easily breakable stuff else if( const std::unordered_set weak_window_ters = {ter_t_window, ter_t_window_taped, ter_t_window_domestic, ter_t_window_boarded_noglass, ter_t_window_domestic_taped, ter_t_window_alarm_taped, ter_t_window_boarded, ter_t_curtains, ter_t_window_alarm, ter_t_window_no_curtains, ter_t_window_no_curtains_taped }; weak_window_ters.find( t.id() ) != weak_window_ters.end() && one_in( 4 ) ) { - const map_bash_info &bash = to.bash; - bay.ter_set( p, bash.ter_set ); - bay.spawn_items( p, item_group::items_from( bash.drop_group, calendar::turn ) ); + const std::optional &bash = bay.ter( p ).obj().bash; + if( bash ) { + bay.ter_set( p, bash->ter_set ); + bay.spawn_items( p, item_group::items_from( bash->drop_group, calendar::turn ) ); + } } else if( ( t == ter_t_wall_glass || t == ter_t_wall_glass_alarm ) && one_in( 3 ) ) { - const map_bash_info &bash = to.bash; - bay.ter_set( p, bash.ter_set ); - bay.spawn_items( p, item_group::items_from( bash.drop_group, calendar::turn ) ); - } else if( bay.has_furn( p ) && to.bash.str_max != -1 && one_in( 10 ) ) { - const map_bash_info &bash = to.bash; - bay.furn_set( p, bash.furn_set ); - bay.delete_signage( p ); - bay.spawn_items( p, item_group::items_from( bash.drop_group, calendar::turn ) ); + const std::optional &bash = bay.ter( p ).obj().bash; + if( bash ) { + bay.ter_set( p, bash->ter_set ); + bay.spawn_items( p, item_group::items_from( bash->drop_group, calendar::turn ) ); + } + } else if( bay.has_furn( p ) && bay.furn( p ).obj().bash && one_in( 10 ) ) { + const std::optional &bash = bay.furn( p ).obj().bash; + if( bash ) { + bay.furn_set( p, bash->furn_set ); + bay.delete_signage( p ); + bay.spawn_items( p, item_group::items_from( bash->drop_group, calendar::turn ) ); + } } //Kill zombies! Only works against pre-spawned enemies at the moment... Creature *critter = creatures.creature_at( rebase_bub( p ) ); diff --git a/src/vehicle_autodrive.cpp b/src/vehicle_autodrive.cpp index 9578a69a9f267..cbc1b498c8d26 100644 --- a/src/vehicle_autodrive.cpp +++ b/src/vehicle_autodrive.cpp @@ -778,7 +778,7 @@ bool vehicle::autodrive_controller::check_drivable( const tripoint_bub_ms &pt ) // terrain with neutral move cost or tagged with NOCOLLIDE will never cause // collisions return true; - } else if( terrain_type.bash.str_max >= 0 && !terrain_type.bash.bash_below ) { + } else if( terrain_type.is_smashable() ) { // bashable terrain (but not bashable floors) will cause collisions return false; } else if( terrain_type.has_flag( ter_furn_flag::TFLAG_LIQUID ) ) { diff --git a/tests/map_bash_test.cpp b/tests/map_bash_test.cpp index bc25eb8fed249..80c6b7b8cc14f 100644 --- a/tests/map_bash_test.cpp +++ b/tests/map_bash_test.cpp @@ -100,10 +100,10 @@ TEST_CASE( "map_bash_ephemeral_persistence", "[map][bash]" ) const tripoint_bub_ms test_pt( 40, 40, 0 ); // Assumptions - REQUIRE( furn_test_f_bash_persist->bash.str_min == 4 ); - REQUIRE( furn_test_f_bash_persist->bash.str_max == 100 ); - REQUIRE( ter_test_t_bash_persist->bash.str_min == 4 ); - REQUIRE( ter_test_t_bash_persist->bash.str_max == 100 ); + REQUIRE( furn_test_f_bash_persist->bash->str_min == 4 ); + REQUIRE( furn_test_f_bash_persist->bash->str_max == 100 ); + REQUIRE( ter_test_t_bash_persist->bash->str_min == 4 ); + REQUIRE( ter_test_t_bash_persist->bash->str_max == 100 ); SECTION( "bashing a furniture to completion leaves behind no map bash info" ) { here.furn_set( test_pt, furn_test_f_bash_persist );