2021-05-15 19:36:27 +00:00
|
|
|
use specs::{world::WorldExt, Builder, Entity as EcsEntity, Join};
|
2021-01-08 19:12:09 +00:00
|
|
|
use tracing::error;
|
2021-03-21 16:09:16 +00:00
|
|
|
use vek::*;
|
2021-01-08 19:12:09 +00:00
|
|
|
|
2020-02-16 20:04:06 +00:00
|
|
|
use common::{
|
2021-06-09 05:14:20 +00:00
|
|
|
assets,
|
2021-03-21 17:45:01 +00:00
|
|
|
comp::{
|
2021-05-15 19:36:27 +00:00
|
|
|
self,
|
2021-06-15 16:57:52 +00:00
|
|
|
agent::{AgentEvent, Sound, SoundKind, MAX_LISTEN_DIST},
|
2021-05-15 19:36:27 +00:00
|
|
|
dialogue::Subject,
|
|
|
|
inventory::slot::EquipSlot,
|
|
|
|
item,
|
|
|
|
slot::Slot,
|
|
|
|
tool::ToolKind,
|
2021-06-09 05:14:20 +00:00
|
|
|
Inventory, Pos, SkillGroupKind,
|
2021-03-21 17:45:01 +00:00
|
|
|
},
|
2021-05-15 19:36:27 +00:00
|
|
|
consts::{MAX_MOUNT_RANGE, SOUND_TRAVEL_DIST_PER_VOLUME},
|
2021-03-21 20:09:59 +00:00
|
|
|
outcome::Outcome,
|
2021-06-19 18:53:23 +00:00
|
|
|
terrain::{Block, SpriteKind},
|
2020-12-13 17:11:55 +00:00
|
|
|
uid::Uid,
|
2021-03-21 16:09:16 +00:00
|
|
|
vol::ReadVol,
|
2020-02-16 20:04:06 +00:00
|
|
|
};
|
2020-12-13 17:11:55 +00:00
|
|
|
use common_net::{msg::ServerGeneral, sync::WorldSyncExt};
|
2021-01-08 19:12:09 +00:00
|
|
|
|
|
|
|
use crate::{
|
|
|
|
client::Client,
|
|
|
|
presence::{Presence, RegionSubscription},
|
2021-02-17 02:15:45 +00:00
|
|
|
state_ext::StateExt,
|
2021-01-08 19:12:09 +00:00
|
|
|
Server,
|
|
|
|
};
|
2020-02-16 20:04:06 +00:00
|
|
|
|
2021-06-09 20:03:25 +00:00
|
|
|
use hashbrown::{HashMap, HashSet};
|
2021-06-09 05:14:20 +00:00
|
|
|
use lazy_static::lazy_static;
|
|
|
|
use serde::Deserialize;
|
2021-06-09 20:03:25 +00:00
|
|
|
use std::iter::FromIterator;
|
2021-06-09 05:14:20 +00:00
|
|
|
|
2020-10-07 02:23:20 +00:00
|
|
|
pub fn handle_lantern(server: &mut Server, entity: EcsEntity, enable: bool) {
|
2020-05-04 15:15:31 +00:00
|
|
|
let ecs = server.state_mut().ecs();
|
2020-10-07 02:23:20 +00:00
|
|
|
|
|
|
|
let lantern_exists = ecs
|
2020-05-04 15:15:31 +00:00
|
|
|
.read_storage::<comp::LightEmitter>()
|
|
|
|
.get(entity)
|
2020-10-07 02:23:20 +00:00
|
|
|
.map_or(false, |light| light.strength > 0.0);
|
|
|
|
|
|
|
|
if lantern_exists != enable {
|
|
|
|
if !enable {
|
|
|
|
server
|
|
|
|
.state_mut()
|
|
|
|
.ecs()
|
2020-05-04 15:15:31 +00:00
|
|
|
.write_storage::<comp::LightEmitter>()
|
2020-10-07 02:23:20 +00:00
|
|
|
.remove(entity);
|
2021-07-06 21:35:35 +00:00
|
|
|
} else if ecs // Only enable lantern if entity is alive
|
|
|
|
.read_storage::<comp::Health>()
|
|
|
|
.get(entity)
|
|
|
|
.map_or(true, |h| !h.is_dead)
|
|
|
|
{
|
2021-01-08 19:12:09 +00:00
|
|
|
let inventory_storage = ecs.read_storage::<Inventory>();
|
|
|
|
let lantern_opt = inventory_storage
|
2020-10-07 02:23:20 +00:00
|
|
|
.get(entity)
|
2021-01-08 19:12:09 +00:00
|
|
|
.and_then(|inventory| inventory.equipped(EquipSlot::Lantern))
|
2020-10-07 02:23:20 +00:00
|
|
|
.and_then(|item| {
|
|
|
|
if let comp::item::ItemKind::Lantern(l) = item.kind() {
|
|
|
|
Some(l)
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
2020-05-04 15:15:31 +00:00
|
|
|
});
|
2020-10-07 02:23:20 +00:00
|
|
|
if let Some(lantern) = lantern_opt {
|
|
|
|
let _ =
|
|
|
|
ecs.write_storage::<comp::LightEmitter>()
|
|
|
|
.insert(entity, comp::LightEmitter {
|
|
|
|
col: lantern.color(),
|
|
|
|
strength: lantern.strength(),
|
|
|
|
flicker: 0.35,
|
|
|
|
animated: true,
|
|
|
|
});
|
|
|
|
}
|
2020-05-04 15:15:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-31 20:29:50 +00:00
|
|
|
pub fn handle_npc_interaction(server: &mut Server, interactor: EcsEntity, npc_entity: EcsEntity) {
|
|
|
|
let state = server.state_mut();
|
|
|
|
if let Some(agent) = state
|
|
|
|
.ecs()
|
|
|
|
.write_storage::<comp::Agent>()
|
|
|
|
.get_mut(npc_entity)
|
|
|
|
{
|
|
|
|
if let Some(interactor_uid) = state.ecs().uid_from_entity(interactor) {
|
2021-03-29 14:47:42 +00:00
|
|
|
agent
|
|
|
|
.inbox
|
2021-05-15 19:36:27 +00:00
|
|
|
.push_back(AgentEvent::Talk(interactor_uid, Subject::Regular));
|
2021-01-31 20:29:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-09 07:34:58 +00:00
|
|
|
/// FIXME: Make mounting more robust, avoid bidirectional links.
|
2020-02-16 20:04:06 +00:00
|
|
|
pub fn handle_mount(server: &mut Server, mounter: EcsEntity, mountee: EcsEntity) {
|
|
|
|
let state = server.state_mut();
|
|
|
|
|
|
|
|
if state
|
|
|
|
.ecs()
|
|
|
|
.read_storage::<comp::Mounting>()
|
|
|
|
.get(mounter)
|
|
|
|
.is_none()
|
|
|
|
{
|
2020-07-31 05:13:31 +00:00
|
|
|
let not_mounting_yet = matches!(
|
|
|
|
state.ecs().read_storage::<comp::MountState>().get(mountee),
|
|
|
|
Some(comp::MountState::Unmounted)
|
|
|
|
);
|
2020-02-16 20:04:06 +00:00
|
|
|
|
2021-07-03 04:58:11 +00:00
|
|
|
let within_range = || {
|
|
|
|
let positions = state.ecs().read_storage::<comp::Pos>();
|
|
|
|
within_mounting_range(positions.get(mounter), positions.get(mountee))
|
2021-05-15 23:48:14 +00:00
|
|
|
};
|
2021-07-03 04:58:11 +00:00
|
|
|
let healths = state.ecs().read_storage::<comp::Health>();
|
|
|
|
let alive = |e| healths.get(e).map_or(true, |h| !h.is_dead);
|
2020-10-31 02:34:44 +00:00
|
|
|
|
2021-07-03 04:58:11 +00:00
|
|
|
if not_mounting_yet && within_range() && alive(mounter) && alive(mountee) {
|
|
|
|
let uids = state.ecs().read_storage::<Uid>();
|
|
|
|
if let (Some(mounter_uid), Some(mountee_uid)) =
|
|
|
|
(uids.get(mounter).copied(), uids.get(mountee).copied())
|
|
|
|
{
|
|
|
|
drop(uids);
|
|
|
|
drop(healths);
|
2021-04-09 07:34:58 +00:00
|
|
|
// We know the entities must exist to be able to look up their UIDs, so these
|
2021-05-15 19:36:27 +00:00
|
|
|
// are guaranteed to work; hence we can ignore possible errors here.
|
2021-04-09 07:34:58 +00:00
|
|
|
state.write_component_ignore_entity_dead(
|
|
|
|
mountee,
|
|
|
|
comp::MountState::MountedBy(mounter_uid),
|
|
|
|
);
|
|
|
|
state.write_component_ignore_entity_dead(mounter, comp::Mounting(mountee_uid));
|
2020-02-16 20:04:06 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn handle_unmount(server: &mut Server, mounter: EcsEntity) {
|
|
|
|
let state = server.state_mut();
|
|
|
|
let mountee_entity = state
|
|
|
|
.ecs()
|
|
|
|
.write_storage::<comp::Mounting>()
|
|
|
|
.get(mounter)
|
|
|
|
.and_then(|mountee| state.ecs().entity_from_uid(mountee.0.into()));
|
|
|
|
if let Some(mountee_entity) = mountee_entity {
|
|
|
|
state
|
|
|
|
.ecs()
|
|
|
|
.write_storage::<comp::MountState>()
|
|
|
|
.get_mut(mountee_entity)
|
2021-01-07 20:25:12 +00:00
|
|
|
.map(|mut ms| *ms = comp::MountState::Unmounted);
|
2020-02-16 20:04:06 +00:00
|
|
|
}
|
|
|
|
state.delete_component::<comp::Mounting>(mounter);
|
|
|
|
}
|
|
|
|
|
2021-04-09 07:34:58 +00:00
|
|
|
/// FIXME: This code is dangerous and needs to be refactored. We can't just
|
|
|
|
/// comment it out, but it needs to be fixed for a variety of reasons. Get rid
|
|
|
|
/// of this ASAP!
|
|
|
|
pub fn handle_possess(server: &mut Server, possessor_uid: Uid, possesse_uid: Uid) {
|
|
|
|
let ecs = server.state.ecs();
|
2020-02-16 20:04:06 +00:00
|
|
|
if let (Some(possessor), Some(possesse)) = (
|
|
|
|
ecs.entity_from_uid(possessor_uid.into()),
|
|
|
|
ecs.entity_from_uid(possesse_uid.into()),
|
|
|
|
) {
|
2020-04-01 15:04:04 +00:00
|
|
|
// Check that entities still exist
|
2021-07-18 17:11:46 +00:00
|
|
|
if !possessor.gen().is_alive()
|
|
|
|
|| !ecs.is_alive(possessor)
|
|
|
|
|| !possesse.gen().is_alive()
|
|
|
|
|| !ecs.is_alive(possesse)
|
2020-04-01 15:04:04 +00:00
|
|
|
{
|
|
|
|
error!(
|
|
|
|
"Error possessing! either the possessor entity or possesse entity no longer exists"
|
|
|
|
);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-02-17 02:15:45 +00:00
|
|
|
if ecs.read_storage::<Client>().get(possesse).is_some() {
|
2020-10-21 10:23:34 +00:00
|
|
|
error!("can't possess other players");
|
|
|
|
return;
|
|
|
|
}
|
2020-03-17 13:15:39 +00:00
|
|
|
|
2020-10-21 10:23:34 +00:00
|
|
|
match (|| -> Option<Result<(), specs::error::Error>> {
|
2021-02-17 02:15:45 +00:00
|
|
|
let mut clients = ecs.write_storage::<Client>();
|
2020-10-21 10:23:34 +00:00
|
|
|
let c = clients.remove(possessor)?;
|
|
|
|
clients.insert(possesse, c).ok()?;
|
2021-02-17 02:15:45 +00:00
|
|
|
let playerlist_messages = if let Some(client) = clients.get(possesse) {
|
|
|
|
client.send_fallible(ServerGeneral::SetPlayerEntity(possesse_uid));
|
|
|
|
// If a player is posessing non player, add possesse to playerlist as player and
|
|
|
|
// remove old player
|
|
|
|
if let Some(possessor_player) = ecs.read_storage::<comp::Player>().get(possessor) {
|
|
|
|
let admins = ecs.read_storage::<comp::Admin>();
|
|
|
|
let entity_possession_msg = ServerGeneral::PlayerListUpdate(
|
|
|
|
common_net::msg::server::PlayerListUpdate::Add(
|
|
|
|
possesse_uid,
|
|
|
|
common_net::msg::server::PlayerInfo {
|
|
|
|
player_alias: possessor_player.alias.clone(),
|
|
|
|
is_online: true,
|
Added non-admin moderators and timed bans.
The security model has been updated to reflect this change (for example,
moderators cannot revert a ban by an administrator). Ban history is
also now recorded in the ban file, and much more information about the
ban is stored (whitelists and administrators also have extra
information).
To support the new information without losing important information,
this commit also introduces a new migration path for editable settings
(both from legacy to the new format, and between versions). Examples
of how to do this correctly, and migrate to new versions of a settings
file, are in the settings/ subdirectory.
As part of this effort, editable settings have been revamped to
guarantee atomic saves (due to the increased amount of information in
each file), some latent bugs in networking were fixed, and server-cli
has been updated to go through StructOpt for both calls through TUI
and argv, greatly simplifying parsing logic.
2021-05-08 18:22:21 +00:00
|
|
|
is_moderator: admins.get(possessor).is_some(),
|
2021-02-17 02:15:45 +00:00
|
|
|
character: ecs.read_storage::<comp::Stats>().get(possesse).map(
|
|
|
|
|s| common_net::msg::CharacterInfo {
|
|
|
|
name: s.name.clone(),
|
|
|
|
},
|
|
|
|
),
|
|
|
|
},
|
|
|
|
),
|
|
|
|
);
|
|
|
|
let remove_old_player_msg = ServerGeneral::PlayerListUpdate(
|
|
|
|
common_net::msg::server::PlayerListUpdate::Remove(possessor_uid),
|
|
|
|
);
|
|
|
|
|
|
|
|
// Send msg to new possesse client now because it is not yet considered a player
|
|
|
|
// and will be missed by notify_players
|
|
|
|
client.send_fallible(entity_possession_msg.clone());
|
|
|
|
client.send_fallible(remove_old_player_msg.clone());
|
|
|
|
Some((remove_old_player_msg, entity_possession_msg))
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
|
|
|
drop(clients);
|
|
|
|
if let Some((remove_player, possess_entity)) = playerlist_messages {
|
|
|
|
server.state().notify_players(possess_entity);
|
|
|
|
server.state().notify_players(remove_player);
|
|
|
|
}
|
2020-10-21 10:23:34 +00:00
|
|
|
//optional entities
|
|
|
|
let mut players = ecs.write_storage::<comp::Player>();
|
2020-11-04 10:46:17 +00:00
|
|
|
let mut presence = ecs.write_storage::<Presence>();
|
2020-10-21 10:23:34 +00:00
|
|
|
let mut subscriptions = ecs.write_storage::<RegionSubscription>();
|
|
|
|
let mut admins = ecs.write_storage::<comp::Admin>();
|
|
|
|
let mut waypoints = ecs.write_storage::<comp::Waypoint>();
|
|
|
|
players
|
|
|
|
.remove(possessor)
|
|
|
|
.map(|p| players.insert(possesse, p).ok()?);
|
2020-11-04 10:46:17 +00:00
|
|
|
presence
|
|
|
|
.remove(possessor)
|
|
|
|
.map(|p| presence.insert(possesse, p).ok()?);
|
2020-10-21 10:23:34 +00:00
|
|
|
subscriptions
|
|
|
|
.remove(possessor)
|
|
|
|
.map(|s| subscriptions.insert(possesse, s).ok()?);
|
|
|
|
admins
|
|
|
|
.remove(possessor)
|
|
|
|
.map(|a| admins.insert(possesse, a).ok()?);
|
|
|
|
waypoints
|
|
|
|
.remove(possessor)
|
|
|
|
.map(|w| waypoints.insert(possesse, w).ok()?);
|
|
|
|
|
|
|
|
Some(Ok(()))
|
|
|
|
})() {
|
|
|
|
Some(Ok(())) => (),
|
|
|
|
Some(Err(e)) => {
|
|
|
|
error!(?e, ?possesse, "Error inserting component during possession");
|
|
|
|
return;
|
|
|
|
},
|
|
|
|
None => {
|
|
|
|
error!(?possessor, "Error removing component during possession");
|
|
|
|
return;
|
|
|
|
},
|
2020-02-16 20:04:06 +00:00
|
|
|
}
|
2020-10-21 10:23:34 +00:00
|
|
|
|
|
|
|
// Put possess item into loadout
|
2021-01-08 19:12:09 +00:00
|
|
|
let mut inventories = ecs.write_storage::<Inventory>();
|
|
|
|
let mut inventory = inventories
|
2020-10-21 10:23:34 +00:00
|
|
|
.entry(possesse)
|
2021-04-09 07:34:58 +00:00
|
|
|
.expect("Nobody has &mut World, so there's no way to delete an entity.")
|
2021-01-08 19:12:09 +00:00
|
|
|
.or_insert(Inventory::new_empty());
|
|
|
|
|
2021-02-19 23:45:48 +00:00
|
|
|
let debug_item = comp::Item::new_from_asset_expect("common.items.debug.admin_stick");
|
2021-01-08 19:12:09 +00:00
|
|
|
if let item::ItemKind::Tool(_) = debug_item.kind() {
|
2021-04-09 07:34:58 +00:00
|
|
|
assert!(
|
|
|
|
inventory
|
|
|
|
.swap(
|
2021-05-09 21:18:36 +00:00
|
|
|
Slot::Equip(EquipSlot::ActiveMainhand),
|
|
|
|
Slot::Equip(EquipSlot::InactiveMainhand),
|
2021-04-09 07:34:58 +00:00
|
|
|
)
|
|
|
|
.first()
|
|
|
|
.is_none(),
|
2021-05-09 21:18:36 +00:00
|
|
|
"Swapping active and inactive mainhands never results in leftover items",
|
2021-04-09 07:34:58 +00:00
|
|
|
);
|
2021-01-08 19:12:09 +00:00
|
|
|
|
2021-05-09 21:18:36 +00:00
|
|
|
inventory.replace_loadout_item(EquipSlot::ActiveMainhand, Some(debug_item));
|
2020-10-21 10:23:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Remove will of the entity
|
|
|
|
ecs.write_storage::<comp::Agent>().remove(possesse);
|
|
|
|
// Reset controller of former shell
|
|
|
|
ecs.write_storage::<comp::Controller>()
|
|
|
|
.get_mut(possessor)
|
|
|
|
.map(|c| c.reset());
|
2020-02-16 20:04:06 +00:00
|
|
|
}
|
|
|
|
}
|
2020-10-31 02:34:44 +00:00
|
|
|
|
|
|
|
fn within_mounting_range(player_position: Option<&Pos>, mount_position: Option<&Pos>) -> bool {
|
|
|
|
match (player_position, mount_position) {
|
|
|
|
(Some(ppos), Some(ipos)) => ppos.0.distance_squared(ipos.0) < MAX_MOUNT_RANGE.powi(2),
|
|
|
|
_ => false,
|
|
|
|
}
|
|
|
|
}
|
2021-03-21 16:09:16 +00:00
|
|
|
|
2021-06-09 05:14:20 +00:00
|
|
|
#[derive(Deserialize)]
|
|
|
|
struct ResourceExperienceManifest(HashMap<String, i32>);
|
|
|
|
|
|
|
|
impl assets::Asset for ResourceExperienceManifest {
|
|
|
|
type Loader = assets::RonLoader;
|
|
|
|
|
|
|
|
const EXTENSION: &'static str = "ron";
|
|
|
|
}
|
|
|
|
|
|
|
|
lazy_static! {
|
|
|
|
static ref RESOURCE_EXPERIENCE_MANIFEST: assets::AssetHandle<ResourceExperienceManifest> =
|
|
|
|
assets::AssetExt::load_expect("server.manifests.resource_experience_manifest");
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn handle_mine_block(
|
|
|
|
server: &mut Server,
|
|
|
|
entity: EcsEntity,
|
|
|
|
pos: Vec3<i32>,
|
|
|
|
tool: Option<ToolKind>,
|
|
|
|
) {
|
2021-03-21 16:09:16 +00:00
|
|
|
let state = server.state_mut();
|
|
|
|
if state.can_set_block(pos) {
|
|
|
|
let block = state.terrain().get(pos).ok().copied();
|
2021-03-21 20:38:08 +00:00
|
|
|
if let Some(block) = block.filter(|b| b.mine_tool().map_or(false, |t| Some(t) == tool)) {
|
2021-03-21 20:09:59 +00:00
|
|
|
// Drop item if one is recoverable from the block
|
2021-06-09 05:14:20 +00:00
|
|
|
if let Some(mut item) = comp::Item::try_reclaim_from_block(block) {
|
|
|
|
if let Some(mut skillset) = state
|
|
|
|
.ecs()
|
|
|
|
.write_storage::<comp::SkillSet>()
|
|
|
|
.get_mut(entity)
|
|
|
|
{
|
|
|
|
if let (Some(tool), Some(uid), Some(exp_reward)) = (
|
|
|
|
tool,
|
|
|
|
state.ecs().uid_from_entity(entity),
|
|
|
|
RESOURCE_EXPERIENCE_MANIFEST
|
|
|
|
.read()
|
|
|
|
.0
|
|
|
|
.get(item.item_definition_id()),
|
|
|
|
) {
|
|
|
|
skillset.change_experience(SkillGroupKind::Weapon(tool), *exp_reward);
|
|
|
|
state
|
|
|
|
.ecs()
|
|
|
|
.write_resource::<Vec<Outcome>>()
|
|
|
|
.push(Outcome::ExpChange {
|
|
|
|
uid,
|
|
|
|
exp: *exp_reward,
|
2021-06-09 20:03:25 +00:00
|
|
|
xp_pools: HashSet::from_iter(vec![SkillGroupKind::Weapon(tool)]),
|
2021-06-09 05:14:20 +00:00
|
|
|
});
|
|
|
|
}
|
2021-06-09 19:07:34 +00:00
|
|
|
use common::comp::skills::{MiningSkill, Skill};
|
2021-06-09 05:14:20 +00:00
|
|
|
use rand::Rng;
|
|
|
|
let mut rng = rand::thread_rng();
|
|
|
|
if item.item_definition_id().contains("mineral.ore.")
|
|
|
|
&& rng.gen_bool(
|
|
|
|
0.05 * skillset
|
2021-06-09 19:07:34 +00:00
|
|
|
.skill_level(Skill::Pick(MiningSkill::OreGain))
|
2021-06-09 05:14:20 +00:00
|
|
|
.ok()
|
|
|
|
.flatten()
|
|
|
|
.unwrap_or(0) as f64,
|
|
|
|
)
|
|
|
|
{
|
|
|
|
let _ = item.increase_amount(1);
|
|
|
|
}
|
|
|
|
if item.item_definition_id().contains("mineral.gem.")
|
|
|
|
&& rng.gen_bool(
|
|
|
|
0.05 * skillset
|
2021-06-09 19:07:34 +00:00
|
|
|
.skill_level(Skill::Pick(MiningSkill::GemGain))
|
2021-06-09 05:14:20 +00:00
|
|
|
.ok()
|
|
|
|
.flatten()
|
|
|
|
.unwrap_or(0) as f64,
|
|
|
|
)
|
|
|
|
{
|
|
|
|
let _ = item.increase_amount(1);
|
|
|
|
}
|
|
|
|
}
|
2021-03-21 16:09:16 +00:00
|
|
|
state
|
|
|
|
.create_object(Default::default(), comp::object::Body::Pouch)
|
|
|
|
.with(comp::Pos(pos.map(|e| e as f32) + Vec3::new(0.5, 0.5, 0.0)))
|
|
|
|
.with(item)
|
|
|
|
.build();
|
|
|
|
}
|
|
|
|
|
|
|
|
state.set_block(pos, block.into_vacant());
|
2021-03-21 20:09:59 +00:00
|
|
|
state
|
|
|
|
.ecs()
|
|
|
|
.write_resource::<Vec<Outcome>>()
|
|
|
|
.push(Outcome::BreakBlock {
|
|
|
|
pos,
|
|
|
|
color: block.get_color(),
|
|
|
|
});
|
2021-03-21 16:09:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-05-15 19:36:27 +00:00
|
|
|
|
|
|
|
pub fn handle_sound(server: &mut Server, sound: &Sound) {
|
|
|
|
let ecs = &server.state.ecs();
|
|
|
|
let positions = &ecs.read_storage::<comp::Pos>();
|
|
|
|
let agents = &mut ecs.write_storage::<comp::Agent>();
|
|
|
|
|
2021-06-15 16:57:52 +00:00
|
|
|
// TODO: Reduce the complexity of this problem by using spatial partitioning
|
|
|
|
// system
|
2021-05-15 19:36:27 +00:00
|
|
|
for (agent, agent_pos) in (agents, positions).join() {
|
|
|
|
// TODO: Use pathfinding for more dropoff around obstacles
|
|
|
|
let agent_dist_sqrd = agent_pos.0.distance_squared(sound.pos);
|
|
|
|
let sound_travel_dist_sqrd = (sound.vol * SOUND_TRAVEL_DIST_PER_VOLUME).powi(2);
|
|
|
|
|
|
|
|
let vol_dropoff = agent_dist_sqrd / sound_travel_dist_sqrd * sound.vol;
|
|
|
|
let propagated_sound = sound.with_new_vol(sound.vol - vol_dropoff);
|
|
|
|
|
|
|
|
let can_hear_sound = propagated_sound.vol > 0.00;
|
|
|
|
let should_hear_sound = agent_dist_sqrd < MAX_LISTEN_DIST.powi(2);
|
|
|
|
|
|
|
|
if can_hear_sound && should_hear_sound {
|
|
|
|
agent
|
|
|
|
.inbox
|
|
|
|
.push_back(AgentEvent::ServerSound(propagated_sound));
|
|
|
|
}
|
2021-06-16 16:16:05 +00:00
|
|
|
}
|
2021-06-15 16:15:58 +00:00
|
|
|
|
2021-06-16 16:16:05 +00:00
|
|
|
// Attempt to turn this sound into an outcome to be received by frontends.
|
|
|
|
if let Some(outcome) = match sound.kind {
|
|
|
|
SoundKind::Utterance(kind, body) => Some(Outcome::Utterance {
|
|
|
|
kind,
|
|
|
|
pos: sound.pos,
|
|
|
|
body,
|
|
|
|
}),
|
|
|
|
_ => None,
|
|
|
|
} {
|
|
|
|
ecs.write_resource::<Vec<Outcome>>().push(outcome);
|
2021-05-15 19:36:27 +00:00
|
|
|
}
|
|
|
|
}
|
2021-06-19 18:53:23 +00:00
|
|
|
|
|
|
|
pub fn handle_create_sprite(server: &mut Server, pos: Vec3<i32>, sprite: SpriteKind) {
|
|
|
|
let state = server.state_mut();
|
|
|
|
if state.can_set_block(pos) {
|
|
|
|
let block = state.terrain().get(pos).ok().copied();
|
|
|
|
if block.map_or(false, |b| (*b).is_air()) {
|
|
|
|
let new_block = state
|
|
|
|
.get_block(pos)
|
|
|
|
.unwrap_or_else(|| Block::air(SpriteKind::Empty))
|
|
|
|
.with_sprite(sprite);
|
|
|
|
server.state.set_block(pos, new_block);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|