mirror of
https://github.com/redstrate/Kawari.git
synced 2025-04-21 15:07:45 +00:00
Add GM command to give items, support equipping from inventory
Now with this command and support for *one* inventory slot, you can now freely equip items to your character for testing.
This commit is contained in:
parent
32b6840e38
commit
822d1f5139
8 changed files with 114 additions and 37 deletions
1
USAGE.md
1
USAGE.md
|
@ -100,3 +100,4 @@ These GM commands are implemented in the FFXIV protocol, but only some of them a
|
||||||
* `//gm teri <id>`: Changes to the specified territory
|
* `//gm teri <id>`: Changes to the specified territory
|
||||||
* `//gm weather <id>`: Changes the weather
|
* `//gm weather <id>`: Changes the weather
|
||||||
* `//gm wireframe`: Toggle wireframe rendering for the environment
|
* `//gm wireframe`: Toggle wireframe rendering for the environment
|
||||||
|
* `//gm item <id>`: Gives yourself an item. This can only place a single item in the first page of your inventory currently.
|
||||||
|
|
Binary file not shown.
|
@ -655,6 +655,11 @@ async fn client_loop(
|
||||||
ActorControlCategory::ToggleWireframeRendering(),
|
ActorControlCategory::ToggleWireframeRendering(),
|
||||||
})
|
})
|
||||||
.await,
|
.await,
|
||||||
|
GameMasterCommandType::GiveItem => {
|
||||||
|
connection.inventory.extra_slot.id = *arg;
|
||||||
|
connection.inventory.extra_slot.quantity = 1;
|
||||||
|
connection.send_inventory(false).await;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ClientZoneIpcData::EnterZoneLine {
|
ClientZoneIpcData::EnterZoneLine {
|
||||||
|
|
|
@ -448,7 +448,37 @@ impl ZoneConnection {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn send_inventory(&mut self, send_appearance_update: bool) {
|
pub async fn send_inventory(&mut self, send_appearance_update: bool) {
|
||||||
// item list
|
// page 1
|
||||||
|
{
|
||||||
|
let extra_slot = self.inventory.extra_slot;
|
||||||
|
|
||||||
|
let mut send_slot = async |slot_index: u16, item: &Item| {
|
||||||
|
let ipc = ServerZoneIpcSegment {
|
||||||
|
op_code: ServerZoneIpcType::ItemInfo,
|
||||||
|
timestamp: timestamp_secs(),
|
||||||
|
data: ServerZoneIpcData::ItemInfo(ItemInfo {
|
||||||
|
container: ContainerType::Inventory0,
|
||||||
|
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: ipc },
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
};
|
||||||
|
|
||||||
|
send_slot(0, &extra_slot).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
// equipped
|
||||||
{
|
{
|
||||||
let equipped = self.inventory.equipped;
|
let equipped = self.inventory.equipped;
|
||||||
|
|
||||||
|
@ -490,6 +520,27 @@ impl ZoneConnection {
|
||||||
send_slot(13, &equipped.soul_crystal).await;
|
send_slot(13, &equipped.soul_crystal).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// inform the client of page 1
|
||||||
|
{
|
||||||
|
let ipc = ServerZoneIpcSegment {
|
||||||
|
op_code: ServerZoneIpcType::ContainerInfo,
|
||||||
|
timestamp: timestamp_secs(),
|
||||||
|
data: ServerZoneIpcData::ContainerInfo(ContainerInfo {
|
||||||
|
container: ContainerType::Inventory0,
|
||||||
|
num_items: 1,
|
||||||
|
..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: ipc },
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
// inform the client they have items equipped
|
// inform the client they have items equipped
|
||||||
{
|
{
|
||||||
let ipc = ServerZoneIpcSegment {
|
let ipc = ServerZoneIpcSegment {
|
||||||
|
@ -498,6 +549,7 @@ impl ZoneConnection {
|
||||||
data: ServerZoneIpcData::ContainerInfo(ContainerInfo {
|
data: ServerZoneIpcData::ContainerInfo(ContainerInfo {
|
||||||
container: ContainerType::Equipped,
|
container: ContainerType::Equipped,
|
||||||
num_items: self.inventory.equipped.num_items(),
|
num_items: self.inventory.equipped.num_items(),
|
||||||
|
sequence: 1,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
|
|
@ -5,7 +5,7 @@ use physis::{
|
||||||
|
|
||||||
use crate::config::get_config;
|
use crate::config::get_config;
|
||||||
|
|
||||||
use super::ipc::InventoryModify;
|
use super::ipc::{ContainerType, InventoryModify};
|
||||||
|
|
||||||
#[derive(Default, Copy, Clone)]
|
#[derive(Default, Copy, Clone)]
|
||||||
pub struct Item {
|
pub struct Item {
|
||||||
|
@ -52,10 +52,30 @@ impl EquippedContainer {
|
||||||
+ self.left_ring.quantity
|
+ self.left_ring.quantity
|
||||||
+ self.soul_crystal.quantity
|
+ self.soul_crystal.quantity
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_slot(&mut self, index: u16) -> &mut Item {
|
||||||
|
match index {
|
||||||
|
0 => &mut self.main_hand,
|
||||||
|
1 => &mut self.off_hand,
|
||||||
|
2 => &mut self.head,
|
||||||
|
3 => &mut self.body,
|
||||||
|
4 => &mut self.hands,
|
||||||
|
6 => &mut self.legs,
|
||||||
|
7 => &mut self.feet,
|
||||||
|
8 => &mut self.ears,
|
||||||
|
9 => &mut self.neck,
|
||||||
|
10 => &mut self.wrists,
|
||||||
|
11 => &mut self.right_ring,
|
||||||
|
12 => &mut self.left_ring,
|
||||||
|
13 => &mut self.soul_crystal,
|
||||||
|
_ => panic!("Not a valid src_container_index?!?"),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Inventory {
|
pub struct Inventory {
|
||||||
pub equipped: EquippedContainer,
|
pub equipped: EquippedContainer,
|
||||||
|
pub extra_slot: Item, // WIP for inventory pages
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Inventory {
|
impl Default for Inventory {
|
||||||
|
@ -68,6 +88,7 @@ impl Inventory {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
equipped: EquippedContainer::default(),
|
equipped: EquippedContainer::default(),
|
||||||
|
extra_slot: Item::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,26 +137,15 @@ impl Inventory {
|
||||||
|
|
||||||
pub fn process_action(&mut self, action: &InventoryModify) {
|
pub fn process_action(&mut self, action: &InventoryModify) {
|
||||||
// equipped
|
// equipped
|
||||||
if action.src_storage_id == 1000 {
|
if action.src_storage_id == ContainerType::Equipped {
|
||||||
let slot = match action.src_container_index {
|
let src_slot = self.equipped.get_slot(action.src_container_index);
|
||||||
0 => &mut self.equipped.main_hand,
|
|
||||||
1 => &mut self.equipped.off_hand,
|
|
||||||
2 => &mut self.equipped.head,
|
|
||||||
3 => &mut self.equipped.body,
|
|
||||||
4 => &mut self.equipped.hands,
|
|
||||||
6 => &mut self.equipped.legs,
|
|
||||||
7 => &mut self.equipped.feet,
|
|
||||||
8 => &mut self.equipped.ears,
|
|
||||||
9 => &mut self.equipped.neck,
|
|
||||||
10 => &mut self.equipped.wrists,
|
|
||||||
11 => &mut self.equipped.right_ring,
|
|
||||||
12 => &mut self.equipped.left_ring,
|
|
||||||
13 => &mut self.equipped.soul_crystal,
|
|
||||||
_ => panic!("Not a valid src_container_index?!?"),
|
|
||||||
};
|
|
||||||
|
|
||||||
// it only unequips for now, doesn't move the item
|
// it only unequips for now, doesn't move the item
|
||||||
*slot = Item::default();
|
*src_slot = Item::default();
|
||||||
|
} else if action.src_storage_id == ContainerType::Inventory0 {
|
||||||
|
let dst_slot = self.equipped.get_slot(action.dst_container_index);
|
||||||
|
|
||||||
|
*dst_slot = self.extra_slot;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ pub enum ContainerType {
|
||||||
Inventory2 = 2,
|
Inventory2 = 2,
|
||||||
Inventory3 = 3,
|
Inventory3 = 3,
|
||||||
Equipped = 1000,
|
Equipped = 1000,
|
||||||
|
ArmouryBody = 3202,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[binrw]
|
#[binrw]
|
||||||
|
@ -19,7 +20,7 @@ pub enum ContainerType {
|
||||||
pub struct ContainerInfo {
|
pub struct ContainerInfo {
|
||||||
pub sequence: u32,
|
pub sequence: u32,
|
||||||
pub num_items: u32,
|
pub num_items: u32,
|
||||||
#[brw(pad_after = 2)] // not used
|
#[brw(pad_size_to = 4)]
|
||||||
pub container: ContainerType,
|
pub container: ContainerType,
|
||||||
pub start_or_finish: u32,
|
pub start_or_finish: u32,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,27 @@
|
||||||
use binrw::binrw;
|
use binrw::binrw;
|
||||||
|
|
||||||
|
use super::ContainerType;
|
||||||
|
|
||||||
#[binrw]
|
#[binrw]
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct InventoryModify {
|
pub struct InventoryModify {
|
||||||
pub context_id: u32,
|
pub context_id: u32,
|
||||||
pub operation_type: u8,
|
pub operation_type: u16,
|
||||||
#[brw(pad_before = 3)]
|
|
||||||
|
#[brw(pad_before = 2)]
|
||||||
pub src_actor_id: u32,
|
pub src_actor_id: u32,
|
||||||
pub src_storage_id: u32,
|
#[brw(pad_size_to = 4)]
|
||||||
pub src_container_index: i16,
|
pub src_storage_id: ContainerType,
|
||||||
#[brw(pad_before = 4)]
|
pub src_container_index: u16,
|
||||||
|
#[brw(pad_before = 2)]
|
||||||
pub src_stack: u32,
|
pub src_stack: u32,
|
||||||
pub src_catalog_id: u32,
|
pub src_catalog_id: u32,
|
||||||
|
|
||||||
pub dst_actor_id: u32,
|
pub dst_actor_id: u32,
|
||||||
pub dst_storage_id: u32,
|
#[brw(pad_size_to = 4)]
|
||||||
pub dst_container_index: i16,
|
pub dst_storage_id: ContainerType,
|
||||||
|
pub dst_container_index: u16,
|
||||||
|
#[brw(pad_before = 2)]
|
||||||
pub dst_stack: u32,
|
pub dst_stack: u32,
|
||||||
pub dst_catalog_id: u32,
|
pub dst_catalog_id: u32,
|
||||||
}
|
}
|
||||||
|
@ -36,16 +43,16 @@ mod tests {
|
||||||
let mut buffer = Cursor::new(&buffer);
|
let mut buffer = Cursor::new(&buffer);
|
||||||
|
|
||||||
let modify_inventory = InventoryModify::read_le(&mut buffer).unwrap();
|
let modify_inventory = InventoryModify::read_le(&mut buffer).unwrap();
|
||||||
assert_eq!(modify_inventory.context_id, 0x10000002);
|
assert_eq!(modify_inventory.context_id, 0x10000000);
|
||||||
assert_eq!(modify_inventory.operation_type, 70);
|
assert_eq!(modify_inventory.operation_type, 572);
|
||||||
assert_eq!(modify_inventory.src_actor_id, 0);
|
assert_eq!(modify_inventory.src_actor_id, 0);
|
||||||
assert_eq!(modify_inventory.src_storage_id, 1000);
|
assert_eq!(modify_inventory.src_storage_id, ContainerType::Equipped);
|
||||||
assert_eq!(modify_inventory.src_container_index, 4);
|
assert_eq!(modify_inventory.src_container_index, 3);
|
||||||
assert_eq!(modify_inventory.src_stack, 0);
|
assert_eq!(modify_inventory.src_stack, 1);
|
||||||
assert_eq!(modify_inventory.src_catalog_id, 0);
|
assert_eq!(modify_inventory.src_catalog_id, 0);
|
||||||
assert_eq!(modify_inventory.dst_actor_id, 209911808);
|
assert_eq!(modify_inventory.dst_actor_id, 0);
|
||||||
assert_eq!(modify_inventory.dst_storage_id, 0);
|
assert_eq!(modify_inventory.dst_storage_id, ContainerType::ArmouryBody);
|
||||||
assert_eq!(modify_inventory.dst_container_index, 96);
|
assert_eq!(modify_inventory.dst_container_index, 0);
|
||||||
assert_eq!(modify_inventory.dst_stack, 0);
|
assert_eq!(modify_inventory.dst_stack, 0);
|
||||||
assert_eq!(modify_inventory.dst_catalog_id, 0);
|
assert_eq!(modify_inventory.dst_catalog_id, 0);
|
||||||
}
|
}
|
||||||
|
|
|
@ -136,6 +136,7 @@ pub enum GameMasterCommandType {
|
||||||
ToggleInvisibility = 0xD,
|
ToggleInvisibility = 0xD,
|
||||||
ToggleWireframe = 0x26,
|
ToggleWireframe = 0x26,
|
||||||
ChangeTerritory = 0x58,
|
ChangeTerritory = 0x58,
|
||||||
|
GiveItem = 0xC8,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[binrw]
|
#[binrw]
|
||||||
|
|
Loading…
Add table
Reference in a new issue