mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'maxicarlos08/dungeon-avoid-cave' into 'master'
Cheesing fixes to old-style dungeons See merge request veloren/veloren!4014
This commit is contained in:
commit
6fbca74e88
@ -22,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Recipe for a new leather pack
|
- Recipe for a new leather pack
|
||||||
- Keybinds for zooming the camera (Defaults: ']' for zooming in and '[' for zooming out)
|
- Keybinds for zooming the camera (Defaults: ']' for zooming in and '[' for zooming out)
|
||||||
- Added the ability to make pets sit, they wont follow nor defend you in this state
|
- Added the ability to make pets sit, they wont follow nor defend you in this state
|
||||||
|
- Portals that spawn in place of the last staircase at old style dungeons to prevent stair cheesing
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
@ -53,6 +54,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- Fixed wild roaming cyclop loot table to not drop the quarry key
|
- Fixed wild roaming cyclop loot table to not drop the quarry key
|
||||||
|
- Dungeons now have an outer wall, preventing them from intersecting with caves or leaving holes in sides of mountains.
|
||||||
|
|
||||||
## [0.15.0] - 2023-07-01
|
## [0.15.0] - 2023-07-01
|
||||||
|
|
||||||
|
@ -1498,6 +1498,20 @@
|
|||||||
threshold: 0.8,
|
threshold: 0.8,
|
||||||
subtitle: "subtitle-attack-shovel",
|
subtitle: "subtitle-attack-shovel",
|
||||||
),
|
),
|
||||||
|
PortalActivated: (
|
||||||
|
files: [
|
||||||
|
"voxygen.audio.sfx.ambient.portal_2",
|
||||||
|
],
|
||||||
|
threshold: 0.8,
|
||||||
|
subtitle: "subtitle-portal-activated",
|
||||||
|
),
|
||||||
|
TeleportedByPortal: (
|
||||||
|
files: [
|
||||||
|
"voxygen.audio.sfx.ambient.portal_1",
|
||||||
|
],
|
||||||
|
threshold: 0.8,
|
||||||
|
subtitle: "subtitle-portal-teleported",
|
||||||
|
),
|
||||||
|
|
||||||
// Utterances (NPCs)
|
// Utterances (NPCs)
|
||||||
|
|
||||||
|
BIN
assets/voxygen/audio/sfx/ambient/portal_1.ogg
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/audio/sfx/ambient/portal_1.ogg
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/audio/sfx/ambient/portal_2.ogg
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/audio/sfx/ambient/portal_2.ogg
(Stored with Git LFS)
Normal file
Binary file not shown.
@ -39,6 +39,7 @@ hud-auto_walk_indicator = Auto walk/swim active
|
|||||||
hud-zoom_lock_indicator-remind = Zoom locked
|
hud-zoom_lock_indicator-remind = Zoom locked
|
||||||
hud-zoom_lock_indicator-enable = Camera zoom locked
|
hud-zoom_lock_indicator-enable = Camera zoom locked
|
||||||
hud-zoom_lock_indicator-disable = Camera zoom unlocked
|
hud-zoom_lock_indicator-disable = Camera zoom unlocked
|
||||||
|
hud-activate = Activate
|
||||||
hud-collect = Collect
|
hud-collect = Collect
|
||||||
hud-pick_up = Pick up
|
hud-pick_up = Pick up
|
||||||
hud-open = Open
|
hud-open = Open
|
||||||
@ -58,3 +59,4 @@ hud-follow = Follow
|
|||||||
hud-stay= Stay
|
hud-stay= Stay
|
||||||
hud-sit = Sit
|
hud-sit = Sit
|
||||||
hud-steer = Steer
|
hud-steer = Steer
|
||||||
|
hud-portal = Portal
|
||||||
|
@ -4,6 +4,8 @@ subtitle-bees = Bees buzzing
|
|||||||
subtitle-owl = Owl hooting
|
subtitle-owl = Owl hooting
|
||||||
subtitle-running_water = Water bubbling
|
subtitle-running_water = Water bubbling
|
||||||
subtitle-lightning = Thunder
|
subtitle-lightning = Thunder
|
||||||
|
subtitle-portal-activated = Portal Activated
|
||||||
|
subtitle-portal-teleported = Teleported via portal
|
||||||
|
|
||||||
subtitle-footsteps_grass = Walking on grass
|
subtitle-footsteps_grass = Walking on grass
|
||||||
subtitle-footsteps_earth = Walking on dirt
|
subtitle-footsteps_earth = Walking on dirt
|
||||||
|
@ -83,6 +83,7 @@ const int GIGA_SNOW = 42;
|
|||||||
const int CYCLOPS_CHARGE = 43;
|
const int CYCLOPS_CHARGE = 43;
|
||||||
const int PORTAL_FIZZ = 45;
|
const int PORTAL_FIZZ = 45;
|
||||||
const int INK = 46;
|
const int INK = 46;
|
||||||
|
const int UPWARD_PORTAL_FIZZ = 47;
|
||||||
|
|
||||||
// meters per second squared (acceleration)
|
// meters per second squared (acceleration)
|
||||||
const float earth_gravity = 9.807;
|
const float earth_gravity = 9.807;
|
||||||
@ -703,6 +704,15 @@ void main() {
|
|||||||
spin_in_axis(vec3(rand6, rand7, rand8), percent() * 10 + 3 * rand9)
|
spin_in_axis(vec3(rand6, rand7, rand8), percent() * 10 + 3 * rand9)
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
case UPWARD_PORTAL_FIZZ:
|
||||||
|
f_reflect = 0.0;
|
||||||
|
attr = Attr(
|
||||||
|
inst_dir * percent(),
|
||||||
|
vec3(max(1.0, 0.05 * length(start_pos + inst_dir * percent()))),
|
||||||
|
vec4(vec3(1.23, 1.41, 1.44), 1.0),// * (1.0 - length(inst_dir) * 0.1),
|
||||||
|
identity()//spin_in_axis(perp_axis, asin(inst_dir.z / length(inst_dir)) + PI / 2.0)
|
||||||
|
);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
attr = Attr(
|
attr = Attr(
|
||||||
linear_motion(
|
linear_motion(
|
||||||
|
BIN
assets/voxygen/voxel/object/portal.vox
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/voxel/object/portal.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/voxel/object/portal_active.vox
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/voxel/object/portal_active.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
@ -979,4 +979,24 @@
|
|||||||
central: ("armor.empty"),
|
central: ("armor.empty"),
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
Portal: (
|
||||||
|
bone0: (
|
||||||
|
offset: (-33.0, -33.0, 0.0),
|
||||||
|
central: ("object.portal"),
|
||||||
|
),
|
||||||
|
bone1: (
|
||||||
|
offset: (0.0, 0.0, 0.0),
|
||||||
|
central: ("armor.empty"),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
PortalActive: (
|
||||||
|
bone0: (
|
||||||
|
offset: (-33.0, -33.0, 0.0),
|
||||||
|
central: ("object.portal_active"),
|
||||||
|
),
|
||||||
|
bone1: (
|
||||||
|
offset: (0.0, 0.0, 0.0),
|
||||||
|
central: ("armor.empty"),
|
||||||
|
)
|
||||||
|
),
|
||||||
})
|
})
|
||||||
|
BIN
assets/world/structure/dungeon/temperate_entrance/ruins_4.vox
(Stored with Git LFS)
BIN
assets/world/structure/dungeon/temperate_entrance/ruins_4.vox
(Stored with Git LFS)
Binary file not shown.
@ -1625,6 +1625,12 @@ impl Client {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn activate_portal(&mut self, portal: Uid) {
|
||||||
|
self.send_msg(ClientGeneral::ControlEvent(ControlEvent::ActivatePortal(
|
||||||
|
portal,
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
fn control_action(&mut self, control_action: ControlAction) {
|
fn control_action(&mut self, control_action: ControlAction) {
|
||||||
if let Some(controller) = self
|
if let Some(controller) = self
|
||||||
.state
|
.state
|
||||||
|
@ -310,6 +310,7 @@ pub enum ServerChatCommand {
|
|||||||
Object,
|
Object,
|
||||||
PermitBuild,
|
PermitBuild,
|
||||||
Players,
|
Players,
|
||||||
|
Portal,
|
||||||
Region,
|
Region,
|
||||||
ReloadChunks,
|
ReloadChunks,
|
||||||
RemoveLights,
|
RemoveLights,
|
||||||
@ -618,6 +619,17 @@ impl ServerChatCommand {
|
|||||||
Some(Admin),
|
Some(Admin),
|
||||||
),
|
),
|
||||||
ServerChatCommand::Players => cmd(vec![], "Lists players currently online", None),
|
ServerChatCommand::Players => cmd(vec![], "Lists players currently online", None),
|
||||||
|
ServerChatCommand::Portal => cmd(
|
||||||
|
vec![
|
||||||
|
Float("x", 0., Required),
|
||||||
|
Float("y", 0., Required),
|
||||||
|
Float("z", 0., Required),
|
||||||
|
Boolean("requires_no_aggro", "true".to_string(), Optional),
|
||||||
|
Float("buildup_time", 5., Optional),
|
||||||
|
],
|
||||||
|
"Spawns a portal",
|
||||||
|
Some(Admin),
|
||||||
|
),
|
||||||
ServerChatCommand::ReloadChunks => cmd(
|
ServerChatCommand::ReloadChunks => cmd(
|
||||||
vec![],
|
vec![],
|
||||||
"Reloads all chunks loaded on the server",
|
"Reloads all chunks loaded on the server",
|
||||||
@ -890,6 +902,7 @@ impl ServerChatCommand {
|
|||||||
ServerChatCommand::Object => "object",
|
ServerChatCommand::Object => "object",
|
||||||
ServerChatCommand::PermitBuild => "permit_build",
|
ServerChatCommand::PermitBuild => "permit_build",
|
||||||
ServerChatCommand::Players => "players",
|
ServerChatCommand::Players => "players",
|
||||||
|
ServerChatCommand::Portal => "portal",
|
||||||
ServerChatCommand::Region => "region",
|
ServerChatCommand::Region => "region",
|
||||||
ServerChatCommand::ReloadChunks => "reload_chunks",
|
ServerChatCommand::ReloadChunks => "reload_chunks",
|
||||||
ServerChatCommand::RemoveLights => "remove_lights",
|
ServerChatCommand::RemoveLights => "remove_lights",
|
||||||
|
@ -261,6 +261,13 @@ impl Body {
|
|||||||
|
|
||||||
pub fn is_campfire(&self) -> bool { matches!(self, Body::Object(object::Body::CampfireLit)) }
|
pub fn is_campfire(&self) -> bool { matches!(self, Body::Object(object::Body::CampfireLit)) }
|
||||||
|
|
||||||
|
pub fn is_portal(&self) -> bool {
|
||||||
|
matches!(
|
||||||
|
self,
|
||||||
|
Body::Object(object::Body::Portal | object::Body::PortalActive)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn bleeds(&self) -> bool {
|
pub fn bleeds(&self) -> bool {
|
||||||
!matches!(
|
!matches!(
|
||||||
self,
|
self,
|
||||||
|
@ -110,6 +110,8 @@ make_case_elim!(
|
|||||||
Mine = 95,
|
Mine = 95,
|
||||||
LightningBolt = 96,
|
LightningBolt = 96,
|
||||||
SpearIcicle = 97,
|
SpearIcicle = 97,
|
||||||
|
Portal = 98,
|
||||||
|
PortalActive = 99,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -120,7 +122,7 @@ impl Body {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const ALL_OBJECTS: [Body; 98] = [
|
pub const ALL_OBJECTS: [Body; 100] = [
|
||||||
Body::Arrow,
|
Body::Arrow,
|
||||||
Body::Bomb,
|
Body::Bomb,
|
||||||
Body::Scarecrow,
|
Body::Scarecrow,
|
||||||
@ -219,6 +221,8 @@ pub const ALL_OBJECTS: [Body; 98] = [
|
|||||||
Body::Mine,
|
Body::Mine,
|
||||||
Body::LightningBolt,
|
Body::LightningBolt,
|
||||||
Body::SpearIcicle,
|
Body::SpearIcicle,
|
||||||
|
Body::Portal,
|
||||||
|
Body::PortalActive,
|
||||||
];
|
];
|
||||||
|
|
||||||
impl From<Body> for super::Body {
|
impl From<Body> for super::Body {
|
||||||
@ -326,6 +330,8 @@ impl Body {
|
|||||||
Body::Mine => "mine",
|
Body::Mine => "mine",
|
||||||
Body::LightningBolt => "lightning_bolt",
|
Body::LightningBolt => "lightning_bolt",
|
||||||
Body::SpearIcicle => "spear_icicle",
|
Body::SpearIcicle => "spear_icicle",
|
||||||
|
Body::Portal => "portal",
|
||||||
|
Body::PortalActive => "portal_active",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -450,6 +456,7 @@ impl Body {
|
|||||||
Body::AdletTrap => 10.0,
|
Body::AdletTrap => 10.0,
|
||||||
Body::Mine => 100.0,
|
Body::Mine => 100.0,
|
||||||
Body::LightningBolt | Body::SpearIcicle => 20000.0,
|
Body::LightningBolt | Body::SpearIcicle => 20000.0,
|
||||||
|
Body::Portal | Body::PortalActive => 10., // I dont know really
|
||||||
};
|
};
|
||||||
|
|
||||||
Mass(m)
|
Mass(m)
|
||||||
|
@ -158,6 +158,7 @@ pub enum ControlEvent {
|
|||||||
auxiliary_key: ability::AuxiliaryKey,
|
auxiliary_key: ability::AuxiliaryKey,
|
||||||
new_ability: ability::AuxiliaryAbility,
|
new_ability: ability::AuxiliaryAbility,
|
||||||
},
|
},
|
||||||
|
ActivatePortal(Uid),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
|
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
|
||||||
|
@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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;
|
||||||
@ -34,6 +34,7 @@ pub mod projectile;
|
|||||||
pub mod shockwave;
|
pub mod shockwave;
|
||||||
pub mod skillset;
|
pub mod skillset;
|
||||||
mod stats;
|
mod stats;
|
||||||
|
pub mod teleport;
|
||||||
pub mod visual;
|
pub mod visual;
|
||||||
|
|
||||||
// Reexports
|
// Reexports
|
||||||
@ -103,6 +104,7 @@ pub use self::{
|
|||||||
SkillGroup, SkillGroupKind, SkillSet,
|
SkillGroup, SkillGroupKind, SkillSet,
|
||||||
},
|
},
|
||||||
stats::{Stats, StatsModifier},
|
stats::{Stats, StatsModifier},
|
||||||
|
teleport::Teleporting,
|
||||||
visual::{LightAnimation, LightEmitter},
|
visual::{LightAnimation, LightEmitter},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
13
common/src/comp/teleport.rs
Normal file
13
common/src/comp/teleport.rs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
use specs::{Component, DerefFlaggedStorage, Entity};
|
||||||
|
|
||||||
|
use crate::resources::Time;
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||||
|
pub struct Teleporting {
|
||||||
|
pub portal: Entity,
|
||||||
|
pub end_time: Time,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for Teleporting {
|
||||||
|
type Storage = DerefFlaggedStorage<Self, specs::VecStorage<Self>>;
|
||||||
|
}
|
@ -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.;
|
||||||
|
@ -6,6 +6,7 @@ use crate::{
|
|||||||
agent::Sound,
|
agent::Sound,
|
||||||
dialogue::Subject,
|
dialogue::Subject,
|
||||||
invite::{InviteKind, InviteResponse},
|
invite::{InviteKind, InviteResponse},
|
||||||
|
misc::PortalData,
|
||||||
DisconnectReason, Ori, Pos,
|
DisconnectReason, Ori, Pos,
|
||||||
},
|
},
|
||||||
lottery::LootSpec,
|
lottery::LootSpec,
|
||||||
@ -239,6 +240,7 @@ pub enum ServerEvent {
|
|||||||
driver: Option<NpcBuilder>,
|
driver: Option<NpcBuilder>,
|
||||||
},
|
},
|
||||||
CreateWaypoint(Vec3<f32>),
|
CreateWaypoint(Vec3<f32>),
|
||||||
|
CreateTeleporter(Vec3<f32>, PortalData),
|
||||||
ClientDisconnect(EcsEntity, DisconnectReason),
|
ClientDisconnect(EcsEntity, DisconnectReason),
|
||||||
ClientDisconnectWithoutPersistence(EcsEntity),
|
ClientDisconnectWithoutPersistence(EcsEntity),
|
||||||
Command(EcsEntity, String, Vec<String>),
|
Command(EcsEntity, String, Vec<String>),
|
||||||
@ -331,6 +333,14 @@ pub enum ServerEvent {
|
|||||||
RemoveLightEmitter {
|
RemoveLightEmitter {
|
||||||
entity: EcsEntity,
|
entity: EcsEntity,
|
||||||
},
|
},
|
||||||
|
TeleportToPosition {
|
||||||
|
entity: EcsEntity,
|
||||||
|
position: Vec3<f32>,
|
||||||
|
},
|
||||||
|
StartTeleporting {
|
||||||
|
entity: EcsEntity,
|
||||||
|
portal: EcsEntity,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct EventBus<E> {
|
pub struct EventBus<E> {
|
||||||
|
@ -3,6 +3,7 @@ use crate::{
|
|||||||
comp::{
|
comp::{
|
||||||
self, agent, humanoid,
|
self, agent, humanoid,
|
||||||
inventory::loadout_builder::{LoadoutBuilder, LoadoutSpec},
|
inventory::loadout_builder::{LoadoutBuilder, LoadoutSpec},
|
||||||
|
misc::PortalData,
|
||||||
Alignment, Body, Item,
|
Alignment, Body, Item,
|
||||||
},
|
},
|
||||||
lottery::LootSpec,
|
lottery::LootSpec,
|
||||||
@ -163,10 +164,15 @@ pub fn try_all_entity_configs() -> Result<Vec<String>, Error> {
|
|||||||
Ok(configs.ids().map(|id| id.to_string()).collect())
|
Ok(configs.ids().map(|id| id.to_string()).collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum SpecialEntity {
|
||||||
|
Waypoint,
|
||||||
|
Teleporter(PortalData),
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct EntityInfo {
|
pub struct EntityInfo {
|
||||||
pub pos: Vec3<f32>,
|
pub pos: Vec3<f32>,
|
||||||
pub is_waypoint: bool, // Edge case, overrides everything else
|
|
||||||
pub alignment: Alignment,
|
pub alignment: Alignment,
|
||||||
/// Parameterises agent behaviour
|
/// Parameterises agent behaviour
|
||||||
pub has_agency: bool,
|
pub has_agency: bool,
|
||||||
@ -194,13 +200,15 @@ pub struct EntityInfo {
|
|||||||
// we can't use DHashMap, do we want to move that into common?
|
// we can't use DHashMap, do we want to move that into common?
|
||||||
pub trading_information: Option<SiteInformation>,
|
pub trading_information: Option<SiteInformation>,
|
||||||
//Option<hashbrown::HashMap<crate::trade::Good, (f32, f32)>>, /* price and available amount */
|
//Option<hashbrown::HashMap<crate::trade::Good, (f32, f32)>>, /* price and available amount */
|
||||||
|
|
||||||
|
// Edge cases, override everything else
|
||||||
|
pub special_entity: Option<SpecialEntity>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EntityInfo {
|
impl EntityInfo {
|
||||||
pub fn at(pos: Vec3<f32>) -> Self {
|
pub fn at(pos: Vec3<f32>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
pos,
|
pos,
|
||||||
is_waypoint: false,
|
|
||||||
alignment: Alignment::Wild,
|
alignment: Alignment::Wild,
|
||||||
|
|
||||||
has_agency: true,
|
has_agency: true,
|
||||||
@ -219,6 +227,7 @@ impl EntityInfo {
|
|||||||
skillset_asset: None,
|
skillset_asset: None,
|
||||||
pet: None,
|
pet: None,
|
||||||
trading_information: None,
|
trading_information: None,
|
||||||
|
special_entity: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -377,8 +386,8 @@ impl EntityInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn into_waypoint(mut self) -> Self {
|
pub fn into_special(mut self, special: SpecialEntity) -> Self {
|
||||||
self.is_waypoint = true;
|
self.special_entity = Some(special);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -140,6 +140,12 @@ pub enum Outcome {
|
|||||||
GroundDig {
|
GroundDig {
|
||||||
pos: Vec3<f32>,
|
pos: Vec3<f32>,
|
||||||
},
|
},
|
||||||
|
PortalActivated {
|
||||||
|
pos: Vec3<f32>,
|
||||||
|
},
|
||||||
|
TeleportedByPortal {
|
||||||
|
pos: Vec3<f32>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Outcome {
|
impl Outcome {
|
||||||
@ -170,6 +176,8 @@ impl Outcome {
|
|||||||
| Outcome::FlamethrowerCharge { pos }
|
| Outcome::FlamethrowerCharge { pos }
|
||||||
| Outcome::LaserBeam { pos }
|
| Outcome::LaserBeam { pos }
|
||||||
| Outcome::GroundDig { pos }
|
| Outcome::GroundDig { pos }
|
||||||
|
| Outcome::PortalActivated { pos }
|
||||||
|
| Outcome::TeleportedByPortal { pos}
|
||||||
| Outcome::Glider { pos, .. } => Some(*pos),
|
| Outcome::Glider { pos, .. } => Some(*pos),
|
||||||
Outcome::BreakBlock { pos, .. }
|
Outcome::BreakBlock { pos, .. }
|
||||||
| Outcome::SpriteUnlocked { pos }
|
| Outcome::SpriteUnlocked { pos }
|
||||||
|
@ -218,6 +218,7 @@ 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::Teleporting>();
|
||||||
|
|
||||||
// Register components send from clients -> server
|
// Register components send from clients -> server
|
||||||
ecs.register::<comp::Controller>();
|
ecs.register::<comp::Controller>();
|
||||||
|
@ -138,6 +138,11 @@ impl<'a> System<'a> for Sys {
|
|||||||
stance: Stance::None,
|
stance: Stance::None,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
ControlEvent::ActivatePortal(portal_uid) => {
|
||||||
|
if let Some(portal) = read_data.id_maps.uid_entity(portal_uid) {
|
||||||
|
server_emitter.emit(ServerEvent::StartTeleporting { entity, portal });
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
};
|
};
|
||||||
@ -167,6 +166,7 @@ fn do_command(
|
|||||||
ServerChatCommand::Object => handle_object,
|
ServerChatCommand::Object => handle_object,
|
||||||
ServerChatCommand::PermitBuild => handle_permit_build,
|
ServerChatCommand::PermitBuild => handle_permit_build,
|
||||||
ServerChatCommand::Players => handle_players,
|
ServerChatCommand::Players => handle_players,
|
||||||
|
ServerChatCommand::Portal => handle_spawn_portal,
|
||||||
ServerChatCommand::Region => handle_region,
|
ServerChatCommand::Region => handle_region,
|
||||||
ServerChatCommand::ReloadChunks => handle_reload_chunks,
|
ServerChatCommand::ReloadChunks => handle_reload_chunks,
|
||||||
ServerChatCommand::RemoveLights => handle_remove_lights,
|
ServerChatCommand::RemoveLights => handle_remove_lights,
|
||||||
@ -222,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,
|
||||||
@ -694,6 +611,9 @@ fn handle_make_npc(
|
|||||||
NpcData::Waypoint(_) => {
|
NpcData::Waypoint(_) => {
|
||||||
return Err("Waypoint spawning is not implemented".to_owned());
|
return Err("Waypoint spawning is not implemented".to_owned());
|
||||||
},
|
},
|
||||||
|
NpcData::Teleporter(_, _) => {
|
||||||
|
return Err("Teleporter spawning is not implemented".to_owned());
|
||||||
|
},
|
||||||
NpcData::Data {
|
NpcData::Data {
|
||||||
inventory,
|
inventory,
|
||||||
pos,
|
pos,
|
||||||
@ -855,9 +775,12 @@ 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
|
||||||
current_pos.0 += Vec3::new(x, y, z)
|
.state
|
||||||
})
|
.position_mut(target, dismount_volume.unwrap_or(true), |current_pos| {
|
||||||
|
current_pos.0 += Vec3::new(x, y, z)
|
||||||
|
})
|
||||||
|
.map_err(ToString::to_string)
|
||||||
} else {
|
} else {
|
||||||
Err(action.help_string())
|
Err(action.help_string())
|
||||||
}
|
}
|
||||||
@ -872,9 +795,12 @@ 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
|
||||||
current_pos.0 = Vec3::new(x, y, z)
|
.state
|
||||||
})
|
.position_mut(target, dismount_volume.unwrap_or(true), |current_pos| {
|
||||||
|
current_pos.0 = Vec3::new(x, y, z)
|
||||||
|
})
|
||||||
|
.map_err(ToString::to_string)
|
||||||
} else {
|
} else {
|
||||||
Err(action.help_string())
|
Err(action.help_string())
|
||||||
}
|
}
|
||||||
@ -907,9 +833,12 @@ fn handle_site(
|
|||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
|
|
||||||
position_mut(server, target, "target", dismount_volume, |current_pos| {
|
server
|
||||||
current_pos.0 = site_pos
|
.state
|
||||||
})
|
.position_mut(target, dismount_volume.unwrap_or(true), |current_pos| {
|
||||||
|
current_pos.0 = site_pos
|
||||||
|
})
|
||||||
|
.map_err(ToString::to_string)
|
||||||
} else {
|
} else {
|
||||||
Err(action.help_string())
|
Err(action.help_string())
|
||||||
}
|
}
|
||||||
@ -932,9 +861,12 @@ 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
|
||||||
current_pos.0 = waypoint;
|
.state
|
||||||
})
|
.position_mut(target, true, |current_pos| {
|
||||||
|
current_pos.0 = waypoint;
|
||||||
|
})
|
||||||
|
.map_err(ToString::to_string)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_kill(
|
fn handle_kill(
|
||||||
@ -1304,9 +1236,12 @@ 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
|
||||||
*target_pos = player_pos
|
.state
|
||||||
})
|
.position_mut(target, dismount_volume.unwrap_or(true), |target_pos| {
|
||||||
|
*target_pos = player_pos
|
||||||
|
})
|
||||||
|
.map_err(ToString::to_string)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_rtsim_tp(
|
fn handle_rtsim_tp(
|
||||||
@ -1334,9 +1269,12 @@ 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
|
||||||
target_pos.0 = pos;
|
.state
|
||||||
})
|
.position_mut(target, dismount_volume.unwrap_or(true), |target_pos| {
|
||||||
|
target_pos.0 = pos;
|
||||||
|
})
|
||||||
|
.map_err(ToString::to_string)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_rtsim_info(
|
fn handle_rtsim_info(
|
||||||
@ -2038,6 +1976,39 @@ fn handle_players(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_spawn_portal(
|
||||||
|
server: &mut Server,
|
||||||
|
client: EcsEntity,
|
||||||
|
target: EcsEntity,
|
||||||
|
args: Vec<String>,
|
||||||
|
_action: &ServerChatCommand,
|
||||||
|
) -> CmdResult<()> {
|
||||||
|
let pos = position(server, target, "target")?;
|
||||||
|
|
||||||
|
if let (Some(x), Some(y), Some(z), requires_no_aggro, buildup_time) =
|
||||||
|
parse_cmd_args!(args, f32, f32, f32, bool, f64)
|
||||||
|
{
|
||||||
|
let requires_no_aggro = requires_no_aggro.unwrap_or(false);
|
||||||
|
let buildup_time = Secs(buildup_time.unwrap_or(7.));
|
||||||
|
server
|
||||||
|
.state
|
||||||
|
.create_teleporter(pos, PortalData {
|
||||||
|
target: Vec3::new(x, y, z),
|
||||||
|
buildup_time,
|
||||||
|
requires_no_aggro,
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
|
||||||
|
server.notify_client(
|
||||||
|
client,
|
||||||
|
ServerGeneral::server_msg(ChatType::CommandInfo, "Spawned portal"),
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err("Invalid arguments".to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn handle_build(
|
fn handle_build(
|
||||||
server: &mut Server,
|
server: &mut Server,
|
||||||
client: EcsEntity,
|
client: EcsEntity,
|
||||||
@ -4075,9 +4046,12 @@ 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
|
||||||
target_pos.0 = loc;
|
.state
|
||||||
}),
|
.position_mut(target, true, |target_pos| {
|
||||||
|
target_pos.0 = loc;
|
||||||
|
})
|
||||||
|
.map_err(ToString::to_string),
|
||||||
Err(e) => Err(e.to_string()),
|
Err(e) => Err(e.to_string()),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -9,6 +9,7 @@ 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, TradingBehavior, Vel, WaypointArea,
|
Projectile, TradingBehavior, Vel, WaypointArea,
|
||||||
@ -418,3 +419,10 @@ pub fn handle_create_waypoint(server: &mut Server, pos: Vec3<f32>) {
|
|||||||
]))
|
]))
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn handle_create_teleporter(server: &mut Server, pos: Vec3<f32>, portal: PortalData) {
|
||||||
|
server
|
||||||
|
.state
|
||||||
|
.create_teleporter(comp::Pos(pos), portal)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
@ -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)]
|
||||||
@ -1656,3 +1657,47 @@ pub fn handle_remove_light_emitter(server: &mut Server, entity: EcsEntity) {
|
|||||||
.write_storage::<comp::LightEmitter>()
|
.write_storage::<comp::LightEmitter>()
|
||||||
.remove(entity);
|
.remove(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn handle_teleport_to_position(server: &mut Server, entity: EcsEntity, position: Vec3<f32>) {
|
||||||
|
if let Err(error) = server
|
||||||
|
.state
|
||||||
|
.position_mut(entity, true, |pos| pos.0 = position)
|
||||||
|
{
|
||||||
|
warn!("Failed to teleport entity: {error}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_start_teleporting(server: &mut Server, entity: EcsEntity, portal: EcsEntity) {
|
||||||
|
let ecs = server.state.ecs();
|
||||||
|
let positions = ecs.read_storage::<comp::Pos>();
|
||||||
|
let mut teleportings = ecs.write_storage::<comp::Teleporting>();
|
||||||
|
let now = ecs.read_resource::<Time>().0;
|
||||||
|
|
||||||
|
if let Some(end_time) = (!teleportings.contains(entity))
|
||||||
|
.then(|| positions.get(entity))
|
||||||
|
.flatten()
|
||||||
|
.zip(positions.get(portal))
|
||||||
|
.filter(|(entity_pos, portal_pos)| {
|
||||||
|
entity_pos.0.distance_squared(portal_pos.0) <= TELEPORTER_RADIUS.powi(2)
|
||||||
|
})
|
||||||
|
.and_then(|(_, _)| {
|
||||||
|
Some(
|
||||||
|
now + ecs
|
||||||
|
.read_storage::<comp::Object>()
|
||||||
|
.get(portal)
|
||||||
|
.and_then(|object| {
|
||||||
|
if let Object::Portal { buildup_time, .. } = object {
|
||||||
|
Some(buildup_time.0)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})?,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
{
|
||||||
|
let _ = teleportings.insert(entity, comp::Teleporting {
|
||||||
|
portal,
|
||||||
|
end_time: Time(end_time),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
events::interaction::{handle_mount_volume, handle_tame_pet},
|
events::{
|
||||||
|
entity_creation::handle_create_teleporter,
|
||||||
|
entity_manipulation::{handle_start_teleporting, handle_teleport_to_position},
|
||||||
|
interaction::{handle_mount_volume, handle_tame_pet},
|
||||||
|
},
|
||||||
persistence::PersistedComponents,
|
persistence::PersistedComponents,
|
||||||
state_ext::StateExt,
|
state_ext::StateExt,
|
||||||
Server,
|
Server,
|
||||||
@ -209,6 +213,9 @@ 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, portal) => {
|
||||||
|
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))
|
||||||
},
|
},
|
||||||
@ -293,6 +300,12 @@ impl Server {
|
|||||||
ServerEvent::RemoveLightEmitter { entity } => {
|
ServerEvent::RemoveLightEmitter { entity } => {
|
||||||
handle_remove_light_emitter(self, entity)
|
handle_remove_light_emitter(self, entity)
|
||||||
},
|
},
|
||||||
|
ServerEvent::TeleportToPosition { entity, position } => {
|
||||||
|
handle_teleport_to_position(self, entity, position)
|
||||||
|
},
|
||||||
|
ServerEvent::StartTeleporting { entity, portal } => {
|
||||||
|
handle_start_teleporting(self, entity, portal)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -338,6 +338,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
// as of now, and if someone will try to spawn
|
// as of now, and if someone will try to spawn
|
||||||
// rtsim waypoint it is definitely error.
|
// rtsim waypoint it is definitely error.
|
||||||
NpcData::Waypoint(_) => unimplemented!(),
|
NpcData::Waypoint(_) => unimplemented!(),
|
||||||
|
NpcData::Teleporter(_, _) => unimplemented!(),
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
error!("Npc is loaded but vehicle is unloaded");
|
error!("Npc is loaded but vehicle is unloaded");
|
||||||
@ -401,6 +402,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
// as of now, and if someone will try to spawn
|
// as of now, and if someone will try to spawn
|
||||||
// rtsim waypoint it is definitely error.
|
// rtsim waypoint it is definitely error.
|
||||||
NpcData::Waypoint(_) => unimplemented!(),
|
NpcData::Waypoint(_) => unimplemented!(),
|
||||||
|
NpcData::Teleporter(_, _) => unimplemented!(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,12 +18,14 @@ use common::{
|
|||||||
comp::{
|
comp::{
|
||||||
self,
|
self,
|
||||||
item::{ItemKind, MaterialStatManifest},
|
item::{ItemKind, MaterialStatManifest},
|
||||||
|
misc::PortalData,
|
||||||
|
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,
|
||||||
@ -114,6 +116,10 @@ pub trait StateExt {
|
|||||||
world: &std::sync::Arc<world::World>,
|
world: &std::sync::Arc<world::World>,
|
||||||
index: &world::IndexOwned,
|
index: &world::IndexOwned,
|
||||||
) -> EcsEntityBuilder;
|
) -> EcsEntityBuilder;
|
||||||
|
/// 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, portal: PortalData) -> 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,
|
||||||
@ -152,6 +158,18 @@ 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>;
|
||||||
|
/// Mutate the position of an entity or, if the entity is mounted, the
|
||||||
|
/// mount.
|
||||||
|
///
|
||||||
|
/// If `dismount_volume` is `true`, an entity mounted on a volume entity
|
||||||
|
/// (such as an airship) will be dismounted to avoid teleporting the volume
|
||||||
|
/// entity.
|
||||||
|
fn position_mut<T>(
|
||||||
|
&mut self,
|
||||||
|
entity: EcsEntity,
|
||||||
|
dismount_volume: bool,
|
||||||
|
f: impl for<'a> FnOnce(&'a mut comp::Pos) -> T,
|
||||||
|
) -> Result<T, &'static str>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StateExt for State {
|
impl StateExt for State {
|
||||||
@ -607,6 +625,12 @@ impl StateExt for State {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn create_teleporter(&mut self, pos: comp::Pos, portal: PortalData) -> EcsEntityBuilder {
|
||||||
|
self.create_object(pos, object::Body::Portal)
|
||||||
|
.with(comp::Immovable)
|
||||||
|
.with(comp::Object::from(portal))
|
||||||
|
}
|
||||||
|
|
||||||
fn initialize_character_data(
|
fn initialize_character_data(
|
||||||
&mut self,
|
&mut self,
|
||||||
entity: EcsEntity,
|
entity: EcsEntity,
|
||||||
@ -1226,6 +1250,74 @@ impl StateExt for State {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn position_mut<T>(
|
||||||
|
&mut self,
|
||||||
|
entity: EcsEntity,
|
||||||
|
dismount_volume: bool,
|
||||||
|
f: impl for<'a> FnOnce(&'a mut comp::Pos) -> T,
|
||||||
|
) -> Result<T, &'static str> {
|
||||||
|
if dismount_volume {
|
||||||
|
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."),
|
||||||
|
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("Cannot get position for entity!");
|
||||||
|
|
||||||
|
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) {
|
||||||
|
@ -11,6 +11,7 @@ pub mod persistence;
|
|||||||
pub mod pets;
|
pub mod pets;
|
||||||
pub mod sentinel;
|
pub mod sentinel;
|
||||||
pub mod subscription;
|
pub mod subscription;
|
||||||
|
pub mod teleporter;
|
||||||
pub mod terrain;
|
pub mod terrain;
|
||||||
pub mod terrain_sync;
|
pub mod terrain_sync;
|
||||||
pub mod waypoint;
|
pub mod waypoint;
|
||||||
@ -32,6 +33,7 @@ pub fn add_server_systems(dispatch_builder: &mut DispatcherBuilder) {
|
|||||||
dispatch::<agent::Sys>(dispatch_builder, &[]);
|
dispatch::<agent::Sys>(dispatch_builder, &[]);
|
||||||
dispatch::<terrain::Sys>(dispatch_builder, &[&msg::terrain::Sys::sys_name()]);
|
dispatch::<terrain::Sys>(dispatch_builder, &[&msg::terrain::Sys::sys_name()]);
|
||||||
dispatch::<waypoint::Sys>(dispatch_builder, &[]);
|
dispatch::<waypoint::Sys>(dispatch_builder, &[]);
|
||||||
|
dispatch::<teleporter::Sys>(dispatch_builder, &[]);
|
||||||
dispatch::<invite_timeout::Sys>(dispatch_builder, &[]);
|
dispatch::<invite_timeout::Sys>(dispatch_builder, &[]);
|
||||||
dispatch::<persistence::Sys>(dispatch_builder, &[]);
|
dispatch::<persistence::Sys>(dispatch_builder, &[]);
|
||||||
dispatch::<object::Sys>(dispatch_builder, &[]);
|
dispatch::<object::Sys>(dispatch_builder, &[]);
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
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},
|
||||||
|
outcome::Outcome,
|
||||||
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 +20,14 @@ 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, EventBus<Outcome>>,
|
||||||
|
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 +36,31 @@ 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,
|
||||||
|
outcome_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 +93,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 +189,32 @@ 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 {
|
||||||
|
outcome_bus.emit_now(Outcome::PortalActivated { pos: pos.0 });
|
||||||
|
object::Body::PortalActive
|
||||||
|
} else {
|
||||||
|
object::Body::Portal
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
134
server/src/sys/teleporter.rs
Normal file
134
server/src/sys/teleporter.rs
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
use common::{
|
||||||
|
comp::{Agent, Alignment, CharacterState, Object, Pos, Teleporting},
|
||||||
|
consts::TELEPORTER_RADIUS,
|
||||||
|
event::{EventBus, ServerEvent},
|
||||||
|
outcome::Outcome,
|
||||||
|
resources::Time,
|
||||||
|
uid::Uid,
|
||||||
|
CachedSpatialGrid,
|
||||||
|
};
|
||||||
|
use common_ecs::{Origin, Phase, System};
|
||||||
|
use specs::{Entities, Join, Read, ReadStorage, WriteStorage};
|
||||||
|
use vek::Vec3;
|
||||||
|
|
||||||
|
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.;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct Sys;
|
||||||
|
|
||||||
|
fn in_portal_range(player_pos: Vec3<f32>, portal_pos: Vec3<f32>) -> bool {
|
||||||
|
player_pos.distance_squared(portal_pos) <= TELEPORTER_RADIUS.powi(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> System<'a> for Sys {
|
||||||
|
type SystemData = (
|
||||||
|
Entities<'a>,
|
||||||
|
ReadStorage<'a, Pos>,
|
||||||
|
ReadStorage<'a, Uid>,
|
||||||
|
ReadStorage<'a, Alignment>,
|
||||||
|
ReadStorage<'a, Agent>,
|
||||||
|
ReadStorage<'a, Object>,
|
||||||
|
WriteStorage<'a, Teleporting>,
|
||||||
|
ReadStorage<'a, CharacterState>,
|
||||||
|
Read<'a, CachedSpatialGrid>,
|
||||||
|
Read<'a, Time>,
|
||||||
|
Read<'a, EventBus<ServerEvent>>,
|
||||||
|
Read<'a, EventBus<Outcome>>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const NAME: &'static str = "teleporter";
|
||||||
|
const ORIGIN: Origin = Origin::Server;
|
||||||
|
const PHASE: Phase = Phase::Create;
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
_job: &mut common_ecs::Job<Self>,
|
||||||
|
(
|
||||||
|
entities,
|
||||||
|
positions,
|
||||||
|
uids,
|
||||||
|
alignments,
|
||||||
|
agent,
|
||||||
|
objects,
|
||||||
|
mut teleporting,
|
||||||
|
character_states,
|
||||||
|
spatial_grid,
|
||||||
|
time,
|
||||||
|
server_bus,
|
||||||
|
outcome_bus,
|
||||||
|
): Self::SystemData,
|
||||||
|
) {
|
||||||
|
let check_aggro = |entity, pos: Vec3<f32>| {
|
||||||
|
spatial_grid
|
||||||
|
.0
|
||||||
|
.in_circle_aabr(pos.xy(), MAX_AGGRO_DIST)
|
||||||
|
.any(|agent_entity| {
|
||||||
|
agent.get(agent_entity).map_or(false, |agent| {
|
||||||
|
agent.target.map_or(false, |agent_target| {
|
||||||
|
agent_target.target == entity && agent_target.aggro_on
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut cancel_teleporting = Vec::new();
|
||||||
|
|
||||||
|
for (entity, uid, position, teleporting, character_state) in (
|
||||||
|
&entities,
|
||||||
|
&uids,
|
||||||
|
&positions,
|
||||||
|
&teleporting,
|
||||||
|
&character_states,
|
||||||
|
)
|
||||||
|
.join()
|
||||||
|
{
|
||||||
|
let portal_pos = positions.get(teleporting.portal);
|
||||||
|
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)
|
||||||
|
}) || (*requires_no_aggro && check_aggro(entity, position.0))
|
||||||
|
|| !matches!(
|
||||||
|
character_state,
|
||||||
|
CharacterState::Idle(_) | CharacterState::Wielding(_)
|
||||||
|
)
|
||||||
|
{
|
||||||
|
cancel_teleporting.push(entity);
|
||||||
|
} else if teleporting.end_time.0 <= time.0 {
|
||||||
|
// Send teleport events for all nearby pets and the owner
|
||||||
|
let nearby = spatial_grid
|
||||||
|
.0
|
||||||
|
.in_circle_aabr(position.0.xy(), PET_TELEPORT_RADIUS)
|
||||||
|
.filter_map(|entity| {
|
||||||
|
(&entities, &positions, &alignments)
|
||||||
|
.join()
|
||||||
|
.get(entity, &entities)
|
||||||
|
})
|
||||||
|
.filter_map(|(entity, entity_position, alignment)| {
|
||||||
|
(matches!(alignment, Alignment::Owned(entity_uid) if entity_uid == uid)
|
||||||
|
&& entity_position.0.distance_squared(position.0)
|
||||||
|
<= PET_TELEPORT_RADIUS.powi(2))
|
||||||
|
.then_some(entity)
|
||||||
|
});
|
||||||
|
|
||||||
|
for entity in nearby {
|
||||||
|
cancel_teleporting.push(entity);
|
||||||
|
server_bus.emit_now(ServerEvent::TeleportToPosition {
|
||||||
|
entity,
|
||||||
|
position: *target,
|
||||||
|
});
|
||||||
|
outcome_bus.emit_now(Outcome::TeleportedByPortal { pos: *target });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for entity in cancel_teleporting {
|
||||||
|
let _ = teleporting.remove(entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -12,11 +12,11 @@ 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, Waypoint,
|
BehaviorCapability, ForceUpdate, Pos, Presence, Waypoint,
|
||||||
},
|
},
|
||||||
event::{EventBus, NpcBuilder, ServerEvent},
|
event::{EventBus, NpcBuilder, ServerEvent},
|
||||||
generation::EntityInfo,
|
generation::{EntityInfo, SpecialEntity},
|
||||||
lottery::LootSpec,
|
lottery::LootSpec,
|
||||||
resources::{Time, TimeOfDay},
|
resources::{Time, TimeOfDay},
|
||||||
slowjob::SlowJobPool,
|
slowjob::SlowJobPool,
|
||||||
@ -222,6 +222,9 @@ impl<'a> System<'a> for Sys {
|
|||||||
.with_loot(loot),
|
.with_loot(loot),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
NpcData::Teleporter(pos, teleporter) => {
|
||||||
|
server_emitter.emit(ServerEvent::CreateTeleporter(pos, teleporter));
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -415,13 +418,14 @@ pub enum NpcData {
|
|||||||
loot: LootSpec<String>,
|
loot: LootSpec<String>,
|
||||||
},
|
},
|
||||||
Waypoint(Vec3<f32>),
|
Waypoint(Vec3<f32>),
|
||||||
|
Teleporter(Vec3<f32>, PortalData),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NpcData {
|
impl NpcData {
|
||||||
pub fn from_entity_info(entity: EntityInfo) -> Self {
|
pub fn from_entity_info(entity: EntityInfo) -> Self {
|
||||||
let EntityInfo {
|
let EntityInfo {
|
||||||
// flags
|
// flags
|
||||||
is_waypoint,
|
special_entity,
|
||||||
has_agency,
|
has_agency,
|
||||||
agent_mark,
|
agent_mark,
|
||||||
alignment,
|
alignment,
|
||||||
@ -444,8 +448,11 @@ impl NpcData {
|
|||||||
pet: _, // TODO: I had no idea we have this.
|
pet: _, // TODO: I had no idea we have this.
|
||||||
} = entity;
|
} = entity;
|
||||||
|
|
||||||
if is_waypoint {
|
if let Some(special) = special_entity {
|
||||||
return Self::Waypoint(pos);
|
return match special {
|
||||||
|
SpecialEntity::Waypoint => Self::Waypoint(pos),
|
||||||
|
SpecialEntity::Teleporter(teleporter) => Self::Teleporter(pos, teleporter),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let name = name.unwrap_or_else(|| "Unnamed".to_string());
|
let name = name.unwrap_or_else(|| "Unnamed".to_string());
|
||||||
|
@ -183,6 +183,8 @@ pub enum SfxEvent {
|
|||||||
Whoosh,
|
Whoosh,
|
||||||
Swoosh,
|
Swoosh,
|
||||||
GroundDig,
|
GroundDig,
|
||||||
|
PortalActivated,
|
||||||
|
TeleportedByPortal,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Deserialize, Hash, Eq)]
|
#[derive(Copy, Clone, Debug, PartialEq, Deserialize, Hash, Eq)]
|
||||||
@ -493,6 +495,14 @@ impl SfxMgr {
|
|||||||
let sfx_trigger_item = triggers.get_key_value(&SfxEvent::GroundDig);
|
let sfx_trigger_item = triggers.get_key_value(&SfxEvent::GroundDig);
|
||||||
audio.emit_sfx(sfx_trigger_item, *pos, Some(2.0), underwater);
|
audio.emit_sfx(sfx_trigger_item, *pos, Some(2.0), underwater);
|
||||||
},
|
},
|
||||||
|
Outcome::PortalActivated { pos, .. } => {
|
||||||
|
let sfx_trigger_item = triggers.get_key_value(&SfxEvent::PortalActivated);
|
||||||
|
audio.emit_sfx(sfx_trigger_item, *pos, Some(2.0), underwater);
|
||||||
|
},
|
||||||
|
Outcome::TeleportedByPortal { pos, .. } => {
|
||||||
|
let sfx_trigger_item = triggers.get_key_value(&SfxEvent::TeleportedByPortal);
|
||||||
|
audio.emit_sfx(sfx_trigger_item, *pos, Some(2.0), underwater);
|
||||||
|
},
|
||||||
Outcome::IceSpikes { pos, .. } => {
|
Outcome::IceSpikes { pos, .. } => {
|
||||||
let sfx_trigger_item = triggers.get_key_value(&SfxEvent::IceSpikes);
|
let sfx_trigger_item = triggers.get_key_value(&SfxEvent::IceSpikes);
|
||||||
audio.emit_sfx(sfx_trigger_item, *pos, Some(2.0), underwater);
|
audio.emit_sfx(sfx_trigger_item, *pos, Some(2.0), underwater);
|
||||||
|
@ -2219,13 +2219,13 @@ impl Hud {
|
|||||||
.set(overitem_id, ui_widgets);
|
.set(overitem_id, ui_widgets);
|
||||||
}
|
}
|
||||||
} else if let Some(Interactable::Entity(entity)) = interactable {
|
} else if let Some(Interactable::Entity(entity)) = interactable {
|
||||||
// show hud for campfire
|
// show hud for campfires and portals
|
||||||
if client
|
if let Some(body) = client
|
||||||
.state()
|
.state()
|
||||||
.ecs()
|
.ecs()
|
||||||
.read_storage::<comp::Body>()
|
.read_storage::<comp::Body>()
|
||||||
.get(*entity)
|
.get(*entity)
|
||||||
.map_or(false, |b| b.is_campfire())
|
.filter(|b| b.is_campfire() || b.is_portal())
|
||||||
{
|
{
|
||||||
let overitem_id = overitem_walker.next(
|
let overitem_id = overitem_walker.next(
|
||||||
&mut self.ids.overitems,
|
&mut self.ids.overitems,
|
||||||
@ -2245,7 +2245,13 @@ impl Hud {
|
|||||||
let over_pos = pos + Vec3::unit_z() * 1.5;
|
let over_pos = pos + Vec3::unit_z() * 1.5;
|
||||||
|
|
||||||
overitem::Overitem::new(
|
overitem::Overitem::new(
|
||||||
i18n.get_msg("hud-crafting-campfire"),
|
i18n.get_msg(if body.is_campfire() {
|
||||||
|
"hud-crafting-campfire"
|
||||||
|
} else if body.is_portal() {
|
||||||
|
"hud-portal"
|
||||||
|
} else {
|
||||||
|
"hud-use"
|
||||||
|
}),
|
||||||
overitem::TEXT_COLOR,
|
overitem::TEXT_COLOR,
|
||||||
pos.distance_squared(player_pos),
|
pos.distance_squared(player_pos),
|
||||||
&self.fonts,
|
&self.fonts,
|
||||||
@ -2256,7 +2262,13 @@ impl Hud {
|
|||||||
&global_state.window.key_layout,
|
&global_state.window.key_layout,
|
||||||
vec![(
|
vec![(
|
||||||
Some(GameInput::Interact),
|
Some(GameInput::Interact),
|
||||||
i18n.get_msg("hud-sit").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)
|
||||||
|
@ -98,6 +98,7 @@ pub enum ParticleMode {
|
|||||||
SnowStorm = 44,
|
SnowStorm = 44,
|
||||||
PortalFizz = 45,
|
PortalFizz = 45,
|
||||||
Ink = 46,
|
Ink = 46,
|
||||||
|
UpwardPortalFizz = 47,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ParticleMode {
|
impl ParticleMode {
|
||||||
|
@ -396,6 +396,21 @@ impl ParticleMgr {
|
|||||||
)
|
)
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
Outcome::TeleportedByPortal { pos, .. } => {
|
||||||
|
self.particles.resize_with(self.particles.len() + 80, || {
|
||||||
|
Particle::new_directed(
|
||||||
|
Duration::from_millis(500),
|
||||||
|
time,
|
||||||
|
ParticleMode::CultistFlame,
|
||||||
|
*pos,
|
||||||
|
pos + Vec3::unit_z()
|
||||||
|
+ Vec3::zero()
|
||||||
|
.map(|_: f32| rng.gen_range(-0.1..0.1))
|
||||||
|
.normalized()
|
||||||
|
* 2.0,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
},
|
||||||
Outcome::ProjectileShot { .. }
|
Outcome::ProjectileShot { .. }
|
||||||
| Outcome::Beam { .. }
|
| Outcome::Beam { .. }
|
||||||
| Outcome::ExpChange { .. }
|
| Outcome::ExpChange { .. }
|
||||||
@ -411,6 +426,7 @@ impl ParticleMgr {
|
|||||||
| Outcome::Swoosh { .. }
|
| Outcome::Swoosh { .. }
|
||||||
| Outcome::Steam { .. }
|
| Outcome::Steam { .. }
|
||||||
| Outcome::FireShockwave { .. }
|
| Outcome::FireShockwave { .. }
|
||||||
|
| Outcome::PortalActivated { .. }
|
||||||
| Outcome::LaserBeam { .. } => {},
|
| Outcome::LaserBeam { .. } => {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -499,6 +515,12 @@ impl ParticleMgr {
|
|||||||
| object::Body::FireworkWhite
|
| object::Body::FireworkWhite
|
||||||
| object::Body::FireworkYellow,
|
| object::Body::FireworkYellow,
|
||||||
) => self.maintain_bomb_particles(scene_data, interpolated.pos, vel),
|
) => self.maintain_bomb_particles(scene_data, interpolated.pos, vel),
|
||||||
|
Body::Object(object::Body::PortalActive) => {
|
||||||
|
self.maintain_active_portal_particles(scene_data, interpolated.pos)
|
||||||
|
},
|
||||||
|
Body::Object(object::Body::Portal) => {
|
||||||
|
self.maintain_portal_particles(scene_data, interpolated.pos)
|
||||||
|
},
|
||||||
_ => {},
|
_ => {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -723,6 +745,56 @@ impl ParticleMgr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn maintain_active_portal_particles(&mut self, scene_data: &SceneData, pos: Vec3<f32>) {
|
||||||
|
span!(
|
||||||
|
_guard,
|
||||||
|
"active_portal_particles",
|
||||||
|
"ParticleMgr::maintain_active_portal_particles"
|
||||||
|
);
|
||||||
|
|
||||||
|
let time = scene_data.state.get_time();
|
||||||
|
let mut rng = thread_rng();
|
||||||
|
|
||||||
|
for _ in 0..self.scheduler.heartbeats(Duration::from_millis(5)) {
|
||||||
|
let outer_pos =
|
||||||
|
pos + (Vec2::unit_x().rotated_z(rng.gen_range((0.)..PI * 2.)) * 2.7).with_z(0.);
|
||||||
|
|
||||||
|
self.particles.push(Particle::new_directed(
|
||||||
|
Duration::from_secs_f32(rng.gen_range(0.2..0.5)),
|
||||||
|
time,
|
||||||
|
ParticleMode::UpwardPortalFizz,
|
||||||
|
outer_pos,
|
||||||
|
outer_pos + Vec3::unit_z() * rng.gen_range(5.0..7.0),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn maintain_portal_particles(&mut self, scene_data: &SceneData, pos: Vec3<f32>) {
|
||||||
|
span!(
|
||||||
|
_guard,
|
||||||
|
"portal_particles",
|
||||||
|
"ParticleMgr::maintain_portal_particles"
|
||||||
|
);
|
||||||
|
|
||||||
|
let time = scene_data.state.get_time();
|
||||||
|
let mut rng = thread_rng();
|
||||||
|
|
||||||
|
for _ in 0..self.scheduler.heartbeats(Duration::from_millis(150)) {
|
||||||
|
let outer_pos = pos
|
||||||
|
+ (Vec2::unit_x().rotated_z(rng.gen_range((0.)..PI * 2.))
|
||||||
|
* rng.gen_range(1.0..2.9))
|
||||||
|
.with_z(0.);
|
||||||
|
|
||||||
|
self.particles.push(Particle::new_directed(
|
||||||
|
Duration::from_secs_f32(rng.gen_range(0.5..3.0)),
|
||||||
|
time,
|
||||||
|
ParticleMode::UpwardPortalFizz,
|
||||||
|
outer_pos,
|
||||||
|
outer_pos + Vec3::unit_z() * rng.gen_range(3.0..4.0),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn maintain_mine_particles(&mut self, scene_data: &SceneData, pos: Vec3<f32>) {
|
fn maintain_mine_particles(&mut self, scene_data: &SceneData, pos: Vec3<f32>) {
|
||||||
span!(
|
span!(
|
||||||
_guard,
|
_guard,
|
||||||
|
@ -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() || 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;
|
||||||
};
|
};
|
||||||
|
@ -1027,6 +1027,10 @@ impl PlayState for SessionState {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
Interactable::Entity(entity) => {
|
Interactable::Entity(entity) => {
|
||||||
|
let body = client
|
||||||
|
.state()
|
||||||
|
.read_component_cloned::<comp::Body>(*entity);
|
||||||
|
|
||||||
if client
|
if client
|
||||||
.state()
|
.state()
|
||||||
.ecs()
|
.ecs()
|
||||||
@ -1035,15 +1039,21 @@ impl PlayState for SessionState {
|
|||||||
.is_some()
|
.is_some()
|
||||||
{
|
{
|
||||||
client.pick_up(*entity);
|
client.pick_up(*entity);
|
||||||
} else if client
|
} else if body
|
||||||
.state()
|
.map_or(false, |body| body.is_campfire())
|
||||||
.ecs()
|
|
||||||
.read_storage::<comp::Body>()
|
|
||||||
.get(*entity)
|
|
||||||
.map_or(false, |b| b.is_campfire())
|
|
||||||
{
|
{
|
||||||
// TODO: maybe start crafting instead?
|
|
||||||
client.toggle_sit();
|
client.toggle_sit();
|
||||||
|
} else if let Some(portal_uid) = body
|
||||||
|
.map_or(false, |body| body.is_portal())
|
||||||
|
.then(|| {
|
||||||
|
client
|
||||||
|
.state()
|
||||||
|
.ecs()
|
||||||
|
.uid_from_entity(*entity)
|
||||||
|
})
|
||||||
|
.flatten()
|
||||||
|
{
|
||||||
|
client.activate_portal(portal_uid);
|
||||||
} else {
|
} else {
|
||||||
client.npc_interact(*entity, Subject::Regular);
|
client.npc_interact(*entity, Subject::Regular);
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ mod econ;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
config::CONFIG,
|
config::CONFIG,
|
||||||
|
layer::cave,
|
||||||
sim::WorldSim,
|
sim::WorldSim,
|
||||||
site::{namegen::NameGen, Castle, Settlement, Site as WorldSite, Tree},
|
site::{namegen::NameGen, Castle, Settlement, Site as WorldSite, Tree},
|
||||||
site2,
|
site2,
|
||||||
@ -1936,7 +1937,28 @@ impl SiteKind {
|
|||||||
}
|
}
|
||||||
true
|
true
|
||||||
},
|
},
|
||||||
SiteKind::Dungeon => on_land(),
|
SiteKind::Dungeon => {
|
||||||
|
on_land() && {
|
||||||
|
let land = Land::from_sim(sim);
|
||||||
|
let loc = loc.cpos_to_wpos();
|
||||||
|
let dungeon_aabr = Aabr {
|
||||||
|
min: loc - Vec2::broadcast(200),
|
||||||
|
max: loc + Vec2::broadcast(200),
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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))
|
||||||
|
.all(|tunnel| {
|
||||||
|
!dungeon_aabr.collides_with_aabr(Aabr {
|
||||||
|
min: tunnel.nodes().0.wpos,
|
||||||
|
max: tunnel.nodes().1.wpos,
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
collides_with_cave
|
||||||
|
}
|
||||||
|
},
|
||||||
SiteKind::Refactor | SiteKind::Settlement => suitable_for_town(),
|
SiteKind::Refactor | SiteKind::Settlement => suitable_for_town(),
|
||||||
SiteKind::Bridge(_, _) => true,
|
SiteKind::Bridge(_, _) => true,
|
||||||
}
|
}
|
||||||
|
@ -215,9 +215,11 @@ impl Tunnel {
|
|||||||
depth,
|
depth,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn nodes(&self) -> (&Node, &Node) { (&self.a, &self.b) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tunnels_at<'a>(
|
pub(crate) fn tunnels_at<'a>(
|
||||||
wpos: Vec2<i32>,
|
wpos: Vec2<i32>,
|
||||||
level: u32,
|
level: u32,
|
||||||
land: &'a Land,
|
land: &'a Land,
|
||||||
|
@ -46,7 +46,7 @@ use crate::{
|
|||||||
use common::{
|
use common::{
|
||||||
assets,
|
assets,
|
||||||
calendar::Calendar,
|
calendar::Calendar,
|
||||||
generation::{ChunkSupplement, EntityInfo},
|
generation::{ChunkSupplement, EntityInfo, SpecialEntity},
|
||||||
lod,
|
lod,
|
||||||
resources::TimeOfDay,
|
resources::TimeOfDay,
|
||||||
rtsim::ChunkResource,
|
rtsim::ChunkResource,
|
||||||
@ -488,7 +488,8 @@ impl World {
|
|||||||
.fold(SpawnRules::default(), |a, b| a.combine(b))
|
.fold(SpawnRules::default(), |a, b| a.combine(b))
|
||||||
.waypoints
|
.waypoints
|
||||||
{
|
{
|
||||||
supplement.add_entity(EntityInfo::at(waypoint_pos).into_waypoint());
|
supplement
|
||||||
|
.add_entity(EntityInfo::at(waypoint_pos).into_special(SpecialEntity::Waypoint));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ use crate::{
|
|||||||
Land,
|
Land,
|
||||||
};
|
};
|
||||||
use common::{
|
use common::{
|
||||||
generation::EntityInfo,
|
generation::{EntityInfo, SpecialEntity},
|
||||||
terrain::{BlockKind, SpriteKind},
|
terrain::{BlockKind, SpriteKind},
|
||||||
};
|
};
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
@ -752,7 +752,10 @@ impl Structure for CliffTower {
|
|||||||
// spawn campfire next to some clifftowers
|
// spawn campfire next to some clifftowers
|
||||||
if self.campfire {
|
if self.campfire {
|
||||||
let campfire_pos = (plot_center - 20).with_z(self.alt + 18);
|
let campfire_pos = (plot_center - 20).with_z(self.alt + 18);
|
||||||
painter.spawn(EntityInfo::at(campfire_pos.map(|e| e as f32)).into_waypoint());
|
painter.spawn(
|
||||||
|
EntityInfo::at(campfire_pos.map(|e| e as f32))
|
||||||
|
.into_special(SpecialEntity::Waypoint),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,10 @@ use crate::{
|
|||||||
util::{RandomField, Sampler, CARDINALS},
|
util::{RandomField, Sampler, CARDINALS},
|
||||||
Land,
|
Land,
|
||||||
};
|
};
|
||||||
use common::terrain::{BlockKind, SpriteKind};
|
use common::{
|
||||||
|
generation::SpecialEntity,
|
||||||
|
terrain::{BlockKind, SpriteKind},
|
||||||
|
};
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use vek::*;
|
use vek::*;
|
||||||
@ -326,7 +329,8 @@ impl Structure for CoastalWorkshop {
|
|||||||
}
|
}
|
||||||
|
|
||||||
painter.spawn(
|
painter.spawn(
|
||||||
EntityInfo::at((center - 2).with_z(base - 2).map(|e| e as f32 + 0.5)).into_waypoint(),
|
EntityInfo::at((center - 2).with_z(base - 2).map(|e| e as f32 + 0.5))
|
||||||
|
.into_special(SpecialEntity::Waypoint),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ use crate::{
|
|||||||
Land,
|
Land,
|
||||||
};
|
};
|
||||||
use common::{
|
use common::{
|
||||||
generation::EntityInfo,
|
generation::{EntityInfo, SpecialEntity},
|
||||||
terrain::{Block, BlockKind, SpriteKind, Structure as PrefabStructure, StructuresGroup},
|
terrain::{Block, BlockKind, SpriteKind, Structure as PrefabStructure, StructuresGroup},
|
||||||
};
|
};
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
@ -2432,7 +2432,10 @@ impl Structure for DesertCityMultiPlot {
|
|||||||
// spawn campfire in some multiplots that are not markethall
|
// spawn campfire in some multiplots that are not markethall
|
||||||
let campfire_pos = (center).with_z(base + 1);
|
let campfire_pos = (center).with_z(base + 1);
|
||||||
if self.campfire {
|
if self.campfire {
|
||||||
painter.spawn(EntityInfo::at(campfire_pos.map(|e| e as f32)).into_waypoint())
|
painter.spawn(
|
||||||
|
EntityInfo::at(campfire_pos.map(|e| e as f32))
|
||||||
|
.into_special(SpecialEntity::Waypoint),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,9 @@ use crate::{
|
|||||||
use common::{
|
use common::{
|
||||||
assets::{self, AssetExt, AssetHandle},
|
assets::{self, AssetExt, AssetHandle},
|
||||||
astar::Astar,
|
astar::Astar,
|
||||||
generation::{ChunkSupplement, EntityInfo},
|
comp::misc::PortalData,
|
||||||
|
generation::{ChunkSupplement, EntityInfo, SpecialEntity},
|
||||||
|
resources::Secs,
|
||||||
store::{Id, Store},
|
store::{Id, Store},
|
||||||
terrain::{
|
terrain::{
|
||||||
BiomeKind, Block, BlockKind, SpriteKind, Structure, StructuresGroup, TerrainChunkSize,
|
BiomeKind, Block, BlockKind, SpriteKind, Structure, StructuresGroup, TerrainChunkSize,
|
||||||
@ -146,7 +148,7 @@ impl Dungeon {
|
|||||||
if area.contains_point(pos - self.origin) {
|
if area.contains_point(pos - self.origin) {
|
||||||
supplement.add_entity(
|
supplement.add_entity(
|
||||||
EntityInfo::at(Vec3::new(pos.x as f32, pos.y as f32, self.alt as f32) + 5.0)
|
EntityInfo::at(Vec3::new(pos.x as f32, pos.y as f32, self.alt as f32) + 5.0)
|
||||||
.into_waypoint(),
|
.into_special(SpecialEntity::Waypoint),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -615,6 +617,14 @@ impl Floor {
|
|||||||
for y in area.min.y..area.max.y {
|
for y in area.min.y..area.max.y {
|
||||||
let tile_pos = Vec2::new(x, y).map(|e| e.div_euclid(TILE_SIZE)) - self.tile_offset;
|
let tile_pos = Vec2::new(x, y).map(|e| e.div_euclid(TILE_SIZE)) - self.tile_offset;
|
||||||
let wpos2d = origin.xy() + Vec2::new(x, y);
|
let wpos2d = origin.xy() + Vec2::new(x, y);
|
||||||
|
let is_boss_tile = self.tiles.get(tile_pos).filter(|_| self.final_level);
|
||||||
|
|
||||||
|
let tile_wcenter = origin
|
||||||
|
+ Vec3::from(
|
||||||
|
Vec2::new(x, y)
|
||||||
|
.map(|e| e.div_euclid(TILE_SIZE) * TILE_SIZE + TILE_SIZE / 2),
|
||||||
|
);
|
||||||
|
|
||||||
if let Some(Tile::Room(room)) = self.tiles.get(tile_pos) {
|
if let Some(Tile::Room(room)) = self.tiles.get(tile_pos) {
|
||||||
let room = &self.rooms[*room];
|
let room = &self.rooms[*room];
|
||||||
|
|
||||||
@ -648,6 +658,27 @@ impl Floor {
|
|||||||
),
|
),
|
||||||
RoomKind::Peaceful | RoomKind::LavaPlatforming => {},
|
RoomKind::Peaceful | RoomKind::LavaPlatforming => {},
|
||||||
}
|
}
|
||||||
|
} else if let Some(Tile::UpStair(_, _)) = is_boss_tile && tile_wcenter.xy() == wpos2d {
|
||||||
|
// Create one teleporter at the top of the "stairs" and one at the botton
|
||||||
|
let bottom_pos = tile_wcenter.map(|v| v as f32) ;
|
||||||
|
let top_pos =
|
||||||
|
(tile_wcenter + Vec3::unit_z() * self.total_depth()).map(|v| v as f32);
|
||||||
|
|
||||||
|
// 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(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(PortalData {
|
||||||
|
target: top_pos + Vec3::unit_x() * 5.,
|
||||||
|
requires_no_aggro: true,
|
||||||
|
buildup_time: Secs(5.),
|
||||||
|
}),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -976,6 +1007,7 @@ impl Floor {
|
|||||||
let lava = Block::new(BlockKind::Lava, noisy_color(Rgb::new(184, 39, 0), 8));
|
let lava = Block::new(BlockKind::Lava, noisy_color(Rgb::new(184, 39, 0), 8));
|
||||||
let stone = Block::new(BlockKind::Rock, Rgb::new(150, 150, 175));
|
let stone = Block::new(BlockKind::Rock, Rgb::new(150, 150, 175));
|
||||||
let stone_purple = Block::new(BlockKind::GlowingRock, Rgb::new(96, 0, 128));
|
let stone_purple = Block::new(BlockKind::GlowingRock, Rgb::new(96, 0, 128));
|
||||||
|
let stone_wall = Block::new(BlockKind::Rock, Rgb::new(120, 120, 135));
|
||||||
|
|
||||||
// Sprites are randomly positioned and have random kinds, this primitive
|
// Sprites are randomly positioned and have random kinds, this primitive
|
||||||
// produces a box of dots that will later get truncated to just the
|
// produces a box of dots that will later get truncated to just the
|
||||||
@ -1097,6 +1129,10 @@ impl Floor {
|
|||||||
min: tile_corner,
|
min: tile_corner,
|
||||||
max: tile_corner + Vec2::broadcast(TILE_SIZE),
|
max: tile_corner + Vec2::broadcast(TILE_SIZE),
|
||||||
};
|
};
|
||||||
|
let outer_tile_aabr = |dist| Aabr {
|
||||||
|
min: tile_corner - Vec2::broadcast(dist),
|
||||||
|
max: tile_corner + Vec2::broadcast(TILE_SIZE + dist),
|
||||||
|
};
|
||||||
let tile_center = tile_corner + Vec2::broadcast(TILE_SIZE / 2);
|
let tile_center = tile_corner + Vec2::broadcast(TILE_SIZE / 2);
|
||||||
let (mut height, room) = match tile {
|
let (mut height, room) = match tile {
|
||||||
Tile::UpStair(room, _) => (self.hollow_depth, Some(room)),
|
Tile::UpStair(room, _) => (self.hollow_depth, Some(room)),
|
||||||
@ -1106,6 +1142,20 @@ impl Floor {
|
|||||||
Tile::Solid => continue,
|
Tile::Solid => continue,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let outer_tile_aabb = painter.prim(Primitive::Aabb(aabr_with_z(
|
||||||
|
outer_tile_aabr(0),
|
||||||
|
floor_z - 2..(floor_z + tunnel_height as i32) + 2,
|
||||||
|
)));
|
||||||
|
|
||||||
|
// Fill Walls
|
||||||
|
painter.fill(outer_tile_aabb, Fill::Block(stone_wall));
|
||||||
|
|
||||||
|
let tile_floor_fill = painter.prim(Primitive::Aabb(aabr_with_z(
|
||||||
|
tile_aabr,
|
||||||
|
floor_z - 1..floor_z,
|
||||||
|
)));
|
||||||
|
painter.fill(tile_floor_fill, Fill::Block(stone_wall));
|
||||||
|
|
||||||
// Sprites are contained to the level above the floor, and not within walls
|
// Sprites are contained to the level above the floor, and not within walls
|
||||||
let sprite_layer = painter.prim(Primitive::Aabb(aabr_with_z(
|
let sprite_layer = painter.prim(Primitive::Aabb(aabr_with_z(
|
||||||
tile_aabr,
|
tile_aabr,
|
||||||
@ -1124,17 +1174,27 @@ impl Floor {
|
|||||||
|
|
||||||
if let Some(room) = room.map(|i| self.rooms.get(*i)) {
|
if let Some(room) = room.map(|i| self.rooms.get(*i)) {
|
||||||
height = height.min(room.height);
|
height = height.min(room.height);
|
||||||
if let Tile::UpStair(_, kind) = tile {
|
if let Tile::UpStair(_, kind) = tile && !self.final_level {
|
||||||
// Construct the staircase that connects this tile to the matching DownStair
|
// Construct the staircase that connects this tile to the matching DownStair
|
||||||
// tile on the floor above (or to the surface if this is the top floor), and a
|
// tile on the floor above (or to the surface if this is the top floor), and a
|
||||||
// hollow bounding box to place air in
|
// hollow bounding box to place air in
|
||||||
let center = tile_center.with_z(floor_z);
|
let center = tile_center.with_z(floor_z);
|
||||||
let radius = TILE_SIZE as f32 / 2.0;
|
let radius = TILE_SIZE as f32 / 2.0;
|
||||||
let aabb = aabr_with_z(tile_aabr, floor_z..floor_z + self.total_depth());
|
let aabb = aabr_with_z(tile_aabr, floor_z..floor_z + self.total_depth());
|
||||||
|
let outer_aabb = aabr_with_z(
|
||||||
|
outer_tile_aabr(2),
|
||||||
|
floor_z + tunnel_height as i32..floor_z + self.total_depth() - 1,
|
||||||
|
);
|
||||||
let bb = painter.prim(match kind {
|
let bb = painter.prim(match kind {
|
||||||
StairsKind::Spiral => Primitive::Cylinder(aabb),
|
StairsKind::Spiral => Primitive::Cylinder(aabb),
|
||||||
StairsKind::WallSpiral => Primitive::Aabb(aabb),
|
StairsKind::WallSpiral => Primitive::Aabb(aabb),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
painter.fill(painter.prim(match kind {
|
||||||
|
StairsKind::WallSpiral => Primitive::Aabb(outer_aabb),
|
||||||
|
StairsKind::Spiral => Primitive::Cylinder(outer_aabb),
|
||||||
|
}), Fill::Block(stone_wall));
|
||||||
|
|
||||||
let stair = painter.prim(Primitive::sampling(bb, match kind {
|
let stair = painter.prim(Primitive::sampling(bb, match kind {
|
||||||
StairsKind::Spiral => spiral_staircase(center, radius, 0.5, 9.0),
|
StairsKind::Spiral => spiral_staircase(center, radius, 0.5, 9.0),
|
||||||
StairsKind::WallSpiral => wall_staircase(center, radius, 27.0),
|
StairsKind::WallSpiral => wall_staircase(center, radius, 27.0),
|
||||||
@ -1161,6 +1221,7 @@ impl Floor {
|
|||||||
stairs_bb.push(bb);
|
stairs_bb.push(bb);
|
||||||
stairs.push((stair, lights));
|
stairs.push((stair, lights));
|
||||||
}
|
}
|
||||||
|
|
||||||
if matches!(tile, Tile::Room(_) | Tile::DownStair(_)) {
|
if matches!(tile, Tile::Room(_) | Tile::DownStair(_)) {
|
||||||
let seed = room.seed;
|
let seed = room.seed;
|
||||||
let loot_density = room.loot_density;
|
let loot_density = room.loot_density;
|
||||||
@ -1274,7 +1335,7 @@ impl Floor {
|
|||||||
// Carve out the room's air inside the walls
|
// Carve out the room's air inside the walls
|
||||||
let tile_air = painter.prim(Primitive::Aabb(aabr_with_z(
|
let tile_air = painter.prim(Primitive::Aabb(aabr_with_z(
|
||||||
tile_aabr,
|
tile_aabr,
|
||||||
floor_z..floor_z + height,
|
floor_z..floor_z + tunnel_height as i32,
|
||||||
)));
|
)));
|
||||||
let tile_air = painter.prim(Primitive::without(tile_air, wall_contours));
|
let tile_air = painter.prim(Primitive::without(tile_air, wall_contours));
|
||||||
painter.fill(tile_air, Fill::Block(vacant));
|
painter.fill(tile_air, Fill::Block(vacant));
|
||||||
@ -1319,11 +1380,12 @@ impl Floor {
|
|||||||
painter.fill(*lights, sconces_inward.clone());
|
painter.fill(*lights, sconces_inward.clone());
|
||||||
painter.fill(*pillar, Fill::Block(stone));
|
painter.fill(*pillar, Fill::Block(stone));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Carve out space for the stairs
|
// Carve out space for the stairs
|
||||||
for stair_bb in stairs_bb.iter() {
|
for stair_bb in stairs_bb {
|
||||||
painter.fill(*stair_bb, Fill::Block(vacant));
|
painter.fill(stair_bb, Fill::Block(vacant));
|
||||||
// Prevent sprites from floating above the stairs
|
// Prevent sprites from floating above the stairs
|
||||||
let stair_bb_up = painter.prim(Primitive::translate(*stair_bb, Vec3::unit_z()));
|
let stair_bb_up = painter.prim(Primitive::translate(stair_bb, Vec3::unit_z()));
|
||||||
for (sprite, _) in sprites.iter_mut() {
|
for (sprite, _) in sprites.iter_mut() {
|
||||||
*sprite = painter.prim(Primitive::without(*sprite, stair_bb_up));
|
*sprite = painter.prim(Primitive::without(*sprite, stair_bb_up));
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ ChatGPT: Improving Code Quality, Speeding up finding Syntax Errors
|
|||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{site2::gen::PrimitiveTransform, Land};
|
use crate::{site2::gen::PrimitiveTransform, Land};
|
||||||
use common::terrain::Structure as PrefabStructure;
|
use common::{generation::SpecialEntity, terrain::Structure as PrefabStructure};
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
use vek::*;
|
use vek::*;
|
||||||
|
|
||||||
@ -166,7 +166,8 @@ impl Structure for DwarvenMine {
|
|||||||
let entrance_offset: Vec3<f32> = (20.0, -20.0, 45.0).into();
|
let entrance_offset: Vec3<f32> = (20.0, -20.0, 45.0).into();
|
||||||
// Spawn waypoint
|
// Spawn waypoint
|
||||||
let waypoint_pos = (entrance_pos + Vec3::new(1, -1, 4)).map(|x| x as f32) + entrance_offset;
|
let waypoint_pos = (entrance_pos + Vec3::new(1, -1, 4)).map(|x| x as f32) + entrance_offset;
|
||||||
painter.spawn(EntityInfo::at(waypoint_pos.map(|e| e)).into_waypoint());
|
painter
|
||||||
|
.spawn(EntityInfo::at(waypoint_pos.map(|e| e)).into_special(SpecialEntity::Waypoint));
|
||||||
|
|
||||||
let miner_pos: Vec<(Vec3<f32>, &str)> = vec![
|
let miner_pos: Vec<(Vec3<f32>, &str)> = vec![
|
||||||
// Entrance
|
// Entrance
|
||||||
|
@ -4,7 +4,7 @@ use crate::{
|
|||||||
Land,
|
Land,
|
||||||
};
|
};
|
||||||
use common::{
|
use common::{
|
||||||
generation::EntityInfo,
|
generation::{EntityInfo, SpecialEntity},
|
||||||
terrain::{BlockKind, SpriteKind},
|
terrain::{BlockKind, SpriteKind},
|
||||||
};
|
};
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
@ -936,7 +936,9 @@ impl Structure for SavannahPit {
|
|||||||
// campfire
|
// campfire
|
||||||
let campfire_pos = (center - 10).with_z(base);
|
let campfire_pos = (center - 10).with_z(base);
|
||||||
|
|
||||||
painter.spawn(EntityInfo::at(campfire_pos.map(|e| e as f32)).into_waypoint());
|
painter.spawn(
|
||||||
|
EntityInfo::at(campfire_pos.map(|e| e as f32)).into_special(SpecialEntity::Waypoint),
|
||||||
|
);
|
||||||
|
|
||||||
// underground market hall
|
// underground market hall
|
||||||
let stairs_pos = Vec2::new(center.x, center.y - (2 * length) + 1);
|
let stairs_pos = Vec2::new(center.x, center.y - (2 * length) + 1);
|
||||||
|
@ -3,7 +3,10 @@ use crate::{
|
|||||||
util::{RandomField, Sampler, CARDINALS, DIAGONALS},
|
util::{RandomField, Sampler, CARDINALS, DIAGONALS},
|
||||||
Land,
|
Land,
|
||||||
};
|
};
|
||||||
use common::terrain::{BlockKind, SpriteKind};
|
use common::{
|
||||||
|
generation::SpecialEntity,
|
||||||
|
terrain::{BlockKind, SpriteKind},
|
||||||
|
};
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
use std::{f32::consts::TAU, sync::Arc};
|
use std::{f32::consts::TAU, sync::Arc};
|
||||||
use vek::*;
|
use vek::*;
|
||||||
@ -302,7 +305,8 @@ impl Structure for SavannahWorkshop {
|
|||||||
}
|
}
|
||||||
|
|
||||||
painter.spawn(
|
painter.spawn(
|
||||||
EntityInfo::at((center).with_z(base - 2).map(|e| e as f32 + 0.5)).into_waypoint(),
|
EntityInfo::at((center).with_z(base - 2).map(|e| e as f32 + 0.5))
|
||||||
|
.into_special(SpecialEntity::Waypoint),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ use crate::{
|
|||||||
Land,
|
Land,
|
||||||
};
|
};
|
||||||
use common::{
|
use common::{
|
||||||
generation::EntityInfo,
|
generation::{EntityInfo, SpecialEntity},
|
||||||
terrain::{Block, BlockKind, SpriteKind},
|
terrain::{Block, BlockKind, SpriteKind},
|
||||||
};
|
};
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
@ -145,7 +145,7 @@ impl Structure for Workshop {
|
|||||||
|
|
||||||
painter.spawn(
|
painter.spawn(
|
||||||
EntityInfo::at(self.bounds.center().with_z(base).map(|e| e as f32 + 0.5))
|
EntityInfo::at(self.bounds.center().with_z(base).map(|e| e as f32 + 0.5))
|
||||||
.into_waypoint(),
|
.into_special(SpecialEntity::Waypoint),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user