Browse Source

another item refactor (plus tool usage)

pbs
jake 5 years ago
parent
commit
f7b91772b0
  1. 13
      src/entity/item/mod.rs
  2. 8
      src/entity/item/tool.rs
  3. 2
      src/ship/character.rs
  4. 856
      src/ship/items.rs
  5. 30
      src/ship/packet/builder/message.rs
  6. 6
      src/ship/packet/handler/direct_message.rs
  7. 63
      src/ship/packet/handler/message.rs
  8. 5
      src/ship/ship.rs
  9. 1
      tests/common.rs
  10. 192
      tests/test_item_pickup.rs

13
src/entity/item/mod.rs

@ -42,6 +42,7 @@ pub enum ItemLocation {
y: f32,
z: f32,
},
Consumed,
/*Destroyed {
// marks an item that has been consumed in some way
},
@ -130,6 +131,18 @@ impl ItemDetail {
_ => None,
}
}
pub fn as_client_bytes(&self) -> [u8; 16] {
match self {
ItemDetail::Weapon(w) => w.as_bytes(),
ItemDetail::Armor(a) => a.as_bytes(),
ItemDetail::Shield(s) => s.as_bytes(),
ItemDetail::Unit(u) => u.as_bytes(),
ItemDetail::Tool(t) => t.as_individual_bytes(),
ItemDetail::TechniqueDisk(d) => d.as_bytes(),
ItemDetail::Mag(m) => m.as_bytes(),
}
}
}
#[derive(Clone, Debug)]

8
src/entity/item/tool.rs

@ -642,4 +642,12 @@ impl Tool {
Err(ItemParseError::InvalidToolBytes) // TODO: error handling if wrong bytes are given
}
}
pub fn is_stackable(&self) -> bool {
self.tool.is_stackable()
}
pub fn max_stack(&self) -> usize {
self.tool.max_stack()
}
}

2
src/ship/character.rs

@ -46,7 +46,7 @@ impl<'a> CharacterBytesBuilder<'a> {
let level = self.level.unwrap();
character::Character {
name: libpso::utf8_to_utf16_array!(character.name, 16),
hp: stats.hp + character.materials.hp as u16 * 2,
hp: stats.hp,
atp: stats.atp + character.materials.power as u16 * 2,
mst: stats.mst + character.materials.mind as u16 * 2,
evp: stats.evp + character.materials.evade as u16 * 2,

856
src/ship/items.rs
File diff suppressed because it is too large
View File

30
src/ship/packet/builder/message.rs

@ -7,30 +7,30 @@ use std::convert::TryInto;
pub fn item_drop(client: u8, target: u8, item_drop: &FloorItem) -> Result<ItemDrop, ShipError> {
let item_bytes = item_drop.item.as_client_bytes();
let item_bytes = item_drop.as_client_bytes();
Ok(ItemDrop {
client: client,
target: target,
area: item_drop.map_area.area_value(),
area: item_drop.map_area().area_value(),
variety: 0,
unknown: 0,
x: item_drop.x,
z: item_drop.z,
y: item_drop.y,
x: item_drop.x(),
z: item_drop.z(),
y: item_drop.y(),
item_bytes: item_bytes[0..12].try_into()?,
item_id: item_drop.item_id.0,
item_id: item_drop.item_id().0,
item_bytes2: item_bytes[12..16].try_into()?,
unknown2: 0,
})
}
pub fn create_item(area_client: AreaClient, item: &FloorItem) -> Result<CreateItem, ShipError> {
let bytes = item.item.as_client_bytes();
let bytes = item.as_client_bytes();
Ok(CreateItem {
client: area_client.local_client.id(),
target: 0,
item_data: bytes[0..12].try_into()?,
item_id: item.item_id.0,
item_id: item.item_id().0,
item_data2: bytes[12..16].try_into()?,
unknown: 0,
})
@ -42,24 +42,24 @@ pub fn remove_item_from_floor(area_client: AreaClient, item: &FloorItem) -> Resu
target: 0,
client_id: area_client.local_client.id(),
unknown: 0,
area: item.map_area.area_value(),
area: item.map_area().area_value(),
unknown2: 0,
item_id: item.item_id.0,
item_id: item.item_id().0,
})
}
pub fn drop_split_stack(area_client: AreaClient, item: &FloorItem) -> Result<DropSplitStack, ShipError> {
let item_bytes = item.item.as_client_bytes();
let item_bytes = item.as_client_bytes();
Ok(DropSplitStack {
client: area_client.local_client.id(),
target: 0,
variety: 0,
unknown1: 0,
map_area: item.map_area.area_value(),
x: item.x,
z: item.z,
map_area: item.map_area().area_value(),
x: item.x(),
z: item.z(),
item_bytes: item_bytes[0..12].try_into()?,
item_id: item.item_id.0,
item_id: item.item_id().0,
item_bytes2: item_bytes[12..16].try_into()?,
unknown2: 0,
})

6
src/ship/packet/handler/direct_message.rs

@ -5,7 +5,7 @@ use crate::common::serverstate::ClientId;
use crate::ship::ship::{SendShipPacket, ShipError, Clients, Rooms};
use crate::ship::location::{ClientLocation, ClientLocationError};
use crate::ship::drops::ItemDrop;
use crate::ship::items::{ItemManager, FloorItemType, ClientItemId, TriggerCreateItem};
use crate::ship::items::{ItemManager, ClientItemId, TriggerCreateItem, FloorItem};
use crate::entity::gateway::EntityGateway;
use libpso::utf8_to_utf16_array;
use crate::ship::packet::builder;
@ -150,8 +150,8 @@ where
let clients_in_area = client_location.get_clients_in_room(room_id).map_err(|err| -> ClientLocationError { err.into() })?;
let item = item_manager.get_floor_item_by_id(&client.character, ClientItemId(pickup_item.item_id))?;
let remove_item = builder::message::remove_item_from_floor(area_client, &item)?;
let create_item = match item.item {
FloorItemType::Meseta(_) => None,
let create_item = match item {
FloorItem::Meseta(_) => None,
_ => Some(builder::message::create_item(area_client, &item)?),
};

63
src/ship/packet/handler/message.rs

@ -1,12 +1,14 @@
use libpso::packet::ship::*;
use libpso::packet::messages::*;
use crate::entity::gateway::EntityGateway;
use crate::entity::item::ItemDetail;
use crate::entity::item::tool::ToolType;
use crate::common::serverstate::ClientId;
use crate::common::leveltable::CharacterLevelTable;
use crate::ship::ship::{SendShipPacket, ShipError, Rooms, Clients, ItemDropLocation};
use crate::ship::location::{ClientLocation, ClientLocationError};
use crate::ship::map::{MapArea};
use crate::ship::items::{ItemManager, ClientItemId};
use crate::ship::items::{ItemManager, ClientItemId, InventoryItem};
use crate::ship::packet::builder;
pub async fn request_exp<EG: EntityGateway>(id: ClientId,
@ -210,3 +212,62 @@ where
Err(ShipError::NotEnoughMeseta(id, client.character.meseta))
}
}
pub async fn use_item<EG>(id: ClientId,
player_use_tool: &PlayerUseItem,
entity_gateway: &mut EG,
client_location: &ClientLocation,
clients: &mut Clients,
item_manager: &mut ItemManager)
-> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, ShipError>
where
EG: EntityGateway
{
let client = clients.get_mut(&id).ok_or(ShipError::ClientNotFound(id))?;
let inventory_item_used = item_manager.get_inventory_item_by_id(&client.character, ClientItemId(player_use_tool.item_id))?;
let item_used_type = item_manager.player_consumes_tool(entity_gateway, &client.character, inventory_item_used, 1).await?;
match item_used_type {
ItemDetail::Weapon(_w) => {
// something like when items are used to combine/transform them?
//_ => {}
},
ItemDetail::Tool(t) => {
match t.tool {
ToolType::PowerMaterial => {
client.character.materials.power += 1;
entity_gateway.save_character(&client.character).await;
},
ToolType::MindMaterial => {
client.character.materials.mind += 1;
entity_gateway.save_character(&client.character).await;
},
ToolType::EvadeMaterial => {
client.character.materials.evade += 1;
entity_gateway.save_character(&client.character).await;
},
ToolType::DefMaterial => {
client.character.materials.def += 1;
entity_gateway.save_character(&client.character).await;
},
ToolType::LuckMaterial => {
client.character.materials.luck += 1;
entity_gateway.save_character(&client.character).await;
},
ToolType::HpMaterial => {
client.character.materials.hp += 1;
entity_gateway.save_character(&client.character).await;
},
ToolType::TpMaterial => {
client.character.materials.tp += 1;
entity_gateway.save_character(&client.character).await;
},
_ => {}
}
}
_ => {}
}
Ok(Box::new(None.into_iter()))
}

5
src/ship/ship.rs

@ -252,7 +252,7 @@ pub struct ShipServerState<EG: EntityGateway> {
level_table: CharacterLevelTable,
name: String,
pub rooms: Rooms,
pub item_manager: items::ItemManager,
item_manager: items::ItemManager,
quests: quests::QuestList,
}
@ -293,6 +293,9 @@ impl<EG: EntityGateway> ShipServerState<EG> {
GameMessage::ChargeAttack(charge_attack) => {
handler::message::charge_attack(id, charge_attack, &mut self.clients, &mut self.entity_gateway).await
},
GameMessage::PlayerUseItem(player_use_item) => {
handler::message::use_item(id, player_use_item, &mut self.entity_gateway, &mut self.client_location, &mut self.clients, &mut self.item_manager).await
},
_ => {
let cmsg = msg.clone();
Ok(Box::new(self.client_location.get_client_neighbors(id).unwrap().into_iter()

1
tests/common.rs

@ -1,3 +1,4 @@
#[allow(dead_code)]
use std::time::SystemTime;
use elseware::common::serverstate::{ClientId, ServerState};

192
tests/test_item_pickup.rs

@ -2,7 +2,7 @@ use elseware::common::serverstate::{ClientId, ServerState};
use elseware::entity::gateway::{EntityGateway, InMemoryGateway};
use elseware::entity::item;
use elseware::ship::ship::{ShipServerState, RecvShipPacket};
use elseware::ship::items::{ClientItemId, ActiveItemEntityId, HeldItemType, FloorItemType};
use elseware::ship::items::{ClientItemId};
use libpso::packet::ship::*;
use libpso::packet::messages::*;
@ -79,11 +79,21 @@ async fn test_pick_up_item_stack_of_items_already_in_inventory() {
unknown: [0; 3]
})))).await.unwrap().for_each(drop);
let p1_inventory = ship.item_manager.get_character_inventory(&char1).unwrap();
assert!(p1_inventory.count() == 1);
let inventory_item = p1_inventory.slot(0).unwrap();
assert!(inventory_item.entity_id == ActiveItemEntityId::Stacked(vec![item::ItemEntityId(1), item::ItemEntityId(2), item::ItemEntityId(3), item::ItemEntityId(4), item::ItemEntityId(5), item::ItemEntityId(6)]));
assert!(inventory_item.item == HeldItemType::Stacked(item::tool::Tool {tool: item::tool::ToolType::Monomate}, 6));
let p1_items = entity_gateway.get_items_by_character(&char1).await;
assert!(p1_items.len() == 6);
let p1_item_ids = p1_items.iter().map(|item| {
item.id
}).collect::<Vec<_>>();
assert!(p1_item_ids == vec![item::ItemEntityId(1), item::ItemEntityId(2), item::ItemEntityId(3), item::ItemEntityId(4), item::ItemEntityId(5), item::ItemEntityId(6)]);
let all_items_are_monomates = p1_items.iter().all(|item| {
match item.item {
item::ItemDetail::Tool(tool) => tool.tool == item::tool::ToolType::Monomate,
_ => false
}
});
assert!(all_items_are_monomates);
}
#[async_std::test]
@ -136,19 +146,22 @@ async fn test_pick_up_item_stack_of_items_not_already_held() {
unknown: [0; 3]
})))).await.unwrap().for_each(drop);
let p1_inventory = ship.item_manager.get_character_inventory(&char1).unwrap();
assert!(p1_inventory.count() == 1);
let inventory_item = p1_inventory.slot(0).unwrap();
assert!(inventory_item.entity_id == ActiveItemEntityId::Stacked(vec![item::ItemEntityId(1)]));
assert!(inventory_item.item == HeldItemType::Stacked(item::tool::Tool {tool: item::tool::ToolType::Monomate}, 1));
let p1_items = entity_gateway.get_items_by_character(&char1).await;
assert!(p1_items.len() == 1);
let first_item = p1_items.get(0).unwrap();
assert!(first_item.id == item::ItemEntityId(1));
assert!(first_item.item == item::ItemDetail::Tool(item::tool::Tool {
tool: item::tool::ToolType::Monomate,
}));
}
#[async_std::test]
async fn test_pick_up_meseta_when_inventory_full() {
let mut entity_gateway = InMemoryGateway::new();
let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a").await;
let (_user2, mut char2) = new_user_character(&mut entity_gateway, "a2", "a").await;
let (user1, char1) = new_user_character(&mut entity_gateway, "a1", "a").await;
let (user2, mut char2) = new_user_character(&mut entity_gateway, "a2", "a").await;
for slot in 0..30 {
entity_gateway.create_item(
@ -207,13 +220,15 @@ async fn test_pick_up_meseta_when_inventory_full() {
unknown: [0; 3]
})))).await.unwrap().for_each(drop);
let p1_inventory = ship.item_manager.get_character_inventory(&char1).unwrap();
assert!(p1_inventory.count() == 30);
let p1_items = entity_gateway.get_items_by_character(&char1).await;
assert!(p1_items.len() == 30);
let c1 = ship.clients.get(&ClientId(1)).unwrap();
let c2 = ship.clients.get(&ClientId(2)).unwrap();
assert!(c1.character.meseta == 23);
assert!(c2.character.meseta == 277);
let characters1 = entity_gateway.get_characters_by_user(&user1).await;
let c1 = characters1.get(0).as_ref().unwrap().as_ref().unwrap();
let characters2 = entity_gateway.get_characters_by_user(&user2).await;
let c2 = characters2.get(0).as_ref().unwrap().as_ref().unwrap();
assert!(c1.meseta == 23);
assert!(c2.meseta == 277);
}
#[async_std::test]
@ -299,11 +314,17 @@ async fn test_pick_up_partial_stacked_item_when_inventory_is_otherwise_full() {
unknown: [0; 3]
})))).await.unwrap().for_each(drop);
let p1_inventory = ship.item_manager.get_character_inventory(&char1).unwrap();
assert!(p1_inventory.count() == 30);
let monomates = p1_inventory.slot(29).unwrap();
assert!(monomates.item == HeldItemType::Stacked(item::tool::Tool {tool: item::tool::ToolType::Monomate}, 2));
let p1_items = entity_gateway.get_items_by_character(&char1).await;
assert!(p1_items.len() == 31);
let monomate1 = p1_items.get(29).unwrap();
assert!(monomate1.item == item::ItemDetail::Tool(item::tool::Tool {
tool: item::tool::ToolType::Monomate,
}));
let monomate2 = p1_items.get(30).unwrap();
assert!(monomate2.item == item::ItemDetail::Tool(item::tool::Tool {
tool: item::tool::ToolType::Monomate,
}));
}
#[async_std::test]
@ -380,17 +401,29 @@ async fn test_can_not_pick_up_item_when_inventory_full() {
unknown: [0; 3]
})))).await.unwrap().for_each(drop);
let p1_inventory = ship.item_manager.get_character_inventory(&char1).unwrap();
assert!(p1_inventory.count() == 30);
let floor_item = ship.item_manager.get_floor_item_by_id(&char1, ClientItemId(0x210000)).unwrap();
assert!(floor_item.item_id == ClientItemId(0x210000));
let p1_items = entity_gateway.get_items_by_character(&char1).await;
assert!(p1_items.len() == 30);
let p2_items = entity_gateway.get_items_by_character(&char2).await;
assert!(p2_items.len() == 0);
ship.handle(ClientId(2), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::PickupItem(PickupItem {
client: 0,
target: 0,
item_id: 0x210000,
area: 0,
unknown: [0; 3]
})))).await.unwrap().for_each(drop);
let p2_items = entity_gateway.get_items_by_character(&char2).await;
assert!(p2_items.len() == 1);
}
#[async_std::test]
async fn test_can_not_drop_more_meseta_than_is_held() {
let mut entity_gateway = InMemoryGateway::new();
let (_user1, mut char1) = new_user_character(&mut entity_gateway, "a1", "a").await;
let (user1, mut char1) = new_user_character(&mut entity_gateway, "a1", "a").await;
char1.meseta = 300;
entity_gateway.save_character(&char1).await;
@ -419,9 +452,9 @@ async fn test_can_not_drop_more_meseta_than_is_held() {
})))).await;
assert!(split_attempt.is_err());
let c1 = ship.clients.get(&ClientId(1)).unwrap();
assert!(c1.character.meseta == 300);
assert!(ship.item_manager.get_floor_item_by_id(&char1, ClientItemId(0xF0000001)).is_err())
let characters1 = entity_gateway.get_characters_by_user(&user1).await;
let c1 = characters1.get(0).as_ref().unwrap().as_ref().unwrap();
assert!(c1.meseta == 300);
}
#[async_std::test]
@ -483,27 +516,28 @@ async fn test_pick_up_stack_that_would_exceed_stack_limit() {
z: 0.0,
})))).await.unwrap().for_each(drop);
ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::PickupItem(PickupItem {
let packets = ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::PickupItem(PickupItem {
client: 0,
target: 0,
item_id: 0x210000,
area: 0,
unknown: [0; 3]
})))).await.unwrap().for_each(drop);
})))).await.unwrap().collect::<Vec<_>>();
assert!(packets.len() == 0);
let p1_items = entity_gateway.get_items_by_character(&char1).await;
assert!(p1_items.len() == 6);
let p1_inventory = ship.item_manager.get_character_inventory(&char1).unwrap();
let monomates = p1_inventory.slot(0).unwrap();
assert!(monomates.item == HeldItemType::Stacked(item::tool::Tool {tool: item::tool::ToolType::Monomate}, 6));
let floor_monomates = ship.item_manager.get_floor_item_by_id(&char1, ClientItemId(0x210000)).unwrap();
assert!(floor_monomates.item == FloorItemType::Stacked(item::tool::Tool {tool: item::tool::ToolType::Monomate}, 6));
let p2_items = entity_gateway.get_items_by_character(&char2).await;
assert!(p2_items.len() == 0);
}
#[async_std::test]
async fn test_can_not_pick_up_meseta_when_full() {
let mut entity_gateway = InMemoryGateway::new();
let (_user1, mut char1) = new_user_character(&mut entity_gateway, "a1", "a").await;
let (_user2, mut char2) = new_user_character(&mut entity_gateway, "a2", "a").await;
let (user1, mut char1) = new_user_character(&mut entity_gateway, "a1", "a").await;
let (user2, mut char2) = new_user_character(&mut entity_gateway, "a2", "a").await;
char1.meseta = 999999;
entity_gateway.save_character(&char1).await;
@ -536,29 +570,30 @@ async fn test_can_not_pick_up_meseta_when_full() {
amount: 23,
})))).await.unwrap().for_each(drop);
ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::PickupItem(PickupItem {
let packets = ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::PickupItem(PickupItem {
client: 0,
target: 0,
item_id: 0xF0000001,
area: 0,
unknown: [0; 3]
})))).await.unwrap().for_each(drop);
let c1 = ship.clients.get(&ClientId(1)).unwrap();
let c2 = ship.clients.get(&ClientId(2)).unwrap();
assert!(c1.character.meseta == 999999);
assert!(c2.character.meseta == 277);
let floor_meseta = ship.item_manager.get_floor_item_by_id(&char1, ClientItemId(0xF0000001)).unwrap();
assert!(floor_meseta.item == FloorItemType::Meseta(item::Meseta(23)));
})))).await.unwrap().collect::<Vec<_>>();
println!("pkts {:?}", packets);
assert!(packets.len() == 0);
let characters1 = entity_gateway.get_characters_by_user(&user1).await;
let c1 = characters1.get(0).as_ref().unwrap().as_ref().unwrap();
let characters2 = entity_gateway.get_characters_by_user(&user2).await;
let c2 = characters2.get(0).as_ref().unwrap().as_ref().unwrap();
assert!(c1.meseta == 999999);
assert!(c2.meseta == 277);
}
#[async_std::test]
async fn test_meseta_caps_at_999999_when_trying_to_pick_up_more() {
let mut entity_gateway = InMemoryGateway::new();
let (_user1, mut char1) = new_user_character(&mut entity_gateway, "a1", "a").await;
let (_user2, mut char2) = new_user_character(&mut entity_gateway, "a2", "a").await;
let (user1, mut char1) = new_user_character(&mut entity_gateway, "a1", "a").await;
let (user2, mut char2) = new_user_character(&mut entity_gateway, "a2", "a").await;
char1.meseta = 999998;
entity_gateway.save_character(&char1).await;
@ -599,13 +634,12 @@ async fn test_meseta_caps_at_999999_when_trying_to_pick_up_more() {
unknown: [0; 3]
})))).await.unwrap().for_each(drop);
let c1 = ship.clients.get(&ClientId(1)).unwrap();
let c2 = ship.clients.get(&ClientId(2)).unwrap();
assert!(c1.character.meseta == 999999);
assert!(c2.character.meseta == 277);
let floor_meseta = ship.item_manager.get_floor_item_by_id(&char1, ClientItemId(0xF0000001));
assert!(floor_meseta.is_err());
let characters1 = entity_gateway.get_characters_by_user(&user1).await;
let c1 = characters1.get(0).as_ref().unwrap().as_ref().unwrap();
let characters2 = entity_gateway.get_characters_by_user(&user2).await;
let c2 = characters2.get(0).as_ref().unwrap().as_ref().unwrap();
assert!(c1.meseta == 999999);
assert!(c2.meseta == 277);
}
#[async_std::test]
@ -665,13 +699,35 @@ async fn test_player_drops_partial_stack_and_other_player_picks_it_up() {
unknown: [0; 3]
})))).await.unwrap().for_each(drop);
let p1_inventory = ship.item_manager.get_character_inventory(&char1).unwrap();
assert!(p1_inventory.count() == 1);
let inventory_item = p1_inventory.slot(0).unwrap();
assert!(inventory_item.item == HeldItemType::Stacked(item::tool::Tool {tool: item::tool::ToolType::Monomate}, 3));
let p1_items = entity_gateway.get_items_by_character(&char1).await;
assert!(p1_items.len() == 3);
let p1_item_ids = p1_items.iter().map(|item| {
item.id
}).collect::<Vec<_>>();
assert!(p1_item_ids == vec![item::ItemEntityId(3), item::ItemEntityId(4), item::ItemEntityId(5)]);
let all_items_are_monomates = p1_items.iter().all(|item| {
match item.item {
item::ItemDetail::Tool(tool) => tool.tool == item::tool::ToolType::Monomate,
_ => false
}
});
assert!(all_items_are_monomates);
let p2_inventory = ship.item_manager.get_character_inventory(&char2).unwrap();
assert!(p2_inventory.count() == 1);
let inventory_item = p2_inventory.slot(0).unwrap();
assert!(inventory_item.item == HeldItemType::Stacked(item::tool::Tool {tool: item::tool::ToolType::Monomate}, 2));
let p2_items = entity_gateway.get_items_by_character(&char2).await;
assert!(p2_items.len() == 2);
let p2_item_ids = p2_items.iter().map(|item| {
item.id
}).collect::<Vec<_>>();
assert!(p2_item_ids == vec![item::ItemEntityId(1), item::ItemEntityId(2)]);
let all_items_are_monomates = p1_items.iter().all(|item| {
match item.item {
item::ItemDetail::Tool(tool) => tool.tool == item::tool::ToolType::Monomate,
_ => false
}
});
assert!(all_items_are_monomates);
}
Loading…
Cancel
Save