6.6 KiB
title | date | draft | tags | series | summary | |||
---|---|---|---|---|---|---|---|---|
Kawari Progress Report 10 - Polish | 2025-05-10 | true |
|
|
This update is all about polish and making Kawari easier to setup/use. What's the point of an alternative server that's difficult to setup? |
This update is all about polish and making Kawari easier to setup/use. What's the point of an alternative server that's difficult to setup?
Better Artifacts
I have redone the structure of the artifacts, which was mostly putting the binaries in the top-level directory. The original web template files are now included, so they can be freely changed without recompiling:
[screenshot]
Additionally, there are now run scripts (that work!) for Windows and Linux to start all of Kawari's services. I have no clue how to write Batch scripts, so it ends up doing funny things like spawning a dozen console windows. If you're a Windows expert and know how to make that better, I would appreciate some help!
Better Web
I redone the web interface and slapped on Bootstrap styling since I'm too lazy to come up with my own CSS. It actually looks decently nice now:
[screenshot]
[screenshot]
Thanks to [sfss] for bug testing, and some minor issues with the Web/Launcher pages are now fixed.
Official Launcher
Kawari can now be used with the official launcher, making it super easy to use. For Windows, this only consists of dragging files into a folder - and Linux isn't that much harder. The setup instructions are located on the internal website:
[screenshot]
And here's how it looks like in the launcher:
[screenshot]
Breaking apart SqEx's launcher honestly has been the most fun in RE I've had in a while...
Scripting
I have expanded the breadth of things you can script, and I have it working on a variety of things now - such as:
- Inns
- Warps (including ferries, lifts, and exit doors.)
- Beds
- Commands
- Items
Additionally, events used to be hard-coded but can now be added dynamically through Lua:
registerEvent(721028, "tosort/UnendingJourney.lua")
registerEvent(721044, "tosort/CrystalBell.lua")
Note that the event IDs are declared when you register the function, so you can reuse the same script across multiple IDs:
--- all of these are simple, so they can be handled by the same script!
registerEvent(131082, "common/GenericWarp.lua")
registerEvent(131083, "common/GenericWarp.lua")
registerEvent(131084, "common/GenericWarp.lua")
The event ID is injected into the event script as EVENT_ID
, so scripts can be more generic:
player:play_scene(target, EVENT_ID, 00000, 8192, 0)
And of course a breadth of new, interesting player API to make these events functional:
--- go to the pre-defined warp (and switch the zone if nessecary)
player:warp(2)
--- stop this event and give control back to the player
player:finish_event(EVENT_ID)
--- set the player's class/job
player:set_classjob(1)
--- set the player's position
player:set_position({ x = 1, y = 2, z = 3 })
--- set the player's remake flag
player:set_remake_mode("EditAppearance")
To register new commands, define a new Lua script:
function onCommand(args, player)
local parts = split(args)
player:set_position({ x = tonumber(parts[1]), y = tonumber(parts[2]), z = tonumber(parts[3]) })
end
And then register it in Global.lua
:
registerCommand("setpos", "commands/debug/SetPos.lua")
I also want to give a reminder that the entire Lua interface is still a big WIP. At some point when it's more complete, I'll give it a complete redo. I didn't want to prematurely design an API that ended up being too limiting, so I'm bolting on new things instead.
Better Multiplayer
As seen in a previous update, multiplayer "worked" but it was bad. It pretty much only shown the other player's position and their initial appearance. I have greatly improved it, including propagating model id changes, emotes, poses, targeting and more:
I struggled with sending player rotations for a while because they changed the packet used to update actor positions in Endwalker(?) After fixing that, everything fell into place.
s
What's curious is that this means rotations are actually more accurate between clients, and I wondered if that's actually noticeable. So I tested it, "emulating" the old 255-value system and did a comparison:
I also implemented very basic zone isolation, so people won't phase-shift into your view despite being in a totally different place:
s
Fantasia
Everyone's favorite item/coping mechanism (yes, I really did use the same joke from the commit description) is now implemented, just for funsies. It doesn't remove the Fantasia from your inventory because we lack the Lua API for it:
Excel Data
This is just a technical tidbit, hence why it's at the end here. Kawari has to read more and more Excel data, and the code usually looks like this:
/// Returns the pop range object id that's associated with the warp id
pub fn get_warp(&mut self, warp_id: u32) -> (u32, u16) {
let exh = self.game_data.read_excel_sheet_header("Warp").unwrap();
let exd = self
.game_data
.read_excel_sheet("Warp", &exh, Language::English, 0)
.unwrap();
let ExcelRowKind::SingleRow(row) = &exd.get_row(warp_id).unwrap() else {
panic!("Expected a single row!")
};
let physis::exd::ColumnData::UInt32(pop_range_id) = &row.columns[0] else {
panic!("Unexpected type!");
};
let physis::exd::ColumnData::UInt16(zone_id) = &row.columns[1] else {
panic!("Unexpected type!");
};
(*pop_range_id, *zone_id)
}
🤢 Yeah, that's bad! We are associating columns with their indexes, and these tend to change over time. We also don't have that great error handling here, so I spent some time cleaning this up. Now it looks like this:
/// Returns the pop range object id that's associated with the warp id
pub fn get_warp(&mut self, warp_id: u32) -> Option<(u32, u16)> {
let sheet = WarpSheet::read_from(&mut self.game_data, Language::English)?;
let row = sheet.get_row(warp_id)?;
let pop_range_id = row.PopRange().into_u32()?;
let zone_id = row.TerritoryType().into_u16()?;
Some((*pop_range_id, *zone_id))
}
Some of this was fixed by improving the signature of the function itself (with Option
!) Another part of it is introducing a higher-level API for grabbing Excel data. I auto-generate a library called Icarus based on the great schema in EXDSchema, with EXDGen. While it only supports a small portion of the schema, it's more than enough for Kawari.