From 66dff8ddb14b6efcb01a4f0c8dd2542bee038caa Mon Sep 17 00:00:00 2001 From: xeon Date: Wed, 4 Feb 2026 15:32:04 +0300 Subject: [PATCH] refactor(scene): factor out player location into a persistent component --- gamesv/src/fs/persistence.zig | 77 +++++++++++++++++++++++++ gamesv/src/logic/Player.zig | 2 + gamesv/src/logic/Player/Scene.zig | 9 +++ gamesv/src/logic/World.zig | 24 -------- gamesv/src/logic/messaging/map_mark.zig | 10 ++-- gamesv/src/logic/systems/scene.zig | 30 +++++----- 6 files changed, 109 insertions(+), 43 deletions(-) create mode 100644 gamesv/src/logic/Player/Scene.zig diff --git a/gamesv/src/fs/persistence.zig b/gamesv/src/fs/persistence.zig index af537f1..2d2672d 100644 --- a/gamesv/src/fs/persistence.zig +++ b/gamesv/src/fs/persistence.zig @@ -20,6 +20,9 @@ const char_bag_teams_file = "teams"; const char_bag_meta_file = "meta"; const item_bag_path = "item_bag"; const item_bag_weapon_depot_file = "weapon_depot"; +const scene_path = "scene"; +const scene_current_file = "current"; +const default_level = "map02_lv001"; const default_team: []const []const u8 = &.{ "chr_0026_lastrite", @@ -85,6 +88,11 @@ pub fn loadPlayer(io: Io, gpa: Allocator, assets: *const Assets, uid: []const u8 else => |e| return e, }; + result.scene = loadSceneComponent(io, data_dir, uid) catch |err| switch (err) { + error.NeedsReset => try createDefaultSceneComponent(io, data_dir, assets), + else => |e| return e, + }; + return result; } @@ -446,6 +454,75 @@ fn createDefaultBitsetComponent(io: Io, data_dir: Io.Dir, assets: *const Assets) return bitset; } +fn loadSceneComponent(io: Io, data_dir: Io.Dir, uid: []const u8) !Player.Scene { + const scene_dir = data_dir.openDir(io, scene_path, .{}) catch |err| switch (err) { + error.FileNotFound => return error.NeedsReset, + error.Canceled => |e| return e, + else => return error.InputOutput, + }; + + defer scene_dir.close(io); + + const current = fs.loadStruct(Player.Scene.Current, io, scene_dir, scene_current_file) catch |err| switch (err) { + inline error.FileNotFound, error.ChecksumMismatch, error.ReprSizeMismatch => |e| { + if (e == error.ChecksumMismatch) { + log.err( + "checksum mismatched for current scene data of player {s}, resetting to defaults.", + .{uid}, + ); + } + + if (e == error.ReprSizeMismatch) { + log.err( + "struct layout mismatched for current scene data of player {s}, resetting to defaults.", + .{uid}, + ); + } + + return error.NeedsReset; + }, + error.Canceled => |e| return e, + else => return error.InputOutput, + }; + + return .{ .current = current }; +} + +fn createDefaultSceneComponent(io: Io, data_dir: Io.Dir, assets: *const Assets) !Player.Scene { + const scene_dir = data_dir.createDirPathOpen(io, scene_path, .{}) catch |err| switch (err) { + error.Canceled => |e| return e, + else => return error.InputOutput, + }; + + const default_level_config = assets.level_config_table.getPtr(default_level).?; + + const current: Player.Scene.Current = .{ + .level_id = default_level_config.idNum, + .position = .{ + default_level_config.playerInitPos.x, + default_level_config.playerInitPos.y, + default_level_config.playerInitPos.z, + }, + .rotation = .{ + default_level_config.playerInitRot.x, + default_level_config.playerInitRot.y, + default_level_config.playerInitRot.z, + }, + }; + + try fs.saveStruct(Player.Scene.Current, ¤t, io, scene_dir, scene_current_file); + return .{ .current = current }; +} + +pub fn saveSceneComponent(io: Io, data_dir: Io.Dir, component: *const Player.Scene) !void { + const scene_dir = data_dir.createDirPathOpen(io, scene_path, .{}) catch |err| switch (err) { + error.Canceled => |e| return e, + else => return error.InputOutput, + }; + + try fs.saveStruct(Player.Scene.Current, &component.current, io, scene_dir, scene_current_file); +} + fn loadArray( comptime T: type, io: Io, diff --git a/gamesv/src/logic/Player.zig b/gamesv/src/logic/Player.zig index 570de06..804b14c 100644 --- a/gamesv/src/logic/Player.zig +++ b/gamesv/src/logic/Player.zig @@ -10,6 +10,7 @@ pub const Unlock = @import("Player/Unlock.zig"); pub const CharBag = @import("Player/CharBag.zig"); pub const ItemBag = @import("Player/ItemBag.zig"); pub const Bitset = @import("Player/Bitset.zig"); +pub const Scene = @import("Player/Scene.zig"); base: Base, game_vars: GameVars, @@ -17,6 +18,7 @@ unlock: Unlock, char_bag: CharBag, item_bag: ItemBag, bitset: Bitset, +scene: Scene, pub fn deinit(player: *Player, gpa: Allocator) void { player.game_vars.deinit(gpa); diff --git a/gamesv/src/logic/Player/Scene.zig b/gamesv/src/logic/Player/Scene.zig new file mode 100644 index 0000000..8adfad3 --- /dev/null +++ b/gamesv/src/logic/Player/Scene.zig @@ -0,0 +1,9 @@ +const Scene = @This(); + +current: Current, + +pub const Current = struct { + level_id: i32, + position: [3]f32, + rotation: [3]f32, +}; diff --git a/gamesv/src/logic/World.zig b/gamesv/src/logic/World.zig index 75766f2..43aa83c 100644 --- a/gamesv/src/logic/World.zig +++ b/gamesv/src/logic/World.zig @@ -15,32 +15,10 @@ pub const PlayerId = struct { uid: mem.LimitedString(max_length), }; -// TODO: this can be made a persistent Player component. -pub const Location = struct { - const default_level = "map02_lv001"; - - level: i32, - position: [3]f32, - - fn createDefault(assets: *const Assets) Location { - const level_config = assets.level_config_table.getPtr(default_level).?; - - return .{ - .level = level_config.idNum, - .position = .{ - level_config.playerInitPos.x, - level_config.playerInitPos.y, - level_config.playerInitPos.z, - }, - }; - } -}; - player_id: PlayerId, session: *Session, // TODO: should it be here this way? Do we need an abstraction? res: logic.Resource, player: logic.Player, -location: Location, pub fn init( session: *Session, @@ -56,7 +34,6 @@ pub fn init( .session = session, .player = player, .res = .init(assets, io), - .location = .createDefault(assets), }; } @@ -71,7 +48,6 @@ pub const GetComponentError = error{ pub fn getComponentByType(world: *World, comptime T: type) GetComponentError!T { switch (T) { PlayerId => return world.player_id, - *Location, *const Location => return &world.location, *Session => return world.session, *logic.Resource.PingTimer => return &world.res.ping_timer, *const Assets => return world.res.assets, diff --git a/gamesv/src/logic/messaging/map_mark.zig b/gamesv/src/logic/messaging/map_mark.zig index 8de4024..db7bbcc 100644 --- a/gamesv/src/logic/messaging/map_mark.zig +++ b/gamesv/src/logic/messaging/map_mark.zig @@ -2,12 +2,14 @@ const std = @import("std"); const pb = @import("proto").pb; const logic = @import("../../logic.zig"); const Assets = @import("../../Assets.zig"); + +const Player = logic.Player; const messaging = logic.messaging; pub fn onSceneSetTrackPoint( request: messaging.Request(pb.CS_SCENE_SET_TRACK_POINT), assets: *const Assets, - location: *logic.World.Location, + scene: Player.Component(.scene), change_scene_tx: logic.event.Sender(.change_scene_begin), ) !void { const log = std.log.scoped(.scene_set_track_point); @@ -36,8 +38,8 @@ pub fn onSceneSetTrackPoint( return; }; - location.level = level_config.idNum; - location.position = .{ + scene.data.current.level_id = level_config.idNum; + scene.data.current.position = .{ validation_config.position.x, validation_config.position.y, validation_config.position.z, @@ -47,6 +49,6 @@ pub fn onSceneSetTrackPoint( log.info( "transitioning to scene '{s}', position: {any}", - .{ validation_config.sceneId, location.position }, + .{ validation_config.sceneId, scene.data.current.position }, ); } diff --git a/gamesv/src/logic/systems/scene.zig b/gamesv/src/logic/systems/scene.zig index f92b1ba..2abb0e3 100644 --- a/gamesv/src/logic/systems/scene.zig +++ b/gamesv/src/logic/systems/scene.zig @@ -19,26 +19,26 @@ pub fn enterSceneOnLogin( pub fn beginChangingScene( rx: logic.event.Receiver(.change_scene_begin), session: *Session, - base_comp: Player.Component(.base), - location: *const logic.World.Location, + base: Player.Component(.base), + scene: Player.Component(.scene), ) !void { _ = rx; const position: pb.VECTOR = .{ - .X = location.position[0], - .Y = location.position[1], - .Z = location.position[2], + .X = scene.data.current.position[0], + .Y = scene.data.current.position[1], + .Z = scene.data.current.position[2], }; try session.send(pb.SC_CHANGE_SCENE_BEGIN_NOTIFY{ - .scene_num_id = location.level, + .scene_num_id = scene.data.current.level_id, .position = position, .pass_through_data = .init, }); try session.send(pb.SC_ENTER_SCENE_NOTIFY{ - .role_id = base_comp.data.role_id, - .scene_num_id = location.level, + .role_id = base.data.role_id, + .scene_num_id = scene.data.current.level_id, .position = position, .pass_through_data = .init, }); @@ -62,8 +62,8 @@ pub fn syncSelfScene( rx: logic.event.Receiver(.sync_self_scene), session: *Session, arena: logic.Resource.Allocator(.arena), - char_bag: logic.Player.Component(.char_bag), - location: *const logic.World.Location, + char_bag: Player.Component(.char_bag), + scene: Player.Component(.scene), assets: *const Assets, ) !void { const reason: pb.SELF_INFO_REASON_TYPE = switch (rx.payload.reason) { @@ -72,16 +72,16 @@ pub fn syncSelfScene( }; const position: pb.VECTOR = .{ - .X = location.position[0], - .Y = location.position[1], - .Z = location.position[2], + .X = scene.data.current.position[0], + .Y = scene.data.current.position[1], + .Z = scene.data.current.position[2], }; const team_index = char_bag.data.meta.curr_team_index; const leader_index = char_bag.data.teams.items(.leader_index)[team_index]; var self_scene_info: pb.SC_SELF_SCENE_INFO = .{ - .scene_num_id = location.level, + .scene_num_id = scene.data.current.level_id, .self_info_reason = @intFromEnum(reason), .teamInfo = .{ .team_type = .CHAR_BAG_TEAM_TYPE_MAIN, @@ -111,7 +111,7 @@ pub fn syncSelfScene( .templateid = char_template_id, .position = position, .rotation = .{}, - .scene_num_id = location.level, + .scene_num_id = scene.data.current.level_id, }, };