diff --git a/USAGE.md b/USAGE.md index 5450ee3..74f7846 100644 --- a/USAGE.md +++ b/USAGE.md @@ -113,6 +113,10 @@ These special debug commands start with `!` and are custom to Kawari. * `!nudge `: Teleport forward, back, up or down `distance` yalms. Specifying up or down will move the player up or down instead of forward or back. Examples: `!nudge 5 up` to move up 5 yalms, `!nudge 5` to move forward 5 yalms, `!nudge -5` to move backward 5 yalms. * `!festival `: Sets the festival in the current zone. Multiple festivals can be set together to create interesting effects. * `!reload`: Reloads `Global.lua` that is normally only loaded once at start-up. +* `!wireframe: Toggles the hidden GM wireframe rendering mode on or off.` +* `!invis: Toggles the hidden GM invisibility mode on or off for your character.` +* `!unlockaetheryte `: Unlock an aetheryte. For the first parameter, the literal words are required. +* `!teri `: Changes to the specified territory. ### GM commands diff --git a/resources/scripts/Global.lua b/resources/scripts/Global.lua index 8053150..f1962d0 100644 --- a/resources/scripts/Global.lua +++ b/resources/scripts/Global.lua @@ -86,3 +86,8 @@ registerCommand("nudge", "commands/debug/Nudge.lua") registerCommand("festival", "commands/debug/Festival.lua") registerCommand("permtest", "commands/debug/PermissionTest.lua") registerCommand("reload", "commands/debug/Reload.lua") +registerCommand("unlockaction", "commands/debug/UnlockAction.lua") +registerCommand("wireframe", "commands/debug/ToggleWireframe.lua") +registerCommand("invis", "commands/debug/ToggleInvisibility.lua") +registerCommand("unlockaetheryte", "commands/debug/UnlockAetheryte.lua") +registerCommand("teri", "commands/debug/ChangeTerritory.lua") diff --git a/resources/scripts/commands/debug/ChangeTerritory.lua b/resources/scripts/commands/debug/ChangeTerritory.lua new file mode 100644 index 0000000..b2b0135 --- /dev/null +++ b/resources/scripts/commands/debug/ChangeTerritory.lua @@ -0,0 +1,22 @@ +required_rank = GM_RANK_DEBUG + +function onCommand(args, player) + local parts = split(args) + local argc = table.getn(parts) + local sender = "[teri] " + local usage = "\nThis command moves the user to a new zone/territory.\nUsage: !teri " + + if argc == 0 then + player:send_message(sender.."A territory id is required to use this command."..usage) + end + + local id = tonumber(parts[1]) + + if not id or id < 0 or id > 65535 then -- Must be in range of unsigned 16-bit value + player:send_message(sender.."Error parsing territory id! Make sure your input is an integer between 0 and 65535."..usage) + return + end + + player:change_territory(id) + player:send_message(string.format("%s Changing territory to %s.", sender, id)) +end diff --git a/resources/scripts/commands/debug/SetSpeed.lua b/resources/scripts/commands/debug/SetSpeed.lua new file mode 100644 index 0000000..6ddee89 --- /dev/null +++ b/resources/scripts/commands/debug/SetSpeed.lua @@ -0,0 +1,26 @@ +required_rank = GM_RANK_DEBUG + +function onCommand(args, player) + local parts = split(args) + local argc = table.getn(parts) + local sender = "[setspeed] " + local usage = "\nThis command sets the user's speed to a desired multiplier.\nUsage: !setspeed " + local SPEED_MAX = 10 -- Arbitrary, but it's more or less unplayable even at this amount + local speed_multiplier = tonumber(parts[1]) + + if argc == 1 and not speed_multiplier then + player:send_message(sender.."Error parsing speed multiplier! Make sure the multiplier is an integer."..usage) + return + elseif argc == 0 then + speed_multiplier = 1 + end + + if speed_multiplier <= 0 then + speed_multiplier = 1 + elseif speed_multiplier > SPEED_MAX then + speed_multiplier = SPEED_MAX + end + + player:set_speed(speed_multiplier) + player:send_message(string.format("%sSpeed multiplier set to %s.", sender, speed_multiplier)) +end diff --git a/resources/scripts/commands/debug/ToggleInvisibility.lua b/resources/scripts/commands/debug/ToggleInvisibility.lua new file mode 100644 index 0000000..3277f4c --- /dev/null +++ b/resources/scripts/commands/debug/ToggleInvisibility.lua @@ -0,0 +1,11 @@ +required_rank = GM_RANK_DEBUG + +function onCommand(args, player) + local parts = split(args) + local argc = table.getn(parts) + local sender = "[invis] " + local usage = "\nThis command makes the user invisible to all other actors." + + player:toggle_invisibility() + player:send_message(sender.."Invisibility toggled.") +end diff --git a/resources/scripts/commands/debug/ToggleWireframe.lua b/resources/scripts/commands/debug/ToggleWireframe.lua new file mode 100644 index 0000000..978c6c4 --- /dev/null +++ b/resources/scripts/commands/debug/ToggleWireframe.lua @@ -0,0 +1,11 @@ +required_rank = GM_RANK_DEBUG + +function onCommand(args, player) + local parts = split(args) + local argc = table.getn(parts) + local sender = "[wireframe] " + local usage = "\nThis command allows the user to view the world in wireframe mode." + + player:toggle_wireframe() + player:send_message(sender.."Wireframe mode toggled.") +end diff --git a/resources/scripts/commands/debug/UnlockAction.lua b/resources/scripts/commands/debug/UnlockAction.lua new file mode 100644 index 0000000..a8a7ccf --- /dev/null +++ b/resources/scripts/commands/debug/UnlockAction.lua @@ -0,0 +1,23 @@ +required_rank = GM_RANK_DEBUG + +function onCommand(args, player) + local parts = split(args) + local argc = table.getn(parts) + local sender = "[unlockaction] " + local usage = "\nThis command teaches the user an action.\nUsage: !useaction " + + if argc < 1 then + player:send_message(sender.."This command requires 1 parameter."..usage) + return + end + + local id = tonumber(parts[1]) + + if not id then + player:send_message(sender.."Error parsing action id! Make sure the id is an integer."..usage) + return + end + + player:unlock_action(id) + player:send_message(string.format("%s Action %s unlocked!", sender, id)) +end diff --git a/resources/scripts/commands/debug/UnlockAetheryte.lua b/resources/scripts/commands/debug/UnlockAetheryte.lua new file mode 100644 index 0000000..eb94fc5 --- /dev/null +++ b/resources/scripts/commands/debug/UnlockAetheryte.lua @@ -0,0 +1,39 @@ +required_rank = GM_RANK_DEBUG + +function onCommand(args, player) + local parts = split(args) + local argc = table.getn(parts) + local sender = "[unlockaetheryte] " + local usage = "\nThis command unlocks an aetheryte for the user.\nUsage: !unlockaetheryte " + + if argc < 2 then + player:send_message(sender.."This command requires two parameters."..usage) + return + end + + local on = parts[1] + + if on == "on" then + on = 0 + elseif on == "off" then + on = 1 + else + player:send_message(sender.."Error parsing first parameter. Must be either of the words: 'on' or 'off'."..usage) + return + end + + local id = tonumber(parts[2]) + + if not id then + id = parts[2] + if id == "all" then + id = 0 + else + player:send_message(sender.."Error parsing id parameter. Must be a territory id or the word 'all'."..usage) + return + end + end + + player:unlock_aetheryte(on, id) + player:send_message(string.format("%s Aetheryte(s) %s had their unlocked status changed!", sender, id)) +end diff --git a/src/world/chat_handler.rs b/src/world/chat_handler.rs index ec34e10..0ed6fd9 100644 --- a/src/world/chat_handler.rs +++ b/src/world/chat_handler.rs @@ -1,6 +1,6 @@ use crate::{ inventory::Storage, - ipc::zone::{ActorControlCategory, ActorControlSelf, ChatMessage, GameMasterRank}, + ipc::zone::{ChatMessage, GameMasterRank}, world::ToServer, }; @@ -44,16 +44,6 @@ impl ChatHandler { )) .await; } - "!unlockaction" => { - let parts: Vec<&str> = chat_message.message.split(' ').collect(); - let id = parts[1].parse::().unwrap(); - - connection - .actor_control_self(ActorControlSelf { - category: ActorControlCategory::ToggleActionUnlock { id, unlocked: true }, - }) - .await; - } "!equip" => { let (_, name) = chat_message.message.split_once(' ').unwrap(); diff --git a/src/world/connection.rs b/src/world/connection.rs index a2dfee6..788b427 100644 --- a/src/world/connection.rs +++ b/src/world/connection.rs @@ -568,6 +568,26 @@ impl ZoneConnection { .await; } + pub async fn toggle_invisibility(&mut self, invisible: bool) { + self.player_data.gm_invisible = invisible; + let ipc = ServerZoneIpcSegment { + op_code: ServerZoneIpcType::ActorControlSelf, + timestamp: timestamp_secs(), + data: ServerZoneIpcData::ActorControlSelf(ActorControlSelf { + category: ActorControlCategory::ToggleInvisibility { invisible }, + }), + ..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; + } + pub async fn process_lua_player(&mut self, player: &mut LuaPlayer) { for segment in &player.queued_segments { self.send_segment(segment.clone()).await; @@ -596,6 +616,9 @@ impl ZoneConnection { let mut lua = self.lua.lock().unwrap(); load_global_script(&mut lua); } + Task::ToggleInvisibility { invisible } => { + self.toggle_invisibility(*invisible).await; + } } } player.queued_tasks.clear(); diff --git a/src/world/lua.rs b/src/world/lua.rs index 32af325..36c4dbc 100644 --- a/src/world/lua.rs +++ b/src/world/lua.rs @@ -26,6 +26,7 @@ pub enum Task { SetClassJob { classjob_id: u8 }, WarpAetheryte { aetheryte_id: u32 }, ReloadScripts, + ToggleInvisibility { invisible: bool }, } #[derive(Default)] @@ -132,6 +133,58 @@ impl LuaPlayer { self.create_segment_self(op_code, data); } + fn unlock_action(&mut self, id: u32) { + let op_code = ServerZoneIpcType::ActorControlSelf; + let data = ServerZoneIpcData::ActorControlSelf(ActorControlSelf { + category: ActorControlCategory::ToggleActionUnlock { id, unlocked: true }, + }); + + self.create_segment_self(op_code, data); + } + + fn set_speed(&mut self, speed: u16) { + let op_code = ServerZoneIpcType::ActorControlSelf; + let data = ServerZoneIpcData::ActorControlSelf(ActorControlSelf { + category: ActorControlCategory::Flee { speed }, + }); + + self.create_segment_self(op_code, data); + } + + fn toggle_wireframe(&mut self) { + let op_code = ServerZoneIpcType::ActorControlSelf; + let data = ServerZoneIpcData::ActorControlSelf(ActorControlSelf { + category: ActorControlCategory::ToggleWireframeRendering(), + }); + + self.create_segment_self(op_code, data); + } + + fn unlock_aetheryte(&mut self, unlocked: u32, id: u32) { + let op_code = ServerZoneIpcType::ActorControlSelf; + let on = unlocked == 0; + if id == 0 { + for i in 1..239 { + let data = ServerZoneIpcData::ActorControlSelf(ActorControlSelf { + category: ActorControlCategory::LearnTeleport { + id: i, + unlocked: on, + }, + }); + + /* Unknown if this will make the server panic from a flood of packets. + * Needs testing once toggling aetherytes actually works. */ + self.create_segment_self(op_code.clone(), data); + } + } else { + let data = ServerZoneIpcData::ActorControlSelf(ActorControlSelf { + category: ActorControlCategory::LearnTeleport { id, unlocked: on }, + }); + + self.create_segment_self(op_code, data); + } + } + fn change_territory(&mut self, zone_id: u16) { self.queued_tasks.push(Task::ChangeTerritory { zone_id }); } @@ -163,6 +216,11 @@ impl LuaPlayer { fn reload_scripts(&mut self) { self.queued_tasks.push(Task::ReloadScripts); } + fn toggle_invisiblity(&mut self) { + self.queued_tasks.push(Task::ToggleInvisibility { + invisible: !self.player_data.gm_invisible, + }); + } } impl UserData for LuaPlayer { @@ -201,6 +259,26 @@ impl UserData for LuaPlayer { Ok(()) }, ); + methods.add_method_mut("unlock_aetheryte", |_, this, (unlock, id): (u32, u32)| { + this.unlock_aetheryte(unlock, id); + Ok(()) + }); + methods.add_method_mut("unlock_action", |_, this, action_id: u32| { + this.unlock_action(action_id); + Ok(()) + }); + methods.add_method_mut("set_speed", |_, this, speed: u16| { + this.set_speed(speed); + Ok(()) + }); + methods.add_method_mut("toggle_wireframe", |_, this, _: Value| { + this.toggle_wireframe(); + Ok(()) + }); + methods.add_method_mut("toggle_invisibility", |_, this, _: Value| { + this.toggle_invisiblity(); + Ok(()) + }); methods.add_method_mut("change_territory", |_, this, zone_id: u16| { this.change_territory(zone_id); Ok(())