Skip to content

Commit

Permalink
feat(port): move vehicles in elevator (#3151)
Browse files Browse the repository at this point in the history
* refactor: flatten nested conditions

* feat: utility function for point rotation

* feat: add elevator to ter_bitflags

* feat(port): move vehicles in elevator

DDA PR: CleverRaven/Cataclysm-DDA#58840

Co-authored-by: andrei <[email protected]>

* refactor: split into functions

* fix: negative retval `-1`confuses uilist

prevent exciting magic numbers from happening

Co-authored-by: andrei <[email protected]>

* fix: check vehicles in destination too

prevents exciting amalgamations from happening

* fix: include headers

make MSYS2 happy

---------

Co-authored-by: andrei <[email protected]>
Co-authored-by: andrei <[email protected]>
  • Loading branch information
3 people authored Sep 17, 2023
1 parent 503798f commit 4e48081
Show file tree
Hide file tree
Showing 6 changed files with 309 additions and 86 deletions.
86 changes: 0 additions & 86 deletions src/iexamine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,6 @@ static const std::string flag_SPLINT( "SPLINT" );
static const std::string flag_VARSIZE( "VARSIZE" );
static const std::string flag_WALL( "WALL" );
static const std::string flag_WRITE_MESSAGE( "WRITE_MESSAGE" );
static const std::string flag_ELEVATOR( "ELEVATOR" );

// @TODO maybe make this a property of the item (depend on volume/type)
static const time_duration milling_time = 6_hours;
Expand Down Expand Up @@ -879,91 +878,6 @@ void iexamine::toilet( player &p, const tripoint &examp )
}
}

/**
* If underground, move 2 levels up else move 2 levels down. Stable movement between levels 0 and -2.
*/
void iexamine::elevator( player &p, const tripoint &examp )
{
map &here = get_map();
if( !query_yn( _( "Use the %s?" ), here.tername( examp ) ) ) {
return;
}
int movez = ( examp.z < 0 ? 2 : -2 );

tripoint original_floor_omt = ms_to_omt_copy( here.getabs( examp ) );
tripoint new_floor_omt = original_floor_omt + tripoint( point_zero, movez );


// first find critters in the destination elevator and move them out of the way
for( Creature &critter : g->all_creatures() ) {
if( critter.is_player() ) {
continue;
} else if( here.has_flag( flag_ELEVATOR, critter.pos() ) ) {
tripoint critter_omt = ms_to_omt_copy( here.getabs( critter.pos() ) );
if( critter_omt == new_floor_omt ) {
for( const tripoint &candidate : closest_points_first( critter.pos(), 10 ) ) {
if( !here.has_flag( flag_ELEVATOR, candidate ) &&
here.passable( candidate ) &&
!g->critter_at( candidate ) ) {
critter.setpos( candidate );
break;
}
}
}
}
}

// TODO: do we have struct or pair to indicate from -> to?
const auto move_item = [&]( map_stack & items, const tripoint & src, const tripoint & dest ) {
for( auto it = items.begin(); it != items.end(); ) {
here.add_item_or_charges( dest, *it );
it = here.i_rem( src, it );
}
};

const auto first_elevator_tile = [&]( const tripoint & pos ) -> tripoint {
for( const tripoint &candidate : closest_points_first( pos, 10 ) )
{
if( here.has_flag( flag_ELEVATOR, candidate ) ) {
return candidate;
}
}
return pos;
};

// move along every item in the elevator
for( const tripoint &pos : closest_points_first( p.pos(), 10 ) ) {
if( here.has_flag( flag_ELEVATOR, pos ) ) {
map_stack items = here.i_at( pos );
tripoint dest = first_elevator_tile( pos + tripoint( 0, 0, movez ) );
move_item( items, pos, dest );
}
}

// move the player
g->vertical_move( movez, false );

// finally, bring along everyone who was in the elevator with the player
for( Creature &critter : g->all_creatures() ) {
if( critter.is_player() ) {
continue;
} else if( here.has_flag( flag_ELEVATOR, critter.pos() ) ) {
tripoint critter_omt = ms_to_omt_copy( here.getabs( critter.pos() ) );

if( critter_omt == original_floor_omt ) {
for( const tripoint &candidate : closest_points_first( p.pos(), 10 ) ) {
if( here.has_flag( flag_ELEVATOR, candidate ) &&
candidate != p.pos() &&
!g->critter_at( candidate ) ) {
critter.setpos( candidate );
break;
}
}
}
}
}
}

