shops!
This commit is contained in:
parent
7028f91187
commit
d08db622e2
@ -46,7 +46,8 @@ pub enum ItemLocation {
|
||||
Consumed,
|
||||
FedToMag {
|
||||
mag: ItemEntityId,
|
||||
}
|
||||
},
|
||||
Shop,
|
||||
/*Destroyed {
|
||||
// marks an item that has been consumed in some way
|
||||
},
|
||||
|
@ -10,6 +10,7 @@ use crate::ship::map::MapArea;
|
||||
use crate::ship::ship::ItemDropLocation;
|
||||
use crate::ship::drops::{ItemDrop, ItemDropType};
|
||||
use crate::ship::location::{AreaClient, RoomId};
|
||||
use crate::ship::shops::ShopItem;
|
||||
|
||||
use crate::ship::items::bank::*;
|
||||
use crate::ship::items::floor::*;
|
||||
@ -39,6 +40,9 @@ pub enum ItemManagerError {
|
||||
BankFull,
|
||||
WrongItemType(ClientItemId),
|
||||
UseItemError(#[from] use_tool::UseItemError),
|
||||
CouldNotBuyItem,
|
||||
CouldNotAddBoughtItemToInventory,
|
||||
ItemIdNotInInventory(ClientItemId)
|
||||
}
|
||||
|
||||
pub struct ItemManager {
|
||||
@ -752,4 +756,107 @@ impl ItemManager {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn player_buys_item<EG: EntityGateway>(&mut self,
|
||||
entity_gateway: &mut EG,
|
||||
character: &CharacterEntity,
|
||||
shop_item: &(dyn ShopItem + Send + Sync),
|
||||
item_id: ClientItemId,
|
||||
amount: usize)
|
||||
-> Result<(&InventoryItem), ItemManagerError> {
|
||||
let inventory = self.character_inventory.get_mut(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?;
|
||||
|
||||
let item_detail = shop_item.as_item();
|
||||
let inventory_item = match item_detail {
|
||||
ItemDetail::Tool(tool) => {
|
||||
if tool.is_stackable() {
|
||||
let mut item_entities = Vec::new();
|
||||
for _ in 0..amount {
|
||||
item_entities.push(entity_gateway.create_item(NewItemEntity {
|
||||
location: ItemLocation::Shop,
|
||||
item: ItemDetail::Tool(tool),
|
||||
}).await.ok_or(ItemManagerError::EntityGatewayError)?);
|
||||
}
|
||||
let floor_item = StackedFloorItem {
|
||||
entity_ids: item_entities.into_iter().map(|i| i.id).collect(),
|
||||
item_id: item_id,
|
||||
tool: tool,
|
||||
// TODO: this is gonna choke if I ever require the item being near the player for pickup
|
||||
map_area: MapArea::Pioneer2Ep1,
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.0,
|
||||
};
|
||||
let item_id = {
|
||||
let (picked_up_item, slot) = inventory.pick_up_stacked_floor_item(&floor_item).ok_or(ItemManagerError::CouldNotAddBoughtItemToInventory)?;
|
||||
for entity_id in &picked_up_item.entity_ids {
|
||||
entity_gateway.change_item_location(entity_id,
|
||||
ItemLocation::Inventory {
|
||||
character_id: character.id,
|
||||
slot: slot.0,
|
||||
equipped: false,
|
||||
}).await;//.ok_or(ItemManagerError::EntityGatewayError)?;
|
||||
}
|
||||
picked_up_item.item_id
|
||||
};
|
||||
inventory.get_item_by_id(item_id).ok_or(ItemManagerError::ItemIdNotInInventory(item_id))?
|
||||
}
|
||||
else {
|
||||
let item_entity = entity_gateway.create_item(NewItemEntity {
|
||||
location: ItemLocation::Shop,
|
||||
item: ItemDetail::Tool(tool),
|
||||
}).await.ok_or(ItemManagerError::EntityGatewayError)?;
|
||||
let floor_item = IndividualFloorItem {
|
||||
entity_id: item_entity.id,
|
||||
item_id: item_id,
|
||||
item: ItemDetail::Tool(tool),
|
||||
// TODO: this is gonna choke if I ever require the item being near the player for pickup
|
||||
map_area: MapArea::Pioneer2Ep1,
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.0,
|
||||
};
|
||||
let item_id = {
|
||||
let (picked_up_item, slot) = inventory.pick_up_individual_floor_item(&floor_item).ok_or(ItemManagerError::CouldNotAddBoughtItemToInventory)?;
|
||||
entity_gateway.change_item_location(&picked_up_item.entity_id,
|
||||
ItemLocation::Inventory {
|
||||
character_id: character.id,
|
||||
slot: slot.0,
|
||||
equipped: false,
|
||||
}).await;//.ok_or(ItemManagerError::EntityGatewayError)?;
|
||||
picked_up_item.item_id
|
||||
};
|
||||
inventory.get_item_by_id(item_id).ok_or(ItemManagerError::ItemIdNotInInventory(item_id))?
|
||||
}
|
||||
},
|
||||
item_detail @ _ => {
|
||||
let item_entity = entity_gateway.create_item(NewItemEntity {
|
||||
location: ItemLocation::Shop,
|
||||
item: item_detail.clone(),
|
||||
}).await.ok_or(ItemManagerError::EntityGatewayError)?;
|
||||
let floor_item = IndividualFloorItem {
|
||||
entity_id: item_entity.id,
|
||||
item_id: item_id,
|
||||
item: item_detail,
|
||||
// TODO: this is gonna choke if I ever require the item being near the player for pickup
|
||||
map_area: MapArea::Pioneer2Ep1,
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.0,
|
||||
};
|
||||
let item_id = {
|
||||
let (picked_up_item, slot) = inventory.pick_up_individual_floor_item(&floor_item).ok_or(ItemManagerError::CouldNotAddBoughtItemToInventory)?;
|
||||
entity_gateway.change_item_location(&picked_up_item.entity_id,
|
||||
ItemLocation::Inventory {
|
||||
character_id: character.id,
|
||||
slot: slot.0,
|
||||
equipped: false,
|
||||
}).await;//.ok_or(ItemManagerError::EntityGatewayError)?;
|
||||
picked_up_item.item_id
|
||||
};
|
||||
inventory.get_item_by_id(item_id).ok_or(ItemManagerError::ItemIdNotInInventory(item_id))?
|
||||
},
|
||||
};
|
||||
Ok(inventory_item)
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ use crate::ship::ship::{ShipError};
|
||||
use crate::ship::items::{ClientItemId, InventoryItem, StackedFloorItem, FloorItem, CharacterBank};
|
||||
use crate::ship::location::AreaClient;
|
||||
use std::convert::TryInto;
|
||||
use crate::ship::shops::ShopItem;
|
||||
|
||||
|
||||
pub fn item_drop(client: u8, target: u8, item_drop: &FloorItem) -> Result<ItemDrop, ShipError> {
|
||||
@ -139,3 +140,25 @@ pub fn player_no_longer_has_item(area_client: AreaClient, item_id: ClientItemId,
|
||||
amount: amount,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn shop_list<I: ShopItem>(shop_type: u8, items: &Vec<I>) -> ShopList {
|
||||
let items = items.into_iter()
|
||||
.enumerate()
|
||||
.map(|(i, item)| {
|
||||
ShopListItem {
|
||||
item_bytes: item.as_bytes(),
|
||||
unknown: i as u32 + 23,
|
||||
price: item.price() as u32,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
ShopList {
|
||||
client: 0,
|
||||
target: 0,
|
||||
shop_type: shop_type,
|
||||
num_items: 0,
|
||||
unused: 0,
|
||||
items: items,
|
||||
}
|
||||
}
|
||||
|
@ -1,17 +1,24 @@
|
||||
use log::warn;
|
||||
use libpso::packet::ship::*;
|
||||
use libpso::packet::messages::*;
|
||||
use crate::common::leveltable::CharacterLevelTable;
|
||||
use crate::common::serverstate::ClientId;
|
||||
use crate::ship::ship::{SendShipPacket, ShipError, Clients, Rooms};
|
||||
use crate::ship::ship::{SendShipPacket, ShipError, Clients, Rooms, ItemShops};
|
||||
use crate::ship::location::{ClientLocation, ClientLocationError};
|
||||
use crate::ship::drops::ItemDrop;
|
||||
use crate::ship::items::{ItemManager, ClientItemId, TriggerCreateItem, FloorItem};
|
||||
use crate::entity::gateway::EntityGateway;
|
||||
use libpso::utf8_to_utf16_array;
|
||||
use crate::ship::packet::builder;
|
||||
use crate::ship::shops::ShopItem;
|
||||
|
||||
const BANK_ACTION_DEPOSIT: u8 = 0;
|
||||
const BANK_ACTION_WITHDRAW: u8 = 1;
|
||||
|
||||
const SHOP_OPTION_TOOL: u8 = 0;
|
||||
const SHOP_OPTION_WEAPON: u8 = 1;
|
||||
const SHOP_OPTION_ARMOR: u8 = 2;
|
||||
|
||||
//const BANK_ACTION_: u8 = 1;
|
||||
|
||||
fn send_to_client(id: ClientId, target: u8, msg: DirectMessage, client_location: &ClientLocation)
|
||||
@ -269,3 +276,90 @@ where
|
||||
.flatten()
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn shop_request(id: ClientId,
|
||||
shop_request: &ShopRequest,
|
||||
client_location: &ClientLocation,
|
||||
clients: &mut Clients,
|
||||
rooms: &Rooms,
|
||||
level_table: &CharacterLevelTable,
|
||||
shops: &mut ItemShops)
|
||||
-> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, ShipError>
|
||||
{
|
||||
let client = clients.get_mut(&id).ok_or(ShipError::ClientNotFound(id))?;
|
||||
let room_id = client_location.get_room(id).map_err(|err| -> ClientLocationError { err.into() })?;
|
||||
let room = rooms.get(room_id.0)
|
||||
.ok_or_else(|| ShipError::InvalidRoom(room_id.0 as u32))?
|
||||
.as_ref()
|
||||
.ok_or_else(|| ShipError::InvalidRoom(room_id.0 as u32))?;
|
||||
let level = level_table.get_level_from_exp(client.character.char_class, client.character.exp) as usize;
|
||||
let shop_list = match shop_request.shop_type {
|
||||
SHOP_OPTION_WEAPON => {
|
||||
client.weapon_shop = shops.weapon_shop.get_mut(&(room.mode.difficulty(), client.character.section_id))
|
||||
.ok_or(ShipError::ShopError)?
|
||||
.generate_weapon_list(level);
|
||||
builder::message::shop_list(shop_request.shop_type, &client.weapon_shop)
|
||||
},
|
||||
SHOP_OPTION_TOOL => {
|
||||
client.tool_shop = shops.tool_shop.generate_tool_list(level);
|
||||
builder::message::shop_list(shop_request.shop_type, &client.tool_shop)
|
||||
},
|
||||
SHOP_OPTION_ARMOR => {
|
||||
client.armor_shop = shops.armor_shop.generate_armor_list(level);
|
||||
builder::message::shop_list(shop_request.shop_type, &client.armor_shop)
|
||||
},
|
||||
_ => {
|
||||
return Err(ShipError::ShopError)
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Box::new(vec![(id, SendShipPacket::Message(Message::new(GameMessage::ShopList(shop_list))))].into_iter()))
|
||||
}
|
||||
|
||||
|
||||
pub async fn buy_item<EG>(id: ClientId,
|
||||
buy_item: &BuyItem,
|
||||
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 area_client = client_location.get_local_client(id).map_err(|err| -> ClientLocationError { err.into() })?;
|
||||
|
||||
let item: &(dyn ShopItem + Send + Sync) = match buy_item.shop_type {
|
||||
SHOP_OPTION_WEAPON => {
|
||||
client.weapon_shop.get(buy_item.shop_index as usize).ok_or(ShipError::ShopError)?
|
||||
},
|
||||
SHOP_OPTION_TOOL => {
|
||||
client.tool_shop.get(buy_item.shop_index as usize).ok_or(ShipError::ShopError)?
|
||||
},
|
||||
SHOP_OPTION_ARMOR => {
|
||||
client.armor_shop.get(buy_item.shop_index as usize).ok_or(ShipError::ShopError)?
|
||||
},
|
||||
_ => {
|
||||
return Err(ShipError::ShopError)
|
||||
}
|
||||
};
|
||||
|
||||
if client.character.meseta < item.price() as u32{
|
||||
return Err(ShipError::ShopError)
|
||||
}
|
||||
|
||||
client.character.meseta -= item.price() as u32;
|
||||
entity_gateway.save_character(&client.character).await;
|
||||
|
||||
let inventory_item = item_manager.player_buys_item(entity_gateway, &client.character, item, ClientItemId(buy_item.item_id), buy_item.amount as usize).await?;
|
||||
|
||||
let create = builder::message::create_withdrawn_inventory_item(area_client, inventory_item)?;
|
||||
|
||||
let other_clients_in_area = client_location.get_client_neighbors(id).map_err(|err| -> ClientLocationError { err.into() })?;
|
||||
Ok(Box::new(other_clients_in_area.into_iter()
|
||||
.map(move |c| {
|
||||
(c.client, SendShipPacket::Message(Message::new(GameMessage::CreateItem(create.clone()))))
|
||||
})))
|
||||
|
||||
}
|
||||
|
@ -60,7 +60,7 @@ impl Episode {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, derive_more::Display)]
|
||||
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, derive_more::Display)]
|
||||
pub enum Difficulty {
|
||||
Normal,
|
||||
Hard,
|
||||
|
@ -20,7 +20,7 @@ use crate::common::interserver::{AuthToken, Ship, ServerId, InterserverActor, Lo
|
||||
|
||||
use crate::entity::gateway::EntityGateway;
|
||||
use crate::entity::account::{UserAccountEntity, UserSettingsEntity};
|
||||
use crate::entity::character::CharacterEntity;
|
||||
use crate::entity::character::{CharacterEntity, SectionID};
|
||||
|
||||
use crate::ship::location::{ClientLocation, RoomLobby, MAX_ROOMS, ClientLocationError};
|
||||
|
||||
@ -29,6 +29,7 @@ use crate::ship::room;
|
||||
use crate::ship::quests;
|
||||
use crate::ship::map::{MapsError, MapAreaError, MapArea};
|
||||
use crate::ship::packet::handler;
|
||||
use crate::ship::shops::{ShopType, WeaponShop, ToolShop, ArmorShop, WeaponShopItem, ToolShopItem, ArmorShopItem};
|
||||
|
||||
pub const SHIP_PORT: u16 = 23423;
|
||||
pub const QUEST_CATEGORY_MENU_ID: u32 = 0xA2;
|
||||
@ -60,6 +61,7 @@ pub enum ShipError {
|
||||
InvalidQuestFilename(String),
|
||||
IoError(#[from] std::io::Error),
|
||||
NotEnoughMeseta(ClientId, u32),
|
||||
ShopError,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -228,6 +230,9 @@ pub struct ClientState {
|
||||
pub x: f32,
|
||||
pub y: f32,
|
||||
pub z: f32,
|
||||
pub weapon_shop: Vec<WeaponShopItem>,
|
||||
pub tool_shop: Vec<ToolShopItem>,
|
||||
pub armor_shop: Vec<ArmorShopItem>,
|
||||
}
|
||||
|
||||
impl ClientState {
|
||||
@ -244,10 +249,41 @@ impl ClientState {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.0,
|
||||
weapon_shop: Vec::new(),
|
||||
tool_shop: Vec::new(),
|
||||
armor_shop: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ItemShops {
|
||||
pub weapon_shop: HashMap<(room::Difficulty, SectionID), WeaponShop<rand_chacha::ChaCha20Rng>>,
|
||||
pub tool_shop: ToolShop<rand_chacha::ChaCha20Rng>,
|
||||
pub armor_shop: ArmorShop<rand_chacha::ChaCha20Rng>,
|
||||
}
|
||||
|
||||
impl ItemShops {
|
||||
pub fn new() -> ItemShops {
|
||||
let difficulty = [room::Difficulty::Normal, room::Difficulty::Hard, room::Difficulty::VeryHard, room::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), WeaponShop::new(*d, *id));
|
||||
}
|
||||
}
|
||||
|
||||
ItemShops {
|
||||
weapon_shop: weapon_shop,
|
||||
tool_shop: ToolShop::new(),
|
||||
armor_shop: ArmorShop::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub struct ShipServerStateBuilder<EG: EntityGateway> {
|
||||
entity_gateway: Option<EG>,
|
||||
name: Option<String>,
|
||||
@ -297,6 +333,7 @@ impl<EG: EntityGateway> ShipServerStateBuilder<EG> {
|
||||
quests: quests::load_quests("data/quests.toml".into()).unwrap(),
|
||||
ip: self.ip.unwrap_or(Ipv4Addr::new(127,0,0,1)),
|
||||
port: self.port.unwrap_or(SHIP_PORT),
|
||||
shops: Box::new(ItemShops::new()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -312,6 +349,7 @@ pub struct ShipServerState<EG: EntityGateway> {
|
||||
quests: quests::QuestList,
|
||||
ip: Ipv4Addr,
|
||||
port: u16,
|
||||
shops: Box<ItemShops>,
|
||||
}
|
||||
|
||||
impl<EG: EntityGateway> ShipServerState<EG> {
|
||||
@ -382,6 +420,12 @@ impl<EG: EntityGateway> ShipServerState<EG> {
|
||||
GameMessage::BankInteraction(bank_interaction) => {
|
||||
handler::direct_message::bank_interaction(id, bank_interaction, &mut self.entity_gateway, &self.client_location, &mut self.clients, &mut self.item_manager).await
|
||||
},
|
||||
GameMessage::ShopRequest(shop_request) => {
|
||||
handler::direct_message::shop_request(id, shop_request, &self.client_location, &mut self.clients, &self.rooms, &self.level_table, &mut self.shops).await
|
||||
},
|
||||
GameMessage::BuyItem(buy_item) => {
|
||||
handler::direct_message::buy_item(id, buy_item, &mut self.entity_gateway, &self.client_location, &mut self.clients, &mut self.item_manager).await
|
||||
},
|
||||
_ => {
|
||||
let cmsg = msg.clone();
|
||||
Ok(Box::new(self.client_location.get_all_clients_by_client(id).unwrap().into_iter()
|
||||
|
@ -2,20 +2,22 @@ use std::collections::HashMap;
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::path::PathBuf;
|
||||
use std::convert::TryInto;
|
||||
use serde::Deserialize;
|
||||
use rand::{Rng, SeedableRng};
|
||||
use rand::distributions::{WeightedIndex, Distribution};
|
||||
use rand::seq::{SliceRandom, IteratorRandom};
|
||||
use crate::entity::character::SectionID;
|
||||
use crate::ship::room::Difficulty;
|
||||
use crate::entity::item::armor::ArmorType;
|
||||
use crate::entity::item::shield::ShieldType;
|
||||
use crate::entity::item::unit::UnitType;
|
||||
use crate::entity::item::ItemDetail;
|
||||
use crate::entity::item::armor::{Armor, ArmorType};
|
||||
use crate::entity::item::shield::{Shield, ShieldType};
|
||||
use crate::entity::item::unit::{Unit, UnitType};
|
||||
use crate::ship::shops::ShopItem;
|
||||
use crate::ship::item_stats::{ARMOR_STATS, SHIELD_STATS, UNIT_STATS};
|
||||
|
||||
#[derive(Debug)]
|
||||
enum ShopArmor {
|
||||
pub enum ArmorShopItem {
|
||||
Frame(ArmorType, usize),
|
||||
Barrier(ShieldType),
|
||||
Unit(UnitType),
|
||||
@ -25,10 +27,10 @@ const ARMOR_MULTIPLIER: f32 = 0.799999952;
|
||||
const SHIELD_MULTIPLIER: f32 = 1.5;
|
||||
const UNIT_MULTIPLIER: f32 = 1000.0;
|
||||
|
||||
impl ShopItem for ShopArmor {
|
||||
impl ShopItem for ArmorShopItem {
|
||||
fn price(&self) -> usize {
|
||||
match self {
|
||||
ShopArmor::Frame(frame, slot) => {
|
||||
ArmorShopItem::Frame(frame, slot) => {
|
||||
ARMOR_STATS.get(&frame)
|
||||
.map(|frame_stats| {
|
||||
let mut price = (frame_stats.dfp + frame_stats.evp) as f32;
|
||||
@ -40,7 +42,7 @@ impl ShopItem for ShopArmor {
|
||||
})
|
||||
.unwrap_or(0xFFFF)
|
||||
},
|
||||
ShopArmor::Barrier(barrier) => {
|
||||
ArmorShopItem::Barrier(barrier) => {
|
||||
SHIELD_STATS.get(&barrier)
|
||||
.map(|barrier_stats| {
|
||||
let mut price = (barrier_stats.dfp + barrier_stats.evp) as f32;
|
||||
@ -51,7 +53,7 @@ impl ShopItem for ShopArmor {
|
||||
})
|
||||
.unwrap_or(0xFFFF)
|
||||
},
|
||||
ShopArmor::Unit(unit) => {
|
||||
ArmorShopItem::Unit(unit) => {
|
||||
UNIT_STATS.get(&unit)
|
||||
.map(|unit_stats| {
|
||||
(unit_stats.stars as f32 * UNIT_MULTIPLIER) as usize
|
||||
@ -60,6 +62,37 @@ impl ShopItem for ShopArmor {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn as_bytes(&self) -> [u8; 12] {
|
||||
self.as_item().as_client_bytes()[0..12].try_into().unwrap()
|
||||
}
|
||||
|
||||
fn as_item(&self) -> ItemDetail {
|
||||
match self {
|
||||
ArmorShopItem::Frame(frame, slot) => {
|
||||
ItemDetail::Armor(Armor {
|
||||
armor: *frame,
|
||||
dfp: 0,
|
||||
evp: 0,
|
||||
slots: *slot as u8,
|
||||
modifiers: Vec::new()
|
||||
})
|
||||
},
|
||||
ArmorShopItem::Barrier(barrier) => {
|
||||
ItemDetail::Shield(Shield {
|
||||
shield: *barrier,
|
||||
dfp: 0,
|
||||
evp: 0,
|
||||
})
|
||||
},
|
||||
ArmorShopItem::Unit(unit) => {
|
||||
ItemDetail::Unit(Unit {
|
||||
unit: *unit,
|
||||
modifier: None,
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -201,7 +234,7 @@ fn number_of_units_to_generate(character_level: usize) -> usize {
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ArmorShop<R: Rng + SeedableRng> {
|
||||
pub struct ArmorShop<R: Rng + SeedableRng> {
|
||||
frame: FrameTable,
|
||||
barrier: BarrierTable,
|
||||
unit: UnitTable,
|
||||
@ -218,7 +251,7 @@ impl<R: Rng + SeedableRng> ArmorShop<R> {
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_frame_list(&mut self, character_level: usize) -> Vec<ShopArmor> {
|
||||
fn generate_frame_list(&mut self, character_level: usize) -> Vec<ArmorShopItem> {
|
||||
let tier = self.frame.frame.iter()
|
||||
.filter(|t| t.level <= character_level)
|
||||
.last()
|
||||
@ -231,13 +264,13 @@ impl<R: Rng + SeedableRng> ArmorShop<R> {
|
||||
.map(|_| {
|
||||
let frame_detail = tier.item.get(frame_choice.sample(&mut self.rng)).unwrap();
|
||||
let slot = self.frame.slot_rate.get(slot_choice.sample(&mut self.rng)).unwrap();
|
||||
|
||||
ShopArmor::Frame(frame_detail.item, slot.slot)
|
||||
|
||||
ArmorShopItem::Frame(frame_detail.item, slot.slot)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn generate_barrier_list(&mut self, character_level: usize) -> Vec<ShopArmor> {
|
||||
fn generate_barrier_list(&mut self, character_level: usize) -> Vec<ArmorShopItem> {
|
||||
let tier = self.barrier.barrier.iter()
|
||||
.filter(|t| t.level <= character_level)
|
||||
.last()
|
||||
@ -248,13 +281,13 @@ impl<R: Rng + SeedableRng> ArmorShop<R> {
|
||||
(0..number_of_barriers_to_generate(character_level))
|
||||
.map(|_| {
|
||||
let barrier_detail = tier.item.get(barrier_choice.sample(&mut self.rng)).unwrap();
|
||||
|
||||
ShopArmor::Barrier(barrier_detail.item)
|
||||
|
||||
ArmorShopItem::Barrier(barrier_detail.item)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn generate_unit_list(&mut self, character_level: usize) -> Vec<ShopArmor> {
|
||||
fn generate_unit_list(&mut self, character_level: usize) -> Vec<ArmorShopItem> {
|
||||
self.unit.unit.iter()
|
||||
.filter(|t| t.level <= character_level)
|
||||
.last()
|
||||
@ -264,15 +297,15 @@ impl<R: Rng + SeedableRng> ArmorShop<R> {
|
||||
(0..number_of_units_to_generate(character_level))
|
||||
.map(|_| {
|
||||
let unit_detail = tier.item.get(unit_choice.sample(&mut self.rng)).unwrap();
|
||||
|
||||
ShopArmor::Unit(unit_detail.item)
|
||||
|
||||
ArmorShopItem::Unit(unit_detail.item)
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or(Vec::new())
|
||||
}
|
||||
|
||||
pub fn generate_armor_list(&mut self, character_level: usize) -> Vec<ShopArmor> {
|
||||
pub fn generate_armor_list(&mut self, character_level: usize) -> Vec<ArmorShopItem> {
|
||||
self.generate_frame_list(character_level).into_iter()
|
||||
.chain(self.generate_barrier_list(character_level).into_iter())
|
||||
.chain(self.generate_unit_list(character_level).into_iter())
|
||||
|
@ -1,7 +1,21 @@
|
||||
pub mod weapon;
|
||||
pub mod tool;
|
||||
pub mod armor;
|
||||
mod weapon;
|
||||
mod tool;
|
||||
mod armor;
|
||||
|
||||
use crate::entity::item::ItemDetail;
|
||||
|
||||
pub trait ShopItem {
|
||||
fn price(&self) -> usize;
|
||||
fn as_bytes(&self) -> [u8; 12];
|
||||
fn as_item(&self) -> ItemDetail;
|
||||
}
|
||||
|
||||
pub enum ShopType {
|
||||
Weapon,
|
||||
Tool,
|
||||
Armor
|
||||
}
|
||||
|
||||
pub use weapon::{WeaponShop, WeaponShopItem};
|
||||
pub use tool::{ToolShop, ToolShopItem};
|
||||
pub use armor::{ArmorShop, ArmorShopItem};
|
||||
|
@ -2,12 +2,14 @@ use std::collections::HashMap;
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::path::PathBuf;
|
||||
use std::convert::TryInto;
|
||||
use serde::Deserialize;
|
||||
use rand::{Rng, SeedableRng};
|
||||
use rand::distributions::{WeightedIndex, Distribution};
|
||||
use rand::seq::{SliceRandom, IteratorRandom};
|
||||
use crate::entity::character::SectionID;
|
||||
use crate::ship::room::Difficulty;
|
||||
use crate::entity::item::ItemDetail;
|
||||
use crate::entity::item::tool::{Tool, ToolType};
|
||||
use crate::entity::item::tech::{Technique, TechniqueDisk};
|
||||
use crate::ship::shops::ShopItem;
|
||||
@ -15,52 +17,52 @@ use crate::ship::item_stats::{TOOL_STATS, TECH_STATS};
|
||||
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum ShopTool {
|
||||
pub enum ToolShopItem {
|
||||
Tool(ToolType),
|
||||
Tech(TechniqueDisk),
|
||||
}
|
||||
|
||||
impl Ord for ShopTool {
|
||||
fn cmp(&self, other: &ShopTool) -> std::cmp::Ordering {
|
||||
impl Ord for ToolShopItem {
|
||||
fn cmp(&self, other: &ToolShopItem) -> std::cmp::Ordering {
|
||||
let a = match self {
|
||||
ShopTool::Tool(t) => Tool{tool : *t}.as_individual_bytes(),
|
||||
ShopTool::Tech(t) => t.as_bytes(),
|
||||
ToolShopItem::Tool(t) => Tool{tool : *t}.as_individual_bytes(),
|
||||
ToolShopItem::Tech(t) => t.as_bytes(),
|
||||
};
|
||||
let b = match other {
|
||||
ShopTool::Tool(t) => Tool{tool : *t}.as_individual_bytes(),
|
||||
ShopTool::Tech(t) => t.as_bytes(),
|
||||
ToolShopItem::Tool(t) => Tool{tool : *t}.as_individual_bytes(),
|
||||
ToolShopItem::Tech(t) => t.as_bytes(),
|
||||
};
|
||||
|
||||
a.cmp(&b)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for ShopTool {
|
||||
fn partial_cmp(&self, other: &ShopTool) -> Option<std::cmp::Ordering> {
|
||||
impl PartialOrd for ToolShopItem {
|
||||
fn partial_cmp(&self, other: &ToolShopItem) -> Option<std::cmp::Ordering> {
|
||||
let a = match self {
|
||||
ShopTool::Tool(t) => Tool{tool : *t}.as_individual_bytes(),
|
||||
ShopTool::Tech(t) => t.as_bytes(),
|
||||
ToolShopItem::Tool(t) => Tool{tool : *t}.as_individual_bytes(),
|
||||
ToolShopItem::Tech(t) => t.as_bytes(),
|
||||
};
|
||||
let b = match other {
|
||||
ShopTool::Tool(t) => Tool{tool : *t}.as_individual_bytes(),
|
||||
ShopTool::Tech(t) => t.as_bytes(),
|
||||
ToolShopItem::Tool(t) => Tool{tool : *t}.as_individual_bytes(),
|
||||
ToolShopItem::Tech(t) => t.as_bytes(),
|
||||
};
|
||||
|
||||
a.partial_cmp(&b)
|
||||
}
|
||||
}
|
||||
|
||||
impl ShopItem for ShopTool {
|
||||
impl ShopItem for ToolShopItem {
|
||||
fn price(&self) -> usize {
|
||||
match self {
|
||||
ShopTool::Tool(tool) => {
|
||||
ToolShopItem::Tool(tool) => {
|
||||
TOOL_STATS.get(&tool)
|
||||
.map(|tool_stats| {
|
||||
tool_stats.price
|
||||
})
|
||||
.unwrap_or(0xFFFF)
|
||||
},
|
||||
ShopTool::Tech(tech) => {
|
||||
ToolShopItem::Tech(tech) => {
|
||||
TECH_STATS.get(&tech.tech)
|
||||
.map(|tech_stats| {
|
||||
tech_stats.price * tech.level as usize
|
||||
@ -69,6 +71,32 @@ impl ShopItem for ShopTool {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn as_bytes(&self) -> [u8; 12] {
|
||||
match self {
|
||||
ToolShopItem::Tool(tool) => {
|
||||
Tool {
|
||||
tool: *tool
|
||||
}.as_individual_bytes()[0..12].try_into().unwrap()
|
||||
},
|
||||
ToolShopItem::Tech(tech) => {
|
||||
tech.as_bytes()[0..12].try_into().unwrap()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn as_item(&self) -> ItemDetail {
|
||||
match self {
|
||||
ToolShopItem::Tool(tool) => {
|
||||
ItemDetail::Tool(Tool {
|
||||
tool: *tool
|
||||
})
|
||||
},
|
||||
ToolShopItem::Tech(tech) => {
|
||||
ItemDetail::TechniqueDisk(*tech)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -163,7 +191,7 @@ fn number_of_techs_to_generate(character_level: usize) -> usize {
|
||||
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ToolShop<R: Rng + SeedableRng> {
|
||||
pub struct ToolShop<R: Rng + SeedableRng> {
|
||||
tools: ToolTable,
|
||||
techs: TechTable,
|
||||
rng: R,
|
||||
@ -178,7 +206,7 @@ impl<R: Rng + SeedableRng> ToolShop<R> {
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_tech(&mut self, character_level: usize) -> ShopTool {
|
||||
fn generate_tech(&mut self, character_level: usize) -> ToolShopItem {
|
||||
let tier = self.techs.0.iter()
|
||||
.filter(|t| t.level <= character_level)
|
||||
.last()
|
||||
@ -198,7 +226,7 @@ impl<R: Rng + SeedableRng> ToolShop<R> {
|
||||
TechLevel::Range{min, max} => self.rng.gen_range(min, max+1),
|
||||
};
|
||||
|
||||
ShopTool::Tech(
|
||||
ToolShopItem::Tech(
|
||||
TechniqueDisk {
|
||||
tech: *tech_detail.0,
|
||||
level: tech_level as u32,
|
||||
@ -245,7 +273,7 @@ impl<R: Rng + SeedableRng> ToolShop<R> {
|
||||
|
||||
}
|
||||
|
||||
fn generate_techs(&mut self, character_level: usize) -> Vec<ShopTool> {
|
||||
fn generate_techs(&mut self, character_level: usize) -> Vec<ToolShopItem> {
|
||||
let tier = self.techs.0.iter()
|
||||
.filter(|t| t.level <= character_level)
|
||||
.last()
|
||||
@ -262,7 +290,7 @@ impl<R: Rng + SeedableRng> ToolShop<R> {
|
||||
TechLevel::Range{min, max} => self.rng.gen_range(min, max+1),
|
||||
};
|
||||
|
||||
ShopTool::Tech(TechniqueDisk {
|
||||
ToolShopItem::Tech(TechniqueDisk {
|
||||
tech: tech,
|
||||
level: level as u32,
|
||||
})
|
||||
@ -270,9 +298,9 @@ impl<R: Rng + SeedableRng> ToolShop<R> {
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn generate_tool_list(&mut self, character_level: usize) -> Vec<ShopTool> {
|
||||
pub fn generate_tool_list(&mut self, character_level: usize) -> Vec<ToolShopItem> {
|
||||
let mut tools = Vec::new().into_iter()
|
||||
.chain(self.tools.0.clone().into_iter().map(|t| ShopTool::Tool(t)))
|
||||
.chain(self.tools.0.clone().into_iter().map(|t| ToolShopItem::Tool(t)))
|
||||
.chain(self.generate_techs(character_level).into_iter())
|
||||
.collect::<Vec<_>>();
|
||||
tools.sort();
|
||||
|
@ -2,13 +2,15 @@ use std::collections::HashMap;
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::path::PathBuf;
|
||||
use std::convert::TryInto;
|
||||
use serde::Deserialize;
|
||||
use rand::{Rng, SeedableRng};
|
||||
use rand::distributions::{WeightedIndex, Distribution};
|
||||
use rand::seq::{SliceRandom, IteratorRandom};
|
||||
use crate::entity::character::SectionID;
|
||||
use crate::ship::room::Difficulty;
|
||||
use crate::entity::item::weapon::{WeaponType, WeaponSpecial, Attribute, WeaponAttribute};
|
||||
use crate::entity::item::ItemDetail;
|
||||
use crate::entity::item::weapon::{Weapon, WeaponType, WeaponSpecial, Attribute, WeaponAttribute};
|
||||
use crate::ship::shops::ShopItem;
|
||||
use crate::ship::item_stats::WEAPON_STATS;
|
||||
|
||||
@ -20,7 +22,7 @@ const TIER2_SPECIAL: [WeaponSpecial; 10] = [WeaponSpecial::Drain, WeaponSpecial:
|
||||
WeaponSpecial::Hold, WeaponSpecial::Fire, WeaponSpecial::Thunder, WeaponSpecial::Shadow, WeaponSpecial::Riot];
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ShopWeapon {
|
||||
pub struct WeaponShopItem {
|
||||
weapon: WeaponType,
|
||||
special: Option<WeaponSpecial>,
|
||||
grind: usize,
|
||||
@ -74,7 +76,7 @@ fn special_stars(special: &WeaponSpecial) -> usize {
|
||||
|
||||
}
|
||||
|
||||
impl ShopItem for ShopWeapon {
|
||||
impl ShopItem for WeaponShopItem {
|
||||
fn price(&self) -> usize {
|
||||
WEAPON_STATS.get(&self.weapon)
|
||||
.map(|weapon_stat| {
|
||||
@ -99,6 +101,23 @@ impl ShopItem for ShopWeapon {
|
||||
})
|
||||
.unwrap_or(0xFFFF)
|
||||
}
|
||||
|
||||
fn as_bytes(&self) -> [u8; 12] {
|
||||
self.as_item().as_client_bytes()[0..12].try_into().unwrap()
|
||||
}
|
||||
|
||||
fn as_item(&self) -> ItemDetail {
|
||||
ItemDetail::Weapon(
|
||||
Weapon {
|
||||
weapon: self.weapon,
|
||||
special: self.special,
|
||||
grind: self.grind as u8,
|
||||
attrs: [self.attributes[0], self.attributes[1], None],
|
||||
tekked: true,
|
||||
modifiers: Vec::new(),
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -259,8 +278,20 @@ fn load_attribute2_table() -> AttributeTable {
|
||||
AttributeTable(table.remove("attributes".into()).unwrap())
|
||||
}
|
||||
|
||||
fn number_of_weapons_to_generate(character_level: usize) -> usize {
|
||||
if character_level <= 10 {
|
||||
10
|
||||
}
|
||||
else if character_level <= 42 {
|
||||
12
|
||||
}
|
||||
else {
|
||||
16
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct WeaponShop<R: Rng + SeedableRng> {
|
||||
pub struct WeaponShop<R: Rng + SeedableRng> {
|
||||
difficulty: Difficulty,
|
||||
section_id: SectionID,
|
||||
weapon: WeaponTable,
|
||||
@ -429,7 +460,7 @@ impl<R: Rng + SeedableRng> WeaponShop<R> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_weapon(&mut self, level: usize) -> ShopWeapon {
|
||||
fn generate_weapon(&mut self, level: usize) -> WeaponShopItem {
|
||||
let weapon = self.generate_type(level);
|
||||
let grind = if self.is_alt_grind(&weapon) {
|
||||
self.generate_alt_grind(level)
|
||||
@ -460,13 +491,21 @@ impl<R: Rng + SeedableRng> WeaponShop<R> {
|
||||
}
|
||||
};
|
||||
|
||||
ShopWeapon {
|
||||
WeaponShopItem {
|
||||
weapon: weapon,
|
||||
grind: grind,
|
||||
special: special,
|
||||
attributes: [attr1, attr2],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_weapon_list(&mut self, level: usize) -> Vec<WeaponShopItem> {
|
||||
(0..number_of_weapons_to_generate(level))
|
||||
.map(|_| {
|
||||
self.generate_weapon(level)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@ -481,7 +520,7 @@ mod test {
|
||||
fn test_generating_some_weapons() {
|
||||
let mut ws = WeaponShop::<rand_chacha::ChaCha20Rng>::new(Difficulty::Ultimate, SectionID::Pinkal);
|
||||
for i in 0..200 {
|
||||
ws.generate_weapon(i);
|
||||
ws.generate_weapon_list(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ use elseware::entity::gateway::EntityGateway;
|
||||
use elseware::entity::account::{UserAccountEntity, NewUserAccountEntity, NewUserSettingsEntity};
|
||||
use elseware::entity::character::{CharacterEntity, NewCharacterEntity};
|
||||
use elseware::ship::ship::{ShipServerState, RecvShipPacket};
|
||||
use elseware::ship::room::Difficulty;
|
||||
|
||||
use libpso::packet::ship::*;
|
||||
use libpso::packet::login::{Login, Session};
|
||||
@ -58,11 +59,15 @@ pub async fn join_lobby<EG: EntityGateway>(ship: &mut ShipServerState<EG>, id: C
|
||||
}
|
||||
|
||||
pub async fn create_room<EG: EntityGateway>(ship: &mut ShipServerState<EG>, id: ClientId, name: &str, password: &str) {
|
||||
create_room_with_difficulty(ship, id, name, password, Difficulty::Normal).await;
|
||||
}
|
||||
|
||||
pub async fn create_room_with_difficulty<EG: EntityGateway>(ship: &mut ShipServerState<EG>, id: ClientId, name: &str, password: &str, difficulty: Difficulty) {
|
||||
ship.handle(id, &RecvShipPacket::CreateRoom(CreateRoom {
|
||||
unknown: [0; 2],
|
||||
name: utf8_to_utf16_array!(name, 16),
|
||||
password: utf8_to_utf16_array!(password, 16),
|
||||
difficulty: 0,
|
||||
difficulty: difficulty.into(),
|
||||
battle: 0,
|
||||
challenge: 0,
|
||||
episode: 1,
|
||||
|
449
tests/test_shops.rs
Normal file
449
tests/test_shops.rs
Normal file
@ -0,0 +1,449 @@
|
||||
use elseware::common::serverstate::{ClientId, ServerState};
|
||||
use elseware::entity::gateway::{EntityGateway, InMemoryGateway};
|
||||
use elseware::entity::item;
|
||||
use elseware::ship::ship::{ShipServerState, RecvShipPacket, SendShipPacket};
|
||||
use elseware::ship::room::Difficulty;
|
||||
|
||||
use libpso::packet::ship::*;
|
||||
use libpso::packet::messages::*;
|
||||
|
||||
#[path = "common.rs"]
|
||||
mod common;
|
||||
use common::*;
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_player_opens_weapon_shop() {
|
||||
let mut entity_gateway = InMemoryGateway::new();
|
||||
|
||||
let (_user1, mut char1) = new_user_character(&mut entity_gateway, "a1", "a").await;
|
||||
char1.exp = 80000000;
|
||||
entity_gateway.save_character(&char1).await;
|
||||
|
||||
let mut ship = ShipServerState::builder()
|
||||
.gateway(entity_gateway.clone())
|
||||
.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;
|
||||
|
||||
let packets = ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::ShopRequest(ShopRequest {
|
||||
client: 255,
|
||||
target: 255,
|
||||
shop_type: 1
|
||||
})))).await.unwrap().collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(packets.len(), 1);
|
||||
match &packets[0].1 {
|
||||
SendShipPacket::Message(Message {msg: GameMessage::ShopList(shop_list)}) => {
|
||||
assert_eq!(shop_list.items.len(), 16)
|
||||
}
|
||||
_ => panic!("")
|
||||
}
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_player_opens_tool_shop() {
|
||||
let mut entity_gateway = InMemoryGateway::new();
|
||||
|
||||
let (_user1, mut char1) = new_user_character(&mut entity_gateway, "a1", "a").await;
|
||||
char1.exp = 80000000;
|
||||
entity_gateway.save_character(&char1).await;
|
||||
|
||||
let mut ship = ShipServerState::builder()
|
||||
.gateway(entity_gateway.clone())
|
||||
.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;
|
||||
|
||||
let packets = ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::ShopRequest(ShopRequest {
|
||||
client: 255,
|
||||
target: 255,
|
||||
shop_type: 0
|
||||
})))).await.unwrap().collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(packets.len(), 1);
|
||||
match &packets[0].1 {
|
||||
SendShipPacket::Message(Message {msg: GameMessage::ShopList(shop_list)}) => {
|
||||
assert_eq!(shop_list.items.len(), 18)
|
||||
}
|
||||
_ => panic!("")
|
||||
}
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_player_opens_armor_shop() {
|
||||
let mut entity_gateway = InMemoryGateway::new();
|
||||
|
||||
let (_user1, mut char1) = new_user_character(&mut entity_gateway, "a1", "a").await;
|
||||
char1.exp = 80000000;
|
||||
entity_gateway.save_character(&char1).await;
|
||||
|
||||
let mut ship = ShipServerState::builder()
|
||||
.gateway(entity_gateway.clone())
|
||||
.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;
|
||||
|
||||
let packets = ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::ShopRequest(ShopRequest {
|
||||
client: 255,
|
||||
target: 255,
|
||||
shop_type: 2
|
||||
})))).await.unwrap().collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(packets.len(), 1);
|
||||
match &packets[0].1 {
|
||||
SendShipPacket::Message(Message {msg: GameMessage::ShopList(shop_list)}) => {
|
||||
assert_eq!(shop_list.items.len(), 21)
|
||||
}
|
||||
_ => panic!("")
|
||||
}
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_player_buys_from_weapon_shop() {
|
||||
let mut entity_gateway = InMemoryGateway::new();
|
||||
|
||||
let (user1, mut char1) = new_user_character(&mut entity_gateway, "a1", "a").await;
|
||||
char1.exp = 80000000;
|
||||
char1.meseta = 999999;
|
||||
entity_gateway.save_character(&char1).await;
|
||||
|
||||
let mut ship = ShipServerState::builder()
|
||||
.gateway(entity_gateway.clone())
|
||||
.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;
|
||||
|
||||
ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::ShopRequest(ShopRequest {
|
||||
client: 255,
|
||||
target: 255,
|
||||
shop_type: 1
|
||||
})))).await.unwrap().for_each(drop);
|
||||
ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::BuyItem(BuyItem {
|
||||
client: 255,
|
||||
target: 255,
|
||||
item_id: 0x10000,
|
||||
shop_type: 1,
|
||||
shop_index: 0,
|
||||
amount: 1,
|
||||
unknown1: 0,
|
||||
})))).await.unwrap().for_each(drop);
|
||||
|
||||
let characters1 = entity_gateway.get_characters_by_user(&user1).await;
|
||||
let c1 = characters1.get(0).as_ref().unwrap().as_ref().unwrap();
|
||||
assert!(c1.meseta < 999999);
|
||||
let p1_items = entity_gateway.get_items_by_character(&char1).await;
|
||||
assert_eq!(p1_items.len(), 1);
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_player_buys_from_tool_shop() {
|
||||
let mut entity_gateway = InMemoryGateway::new();
|
||||
|
||||
let (user1, mut char1) = new_user_character(&mut entity_gateway, "a1", "a").await;
|
||||
char1.exp = 80000000;
|
||||
char1.meseta = 999999;
|
||||
entity_gateway.save_character(&char1).await;
|
||||
|
||||
let mut ship = ShipServerState::builder()
|
||||
.gateway(entity_gateway.clone())
|
||||
.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;
|
||||
|
||||
ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::ShopRequest(ShopRequest {
|
||||
client: 255,
|
||||
target: 255,
|
||||
shop_type: 0,
|
||||
})))).await.unwrap().for_each(drop);
|
||||
ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::BuyItem(BuyItem {
|
||||
client: 255,
|
||||
target: 255,
|
||||
item_id: 0x10000,
|
||||
shop_type: 0,
|
||||
shop_index: 0,
|
||||
amount: 1,
|
||||
unknown1: 0,
|
||||
})))).await.unwrap().for_each(drop);
|
||||
|
||||
let characters1 = entity_gateway.get_characters_by_user(&user1).await;
|
||||
let c1 = characters1.get(0).as_ref().unwrap().as_ref().unwrap();
|
||||
assert!(c1.meseta < 999999);
|
||||
let p1_items = entity_gateway.get_items_by_character(&char1).await;
|
||||
assert_eq!(p1_items.len(), 1);
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_player_buys_multiple_from_tool_shop() {
|
||||
let mut entity_gateway = InMemoryGateway::new();
|
||||
|
||||
let (user1, mut char1) = new_user_character(&mut entity_gateway, "a1", "a").await;
|
||||
char1.exp = 80000000;
|
||||
char1.meseta = 999999;
|
||||
entity_gateway.save_character(&char1).await;
|
||||
|
||||
let mut ship = ShipServerState::builder()
|
||||
.gateway(entity_gateway.clone())
|
||||
.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;
|
||||
|
||||
ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::ShopRequest(ShopRequest {
|
||||
client: 255,
|
||||
target: 255,
|
||||
shop_type: 0,
|
||||
})))).await.unwrap().for_each(drop);
|
||||
ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::BuyItem(BuyItem {
|
||||
client: 255,
|
||||
target: 255,
|
||||
item_id: 0x10000,
|
||||
shop_type: 0,
|
||||
shop_index: 0,
|
||||
amount: 5,
|
||||
unknown1: 0,
|
||||
})))).await.unwrap().for_each(drop);
|
||||
|
||||
let characters1 = entity_gateway.get_characters_by_user(&user1).await;
|
||||
let c1 = characters1.get(0).as_ref().unwrap().as_ref().unwrap();
|
||||
assert!(c1.meseta < 999999);
|
||||
let p1_items = entity_gateway.get_items_by_character(&char1).await;
|
||||
assert_eq!(p1_items.len(), 5);
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_player_buys_from_armor_shop() {
|
||||
let mut entity_gateway = InMemoryGateway::new();
|
||||
|
||||
let (user1, mut char1) = new_user_character(&mut entity_gateway, "a1", "a").await;
|
||||
char1.exp = 80000000;
|
||||
char1.meseta = 999999;
|
||||
entity_gateway.save_character(&char1).await;
|
||||
|
||||
let mut ship = ShipServerState::builder()
|
||||
.gateway(entity_gateway.clone())
|
||||
.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;
|
||||
|
||||
ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::ShopRequest(ShopRequest {
|
||||
client: 255,
|
||||
target: 255,
|
||||
shop_type: 2
|
||||
})))).await.unwrap().for_each(drop);
|
||||
ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::BuyItem(BuyItem {
|
||||
client: 255,
|
||||
target: 255,
|
||||
item_id: 0x10000,
|
||||
shop_type: 2,
|
||||
shop_index: 0,
|
||||
amount: 1,
|
||||
unknown1: 0,
|
||||
})))).await.unwrap().for_each(drop);
|
||||
|
||||
let characters1 = entity_gateway.get_characters_by_user(&user1).await;
|
||||
let c1 = characters1.get(0).as_ref().unwrap().as_ref().unwrap();
|
||||
assert!(c1.meseta < 999999);
|
||||
let p1_items = entity_gateway.get_items_by_character(&char1).await;
|
||||
assert_eq!(p1_items.len(), 1);
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_player_sells_to_shop() {
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_other_clients_see_purchase() {
|
||||
let mut entity_gateway = InMemoryGateway::new();
|
||||
|
||||
let (_user1, mut char1) = new_user_character(&mut entity_gateway, "a1", "a").await;
|
||||
let (_user2, _char2) = new_user_character(&mut entity_gateway, "a2", "a").await;
|
||||
char1.exp = 80000000;
|
||||
char1.meseta = 999999;
|
||||
entity_gateway.save_character(&char1).await;
|
||||
|
||||
let mut ship = ShipServerState::builder()
|
||||
.gateway(entity_gateway.clone())
|
||||
.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;
|
||||
join_lobby(&mut ship, ClientId(2)).await;
|
||||
create_room_with_difficulty(&mut ship, ClientId(1), "room", "", Difficulty::Ultimate).await;
|
||||
join_room(&mut ship, ClientId(2), 0).await;
|
||||
|
||||
ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::ShopRequest(ShopRequest {
|
||||
client: 255,
|
||||
target: 255,
|
||||
shop_type: 1
|
||||
})))).await.unwrap().for_each(drop);
|
||||
let packets = ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::BuyItem(BuyItem {
|
||||
client: 255,
|
||||
target: 255,
|
||||
item_id: 0x10000,
|
||||
shop_type: 1,
|
||||
shop_index: 0,
|
||||
amount: 1,
|
||||
unknown1: 0,
|
||||
})))).await.unwrap().collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(packets.len(), 1);
|
||||
assert_eq!(packets[0].0, ClientId(2));
|
||||
match &packets[0].1 {
|
||||
SendShipPacket::Message(Message{msg: GameMessage::CreateItem(_)}) => {},
|
||||
_ => panic!(""),
|
||||
}
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_other_clients_see_stacked_purchase() {
|
||||
let mut entity_gateway = InMemoryGateway::new();
|
||||
|
||||
let (_user1, mut char1) = new_user_character(&mut entity_gateway, "a1", "a").await;
|
||||
let (_user2, _char2) = new_user_character(&mut entity_gateway, "a2", "a").await;
|
||||
char1.exp = 80000000;
|
||||
char1.meseta = 999999;
|
||||
entity_gateway.save_character(&char1).await;
|
||||
entity_gateway.create_item(
|
||||
item::NewItemEntity {
|
||||
item: item::ItemDetail::Tool(
|
||||
item::tool::Tool {
|
||||
tool: item::tool::ToolType::Monomate
|
||||
}
|
||||
),
|
||||
location: item::ItemLocation::Inventory {
|
||||
character_id: char1.id,
|
||||
slot: 0,
|
||||
equipped: false,
|
||||
}
|
||||
}).await;
|
||||
|
||||
let mut ship = ShipServerState::builder()
|
||||
.gateway(entity_gateway.clone())
|
||||
.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;
|
||||
join_lobby(&mut ship, ClientId(2)).await;
|
||||
create_room_with_difficulty(&mut ship, ClientId(1), "room", "", Difficulty::Ultimate).await;
|
||||
join_room(&mut ship, ClientId(2), 0).await;
|
||||
|
||||
ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::ShopRequest(ShopRequest {
|
||||
client: 255,
|
||||
target: 255,
|
||||
shop_type: 1
|
||||
})))).await.unwrap().for_each(drop);
|
||||
let packets = ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::BuyItem(BuyItem {
|
||||
client: 255,
|
||||
target: 255,
|
||||
item_id: 0x10000,
|
||||
shop_type: 1,
|
||||
shop_index: 0,
|
||||
amount: 1,
|
||||
unknown1: 0,
|
||||
})))).await.unwrap().collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(packets.len(), 1);
|
||||
assert_eq!(packets[0].0, ClientId(2));
|
||||
match &packets[0].1 {
|
||||
SendShipPacket::Message(Message{msg: GameMessage::CreateItem(_)}) => {},
|
||||
_ => panic!(""),
|
||||
}
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_buying_item_without_enough_mseseta() {
|
||||
let mut entity_gateway = InMemoryGateway::new();
|
||||
|
||||
let (user1, mut char1) = new_user_character(&mut entity_gateway, "a1", "a").await;
|
||||
|
||||
let mut ship = ShipServerState::builder()
|
||||
.gateway(entity_gateway.clone())
|
||||
.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;
|
||||
|
||||
ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::ShopRequest(ShopRequest {
|
||||
client: 255,
|
||||
target: 255,
|
||||
shop_type: 1
|
||||
})))).await.unwrap().for_each(drop);
|
||||
let packets = ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::BuyItem(BuyItem {
|
||||
client: 255,
|
||||
target: 255,
|
||||
item_id: 0x10000,
|
||||
shop_type: 1,
|
||||
shop_index: 0,
|
||||
amount: 1,
|
||||
unknown1: 0,
|
||||
})))).await;
|
||||
|
||||
assert!(packets.is_err());
|
||||
let characters1 = entity_gateway.get_characters_by_user(&user1).await;
|
||||
let c1 = characters1.get(0).as_ref().unwrap().as_ref().unwrap();
|
||||
assert_eq!(c1.meseta, 0);
|
||||
let p1_items = entity_gateway.get_items_by_character(&char1).await;
|
||||
assert_eq!(p1_items.len(), 0);
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_player_double_buys_from_tool_shop() {
|
||||
let mut entity_gateway = InMemoryGateway::new();
|
||||
|
||||
let (user1, mut char1) = new_user_character(&mut entity_gateway, "a1", "a").await;
|
||||
char1.exp = 80000000;
|
||||
char1.meseta = 999999;
|
||||
entity_gateway.save_character(&char1).await;
|
||||
|
||||
let mut ship = ShipServerState::builder()
|
||||
.gateway(entity_gateway.clone())
|
||||
.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;
|
||||
|
||||
ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::ShopRequest(ShopRequest {
|
||||
client: 255,
|
||||
target: 255,
|
||||
shop_type: 0,
|
||||
})))).await.unwrap().for_each(drop);
|
||||
ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::BuyItem(BuyItem {
|
||||
client: 255,
|
||||
target: 255,
|
||||
item_id: 0x10000,
|
||||
shop_type: 0,
|
||||
shop_index: 0,
|
||||
amount: 3,
|
||||
unknown1: 0,
|
||||
})))).await.unwrap().for_each(drop);
|
||||
ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::BuyItem(BuyItem {
|
||||
client: 255,
|
||||
target: 255,
|
||||
item_id: 0x10001,
|
||||
shop_type: 0,
|
||||
shop_index: 1,
|
||||
amount: 2,
|
||||
unknown1: 0,
|
||||
})))).await.unwrap().for_each(drop);
|
||||
ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::BuyItem(BuyItem {
|
||||
client: 255,
|
||||
target: 255,
|
||||
item_id: 0x10002,
|
||||
shop_type: 0,
|
||||
shop_index: 0,
|
||||
amount: 4,
|
||||
unknown1: 0,
|
||||
})))).await.unwrap().for_each(drop);
|
||||
|
||||
let characters1 = entity_gateway.get_characters_by_user(&user1).await;
|
||||
let c1 = characters1.get(0).as_ref().unwrap().as_ref().unwrap();
|
||||
assert!(c1.meseta < 999999);
|
||||
let p1_items = entity_gateway.get_items_by_character(&char1).await;
|
||||
assert_eq!(p1_items.len(), 10);
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user