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, beam_segment: BeamSegment,
alignment: Alignment, alignment: Alignment,
stance: Stance, stance: Stance,
teleporter: Teleporter,
// TODO: change this to `SyncFrom::ClientEntity` and sync the bare minimum // TODO: change this to `SyncFrom::ClientEntity` and sync the bare minimum
// from other entities (e.g. just keys needed to show appearance // from other entities (e.g. just keys needed to show appearance
// based on their loadout). Also, it looks like this actually has // 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; 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. // These are synced only from the client's own entity.
impl NetSync for Admin { impl NetSync for Admin {

View File

@ -1,8 +1,12 @@
use super::item::Reagent; use super::item::Reagent;
use crate::{resources::Time, uid::Uid}; use crate::{
resources::{Secs, Time},
uid::Uid,
};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use specs::{Component, DerefFlaggedStorage}; use specs::{Component, DerefFlaggedStorage};
use std::time::Duration; use std::time::Duration;
use vek::Vec3;
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum Object { pub enum Object {
@ -17,8 +21,36 @@ pub enum Object {
spawned_at: Time, spawned_at: Time,
timeout: Duration, timeout: Duration,
}, },
Portal {
target: Vec3<f32>,
requires_no_aggro: bool,
buildup_time: Secs,
},
} }
impl Component for Object { impl Component for Object {
type Storage = DerefFlaggedStorage<Self, specs::VecStorage<Self>>; 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; mod location;
pub mod loot_owner; pub mod loot_owner;
pub mod melee; pub mod melee;
mod misc; pub mod misc;
pub mod ori; pub mod ori;
pub mod pet; pub mod pet;
mod phys; mod phys;
@ -104,7 +104,7 @@ pub use self::{
SkillGroup, SkillGroupKind, SkillSet, SkillGroup, SkillGroupKind, SkillSet,
}, },
stats::{Stats, StatsModifier}, stats::{Stats, StatsModifier},
teleport::{Teleporter, Teleporting}, teleport::Teleporting,
visual::{LightAnimation, LightEmitter}, visual::{LightAnimation, LightEmitter},
}; };

View File

@ -1,19 +1,6 @@
use serde::{Deserialize, Serialize};
use specs::{Component, DerefFlaggedStorage, Entity}; use specs::{Component, DerefFlaggedStorage, Entity};
use vek::Vec3;
use crate::resources::{Secs, Time}; use crate::resources::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>>;
}
#[derive(Copy, Clone, Debug, PartialEq)] #[derive(Copy, Clone, Debug, PartialEq)]
pub struct Teleporting { 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) // Stat increase per level (multiplied by 10 compared to what you'll see in UI)
pub const ENERGY_PER_LEVEL: u16 = 5; pub const ENERGY_PER_LEVEL: u16 = 5;
pub const HP_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, agent::Sound,
dialogue::Subject, dialogue::Subject,
invite::{InviteKind, InviteResponse}, invite::{InviteKind, InviteResponse},
DisconnectReason, Ori, Pos, Teleporter, misc::PortalData,
DisconnectReason, Ori, Pos,
}, },
lottery::LootSpec, lottery::LootSpec,
mounting::VolumePos, mounting::VolumePos,
@ -239,7 +240,7 @@ pub enum ServerEvent {
driver: Option<NpcBuilder>, driver: Option<NpcBuilder>,
}, },
CreateWaypoint(Vec3<f32>), CreateWaypoint(Vec3<f32>),
CreateTeleporter(Vec3<f32>, Teleporter), CreateTeleporter(Vec3<f32>, PortalData),
ClientDisconnect(EcsEntity, DisconnectReason), ClientDisconnect(EcsEntity, DisconnectReason),
ClientDisconnectWithoutPersistence(EcsEntity), ClientDisconnectWithoutPersistence(EcsEntity),
Command(EcsEntity, String, Vec<String>), Command(EcsEntity, String, Vec<String>),

View File

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

View File

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

View File