/**
* Open or close gate.
*/
Expand Down
241 changes: 241 additions & 0 deletions src/iexamine_elevator.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
#include "game.h"
#include "iexamine.h"
#include "mapdata.h"
#include "output.h"
#include "omdata.h"
#include "overmapbuffer.h"
#include "player.h"
#include "coordinates.h"
#include "map.h"
#include "point.h"
#include "ui.h"
#include "vpart_range.h"
#include "point_rotate.h"
namespace
{

// still not sure whether there's a utility function for this
// NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
auto move_item( map &here, const tripoint &src, const tripoint &dest ) -> void
{
map_stack items = here.i_at( src );
for( auto it = items.begin(); it != items.end(); ) {
here.add_item_or_charges( dest, *it );
it = here.i_rem( src, it );
}
};

namespace elevator
{

using tiles = std::vector<tripoint>;

auto here( const Character &you ) -> elevator::tiles
{
const auto &here = get_map();
const auto &points = closest_points_first( you.pos(), SEEX - 1 );

elevator::tiles tiles;
std::copy_if( points.begin(), points.end(), std::back_inserter( tiles ),
[&here]( const tripoint & pos ) {
return here.has_flag( TFLAG_ELEVATOR, pos );
} );

return tiles;
}

auto dest( const elevator::tiles &elevator_here,
const tripoint &sm_orig,
int turns,
int movez ) -> elevator::tiles
{
elevator::tiles tiles;
std::transform( elevator_here.begin(), elevator_here.end(), std::back_inserter( tiles ),
[turns, &sm_orig, movez]( tripoint const & p ) {
return rotate_point_sm( { p.xy(), movez }, sm_orig, turns );
} );

return tiles;
}

auto choose_floor( const tripoint &examp, const tripoint_abs_omt &this_omt,
const tripoint &sm_orig ) -> int
{
constexpr int retval_offset = 10000; // workaround for uilist retval autoassign when retval == -1
const auto this_floor = _( " (this floor)" );

map &here = get_map();
uilist choice;
choice.title = _( "Please select destination floor" );
for( int z = OVERMAP_HEIGHT; z >= -OVERMAP_DEPTH; z-- ) {
const tripoint_abs_omt that_omt{ this_omt.xy(), z };
const int delta = get_rot_delta( this_omt, that_omt );
const tripoint zp =
rotate_point_sm( { examp.xy(), z }, sm_orig, delta );

if( here.ter( zp )->examine != &iexamine::elevator ) {
continue;
}
const std::string omt_name = overmap_buffer.ter_existing( that_omt )->get_name();
const auto floor_name = z == examp.z ? this_floor : "";
const std::string name = string_format( "%3iF %s%s", z, omt_name, floor_name );

choice.addentry( z + retval_offset, z != examp.z, MENU_AUTOASSIGN, name );
}
choice.query();
return choice.ret - retval_offset;
}

enum class overlap_status { outside, inside, overlap };

auto vehicle_status( const wrapped_vehicle &veh, const elevator::tiles &tiles ) -> overlap_status
{
const auto &ps = veh.v->get_all_parts();
const int all_vparts_count = ps.part_count();
const int vparts_inside = std::count_if( ps.begin(), ps.end(), [&]( const vpart_reference & vp ) {
const tripoint p = veh.pos + vp.part().precalc[0];
const auto eit = std::find( tiles.cbegin(), tiles.cend(), p );
return eit != tiles.cend();
} );

if( vparts_inside == all_vparts_count ) {
return overlap_status::inside;
} else if( vparts_inside == 0 ) {
return overlap_status::outside;
} else {
return overlap_status::overlap;
}
}

struct elevator_vehicles {
bool blocking = false;
std::vector<vehicle *> v;
};

auto vehicles_on( const elevator::tiles &tiles ) -> elevator_vehicles
{
const VehicleList vehs = get_map().get_vehicles();
std::vector<vehicle *> ret;

for( const wrapped_vehicle &veh : vehs ) {
const auto status = vehicle_status( veh, tiles );
if( status == overlap_status::overlap ) {
return { true, { veh.v } };
}
if( status == overlap_status::inside ) {
ret.push_back( veh.v );
}
}
return { false, ret };
}

auto warn_blocking( const elevator_vehicles &vehs, std::string_view location ) -> void
{
const auto &first_veh_name = vehs.v.front()->name;
popup( string_format( _( "%1$s %2$s is blocking the elevator." ), first_veh_name, location ) );
}

auto move_creatures_away( const elevator::tiles &dest ) -> void
{
map &here = get_map();

const auto is_movable = [&]( const tripoint & candidate ) {
return !here.has_flag( TFLAG_ELEVATOR, candidate )
&& here.passable( candidate )
&& !g->critter_at( candidate );
};

for( Creature &critter : g->all_creatures() ) {
const tripoint local_pos = here.getlocal( here.getglobal( critter.pos() ) );

const auto eit = std::find( dest.cbegin(), dest.cend(), local_pos );
if( eit == dest.cend() ) {
continue;
}

const auto xs = closest_points_first( *eit, 10 );
const auto candidate = std::find_if( xs.begin(), xs.end(), is_movable );
if( candidate == xs.end() ) {
continue;
}
critter.setpos( *candidate );
}
}

auto move_items( const elevator::tiles from, const elevator::tiles dest ) -> void
{
map &here = get_map();

for( decltype( from )::size_type i = 0; i < from.size(); i++ ) {
const tripoint &src = from[i];
move_item( here, src, dest[i] );
}
}

auto move_creatures( const elevator::tiles from, const elevator::tiles dest ) -> void
{
for( Creature &critter : g->all_creatures() ) {
const auto eit = std::find( from.cbegin(), from.cend(), critter.pos() );
if( eit != from.cend() ) {
critter.setpos( dest[ std::distance( from.cbegin(), eit ) ] );
}
}
}

auto move_vehicles( const elevator_vehicles &vehs, const tripoint &sm_orig, int movez,
int turns ) -> void
{
map &here = get_map();

for( vehicle *v : vehs.v ) {
const tripoint p = rotate_point_sm( { v->global_pos3().xy(), movez }, sm_orig, turns );
here.displace_vehicle( *v, p - v->global_pos3() );
v->turn( turns * 90_degrees );
v->face = tileray( v->turn_dir );
v->precalc_mounts( 0, v->turn_dir, v->pivot_anchor[0] );
}
here.reset_vehicle_cache();
}

} //namespace elevator

} // namespace

