diff --git a/src/character.cpp b/src/character.cpp index 53a08db40e63..e392d3222969 100644 --- a/src/character.cpp +++ b/src/character.cpp @@ -9105,26 +9105,35 @@ int Character::floor_bedding_warmth( const tripoint &pos ) int Character::floor_item_warmth( const tripoint &pos ) { - if( !g->m.has_items( pos ) ) { - return 0; - } - int item_warmth = 0; - // Search the floor for items - const auto floor_item = g->m.i_at( pos ); - for( const auto &elem : floor_item ) { - if( !elem.is_armor() ) { - continue; + + const auto warm = [&item_warmth]( const auto & stack ) { + for( const item &elem : stack ) { + if( !elem.is_armor() ) { + continue; + } + // Items that are big enough and covers the torso are used to keep warm. + // Smaller items don't do as good a job + if( elem.volume() > 250_ml && + ( elem.covers( bp_torso ) || elem.covers( bp_leg_l ) || + elem.covers( bp_leg_r ) ) ) { + item_warmth += 60 * elem.get_warmth() * elem.volume() / 2500_ml; + } } - // Items that are big enough and covers the torso are used to keep warm. - // Smaller items don't do as good a job - if( elem.volume() > 250_ml && - ( elem.covers( bp_torso ) || elem.covers( bp_leg_l ) || - elem.covers( bp_leg_r ) ) ) { - item_warmth += 60 * elem.get_warmth() * elem.volume() / 2500_ml; + }; + + if( !!g->m.veh_at( pos ) ) { + if( const cata::optional vp = g->m.veh_at( pos ).part_with_feature( VPFLAG_CARGO, + false ) ) { + vehicle *const veh = &vp->vehicle(); + const int cargo = vp->part_index(); + vehicle_stack vehicle_items = veh->get_items( cargo ); + warm( vehicle_items ); } + return item_warmth; } - + map_stack floor_items = g->m.i_at( pos ); + warm( floor_items ); return item_warmth; } diff --git a/src/computer_session.cpp b/src/computer_session.cpp index 630508693959..23d74c7c9205 100644 --- a/src/computer_session.cpp +++ b/src/computer_session.cpp @@ -68,6 +68,8 @@ static const mtype_id mon_secubot( "mon_secubot" ); static const std::string flag_CONSOLE( "CONSOLE" ); +static const itype_id itype_sarcophagus_access_code( "sarcophagus_access_code" ); + static catacurses::window init_window() { const int width = FULL_SCREEN_WIDTH; @@ -931,19 +933,68 @@ void computer_session::action_srcf_seal() void computer_session::action_srcf_elevator() { - if( !g->u.has_amount( "sarcophagus_access_code", 1 ) ) { - print_error( _( "Access code required!" ) ); - } else { - g->u.use_amount( "sarcophagus_access_code", 1 ); + Character &player_character = g->u; + map &here = g->m; + tripoint surface_elevator; + tripoint underground_elevator; + bool is_surface_elevator_on = false; + bool is_surface_elevator_exist = false; + bool is_underground_elevator_on = false; + bool is_underground_elevator_exist = false; + + for( const tripoint &p : here.points_on_zlevel( 0 ) ) { + if( here.ter( p ) == t_elevator_control_off || here.ter( p ) == t_elevator_control ) { + surface_elevator = p; + is_surface_elevator_on = here.ter( p ) == t_elevator_control; + is_surface_elevator_exist = true; + } + } + for( const tripoint &p : here.points_on_zlevel( -2 ) ) { + if( here.ter( p ) == t_elevator_control_off || here.ter( p ) == t_elevator_control ) { + underground_elevator = p; + is_underground_elevator_on = here.ter( p ) == t_elevator_control; + is_underground_elevator_exist = true; + } + } + + //If some are destroyed + if( !is_surface_elevator_exist || !is_underground_elevator_exist ) { + reset_terminal(); + print_error( + _( "\nElevator control network unreachable!\n\n" ) ); + } + + //If both are disabled try to enable + else if( !is_surface_elevator_on && !is_underground_elevator_on ) { + if( !player_character.has_amount( itype_sarcophagus_access_code, 1 ) ) { + print_error( _( "Access code required!\n\n" ) ); + } else { + player_character.use_amount( itype_sarcophagus_access_code, 1 ); + here.ter_set( surface_elevator, t_elevator_control ); + is_surface_elevator_on = true; + here.ter_set( underground_elevator, t_elevator_control ); + is_underground_elevator_on = true; + } + } + + //If only one is enabled, enable the other one. Fix for before this change + else if( is_surface_elevator_on && !is_underground_elevator_on && is_underground_elevator_exist ) { + here.ter_set( underground_elevator, t_elevator_control ); + is_underground_elevator_on = true; + } + + else if( is_underground_elevator_on && !is_surface_elevator_on && is_surface_elevator_exist ) { + here.ter_set( surface_elevator, t_elevator_control ); + is_surface_elevator_on = true; + } + + //If the elevator is working + if( is_surface_elevator_on && is_underground_elevator_on ) { reset_terminal(); print_line( _( "\nPower: Backup Only\nRadiation Level: Very Dangerous\nOperational: Overridden\n\n" ) ); - for( const tripoint &p : g->m.points_on_zlevel() ) { - if( g->m.ter( p ) == t_elevator_control_off ) { - g->m.ter_set( p, t_elevator_control ); - } - } } + query_any( _( "Press any key…" ) ); } diff --git a/src/game.cpp b/src/game.cpp index dfd6e87d4f71..ff3acdd93b23 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -1734,6 +1734,7 @@ bool game::cancel_activity_query( const std::string &text ) } if( query_yn( "%s %s", text, u.activity.get_stop_phrase() ) ) { u.cancel_activity(); + u.clear_destination(); u.resume_backlog_activity(); return true; } @@ -4879,10 +4880,11 @@ monster *game::place_critter_around( const mtype_id &id, const tripoint ¢er, monster *game::place_critter_around( const shared_ptr_fast &mon, const tripoint ¢er, - const int radius ) + const int radius, + bool forced ) { cata::optional where; - if( can_place_monster( *mon, center ) ) { + if( forced || can_place_monster( *mon, center ) ) { where = center; } diff --git a/src/game.h b/src/game.h index 28c661f06a47..5a2bdc14f22f 100644 --- a/src/game.h +++ b/src/game.h @@ -338,7 +338,7 @@ class game monster *place_critter_at( const shared_ptr_fast &mon, const tripoint &p ); monster *place_critter_around( const mtype_id &id, const tripoint ¢er, int radius ); monster *place_critter_around( const shared_ptr_fast &mon, const tripoint ¢er, - int radius ); + int radius, bool forced = false ); monster *place_critter_within( const mtype_id &id, const tripoint_range &range ); monster *place_critter_within( const shared_ptr_fast &mon, const tripoint_range &range ); diff --git a/src/gates.cpp b/src/gates.cpp index de5b17c59680..70f550fd4199 100644 --- a/src/gates.cpp +++ b/src/gates.cpp @@ -282,8 +282,13 @@ void doors::close_door( map &m, Character &who, const tripoint &closep ) if( !veh->handle_potential_theft( dynamic_cast( g->u ) ) ) { return; } - veh->close( closable ); - didit = true; + Character *ch = who.as_character(); + if( ch && veh->can_close( closable, *ch ) ) { + veh->close( closable ); + //~ %1$s - vehicle name, %2$s - part name + who.add_msg_if_player( _( "You close the %1$s's %2$s." ), veh->name, veh->parts[closable].name() ); + didit = true; + } } else if( inside_closable >= 0 ) { who.add_msg_if_player( m_info, _( "That %s can only be closed from the inside." ), veh->parts[inside_closable].name() ); diff --git a/src/magic_spell_effect.cpp b/src/magic_spell_effect.cpp index 9ece4403a018..606e751bba0b 100644 --- a/src/magic_spell_effect.cpp +++ b/src/magic_spell_effect.cpp @@ -435,7 +435,9 @@ static void damage_targets( const spell &sp, Creature &caster, cr->deal_projectile_attack( &caster, atk, true ); } else if( sp.damage() < 0 ) { sp.heal( target ); - add_msg( m_good, _( "%s wounds are closing up!" ), cr->disp_name( true ) ); + if( g->u.sees( cr->pos() ) ) { + add_msg( m_good, _( "%s wounds are closing up!" ), cr->disp_name( true ) ); + } } } } diff --git a/src/monattack.cpp b/src/monattack.cpp index 65e70df4fa09..95c8ed473870 100644 --- a/src/monattack.cpp +++ b/src/monattack.cpp @@ -1634,9 +1634,7 @@ bool mattack::fungus( monster *z ) // TODO: Infect NPCs? // It takes a while z->moves -= 200; - if( g->u.has_trait( trait_THRESH_MYCUS ) ) { - z->friendly = 100; - } + //~ the sound of a fungus releasing spores sounds::sound( z->pos(), 10, sounds::sound_t::combat, _( "Pouf!" ), false, "misc", "puff" ); if( g->u.sees( *z ) ) { diff --git a/src/output.cpp b/src/output.cpp index e55b90f5cc60..a83f1174ca67 100644 --- a/src/output.cpp +++ b/src/output.cpp @@ -150,7 +150,7 @@ std::string remove_color_tags( const std::string &s ) std::vector tag_positions = get_tag_positions( s ); size_t next_pos = 0; - if( tag_positions.size() > 1 ) { + if( !tag_positions.empty() ) { for( size_t tag_position : tag_positions ) { ret += s.substr( next_pos, tag_position - next_pos ); next_pos = s.find( ">", tag_position, 1 ) + 1; @@ -240,8 +240,9 @@ std::string trim_by_length( const std::string &text, int width ) iLength += iTempLen; if( iLength > width ) { - sTempText = sTempText.substr( 0, cursorx_to_position( sTempText.c_str(), - iTempLen - ( iLength - width ) - 1, nullptr, -1 ) ) + "\u2026"; + int pos = 0; + cursorx_to_position( sTempText.c_str(), iTempLen - ( iLength - width ) - 1, &pos, -1 ); + sTempText = sTempText.substr( 0, pos ) + "\u2026"; } sText += sColor + sTempText; diff --git a/src/overmapbuffer.cpp b/src/overmapbuffer.cpp index 69a54b74f6f2..e7bb540f4981 100644 --- a/src/overmapbuffer.cpp +++ b/src/overmapbuffer.cpp @@ -1418,8 +1418,8 @@ void overmapbuffer::spawn_monster( const tripoint &p ) // The monster position must be local to the main map when added to the game const tripoint local = tripoint( g->m.getlocal( ms ), p.z ); assert( g->m.inbounds( local ) ); - monster *const placed = g->place_critter_at( make_shared_fast( this_monster ), - local ); + monster *const placed = g->place_critter_around( make_shared_fast( this_monster ), + local, 0, true ); if( placed ) { placed->on_load(); } diff --git a/src/savegame_json.cpp b/src/savegame_json.cpp index 87468db4408e..6a83b978849c 100644 --- a/src/savegame_json.cpp +++ b/src/savegame_json.cpp @@ -1014,7 +1014,8 @@ void avatar::load( const JsonObject &data ) if( data.read( "profession", prof_ident ) && string_id( prof_ident ).is_valid() ) { prof = &string_id( prof_ident ).obj(); } else { - debugmsg( "Tried to use non-existent profession '%s'", prof_ident.c_str() ); + //We are likely an older profession which has since been removed so just set to default. This is only cosmetic after game start. + prof = profession::generic(); } data.read( "controlling_vehicle", controlling_vehicle ); diff --git a/src/turret.cpp b/src/turret.cpp index 7607f11d572b..90a336a99e75 100644 --- a/src/turret.cpp +++ b/src/turret.cpp @@ -577,6 +577,7 @@ int vehicle::automatic_fire_turret( vehicle_part &pt ) } const bool u_see = g->u.sees( pos ); + const bool u_hear = !g->u.is_deaf(); // The current target of the turret. auto &target = pt.target; if( target.first == target.second ) { @@ -593,16 +594,18 @@ int vehicle::automatic_fire_turret( vehicle_part &pt ) if( auto_target == nullptr ) { if( boo_hoo ) { cpu.name = string_format( pgettext( "vehicle turret", "The %s" ), pt.name() ); - if( u_see ) { + // check if the player can see or hear then print chooses a message accordingly + if( u_see && u_hear ) { add_msg( m_warning, ngettext( "%s points in your direction and emits an IFF warning beep.", "%s points in your direction and emits %d annoyed sounding beeps.", boo_hoo ), cpu.name, boo_hoo ); - } else { - add_msg( m_warning, ngettext( "%s emits an IFF warning beep.", - "%s emits %d annoyed sounding beeps.", - boo_hoo ), - cpu.name, boo_hoo ); + } else if( u_hear ) { + add_msg( m_warning, ngettext( "You hear a warning beep.", + "You hear %d annoyed sounding beeps.", + boo_hoo ), boo_hoo ); + } else if( u_see ) { + add_msg( m_warning, _( "%s points in your direction." ), cpu.name ); } } return shots; diff --git a/src/vehicle.h b/src/vehicle.h index 8f6dd6adaf16..25756f7235ef 100644 --- a/src/vehicle.h +++ b/src/vehicle.h @@ -1581,6 +1581,8 @@ class vehicle // returns whether the door is open or not bool is_open( int part_index ) const; + bool can_close( int part_index, Character &who ); + // Consists only of parts with the FOLDABLE tag. bool is_foldable() const; // Restore parts of a folded vehicle. diff --git a/src/vehicle_use.cpp b/src/vehicle_use.cpp index 5e30173a02bd..2ec630e840f3 100644 --- a/src/vehicle_use.cpp +++ b/src/vehicle_use.cpp @@ -1486,6 +1486,27 @@ bool vehicle::is_open( int part_index ) const return parts[part_index].open; } +bool vehicle::can_close( int part_index, Character &who ) +{ + for( auto const &vec : find_lines_of_parts( part_index, "OPENABLE" ) ) { + for( auto const &partID : vec ) { + const Creature *const mon = g->critter_at( global_part_pos3( parts[partID] ) ); + if( mon ) { + if( mon->is_player() ) { + who.add_msg_if_player( m_info, _( "There's some buffoon in the way!" ) ); + } else if( mon->is_monster() ) { + // TODO: Houseflies, mosquitoes, etc shouldn't count + who.add_msg_if_player( m_info, _( "The %s is in the way!" ), mon->get_name() ); + } else { + who.add_msg_if_player( m_info, _( "%s is in the way!" ), mon->disp_name() ); + } + return false; + } + } + } + return true; +} + void vehicle::open_all_at( int p ) { std::vector parts_here = parts_at_relative( parts[p].mount, true ); diff --git a/tests/string_test.cpp b/tests/string_test.cpp new file mode 100644 index 000000000000..2deeecbb3eb5 --- /dev/null +++ b/tests/string_test.cpp @@ -0,0 +1,40 @@ +#include "catch/catch.hpp" + +#include + +#include "output.h" + +static void test_remove_color_tags( const std::string &original, const std::string &expected ) +{ + CHECK( remove_color_tags( original ) == expected ); +} + +TEST_CASE( "string_test" ) +{ + SECTION( "Case 1 - test remove_color_tags" ) { + test_remove_color_tags( "TestString", + "TestString" ); + test_remove_color_tags( "TestStringWithoutOpeningColorTag", + "TestStringWithoutOpeningColorTag" ); + test_remove_color_tags( "TestStringWithoutClosingColorTag", + "TestStringWithoutClosingColorTag" ); + test_remove_color_tags( "TestStringWithMultipleColorTags", + "TestStringWithMultipleColorTags" ); + } +} + +TEST_CASE( "trim_by_length" ) +{ + CHECK( trim_by_length( "ABC", 2 ) == "A…" ); + CHECK( trim_by_length( "ABC", 3 ) == "ABC" ); + CHECK( trim_by_length( "ABCDEF", 4 ) == "ABC…" ); + CHECK( trim_by_length( "AB文字", 6 ) == "AB文字" ); + CHECK( trim_by_length( "AB文字", 5 ) == "AB文…" ); + CHECK( trim_by_length( "AB文字", 4 ) == "AB…" ); + CHECK( trim_by_length( "MRE 主菜(鸡肉意大利香蒜沙司通心粉)(新鲜)", + 5 ) == "MRE …" ); + CHECK( trim_by_length( "MRE 主菜(鸡肉意大利香蒜沙司通心粉)(新鲜)", + 6 ) == "MRE …" ); + CHECK( trim_by_length( "MRE 主菜(鸡肉意大利香蒜沙司通心粉)(新鲜)", + 36 ) == "MRE 主菜(鸡肉意大利香蒜沙司通心粉…" ); +}