mirror of
https://github.com/redstrate/Kawari.git
synced 2025-06-30 11:47:45 +00:00
Port GM commands to Lua
This removes a ton of implementation overlap between the two command systems. For example, we had two different implementations of unlocking aetherytes which is just unnecessary. On the flipside, this makes implementing new GM commands just as easy as writing debug ones. I moved the existing debug Lua implementations into their GM counterparts and updated the USAGE accordingly.
This commit is contained in:
parent
2cbf70fbe5
commit
6951f9448d
21 changed files with 307 additions and 247 deletions
4
USAGE.md
4
USAGE.md
|
@ -113,10 +113,6 @@ These special debug commands start with `!` and are custom to Kawari.
|
||||||
* `!nudge <distance> <up/down (optional)>`: 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.
|
* `!nudge <distance> <up/down (optional)>`: 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 <id1> <id2> <id3> <id4>`: Sets the festival in the current zone. Multiple festivals can be set together to create interesting effects.
|
* `!festival <id1> <id2> <id3> <id4>`: 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.
|
* `!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 <on/off> <id>`: Unlock an aetheryte. For the first parameter, the literal words are required.
|
|
||||||
* `!teri <id>`: Changes to the specified territory.
|
|
||||||
* `!finishevent`: Forcefully finishes the current event, useful if the script has an error and you're stuck talking to something.
|
* `!finishevent`: Forcefully finishes the current event, useful if the script has an error and you're stuck talking to something.
|
||||||
|
|
||||||
### GM commands
|
### GM commands
|
||||||
|
|
|
@ -1,16 +1,41 @@
|
||||||
DBG_DIR = "commands/debug/"
|
DBG_DIR = "commands/debug/"
|
||||||
|
GM_DIR = "commands/gm/"
|
||||||
|
|
||||||
|
-- GM commands
|
||||||
|
|
||||||
|
GM_SET_LEVEL = 1
|
||||||
|
GM_CHANGE_WEATHER = 6
|
||||||
|
GM_SPEED = 9
|
||||||
|
GM_INVISIBILITY = 13
|
||||||
|
GM_AETHERYTE = 94
|
||||||
|
GM_EXP = 104
|
||||||
|
GM_ORCHESTRION = 116
|
||||||
|
GM_GIVE_ITEM = 200
|
||||||
|
GM_GIL = 201
|
||||||
|
GM_WIREFRAME = 550
|
||||||
|
GM_TERRITORY = 600
|
||||||
|
GM_TERRITORY_INFO = 605
|
||||||
|
|
||||||
|
registerGMCommand(GM_SET_LEVEL, GM_DIR.."SetLevel.lua")
|
||||||
|
registerGMCommand(GM_CHANGE_WEATHER, GM_DIR.."ChangeWeather.lua")
|
||||||
|
registerGMCommand(GM_SPEED, GM_DIR.."SetSpeed.lua")
|
||||||
|
registerGMCommand(GM_INVISIBILITY, GM_DIR.."ToggleInvisibility.lua")
|
||||||
|
registerGMCommand(GM_AETHERYTE, GM_DIR.."UnlockAetheryte.lua")
|
||||||
|
registerGMCommand(GM_EXP, GM_DIR.."Exp.lua")
|
||||||
|
registerGMCommand(GM_ORCHESTRION, GM_DIR.."Orchestrion.lua")
|
||||||
|
registerGMCommand(GM_GIVE_ITEM, GM_DIR.."GiveItem.lua")
|
||||||
|
registerGMCommand(GM_GIL, GM_DIR.."Gil.lua")
|
||||||
|
registerGMCommand(GM_WIREFRAME, GM_DIR.."ToggleWireframe.lua")
|
||||||
|
registerGMCommand(GM_TERRITORY, GM_DIR.."ChangeTerritory.lua")
|
||||||
|
registerGMCommand(GM_TERRITORY_INFO, GM_DIR.."TerritoryInfo.lua")
|
||||||
|
|
||||||
|
-- Debug commands
|
||||||
-- Please keep these in alphabetical order!
|
-- Please keep these in alphabetical order!
|
||||||
|
|
||||||
registerCommand("classjob", DBG_DIR.."ClassJob.lua")
|
registerCommand("classjob", DBG_DIR.."ClassJob.lua")
|
||||||
registerCommand("festival", DBG_DIR.."Festival.lua")
|
registerCommand("festival", DBG_DIR.."Festival.lua")
|
||||||
registerCommand("invis", DBG_DIR.."ToggleInvisibility.lua")
|
|
||||||
registerCommand("nudge", DBG_DIR.."Nudge.lua")
|
registerCommand("nudge", DBG_DIR.."Nudge.lua")
|
||||||
registerCommand("ost", DBG_DIR.."OnScreenTest.lua")
|
registerCommand("ost", DBG_DIR.."OnScreenTest.lua")
|
||||||
registerCommand("permtest", DBG_DIR.."PermissionTest.lua")
|
registerCommand("permtest", DBG_DIR.."PermissionTest.lua")
|
||||||
registerCommand("setpos", DBG_DIR.."SetPos.lua")
|
registerCommand("setpos", DBG_DIR.."SetPos.lua")
|
||||||
registerCommand("setspeed", DBG_DIR.."SetSpeed.lua")
|
|
||||||
registerCommand("teri", DBG_DIR.."ChangeTerritory.lua")
|
|
||||||
registerCommand("unlock", DBG_DIR.."Unlock.lua")
|
registerCommand("unlock", DBG_DIR.."Unlock.lua")
|
||||||
registerCommand("unlockaetheryte", DBG_DIR.."UnlockAetheryte.lua")
|
|
||||||
registerCommand("wireframe", DBG_DIR.."ToggleWireframe.lua")
|
|
||||||
|
|
|
@ -1,23 +0,0 @@
|
||||||
required_rank = GM_RANK_DEBUG
|
|
||||||
command_sender = "[teri] "
|
|
||||||
|
|
||||||
function onCommand(args, player)
|
|
||||||
local parts = split(args)
|
|
||||||
local argc = #parts
|
|
||||||
local usage = "\nThis command moves the user to a new zone/territory.\nUsage: !teri <id>"
|
|
||||||
|
|
||||||
if argc == 0 then
|
|
||||||
printf(player, "A territory id is required to use this command."..usage)
|
|
||||||
return
|
|
||||||
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
|
|
||||||
printf(player, "Error parsing territory id! Make sure your input is an integer between 0 and 65535."..usage)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
player:change_territory(id)
|
|
||||||
printf(player, "Changing territory to %s.", id)
|
|
||||||
end
|
|
|
@ -1,39 +0,0 @@
|
||||||
required_rank = GM_RANK_DEBUG
|
|
||||||
command_sender = "[unlockaetheryte] "
|
|
||||||
|
|
||||||
function onCommand(args, player)
|
|
||||||
local parts = split(args)
|
|
||||||
local argc = #parts
|
|
||||||
local usage = "\nThis command unlocks an aetheryte for the user.\nUsage: !unlockaetheryte <on/off> <id>"
|
|
||||||
|
|
||||||
if argc < 2 then
|
|
||||||
printf(player, "This command requires two parameters."..usage)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
local on = parts[1]
|
|
||||||
|
|
||||||
if on == "on" then
|
|
||||||
on = 1
|
|
||||||
elseif on == "off" then
|
|
||||||
on = 0
|
|
||||||
else
|
|
||||||
printf(player, "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
|
|
||||||
printf(player, "Error parsing id parameter. Must be an aetheryte id or the word 'all'."..usage)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
player:unlock_aetheryte(on, id)
|
|
||||||
printf(player, "Aetheryte(s) %s had their unlocked status changed!", id)
|
|
||||||
end
|
|
9
resources/scripts/commands/gm/ChangeTerritory.lua
Normal file
9
resources/scripts/commands/gm/ChangeTerritory.lua
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
required_rank = GM_RANK_DEBUG
|
||||||
|
command_sender = "[teri] "
|
||||||
|
|
||||||
|
function onCommand(args, player)
|
||||||
|
local id = args[1]
|
||||||
|
|
||||||
|
player:change_territory(id)
|
||||||
|
printf(player, "Changing territory to %s.", id)
|
||||||
|
end
|
9
resources/scripts/commands/gm/ChangeWeather.lua
Normal file
9
resources/scripts/commands/gm/ChangeWeather.lua
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
required_rank = GM_RANK_DEBUG
|
||||||
|
command_sender = "[weather] "
|
||||||
|
|
||||||
|
function onCommand(args, player)
|
||||||
|
local id = args[1]
|
||||||
|
|
||||||
|
player:change_weather(id)
|
||||||
|
printf(player, "Changing weather to %s.", id)
|
||||||
|
end
|
0
resources/scripts/commands/gm/Exp.lua
Normal file
0
resources/scripts/commands/gm/Exp.lua
Normal file
9
resources/scripts/commands/gm/Gil.lua
Normal file
9
resources/scripts/commands/gm/Gil.lua
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
required_rank = GM_RANK_DEBUG
|
||||||
|
command_sender = "[gil] "
|
||||||
|
|
||||||
|
function onCommand(args, player)
|
||||||
|
local amount = args[1]
|
||||||
|
|
||||||
|
player:add_gil(amount)
|
||||||
|
printf(player, "Added %s gil.", amount)
|
||||||
|
end
|
10
resources/scripts/commands/gm/Orchestrion.lua
Normal file
10
resources/scripts/commands/gm/Orchestrion.lua
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
required_rank = GM_RANK_DEBUG
|
||||||
|
command_sender = "[orchestrion] "
|
||||||
|
|
||||||
|
function onCommand(args, player)
|
||||||
|
local on = args[1] -- TODO: reverse
|
||||||
|
local id = args[2]
|
||||||
|
|
||||||
|
player:unlock_aetheryte(on, id)
|
||||||
|
printf(player, "Orchestrion(s) %s had their unlocked status changed!", id)
|
||||||
|
end
|
5
resources/scripts/commands/gm/SetLevel.lua
Normal file
5
resources/scripts/commands/gm/SetLevel.lua
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
required_rank = GM_RANK_DEBUG
|
||||||
|
|
||||||
|
function onCommand(args, player)
|
||||||
|
player:set_level(args[1])
|
||||||
|
end
|
|
@ -2,18 +2,8 @@ required_rank = GM_RANK_DEBUG
|
||||||
command_sender = "[setspeed] "
|
command_sender = "[setspeed] "
|
||||||
|
|
||||||
function onCommand(args, player)
|
function onCommand(args, player)
|
||||||
local parts = split(args)
|
|
||||||
local argc = #parts
|
|
||||||
local usage = "\nThis command sets the user's speed to a desired multiplier.\nUsage: !setspeed <multiplier>"
|
|
||||||
local SPEED_MAX = 10 -- Arbitrary, but it's more or less unplayable even at this amount
|
local SPEED_MAX = 10 -- Arbitrary, but it's more or less unplayable even at this amount
|
||||||
local speed_multiplier = tonumber(parts[1])
|
local speed_multiplier = args[1]
|
||||||
|
|
||||||
if argc == 1 and not speed_multiplier then
|
|
||||||
printf(player, "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
|
if speed_multiplier <= 0 then
|
||||||
speed_multiplier = 1
|
speed_multiplier = 1
|
11
resources/scripts/commands/gm/TerritoryInfo.lua
Normal file
11
resources/scripts/commands/gm/TerritoryInfo.lua
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
required_rank = GM_RANK_DEBUG
|
||||||
|
command_sender = "[teri_info] "
|
||||||
|
|
||||||
|
function onCommand(args, player)
|
||||||
|
local teri_info = "Territory Info for zone "..player.zone.id..":"
|
||||||
|
local current_weather = "Current weather: "..player.zone.weather_id
|
||||||
|
local internal_name = "Internal name: "..player.zone.internal_name
|
||||||
|
local region_name = "Region name: "..player.zone.region_name
|
||||||
|
local place_name = "Place name: "..player.zone.place_name
|
||||||
|
printf(player, teri_info.."\n"..current_weather.."\n"..internal_name.."\n"..region_name.."\n"..place_name)
|
||||||
|
end
|
|
@ -2,8 +2,6 @@ required_rank = GM_RANK_DEBUG
|
||||||
command_sender = "[invis] "
|
command_sender = "[invis] "
|
||||||
|
|
||||||
function onCommand(args, player)
|
function onCommand(args, player)
|
||||||
local usage = "\nThis command makes the user invisible to all other actors."
|
|
||||||
|
|
||||||
player:toggle_invisibility()
|
player:toggle_invisibility()
|
||||||
printf(player, "Invisibility toggled.")
|
printf(player, "Invisibility toggled.")
|
||||||
end
|
end
|
|
@ -2,8 +2,6 @@ required_rank = GM_RANK_DEBUG
|
||||||
command_sender = "[wireframe] "
|
command_sender = "[wireframe] "
|
||||||
|
|
||||||
function onCommand(args, player)
|
function onCommand(args, player)
|
||||||
local usage = "\nThis command allows the user to view the world in wireframe mode."
|
|
||||||
|
|
||||||
player:toggle_wireframe()
|
player:toggle_wireframe()
|
||||||
printf(player, "Wireframe mode toggled.")
|
printf(player, "Wireframe mode toggled.")
|
||||||
end
|
end
|
10
resources/scripts/commands/gm/UnlockAetheryte.lua
Normal file
10
resources/scripts/commands/gm/UnlockAetheryte.lua
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
required_rank = GM_RANK_DEBUG
|
||||||
|
command_sender = "[unlockaetheryte] "
|
||||||
|
|
||||||
|
function onCommand(args, player)
|
||||||
|
local on = args[1] -- TODO: reverse
|
||||||
|
local id = args[2]
|
||||||
|
|
||||||
|
player:unlock_aetheryte(on, id)
|
||||||
|
printf(player, "Aetheryte(s) %s had their unlocked status changed!", id)
|
||||||
|
end
|
|
@ -3,24 +3,23 @@ use std::sync::{Arc, Mutex};
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
use kawari::RECEIVE_BUFFER_SIZE;
|
use kawari::RECEIVE_BUFFER_SIZE;
|
||||||
use kawari::common::{GameData, TerritoryNameKind, timestamp_secs};
|
use kawari::common::Position;
|
||||||
use kawari::common::{Position, value_to_flag_byte_index_value};
|
use kawari::common::{GameData, timestamp_secs};
|
||||||
use kawari::config::get_config;
|
use kawari::config::get_config;
|
||||||
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,
|
||||||
};
|
};
|
||||||
use kawari::ipc::zone::{
|
use kawari::ipc::zone::{
|
||||||
ClientTriggerCommand, ClientZoneIpcData, CommonSpawn, EventStart, GameMasterCommandType,
|
ClientTriggerCommand, ClientZoneIpcData, CommonSpawn, EventStart, GameMasterRank, OnlineStatus,
|
||||||
GameMasterRank, OnlineStatus, ServerZoneIpcData, ServerZoneIpcSegment, SocialListRequestType,
|
ServerZoneIpcData, ServerZoneIpcSegment, SocialListRequestType,
|
||||||
};
|
};
|
||||||
use kawari::opcodes::{ServerChatIpcType, ServerZoneIpcType};
|
use kawari::opcodes::{ServerChatIpcType, ServerZoneIpcType};
|
||||||
use kawari::packet::oodle::OodleNetwork;
|
use kawari::packet::oodle::OodleNetwork;
|
||||||
use kawari::packet::{
|
use kawari::packet::{
|
||||||
ConnectionType, PacketSegment, PacketState, SegmentData, SegmentType, send_keep_alive,
|
ConnectionType, PacketSegment, PacketState, SegmentData, SegmentType, send_keep_alive,
|
||||||
};
|
};
|
||||||
use kawari::world::{ChatHandler, ExtraLuaState, Zone, ZoneConnection, load_init_script};
|
use kawari::world::{ChatHandler, ExtraLuaState, LuaZone, Zone, ZoneConnection, load_init_script};
|
||||||
use kawari::world::{
|
use kawari::world::{
|
||||||
ClientHandle, Event, FromServer, LuaPlayer, PlayerData, ServerHandle, StatusEffects, ToServer,
|
ClientHandle, Event, FromServer, LuaPlayer, PlayerData, ServerHandle, StatusEffects, ToServer,
|
||||||
WorldDatabase, handle_custom_ipc, server_main_loop,
|
WorldDatabase, handle_custom_ipc, server_main_loop,
|
||||||
|
@ -617,136 +616,70 @@ async fn client_loop(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ClientZoneIpcData::GMCommand { command, arg0, arg1, .. } => {
|
ClientZoneIpcData::GMCommand { command, arg0, arg1, .. } => {
|
||||||
tracing::info!("Got a game master command!");
|
let lua = lua.lock().unwrap();
|
||||||
|
let state = lua.app_data_ref::<ExtraLuaState>().unwrap();
|
||||||
|
|
||||||
match &command {
|
if let Some(command_script) =
|
||||||
GameMasterCommandType::SetLevel => {
|
state.gm_command_scripts.get(command)
|
||||||
connection.player_data.set_current_level(*arg0 as i32);
|
{
|
||||||
connection.update_class_info().await;
|
let file_name = format!(
|
||||||
}
|
"{}/{}",
|
||||||
GameMasterCommandType::ChangeWeather => {
|
&config.world.scripts_location, command_script
|
||||||
connection.change_weather(*arg0 as u16).await
|
);
|
||||||
}
|
|
||||||
GameMasterCommandType::Speed => {
|
|
||||||
// TODO: Maybe allow setting the speed of a targeted player too?
|
|
||||||
connection
|
|
||||||
.actor_control_self(ActorControlSelf {
|
|
||||||
category:
|
|
||||||
ActorControlCategory::Flee {
|
|
||||||
speed: *arg0 as u16,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
GameMasterCommandType::ChangeTerritory => {
|
|
||||||
connection.change_zone(*arg0 as u16).await
|
|
||||||
}
|
|
||||||
GameMasterCommandType::ToggleInvisibility => {
|
|
||||||
connection.player_data.gm_invisible = !connection.player_data.gm_invisible;
|
|
||||||
connection
|
|
||||||
.actor_control_self(ActorControlSelf {
|
|
||||||
category:
|
|
||||||
ActorControlCategory::ToggleInvisibility {
|
|
||||||
invisible: connection.player_data.gm_invisible,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
GameMasterCommandType::ToggleWireframe => connection
|
|
||||||
.actor_control_self(ActorControlSelf {
|
|
||||||
category:
|
|
||||||
ActorControlCategory::ToggleWireframeRendering(),
|
|
||||||
})
|
|
||||||
.await,
|
|
||||||
GameMasterCommandType::GiveItem => {
|
|
||||||
connection.player_data.inventory.add_in_next_free_slot(Item { id: *arg0, quantity: 1 });
|
|
||||||
connection.send_inventory(false).await;
|
|
||||||
}
|
|
||||||
GameMasterCommandType::Orchestrion => {
|
|
||||||
let on = *arg0 == 1; // This command uses 1 for on, 2 for off.
|
|
||||||
let id = *arg1 as u16;
|
|
||||||
|
|
||||||
// id == 0 means "all"
|
let mut run_script = || -> mlua::Result<()> {
|
||||||
if id == 0 {
|
lua.scope(|scope| {
|
||||||
/* Currently 792 songs ingame.
|
let connection_data = scope
|
||||||
* Commented out because this learns literally zero songs
|
.create_userdata_ref_mut(&mut lua_player)?;
|
||||||
* for some unknown reason. */
|
|
||||||
/*for i in 1..793 {
|
|
||||||
let idd = i as u16;
|
|
||||||
connection.send_message("test!").await;
|
|
||||||
connection.actor_control_self(ActorControlSelf {
|
|
||||||
category: ActorControlCategory::ToggleOrchestrionUnlock { song_id: id, unlocked: on } }).await;
|
|
||||||
}*/
|
|
||||||
} else {
|
|
||||||
connection.actor_control_self(ActorControlSelf {
|
|
||||||
category: ActorControlCategory::ToggleOrchestrionUnlock { song_id: id, unlocked: on }
|
|
||||||
}).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
GameMasterCommandType::Aetheryte => {
|
|
||||||
let on = *arg0 == 0;
|
|
||||||
let id = *arg1;
|
|
||||||
|
|
||||||
// id == 0 means "all"
|
lua.load(
|
||||||
if id == 0 {
|
std::fs::read(&file_name)
|
||||||
for i in 1..239 {
|
.expect(format!("Failed to load script file {}!", &file_name).as_str()),
|
||||||
let (value, index) = value_to_flag_byte_index_value(i);
|
)
|
||||||
if on {
|
.set_name("@".to_string() + &file_name)
|
||||||
connection.player_data.aetherytes[index as usize] |= value;
|
.exec()?;
|
||||||
} else {
|
|
||||||
connection.player_data.aetherytes[index as usize] ^= value;
|
let required_rank = lua.globals().get("required_rank");
|
||||||
|
if let Err(error) = required_rank {
|
||||||
|
tracing::info!("Script is missing required_rank! Unable to run command, sending error to user. Additional information: {}", error);
|
||||||
|
let func: Function =
|
||||||
|
lua.globals().get("onCommandRequiredRankMissingError")?;
|
||||||
|
func.call::<()>((error.to_string(), connection_data))?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Reset state for future commands. Without this it'll stay set to the last value
|
||||||
|
* and allow other commands that omit required_rank to run, which is undesirable. */
|
||||||
|
lua.globals().set("required_rank", mlua::Value::Nil)?;
|
||||||
|
|
||||||
|
if connection.player_data.gm_rank as u8 >= required_rank? {
|
||||||
|
let func: Function =
|
||||||
|
lua.globals().get("onCommand")?;
|
||||||
|
func.call::<()>(([*arg0, *arg1], connection_data))?;
|
||||||
|
|
||||||
|
/* `command_sender` is an optional variable scripts can define to identify themselves in print messages.
|
||||||
|
* It's okay if this global isn't set. We also don't care what its value is, just that it exists.
|
||||||
|
* This is reset -after- running the command intentionally. Resetting beforehand will never display the command's identifier.
|
||||||
|
*/
|
||||||
|
let command_sender: Result<mlua::prelude::LuaValue, mlua::prelude::LuaError> = lua.globals().get("command_sender");
|
||||||
|
if let Ok(_) = command_sender {
|
||||||
|
lua.globals().set("command_sender", mlua::Value::Nil)?;
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
connection.actor_control_self(ActorControlSelf {
|
|
||||||
category: ActorControlCategory::LearnTeleport { id: i, unlocked: on } }).await;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let (value, index) = value_to_flag_byte_index_value(id);
|
|
||||||
if on {
|
|
||||||
connection.player_data.aetherytes[index as usize] |= value;
|
|
||||||
} else {
|
} else {
|
||||||
connection.player_data.aetherytes[index as usize] ^= value;
|
tracing::info!("User with account_id {} tried to invoke GM command {} with insufficient privileges!",
|
||||||
|
connection.player_data.account_id, command);
|
||||||
|
let func: Function =
|
||||||
|
lua.globals().get("onCommandRequiredRankInsufficientError")?;
|
||||||
|
func.call::<()>(connection_data)?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
connection.actor_control_self(ActorControlSelf {
|
if let Err(err) = run_script() {
|
||||||
category: ActorControlCategory::LearnTeleport { id, unlocked: on } }).await;
|
tracing::warn!("Lua error in {file_name}: {:?}", err);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
GameMasterCommandType::EXP => {
|
|
||||||
let amount = *arg0;
|
|
||||||
connection.player_data.set_current_exp(connection.player_data.current_exp() + amount);
|
|
||||||
connection.update_class_info().await;
|
|
||||||
}
|
|
||||||
|
|
||||||
GameMasterCommandType::TerritoryInfo => {
|
|
||||||
let id: u32 = connection.zone.as_ref().unwrap().id.into();
|
|
||||||
let weather_id;
|
|
||||||
let internal_name;
|
|
||||||
let place_name;
|
|
||||||
let region_name;
|
|
||||||
{
|
|
||||||
let mut game_data = connection.gamedata.lock().unwrap();
|
|
||||||
// TODO: Maybe the current weather should be cached somewhere like the zone id?
|
|
||||||
weather_id = game_data
|
|
||||||
.get_weather(id)
|
|
||||||
.unwrap_or(1) as u16;
|
|
||||||
let fallback = "<Unable to load name!>";
|
|
||||||
internal_name = game_data.get_territory_name(id, TerritoryNameKind::Internal).unwrap_or(fallback.to_string());
|
|
||||||
region_name = game_data.get_territory_name(id, TerritoryNameKind::Region).unwrap_or(fallback.to_string());
|
|
||||||
place_name = game_data.get_territory_name(id, TerritoryNameKind::Place).unwrap_or(fallback.to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
connection.send_message(format!(concat!("Territory Info for zone {}:\n",
|
|
||||||
"Current weather: {}\n",
|
|
||||||
"Internal name: {}\n",
|
|
||||||
"Region name: {}\n",
|
|
||||||
"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 {
|
||||||
|
@ -770,7 +703,7 @@ async fn client_loop(
|
||||||
|
|
||||||
// find the pop range on the other side
|
// find the pop range on the other side
|
||||||
let mut game_data = game_data.lock().unwrap();
|
let mut game_data = game_data.lock().unwrap();
|
||||||
let new_zone = Zone::load(&mut game_data.game_data, exit_box.territory_type);
|
let new_zone = Zone::load(&mut game_data, exit_box.territory_type);
|
||||||
let (destination_object, _) = new_zone
|
let (destination_object, _) = new_zone
|
||||||
.find_pop_range(exit_box.destination_instance_id)
|
.find_pop_range(exit_box.destination_instance_id)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -977,6 +910,16 @@ async fn client_loop(
|
||||||
// update lua player
|
// update lua player
|
||||||
lua_player.player_data = connection.player_data.clone();
|
lua_player.player_data = connection.player_data.clone();
|
||||||
lua_player.status_effects = connection.status_effects.clone();
|
lua_player.status_effects = connection.status_effects.clone();
|
||||||
|
|
||||||
|
if let Some(zone) = &connection.zone {
|
||||||
|
lua_player.zone_data = LuaZone {
|
||||||
|
zone_id: zone.id,
|
||||||
|
weather_id: connection.weather_id,
|
||||||
|
internal_name: zone.internal_name.clone(),
|
||||||
|
region_name: zone.region_name.clone(),
|
||||||
|
place_name: zone.place_name.clone(),
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
|
@ -1092,7 +1035,8 @@ async fn main() {
|
||||||
exit_position: None,
|
exit_position: None,
|
||||||
exit_rotation: None,
|
exit_rotation: None,
|
||||||
last_keep_alive: Instant::now(),
|
last_keep_alive: Instant::now(),
|
||||||
gracefully_logged_out: false
|
gracefully_logged_out: false,
|
||||||
|
weather_id: 0,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Some((mut socket, _)) = handle_rcon(&rcon_listener) => {
|
Some((mut socket, _)) = handle_rcon(&rcon_listener) => {
|
||||||
|
|
|
@ -148,24 +148,6 @@ impl Default for ServerZoneIpcSegment {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[binrw]
|
|
||||||
#[brw(repr = u8)]
|
|
||||||
#[derive(Clone, PartialEq, Debug)]
|
|
||||||
pub enum GameMasterCommandType {
|
|
||||||
SetLevel = 0x1,
|
|
||||||
ChangeWeather = 0x6,
|
|
||||||
Speed = 0x9,
|
|
||||||
ToggleInvisibility = 0xD,
|
|
||||||
ToggleWireframe = 0x26,
|
|
||||||
ChangeTerritory = 0x58,
|
|
||||||
EXP = 0x68,
|
|
||||||
Orchestrion = 0x74,
|
|
||||||
GiveItem = 0xC8,
|
|
||||||
Aetheryte = 0x5E,
|
|
||||||
TerritoryInfo = 0x25D,
|
|
||||||
Gil = 0xC9,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[binrw]
|
#[binrw]
|
||||||
#[br(import(magic: &ServerZoneIpcType, _size: &u32))]
|
#[br(import(magic: &ServerZoneIpcType, _size: &u32))]
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
@ -405,8 +387,7 @@ pub enum ClientZoneIpcData {
|
||||||
/// 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.
|
/// 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))]
|
#[br(pre_assert(*magic == ClientZoneIpcType::GMCommand))]
|
||||||
GMCommand {
|
GMCommand {
|
||||||
#[brw(pad_after = 3)] // padding
|
command: u32,
|
||||||
command: GameMasterCommandType,
|
|
||||||
arg0: u32,
|
arg0: u32,
|
||||||
arg1: u32,
|
arg1: u32,
|
||||||
arg2: u32,
|
arg2: u32,
|
||||||
|
|
|
@ -14,7 +14,7 @@ use crate::{
|
||||||
GameData, ObjectId, ObjectTypeId, Position, timestamp_secs, value_to_flag_byte_index_value,
|
GameData, ObjectId, ObjectTypeId, Position, timestamp_secs, value_to_flag_byte_index_value,
|
||||||
},
|
},
|
||||||
config::{WorldConfig, get_config},
|
config::{WorldConfig, get_config},
|
||||||
inventory::{ContainerType, Inventory, Item},
|
inventory::{ContainerType, Inventory, Item, Storage},
|
||||||
ipc::{
|
ipc::{
|
||||||
chat::ServerChatIpcSegment,
|
chat::ServerChatIpcSegment,
|
||||||
zone::{
|
zone::{
|
||||||
|
@ -46,6 +46,7 @@ pub struct ExtraLuaState {
|
||||||
pub action_scripts: HashMap<u32, String>,
|
pub action_scripts: HashMap<u32, String>,
|
||||||
pub event_scripts: HashMap<u32, String>,
|
pub event_scripts: HashMap<u32, String>,
|
||||||
pub command_scripts: HashMap<String, String>,
|
pub command_scripts: HashMap<String, String>,
|
||||||
|
pub gm_command_scripts: HashMap<u32, String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone)]
|
#[derive(Debug, Default, Clone)]
|
||||||
|
@ -132,6 +133,9 @@ pub struct ZoneConnection {
|
||||||
|
|
||||||
/// Whether the player was gracefully logged out
|
/// Whether the player was gracefully logged out
|
||||||
pub gracefully_logged_out: bool,
|
pub gracefully_logged_out: bool,
|
||||||
|
|
||||||
|
// TODO: really needs to be moved somewhere else
|
||||||
|
pub weather_id: u16,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ZoneConnection {
|
impl ZoneConnection {
|
||||||
|
@ -350,7 +354,7 @@ impl ZoneConnection {
|
||||||
// load the new zone now
|
// load the new zone now
|
||||||
{
|
{
|
||||||
let mut game_data = self.gamedata.lock().unwrap();
|
let mut game_data = self.gamedata.lock().unwrap();
|
||||||
self.zone = Some(Zone::load(&mut game_data.game_data, new_zone_id));
|
self.zone = Some(Zone::load(&mut game_data, new_zone_id));
|
||||||
}
|
}
|
||||||
|
|
||||||
self.player_data.zone_id = new_zone_id;
|
self.player_data.zone_id = new_zone_id;
|
||||||
|
@ -362,10 +366,9 @@ impl ZoneConnection {
|
||||||
{
|
{
|
||||||
let config = get_config();
|
let config = get_config();
|
||||||
|
|
||||||
let weather_id;
|
|
||||||
{
|
{
|
||||||
let mut game_data = self.gamedata.lock().unwrap();
|
let mut game_data = self.gamedata.lock().unwrap();
|
||||||
weather_id = game_data
|
self.weather_id = game_data
|
||||||
.get_weather(self.zone.as_ref().unwrap().id.into())
|
.get_weather(self.zone.as_ref().unwrap().id.into())
|
||||||
.unwrap_or(1) as u16;
|
.unwrap_or(1) as u16;
|
||||||
}
|
}
|
||||||
|
@ -375,7 +378,7 @@ impl ZoneConnection {
|
||||||
timestamp: timestamp_secs(),
|
timestamp: timestamp_secs(),
|
||||||
data: ServerZoneIpcData::InitZone(InitZone {
|
data: ServerZoneIpcData::InitZone(InitZone {
|
||||||
territory_type: self.zone.as_ref().unwrap().id,
|
territory_type: self.zone.as_ref().unwrap().id,
|
||||||
weather_id,
|
weather_id: self.weather_id,
|
||||||
obsfucation_mode: if config.world.enable_packet_obsfucation {
|
obsfucation_mode: if config.world.enable_packet_obsfucation {
|
||||||
OBFUSCATION_ENABLED_MODE
|
OBFUSCATION_ENABLED_MODE
|
||||||
} else {
|
} else {
|
||||||
|
@ -405,7 +408,7 @@ impl ZoneConnection {
|
||||||
.get_warp(warp_id)
|
.get_warp(warp_id)
|
||||||
.expect("Failed to find the warp!");
|
.expect("Failed to find the warp!");
|
||||||
|
|
||||||
let new_zone = Zone::load(&mut game_data.game_data, zone_id);
|
let new_zone = Zone::load(&mut game_data, zone_id);
|
||||||
|
|
||||||
// find it on the other side
|
// find it on the other side
|
||||||
if let Some((object, _)) = new_zone.find_pop_range(pop_range_id) {
|
if let Some((object, _)) = new_zone.find_pop_range(pop_range_id) {
|
||||||
|
@ -438,7 +441,7 @@ impl ZoneConnection {
|
||||||
.get_aetheryte(aetheryte_id)
|
.get_aetheryte(aetheryte_id)
|
||||||
.expect("Failed to find the aetheryte!");
|
.expect("Failed to find the aetheryte!");
|
||||||
|
|
||||||
let new_zone = Zone::load(&mut game_data.game_data, zone_id);
|
let new_zone = Zone::load(&mut game_data, zone_id);
|
||||||
|
|
||||||
// find it on the other side
|
// find it on the other side
|
||||||
if let Some((object, _)) = new_zone.find_pop_range(pop_range_id) {
|
if let Some((object, _)) = new_zone.find_pop_range(pop_range_id) {
|
||||||
|
@ -462,6 +465,8 @@ impl ZoneConnection {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn change_weather(&mut self, new_weather_id: u16) {
|
pub async fn change_weather(&mut self, new_weather_id: u16) {
|
||||||
|
self.weather_id = new_weather_id;
|
||||||
|
|
||||||
let ipc = ServerZoneIpcSegment {
|
let ipc = ServerZoneIpcSegment {
|
||||||
op_code: ServerZoneIpcType::WeatherId,
|
op_code: ServerZoneIpcType::WeatherId,
|
||||||
timestamp: timestamp_secs(),
|
timestamp: timestamp_secs(),
|
||||||
|
@ -729,6 +734,39 @@ impl ZoneConnection {
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Task::SetLevel { level } => {
|
||||||
|
self.player_data.set_current_level(*level);
|
||||||
|
self.update_class_info().await;
|
||||||
|
}
|
||||||
|
Task::ChangeWeather { id } => {
|
||||||
|
self.change_weather(*id).await;
|
||||||
|
}
|
||||||
|
Task::AddGil { amount } => {
|
||||||
|
self.player_data.inventory.currency.get_slot_mut(0).quantity += *amount as u32;
|
||||||
|
self.send_inventory(false).await;
|
||||||
|
}
|
||||||
|
Task::UnlockOrchestrion { id, on } => {
|
||||||
|
// id == 0 means "all"
|
||||||
|
if *id == 0 {
|
||||||
|
/* Currently 792 songs ingame.
|
||||||
|
* Commented out because this learns literally zero songs
|
||||||
|
* for some unknown reason. */
|
||||||
|
/*for i in 1..793 {
|
||||||
|
let idd = i as u16;
|
||||||
|
connection.send_message("test!").await;
|
||||||
|
connection.actor_control_self(ActorControlSelf {
|
||||||
|
category: ActorControlCategory::ToggleOrchestrionUnlock { song_id: id, unlocked: on } }).await;
|
||||||
|
}*/
|
||||||
|
} else {
|
||||||
|
self.actor_control_self(ActorControlSelf {
|
||||||
|
category: ActorControlCategory::ToggleOrchestrionUnlock {
|
||||||
|
song_id: *id,
|
||||||
|
unlocked: *on,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
player.queued_tasks.clear();
|
player.queued_tasks.clear();
|
||||||
|
|
|
@ -29,6 +29,29 @@ pub enum Task {
|
||||||
ToggleInvisibility { invisible: bool },
|
ToggleInvisibility { invisible: bool },
|
||||||
Unlock { id: u32 },
|
Unlock { id: u32 },
|
||||||
UnlockAetheryte { id: u32, on: bool },
|
UnlockAetheryte { id: u32, on: bool },
|
||||||
|
SetLevel { level: i32 },
|
||||||
|
ChangeWeather { id: u16 },
|
||||||
|
AddGil { amount: u32 },
|
||||||
|
UnlockOrchestrion { id: u16, on: bool },
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Clone)]
|
||||||
|
pub struct LuaZone {
|
||||||
|
pub zone_id: u16,
|
||||||
|
pub weather_id: u16,
|
||||||
|
pub internal_name: String,
|
||||||
|
pub region_name: String,
|
||||||
|
pub place_name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UserData for LuaZone {
|
||||||
|
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
|
||||||
|
fields.add_field_method_get("id", |_, this| Ok(this.zone_id));
|
||||||
|
fields.add_field_method_get("weather_id", |_, this| Ok(this.weather_id));
|
||||||
|
fields.add_field_method_get("internal_name", |_, this| Ok(this.internal_name.clone()));
|
||||||
|
fields.add_field_method_get("region_name", |_, this| Ok(this.region_name.clone()));
|
||||||
|
fields.add_field_method_get("place_name", |_, this| Ok(this.place_name.clone()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
@ -37,6 +60,7 @@ pub struct LuaPlayer {
|
||||||
pub status_effects: StatusEffects,
|
pub status_effects: StatusEffects,
|
||||||
pub queued_segments: Vec<PacketSegment<ServerZoneIpcSegment>>,
|
pub queued_segments: Vec<PacketSegment<ServerZoneIpcSegment>>,
|
||||||
pub queued_tasks: Vec<Task>,
|
pub queued_tasks: Vec<Task>,
|
||||||
|
pub zone_data: LuaZone,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LuaPlayer {
|
impl LuaPlayer {
|
||||||
|
@ -197,11 +221,31 @@ impl LuaPlayer {
|
||||||
fn reload_scripts(&mut self) {
|
fn reload_scripts(&mut self) {
|
||||||
self.queued_tasks.push(Task::ReloadScripts);
|
self.queued_tasks.push(Task::ReloadScripts);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn toggle_invisiblity(&mut self) {
|
fn toggle_invisiblity(&mut self) {
|
||||||
self.queued_tasks.push(Task::ToggleInvisibility {
|
self.queued_tasks.push(Task::ToggleInvisibility {
|
||||||
invisible: !self.player_data.gm_invisible,
|
invisible: !self.player_data.gm_invisible,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_level(&mut self, level: i32) {
|
||||||
|
self.queued_tasks.push(Task::SetLevel { level });
|
||||||
|
}
|
||||||
|
|
||||||
|
fn change_weather(&mut self, id: u16) {
|
||||||
|
self.queued_tasks.push(Task::ChangeWeather { id });
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_gil(&mut self, amount: u32) {
|
||||||
|
self.queued_tasks.push(Task::AddGil { amount });
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unlock_orchestrion(&mut self, unlocked: u32, id: u16) {
|
||||||
|
self.queued_tasks.push(Task::UnlockOrchestrion {
|
||||||
|
id,
|
||||||
|
on: unlocked == 1,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UserData for LuaPlayer {
|
impl UserData for LuaPlayer {
|
||||||
|
@ -317,6 +361,22 @@ impl UserData for LuaPlayer {
|
||||||
this.reload_scripts();
|
this.reload_scripts();
|
||||||
Ok(())
|
Ok(())
|
||||||
});
|
});
|
||||||
|
methods.add_method_mut("set_level", |_, this, level: i32| {
|
||||||
|
this.set_level(level);
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
methods.add_method_mut("change_weather", |_, this, id: u16| {
|
||||||
|
this.change_weather(id);
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
methods.add_method_mut("add_gil", |_, this, amount: u32| {
|
||||||
|
this.add_gil(amount);
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
methods.add_method_mut("unlock_orchestrion", |_, this, (unlock, id): (u32, u16)| {
|
||||||
|
this.unlock_orchestrion(unlock, id);
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
|
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
|
||||||
|
@ -332,6 +392,7 @@ impl UserData for LuaPlayer {
|
||||||
});
|
});
|
||||||
fields.add_field_method_get("rotation", |_, this| Ok(this.player_data.rotation));
|
fields.add_field_method_get("rotation", |_, this| Ok(this.player_data.rotation));
|
||||||
fields.add_field_method_get("position", |_, this| Ok(this.player_data.position));
|
fields.add_field_method_get("position", |_, this| Ok(this.player_data.position));
|
||||||
|
fields.add_field_method_get("zone", |_, this| Ok(this.zone_data.clone()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -441,11 +502,22 @@ pub fn load_init_script(lua: &mut Lua) -> mlua::Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
let register_gm_command_func =
|
||||||
|
lua.create_function(|lua, (command_type, command_script): (u32, String)| {
|
||||||
|
let mut state = lua.app_data_mut::<ExtraLuaState>().unwrap();
|
||||||
|
let _ = state
|
||||||
|
.gm_command_scripts
|
||||||
|
.insert(command_type, command_script);
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
|
||||||
lua.set_app_data(ExtraLuaState::default());
|
lua.set_app_data(ExtraLuaState::default());
|
||||||
lua.globals().set("registerAction", register_action_func)?;
|
lua.globals().set("registerAction", register_action_func)?;
|
||||||
lua.globals().set("registerEvent", register_event_func)?;
|
lua.globals().set("registerEvent", register_event_func)?;
|
||||||
lua.globals()
|
lua.globals()
|
||||||
.set("registerCommand", register_command_func)?;
|
.set("registerCommand", register_command_func)?;
|
||||||
|
lua.globals()
|
||||||
|
.set("registerGMCommand", register_gm_command_func)?;
|
||||||
|
|
||||||
let effectsbuilder_constructor = lua.create_function(|_, ()| Ok(EffectsBuilder::default()))?;
|
let effectsbuilder_constructor = lua.create_function(|_, ()| Ok(EffectsBuilder::default()))?;
|
||||||
lua.globals()
|
lua.globals()
|
||||||
|
|
|
@ -11,7 +11,7 @@ mod database;
|
||||||
pub use database::{CharacterData, WorldDatabase};
|
pub use database::{CharacterData, WorldDatabase};
|
||||||
|
|
||||||
mod lua;
|
mod lua;
|
||||||
pub use lua::{EffectsBuilder, LuaPlayer, load_init_script};
|
pub use lua::{EffectsBuilder, LuaPlayer, LuaZone, load_init_script};
|
||||||
|
|
||||||
mod event;
|
mod event;
|
||||||
pub use event::Event;
|
pub use event::Event;
|
||||||
|
|
|
@ -1,16 +1,20 @@
|
||||||
use icarus::TerritoryType::TerritoryTypeSheet;
|
use icarus::TerritoryType::TerritoryTypeSheet;
|
||||||
use physis::{
|
use physis::{
|
||||||
common::Language,
|
common::Language,
|
||||||
gamedata::GameData,
|
|
||||||
layer::{
|
layer::{
|
||||||
ExitRangeInstanceObject, InstanceObject, LayerEntryData, LayerGroup, PopRangeInstanceObject,
|
ExitRangeInstanceObject, InstanceObject, LayerEntryData, LayerGroup, PopRangeInstanceObject,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::common::{GameData, TerritoryNameKind};
|
||||||
|
|
||||||
/// Represents a loaded zone
|
/// Represents a loaded zone
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
pub struct Zone {
|
pub struct Zone {
|
||||||
pub id: u16,
|
pub id: u16,
|
||||||
|
pub internal_name: String,
|
||||||
|
pub region_name: String,
|
||||||
|
pub place_name: String,
|
||||||
planevent: Option<LayerGroup>,
|
planevent: Option<LayerGroup>,
|
||||||
vfx: Option<LayerGroup>,
|
vfx: Option<LayerGroup>,
|
||||||
planmap: Option<LayerGroup>,
|
planmap: Option<LayerGroup>,
|
||||||
|
@ -27,7 +31,8 @@ impl Zone {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
let sheet = TerritoryTypeSheet::read_from(game_data, Language::None).unwrap();
|
let sheet =
|
||||||
|
TerritoryTypeSheet::read_from(&mut game_data.game_data, Language::None).unwrap();
|
||||||
let Some(row) = sheet.get_row(id as u32) else {
|
let Some(row) = sheet.get_row(id as u32) else {
|
||||||
tracing::warn!("Invalid zone id {id}, allowing anyway...");
|
tracing::warn!("Invalid zone id {id}, allowing anyway...");
|
||||||
return zone;
|
return zone;
|
||||||
|
@ -42,7 +47,7 @@ impl Zone {
|
||||||
|
|
||||||
let mut load_lgb = |name: &str| -> Option<LayerGroup> {
|
let mut load_lgb = |name: &str| -> Option<LayerGroup> {
|
||||||
let path = format!("bg/{}/level/{}.lgb", &bg_path[..level_index], name);
|
let path = format!("bg/{}/level/{}.lgb", &bg_path[..level_index], name);
|
||||||
let lgb_file = game_data.extract(&path)?;
|
let lgb_file = game_data.game_data.extract(&path)?;
|
||||||
tracing::info!("Loading {path}");
|
tracing::info!("Loading {path}");
|
||||||
let lgb = LayerGroup::from_existing(&lgb_file);
|
let lgb = LayerGroup::from_existing(&lgb_file);
|
||||||
if lgb.is_none() {
|
if lgb.is_none() {
|
||||||
|
@ -61,6 +66,18 @@ impl Zone {
|
||||||
zone.sound = load_lgb("sound");
|
zone.sound = load_lgb("sound");
|
||||||
zone.planlive = load_lgb("planlive");
|
zone.planlive = load_lgb("planlive");
|
||||||
|
|
||||||
|
// load names
|
||||||
|
let fallback = "<Unable to load name!>";
|
||||||
|
zone.internal_name = game_data
|
||||||
|
.get_territory_name(id as u32, TerritoryNameKind::Internal)
|
||||||
|
.unwrap_or(fallback.to_string());
|
||||||
|
zone.region_name = game_data
|
||||||
|
.get_territory_name(id as u32, TerritoryNameKind::Region)
|
||||||
|
.unwrap_or(fallback.to_string());
|
||||||
|
zone.place_name = game_data
|
||||||
|
.get_territory_name(id as u32, TerritoryNameKind::Place)
|
||||||
|
.unwrap_or(fallback.to_string());
|
||||||
|
|
||||||
zone
|
zone
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue