Browse Source

add nullable item shops for faster test runtimes

pull/141/head
jake 6 months ago
parent
commit
15c0ac50ee
  1. 4
      shops/Cargo.toml
  2. 80
      shops/src/lib.rs
  3. 32
      src/ship/packet/handler/direct_message.rs
  4. 46
      src/ship/ship.rs
  5. 20
      tests/common.rs
  6. 5
      tests/test_bank.rs
  7. 49
      tests/test_shops.rs

4
shops/Cargo.toml

@ -8,6 +8,10 @@ maps = { workspace = true }
stats = { workspace = true }
entity = { workspace = true }
async-std = { workspace = true }
async-trait = { workspace = true }
futures = { workspace = true }
rand = { workspace = true }
rand_chacha = { workspace = true }
toml = { workspace = true }
serde = { workspace = true }

80
shops/src/lib.rs

@ -2,7 +2,16 @@ mod weapon;
mod tool;
mod armor;
use async_std::sync::{Arc, Mutex};
use futures::future::OptionFuture;
use std::collections::HashMap;
use entity::item::ItemDetail;
use maps::room::Difficulty;
use entity::character::SectionID;
pub use weapon::{WeaponShop, WeaponShopItem};
pub use tool::{ToolShop, ToolShopItem};
pub use armor::{ArmorShop, ArmorShopItem};
pub trait ShopItem {
fn price(&self) -> usize;
@ -10,12 +19,77 @@ pub trait ShopItem {
fn as_item(&self) -> ItemDetail;
}
#[async_trait::async_trait]
pub trait ItemShops {
async fn generate_weapon_list(&self, difficulty: Difficulty, section_id: SectionID, char_level: usize) -> Option<Vec<WeaponShopItem>>;
async fn generate_tool_list(&self, char_level: usize) -> Vec<ToolShopItem>;
async fn generate_armor_list(&self, char_level: usize) -> Vec<ArmorShopItem>;
}
#[derive(Clone)]
pub struct StandardItemShops {
weapon_shop: HashMap<(Difficulty, SectionID), Arc<Mutex<WeaponShop<rand_chacha::ChaCha20Rng>>>>,
tool_shop: Arc<Mutex<ToolShop<rand_chacha::ChaCha20Rng>>>,
armor_shop: Arc<Mutex<ArmorShop<rand_chacha::ChaCha20Rng>>>,
}
impl Default for StandardItemShops {
fn default() -> StandardItemShops {
let difficulty = [Difficulty::Normal, Difficulty::Hard, Difficulty::VeryHard, Difficulty::Ultimate];
let section_id = [SectionID::Viridia, SectionID::Greenill, SectionID::Skyly, SectionID::Bluefull, SectionID::Purplenum,
SectionID::Pinkal, SectionID::Redria, SectionID::Oran, SectionID::Yellowboze, SectionID::Whitill];
let mut weapon_shop = HashMap::new();
for d in difficulty.iter() {
for id in section_id.iter() {
weapon_shop.insert((*d, *id), Arc::new(Mutex::new(WeaponShop::new(*d, *id))));
}
}
StandardItemShops {
weapon_shop,
tool_shop: Arc::new(Mutex::new(ToolShop::default())),
armor_shop: Arc::new(Mutex::new(ArmorShop::default())),
}
}
}
#[async_trait::async_trait]
impl ItemShops for StandardItemShops {
async fn generate_weapon_list(&self, difficulty: Difficulty, section_id: SectionID, char_level: usize) -> Option<Vec<WeaponShopItem>> {
OptionFuture::from(
self.weapon_shop
.get(&(difficulty, section_id))
.map(|shop| async {
shop
.lock()
.await
.generate_weapon_list(char_level)
})).await
}
async fn generate_tool_list(&self, char_level: usize) -> Vec<ToolShopItem> {
self.tool_shop
.lock()
.await
.generate_tool_list(char_level)
}
async fn generate_armor_list(&self, char_level: usize) -> Vec<ArmorShopItem> {
self.armor_shop
.lock()
.await
.generate_armor_list(char_level)
}
}
pub enum ShopType {
Weapon,
Tool,
Armor
}
pub use weapon::{WeaponShop, WeaponShopItem};
pub use tool::{ToolShop, ToolShopItem};
pub use armor::{ArmorShop, ArmorShopItem};

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

@ -1,11 +1,12 @@
use log::warn;
use async_std::sync::Arc;
use rand::Rng;
use rand::seq::SliceRandom;
use libpso::packet::ship::*;
use libpso::packet::messages::*;
use stats::leveltable::LEVEL_TABLE;
use networking::serverstate::ClientId;
use crate::ship::ship::{SendShipPacket, ShipError, Clients, ItemShops};
use crate::ship::ship::{SendShipPacket, ShipError, Clients};
use location::ClientLocation;
use drops::ItemDrop;
use room::Rooms;
@ -14,7 +15,7 @@ use entity::gateway::EntityGateway;
use entity::item;
use libpso::utf8_to_utf16_array;
use pktbuilder as builder;
use shops::{ShopItem, ToolShopItem, ArmorShopItem};
use shops::{ItemShops, ShopItem, ToolShopItem, ArmorShopItem};
use items::state::{ItemState, ItemStateError};
use items::floor::{FloorType, FloorItemDetail};
use items::actions::TriggerCreateItem;
@ -320,45 +321,30 @@ pub async fn shop_request(id: ClientId,
client_location: &ClientLocation,
clients: &Clients,
rooms: &Rooms,
shops: &ItemShops)
shops: &Arc<Box<dyn ItemShops + Send + Sync>>)
-> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error>
{
//let client = clients.get_mut(&id).ok_or(ShipError::ClientNotFound(id))?;
let room_id = client_location.get_room(id).await?;
/*
let room = rooms.get(room_id.0)
.ok_or(ShipError::InvalidRoom(room_id.0 as u32))?
.as_ref()
.ok_or(ShipError::InvalidRoom(room_id.0 as u32))?;
*/
let difficulty = rooms.with(room_id, |room| Box::pin(async move {
room.mode.difficulty()
})).await?;
let shop_list = clients.with_mut(id, |client| {
let mut shops = shops.clone();
let shops = shops.clone();
Box::pin(async move {
let level = LEVEL_TABLE.get_level_from_exp(client.character.char_class, client.character.exp) as usize;
match shop_request.shop_type {
SHOP_OPTION_WEAPON => {
client.weapon_shop = shops.weapon_shop.get_mut(&(difficulty, client.character.section_id))
.ok_or(ShipError::ShopError)?
.lock()
client.weapon_shop = shops.generate_weapon_list(difficulty, client.character.section_id, level)
.await
.generate_weapon_list(level);
.ok_or(ShipError::ShopError)?;
Ok(builder::message::shop_list(shop_request.shop_type, &client.weapon_shop))
},
SHOP_OPTION_TOOL => {
client.tool_shop = shops.tool_shop
.lock()
.await
.generate_tool_list(level);
client.tool_shop = shops.generate_tool_list(level).await;
Ok(builder::message::shop_list(shop_request.shop_type, &client.tool_shop))
},
SHOP_OPTION_ARMOR => {
client.armor_shop = shops.armor_shop
.lock()
.await
.generate_armor_list(level);
client.armor_shop = shops.generate_armor_list(level).await;
Ok(builder::message::shop_list(shop_request.shop_type, &client.armor_shop))
},
_ => {

46
src/ship/ship.rs

@ -1,9 +1,8 @@
#![allow(dead_code, unused_must_use)]
use std::net::Ipv4Addr;
use std::collections::HashMap;
use async_std::channel;
use async_std::sync::{Arc, Mutex, RwLock};
use async_std::sync::{Arc, RwLock};
use rand::Rng;
use thiserror::Error;
@ -32,7 +31,7 @@ use maps::Holiday;
use maps::area::MapAreaError;
use maps::maps::{Maps, MapsError, generate_free_roam_maps};
use crate::ship::packet::handler;
use shops::{WeaponShop, ToolShop, ArmorShop};
use shops::{ItemShops, StandardItemShops};
use trade::TradeState;
use crate::ship::chatcommand;
use pktbuilder::quest::{QUEST_CATEGORY_MENU_ID, QUEST_SELECT_MENU_ID};
@ -294,34 +293,6 @@ impl SendServerPacket for SendShipPacket {
}
}
#[derive(Clone)]
pub struct ItemShops {
pub weapon_shop: HashMap<(Difficulty, SectionID), Arc<Mutex<WeaponShop<rand_chacha::ChaCha20Rng>>>>,
pub tool_shop: Arc<Mutex<ToolShop<rand_chacha::ChaCha20Rng>>>,
pub armor_shop: Arc<Mutex<ArmorShop<rand_chacha::ChaCha20Rng>>>,
}
impl Default for ItemShops {
fn default() -> ItemShops {
let difficulty = [Difficulty::Normal, Difficulty::Hard, Difficulty::VeryHard, Difficulty::Ultimate];
let section_id = [SectionID::Viridia, SectionID::Greenill, SectionID::Skyly, SectionID::Bluefull, SectionID::Purplenum,
SectionID::Pinkal, SectionID::Redria, SectionID::Oran, SectionID::Yellowboze, SectionID::Whitill];
let mut weapon_shop = HashMap::new();
for d in difficulty.iter() {
for id in section_id.iter() {
weapon_shop.insert((*d, *id), Arc::new(Mutex::new(WeaponShop::new(*d, *id))));
}
}
ItemShops {
weapon_shop,
tool_shop: Arc::new(Mutex::new(ToolShop::default())),
armor_shop: Arc::new(Mutex::new(ArmorShop::default())),
}
}
}
pub struct ShipServerStateBuilder<EG: EntityGateway + Clone + 'static> {
entity_gateway: Option<EG>,
@ -330,6 +301,7 @@ pub struct ShipServerStateBuilder<EG: EntityGateway + Clone + 'static> {
port: Option<u16>,
auth_token: Option<AuthToken>,
event: Option<Holiday>,
shops: Option<Box<dyn ItemShops + Send + Sync>>,
map_builder: Option<Box<dyn Fn(RoomMode, Holiday) -> Maps + Send + Sync>>,
drop_table_builder: Option<Box<dyn Fn(Episode, Difficulty, SectionID) -> Box<dyn DropTable + Send + Sync> + Send + Sync>>,
standard_quest_builder: Option<Box<dyn Fn(RoomMode) -> Result<quests::QuestList, QuestLoadError> + Send + Sync>>,
@ -346,6 +318,7 @@ impl<EG: EntityGateway + Clone + 'static> Default for ShipServerStateBuilder<EG>
port: None,
auth_token: None,
event: None,
shops: None,
map_builder: None,
drop_table_builder: None,
standard_quest_builder: None,
@ -416,6 +389,12 @@ impl<EG: EntityGateway + Clone + 'static> ShipServerStateBuilder<EG> {
self
}
#[must_use]
pub fn item_shops(mut self, item_shops: impl ItemShops + Send + Sync + 'static) -> ShipServerStateBuilder<EG> {
self.shops = Some(Box::new(item_shops));
self
}
#[must_use]
pub fn blocks(mut self, num_blocks: usize) -> ShipServerStateBuilder<EG> {
self.num_blocks = num_blocks;
@ -431,7 +410,8 @@ impl<EG: EntityGateway + Clone + 'static> ShipServerStateBuilder<EG> {
item_state: items::state::ItemState::default(),
ip: self.ip.unwrap_or_else(|| Ipv4Addr::new(127,0,0,1)),
port: self.port.unwrap_or(SHIP_PORT),
shops: ItemShops::default(),
#[allow(clippy::box_default)] // this lint leads to another which just doesn't work
shops: Arc::new(self.shops.unwrap_or(Box::new(StandardItemShops::default()))),
blocks: Blocks(blocks),
event: self.event.unwrap_or(Holiday::None),
map_builder: Arc::new(self.map_builder.unwrap_or(Box::new(generate_free_roam_maps))),
@ -474,7 +454,7 @@ pub struct ShipServerState<EG: EntityGateway + Clone + 'static> {
pub clients: Clients,
name: String,
pub(crate) item_state: items::state::ItemState,
shops: ItemShops,
shops: Arc<Box<dyn ItemShops + Send + Sync>>,
pub blocks: Blocks,
event: Holiday,

20
tests/common.rs

@ -13,6 +13,7 @@ use maps::object::MapObject;
use maps::monster::MonsterType;
use quests::{QuestList, QuestLoadError};
use drops::{DropTable, ItemDropType};
use shops::{ItemShops, WeaponShopItem, ToolShopItem, ArmorShopItem};
use entity::item;
@ -35,6 +36,23 @@ impl DropTable for NullDropTable {
}
}
struct NullItemShops;
#[async_trait::async_trait]
impl ItemShops for NullItemShops {
async fn generate_weapon_list(&self, _difficulty: Difficulty, _section_id: SectionID, _char_level: usize) -> Option<Vec<WeaponShopItem>> {
Some(Vec::new())
}
async fn generate_tool_list(&self, _char_level: usize) -> Vec<ToolShopItem> {
Vec::new()
}
async fn generate_armor_list(&self, _char_level: usize) -> Vec<ArmorShopItem> {
Vec::new()
}
}
pub fn null_drop_table_builder(_episode: Episode, _difficult: Difficulty, _section_id: SectionID) -> Box<dyn DropTable + Send + Sync> {
Box::new(NullDropTable)
}
@ -46,6 +64,7 @@ pub fn standard_ship_buildable<EG: EntityGateway + Clone>(gateway: EG) -> ShipS
.government_quest_builder(Box::new(null_quest_builder))
.drop_table_builder(Box::new(null_drop_table_builder))
.map_builder(Box::new(null_free_roam_maps))
.item_shops(NullItemShops)
}
pub fn standard_ship<EG: EntityGateway + Clone>(gateway: EG) -> ShipServerState<EG> {
@ -55,6 +74,7 @@ pub fn standard_ship<EG: EntityGateway + Clone>(gateway: EG) -> ShipServerState
.government_quest_builder(Box::new(null_quest_builder))
.drop_table_builder(Box::new(null_drop_table_builder))
.map_builder(Box::new(null_free_roam_maps))
.item_shops(NullItemShops)
.build()
}

5
tests/test_bank.rs

@ -3,6 +3,7 @@ use networking::serverstate::{ClientId, ServerState};
use entity::gateway::{EntityGateway, InMemoryGateway};
use entity::item;
use elseware::ship::ship::{ShipServerState, RecvShipPacket, SendShipPacket};
use shops::StandardItemShops;
use libpso::packet::ship::*;
use libpso::packet::messages::*;
@ -1538,7 +1539,9 @@ async fn test_withdraw_meseta_and_buy_a_few_monomates_with_it() {
entity_gateway.set_character_meseta(&char1.id, item::Meseta(100)).await.unwrap();
entity_gateway.set_bank_meseta(&char1.id, &item::BankIdentifier::Character, item::Meseta(300)).await.unwrap();
let mut ship = standard_ship(entity_gateway.clone());
let mut ship = standard_ship_buildable(entity_gateway.clone())
.item_shops(StandardItemShops::default())
.build();
log_in_char(&mut ship, ClientId(1), "a1", "a").await;
join_lobby(&mut ship, ClientId(1)).await;
create_room(&mut ship, ClientId(1), "room", "").await;

49
tests/test_shops.rs

@ -4,6 +4,7 @@ use entity::item;
use elseware::ship::ship::{ShipServerState, RecvShipPacket, SendShipPacket};
use maps::room::Difficulty;
use items::state::ItemStateError;
use shops::StandardItemShops;
use libpso::packet::ship::*;
use libpso::packet::messages::*;
@ -20,7 +21,9 @@ async fn test_player_opens_weapon_shop() {
char1.exp = 80000000;
entity_gateway.save_character(&char1).await.unwrap();
let mut ship = standard_ship(entity_gateway.clone());
let mut ship = standard_ship_buildable(entity_gateway.clone())
.item_shops(StandardItemShops::default())
.build();
log_in_char(&mut ship, ClientId(1), "a1", "a").await;
join_lobby(&mut ship, ClientId(1)).await;
create_room_with_difficulty(&mut ship, ClientId(1), "room", "", Difficulty::Ultimate).await;
@ -48,7 +51,9 @@ async fn test_player_opens_tool_shop() {
char1.exp = 80000000;
entity_gateway.save_character(&char1).await.unwrap();
let mut ship = standard_ship(entity_gateway.clone());
let mut ship = standard_ship_buildable(entity_gateway.clone())
.item_shops(StandardItemShops::default())
.build();
log_in_char(&mut ship, ClientId(1), "a1", "a").await;
join_lobby(&mut ship, ClientId(1)).await;
create_room_with_difficulty(&mut ship, ClientId(1), "room", "", Difficulty::Ultimate).await;
@ -76,7 +81,9 @@ async fn test_player_opens_armor_shop() {
char1.exp = 80000000;
entity_gateway.save_character(&char1).await.unwrap();
let mut ship = standard_ship(entity_gateway.clone());
let mut ship = standard_ship_buildable(entity_gateway.clone())
.item_shops(StandardItemShops::default())
.build();
log_in_char(&mut ship, ClientId(1), "a1", "a").await;
join_lobby(&mut ship, ClientId(1)).await;
create_room_with_difficulty(&mut ship, ClientId(1), "room", "", Difficulty::Ultimate).await;
@ -105,7 +112,9 @@ async fn test_player_buys_from_weapon_shop() {
entity_gateway.save_character(&char1).await.unwrap();
entity_gateway.set_character_meseta(&char1.id, item::Meseta(999999)).await.unwrap();
let mut ship = standard_ship(entity_gateway.clone());
let mut ship = standard_ship_buildable(entity_gateway.clone())
.item_shops(StandardItemShops::default())
.build();
log_in_char(&mut ship, ClientId(1), "a1", "a").await;
join_lobby(&mut ship, ClientId(1)).await;
create_room_with_difficulty(&mut ship, ClientId(1), "room", "", Difficulty::Ultimate).await;
@ -141,7 +150,9 @@ async fn test_player_buys_from_tool_shop() {
entity_gateway.save_character(&char1).await.unwrap();
entity_gateway.set_character_meseta(&char1.id, item::Meseta(999999)).await.unwrap();
let mut ship = standard_ship(entity_gateway.clone());
let mut ship = standard_ship_buildable(entity_gateway.clone())
.item_shops(StandardItemShops::default())
.build();
log_in_char(&mut ship, ClientId(1), "a1", "a").await;
join_lobby(&mut ship, ClientId(1)).await;
create_room_with_difficulty(&mut ship, ClientId(1), "room", "", Difficulty::Ultimate).await;
@ -176,7 +187,9 @@ async fn test_player_buys_multiple_from_tool_shop() {
entity_gateway.save_character(&char1).await.unwrap();
entity_gateway.set_character_meseta(&char1.id, item::Meseta(999999)).await.unwrap();
let mut ship = standard_ship(entity_gateway.clone());
let mut ship = standard_ship_buildable(entity_gateway.clone())
.item_shops(StandardItemShops::default())
.build();
log_in_char(&mut ship, ClientId(1), "a1", "a").await;
join_lobby(&mut ship, ClientId(1)).await;
create_room_with_difficulty(&mut ship, ClientId(1), "room", "", Difficulty::Ultimate).await;
@ -215,7 +228,9 @@ async fn test_player_buys_from_armor_shop() {
entity_gateway.save_character(&char1).await.unwrap();
entity_gateway.set_character_meseta(&char1.id, item::Meseta(999999)).await.unwrap();
let mut ship = standard_ship(entity_gateway.clone());
let mut ship = standard_ship_buildable(entity_gateway.clone())
.item_shops(StandardItemShops::default())
.build();
log_in_char(&mut ship, ClientId(1), "a1", "a").await;
join_lobby(&mut ship, ClientId(1)).await;
create_room_with_difficulty(&mut ship, ClientId(1), "room", "", Difficulty::Ultimate).await;
@ -293,7 +308,9 @@ async fn test_other_clients_see_purchase() {
entity_gateway.set_character_meseta(&char1.id, item::Meseta(999999)).await.unwrap();
entity_gateway.save_character(&char1).await.unwrap();
let mut ship = standard_ship(entity_gateway.clone());
let mut ship = standard_ship_buildable(entity_gateway.clone())
.item_shops(StandardItemShops::default())
.build();
log_in_char(&mut ship, ClientId(1), "a1", "a").await;
log_in_char(&mut ship, ClientId(2), "a2", "a").await;
join_lobby(&mut ship, ClientId(1)).await;
@ -342,7 +359,9 @@ async fn test_other_clients_see_stacked_purchase() {
),
}).await.unwrap();
let mut ship = standard_ship(entity_gateway.clone());
let mut ship = standard_ship_buildable(entity_gateway.clone())
.item_shops(StandardItemShops::default())
.build();
log_in_char(&mut ship, ClientId(1), "a1", "a").await;
log_in_char(&mut ship, ClientId(2), "a2", "a").await;
join_lobby(&mut ship, ClientId(1)).await;
@ -415,7 +434,9 @@ async fn test_player_double_buys_from_tool_shop() {
entity_gateway.save_character(&char1).await.unwrap();
entity_gateway.set_character_meseta(&char1.id, item::Meseta(999999)).await.unwrap();
let mut ship = standard_ship(entity_gateway.clone());
let mut ship = standard_ship_buildable(entity_gateway.clone())
.item_shops(StandardItemShops::default())
.build();
log_in_char(&mut ship, ClientId(1), "a1", "a").await;
join_lobby(&mut ship, ClientId(1)).await;
create_room_with_difficulty(&mut ship, ClientId(1), "room", "", Difficulty::Ultimate).await;
@ -478,7 +499,9 @@ async fn test_techs_disappear_from_shop_when_bought() {
entity_gateway.save_character(&char1).await.unwrap();
entity_gateway.set_character_meseta(&char1.id, item::Meseta(999999)).await.unwrap();
let mut ship = standard_ship(entity_gateway.clone());
let mut ship = standard_ship_buildable(entity_gateway.clone())
.item_shops(StandardItemShops::default())
.build();
log_in_char(&mut ship, ClientId(1), "a1", "a").await;
join_lobby(&mut ship, ClientId(1)).await;
create_room_with_difficulty(&mut ship, ClientId(1), "room", "", Difficulty::Ultimate).await;
@ -538,7 +561,9 @@ async fn test_units_disappear_from_shop_when_bought() {
entity_gateway.save_character(&char1).await.unwrap();
entity_gateway.set_character_meseta(&char1.id, item::Meseta(999999)).await.unwrap();
let mut ship = standard_ship(entity_gateway.clone());
let mut ship = standard_ship_buildable(entity_gateway.clone())
.item_shops(StandardItemShops::default())
.build();
log_in_char(&mut ship, ClientId(1), "a1", "a").await;
join_lobby(&mut ship, ClientId(1)).await;
create_room_with_difficulty(&mut ship, ClientId(1), "room", "", Difficulty::Ultimate).await;

Loading…
Cancel
Save