diff --git a/src/iexamine.cpp b/src/iexamine.cpp index ae0fd466304c..9de8b3365be3 100644 --- a/src/iexamine.cpp +++ b/src/iexamine.cpp @@ -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; @@ -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. */ diff --git a/src/iexamine_elevator.cpp b/src/iexamine_elevator.cpp new file mode 100644 index 000000000000..8996d3257313 --- /dev/null +++ b/src/iexamine_elevator.cpp @@ -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; + +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 v; +}; + +auto vehicles_on( const elevator::tiles &tiles ) -> elevator_vehicles +{ + const VehicleList vehs = get_map().get_vehicles(); + std::vector 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( here.getglobal( examp ) ); + const tripoint sm_orig = here.getlocal( project_to( 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() ); +} diff --git a/src/mapdata.cpp b/src/mapdata.cpp index 67d295f47148..9cc15a0f7e69 100644 --- a/src/mapdata.cpp +++ b/src/mapdata.cpp @@ -194,6 +194,7 @@ static const std::unordered_map 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. } }; diff --git a/src/mapdata.h b/src/mapdata.h index 2cc1ccc24f8b..2405ce7bd75a 100644 --- a/src/mapdata.h +++ b/src/mapdata.h @@ -324,6 +324,7 @@ enum ter_bitflags : int { TFLAG_SUSPENDED, TFLAG_FRIDGE, TFLAG_FREEZER, + TFLAG_ELEVATOR, NUM_TERFLAGS }; diff --git a/src/point_rotate.cpp b/src/point_rotate.cpp new file mode 100644 index 000000000000..590350aa6fba --- /dev/null +++ b/src/point_rotate.cpp @@ -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( this_dir ) - static_cast( that_dir ); + return diff >= 0 ? diff : 4 + diff; +} diff --git a/src/point_rotate.h b/src/point_rotate.h new file mode 100644 index 000000000000..a93706e806e0 --- /dev/null +++ b/src/point_rotate.h @@ -0,0 +1,26 @@ +#pragma once +#ifndef CATA_SRC_POINT_ROTATE_H +#define CATA_SRC_POINT_ROTATE_H + +#include "point.h" +#include "coordinates.h" + +/** + * Rotate point clockwise @param turns times, 90 degrees per turn, + * around the center of a rectangle with the dimensions specified + * by @param dim + * set dim to (0, 0) to rotate around the origin. + */ +auto rotate( point p, point dim, int turns ) -> point; + +/** + * Rotates just the x,y component of the tripoint. See point::rotate() + */ +auto rotate( const tripoint &p, point dim, int turns ) -> tripoint; + +/** works like rotate but for submaps. */ +auto rotate_point_sm( const tripoint &p, const tripoint &orig, int turns ) -> tripoint; + +auto get_rot_delta( const tripoint_abs_omt &here, const tripoint_abs_omt &there ) -> int; + +#endif // CATA_SRC_POINT_ROTATE_H