Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix vehicle accessing the main map during mapgen: #34179

Merged
merged 1 commit into from
Sep 22, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/map_extras.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 ) );
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/mapgen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6887,7 +6887,7 @@ std::unique_ptr<vehicle> map::add_vehicle_to_map(
// Try again with the wreckage
std::unique_ptr<vehicle> new_veh = add_vehicle_to_map( std::move( wreckage ), true );
if( new_veh != nullptr ) {
new_veh->smash();
new_veh->smash( *this );
return new_veh;
}

Expand All @@ -6913,7 +6913,7 @@ std::unique_ptr<vehicle> map::add_vehicle_to_map(
}

if( needs_smashing ) {
veh->smash();
veh->smash( *this );
}

return veh;
Expand Down
2 changes: 1 addition & 1 deletion src/monmove.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
}
}
}
Expand Down
209 changes: 153 additions & 56 deletions src/vehicle.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<tripoint> 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 )
{
Expand Down Expand Up @@ -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 ) {
Expand Down Expand Up @@ -734,6 +849,8 @@ void vehicle::smash( float hp_percent_loss_min, float hp_percent_loss_max,
}
}
}

std::unique_ptr<RemovePartHandler> handler_ptr;
// clear out any duplicated locations
for( int p = static_cast<int>( parts.size() ) - 1; p >= 0; p-- ) {
vehicle_part &part = parts[ p ];
Expand All @@ -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<DefaultRemovePartHandler>();
} else {
handler_ptr = std::make_unique<MapgenRemovePartHandler>( m );
}
}
remove_part( other_p, *handler_ptr );
}
}
}
Expand Down Expand Up @@ -1700,8 +1828,19 @@ bool vehicle::merge_rackable_vehicle( vehicle *carry_veh, const std::vector<int>
* 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<int>( parts.size() ) ) {
debugmsg( "Tried to remove part %d but only %d parts!", p, parts.size() );
return false;
Expand All @@ -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<int> 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`
Expand All @@ -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;
}
}
Expand All @@ -1745,31 +1878,16 @@ 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" );
remove_dependent_part( "BATTERY_MOUNT", "NEEDS_BATTERY_MOUNT" );

// 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<tripoint> 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 );
}
Expand Down Expand Up @@ -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();
Expand Down
15 changes: 12 additions & 3 deletions src/vehicle.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ class Creature;
class nc_color;
class player;
class npc;
class map;
class vehicle;
class vehicle_part_range;
class JsonIn;
Expand Down Expand Up @@ -524,6 +525,8 @@ struct label : public point {
std::string text;
};

class RemovePartHandler;

/**
* A vehicle as a whole with all its components.
*
Expand Down Expand Up @@ -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 );
Expand Down Expand Up @@ -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<int> &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();

Expand Down