mirror of
https://github.com/redstrate/Kawari.git
synced 2025-06-30 11:47:45 +00:00
Begin implementing currency, add //gm gil command and more
This doesn't work yet though , and I'm not sure why. I also fixed a bug where new characters doesn't get their inventories initialized properly.
This commit is contained in:
parent
03609fb8c1
commit
01697c8f62
10 changed files with 156 additions and 31 deletions
1
USAGE.md
1
USAGE.md
|
@ -133,3 +133,4 @@ These GM commands are implemented in the FFXIV protocol, but only some of them a
|
||||||
* `//gm orchestrion <on/off> <id>`: Unlock an Orchestrion song.
|
* `//gm orchestrion <on/off> <id>`: Unlock an Orchestrion song.
|
||||||
* `//gm exp <amount>`: Adds the specified amount of EXP to the current class/job.
|
* `//gm exp <amount>`: Adds the specified amount of EXP to the current class/job.
|
||||||
* `//gm teri_info`: Displays information about the current zone. Currently displays zone id, weather, internal zone name, parent region name, and place/display name.
|
* `//gm teri_info`: Displays information about the current zone. Currently displays zone id, weather, internal zone name, parent region name, and place/display name.
|
||||||
|
* `//gm gil <amount>`: Adds the specified amount of gil to the player
|
||||||
|
|
|
@ -169,6 +169,11 @@
|
||||||
"name": "ActorControlTarget",
|
"name": "ActorControlTarget",
|
||||||
"opcode": 593,
|
"opcode": 593,
|
||||||
"size": 28
|
"size": 28
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "CurrencyCrystalInfo",
|
||||||
|
"opcode": 548,
|
||||||
|
"size": 32
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"ClientZoneIpcType": [
|
"ClientZoneIpcType": [
|
||||||
|
|
|
@ -6,7 +6,7 @@ use kawari::RECEIVE_BUFFER_SIZE;
|
||||||
use kawari::common::Position;
|
use kawari::common::Position;
|
||||||
use kawari::common::{GameData, TerritoryNameKind, timestamp_secs};
|
use kawari::common::{GameData, TerritoryNameKind, timestamp_secs};
|
||||||
use kawari::config::get_config;
|
use kawari::config::get_config;
|
||||||
use kawari::inventory::Item;
|
use kawari::inventory::{Item, Storage};
|
||||||
use kawari::ipc::chat::{ServerChatIpcData, ServerChatIpcSegment};
|
use kawari::ipc::chat::{ServerChatIpcData, ServerChatIpcSegment};
|
||||||
use kawari::ipc::zone::{
|
use kawari::ipc::zone::{
|
||||||
ActorControlCategory, ActorControlSelf, PlayerEntry, PlayerSpawn, PlayerStatus, SocialList,
|
ActorControlCategory, ActorControlSelf, PlayerEntry, PlayerSpawn, PlayerStatus, SocialList,
|
||||||
|
@ -716,7 +716,12 @@ async fn client_loop(
|
||||||
"Internal name: {}\n",
|
"Internal name: {}\n",
|
||||||
"Region name: {}\n",
|
"Region name: {}\n",
|
||||||
"Place name: {}"), id, weather_id, internal_name, region_name, place_name).as_str()).await;
|
"Place name: {}"), id, weather_id, internal_name, region_name, place_name).as_str()).await;
|
||||||
}
|
},
|
||||||
|
GameMasterCommandType::Gil => {
|
||||||
|
let amount = *arg0;
|
||||||
|
connection.player_data.inventory.currency.get_slot_mut(0).quantity += amount;
|
||||||
|
connection.send_inventory(false).await;
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ClientZoneIpcData::ZoneJump {
|
ClientZoneIpcData::ZoneJump {
|
||||||
|
|
40
src/inventory/currency.rs
Normal file
40
src/inventory/currency.rs
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use super::{Item, Storage};
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Deserialize, Serialize, Debug)]
|
||||||
|
pub struct CurrencyStorage {
|
||||||
|
pub gil: Item,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for CurrencyStorage {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
gil: Item { quantity: 0, id: 1 },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Storage for CurrencyStorage {
|
||||||
|
fn max_slots(&self) -> u32 {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn num_items(&self) -> u32 {
|
||||||
|
self.gil.quantity
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_slot_mut(&mut self, index: u16) -> &mut Item {
|
||||||
|
match index {
|
||||||
|
0 => &mut self.gil,
|
||||||
|
_ => panic!("{} is not a valid src_container_index?!?", index),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_slot(&self, index: u16) -> &Item {
|
||||||
|
match index {
|
||||||
|
0 => &self.gil,
|
||||||
|
_ => panic!("{} is not a valid src_container_index?!?", index),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,11 +2,19 @@ use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use super::{Item, Storage};
|
use super::{Item, Storage};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
pub struct GenericStorage<const N: usize> {
|
pub struct GenericStorage<const N: usize> {
|
||||||
pub slots: Vec<Item>,
|
pub slots: Vec<Item>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> GenericStorage<N> {
|
||||||
|
pub fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
slots: vec![Item::default(); N],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<const N: usize> Storage for GenericStorage<N> {
|
impl<const N: usize> Storage for GenericStorage<N> {
|
||||||
fn max_slots(&self) -> u32 {
|
fn max_slots(&self) -> u32 {
|
||||||
N as u32
|
N as u32
|
||||||
|
|
|
@ -18,6 +18,9 @@ pub use item::Item;
|
||||||
mod storage;
|
mod storage;
|
||||||
pub use storage::{ContainerType, Storage};
|
pub use storage::{ContainerType, Storage};
|
||||||
|
|
||||||
|
mod currency;
|
||||||
|
pub use currency::CurrencyStorage;
|
||||||
|
|
||||||
const MAX_NORMAL_STORAGE: usize = 35;
|
const MAX_NORMAL_STORAGE: usize = 35;
|
||||||
const MAX_LARGE_STORAGE: usize = 50;
|
const MAX_LARGE_STORAGE: usize = 50;
|
||||||
|
|
||||||
|
@ -37,6 +40,7 @@ pub struct Inventory {
|
||||||
pub armoury_bracelet: GenericStorage<MAX_NORMAL_STORAGE>,
|
pub armoury_bracelet: GenericStorage<MAX_NORMAL_STORAGE>,
|
||||||
pub armoury_rings: GenericStorage<MAX_LARGE_STORAGE>,
|
pub armoury_rings: GenericStorage<MAX_LARGE_STORAGE>,
|
||||||
pub armoury_soul_crystal: GenericStorage<MAX_NORMAL_STORAGE>,
|
pub armoury_soul_crystal: GenericStorage<MAX_NORMAL_STORAGE>,
|
||||||
|
pub currency: CurrencyStorage,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Inventory {
|
impl Default for Inventory {
|
||||||
|
@ -56,6 +60,7 @@ impl Default for Inventory {
|
||||||
armoury_bracelet: GenericStorage::default(),
|
armoury_bracelet: GenericStorage::default(),
|
||||||
armoury_rings: GenericStorage::default(),
|
armoury_rings: GenericStorage::default(),
|
||||||
armoury_soul_crystal: GenericStorage::default(),
|
armoury_soul_crystal: GenericStorage::default(),
|
||||||
|
currency: CurrencyStorage::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -201,6 +206,7 @@ impl Inventory {
|
||||||
ContainerType::Inventory2 => &mut self.pages[2],
|
ContainerType::Inventory2 => &mut self.pages[2],
|
||||||
ContainerType::Inventory3 => &mut self.pages[3],
|
ContainerType::Inventory3 => &mut self.pages[3],
|
||||||
ContainerType::Equipped => &mut self.equipped,
|
ContainerType::Equipped => &mut self.equipped,
|
||||||
|
ContainerType::Currency => &mut self.currency,
|
||||||
ContainerType::ArmoryOffWeapon => &mut self.armoury_off_hand,
|
ContainerType::ArmoryOffWeapon => &mut self.armoury_off_hand,
|
||||||
ContainerType::ArmoryHead => &mut self.armoury_head,
|
ContainerType::ArmoryHead => &mut self.armoury_head,
|
||||||
ContainerType::ArmoryBody => &mut self.armoury_body,
|
ContainerType::ArmoryBody => &mut self.armoury_body,
|
||||||
|
@ -223,6 +229,7 @@ impl Inventory {
|
||||||
ContainerType::Inventory2 => &self.pages[2],
|
ContainerType::Inventory2 => &self.pages[2],
|
||||||
ContainerType::Inventory3 => &self.pages[3],
|
ContainerType::Inventory3 => &self.pages[3],
|
||||||
ContainerType::Equipped => &self.equipped,
|
ContainerType::Equipped => &self.equipped,
|
||||||
|
ContainerType::Currency => &self.currency,
|
||||||
ContainerType::ArmoryOffWeapon => &self.armoury_off_hand,
|
ContainerType::ArmoryOffWeapon => &self.armoury_off_hand,
|
||||||
ContainerType::ArmoryHead => &self.armoury_head,
|
ContainerType::ArmoryHead => &self.armoury_head,
|
||||||
ContainerType::ArmoryBody => &self.armoury_body,
|
ContainerType::ArmoryBody => &self.armoury_body,
|
||||||
|
|
|
@ -15,6 +15,8 @@ pub enum ContainerType {
|
||||||
|
|
||||||
Equipped = 1000,
|
Equipped = 1000,
|
||||||
|
|
||||||
|
Currency = 2000,
|
||||||
|
|
||||||
ArmoryOffWeapon = 3200,
|
ArmoryOffWeapon = 3200,
|
||||||
ArmoryHead = 3201,
|
ArmoryHead = 3201,
|
||||||
ArmoryBody = 3202,
|
ArmoryBody = 3202,
|
||||||
|
|
16
src/ipc/zone/currency_info.rs
Normal file
16
src/ipc/zone/currency_info.rs
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
use binrw::binrw;
|
||||||
|
|
||||||
|
#[binrw]
|
||||||
|
#[brw(little)]
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
pub struct CurrencyInfo {
|
||||||
|
pub sequence: u32,
|
||||||
|
pub container: u16,
|
||||||
|
pub slot: u16,
|
||||||
|
pub quantity: u32,
|
||||||
|
pub unk1: u32,
|
||||||
|
pub catalog_id: u32,
|
||||||
|
pub unk2: u32,
|
||||||
|
pub unk3: u32,
|
||||||
|
pub unk4: u32,
|
||||||
|
}
|
|
@ -79,6 +79,9 @@ pub use equip::Equip;
|
||||||
mod client_trigger;
|
mod client_trigger;
|
||||||
pub use client_trigger::{ClientTrigger, ClientTriggerCommand};
|
pub use client_trigger::{ClientTrigger, ClientTriggerCommand};
|
||||||
|
|
||||||
|
mod currency_info;
|
||||||
|
pub use currency_info::CurrencyInfo;
|
||||||
|
|
||||||
use crate::common::ObjectTypeId;
|
use crate::common::ObjectTypeId;
|
||||||
use crate::common::Position;
|
use crate::common::Position;
|
||||||
use crate::common::read_string;
|
use crate::common::read_string;
|
||||||
|
@ -149,6 +152,7 @@ pub enum GameMasterCommandType {
|
||||||
GiveItem = 0xC8,
|
GiveItem = 0xC8,
|
||||||
Aetheryte = 0x5E,
|
Aetheryte = 0x5E,
|
||||||
TerritoryInfo = 0x25D,
|
TerritoryInfo = 0x25D,
|
||||||
|
Gil = 0xC9,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[binrw]
|
#[binrw]
|
||||||
|
@ -244,6 +248,8 @@ pub enum ServerZoneIpcData {
|
||||||
},
|
},
|
||||||
/// Used to control target information
|
/// Used to control target information
|
||||||
ActorControlTarget(ActorControlTarget),
|
ActorControlTarget(ActorControlTarget),
|
||||||
|
/// Used to update the player's currencies
|
||||||
|
CurrencyCrystalInfo(CurrencyInfo),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[binrw]
|
#[binrw]
|
||||||
|
@ -374,7 +380,8 @@ pub enum ClientZoneIpcData {
|
||||||
event_id: u32,
|
event_id: u32,
|
||||||
},
|
},
|
||||||
#[br(pre_assert(*magic == ClientZoneIpcType::EventHandlerReturn))]
|
#[br(pre_assert(*magic == ClientZoneIpcType::EventHandlerReturn))]
|
||||||
EventHandlerReturn { // TODO: This is actually EventYieldHandler
|
EventHandlerReturn {
|
||||||
|
// TODO: This is actually EventYieldHandler
|
||||||
handler_id: u32,
|
handler_id: u32,
|
||||||
scene: u16,
|
scene: u16,
|
||||||
error_code: u8,
|
error_code: u8,
|
||||||
|
|
|
@ -12,15 +12,16 @@ use crate::{
|
||||||
OBFUSCATION_ENABLED_MODE,
|
OBFUSCATION_ENABLED_MODE,
|
||||||
common::{GameData, ObjectId, ObjectTypeId, Position, timestamp_secs},
|
common::{GameData, ObjectId, ObjectTypeId, Position, timestamp_secs},
|
||||||
config::{WorldConfig, get_config},
|
config::{WorldConfig, get_config},
|
||||||
inventory::{Inventory, Item},
|
inventory::{ContainerType, Inventory, Item},
|
||||||
ipc::{
|
ipc::{
|
||||||
chat::ServerChatIpcSegment,
|
chat::ServerChatIpcSegment,
|
||||||
zone::{
|
zone::{
|
||||||
ActionEffect, ActionRequest, ActionResult, ActorControl, ActorControlCategory,
|
ActionEffect, ActionRequest, ActionResult, ActorControl, ActorControlCategory,
|
||||||
ActorControlSelf, ActorControlTarget, ClientZoneIpcSegment, CommonSpawn, ContainerInfo,
|
ActorControlSelf, ActorControlTarget, ClientZoneIpcSegment, CommonSpawn, ContainerInfo,
|
||||||
DisplayFlag, EffectKind, Equip, GameMasterRank, InitZone, ItemInfo, Move, NpcSpawn,
|
CurrencyInfo, DisplayFlag, EffectKind, Equip, GameMasterRank, InitZone, ItemInfo, Move,
|
||||||
ObjectKind, PlayerStats, PlayerSubKind, ServerZoneIpcData, ServerZoneIpcSegment,
|
NpcSpawn, ObjectKind, PlayerStats, PlayerSubKind, ServerZoneIpcData,
|
||||||
StatusEffect, StatusEffectList, UpdateClassInfo, Warp, WeatherChange,
|
ServerZoneIpcSegment, StatusEffect, StatusEffectList, UpdateClassInfo, Warp,
|
||||||
|
WeatherChange,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
opcodes::ServerZoneIpcType,
|
opcodes::ServerZoneIpcType,
|
||||||
|
@ -484,33 +485,66 @@ impl ZoneConnection {
|
||||||
let mut sequence = 0;
|
let mut sequence = 0;
|
||||||
|
|
||||||
for (container_type, container) in &self.player_data.inventory.clone() {
|
for (container_type, container) in &self.player_data.inventory.clone() {
|
||||||
let mut send_slot = async |slot_index: u16, item: &Item| {
|
// currencies
|
||||||
let ipc = ServerZoneIpcSegment {
|
if container_type == ContainerType::Currency {
|
||||||
op_code: ServerZoneIpcType::UpdateItem,
|
let mut send_currency = async |slot_index: u16, item: &Item| {
|
||||||
timestamp: timestamp_secs(),
|
let ipc = ServerZoneIpcSegment {
|
||||||
data: ServerZoneIpcData::UpdateItem(ItemInfo {
|
op_code: ServerZoneIpcType::CurrencyCrystalInfo,
|
||||||
sequence,
|
timestamp: timestamp_secs(),
|
||||||
container: container_type,
|
data: ServerZoneIpcData::CurrencyCrystalInfo(CurrencyInfo {
|
||||||
slot: slot_index,
|
sequence,
|
||||||
quantity: item.quantity,
|
container: item.id as u16,
|
||||||
catalog_id: item.id,
|
slot: slot_index,
|
||||||
condition: 30000,
|
quantity: item.quantity,
|
||||||
|
catalog_id: item.id,
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}),
|
};
|
||||||
..Default::default()
|
|
||||||
|
self.send_segment(PacketSegment {
|
||||||
|
source_actor: self.player_data.actor_id,
|
||||||
|
target_actor: self.player_data.actor_id,
|
||||||
|
segment_type: SegmentType::Ipc,
|
||||||
|
data: SegmentData::Ipc { data: ipc },
|
||||||
|
})
|
||||||
|
.await;
|
||||||
};
|
};
|
||||||
|
|
||||||
self.send_segment(PacketSegment {
|
for i in 0..container.max_slots() {
|
||||||
source_actor: self.player_data.actor_id,
|
send_currency(i as u16, container.get_slot(i as u16)).await;
|
||||||
target_actor: self.player_data.actor_id,
|
}
|
||||||
segment_type: SegmentType::Ipc,
|
} else {
|
||||||
data: SegmentData::Ipc { data: ipc },
|
// items
|
||||||
})
|
|
||||||
.await;
|
|
||||||
};
|
|
||||||
|
|
||||||
for i in 0..container.max_slots() {
|
let mut send_slot = async |slot_index: u16, item: &Item| {
|
||||||
send_slot(i as u16, container.get_slot(i as u16)).await;
|
let ipc = ServerZoneIpcSegment {
|
||||||
|
op_code: ServerZoneIpcType::UpdateItem,
|
||||||
|
timestamp: timestamp_secs(),
|
||||||
|
data: ServerZoneIpcData::UpdateItem(ItemInfo {
|
||||||
|
sequence,
|
||||||
|
container: container_type,
|
||||||
|
slot: slot_index,
|
||||||
|
quantity: item.quantity,
|
||||||
|
catalog_id: item.id,
|
||||||
|
condition: 30000,
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
self.send_segment(PacketSegment {
|
||||||
|
source_actor: self.player_data.actor_id,
|
||||||
|
target_actor: self.player_data.actor_id,
|
||||||
|
segment_type: SegmentType::Ipc,
|
||||||
|
data: SegmentData::Ipc { data: ipc },
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
};
|
||||||
|
|
||||||
|
for i in 0..container.max_slots() {
|
||||||
|
send_slot(i as u16, container.get_slot(i as u16)).await;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// inform the client of container state
|
// inform the client of container state
|
||||||
|
|
Loading…
Add table
Reference in a new issue