/**
* If underground, move 2 levels up else move 2 levels down. Stable movement between levels 0 and -2.
*/
void iexamine::elevator( player &p, const tripoint &examp )
{
map &here = get_map();
const tripoint_abs_ms old_abs_pos = here.getglobal( p.pos() );
const tripoint_abs_omt this_omt = project_to<coords::omt>( here.getglobal( examp ) );
const tripoint sm_orig = here.getlocal( project_to<coords::ms>( this_omt ) );

const auto elevator_here = elevator::here( p );
const auto vehs = elevator::vehicles_on( elevator_here );
if( vehs.blocking ) {
return warn_blocking( vehs, _( "here" ) );
}

const int movez = elevator::choose_floor( examp, this_omt, sm_orig );
if( movez < -OVERMAP_DEPTH ) {
return;
}

const tripoint_abs_omt that_omt{ this_omt.xy(), movez };
const int turns = get_rot_delta( this_omt, that_omt );

const auto elevator_dest = elevator::dest( elevator_here, sm_orig, turns, movez );
const auto vehicles_dest = elevator::vehicles_on( elevator_dest );
if( !vehicles_dest.v.empty() ) {
return warn_blocking( vehicles_dest, _( "at the destination floor" ) );
}

elevator::move_creatures_away( elevator_dest );
elevator::move_items( elevator_here, elevator_dest );
elevator::move_creatures( elevator_here, elevator_dest );
elevator::move_vehicles( vehs, sm_orig, movez, turns );

g->vertical_shift( movez );
cata_event_dispatch::avatar_moves( *p.as_avatar(), here, old_abs_pos.raw() );
}
1 change: 1 addition & 0 deletions src/mapdata.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ static const std::unordered_map<std::string, ter_bitflags> ter_bitflags_map = {
{ "SUSPENDED", TFLAG_SUSPENDED }, // This furniture is suspended between other terrain, and will cause a cascading failure on break.
{ "FRIDGE", TFLAG_FRIDGE }, // This is an active fridge.
{ "FREEZER", TFLAG_FREEZER }, // This is an active freezer.
{ "ELEVATOR", TFLAG_ELEVATOR }, // This is an elevator.
}
};

Expand Down
1 change: 1 addition & 0 deletions src/mapdata.h
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,7 @@ enum ter_bitflags : int {
TFLAG_SUSPENDED,
TFLAG_FRIDGE,
TFLAG_FREEZER,
TFLAG_ELEVATOR,

NUM_TERFLAGS
};
Expand Down
40 changes: 40 additions & 0 deletions src/point_rotate.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#include "point_rotate.h"
#include "overmapbuffer.h"
#include "omdata.h"
#include "point.h"

auto rotate( point p, point dim, int turns ) -> point
{
switch( turns ) {
case 1:
return { dim.y - p.y - 1, p.x };
case 2:
return { dim.x - p.x - 1, dim.y - p.y - 1 };
case 3:
return { p.y, dim.x - p.x - 1 };
default:
return p;
}
}

auto rotate( const tripoint &p, point dim, int turns ) -> tripoint
{
return { rotate( p.xy(), dim, turns ), p.z };
}

auto rotate_point_sm( const tripoint &p, const tripoint &orig, int turns ) -> tripoint
{
const tripoint p_sm{ p - orig.xy() };
const tripoint rd{ rotate( p_sm, { SEEX * 2, SEEY * 2 }, turns ) };

return tripoint{ rd + orig.xy() };
}

auto get_rot_delta( const tripoint_abs_omt &here, const tripoint_abs_omt &there ) -> int
{
const auto this_dir = overmap_buffer.ter( there )->get_dir();
const auto that_dir = overmap_buffer.ter( here )->get_dir();

int const diff = static_cast<int>( this_dir ) - static_cast<int>( that_dir );
return diff >= 0 ? diff : 4 + diff;
}
Loading

0 comments on commit 4e48081

Please sign in to comment.