diff --git a/src/bionics.cpp b/src/bionics.cpp index 545142ca55ca3..fc8ff9310c53f 100644 --- a/src/bionics.cpp +++ b/src/bionics.cpp @@ -63,6 +63,7 @@ #include "item_location.h" #include "monster.h" #include "point.h" +#include "teleport.h" const skill_id skilll_electronics( "electronics" ); const skill_id skilll_firstaid( "firstaid" ); @@ -383,7 +384,7 @@ bool player::activate_bionic( int b, bool eff_only ) add_msg_if_player( m_info, _( "You cannot activate that while mounted." ) ); return false; } - g->teleport(); + teleport::teleport( this ); add_effect( effect_teleglow, 30_minutes ); mod_moves( -100 ); } else if( bio.id == "bio_blood_anal" ) { diff --git a/src/game.cpp b/src/game.cpp index ead23c022749d..966d3669c1ca9 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -11035,61 +11035,6 @@ void game::perhaps_add_random_npc() load_npcs(); } -void game::teleport( player *p, bool add_teleglow ) -{ - if( p == nullptr ) { - p = &u; - } - int tries = 0; - tripoint new_pos = p->pos(); - bool is_u = ( p == &u ); - - if( add_teleglow ) { - p->add_effect( effect_teleglow, 30_minutes ); - } - do { - new_pos.x = p->posx() + rng( 0, SEEX * 2 ) - SEEX; - new_pos.y = p->posy() + rng( 0, SEEY * 2 ) - SEEY; - tries++; - } while( tries < 15 && m.impassable( new_pos ) ); - bool can_see = ( is_u || u.sees( new_pos ) ); - if( p->in_vehicle ) { - m.unboard_vehicle( p->pos() ); - } - p->setx( new_pos.x ); - p->sety( new_pos.y ); - if( m.impassable( new_pos ) ) { //Teleported into a wall - const std::string obstacle_name = m.obstacle_name( new_pos ); - g->events().send( p->getID(), obstacle_name ); - if( can_see ) { - if( is_u ) { - add_msg( _( "You teleport into the middle of a %s!" ), - m.obstacle_name( new_pos ) ); - } else { - add_msg( _( "%1$s teleports into the middle of a %2$s!" ), - p->name, m.obstacle_name( new_pos ) ); - } - } - p->apply_damage( nullptr, bp_torso, 500 ); - p->check_dead_state(); - } else if( monster *const mon_ptr = critter_at( new_pos ) ) { - g->events().send( p->getID(), mon_ptr->name() ); - if( can_see ) { - if( is_u ) { - add_msg( _( "You teleport into the middle of a %s!" ), - mon_ptr->name() ); - } else { - add_msg( _( "%1$s teleports into the middle of a %2$s!" ), - p->name, mon_ptr->name() ); - } - mon_ptr->die_in_explosion( p ); - } - } - if( is_u ) { - update_map( *p ); - } -} - void game::display_scent() { if( use_tiles ) { diff --git a/src/game.h b/src/game.h index e82364795c9c6..ee12141d92c1d 100644 --- a/src/game.h +++ b/src/game.h @@ -454,8 +454,6 @@ class game void validate_camps(); /** process vehicles that are following the player */ void following_vehicles(); - /** Performs a random short-distance teleport on the given player, granting teleglow if needed. */ - void teleport( player *p = nullptr, bool add_teleglow = true ); /** Picks and spawns a random fish from the remaining fish list when a fish is caught. */ void catch_a_monster( monster *fish, const tripoint &pos, player *p, const time_duration &catch_duration ); diff --git a/src/iuse.cpp b/src/iuse.cpp index 168f1ac9adead..f974b2460e982 100644 --- a/src/iuse.cpp +++ b/src/iuse.cpp @@ -99,6 +99,7 @@ #include "item_group.h" #include "omdata.h" #include "point.h" +#include "teleport.h" #define RADIO_PER_TURN 25 // how many characters per turn of radio @@ -3431,7 +3432,7 @@ int iuse::teleport( player *p, item *it, bool, const tripoint & ) return 0; } p->moves -= to_moves( 1_seconds ); - g->teleport( p ); + teleport::teleport(p); return it->type->charges_to_use(); } @@ -5275,7 +5276,7 @@ int iuse::artifact( player *p, item *it, bool, const tripoint & ) break; case AEA_TELEPORT: - g->teleport( p ); + teleport::teleport(p); break; case AEA_LIGHT: diff --git a/src/magic.cpp b/src/magic.cpp index 28918714050f4..99030cb45ddae 100644 --- a/src/magic.cpp +++ b/src/magic.cpp @@ -95,6 +95,7 @@ std::string enum_to_string( spell_flag data ) case spell_flag::SOMATIC: return "SOMATIC"; case spell_flag::NO_HANDS: return "NO_HANDS"; case spell_flag::NO_LEGS: return "NO_LEGS"; + case spell_flag::UNSAFE_TELEPORT: return "UNSAFE_TELEPORT"; case spell_flag::CONCENTRATE: return "CONCENTRATE"; case spell_flag::RANDOM_AOE: return "RANDOM_AOE"; case spell_flag::RANDOM_DAMAGE: return "RANDOM_DAMAGE"; diff --git a/src/magic.h b/src/magic.h index 0372984e7f3ca..40a2e750c3d23 100644 --- a/src/magic.h +++ b/src/magic.h @@ -41,6 +41,7 @@ enum spell_flag { VERBAL, // spell makes noise at caster location, mouth encumbrance affects fail % SOMATIC, // arm encumbrance affects fail % and casting time (slightly) NO_HANDS, // hands do not affect spell energy cost + UNSAFE_TELEPORT, // teleport spell risks killing the caster or others NO_LEGS, // legs do not affect casting time CONCENTRATE, // focus affects spell fail % RANDOM_AOE, // picks random number between min+increment*level and max instead of normal behavior diff --git a/src/magic_spell_effect.cpp b/src/magic_spell_effect.cpp index 52d41c74e1fbd..2487c60c8cedb 100644 --- a/src/magic_spell_effect.cpp +++ b/src/magic_spell_effect.cpp @@ -41,6 +41,7 @@ #include "rng.h" #include "translations.h" #include "timed_event.h" +#include "teleport.h" static tripoint random_point( int min_distance, int max_distance, const tripoint &player_pos ) { @@ -53,29 +54,14 @@ static tripoint random_point( int min_distance, int max_distance, const tripoint void spell_effect::teleport_random( const spell &sp, Creature &caster, const tripoint & ) { + bool safe = !sp.has_flag(spell_flag::UNSAFE_TELEPORT); const int min_distance = sp.range(); const int max_distance = sp.range() + sp.aoe(); if( min_distance > max_distance || min_distance < 0 || max_distance < 0 ) { debugmsg( "ERROR: Teleport argument(s) invalid" ); return; } - const tripoint player_pos = caster.pos(); - tripoint target; - // limit the loop just in case it's impossble to find a valid point in the range - int tries = 0; - do { - target = random_point( min_distance, max_distance, player_pos ); - tries++; - } while( g->m.impassable( target ) && tries < 20 ); - if( tries == 20 ) { - add_msg( m_bad, _( "Unable to find a valid target for teleport." ) ); - return; - } - // TODO: make this spell work for non players - if( caster.is_player() ) { - sp.make_sound( caster.pos() ); - g->place_player( target ); - } + teleport::teleport(&caster, min_distance, max_distance, safe, false); } void spell_effect::pain_split( const spell &sp, Creature &caster, const tripoint & ) diff --git a/src/map_field.cpp b/src/map_field.cpp index cdf171540eae2..fa68f3104db07 100644 --- a/src/map_field.cpp +++ b/src/map_field.cpp @@ -57,6 +57,7 @@ #include "point.h" #include "scent_block.h" #include "mongroup.h" +#include "teleport.h" const species_id FUNGUS( "FUNGUS" ); const species_id INSECT( "INSECT" ); @@ -1632,10 +1633,9 @@ void map::player_in_field( player &u ) if( ft == fd_fatigue ) { // Teleports you... somewhere. if( rng( 0, 2 ) < cur.get_field_intensity() && u.is_player() ) { - // TODO: allow teleporting for npcs add_msg( m_bad, _( "You're violently teleported!" ) ); u.hurtall( cur.get_field_intensity(), nullptr ); - g->teleport(); + teleport::teleport( &u ); } } // Why do these get removed??? diff --git a/src/player.cpp b/src/player.cpp index 9f85bf0d294af..f2982c7be363f 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -99,6 +99,7 @@ #include "enums.h" #include "flat_set.h" #include "stomach.h" +#include "teleport.h" const double MAX_RECOIL = 3000; @@ -5575,7 +5576,7 @@ void player::suffer() mutate(); } if( has_artifact_with( AEP_FORCE_TELEPORT ) && one_turn_in( 1_hours ) ) { - g->teleport( this ); + teleport::teleport( this ); } const bool needs_fire = !has_morale( MORALE_PYROMANIA_NEARFIRE ) && !has_morale( MORALE_PYROMANIA_STARTFIRE ); diff --git a/src/player_hardcoded_effects.cpp b/src/player_hardcoded_effects.cpp index 1714bdb612ffa..769506db17d61 100644 --- a/src/player_hardcoded_effects.cpp +++ b/src/player_hardcoded_effects.cpp @@ -24,6 +24,7 @@ #include "enums.h" #include "mtype.h" #include "stomach.h" +#include "teleport.h" #if defined(TILES) # if defined(_MSC_VER) && defined(USE_VCPKG) @@ -616,7 +617,7 @@ void player::hardcoded_effects( effect &it ) if( !is_npc() ) { add_msg( _( "Glowing lights surround you, and you teleport." ) ); } - g->teleport(); + teleport::teleport( this ); g->events().send( getID() ); if( one_in( 10 ) ) { // Set ourselves up for removal diff --git a/src/teleport.cpp b/src/teleport.cpp new file mode 100644 index 0000000000000..5cc3b277389d6 --- /dev/null +++ b/src/teleport.cpp @@ -0,0 +1,93 @@ +#include "teleport.h" + +#include "character.h" +#include "avatar.h" +#include "creature.h" +#include "player.h" +#include "monster.h" +#include "game.h" +#include "map.h" +#include "messages.h" +#include "point.h" + + +const efftype_id effect_teleglow("teleglow"); + +bool teleport::teleport( Creature * c, int min_distance, int max_distance, bool safe, bool add_teleglow) +{ + if (c == nullptr || min_distance > max_distance) { + debugmsg("ERROR: Function teleport::teleport called with invalid arguments."); + return false; + } + + bool c_is_u = (c == &g->u); + player *p = dynamic_cast(c); + int tries = 0; + tripoint origin = c->pos(); + tripoint new_pos = tripoint_zero; + do { + int rangle = rng(0, 360); + int rdistance = rng(min_distance, max_distance); + new_pos.x =origin.x + rdistance * cos(rangle); + new_pos.y =origin.y + rdistance * sin(rangle); + tries++; + } while (g->m.impassable(new_pos) && tries < 20); + //handles teleporting into solids. + if (g->m.impassable(new_pos)) { + if (safe) { + if (c_is_u ) { + add_msg(m_bad, _("You cannot teleport safely")); + } + return false; + } + else { + c->apply_damage(nullptr, bp_torso, 9999); + if (c_is_u) { + g->events().send(p->getID(), g->m.obstacle_name(new_pos)); + add_msg(m_bad, _("You die after teleporting within a solid")); + } + } + } + //handles telefragging other creatures + if (Creature *const poor_soul = g->critter_at( new_pos ) ) { + if ( safe ){ + if ( c_is_u ) { + add_msg(m_bad, _("You cannot teleport safely")); + } + return false; + } + else { + const bool poor_soul_is_u = (poor_soul == &g->u); + if (poor_soul_is_u) { + add_msg(m_bad, _("...")); + add_msg(m_bad, _("You exlpode into thousands of fragments.")); + } + if (p) { + p->add_msg_player_or_npc(m_bad, _("You teleport into %s, and they explode into thousands of fragments."), + _(" teleports into %s, and they explode into thousands of fragments."), poor_soul->disp_name()); + g->events().send(p->getID(), poor_soul->get_name()); + } + else { + if (g->u.sees(poor_soul->pos())) { + add_msg(m_good, _("%1$s teleports into %2$s, killing them!"), + c->disp_name(), poor_soul->disp_name()); + } + } + poor_soul->apply_damage(nullptr, bp_torso, 9999); //Splatter real nice. + poor_soul->check_dead_state(); + } + } + + c->setpos(new_pos); + //player and npc exclusive teleporting effects + if (p) { + if (add_teleglow) { + add_msg(m_bad, _("unsafe")); + p->add_effect(effect_teleglow, 30_minutes); + } + } + if (c_is_u) { + g->update_map(*p); + } + return true; +} diff --git a/src/teleport.h b/src/teleport.h new file mode 100644 index 0000000000000..9a4f23cf4efb7 --- /dev/null +++ b/src/teleport.h @@ -0,0 +1,15 @@ +#pragma once +#ifndef TELEPORT_H +#define TELEPORT_H + +#include "creature.h" + +namespace teleport +{ + /** Teleports a creature to a tile within min_distance and max_distance tiles. Limited to 2D. + *bool safe determines wether the teleported creature can telefrag others/itself. + */ + bool teleport( Creature * c, int min_distance = 2, int max_distance = 12, bool safe = false, bool add_teleglow = true ); +} + +#endif diff --git a/src/trapfunc.cpp b/src/trapfunc.cpp index f76a2d60965c0..82608ebf0f1eb 100644 --- a/src/trapfunc.cpp +++ b/src/trapfunc.cpp @@ -32,6 +32,7 @@ #include "player.h" #include "int_id.h" #include "point.h" +#include "teleport.h" const mtype_id mon_blob( "mon_blob" ); const mtype_id mon_shadow( "mon_shadow" ); @@ -638,39 +639,15 @@ bool trapfunc::telepad( const tripoint &p, Creature *c, item * ) if( c == nullptr ) { return false; } - monster *z = dynamic_cast( c ); - // TODO: NPC don't teleport? if( c == &g->u ) { c->add_msg_if_player( m_warning, _( "The air shimmers around you..." ) ); - g->teleport(); - return true; - } else if( z != nullptr ) { - if( g->u.sees( *z ) ) { - add_msg( _( "The air shimmers around the %s..." ), z->name() ); - } - - int tries = 0; - int newposx = 0; - int newposy = 0; - do { - newposx = rng( z->posx() - SEEX, z->posx() + SEEX ); - newposy = rng( z->posy() - SEEY, z->posy() + SEEY ); - tries++; - } while( g->m.impassable( point( newposx, newposy ) ) && tries != 10 ); - - if( tries == 10 ) { - z->die_in_explosion( nullptr ); - } else if( monster *const mon_hit = g->critter_at( {newposx, newposy, z->posz()} ) ) { - if( g->u.sees( *z ) ) { - add_msg( m_good, _( "The %1$s teleports into a %2$s, killing them both!" ), - z->name(), mon_hit->name() ); - } - mon_hit->die_in_explosion( z ); - } else { - z->setpos( {newposx, newposy, z->posz()} ); + } + else { + if (g->u.sees(p)) { + add_msg(_("The air shimmers around %s..."), c->disp_name()); } - return true; } + teleport::teleport(c); return false; }