@ -33,21 +33,20 @@ use common::{
slot::Slot, slot::Slot,
}, },
invite::InviteKind, invite::InviteKind,
AdminRole, ChatType, Inventory, Item, LightEmitter, Presence, PresenceKind, WaypointArea, misc::PortalData,
AdminRole, ChatType, Inventory, Item, LightEmitter, WaypointArea,
}, },
depot, depot,
effect::Effect, effect::Effect,
event::{EventBus, ServerEvent}, event::{EventBus, ServerEvent},
generation::{EntityConfig, EntityInfo}, generation::{EntityConfig, EntityInfo},
link::Is,
mounting::{Rider, VolumeRider},
npc::{self, get_npc_name}, npc::{self, get_npc_name},
outcome::Outcome, outcome::Outcome,
parse_cmd_args, parse_cmd_args,
resources::{BattleMode, PlayerPhysicsSettings, Secs, Time, TimeOfDay, TimeScale}, resources::{BattleMode, PlayerPhysicsSettings, Secs, Time, TimeOfDay, TimeScale},
rtsim::{Actor, Role}, rtsim::{Actor, Role},
terrain::{Block, BlockKind, CoordinateConversions, SpriteKind, TerrainChunkSize}, terrain::{Block, BlockKind, CoordinateConversions, SpriteKind, TerrainChunkSize},
uid::{IdMaps, Uid}, uid::Uid,
vol::ReadVol, vol::ReadVol,
weather, Damage, DamageKind, DamageSource, Explosion, LoadoutBuilder, RadiusEffect, 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)) .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>( fn insert_or_replace_component<C: specs::Component>(
server: &mut Server, server: &mut Server,
entity: EcsEntity, entity: EcsEntity,
@ -859,7 +775,9 @@ fn handle_jump(
) -> CmdResult<()> { ) -> CmdResult<()> {
if let (Some(x), Some(y), Some(z), dismount_volume) = parse_cmd_args!(args, f32, f32, f32, bool) 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| { server
.state
.position_mut(target, dismount_volume, |current_pos| {
current_pos.0 += Vec3::new(x, y, z) current_pos.0 += Vec3::new(x, y, z)
}) })
} else { } else {
@ -876,7 +794,9 @@ fn handle_goto(
) -> CmdResult<()> { ) -> CmdResult<()> {
if let (Some(x), Some(y), Some(z), dismount_volume) = parse_cmd_args!(args, f32, f32, f32, bool) 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| { server
.state
.position_mut(target, dismount_volume, |current_pos| {
current_pos.0 = Vec3::new(x, y, z) current_pos.0 = Vec3::new(x, y, z)
}) })
} else { } else {
@ -911,7 +831,9 @@ fn handle_site(
false, false,
); );
position_mut(server, target, "target", dismount_volume, |current_pos| { server
.state
.position_mut(target, dismount_volume, |current_pos| {
current_pos.0 = site_pos current_pos.0 = site_pos
}) })
} else { } else {
@ -936,7 +858,9 @@ fn handle_respawn(
.ok_or("No waypoint set")? .ok_or("No waypoint set")?
.get_pos(); .get_pos();
position_mut(server, target, "target", Some(true), |current_pos| { server
.state
.position_mut(target, Some(true), |current_pos| {
current_pos.0 = waypoint; current_pos.0 = waypoint;
}) })
} }
@ -1308,7 +1232,9 @@ fn handle_tp(
return Err(action.help_string()); return Err(action.help_string());
}; };
let player_pos = position(server, player, "player")?; let player_pos = position(server, player, "player")?;
position_mut(server, target, "target", dismount_volume, |target_pos| { server
.state
.position_mut(target, dismount_volume, |target_pos| {
*target_pos = player_pos *target_pos = player_pos
}) })
} }
@ -1338,7 +1264,9 @@ fn handle_rtsim_tp(
} else { } else {
return Err(action.help_string()); return Err(action.help_string());
}; };
position_mut(server, target, "target", dismount_volume, |target_pos| { server
.state
.position_mut(target, dismount_volume, |target_pos| {
target_pos.0 = pos; target_pos.0 = pos;
}) })
} }
@ -2058,10 +1986,10 @@ fn handle_spawn_portal(
let buildup_time = Secs(buildup_time.unwrap_or(7.)); let buildup_time = Secs(buildup_time.unwrap_or(7.));
server server
.state .state
.create_teleporter(pos, comp::Teleporter { .create_teleporter(pos, PortalData {
target: Vec3::new(x, y, z), target: Vec3::new(x, y, z),
requires_no_aggro,
buildup_time, buildup_time,
requires_no_aggro,
}) })
.build(); .build();
@ -4112,7 +4040,7 @@ fn handle_location(
if let Some(name) = parse_cmd_args!(args, String) { if let Some(name) = parse_cmd_args!(args, String) {
let loc = server.state.ecs().read_resource::<Locations>().get(&name); let loc = server.state.ecs().read_resource::<Locations>().get(&name);
match loc { 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; target_pos.0 = loc;
}), }),
Err(e) => Err(e.to_string()), Err(e) => Err(e.to_string()),

View File

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

View File

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

View File

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

View File

@ -18,13 +18,14 @@ use common::{
comp::{ comp::{
self, self,
item::{ItemKind, MaterialStatManifest}, item::{ItemKind, MaterialStatManifest},
misc::PortalData,
object, object,
skills::{GeneralSkill, Skill}, skills::{GeneralSkill, Skill},
ChatType, Group, Inventory, Item, LootOwner, Object, Player, Poise, Presence, PresenceKind, ChatType, Group, Inventory, Item, LootOwner, Object, Player, Poise, Presence, PresenceKind,
}, },
effect::Effect, effect::Effect,
link::{Link, LinkHandle}, link::{Is, Link, LinkHandle},
mounting::{Mounting, VolumeMounting}, mounting::{Mounting, Rider, VolumeMounting, VolumeRider},
resources::{Secs, Time, TimeOfDay}, resources::{Secs, Time, TimeOfDay},
rtsim::{Actor, RtSimEntity}, rtsim::{Actor, RtSimEntity},
slowjob::SlowJobPool, slowjob::SlowJobPool,
@ -118,11 +119,7 @@ pub trait StateExt {
/// Creates a teleporter entity, which allows players to teleport to the /// Creates a teleporter entity, which allows players to teleport to the
/// `target` position. You might want to require the teleporting entity /// `target` position. You might want to require the teleporting entity
/// to not have agro for teleporting. /// to not have agro for teleporting.
fn create_teleporter( fn create_teleporter(&mut self, pos: comp::Pos, portal: PortalData) -> EcsEntityBuilder;
&mut self,
pos: comp::Pos,
teleporter: comp::Teleporter,
) -> EcsEntityBuilder;
/// Insert common/default components for a new character joining the server /// Insert common/default components for a new character joining the server
fn initialize_character_data( fn initialize_character_data(
&mut self, &mut self,
@ -161,6 +158,12 @@ pub trait StateExt {
) -> Result<(), specs::error::WrongGeneration>; ) -> Result<(), specs::error::WrongGeneration>;
/// Get the given entity as an [`Actor`], if it is one. /// Get the given entity as an [`Actor`], if it is one.
fn entity_as_actor(&self, entity: EcsEntity) -> Option<Actor>; 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 { impl StateExt for State {
@ -616,14 +619,10 @@ impl StateExt for State {
)) ))
} }
fn create_teleporter( fn create_teleporter(&mut self, pos: comp::Pos, portal: PortalData) -> EcsEntityBuilder {
&mut self,
pos: comp::Pos,
teleporter: comp::Teleporter,
) -> EcsEntityBuilder {
self.create_object(pos, object::Body::Portal) self.create_object(pos, object::Body::Portal)
.with(comp::Immovable) .with(comp::Immovable)
.with(teleporter) .with(comp::Object::from(portal))
} }
fn initialize_character_data( fn initialize_character_data(
@ -1244,6 +1243,76 @@ impl StateExt for State {
None 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) { fn send_to_group(g: &Group, ecs: &specs::World, msg: &comp::ChatMsg) {

View File

@ -1,9 +1,10 @@
use common::{ use common::{
comp::{Object, PhysicsState, Pos, Vel}, comp::{object, Body, Object, PhysicsState, Pos, Teleporting, Vel},
consts::TELEPORTER_RADIUS,
effect::Effect, effect::Effect,
event::{EventBus, ServerEvent}, event::{EventBus, ServerEvent},
resources::{DeltaTime, Time}, resources::{DeltaTime, Time},
Damage, DamageKind, DamageSource, Explosion, RadiusEffect, CachedSpatialGrid, Damage, DamageKind, DamageSource, Explosion, RadiusEffect,
}; };
use common_ecs::{Job, Origin, Phase, System}; use common_ecs::{Job, Origin, Phase, System};
use specs::{Entities, Join, Read, ReadStorage}; use specs::{Entities, Join, Read, ReadStorage};
@ -18,10 +19,13 @@ impl<'a> System<'a> for Sys {
Read<'a, DeltaTime>, Read<'a, DeltaTime>,
Read<'a, Time>, Read<'a, Time>,
Read<'a, EventBus<ServerEvent>>, Read<'a, EventBus<ServerEvent>>,
Read<'a, CachedSpatialGrid>,
ReadStorage<'a, Pos>, ReadStorage<'a, Pos>,
ReadStorage<'a, Vel>, ReadStorage<'a, Vel>,
ReadStorage<'a, PhysicsState>, ReadStorage<'a, PhysicsState>,
ReadStorage<'a, Object>, ReadStorage<'a, Object>,
ReadStorage<'a, Body>,
ReadStorage<'a, Teleporting>,
); );
const NAME: &'static str = "object"; const NAME: &'static str = "object";
@ -30,17 +34,30 @@ impl<'a> System<'a> for Sys {
fn run( fn run(
_job: &mut Job<Self>, _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(); let mut server_emitter = server_bus.emitter();
// Objects // Objects
for (entity, pos, vel, physics, object) in ( for (entity, pos, vel, physics, object, body) in (
&entities, &entities,
&positions, &positions,
&velocities, &velocities,
&physics_states, &physics_states,
&objects, &objects,
&bodies,
) )
.join() .join()
{ {
@ -73,7 +90,7 @@ impl<'a> System<'a> for Sys {
const ENABLE_RECURSIVE_FIREWORKS: bool = true; const ENABLE_RECURSIVE_FIREWORKS: bool = true;
if ENABLE_RECURSIVE_FIREWORKS { if ENABLE_RECURSIVE_FIREWORKS {
use common::{ use common::{
comp::{object, Body, LightEmitter, Projectile}, comp::{LightEmitter, Projectile},
util::Dir, util::Dir,
}; };
use rand::Rng; use rand::Rng;
@ -169,6 +186,31 @@ impl<'a> System<'a> for Sys {
server_emitter.emit(ServerEvent::Delete(entity)); 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::{ use common::{
comp::{object, Agent, Alignment, Body, CharacterState, Pos, Teleporter, Teleporting}, comp::{Agent, Alignment, CharacterState, Object, Pos, Teleporting},
consts::TELEPORTER_RADIUS,
event::{EventBus, ServerEvent}, event::{EventBus, ServerEvent},
resources::Time, resources::Time,
uid::Uid, uid::Uid,
@ -9,7 +10,6 @@ use common_ecs::{Origin, Phase, System};
use specs::{Entities, Join, Read, ReadStorage, WriteStorage}; use specs::{Entities, Join, Read, ReadStorage, WriteStorage};
use vek::Vec3; 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 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.; const PET_TELEPORT_RADIUS: f32 = 20.;
@ -17,7 +17,7 @@ const PET_TELEPORT_RADIUS: f32 = 20.;
pub struct Sys; pub struct Sys;
fn in_portal_range(player_pos: Vec3<f32>, portal_pos: Vec3<f32>) -> bool { 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 { impl<'a> System<'a> for Sys {
@ -25,11 +25,10 @@ impl<'a> System<'a> for Sys {
Entities<'a>, Entities<'a>,
ReadStorage<'a, Pos>, ReadStorage<'a, Pos>,
ReadStorage<'a, Uid>, ReadStorage<'a, Uid>,
ReadStorage<'a, Teleporter>,
ReadStorage<'a, Alignment>, ReadStorage<'a, Alignment>,
ReadStorage<'a, Agent>, ReadStorage<'a, Agent>,
ReadStorage<'a, Object>,
WriteStorage<'a, Teleporting>, WriteStorage<'a, Teleporting>,
ReadStorage<'a, Body>,
ReadStorage<'a, CharacterState>, ReadStorage<'a, CharacterState>,
Read<'a, CachedSpatialGrid>, Read<'a, CachedSpatialGrid>,
Read<'a, Time>, Read<'a, Time>,
@ -46,11 +45,10 @@ impl<'a> System<'a> for Sys {
entities, entities,
positions, positions,
uids, uids,
teleporters,
alignments, alignments,
agent, agent,
objects,
mut teleporting, mut teleporting,
bodies,
character_states, character_states,
spatial_grid, spatial_grid,
time, time,
@ -72,31 +70,6 @@ impl<'a> System<'a> for Sys {
let mut cancel_teleporting = Vec::new(); 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 ( for (entity, uid, position, teleporting, character_state) in (
&entities, &entities,
&uids, &uids,
@ -107,14 +80,16 @@ impl<'a> System<'a> for Sys {
.join() .join()
{ {
let portal_pos = positions.get(teleporting.portal); 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); cancel_teleporting.push(entity);
continue continue
}; };
if portal_pos.map_or(true, |portal_pos| { if portal_pos.map_or(true, |portal_pos| {
!in_portal_range(position.0, portal_pos.0) !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!( || !matches!(
character_state, character_state,
CharacterState::Idle(_) | CharacterState::Wielding(_) CharacterState::Idle(_) | CharacterState::Wielding(_)
@ -142,7 +117,7 @@ impl<'a> System<'a> for Sys {
cancel_teleporting.push(entity); cancel_teleporting.push(entity);
event_bus.emit_now(ServerEvent::TeleportToPosition { event_bus.emit_now(ServerEvent::TeleportToPosition {
entity, entity,
position: teleporter.target, position: *target,
}); });
} }
} }

View File

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

View File

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

View File

@ -10,7 +10,7 @@ use client::Client;
use common::{ use common::{
comp, comp,
comp::{ship::figuredata::VOXEL_COLLIDER_MANIFEST, tool::ToolKind, Collider, Content}, 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, link::Is,
mounting::{Mount, Rider, VolumePos, VolumeRider}, mounting::{Mount, Rider, VolumePos, VolumeRider},
terrain::{Block, TerrainGrid, UnlockKind}, terrain::{Block, TerrainGrid, UnlockKind},
@ -247,7 +247,7 @@ pub(super) fn select_interactable(
// * Are not riding the player // * Are not riding the player
let not_riding_player = is_rider let not_riding_player = is_rider
.map_or(true, |is_rider| Some(&is_rider.mount) != uids.get(viewpoint_entity)); .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 { if !is_interactable {
return None; return None;
}; };

View File

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

View File

@ -9,7 +9,7 @@ use crate::{
use common::{ use common::{
assets::{self, AssetExt, AssetHandle}, assets::{self, AssetExt, AssetHandle},
astar::Astar, astar::Astar,
comp::Teleporter, comp::misc::PortalData,
generation::{ChunkSupplement, EntityInfo, SpecialEntity}, generation::{ChunkSupplement, EntityInfo, SpecialEntity},
resources::Secs, resources::Secs,
store::{Id, Store}, 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 // 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( supplement.add_entity(EntityInfo::at(top_pos).into_special(
SpecialEntity::Teleporter(Teleporter { SpecialEntity::Teleporter(PortalData {
target: bottom_pos + Vec3::unit_x() * 5., target: bottom_pos + Vec3::unit_x() * 5.,
requires_no_aggro: true, requires_no_aggro: true,
buildup_time: Secs(5.), buildup_time: Secs(5.),
}), }),
)); ));
supplement.add_entity(EntityInfo::at(bottom_pos).into_special( supplement.add_entity(EntityInfo::at(bottom_pos).into_special(
SpecialEntity::Teleporter(Teleporter { SpecialEntity::Teleporter(PortalData {
target: top_pos + Vec3::unit_x() * 5., target: top_pos + Vec3::unit_x() * 5.,
requires_no_aggro: true, requires_no_aggro: true,
buildup_time: Secs(5.), buildup_time: Secs(5.),