mirror of
https://github.com/redstrate/Kawari.git
synced 2025-05-06 04:37:46 +00:00
Update IPC struct names and filenames to match their opcode name
This commit is contained in:
parent
056ab27d39
commit
ff33bd446a
17 changed files with 68 additions and 71 deletions
|
@ -15,7 +15,7 @@ use kawari::ipc::zone::{
|
|||
GameMasterRank, OnlineStatus, ServerZoneIpcData, ServerZoneIpcSegment, SocialListRequestType,
|
||||
};
|
||||
use kawari::ipc::zone::{
|
||||
ActorControlCategory, ActorControlSelf, CommonSpawn, PlayerEntry, PlayerSetup, PlayerSpawn,
|
||||
ActorControlCategory, ActorControlSelf, CommonSpawn, PlayerEntry, PlayerSpawn, PlayerStatus,
|
||||
SocialList,
|
||||
};
|
||||
use kawari::opcodes::{ServerChatIpcType, ServerZoneIpcType};
|
||||
|
@ -393,7 +393,7 @@ async fn client_loop(
|
|||
let ipc = ServerZoneIpcSegment {
|
||||
op_code: ServerZoneIpcType::PlayerStatus,
|
||||
timestamp: timestamp_secs(),
|
||||
data: ServerZoneIpcData::PlayerStatus(PlayerSetup {
|
||||
data: ServerZoneIpcData::PlayerStatus(PlayerStatus {
|
||||
content_id: connection.player_data.content_id,
|
||||
exp: [10000; 32],
|
||||
levels: [100; 32],
|
||||
|
@ -632,7 +632,7 @@ async fn client_loop(
|
|||
)
|
||||
.await
|
||||
}
|
||||
ClientZoneIpcData::GameMasterCommand { command, arg0, .. } => {
|
||||
ClientZoneIpcData::GMCommand { command, arg0, .. } => {
|
||||
tracing::info!("Got a game master command!");
|
||||
|
||||
match &command {
|
||||
|
|
|
@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize};
|
|||
|
||||
use crate::common::GameData;
|
||||
|
||||
use crate::ipc::zone::InventoryModify;
|
||||
use crate::ipc::zone::ItemOperation;
|
||||
|
||||
mod equipped;
|
||||
pub use equipped::EquippedStorage;
|
||||
|
@ -159,7 +159,7 @@ impl Inventory {
|
|||
self.equipped.left_ring = Item::new(1, 0x00003b1d);
|
||||
}
|
||||
|
||||
pub fn process_action(&mut self, action: &InventoryModify) {
|
||||
pub fn process_action(&mut self, action: &ItemOperation) {
|
||||
if action.operation_type == 78 {
|
||||
// discard
|
||||
let src_container = self.get_container_mut(&action.src_storage_id);
|
||||
|
|
|
@ -35,7 +35,7 @@ pub enum LobbyCharacterActionKind {
|
|||
|
||||
#[binrw]
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
pub struct LobbyCharacterAction {
|
||||
pub struct CharaMake {
|
||||
pub sequence: u64,
|
||||
pub content_id: u64,
|
||||
#[br(pad_before = 8)]
|
|
@ -1 +0,0 @@
|
|||
|
|
@ -18,7 +18,7 @@ pub struct ServiceAccount {
|
|||
|
||||
#[binrw]
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct LobbyServiceAccountList {
|
||||
pub struct LoginReply {
|
||||
pub sequence: u64,
|
||||
#[brw(pad_before = 1)]
|
||||
pub num_service_accounts: u8,
|
|
@ -1,18 +1,16 @@
|
|||
use binrw::binrw;
|
||||
|
||||
mod character_action;
|
||||
pub use character_action::{LobbyCharacterAction, LobbyCharacterActionKind};
|
||||
mod chara_make;
|
||||
pub use chara_make::{CharaMake, LobbyCharacterActionKind};
|
||||
|
||||
mod character_list;
|
||||
pub use character_list::{CharacterDetails, CharacterFlag, LobbyCharacterList};
|
||||
|
||||
mod client_version_info;
|
||||
mod service_login_reply;
|
||||
pub use service_login_reply::{CharacterDetails, CharacterFlag, ServiceLoginReply};
|
||||
|
||||
mod server_list;
|
||||
pub use server_list::{LobbyServerList, Server};
|
||||
pub use server_list::{DistWorldInfo, Server};
|
||||
|
||||
mod service_account_list;
|
||||
pub use service_account_list::{LobbyServiceAccountList, ServiceAccount};
|
||||
mod login_reply;
|
||||
pub use login_reply::{LoginReply, ServiceAccount};
|
||||
|
||||
use crate::{
|
||||
common::{read_string, write_string},
|
||||
|
@ -120,7 +118,7 @@ pub enum ClientLobbyIpcData {
|
|||
},
|
||||
/// Sent by the client when they request something about the character (e.g. deletion.)
|
||||
#[br(pre_assert(*magic == ClientLobbyIpcType::CharaMake))]
|
||||
CharaMake(LobbyCharacterAction),
|
||||
CharaMake(CharaMake),
|
||||
}
|
||||
|
||||
#[binrw]
|
||||
|
@ -137,9 +135,9 @@ pub enum ServerLobbyIpcData {
|
|||
unk1: u16,
|
||||
},
|
||||
/// Sent by the server to inform the client of their service accounts.
|
||||
LoginReply(LobbyServiceAccountList),
|
||||
LoginReply(LoginReply),
|
||||
/// Sent by the server to inform the client of their characters.
|
||||
ServiceLoginReply(LobbyCharacterList),
|
||||
ServiceLoginReply(ServiceLoginReply),
|
||||
// Assumed what this is, but probably incorrect
|
||||
CharaMakeReply {
|
||||
sequence: u64,
|
||||
|
@ -172,7 +170,7 @@ pub enum ServerLobbyIpcData {
|
|||
host: String,
|
||||
},
|
||||
/// Sent by the server to inform the client of their servers.
|
||||
DistWorldInfo(LobbyServerList),
|
||||
DistWorldInfo(DistWorldInfo),
|
||||
/// Sent by the server to inform the client of their retainers.
|
||||
DistRetainerInfo {
|
||||
// TODO: what is in here?
|
||||
|
@ -196,11 +194,11 @@ mod tests {
|
|||
let ipc_types = [
|
||||
(
|
||||
ServerLobbyIpcType::LoginReply,
|
||||
ServerLobbyIpcData::LoginReply(LobbyServiceAccountList::default()),
|
||||
ServerLobbyIpcData::LoginReply(LoginReply::default()),
|
||||
),
|
||||
(
|
||||
ServerLobbyIpcType::DistWorldInfo,
|
||||
ServerLobbyIpcData::DistWorldInfo(LobbyServerList::default()),
|
||||
ServerLobbyIpcData::DistWorldInfo(DistWorldInfo::default()),
|
||||
),
|
||||
(
|
||||
ServerLobbyIpcType::DistRetainerInfo,
|
||||
|
@ -208,7 +206,7 @@ mod tests {
|
|||
),
|
||||
(
|
||||
ServerLobbyIpcType::ServiceLoginReply,
|
||||
ServerLobbyIpcData::ServiceLoginReply(LobbyCharacterList::default()),
|
||||
ServerLobbyIpcData::ServiceLoginReply(ServiceLoginReply::default()),
|
||||
),
|
||||
(
|
||||
ServerLobbyIpcType::GameLoginReply,
|
||||
|
|
|
@ -20,7 +20,7 @@ pub struct Server {
|
|||
|
||||
#[binrw]
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct LobbyServerList {
|
||||
pub struct DistWorldInfo {
|
||||
pub sequence: u64,
|
||||
pub unk1: u16,
|
||||
pub offset: u16,
|
||||
|
|
|
@ -67,7 +67,7 @@ pub struct CharacterDetails {
|
|||
|
||||
#[binrw]
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct LobbyCharacterList {
|
||||
pub struct ServiceLoginReply {
|
||||
pub sequence: u64,
|
||||
pub counter: u8,
|
||||
#[brw(pad_after = 2)]
|
|
@ -5,7 +5,7 @@ use crate::common::ObjectTypeId;
|
|||
#[binrw]
|
||||
#[brw(little)]
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct EventPlay {
|
||||
pub struct EventScene {
|
||||
pub actor_id: ObjectTypeId,
|
||||
pub event_id: u32,
|
||||
pub scene: u16,
|
||||
|
@ -37,7 +37,7 @@ mod tests {
|
|||
let buffer = read(d).unwrap();
|
||||
let mut buffer = Cursor::new(&buffer);
|
||||
|
||||
let event_play = EventPlay::read_le(&mut buffer).unwrap();
|
||||
let event_play = EventScene::read_le(&mut buffer).unwrap();
|
||||
assert_eq!(
|
||||
event_play.actor_id,
|
||||
ObjectTypeId {
|
|
@ -4,7 +4,7 @@ use crate::inventory::ContainerType;
|
|||
|
||||
#[binrw]
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct InventoryModify {
|
||||
pub struct ItemOperation {
|
||||
pub context_id: u32,
|
||||
pub operation_type: u8,
|
||||
|
||||
|
@ -42,7 +42,7 @@ mod tests {
|
|||
let buffer = read(d).unwrap();
|
||||
let mut buffer = Cursor::new(&buffer);
|
||||
|
||||
let modify_inventory = InventoryModify::read_le(&mut buffer).unwrap();
|
||||
let modify_inventory = ItemOperation::read_le(&mut buffer).unwrap();
|
||||
assert_eq!(modify_inventory.context_id, 0x10000000);
|
||||
assert_eq!(modify_inventory.operation_type, 60);
|
||||
assert_eq!(modify_inventory.src_actor_id, 0);
|
|
@ -18,7 +18,7 @@ mod update_class_info;
|
|||
pub use update_class_info::UpdateClassInfo;
|
||||
|
||||
mod player_setup;
|
||||
pub use player_setup::PlayerSetup;
|
||||
pub use player_setup::PlayerStatus;
|
||||
|
||||
mod player_stats;
|
||||
pub use player_stats::PlayerStats;
|
||||
|
@ -53,8 +53,8 @@ pub use container_info::ContainerInfo;
|
|||
mod item_info;
|
||||
pub use item_info::ItemInfo;
|
||||
|
||||
mod event_play;
|
||||
pub use event_play::EventPlay;
|
||||
mod event_scene;
|
||||
pub use event_scene::EventScene;
|
||||
|
||||
mod event_start;
|
||||
pub use event_start::EventStart;
|
||||
|
@ -64,14 +64,14 @@ pub use action_result::{
|
|||
ActionEffect, ActionResult, DamageElement, DamageKind, DamageType, EffectKind,
|
||||
};
|
||||
|
||||
mod actor_move;
|
||||
pub use actor_move::ActorMove;
|
||||
mod r#move;
|
||||
pub use r#move::Move;
|
||||
|
||||
mod actor_set_pos;
|
||||
pub use actor_set_pos::ActorSetPos;
|
||||
mod warp;
|
||||
pub use warp::Warp;
|
||||
|
||||
mod inventory_modify;
|
||||
pub use inventory_modify::InventoryModify;
|
||||
mod item_operation;
|
||||
pub use item_operation::ItemOperation;
|
||||
|
||||
mod equip;
|
||||
pub use equip::Equip;
|
||||
|
@ -159,7 +159,7 @@ pub enum ServerZoneIpcData {
|
|||
/// Sent by the server containing character stats
|
||||
PlayerStats(PlayerStats),
|
||||
/// Sent by the server to setup the player on the client
|
||||
PlayerStatus(PlayerSetup),
|
||||
PlayerStatus(PlayerStatus),
|
||||
/// Sent by the server to setup class info
|
||||
UpdateClassInfo(UpdateClassInfo),
|
||||
/// Sent by the server to spawn the player in
|
||||
|
@ -170,7 +170,7 @@ pub enum ServerZoneIpcData {
|
|||
unk: [u8; 8],
|
||||
},
|
||||
/// Sent by the server to modify the client's position
|
||||
Warp(ActorSetPos),
|
||||
Warp(Warp),
|
||||
/// Sent by the server when they send a chat message
|
||||
ServerChatMessage {
|
||||
unk: u8, // channel?
|
||||
|
@ -187,7 +187,7 @@ pub enum ServerZoneIpcData {
|
|||
/// Sent by the server
|
||||
ActorControl(ActorControl),
|
||||
/// Sent by the server
|
||||
Move(ActorMove),
|
||||
Move(Move),
|
||||
/// Sent by the server in response to SocialListRequest
|
||||
SocialList(SocialList),
|
||||
/// Sent by the server to spawn an NPC
|
||||
|
@ -201,7 +201,7 @@ pub enum ServerZoneIpcData {
|
|||
/// Sent to inform the client of container status
|
||||
ContainerInfo(ContainerInfo),
|
||||
/// Sent to tell the client to play a scene
|
||||
EventScene(EventPlay),
|
||||
EventScene(EventScene),
|
||||
/// Sent to tell the client to load a scene, but not play it
|
||||
EventStart(EventStart),
|
||||
/// Sent to update an actor's hp & mp values
|
||||
|
@ -308,7 +308,7 @@ pub enum ClientZoneIpcData {
|
|||
ChatMessage(ChatMessage),
|
||||
/// Sent by the client when they send a GM command. This can only be sent by the client if they are sent a GM rank.
|
||||
#[br(pre_assert(*magic == ClientZoneIpcType::GMCommand))]
|
||||
GameMasterCommand {
|
||||
GMCommand {
|
||||
#[brw(pad_after = 3)] // padding
|
||||
command: GameMasterCommandType,
|
||||
arg0: u32,
|
||||
|
@ -353,7 +353,7 @@ pub enum ClientZoneIpcData {
|
|||
unk: [u8; 16], // TODO: unknown
|
||||
},
|
||||
#[br(pre_assert(*magic == ClientZoneIpcType::ItemOperation))]
|
||||
ItemOperation(InventoryModify),
|
||||
ItemOperation(ItemOperation),
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -392,7 +392,7 @@ mod tests {
|
|||
),
|
||||
(
|
||||
ServerZoneIpcType::PlayerStatus,
|
||||
ServerZoneIpcData::PlayerStatus(PlayerSetup::default()),
|
||||
ServerZoneIpcData::PlayerStatus(PlayerStatus::default()),
|
||||
),
|
||||
(
|
||||
ServerZoneIpcType::UpdateClassInfo,
|
||||
|
@ -408,7 +408,7 @@ mod tests {
|
|||
),
|
||||
(
|
||||
ServerZoneIpcType::Warp,
|
||||
ServerZoneIpcData::Warp(ActorSetPos::default()),
|
||||
ServerZoneIpcData::Warp(Warp::default()),
|
||||
),
|
||||
(
|
||||
ServerZoneIpcType::ServerChatMessage,
|
||||
|
@ -427,7 +427,7 @@ mod tests {
|
|||
),
|
||||
(
|
||||
ServerZoneIpcType::Move,
|
||||
ServerZoneIpcData::Move(ActorMove::default()),
|
||||
ServerZoneIpcData::Move(Move::default()),
|
||||
),
|
||||
(
|
||||
ServerZoneIpcType::NpcSpawn,
|
||||
|
@ -451,7 +451,7 @@ mod tests {
|
|||
),
|
||||
(
|
||||
ServerZoneIpcType::EventScene,
|
||||
ServerZoneIpcData::EventScene(EventPlay::default()),
|
||||
ServerZoneIpcData::EventScene(EventScene::default()),
|
||||
),
|
||||
(
|
||||
ServerZoneIpcType::EventStart,
|
||||
|
|
|
@ -7,7 +7,7 @@ use crate::common::{
|
|||
|
||||
#[binrw]
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct ActorMove {
|
||||
pub struct Move {
|
||||
#[bw(map = write_packed_rotation_float)]
|
||||
#[br(map = read_packed_rotation_float)]
|
||||
pub rotation: f32,
|
|
@ -4,7 +4,7 @@ use crate::common::{CHAR_NAME_MAX_LENGTH, read_string, write_string};
|
|||
|
||||
#[binrw]
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct PlayerSetup {
|
||||
pub struct PlayerStatus {
|
||||
pub content_id: u64,
|
||||
pub crest: u64,
|
||||
pub unknown10: u64,
|
||||
|
@ -180,7 +180,7 @@ mod tests {
|
|||
let buffer = read(d).unwrap();
|
||||
let mut buffer = Cursor::new(&buffer);
|
||||
|
||||
let player_setup = PlayerSetup::read_le(&mut buffer).unwrap();
|
||||
let player_setup = PlayerStatus::read_le(&mut buffer).unwrap();
|
||||
assert_eq!(player_setup.content_id, 0x004000174c50560d);
|
||||
assert_eq!(player_setup.char_id, 0x107476e7);
|
||||
assert_eq!(player_setup.name, "Lavenaa Warren");
|
||||
|
|
|
@ -4,7 +4,7 @@ use crate::common::Position;
|
|||
|
||||
#[binrw]
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct ActorSetPos {
|
||||
pub struct Warp {
|
||||
pub dir: u16,
|
||||
pub warp_type: u8,
|
||||
pub warp_type_arg: u8,
|
|
@ -17,9 +17,9 @@ use crate::{
|
|||
|
||||
use crate::ipc::kawari::{CustomIpcData, CustomIpcSegment, CustomIpcType};
|
||||
use crate::ipc::lobby::{
|
||||
CharacterDetails, ClientLobbyIpcSegment, LobbyCharacterAction, LobbyCharacterActionKind,
|
||||
LobbyCharacterList, LobbyServerList, LobbyServiceAccountList, Server, ServerLobbyIpcData,
|
||||
ServerLobbyIpcSegment, ServiceAccount,
|
||||
CharaMake, CharacterDetails, ClientLobbyIpcSegment, DistWorldInfo, LobbyCharacterActionKind,
|
||||
LoginReply, Server, ServerLobbyIpcData, ServerLobbyIpcSegment, ServiceAccount,
|
||||
ServiceLoginReply,
|
||||
};
|
||||
|
||||
/// Represents a single connection between an instance of the client and the lobby server.
|
||||
|
@ -79,7 +79,7 @@ impl LobbyConnection {
|
|||
|
||||
/// Send the service account list to the client.
|
||||
pub async fn send_account_list(&mut self) {
|
||||
let service_account_list = ServerLobbyIpcData::LoginReply(LobbyServiceAccountList {
|
||||
let service_account_list = ServerLobbyIpcData::LoginReply(LoginReply {
|
||||
sequence: 0,
|
||||
num_service_accounts: self.service_accounts.len() as u8,
|
||||
unk1: 3,
|
||||
|
@ -122,7 +122,7 @@ impl LobbyConnection {
|
|||
// add any empty boys
|
||||
servers.resize(6, Server::default());
|
||||
|
||||
let lobby_server_list = ServerLobbyIpcData::DistWorldInfo(LobbyServerList {
|
||||
let lobby_server_list = ServerLobbyIpcData::DistWorldInfo(DistWorldInfo {
|
||||
sequence: 0,
|
||||
unk1: 1,
|
||||
offset: 0,
|
||||
|
@ -210,7 +210,7 @@ impl LobbyConnection {
|
|||
|
||||
let lobby_character_list = if i == 3 {
|
||||
// On the last packet, add the account-wide information
|
||||
LobbyCharacterList {
|
||||
ServiceLoginReply {
|
||||
sequence,
|
||||
counter: (i * 4) + 1, // TODO: why the + 1 here?
|
||||
num_in_packet: characters_in_packet.len() as u8,
|
||||
|
@ -231,7 +231,7 @@ impl LobbyConnection {
|
|||
characters: characters_in_packet,
|
||||
}
|
||||
} else {
|
||||
LobbyCharacterList {
|
||||
ServiceLoginReply {
|
||||
sequence,
|
||||
counter: i * 4,
|
||||
num_in_packet: characters_in_packet.len() as u8,
|
||||
|
@ -329,7 +329,7 @@ impl LobbyConnection {
|
|||
.await;
|
||||
}
|
||||
|
||||
pub async fn handle_character_action(&mut self, character_action: &LobbyCharacterAction) {
|
||||
pub async fn handle_character_action(&mut self, character_action: &CharaMake) {
|
||||
match &character_action.action {
|
||||
LobbyCharacterActionKind::ReserveName => {
|
||||
tracing::info!(
|
||||
|
|
|
@ -16,10 +16,10 @@ use crate::{
|
|||
ipc::{
|
||||
chat::ServerChatIpcSegment,
|
||||
zone::{
|
||||
ActorControlSelf, ActorMove, ActorSetPos, ClientZoneIpcSegment, CommonSpawn,
|
||||
ContainerInfo, DisplayFlag, Equip, InitZone, ItemInfo, NpcSpawn, ObjectKind,
|
||||
PlayerStats, PlayerSubKind, ServerZoneIpcData, ServerZoneIpcSegment, StatusEffect,
|
||||
StatusEffectList, UpdateClassInfo, WeatherChange,
|
||||
ActorControlSelf, ClientZoneIpcSegment, CommonSpawn, ContainerInfo, DisplayFlag, Equip,
|
||||
InitZone, ItemInfo, Move, NpcSpawn, ObjectKind, PlayerStats, PlayerSubKind,
|
||||
ServerZoneIpcData, ServerZoneIpcSegment, StatusEffect, StatusEffectList,
|
||||
UpdateClassInfo, Warp, WeatherChange,
|
||||
},
|
||||
},
|
||||
opcodes::ServerZoneIpcType,
|
||||
|
@ -233,7 +233,7 @@ impl ZoneConnection {
|
|||
let ipc = ServerZoneIpcSegment {
|
||||
op_code: ServerZoneIpcType::Warp,
|
||||
timestamp: timestamp_secs(),
|
||||
data: ServerZoneIpcData::Warp(ActorSetPos {
|
||||
data: ServerZoneIpcData::Warp(Warp {
|
||||
position,
|
||||
..Default::default()
|
||||
}),
|
||||
|
@ -254,7 +254,7 @@ impl ZoneConnection {
|
|||
let ipc = ServerZoneIpcSegment {
|
||||
op_code: ServerZoneIpcType::Move,
|
||||
timestamp: timestamp_secs(),
|
||||
data: ServerZoneIpcData::Move(ActorMove {
|
||||
data: ServerZoneIpcData::Move(Move {
|
||||
rotation,
|
||||
dir_before_slip: 0x7F,
|
||||
flag1: 0,
|
||||
|
|
|
@ -3,8 +3,8 @@ use mlua::{FromLua, Lua, LuaSerdeExt, UserData, UserDataMethods, Value};
|
|||
use crate::{
|
||||
common::{ObjectId, ObjectTypeId, Position, timestamp_secs, workdefinitions::RemakeMode},
|
||||
ipc::zone::{
|
||||
ActionEffect, ActorSetPos, DamageElement, DamageKind, DamageType, EffectKind, EventPlay,
|
||||
ServerZoneIpcData, ServerZoneIpcSegment,
|
||||
ActionEffect, DamageElement, DamageKind, DamageType, EffectKind, EventScene,
|
||||
ServerZoneIpcData, ServerZoneIpcSegment, Warp,
|
||||
},
|
||||
opcodes::ServerZoneIpcType,
|
||||
packet::{PacketSegment, SegmentData, SegmentType},
|
||||
|
@ -60,7 +60,7 @@ impl LuaPlayer {
|
|||
op_code: ServerZoneIpcType::EventScene,
|
||||
option: 0,
|
||||
timestamp: timestamp_secs(),
|
||||
data: ServerZoneIpcData::EventScene(EventPlay {
|
||||
data: ServerZoneIpcData::EventScene(EventScene {
|
||||
actor_id: ObjectTypeId {
|
||||
object_id: ObjectId(self.player_data.actor_id),
|
||||
object_type: 0,
|
||||
|
@ -85,7 +85,7 @@ impl LuaPlayer {
|
|||
let ipc = ServerZoneIpcSegment {
|
||||
op_code: ServerZoneIpcType::Warp,
|
||||
timestamp: timestamp_secs(),
|
||||
data: ServerZoneIpcData::Warp(ActorSetPos {
|
||||
data: ServerZoneIpcData::Warp(Warp {
|
||||
position,
|
||||
..Default::default()
|
||||
}),
|
||||
|
|
Loading…
Add table
Reference in a new issue