From b768c7e1b771998635f317d36ec68f1798bd876f Mon Sep 17 00:00:00 2001 From: BevapDin Date: Sun, 22 Sep 2019 14:25:47 +0200 Subject: [PATCH] Fix vehicle accessing the main map during mapgen: Add a handler parameter to `vehicle::remove_part` that handles map interactions (and similar stuff outside the reach of the vehicle). This handler is different when the function is called during mapgen and interacts with the temporary mapgen map in that case. --- src/map_extras.cpp | 4 +- src/mapgen.cpp | 4 +- src/monmove.cpp | 2 +- src/vehicle.cpp | 209 +++++++++++++++++++++++++++++++++------------ src/vehicle.h | 15 +++- 5 files changed, 170 insertions(+), 64 deletions(-) diff --git a/src/map_extras.cpp b/src/map_extras.cpp index 933a1803d307f..59ee5bc291e4d 100644 --- a/src/map_extras.cpp +++ b/src/map_extras.cpp @@ -412,9 +412,9 @@ static void mx_helicopter( map &m, const tripoint &abs_sub ) break; } if( !one_in( 4 ) ) { - wreckage->smash( 0.8f, 1.2f, 1.0f, point( dice( 1, 8 ) - 5, dice( 1, 8 ) - 5 ), 6 + dice( 1, 10 ) ); + wreckage->smash( m, 0.8f, 1.2f, 1.0f, point( dice( 1, 8 ) - 5, dice( 1, 8 ) - 5 ), 6 + dice( 1, 10 ) ); } else { - wreckage->smash( 0.1f, 0.9f, 1.0f, point( dice( 1, 8 ) - 5, dice( 1, 8 ) - 5 ), 6 + dice( 1, 10 ) ); + wreckage->smash( m, 0.1f, 0.9f, 1.0f, point( dice( 1, 8 ) - 5, dice( 1, 8 ) - 5 ), 6 + dice( 1, 10 ) ); } } } diff --git a/src/mapgen.cpp b/src/mapgen.cpp index e38c0975ad6f2..88e27a23ec4d8 100644 --- a/src/mapgen.cpp +++ b/src/mapgen.cpp @@ -6887,7 +6887,7 @@ std::unique_ptr map::add_vehicle_to_map( // Try again with the wreckage std::unique_ptr new_veh = add_vehicle_to_map( std::move( wreckage ), true ); if( new_veh != nullptr ) { - new_veh->smash(); + new_veh->smash( *this ); return new_veh; } @@ -6913,7 +6913,7 @@ std::unique_ptr map::add_vehicle_to_map( } if( needs_smashing ) { - veh->smash(); + veh->smash( *this ); } return veh; diff --git a/src/monmove.cpp b/src/monmove.cpp index 83e54059543e4..8c93691845761 100644 --- a/src/monmove.cpp +++ b/src/monmove.cpp @@ -1902,7 +1902,7 @@ void monster::shove_vehicle( const tripoint &remote_destination, g->m.move_vehicle( veh, shove_destination, veh.face ); } veh.move = tileray( point( destination_delta_x, destination_delta_y ) ); - veh.smash( shove_damage_min, shove_damage_max, 0.10F ); + veh.smash( g->m, shove_damage_min, shove_damage_max, 0.10F ); } } } diff --git a/src/vehicle.cpp b/src/vehicle.cpp index f94a72adf6273..c7eb505abb6d2 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -88,6 +88,121 @@ const int bat_energy_j = 1000; // Point dxs for the adjacent cardinal tiles. point vehicles::cardinal_d[5] = { point_west, point_east, point_north, point_south, point_zero }; +// For reference what each function is supposed to do, see their implementation in +// @ref DefaultRemovePartHandler. Add compatible code for it into @ref MapgenRemovePartHandler, +// if needed. +class RemovePartHandler +{ + public: + virtual ~RemovePartHandler() = default; + + virtual void unboard( const tripoint &loc ) = 0; + virtual void add_item_or_charges( const tripoint &loc, item it ) = 0; + virtual void set_transparency_cache_dirty( int z ) = 0; + virtual void removed( vehicle &veh, int part ) = 0; + virtual void spawn_animal_from_part( item &base, const tripoint &loc ) = 0; +}; + +class DefaultRemovePartHandler : public RemovePartHandler +{ + public: + ~DefaultRemovePartHandler() override = default; + + void unboard( const tripoint &loc ) override { + g->m.unboard_vehicle( loc ); + } + void add_item_or_charges( const tripoint &loc, item it ) override { + g->m.add_item_or_charges( loc, std::move( it ) ); + } + void set_transparency_cache_dirty( const int z ) override { + g->m.set_transparency_cache_dirty( z ); + } + void removed( vehicle &veh, const int part ) override { + // If the player is currently working on the removed part, stop them as it's futile now. + const player_activity &act = g->u.activity; + if( act.id() == activity_id( "ACT_VEHICLE" ) && act.moves_left > 0 && act.values.size() > 6 ) { + if( veh_pointer_or_null( g->m.veh_at( tripoint( act.values[0], act.values[1], + g->u.posz() ) ) ) == &veh ) { + if( act.values[6] >= part ) { + g->u.cancel_activity(); + add_msg( m_info, _( "The vehicle part you were working on has gone!" ) ); + } + } + } + // @todo maybe do this for all the nearby NPCs as well? + + if( g->u.get_grab_type() == OBJECT_VEHICLE && g->u.grab_point == veh.global_part_pos3( part ) ) { + if( veh.parts_at_relative( veh.parts[part].mount, false ).empty() ) { + add_msg( m_info, _( "The vehicle part you were holding has been destroyed!" ) ); + g->u.grab( OBJECT_NONE ); + } + } + + g->m.dirty_vehicle_list.insert( &veh ); + } + void spawn_animal_from_part( item &base, const tripoint &loc ) override { + tripoint target = loc; + bool spawn = true; + if( !g->is_empty( target ) ) { + std::vector valid; + for( const tripoint &dest : g->m.points_in_radius( target, 1 ) ) { + if( g->is_empty( dest ) ) { + valid.push_back( dest ); + } + } + if( valid.empty() ) { + spawn = false; + } else { + target = random_entry( valid ); + } + } + base.release_monster( target, spawn ); + } +}; + +class MapgenRemovePartHandler : public RemovePartHandler +{ + private: + map &m; + + public: + MapgenRemovePartHandler( map &m ) : m( m ) { } + + ~MapgenRemovePartHandler() override = default; + + void unboard( const tripoint &/*loc*/ ) override { + debugmsg( "Tried to unboard during mapgen!" ); + // Ignored. Will almost certainly not be called anyway, because + // there are no creatures that could have been mounted during mapgen. + } + void add_item_or_charges( const tripoint &loc, item it ) override { + if( !m.inbounds( loc ) ) { + debugmsg( "Tried to put item %s on invalid tile %d,%d,%d during mapgen!", it.tname(), loc.x, loc.y, + loc.z ); + tripoint copy = loc; + m.clip_to_bounds( copy ); + assert( m.inbounds( copy ) ); // prevent infinite recursion + add_item_or_charges( copy, std::move( it ) ); + return; + } + m.add_item_or_charges( loc, std::move( it ) ); + } + void set_transparency_cache_dirty( const int /*z*/ ) override { + // Ignored for now. We don't initialize the transparency cache in mapgen anyway. + } + void removed( vehicle &veh, const int /*part*/ ) override { + // @todo check if this is necessary, it probably isn't during mapgen + m.dirty_vehicle_list.insert( &veh ); + } + void spawn_animal_from_part( item &/*base*/, const tripoint &/*loc*/ ) override { + debugmsg( "Tried to spawn animal from vehicle part during mapgen!" ); + // Ignored. The base item will not be changed and will spawn as is: + // still containing the animal. + // This should not happend during mapgen anyway. + // @todo *if* this actually happens: create a spawn point for the animal instead. + } +}; + // Vehicle stack methods. vehicle_stack::iterator vehicle_stack::erase( vehicle_stack::const_iterator it ) { @@ -693,7 +808,7 @@ void vehicle::do_autodrive() * (ie, any spot with multiple frames) will be completely destroyed, as that * was the collision point. */ -void vehicle::smash( float hp_percent_loss_min, float hp_percent_loss_max, +void vehicle::smash( map &m, float hp_percent_loss_min, float hp_percent_loss_max, float percent_of_parts_to_affect, point damage_origin, float damage_size ) { for( auto &part : parts ) { @@ -734,6 +849,8 @@ void vehicle::smash( float hp_percent_loss_min, float hp_percent_loss_max, } } } + + std::unique_ptr handler_ptr; // clear out any duplicated locations for( int p = static_cast( parts.size() ) - 1; p >= 0; p-- ) { vehicle_part &part = parts[ p ]; @@ -749,7 +866,18 @@ void vehicle::smash( float hp_percent_loss_min, float hp_percent_loss_max, if( ( part_info( p ).location.empty() && part_info( p ).get_id() == part_info( other_p ).get_id() ) || ( part_info( p ).location == part_info( other_p ).location ) ) { - remove_part( other_p ); + // Deferred creation of the handler to here so it is only created when actually needed. + if( !handler_ptr ) { + // This is a heuristic: we just assume the default handler is good enough when called + // on the main game map. And assume that we run from some mapgen code if called on + // another instance. + if( g && &g->m == &m ) { + handler_ptr = std::make_unique(); + } else { + handler_ptr = std::make_unique( m ); + } + } + remove_part( other_p, *handler_ptr ); } } } @@ -1700,8 +1828,19 @@ bool vehicle::merge_rackable_vehicle( vehicle *carry_veh, const std::vector * Mark a part as removed from the vehicle. * @return bool true if the vehicle's 0,0 point shifted. */ -bool vehicle::remove_part( int p ) +bool vehicle::remove_part( const int p ) { + DefaultRemovePartHandler handler; + return remove_part( p, handler ); +} + +bool vehicle::remove_part( const int p, RemovePartHandler &handler ) +{ + // NOTE: Don't access g or g->m or anything from it directly here. + // Forward all access to the handler. + // There are currently two implementations of it: + // - one for normal game play (vehicle is on the main map g->m), + // - one for mapgen (vehicle is on a temporary map used only during mapgen). if( p >= static_cast( parts.size() ) ) { debugmsg( "Tried to remove part %d but only %d parts!", p, parts.size() ); return false; @@ -1717,13 +1856,8 @@ bool vehicle::remove_part( int p ) const tripoint part_loc = global_part_pos3( p ); // Unboard any entities standing on removed boardable parts - if( part_flag( p, "BOARDABLE" ) ) { - std::vector bp = boarded_parts(); - for( auto &elem : bp ) { - if( elem == p ) { - g->m.unboard_vehicle( part_loc ); - } - } + if( part_flag( p, "BOARDABLE" ) && parts[p].has_flag( vehicle_part::passenger_flag ) ) { + handler.unboard( part_loc ); } // If `p` has flag `parent_flag`, remove child with flag `child_flag` @@ -1733,9 +1867,8 @@ bool vehicle::remove_part( int p ) if( part_flag( p, parent_flag ) ) { int dep = part_with_feature( p, child_flag, false ); if( dep >= 0 ) { - item it = parts[dep].properties_to_item(); - g->m.add_item_or_charges( part_loc, it ); - remove_part( dep ); + handler.add_item_or_charges( part_loc, parts[dep].properties_to_item() ); + remove_part( dep, handler ); return true; } } @@ -1745,7 +1878,7 @@ bool vehicle::remove_part( int p ) // if a windshield is removed (usually destroyed) also remove curtains // attached to it. if( remove_dependent_part( "WINDOW", "CURTAIN" ) || part_flag( p, VPFLAG_OPAQUE ) ) { - g->m.set_transparency_cache_dirty( sm_pos.z ); + handler.set_transparency_cache_dirty( sm_pos.z ); } remove_dependent_part( "SEAT", "SEATBELT" ); @@ -1753,23 +1886,8 @@ bool vehicle::remove_part( int p ) // Release any animal held by the part if( parts[p].has_flag( vehicle_part::animal_flag ) ) { - tripoint target = part_loc; - bool spawn = true; - if( !g->is_empty( target ) ) { - std::vector valid; - for( const tripoint &dest : g->m.points_in_radius( target, 1 ) ) { - if( g->is_empty( dest ) ) { - valid.push_back( dest ); - } - } - if( valid.empty() ) { - spawn = false; - } else { - target = random_entry( valid ); - } - } item base = item( parts[p].get_base() ); - base.release_monster( target, spawn ); + handler.spawn_animal_from_part( base, part_loc ); parts[p].set_base( base ); parts[p].remove_flag( vehicle_part::animal_flag ); } @@ -1807,41 +1925,20 @@ bool vehicle::remove_part( int p ) parts[p].removed = true; removed_part_count++; - // If the player is currently working on the removed part, stop them as it's futile now. - const player_activity &act = g->u.activity; - if( act.id() == activity_id( "ACT_VEHICLE" ) && act.moves_left > 0 && act.values.size() > 6 ) { - if( veh_pointer_or_null( g->m.veh_at( tripoint( act.values[0], act.values[1], - g->u.posz() ) ) ) == this ) { - if( act.values[6] >= p ) { - g->u.cancel_activity(); - add_msg( m_info, _( "The vehicle part you were working on has gone!" ) ); - } - } - } + handler.removed( *this, p ); const point &vp_mount = parts[p].mount; const auto iter = labels.find( label( vp_mount ) ); - const bool no_label = iter != labels.end(); - const bool grab_found = g->u.get_grab_type() == OBJECT_VEHICLE && g->u.grab_point == part_loc; - // Checking these twice to avoid calling the relatively expensive parts_at_relative() unnecessarily. - if( no_label || grab_found ) { - if( parts_at_relative( vp_mount, false ).empty() ) { - if( no_label ) { - labels.erase( iter ); - } - if( grab_found ) { - add_msg( m_info, _( "The vehicle part you were holding has been destroyed!" ) ); - g->u.grab( OBJECT_NONE ); - } - } + if( iter != labels.end() && parts_at_relative( vp_mount, false ).empty() ) { + labels.erase( iter ); } for( auto &i : get_items( p ) ) { // Note: this can spawn items on the other side of the wall! + // @todo fix this ^^ tripoint dest( part_loc + point( rng( -3, 3 ), rng( -3, 3 ) ) ); - g->m.add_item_or_charges( dest, i ); + handler.add_item_or_charges( dest, i ); } - g->m.dirty_vehicle_list.insert( this ); refresh(); coeff_air_changed = true; return shift_if_needed(); diff --git a/src/vehicle.h b/src/vehicle.h index 6959d23b07db0..1a0b4c908be00 100644 --- a/src/vehicle.h +++ b/src/vehicle.h @@ -40,6 +40,7 @@ class Creature; class nc_color; class player; class npc; +class map; class vehicle; class vehicle_part_range; class JsonIn; @@ -524,6 +525,8 @@ struct label : public point { std::string text; }; +class RemovePartHandler; + /** * A vehicle as a whole with all its components. * @@ -720,9 +723,8 @@ class vehicle void init_state( int init_veh_fuel, int init_veh_status ); // damages all parts of a vehicle by a random amount - void smash( float hp_percent_loss_min = 0.1f, float hp_percent_loss_max = 1.2f, - float percent_of_parts_to_affect = 1.0f, point damage_origin = point_zero, - float damage_size = 0 ); + void smash( map &m, float hp_percent_loss_min = 0.1f, float hp_percent_loss_max = 1.2f, + float percent_of_parts_to_affect = 1.0f, point damage_origin = point_zero, float damage_size = 0 ); void serialize( JsonOut &json ) const; void deserialize( JsonIn &jsin ); @@ -804,6 +806,13 @@ class vehicle // merge a previously found single tile vehicle into this vehicle bool merge_rackable_vehicle( vehicle *carry_veh, const std::vector &rack_parts ); + /** + * @param handler A class that receives various callbacks, e.g. for placing items. + * This handler is different when called during mapgen (when items need to be placed + * on the temporary mapgen map), and when called during normal game play (when items + * go on the main map g->m). + */ + bool remove_part( int p, RemovePartHandler &handler ); bool remove_part( int p ); void part_removal_cleanup();