2020-02-16 20:04:06 +00:00
|
|
|
use crate::{Server, StateExt};
|
|
|
|
use common::{
|
2020-03-28 05:51:52 +00:00
|
|
|
comp::{self, item, Pos, MAX_PICKUP_RANGE_SQR},
|
2020-02-16 20:04:06 +00:00
|
|
|
sync::WorldSyncExt,
|
|
|
|
terrain::block::Block,
|
|
|
|
vol::{ReadVol, Vox},
|
|
|
|
};
|
|
|
|
use log::error;
|
|
|
|
use rand::Rng;
|
|
|
|
use specs::{join::Join, world::WorldExt, Builder, Entity as EcsEntity};
|
|
|
|
use vek::Vec3;
|
|
|
|
|
|
|
|
pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::InventoryManip) {
|
|
|
|
let state = server.state_mut();
|
|
|
|
let mut dropped_items = Vec::new();
|
|
|
|
|
|
|
|
match manip {
|
|
|
|
comp::InventoryManip::Pickup(uid) => {
|
|
|
|
let item_entity = if let (Some((item, item_entity)), Some(inv)) = (
|
|
|
|
state
|
|
|
|
.ecs()
|
|
|
|
.entity_from_uid(uid.into())
|
|
|
|
.and_then(|item_entity| {
|
|
|
|
state
|
|
|
|
.ecs()
|
|
|
|
.write_storage::<comp::Item>()
|
|
|
|
.get_mut(item_entity)
|
|
|
|
.map(|item| (item.clone(), item_entity))
|
|
|
|
}),
|
|
|
|
state
|
|
|
|
.ecs()
|
|
|
|
.write_storage::<comp::Inventory>()
|
|
|
|
.get_mut(entity),
|
|
|
|
) {
|
2020-03-10 20:50:04 +00:00
|
|
|
if within_pickup_range(
|
|
|
|
state.ecs().read_storage::<comp::Pos>().get(entity),
|
|
|
|
state.ecs().read_storage::<comp::Pos>().get(item_entity),
|
|
|
|
) && inv.push(item).is_none()
|
|
|
|
{
|
2020-02-16 20:04:06 +00:00
|
|
|
Some(item_entity)
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
|
|
|
|
|
|
|
if let Some(item_entity) = item_entity {
|
|
|
|
if let Err(err) = state.delete_entity_recorded(item_entity) {
|
|
|
|
error!("Failed to delete picked up item entity: {:?}", err);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-04 10:09:48 +00:00
|
|
|
state.write_component(
|
|
|
|
entity,
|
|
|
|
comp::InventoryUpdate::new(comp::InventoryUpdateEvent::Collected),
|
|
|
|
);
|
2020-02-16 20:04:06 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
comp::InventoryManip::Collect(pos) => {
|
|
|
|
let block = state.terrain().get(pos).ok().copied();
|
2020-03-10 20:50:04 +00:00
|
|
|
|
2020-02-16 20:04:06 +00:00
|
|
|
if let Some(block) = block {
|
2020-03-11 10:30:59 +00:00
|
|
|
let has_inv_space = state
|
|
|
|
.ecs()
|
|
|
|
.read_storage::<comp::Inventory>()
|
|
|
|
.get(entity)
|
|
|
|
.map(|inv| !inv.is_full())
|
|
|
|
.unwrap_or(false);
|
|
|
|
|
|
|
|
if !has_inv_space {
|
|
|
|
state.write_component(
|
|
|
|
entity,
|
|
|
|
comp::InventoryUpdate::new(comp::InventoryUpdateEvent::CollectFailed),
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
if block.is_collectible() && state.try_set_block(pos, Block::empty()).is_some()
|
|
|
|
{
|
|
|
|
comp::Item::try_reclaim_from_block(block)
|
|
|
|
.map(|item| state.give_item(entity, item));
|
|
|
|
}
|
2020-02-16 20:04:06 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2020-02-26 17:04:43 +00:00
|
|
|
comp::InventoryManip::Use(slot_idx) => {
|
2020-02-16 20:04:06 +00:00
|
|
|
let item_opt = state
|
|
|
|
.ecs()
|
|
|
|
.write_storage::<comp::Inventory>()
|
|
|
|
.get_mut(entity)
|
2020-03-19 13:30:50 +00:00
|
|
|
.and_then(|inv| inv.take(slot_idx));
|
2020-02-16 20:04:06 +00:00
|
|
|
|
2020-03-04 10:09:48 +00:00
|
|
|
let mut event = comp::InventoryUpdateEvent::Used;
|
|
|
|
|
2020-02-16 20:04:06 +00:00
|
|
|
if let Some(item) = item_opt {
|
2020-02-26 17:04:43 +00:00
|
|
|
match &item.kind {
|
2020-03-28 05:51:52 +00:00
|
|
|
item::ItemKind::Tool(tool) => {
|
2020-02-26 17:04:43 +00:00
|
|
|
if let Some(loadout) =
|
|
|
|
state.ecs().write_storage::<comp::Loadout>().get_mut(entity)
|
|
|
|
{
|
|
|
|
// Insert old item into inventory
|
|
|
|
if let Some(old_item) = loadout.active_item.take() {
|
|
|
|
state
|
|
|
|
.ecs()
|
|
|
|
.write_storage::<comp::Inventory>()
|
|
|
|
.get_mut(entity)
|
|
|
|
.map(|inv| inv.insert(slot_idx, old_item.item));
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut abilities = tool.get_abilities();
|
|
|
|
let mut ability_drain = abilities.drain(..);
|
|
|
|
let active_item = comp::ItemConfig {
|
|
|
|
item,
|
2020-03-24 12:59:53 +00:00
|
|
|
ability1: ability_drain.next(),
|
|
|
|
ability2: ability_drain.next(),
|
|
|
|
ability3: ability_drain.next(),
|
2020-02-26 17:04:43 +00:00
|
|
|
block_ability: Some(comp::CharacterAbility::BasicBlock),
|
|
|
|
dodge_ability: Some(comp::CharacterAbility::Roll),
|
|
|
|
};
|
|
|
|
loadout.active_item = Some(active_item);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2020-03-28 05:51:52 +00:00
|
|
|
item::ItemKind::Consumable { kind, effect, .. } => {
|
2020-02-26 17:04:43 +00:00
|
|
|
event = comp::InventoryUpdateEvent::Consumed(*kind);
|
|
|
|
state.apply_effect(entity, *effect);
|
2020-02-16 20:04:06 +00:00
|
|
|
},
|
2020-02-26 17:04:43 +00:00
|
|
|
|
2020-03-28 05:51:52 +00:00
|
|
|
item::ItemKind::Armor { kind, .. } => {
|
2020-02-26 17:04:43 +00:00
|
|
|
if let Some(loadout) =
|
|
|
|
state.ecs().write_storage::<comp::Loadout>().get_mut(entity)
|
|
|
|
{
|
2020-03-28 05:51:52 +00:00
|
|
|
use comp::item::armor::Armor::*;
|
2020-03-15 18:44:47 +00:00
|
|
|
let slot = match kind.clone() {
|
|
|
|
Shoulder(_) => &mut loadout.shoulder,
|
|
|
|
Chest(_) => &mut loadout.chest,
|
|
|
|
Belt(_) => &mut loadout.belt,
|
|
|
|
Hand(_) => &mut loadout.hand,
|
|
|
|
Pants(_) => &mut loadout.pants,
|
|
|
|
Foot(_) => &mut loadout.foot,
|
2020-04-06 00:25:52 +00:00
|
|
|
Back(_) => &mut loadout.back,
|
2020-04-06 20:03:46 +00:00
|
|
|
Ring(_) => &mut loadout.back,
|
|
|
|
Neck(_) => &mut loadout.back,
|
|
|
|
Lantern(_) => &mut loadout.back,
|
|
|
|
Head(_) => &mut loadout.back,
|
|
|
|
Tabard(_) => &mut loadout.back,
|
2020-03-15 18:44:47 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
// Insert old item into inventory
|
|
|
|
if let Some(old_item) = slot.take() {
|
|
|
|
state
|
|
|
|
.ecs()
|
|
|
|
.write_storage::<comp::Inventory>()
|
|
|
|
.get_mut(entity)
|
|
|
|
.map(|inv| inv.insert(slot_idx, old_item));
|
2020-02-26 17:04:43 +00:00
|
|
|
}
|
2020-03-15 18:44:47 +00:00
|
|
|
|
|
|
|
*slot = Some(item);
|
2020-02-26 17:04:43 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2020-03-28 05:51:52 +00:00
|
|
|
item::ItemKind::Utility { kind, .. } => match kind {
|
2020-02-16 20:04:06 +00:00
|
|
|
comp::item::Utility::Collar => {
|
|
|
|
let reinsert = if let Some(pos) =
|
|
|
|
state.read_storage::<comp::Pos>().get(entity)
|
|
|
|
{
|
|
|
|
if (
|
|
|
|
&state.read_storage::<comp::Alignment>(),
|
|
|
|
&state.read_storage::<comp::Agent>(),
|
|
|
|
)
|
|
|
|
.join()
|
|
|
|
.filter(|(alignment, _)| {
|
|
|
|
alignment == &&comp::Alignment::Owned(entity)
|
|
|
|
})
|
|
|
|
.count()
|
|
|
|
>= 3
|
|
|
|
{
|
|
|
|
true
|
|
|
|
} else if let Some(tameable_entity) = {
|
|
|
|
let nearest_tameable = (
|
|
|
|
&state.ecs().entities(),
|
|
|
|
&state.ecs().read_storage::<comp::Pos>(),
|
|
|
|
&state.ecs().read_storage::<comp::Alignment>(),
|
|
|
|
)
|
|
|
|
.join()
|
|
|
|
.filter(|(_, wild_pos, _)| {
|
|
|
|
wild_pos.0.distance_squared(pos.0) < 5.0f32.powf(2.0)
|
|
|
|
})
|
|
|
|
.filter(|(_, _, alignment)| {
|
|
|
|
alignment == &&comp::Alignment::Wild
|
|
|
|
})
|
|
|
|
.min_by_key(|(_, wild_pos, _)| {
|
|
|
|
(wild_pos.0.distance_squared(pos.0) * 100.0) as i32
|
|
|
|
})
|
|
|
|
.map(|(entity, _, _)| entity);
|
|
|
|
nearest_tameable
|
|
|
|
} {
|
|
|
|
let _ = state
|
|
|
|
.ecs()
|
|
|
|
.write_storage()
|
|
|
|
.insert(tameable_entity, comp::Alignment::Owned(entity));
|
|
|
|
let _ = state
|
|
|
|
.ecs()
|
|
|
|
.write_storage()
|
|
|
|
.insert(tameable_entity, comp::Agent::default());
|
|
|
|
false
|
|
|
|
} else {
|
|
|
|
true
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
true
|
|
|
|
};
|
|
|
|
|
|
|
|
if reinsert {
|
|
|
|
let _ = state
|
|
|
|
.ecs()
|
|
|
|
.write_storage::<comp::Inventory>()
|
|
|
|
.get_mut(entity)
|
2020-02-26 17:04:43 +00:00
|
|
|
.map(|inv| inv.insert(slot_idx, item));
|
2020-02-16 20:04:06 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
},
|
|
|
|
_ => {
|
|
|
|
let _ = state
|
|
|
|
.ecs()
|
|
|
|
.write_storage::<comp::Inventory>()
|
|
|
|
.get_mut(entity)
|
2020-02-26 17:04:43 +00:00
|
|
|
.map(|inv| inv.insert(slot_idx, item));
|
2020-02-16 20:04:06 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-04 10:09:48 +00:00
|
|
|
state.write_component(entity, comp::InventoryUpdate::new(event));
|
2020-02-16 20:04:06 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
comp::InventoryManip::Swap(a, b) => {
|
|
|
|
state
|
|
|
|
.ecs()
|
|
|
|
.write_storage::<comp::Inventory>()
|
|
|
|
.get_mut(entity)
|
|
|
|
.map(|inv| inv.swap_slots(a, b));
|
2020-03-04 10:09:48 +00:00
|
|
|
state.write_component(
|
|
|
|
entity,
|
|
|
|
comp::InventoryUpdate::new(comp::InventoryUpdateEvent::Swapped),
|
|
|
|
);
|
2020-02-16 20:04:06 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
comp::InventoryManip::Drop(slot) => {
|
|
|
|
let item = state
|
|
|
|
.ecs()
|
|
|
|
.write_storage::<comp::Inventory>()
|
|
|
|
.get_mut(entity)
|
|
|
|
.and_then(|inv| inv.remove(slot));
|
|
|
|
|
|
|
|
if let (Some(item), Some(pos)) =
|
|
|
|
(item, state.ecs().read_storage::<comp::Pos>().get(entity))
|
|
|
|
{
|
|
|
|
dropped_items.push((
|
|
|
|
*pos,
|
|
|
|
state
|
|
|
|
.ecs()
|
|
|
|
.read_storage::<comp::Ori>()
|
|
|
|
.get(entity)
|
|
|
|
.copied()
|
2020-03-28 01:31:22 +00:00
|
|
|
.unwrap_or_default(),
|
2020-02-16 20:04:06 +00:00
|
|
|
item,
|
|
|
|
));
|
|
|
|
}
|
2020-03-04 10:09:48 +00:00
|
|
|
state.write_component(
|
|
|
|
entity,
|
|
|
|
comp::InventoryUpdate::new(comp::InventoryUpdateEvent::Dropped),
|
|
|
|
);
|
2020-02-16 20:04:06 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
// Drop items
|
|
|
|
for (pos, ori, item) in dropped_items {
|
2020-03-28 01:31:22 +00:00
|
|
|
let vel = *ori.0 * 5.0
|
2020-02-16 20:04:06 +00:00
|
|
|
+ Vec3::unit_z() * 10.0
|
|
|
|
+ Vec3::<f32>::zero().map(|_| rand::thread_rng().gen::<f32>() - 0.5) * 4.0;
|
|
|
|
|
2020-03-10 02:27:32 +00:00
|
|
|
state
|
2020-02-16 20:04:06 +00:00
|
|
|
.create_object(Default::default(), comp::object::Body::Pouch)
|
|
|
|
.with(comp::Pos(pos.0 + Vec3::unit_z() * 0.25))
|
|
|
|
.with(item)
|
|
|
|
.with(comp::Vel(vel))
|
|
|
|
.build();
|
|
|
|
}
|
|
|
|
}
|
2020-03-10 20:50:04 +00:00
|
|
|
|
|
|
|
fn within_pickup_range(player_position: Option<&Pos>, item_position: Option<&Pos>) -> bool {
|
|
|
|
match (player_position, item_position) {
|
|
|
|
(Some(ppos), Some(ipos)) => ppos.0.distance_squared(ipos.0) < MAX_PICKUP_RANGE_SQR,
|
|
|
|
_ => false,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
use common::comp::Pos;
|
|
|
|
use vek::Vec3;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn pickup_distance_within_range() {
|
|
|
|
let player_position = Pos(Vec3::zero());
|
|
|
|
let item_position = Pos(Vec3::one());
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
within_pickup_range(Some(&player_position), Some(&item_position)),
|
|
|
|
true
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn pickup_distance_not_within_range() {
|
|
|
|
let player_position = Pos(Vec3::zero());
|
|
|
|
let item_position = Pos(Vec3::one() * 500.0);
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
within_pickup_range(Some(&player_position), Some(&item_position)),
|
|
|
|
false
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|