Skip to content

Commit

Permalink
Fixed "Auto-sort infinite loop/game hang CleverRaven#29573" by using …
Browse files Browse the repository at this point in the history
…activity stages.

Removed "num_processed" functions from clzones (not required).
Fixed `get_near_zone_type_for_item`: consistent usage of `range`.
Allow auto resume of ACT_MOVE_LOOT activity.
  • Loading branch information
ipcyborg authored and misterprimus committed Sep 21, 2019
1 parent 55ccd17 commit e3851d9
Show file tree
Hide file tree
Showing 4 changed files with 170 additions and 172 deletions.
4 changes: 1 addition & 3 deletions data/json/player_activities.json
Original file line number Diff line number Diff line change
Expand Up @@ -333,9 +333,7 @@
"type": "activity_type",
"activity_level": "MODERATE_EXERCISE",
"verb": "sorting out the loot",
"suspendable": false,
"based_on": "neither",
"no_resume": true
"based_on": "neither"
},
{
"id": "ACT_TILL_PLOT",
Expand Down
289 changes: 167 additions & 122 deletions src/activity_item_handling.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1947,64 +1947,145 @@ static bool chop_plank_activity( player &p, tripoint src_loc )
return false;
}

void activity_on_turn_move_loot( player_activity &, player &p )
void activity_on_turn_move_loot( player_activity &act, player &p )
{
const activity_id act_move_loot = activity_id( "ACT_MOVE_LOOT" );
const int search_range = 60;

enum activity_stage : int {
INIT = 0, //Initial stage
THINK, //Think about what to do first: choose destination
DO, //Do activity
};

int &stage = act.index;
//Prepare activity stage
if( stage < 0 ) {
stage = INIT;
act.values.push_back( 0 ); //num_processed
}
int &num_processed = act.values[ 0 ];

const auto abspos = g->m.getabs( p.pos() );
auto &mgr = zone_manager::get_manager();
if( g->m.check_vehicle_zones( g->get_levz() ) ) {
mgr.cache_vzones();
}
const auto abspos = g->m.getabs( p.pos() );
const auto &src_set = mgr.get_near( zone_type_id( "LOOT_UNSORTED" ), abspos );
vehicle *src_veh, *dest_veh;
int src_part, dest_part;

// Nuke the current activity, leaving the backlog alone.
p.activity = player_activity();
if( stage == INIT ) {
act.coord_set = mgr.get_near( zone_type_id( "LOOT_UNSORTED" ), abspos, search_range );
stage = THINK;
}

// sort source tiles by distance
const auto &src_sorted = get_sorted_tiles_by_distance( abspos, src_set );
if( stage == THINK ) {
num_processed = 0; //initialize num_processed
const auto &src_set = act.coord_set;
// sort source tiles by distance
const auto &src_sorted = get_sorted_tiles_by_distance( abspos, src_set );

if( !mgr.is_sorting() ) {
mgr.start_sort( src_sorted );
}
for( auto &src : src_sorted ) {
act.placement = src;
act.coord_set.erase( src );

for( auto &src : src_sorted ) {
const auto &src_loc = g->m.getlocal( src );
if( !g->m.inbounds( src_loc ) ) {
if( !g->m.inbounds( p.pos() ) ) {
// p is implicitly an NPC that has been moved off the map, so reset the activity
// and unload them
p.assign_activity( act_move_loot );
p.set_moves( 0 );
g->reload_npcs();
mgr.end_sort();
const auto &src_loc = g->m.getlocal( src );
if( !g->m.inbounds( src_loc ) ) {
if( !g->m.inbounds( p.pos() ) ) {
// p is implicitly an NPC that has been moved off the map, so reset the activity
// and unload them
p.cancel_activity();
p.assign_activity( activity_id( "ACT_MOVE_LOOT" ) );
p.set_moves( 0 );
g->reload_npcs();
return;
}
std::vector<tripoint> route;
route = g->m.route( p.pos(), src_loc, p.get_pathfinding_settings(),
p.get_path_avoid() );
if( route.empty() ) {
// can't get there, can't do anything, skip it
continue;
}
stage = DO;
p.set_destination( route, act );
p.activity.set_to_null();
return;
}
std::vector<tripoint> route;
route = g->m.route( p.pos(), src_loc, p.get_pathfinding_settings(),
p.get_path_avoid() );
if( route.empty() ) {
// can't get there, can't do anything, skip it

// skip tiles in IGNORE zone and tiles on fire
// (to prevent taking out wood off the lit brazier)
// and inaccessible furniture, like filled charcoal kiln
if( mgr.has( zone_type_id( "LOOT_IGNORE" ), src ) ||
g->m.get_field( src_loc, fd_fire ) != nullptr ||
!g->m.can_put_items_ter_furn( src_loc ) ) {
continue;
}
p.set_destination( route, player_activity( act_move_loot ) );
mgr.end_sort();
return;

//nothing to sort?
const cata::optional<vpart_reference> vp = g->m.veh_at( src_loc ).part_with_feature( "CARGO",
false );
if( !vp && g->m.i_at( src_loc ).empty( ) ) {
continue;
}

bool is_adjacent_or_closer = square_dist( p.pos(), src_loc ) <= 1;
// before we move any item, check if player is at or
// adjacent to the loot source tile
if( !is_adjacent_or_closer ) {
std::vector<tripoint> route;
bool adjacent = false;

// get either direct route or route to nearest adjacent tile if
// source tile is impassable
if( g->m.passable( src_loc ) ) {
route = g->m.route( p.pos(), src_loc, p.get_pathfinding_settings(),
p.get_path_avoid() );
} else {
// immpassable source tile (locker etc.),
// get route to nerest adjacent tile instead
route = route_adjacent( p, src_loc );
adjacent = true;
}

// check if we found path to source / adjacent tile
if( route.empty() ) {
add_msg( m_info, _( "%s can't reach the source tile. Try to sort out loot without a cart." ),
p.disp_name() );
continue;
}

// shorten the route to adjacent tile, if necessary
if( !adjacent ) {
route.pop_back();
}

// set the destination and restart activity after player arrives there
// we don't need to check for safe mode,
// activity will be restarted only if
// player arrives on destination tile
stage = DO;
p.set_destination( route, act );
p.activity.set_to_null();
return;
}
stage = DO;
break;
}
}
if( stage == DO ) {
const tripoint &src = act.placement;
const tripoint &src_loc = g->m.getlocal( src );

bool is_adjacent_or_closer = square_dist( p.pos(), src_loc ) <= 1;
// skip tiles in IGNORE zone and tiles on fire
// (to prevent taking out wood off the lit brazier)
// and inaccessible furniture, like filled charcoal kiln
if( mgr.has( zone_type_id( "LOOT_IGNORE" ), src ) ||
g->m.get_field( src_loc, fd_fire ) != nullptr ||
!g->m.can_put_items_ter_furn( src_loc ) ) {
continue;
// before we move any item, check if player is at or
// adjacent to the loot source tile
if( !is_adjacent_or_closer ) {
stage = THINK;
return;
}

// the boolean in this pair being true indicates the item is from a vehicle storage space
auto items = std::vector<std::pair<item *, bool>>();
vehicle *src_veh, *dest_veh;
int src_part, dest_part;

//Check source for cargo part
//map_stack and vehicle_stack are different types but inherit from item_stack
Expand All @@ -2023,111 +2104,75 @@ void activity_on_turn_move_loot( player_activity &, player &p )
for( auto &it : g->m.i_at( src_loc ) ) {
items.push_back( std::make_pair( &it, false ) );
}
//Skip items that have already been processed
for( auto it = items.begin() + mgr.get_num_processed( src ); it < items.end(); it++ ) {

mgr.increment_num_processed( src );

const auto thisitem = it->first;
//Skip items that have already been processed
for( auto it = items.begin() + num_processed; it < items.end(); ++it ) {
++num_processed;
item &thisitem = *it->first;

if( thisitem->made_of_from_type( LIQUID ) ) { // skip unpickable liquid
// skip unpickable liquid
if( thisitem.made_of_from_type( LIQUID ) ) {
continue;
}

// Only if it's from a vehicle do we use the vehicle source location information.
vehicle *this_veh = it->second ? src_veh : nullptr;
const int this_part = it->second ? src_part : -1;

const auto id = mgr.get_near_zone_type_for_item( *thisitem, abspos );
const zone_type_id id = mgr.get_near_zone_type_for_item( thisitem, abspos, search_range );

// checks whether the item is already on correct loot zone or not
// if it is, we can skip such item, if not we move the item to correct pile
// think empty bag on food pile, after you ate the content
if( !mgr.has( id, src ) ) {
const auto &dest_set = mgr.get_near( id, abspos, 60, thisitem );
if( mgr.has( id, src ) ) {
continue;
}

for( auto &dest : dest_set ) {
const auto &dest_loc = g->m.getlocal( dest );
const std::unordered_set<tripoint> &dest_set = mgr.get_near( id, abspos, search_range, &thisitem );
for( const tripoint &dest : dest_set ) {
const tripoint &dest_loc = g->m.getlocal( dest );

//Check destination for cargo part
if( const cata::optional<vpart_reference> vp = g->m.veh_at( dest_loc ).part_with_feature( "CARGO",
false ) ) {
dest_veh = &vp->vehicle();
dest_part = vp->part_index();
} else {
dest_veh = nullptr;
dest_part = -1;
}

// skip tiles with inaccessible furniture, like filled charcoal kiln
if( !g->m.can_put_items_ter_furn( dest_loc ) ) {
continue;
}
//Check destination for cargo part
if( const cata::optional<vpart_reference> vp = g->m.veh_at( dest_loc ).part_with_feature( "CARGO",
false ) ) {
dest_veh = &vp->vehicle();
dest_part = vp->part_index();
} else {
dest_veh = nullptr;
dest_part = -1;
}

units::volume free_space;
// if there's a vehicle with space do not check the tile beneath
if( dest_veh ) {
free_space = dest_veh->free_volume( dest_part );
} else {
free_space = g->m.free_volume( dest_loc );
}
// check free space at destination
if( free_space >= thisitem->volume() ) {
// before we move any item, check if player is at or
// adjacent to the loot source tile
if( !is_adjacent_or_closer ) {
std::vector<tripoint> route;
bool adjacent = false;

// get either direct route or route to nearest adjacent tile if
// source tile is impassable
if( g->m.passable( src_loc ) ) {
route = g->m.route( p.pos(), src_loc, p.get_pathfinding_settings(),
p.get_path_avoid() );
} else {
// immpassable source tile (locker etc.),
// get route to nerest adjacent tile instead
route = route_adjacent( p, src_loc );
adjacent = true;
}

// check if we found path to source / adjacent tile
if( route.empty() ) {
add_msg( m_info, _( "%s can't reach the source tile. Try to sort out loot without a cart." ),
p.disp_name() );
mgr.end_sort();
return;
}

// shorten the route to adjacent tile, if necessary
if( !adjacent ) {
route.pop_back();
}

// set the destination and restart activity after player arrives there
// we don't need to check for safe mode,
// activity will be restarted only if
// player arrives on destination tile
p.set_destination( route, player_activity( act_move_loot ) );
mgr.end_sort();
return;
}
move_item( p, *thisitem, thisitem->count(), src_loc, dest_loc, this_veh, this_part );
// skip tiles with inaccessible furniture, like filled charcoal kiln
if( !g->m.can_put_items_ter_furn( dest_loc ) ) {
continue;
}

// moved item away from source so decrement
mgr.decrement_num_processed( src );
units::volume free_space;
// if there's a vehicle with space do not check the tile beneath
if( dest_veh ) {
free_space = dest_veh->free_volume( dest_part );
} else {
free_space = g->m.free_volume( dest_loc );
}
// check free space at destination
if( free_space >= thisitem.volume() ) {
move_item( p, thisitem, thisitem.count(), src_loc, dest_loc, this_veh, this_part );

break;
// moved item away from source so decrement
if( num_processed > 0 ) {
--num_processed;
}
}
if( p.moves <= 0 ) {
// Restart activity and break from cycle.
p.assign_activity( act_move_loot );
mgr.end_sort();
return;
break;
}
}
if( p.moves <= 0 ) {
return;
}
}

//this location is sorted
stage = THINK;
return;
}

// If we got here without restarting the activity, it means we're done
Expand All @@ -2137,7 +2182,7 @@ void activity_on_turn_move_loot( player_activity &, player &p )
guy->revert_after_activity();
guy->current_activity_id = activity_id::NULL_ID();
}
mgr.end_sort();
p.activity.set_to_null();
}

static int chop_moves( player &p, item *it )
Expand Down
Loading

0 comments on commit e3851d9

Please sign in to comment.