This commit is contained in:
Maxicarlos08 2023-08-17 10:09:17 +02:00
parent 0c9a942027
commit e5e26149ed
No known key found for this signature in database
20 changed files with 270 additions and 230 deletions

View File

@ -46,7 +46,6 @@ macro_rules! synced_components {
beam_segment: BeamSegment,
alignment: Alignment,
stance: Stance,
teleporter: Teleporter,
// TODO: change this to `SyncFrom::ClientEntity` and sync the bare minimum
// from other entities (e.g. just keys needed to show appearance
// based on their loadout). Also, it looks like this actually has
@ -235,10 +234,6 @@ impl NetSync for Stance {
const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
}
impl NetSync for Teleporter {
const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
}
// These are synced only from the client's own entity.
impl NetSync for Admin {

View File

@ -1,8 +1,12 @@
use super::item::Reagent;
use crate::{resources::Time, uid::Uid};
use crate::{
resources::{Secs, Time},
uid::Uid,
};
use serde::{Deserialize, Serialize};
use specs::{Component, DerefFlaggedStorage};
use std::time::Duration;
use vek::Vec3;
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum Object {
@ -17,8 +21,36 @@ pub enum Object {
spawned_at: Time,
timeout: Duration,
},
Portal {
target: Vec3<f32>,
requires_no_aggro: bool,
buildup_time: Secs,
},
}
impl Component for Object {
type Storage = DerefFlaggedStorage<Self, specs::VecStorage<Self>>;
}
#[derive(Clone)]
pub struct PortalData {
pub target: Vec3<f32>,
pub requires_no_aggro: bool,
pub buildup_time: Secs,
}
impl From<PortalData> for Object {
fn from(
PortalData {
target,
requires_no_aggro,
buildup_time,
}: PortalData,
) -> Self {
Self::Portal {
target,
requires_no_aggro,
buildup_time,
}
}
}

View File

@ -23,7 +23,7 @@ mod last;
mod location;
pub mod loot_owner;
pub mod melee;
mod misc;
pub mod misc;
pub mod ori;
pub mod pet;
mod phys;
@ -104,7 +104,7 @@ pub use self::{
SkillGroup, SkillGroupKind, SkillSet,
},
stats::{Stats, StatsModifier},
teleport::{Teleporter, Teleporting},
teleport::Teleporting,
visual::{LightAnimation, LightEmitter},
};

View File

@ -1,19 +1,6 @@
use serde::{Deserialize, Serialize};
use specs::{Component, DerefFlaggedStorage, Entity};
use vek::Vec3;
use crate::resources::{Secs, Time};
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Teleporter {
pub target: Vec3<f32>,
pub requires_no_aggro: bool,
pub buildup_time: Secs,
}
impl Component for Teleporter {
type Storage = DerefFlaggedStorage<Self, specs::VecStorage<Self>>;
}
use crate::resources::Time;
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Teleporting {

View File

@ -32,3 +32,5 @@ pub const SOUND_TRAVEL_DIST_PER_VOLUME: f32 = 3.0;
// Stat increase per level (multiplied by 10 compared to what you'll see in UI)
pub const ENERGY_PER_LEVEL: u16 = 5;
pub const HP_PER_LEVEL: u16 = 5;
pub const TELEPORTER_RADIUS: f32 = 3.;

View File

@ -6,7 +6,8 @@ use crate::{
agent::Sound,
dialogue::Subject,
invite::{InviteKind, InviteResponse},
DisconnectReason, Ori, Pos, Teleporter,
misc::PortalData,
DisconnectReason, Ori, Pos,
},
lottery::LootSpec,
mounting::VolumePos,
@ -239,7 +240,7 @@ pub enum ServerEvent {
driver: Option<NpcBuilder>,
},
CreateWaypoint(Vec3<f32>),
CreateTeleporter(Vec3<f32>, Teleporter),
CreateTeleporter(Vec3<f32>, PortalData),
ClientDisconnect(EcsEntity, DisconnectReason),
ClientDisconnectWithoutPersistence(EcsEntity),
Command(EcsEntity, String, Vec<String>),

View File

@ -3,7 +3,8 @@ use crate::{
comp::{
self, agent, humanoid,
inventory::loadout_builder::{LoadoutBuilder, LoadoutSpec},
Alignment, Body, Item, Teleporter,
misc::PortalData,
Alignment, Body, Item,
},
lottery::LootSpec,
npc::{self, NPC_NAMES},
@ -166,7 +167,7 @@ pub fn try_all_entity_configs() -> Result<Vec<String>, Error> {
#[derive(Clone)]
pub enum SpecialEntity {
Waypoint,
Teleporter(Teleporter),
Teleporter(PortalData),
}
#[derive(Clone)]

View File

@ -219,7 +219,6 @@ impl State {
ecs.register::<comp::LootOwner>();
ecs.register::<comp::Admin>();
ecs.register::<comp::Stance>();
ecs.register::<comp::Teleporter>();
ecs.register::<comp::Teleporting>();
// Register components send from clients -> server

View File

@ -33,21 +33,20 @@ use common::{
slot::Slot,
},
invite::InviteKind,
AdminRole, ChatType, Inventory, Item, LightEmitter, Presence, PresenceKind, WaypointArea,
misc::PortalData,
AdminRole, ChatType, Inventory, Item, LightEmitter, WaypointArea,
},
depot,
effect::Effect,
event::{EventBus, ServerEvent},
generation::{EntityConfig, EntityInfo},
link::Is,
mounting::{Rider, VolumeRider},
npc::{self, get_npc_name},
outcome::Outcome,
parse_cmd_args,
resources::{BattleMode, PlayerPhysicsSettings, Secs, Time, TimeOfDay, TimeScale},
rtsim::{Actor, Role},
terrain::{Block, BlockKind, CoordinateConversions, SpriteKind, TerrainChunkSize},
uid::{IdMaps, Uid},
uid::Uid,
vol::ReadVol,
weather, Damage, DamageKind, DamageSource, Explosion, LoadoutBuilder, RadiusEffect,
};
@ -223,89 +222,6 @@ fn position(server: &Server, entity: EcsEntity, descriptor: &str) -> CmdResult<c
.ok_or_else(|| format!("Cannot get position for {:?}!", descriptor))
}
fn position_mut<T>(
server: &mut Server,
entity: EcsEntity,
descriptor: &str,
dismount_volume: Option<bool>,
f: impl for<'a> FnOnce(&'a mut comp::Pos) -> T,
) -> CmdResult<T> {
if dismount_volume.unwrap_or(true) {
server
.state
.ecs()
.write_storage::<Is<VolumeRider>>()
.remove(entity);
}
let entity = server
.state
.read_storage::<Is<Rider>>()
.get(entity)
.and_then(|is_rider| {
server
.state
.ecs()
.read_resource::<IdMaps>()
.uid_entity(is_rider.mount)
})
.map(Ok)
.or_else(|| {
server
.state
.read_storage::<Is<VolumeRider>>()
.get(entity)
.and_then(|volume_rider| {
Some(match volume_rider.pos.kind {
common::mounting::Volume::Terrain => {
Err("Tried to move the world.".to_string())
},
common::mounting::Volume::Entity(uid) => Ok(server
.state
.ecs()
.read_resource::<IdMaps>()
.uid_entity(uid)?),
})
})
})
.unwrap_or(Ok(entity))?;
let mut maybe_pos = None;
let res = server
.state
.ecs()
.write_storage::<comp::Pos>()
.get_mut(entity)
.map(|pos| {
let res = f(pos);
maybe_pos = Some(pos.0);
res
})
.ok_or_else(|| format!("Cannot get position for {:?}!", descriptor));
if let Some(pos) = maybe_pos {
if server
.state
.ecs()
.read_storage::<Presence>()
.get(entity)
.map(|presence| presence.kind == PresenceKind::Spectator)
.unwrap_or(false)
{
server.notify_client(entity, ServerGeneral::SpectatePosition(pos));
} else {
server
.state
.ecs()
.write_storage::<comp::ForceUpdate>()
.get_mut(entity)
.map(|force_update| force_update.update());
}
}
res
}
fn insert_or_replace_component<C: specs::Component>(
server: &mut Server,
entity: EcsEntity,
@ -859,9 +775,11 @@ fn handle_jump(
) -> CmdResult<()> {
if let (Some(x), Some(y), Some(z), dismount_volume) = parse_cmd_args!(args, f32, f32, f32, bool)
{
position_mut(server, target, "target", dismount_volume, |current_pos| {
current_pos.0 += Vec3::new(x, y, z)
})
server
.state
.position_mut(target, dismount_volume, |current_pos| {
current_pos.0 += Vec3::new(x, y, z)
})
} else {
Err(action.help_string())
}
@ -876,9 +794,11 @@ fn handle_goto(
) -> CmdResult<()> {
if let (Some(x), Some(y), Some(z), dismount_volume) = parse_cmd_args!(args, f32, f32, f32, bool)
{
position_mut(server, target, "target", dismount_volume, |current_pos| {
current_pos.0 = Vec3::new(x, y, z)
})
server
.state
.position_mut(target, dismount_volume, |current_pos| {
current_pos.0 = Vec3::new(x, y, z)
})
} else {
Err(action.help_string())
}
@ -911,9 +831,11 @@ fn handle_site(
false,
);
position_mut(server, target, "target", dismount_volume, |current_pos| {
current_pos.0 = site_pos
})
server
.state
.position_mut(target, dismount_volume, |current_pos| {
current_pos.0 = site_pos
})
} else {
Err(action.help_string())
}
@ -936,9 +858,11 @@ fn handle_respawn(
.ok_or("No waypoint set")?
.get_pos();
position_mut(server, target, "target", Some(true), |current_pos| {
current_pos.0 = waypoint;
})
server
.state
.position_mut(target, Some(true), |current_pos| {
current_pos.0 = waypoint;
})
}
fn handle_kill(
@ -1308,9 +1232,11 @@ fn handle_tp(
return Err(action.help_string());
};
let player_pos = position(server, player, "player")?;
position_mut(server, target, "target", dismount_volume, |target_pos| {
*target_pos = player_pos
})
server
.state
.position_mut(target, dismount_volume, |target_pos| {
*target_pos = player_pos
})
}
fn handle_rtsim_tp(
@ -1338,9 +1264,11 @@ fn handle_rtsim_tp(
} else {
return Err(action.help_string());
};
position_mut(server, target, "target", dismount_volume, |target_pos| {
target_pos.0 = pos;
})
server
.state
.position_mut(target, dismount_volume, |target_pos| {
target_pos.0 = pos;
})
}
fn handle_rtsim_info(
@ -2058,10 +1986,10 @@ fn handle_spawn_portal(
let buildup_time = Secs(buildup_time.unwrap_or(7.));
server
.state
.create_teleporter(pos, comp::Teleporter {
.create_teleporter(pos, PortalData {
target: Vec3::new(x, y, z),
requires_no_aggro,
buildup_time,
requires_no_aggro,
})
.build();
@ -4112,7 +4040,7 @@ fn handle_location(
if let Some(name) = parse_cmd_args!(args, String) {
let loc = server.state.ecs().read_resource::<Locations>().get(&name);
match loc {
Ok(loc) => position_mut(server, target, "target", Some(true), |target_pos| {
Ok(loc) => server.state.position_mut(target, Some(true), |target_pos| {
target_pos.0 = loc;
}),
Err(e) => Err(e.to_string()),

View File

@ -9,9 +9,10 @@ use common::{
aura::{Aura, AuraKind, AuraTarget},
beam,
buff::{BuffCategory, BuffData, BuffKind, BuffSource},
misc::PortalData,
ship::figuredata::VOXEL_COLLIDER_MANIFEST,
shockwave, Alignment, BehaviorCapability, Body, ItemDrops, LightEmitter, Object, Ori, Pos,
Projectile, Teleporter, TradingBehavior, Vel, WaypointArea,
Projectile, TradingBehavior, Vel, WaypointArea,
},
event::{EventBus, NpcBuilder, UpdateCharacterMetadata},
mounting::{Mounting, Volume, VolumeMounting, VolumePos},
@ -419,9 +420,9 @@ pub fn handle_create_waypoint(server: &mut Server, pos: Vec3<f32>) {
.build();
}
pub fn handle_create_teleporter(server: &mut Server, pos: Vec3<f32>, teleporter: Teleporter) {
pub fn handle_create_teleporter(server: &mut Server, pos: Vec3<f32>, portal: PortalData) {
server
.state
.create_teleporter(comp::Pos(pos), teleporter)
.create_teleporter(comp::Pos(pos), portal)
.build();
}

View File

@ -8,7 +8,7 @@ use crate::{
BuffKind, BuffSource, PhysicsState,
},
rtsim,
sys::{teleporter::TELEPORT_RADIUS, terrain::SAFE_ZONE_RADIUS},
sys::terrain::SAFE_ZONE_RADIUS,
Server, SpawnPoint, StateExt,
};
use authc::Uuid;
@ -22,8 +22,9 @@ use common::{
item::flatten_counted_items,
loot_owner::LootOwnerKind,
Alignment, Auras, Body, CharacterState, Energy, Group, Health, HealthChange, Inventory,
Player, Poise, Pos, SkillSet, Stats,
Object, Player, Poise, Pos, SkillSet, Stats,
},
consts::TELEPORTER_RADIUS,
event::{EventBus, ServerEvent},
lottery::distribute_many,
outcome::{HealthChangeInfo, Outcome},
@ -43,7 +44,7 @@ use hashbrown::HashSet;
use rand::Rng;
use specs::{join::Join, Builder, Entity as EcsEntity, Entity, WorldExt};
use std::{collections::HashMap, iter, sync::Arc, time::Duration};
use tracing::{debug, error};
use tracing::{debug, error, warn};
use vek::{Vec2, Vec3};
#[derive(Hash, Eq, PartialEq)]
@ -1658,16 +1659,12 @@ pub fn handle_remove_light_emitter(server: &mut Server, entity: EcsEntity) {
}
pub fn handle_teleport_to_position(server: &mut Server, entity: EcsEntity, position: Vec3<f32>) {
let ecs = server.state.ecs();
ecs.write_storage::<comp::Pos>()
.get_mut(entity)
.map(|old_position| {
old_position.0 = position;
});
ecs.write_storage::<comp::ForceUpdate>()
.get_mut(entity)
.map(|forced_update| forced_update.update());
if let Err(error) = server
.state
.position_mut(entity, Some(true), |pos| pos.0 = position)
{
warn!("Failed to teleport entity: {error}");
}
}
pub fn handle_start_teleporting(server: &mut Server, entity: EcsEntity, portal: EcsEntity) {
@ -1681,15 +1678,20 @@ pub fn handle_start_teleporting(server: &mut Server, entity: EcsEntity, portal:
.flatten()
.zip(positions.get(portal))
.filter(|(entity_pos, portal_pos)| {
entity_pos.0.distance_squared(portal_pos.0) <= TELEPORT_RADIUS.powi(2)
entity_pos.0.distance_squared(portal_pos.0) <= TELEPORTER_RADIUS.powi(2)
})
.and_then(|(_, _)| {
Some(
now + ecs
.read_storage::<comp::Teleporter>()
.get(portal)?
.buildup_time
.0,
.read_storage::<comp::Object>()
.get(portal)
.and_then(|object| {
if let Object::Portal { buildup_time, .. } = object {
Some(buildup_time.0)
} else {
None
}
})?,
)
})
{

View File

@ -213,8 +213,8 @@ impl Server {
driver,
} => handle_create_ship(self, pos, ori, ship, rtsim_entity, driver, Vec::new()),
ServerEvent::CreateWaypoint(pos) => handle_create_waypoint(self, pos),
ServerEvent::CreateTeleporter(pos, teleporter) => {
handle_create_teleporter(self, pos, teleporter)
ServerEvent::CreateTeleporter(pos, portal) => {
handle_create_teleporter(self, pos, portal)
},
ServerEvent::ClientDisconnect(entity, reason) => {
frontend_events.push(handle_client_disconnect(self, entity, reason, false))

View File

@ -18,13 +18,14 @@ use common::{
comp::{
self,
item::{ItemKind, MaterialStatManifest},
misc::PortalData,
object,
skills::{GeneralSkill, Skill},
ChatType, Group, Inventory, Item, LootOwner, Object, Player, Poise, Presence, PresenceKind,
},
effect::Effect,
link::{Link, LinkHandle},
mounting::{Mounting, VolumeMounting},
link::{Is, Link, LinkHandle},
mounting::{Mounting, Rider, VolumeMounting, VolumeRider},
resources::{Secs, Time, TimeOfDay},
rtsim::{Actor, RtSimEntity},
slowjob::SlowJobPool,
@ -118,11 +119,7 @@ pub trait StateExt {
/// Creates a teleporter entity, which allows players to teleport to the
/// `target` position. You might want to require the teleporting entity
/// to not have agro for teleporting.
fn create_teleporter(
&mut self,
pos: comp::Pos,
teleporter: comp::Teleporter,
) -> EcsEntityBuilder;
fn create_teleporter(&mut self, pos: comp::Pos, portal: PortalData) -> EcsEntityBuilder;
/// Insert common/default components for a new character joining the server
fn initialize_character_data(
&mut self,
@ -161,6 +158,12 @@ pub trait StateExt {
) -> Result<(), specs::error::WrongGeneration>;
/// Get the given entity as an [`Actor`], if it is one.
fn entity_as_actor(&self, entity: EcsEntity) -> Option<Actor>;
fn position_mut<T>(
&mut self,
entity: EcsEntity,
dismount_volume: Option<bool>,
f: impl for<'a> FnOnce(&'a mut comp::Pos) -> T,
) -> Result<T, String>;
}
impl StateExt for State {
@ -616,14 +619,10 @@ impl StateExt for State {
))
}
fn create_teleporter(
&mut self,
pos: comp::Pos,
teleporter: comp::Teleporter,
) -> EcsEntityBuilder {
fn create_teleporter(&mut self, pos: comp::Pos, portal: PortalData) -> EcsEntityBuilder {
self.create_object(pos, object::Body::Portal)
.with(comp::Immovable)
.with(teleporter)
.with(comp::Object::from(portal))
}
fn initialize_character_data(
@ -1244,6 +1243,76 @@ impl StateExt for State {
None
}
}
fn position_mut<T>(
&mut self,
entity: EcsEntity,
dismount_volume: Option<bool>,
f: impl for<'a> FnOnce(&'a mut comp::Pos) -> T,
) -> Result<T, String> {
if dismount_volume.unwrap_or(true) {
self.ecs().write_storage::<Is<VolumeRider>>().remove(entity);
}
let entity = self
.read_storage::<Is<Rider>>()
.get(entity)
.and_then(|is_rider| {
self.ecs()
.read_resource::<IdMaps>()
.uid_entity(is_rider.mount)
})
.map(Ok)
.or_else(|| {
self.read_storage::<Is<VolumeRider>>()
.get(entity)
.and_then(|volume_rider| {
Some(match volume_rider.pos.kind {
common::mounting::Volume::Terrain => {
Err("Tried to move the world.".to_string())
},
common::mounting::Volume::Entity(uid) => {
Ok(self.ecs().read_resource::<IdMaps>().uid_entity(uid)?)
},
})
})
})
.unwrap_or(Ok(entity))?;
let mut maybe_pos = None;
let res = self
.ecs()
.write_storage::<comp::Pos>()
.get_mut(entity)
.map(|pos| {
let res = f(pos);
maybe_pos = Some(pos.0);
res
})
.ok_or_else(|| "Cannot get position for entity!".to_string());
if let Some(pos) = maybe_pos {
if self
.ecs()
.read_storage::<Presence>()
.get(entity)
.map(|presence| presence.kind == PresenceKind::Spectator)
.unwrap_or(false)
{
self.read_storage::<Client>().get(entity).map(|client| {
client.send_fallible(ServerGeneral::SpectatePosition(pos));
});
} else {
self.ecs()
.write_storage::<comp::ForceUpdate>()
.get_mut(entity)
.map(|force_update| force_update.update());
}
}
res
}
}
fn send_to_group(g: &Group, ecs: &specs::World, msg: &comp::ChatMsg) {

View File

@ -1,9 +1,10 @@
use common::{
comp::{Object, PhysicsState, Pos, Vel},
comp::{object, Body, Object, PhysicsState, Pos, Teleporting, Vel},
consts::TELEPORTER_RADIUS,
effect::Effect,
event::{EventBus, ServerEvent},
resources::{DeltaTime, Time},
Damage, DamageKind, DamageSource, Explosion, RadiusEffect,
CachedSpatialGrid, Damage, DamageKind, DamageSource, Explosion, RadiusEffect,
};
use common_ecs::{Job, Origin, Phase, System};
use specs::{Entities, Join, Read, ReadStorage};
@ -18,10 +19,13 @@ impl<'a> System<'a> for Sys {
Read<'a, DeltaTime>,
Read<'a, Time>,
Read<'a, EventBus<ServerEvent>>,
Read<'a, CachedSpatialGrid>,
ReadStorage<'a, Pos>,
ReadStorage<'a, Vel>,
ReadStorage<'a, PhysicsState>,
ReadStorage<'a, Object>,
ReadStorage<'a, Body>,
ReadStorage<'a, Teleporting>,
);
const NAME: &'static str = "object";
@ -30,17 +34,30 @@ impl<'a> System<'a> for Sys {
fn run(
_job: &mut Job<Self>,
(entities, _dt, time, server_bus, positions, velocities, physics_states, objects): Self::SystemData,
(
entities,
_dt,
time,
server_bus,
spatial_grid,
positions,
velocities,
physics_states,
objects,
bodies,
teleporting,
): Self::SystemData,
) {
let mut server_emitter = server_bus.emitter();
// Objects
for (entity, pos, vel, physics, object) in (
for (entity, pos, vel, physics, object, body) in (
&entities,
&positions,
&velocities,
&physics_states,
&objects,
&bodies,
)
.join()
{
@ -73,7 +90,7 @@ impl<'a> System<'a> for Sys {
const ENABLE_RECURSIVE_FIREWORKS: bool = true;
if ENABLE_RECURSIVE_FIREWORKS {
use common::{
comp::{object, Body, LightEmitter, Projectile},
comp::{LightEmitter, Projectile},
util::Dir,
};
use rand::Rng;
@ -169,6 +186,31 @@ impl<'a> System<'a> for Sys {
server_emitter.emit(ServerEvent::Delete(entity));
}
},
Object::Portal { .. } => {
let is_active = spatial_grid
.0
.in_circle_aabr(pos.0.xy(), TELEPORTER_RADIUS)
.any(|entity| {
(&positions, &teleporting)
.join()
.get(entity, &entities)
.map_or(false, |(teleporter_pos, _)| {
pos.0.distance_squared(teleporter_pos.0)
<= TELEPORTER_RADIUS.powi(2)
})
});
if (*body == Body::Object(object::Body::PortalActive)) != is_active {
server_bus.emit_now(ServerEvent::ChangeBody {
entity,
new_body: Body::Object(if is_active {
object::Body::PortalActive
} else {
object::Body::Portal
}),
});
}
},
}
}
}

View File

@ -1,5 +1,6 @@
use common::{
comp::{object, Agent, Alignment, Body, CharacterState, Pos, Teleporter, Teleporting},
comp::{Agent, Alignment, CharacterState, Object, Pos, Teleporting},
consts::TELEPORTER_RADIUS,
event::{EventBus, ServerEvent},
resources::Time,
uid::Uid,
@ -9,7 +10,6 @@ use common_ecs::{Origin, Phase, System};
use specs::{Entities, Join, Read, ReadStorage, WriteStorage};
use vek::Vec3;
pub const TELEPORT_RADIUS: f32 = 3.;
const MAX_AGGRO_DIST: f32 = 200.; // If an entity further than this is aggroed at a player, the portal will still work
const PET_TELEPORT_RADIUS: f32 = 20.;
@ -17,7 +17,7 @@ const PET_TELEPORT_RADIUS: f32 = 20.;
pub struct Sys;
fn in_portal_range(player_pos: Vec3<f32>, portal_pos: Vec3<f32>) -> bool {
player_pos.distance_squared(portal_pos) <= (TELEPORT_RADIUS).powi(2)
player_pos.distance_squared(portal_pos) <= TELEPORTER_RADIUS.powi(2)
}
impl<'a> System<'a> for Sys {
@ -25,11 +25,10 @@ impl<'a> System<'a> for Sys {
Entities<'a>,
ReadStorage<'a, Pos>,
ReadStorage<'a, Uid>,
ReadStorage<'a, Teleporter>,
ReadStorage<'a, Alignment>,
ReadStorage<'a, Agent>,
ReadStorage<'a, Object>,
WriteStorage<'a, Teleporting>,
ReadStorage<'a, Body>,
ReadStorage<'a, CharacterState>,
Read<'a, CachedSpatialGrid>,
Read<'a, Time>,
@ -46,11 +45,10 @@ impl<'a> System<'a> for Sys {
entities,
positions,
uids,
teleporters,
alignments,
agent,
objects,
mut teleporting,
bodies,
character_states,
spatial_grid,
time,
@ -72,31 +70,6 @@ impl<'a> System<'a> for Sys {
let mut cancel_teleporting = Vec::new();
for (portal_entity, teleporter_pos, body, _) in
(&entities, &positions, &bodies, &teleporters).join()
{
let is_active = spatial_grid
.0
.in_circle_aabr(teleporter_pos.0.xy(), TELEPORT_RADIUS)
.any(|entity| {
(&positions, &teleporting)
.join()
.get(entity, &entities)
.map_or(false, |(pos, _)| in_portal_range(pos.0, teleporter_pos.0))
});
if (*body == Body::Object(object::Body::PortalActive)) != is_active {
event_bus.emit_now(ServerEvent::ChangeBody {
entity: portal_entity,
new_body: Body::Object(if is_active {
object::Body::PortalActive
} else {
object::Body::Portal
}),
});
}
}
for (entity, uid, position, teleporting, character_state) in (
&entities,
&uids,
@ -107,14 +80,16 @@ impl<'a> System<'a> for Sys {
.join()
{
let portal_pos = positions.get(teleporting.portal);
let Some(teleporter) = teleporters.get(teleporting.portal) else {
let Some(Object::Portal { target, requires_no_aggro, .. }) = objects
.get(teleporting.portal)
else {
cancel_teleporting.push(entity);
continue
};
if portal_pos.map_or(true, |portal_pos| {
!in_portal_range(position.0, portal_pos.0)
}) || (teleporter.requires_no_aggro && check_aggro(entity, position.0))
}) || (*requires_no_aggro && check_aggro(entity, position.0))
|| !matches!(
character_state,
CharacterState::Idle(_) | CharacterState::Wielding(_)
@ -142,7 +117,7 @@ impl<'a> System<'a> for Sys {
cancel_teleporting.push(entity);
event_bus.emit_now(ServerEvent::TeleportToPosition {
entity,
position: teleporter.target,
position: *target,
});
}
}

View File

@ -12,8 +12,8 @@ use crate::{
use common::{
calendar::Calendar,
comp::{
self, agent, biped_small, bird_medium, skillset::skills, BehaviorCapability, ForceUpdate,
Pos, Presence, Teleporter, Waypoint,
self, agent, biped_small, bird_medium, misc::PortalData, skillset::skills,
BehaviorCapability, ForceUpdate, Pos, Presence, Waypoint,
},
event::{EventBus, NpcBuilder, ServerEvent},
generation::{EntityInfo, SpecialEntity},
@ -418,7 +418,7 @@ pub enum NpcData {
loot: LootSpec<String>,
},
Waypoint(Vec3<f32>),
Teleporter(Vec3<f32>, Teleporter),
Teleporter(Vec3<f32>, PortalData),
}
impl NpcData {

View File

@ -2233,12 +2233,13 @@ impl Hud {
.map_or(Vec3::zero(), |e| e.0);
let over_pos = pos + Vec3::unit_z() * 1.5;
let is_campfire = body.is_campfire();
overitem::Overitem::new(
i18n.get_msg(if is_campfire {
i18n.get_msg(if body.is_campfire() {
"hud-crafting-campfire"
} else {
} else if body.is_portal() {
"hud-portal"
} else {
"hud-use"
}),
overitem::TEXT_COLOR,
pos.distance_squared(player_pos),
@ -2250,7 +2251,13 @@ impl Hud {
&global_state.window.key_layout,
vec![(
Some(GameInput::Interact),
i18n.get_msg(if is_campfire { "hud-sit" } else { "hud-activate" }).to_string(),
i18n.get_msg(if body.is_campfire() {
"hud-sit"
} else if body.is_portal() {
"hud-activate"
} else {
"hud-use"
}).to_string(),
)],
)
.x_y(0.0, 100.0)

View File

@ -10,7 +10,7 @@ use client::Client;
use common::{
comp,
comp::{ship::figuredata::VOXEL_COLLIDER_MANIFEST, tool::ToolKind, Collider, Content},
consts::{MAX_PICKUP_RANGE, MAX_SPRITE_MOUNT_RANGE},
consts::{MAX_PICKUP_RANGE, MAX_SPRITE_MOUNT_RANGE, TELEPORTER_RADIUS},
link::Is,
mounting::{Mount, Rider, VolumePos, VolumeRider},
terrain::{Block, TerrainGrid, UnlockKind},
@ -247,7 +247,7 @@ pub(super) fn select_interactable(
// * Are not riding the player
let not_riding_player = is_rider
.map_or(true, |is_rider| Some(&is_rider.mount) != uids.get(viewpoint_entity));
let is_interactable = (b.is_campfire() || (b.is_portal() && (p.0.distance_squared(player_pos) <= 3f32.powi(2))) || has_stats_or_item.is_some()) && not_riding_player;
let is_interactable = (b.is_campfire() || (b.is_portal() && (p.0.distance_squared(player_pos) <= TELEPORTER_RADIUS.powi(2))) || has_stats_or_item.is_some()) && not_riding_player;
if !is_interactable {
return None;
};

View File

@ -1883,24 +1883,23 @@ impl SiteKind {
SiteKind::Dungeon => {
on_land() && {
let land = Land::from_sim(sim);
let loc = loc.map(|v| TerrainChunkSize::RECT_SIZE.y as i32 * v);
let loc = loc.cpos_to_wpos();
let dungeon_aabr = Aabr {
min: loc - Vec2::broadcast(200),
max: loc + Vec2::broadcast(200),
};
// Get shallow caves under the dungeon
let mut tunnels = cave::tunnels_at(loc, 1, &land)
// Make sure there are no shallow caves near the dungeon
let collides_with_cave = cave::tunnels_at(loc, 1, &land)
.chain(cave::tunnels_at(loc, 2, &land))
.filter(|tunnel| {
dungeon_aabr.collides_with_aabr(Aabr {
.all(|tunnel| {
!dungeon_aabr.collides_with_aabr(Aabr {
min: tunnel.nodes().0.wpos,
max: tunnel.nodes().1.wpos,
})
});
// Make sure there are no shallow caves near the dungeon
tunnels.next().is_none()
collides_with_cave
}
},
SiteKind::Refactor | SiteKind::Settlement => suitable_for_town(),

View File

@ -9,7 +9,7 @@ use crate::{
use common::{
assets::{self, AssetExt, AssetHandle},
astar::Astar,
comp::Teleporter,
comp::misc::PortalData,
generation::{ChunkSupplement, EntityInfo, SpecialEntity},
resources::Secs,
store::{Id, Store},
@ -666,14 +666,14 @@ impl Floor {
// Move both a bit to the side to prevent teleportation loop, ideally we'd have the portals at another location
supplement.add_entity(EntityInfo::at(top_pos).into_special(
SpecialEntity::Teleporter(Teleporter {
SpecialEntity::Teleporter(PortalData {
target: bottom_pos + Vec3::unit_x() * 5.,
requires_no_aggro: true,
buildup_time: Secs(5.),
}),
));
supplement.add_entity(EntityInfo::at(bottom_pos).into_special(
SpecialEntity::Teleporter(Teleporter {
SpecialEntity::Teleporter(PortalData {
target: top_pos + Vec3::unit_x() * 5.,
requires_no_aggro: true,
buildup_time: Secs(5.),