diff --git a/gamesv/src/Session.zig b/gamesv/src/Session.zig index 597af12..5238b5f 100644 --- a/gamesv/src/Session.zig +++ b/gamesv/src/Session.zig @@ -123,7 +123,7 @@ pub fn process( .{ stream.socket.address, result.uid.view() }, ); - world = logic.World.init(&session, assets, result.uid, player, gpa, io); + world = logic.World.init(&session, assets, result.uid, player, io); receive_timeout = subsequent_request_timeout; logic.systems.triggerEvent(.{ .login = .{} }, &world.?, gpa) catch |err| switch (err) { diff --git a/gamesv/src/logic.zig b/gamesv/src/logic.zig index 179189c..716ea46 100644 --- a/gamesv/src/logic.zig +++ b/gamesv/src/logic.zig @@ -5,3 +5,4 @@ pub const messaging = @import("logic/messaging.zig"); pub const event = @import("logic/event.zig"); pub const systems = @import("logic/systems.zig"); pub const queries = @import("logic/queries.zig"); +pub const Level = @import("logic/Level.zig"); diff --git a/gamesv/src/logic/Level.zig b/gamesv/src/logic/Level.zig new file mode 100644 index 0000000..23719cf --- /dev/null +++ b/gamesv/src/logic/Level.zig @@ -0,0 +1,136 @@ +const Level = @This(); +const std = @import("std"); +pub const Object = @import("Level/Object.zig"); +const logic = @import("../logic.zig"); + +const max_team_size = logic.Player.CharBag.Team.slots_count; + +const Allocator = std.mem.Allocator; +const ArrayList = std.ArrayList; +const MultiArrayList = std.MultiArrayList; +const HashMap = std.AutoArrayHashMapUnmanaged; + +pub const init: Level = .{ + .object_id_map = .empty, + .objects = .empty, + .team_characters = @splat(.none), +}; + +object_id_map: HashMap(u64, u32), +objects: MultiArrayList(Object), +team_characters: [max_team_size]Object.NetID, + +pub const TeamIterator = struct { + ids: []const Object.NetID, + + pub fn next(iterator: *TeamIterator) Object.NetID { + while (iterator.ids.len != 0) { + defer iterator.ids = iterator.ids[1..]; + if (iterator.ids[0] != .none) return iterator.ids[0]; + } else return .none; + } +}; + +pub fn deinit(level: *Level, gpa: Allocator) void { + level.object_id_map.deinit(gpa); + level.objects.deinit(gpa); +} + +pub fn team(level: *Level) TeamIterator { + return .{ .ids = &level.team_characters }; +} + +pub fn countTeamMembers(level: *Level) usize { + var count: usize = 0; + for (level.team_characters) |id| { + if (id != .none) count += 1; + } + + return count; +} + +pub const SpawnParams = struct { + template_id: i32, + position: Object.Vector, + rotation: Object.Vector, + hp: f64, +}; + +pub const ExtraSpawnParams = union(enum) { + character: struct { + level: i32, + char_index: logic.Player.CharBag.CharIndex, + }, +}; + +pub const SpawnError = Allocator.Error; + +pub fn spawn( + level: *Level, + gpa: Allocator, + common_params: SpawnParams, + extra_params: ExtraSpawnParams, +) SpawnError!Object.Handle { + const index = try level.objects.addOne(gpa); + errdefer level.objects.swapRemove(index); + + const net_id = switch (extra_params) { + .character => |params| blk: { + const object_id = params.char_index.objectId(); + + const team_slot_index = level.countTeamMembers(); + std.debug.assert(team_slot_index < max_team_size); // Forgot to remove previous characters? + level.team_characters[team_slot_index] = @enumFromInt(object_id); + + break :blk object_id; + }, + }; + + try level.object_id_map.put(gpa, net_id, @intCast(index)); + + errdefer comptime unreachable; + + level.objects.set(index, .{ + .net_id = @enumFromInt(net_id), + .template_id = common_params.template_id, + .position = common_params.position, + .rotation = common_params.rotation, + .hp = common_params.hp, + .extra = switch (extra_params) { + .character => |extra| .{ .character = .{ + .level = extra.level, + .char_index = extra.char_index, + } }, + }, + }); + + return @enumFromInt(index); +} + +pub fn despawn(level: *Level, handle: Object.Handle) void { + const index = @intFromEnum(handle); + const prev_net_id = level.objects.items(.net_id)[index]; + + for (level.team_characters, 0..) |id, i| if (prev_net_id == id) { + level.team_characters[i] = .none; + }; + + _ = level.object_id_map.swapRemove(@intFromEnum(prev_net_id)); + level.objects.swapRemove(index); + + if (index < level.objects.len) { + const net_id = level.objects.items(.net_id)[index]; + level.object_id_map.getPtr(@intFromEnum(net_id)).?.* = index; + } +} + +pub fn reset(level: *Level) void { + level.team_characters = @splat(.none); + level.object_id_map.clearRetainingCapacity(); + level.objects.clearRetainingCapacity(); +} + +pub fn getObjectByNetId(level: *Level, net_id: Object.NetID) ?Object.Handle { + const index = level.object_id_map.get(@intFromEnum(net_id)) orelse return null; + return @enumFromInt(index); +} diff --git a/gamesv/src/logic/Level/Object.zig b/gamesv/src/logic/Level/Object.zig new file mode 100644 index 0000000..5b27825 --- /dev/null +++ b/gamesv/src/logic/Level/Object.zig @@ -0,0 +1,36 @@ +const std = @import("std"); +const logic = @import("../../logic.zig"); + +pub const NetID = enum(u64) { + none = 0, + _, +}; + +pub const Handle = enum(u32) { + _, +}; + +net_id: NetID, +template_id: i32, +position: Vector, +rotation: Vector, +hp: f64, +extra: Extra, + +pub const Extra = union(enum) { + character: Character, +}; + +pub const Vector = struct { + pub const zero: Vector = .{ .x = 0, .y = 0, .z = 0 }; + + x: f32, + y: f32, + z: f32, +}; + +pub const Character = struct { + level: i32, + char_index: logic.Player.CharBag.CharIndex, + // TODO: attrs, battle_mgr_info representation. +}; diff --git a/gamesv/src/logic/World.zig b/gamesv/src/logic/World.zig index 43aa83c..773a5c0 100644 --- a/gamesv/src/logic/World.zig +++ b/gamesv/src/logic/World.zig @@ -19,16 +19,15 @@ player_id: PlayerId, session: *Session, // TODO: should it be here this way? Do we need an abstraction? res: logic.Resource, player: logic.Player, +level: logic.Level = .init, pub fn init( session: *Session, assets: *const Assets, uid: mem.LimitedString(PlayerId.max_length), player: logic.Player, - gpa: Allocator, io: Io, ) World { - _ = gpa; return .{ .player_id = .{ .uid = uid }, .session = session, @@ -39,6 +38,7 @@ pub fn init( pub fn deinit(world: *World, gpa: Allocator) void { world.player.deinit(gpa); + world.level.deinit(gpa); } pub const GetComponentError = error{ @@ -49,6 +49,7 @@ pub fn getComponentByType(world: *World, comptime T: type) GetComponentError!T { switch (T) { PlayerId => return world.player_id, *Session => return world.session, + *logic.Level => return &world.level, *logic.Resource.PingTimer => return &world.res.ping_timer, *const Assets => return world.res.assets, Io => return world.res.io(), diff --git a/gamesv/src/logic/systems/scene.zig b/gamesv/src/logic/systems/scene.zig index 2abb0e3..c83bdda 100644 --- a/gamesv/src/logic/systems/scene.zig +++ b/gamesv/src/logic/systems/scene.zig @@ -4,6 +4,7 @@ const logic = @import("../../logic.zig"); const Session = @import("../../Session.zig"); const Assets = @import("../../Assets.zig"); +const Level = logic.Level; const Player = logic.Player; const ArrayList = std.ArrayList; const Allocator = std.mem.Allocator; @@ -13,17 +14,76 @@ pub fn enterSceneOnLogin( tx: logic.event.Sender(.change_scene_begin), ) !void { _ = rx; + try tx.send(.{}); } +fn respawnCharTeam( + gpa: Allocator, + char_bag: *const Player.CharBag, + cur_scene: *const Player.Scene.Current, + level: *Level, +) Level.SpawnError!void { + // Despawn previous team + var team = level.team(); + var char_net_id = team.next(); + while (char_net_id != .none) : (char_net_id = team.next()) { + const handle = level.getObjectByNetId(char_net_id).?; + level.despawn(handle); + } + + // Spawn new team + const position: Level.Object.Vector = .{ + .x = cur_scene.position[0], + .y = cur_scene.position[1], + .z = cur_scene.position[2], + }; + + const rotation: Level.Object.Vector = .{ + .x = cur_scene.rotation[0], + .y = cur_scene.rotation[1], + .z = cur_scene.rotation[2], + }; + + const team_index = char_bag.meta.curr_team_index; + const chars = char_bag.chars.slice(); + + for (char_bag.teams.items(.char_team)[team_index]) |slot| { + const char_index = slot.charIndex() orelse continue; + const i = @intFromEnum(char_index); + + const char_template_id_num = chars.items(.template_id)[i]; + + _ = try level.spawn( + gpa, + .{ + .template_id = char_template_id_num, + .position = position, + .rotation = rotation, + .hp = chars.items(.hp)[i], + }, + .{ .character = .{ + .level = chars.items(.level)[i], + .char_index = char_index, + } }, + ); + } +} + pub fn beginChangingScene( rx: logic.event.Receiver(.change_scene_begin), + gpa: logic.Resource.Allocator(.gpa), session: *Session, base: Player.Component(.base), scene: Player.Component(.scene), + char_bag: Player.Component(.char_bag), + level: *Level, ) !void { _ = rx; + level.reset(); + try respawnCharTeam(gpa.interface, char_bag.data, &scene.data.current, level); + const position: pb.VECTOR = .{ .X = scene.data.current.position[0], .Y = scene.data.current.position[1], @@ -46,13 +106,17 @@ pub fn beginChangingScene( pub fn refreshCharTeam( rx: logic.event.Receiver(.char_bag_team_modified), + gpa: logic.Resource.Allocator(.gpa), char_bag: Player.Component(.char_bag), + scene: Player.Component(.scene), + level: *Level, sync_tx: logic.event.Sender(.sync_self_scene), ) !void { switch (rx.payload.modification) { .set_leader => return, // Doesn't require any action from server. .set_char_team => if (rx.payload.team_index == char_bag.data.meta.curr_team_index) { // If the current active team has been modified, it has to be re-spawned. + try respawnCharTeam(gpa.interface, char_bag.data, &scene.data.current, level); try sync_tx.send(.{ .reason = .team_modified }); }, } @@ -64,6 +128,7 @@ pub fn syncSelfScene( arena: logic.Resource.Allocator(.arena), char_bag: Player.Component(.char_bag), scene: Player.Component(.scene), + level: *Level, assets: *const Assets, ) !void { const reason: pb.SELF_INFO_REASON_TYPE = switch (rx.payload.reason) { @@ -71,12 +136,6 @@ pub fn syncSelfScene( .team_modified => .SLR_CHANGE_TEAM, }; - const position: pb.VECTOR = .{ - .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]; @@ -93,46 +152,51 @@ pub fn syncSelfScene( .detail = .{}, }; - for (char_bag.data.teams.items(.char_team)[team_index]) |slot| { - const char_index = slot.charIndex() orelse continue; - const char_template_id_num = char_bag.data.chars.items(.template_id)[@intFromEnum(char_index)]; - const char_template_id = assets.numToStr(.char_id, char_template_id_num).?; - const char_data = assets.table(.character).getPtr(char_template_id).?; + const objects = level.objects.slice(); + for (0..objects.len) |i| { + const position = objects.items(.position)[i]; + const rotation = objects.items(.rotation)[i]; + const net_id = @intFromEnum(objects.items(.net_id)[i]); - var scene_char: pb.SCENE_CHARACTER = .{ - .level = 1, - .battle_info = .{ - .msg_generation = @intCast(char_index.objectId()), - .battle_inst_id = @intCast(char_index.objectId()), - .part_inst_info = .{}, - }, - .common_info = .{ - .id = char_index.objectId(), - .templateid = char_template_id, - .position = position, - .rotation = .{}, - .scene_num_id = scene.data.current.level_id, - }, + var common_info: pb.SCENE_OBJECT_COMMON_INFO = .{ + .id = net_id, + .position = .{ .X = position.x, .Y = position.y, .Z = position.z }, + .rotation = .{ .X = rotation.x, .Y = rotation.y, .Z = rotation.z }, + .scene_num_id = scene.data.current.level_id, + .hp = objects.items(.hp)[i], }; - for (char_data.attributes[0].Attribute.attrs) |attr| { - if (attr.attrType == .max_hp) - scene_char.common_info.?.hp = attr.attrValue; + switch (objects.items(.extra)[i]) { + .character => |extra| { + const template_id_num = objects.items(.template_id)[i]; + const template_id = assets.numToStr(.char_id, template_id_num).?; - try scene_char.attrs.append(arena.interface, .{ - .attr_type = @intFromEnum(attr.attrType), - .basic_value = attr.attrValue, - .value = attr.attrValue, - }); + common_info.type = 0; + common_info.templateid = template_id; + + var scene_char: pb.SCENE_CHARACTER = .{ + .level = extra.level, + .common_info = common_info, + .battle_info = .{ + .msg_generation = @intCast(net_id), + .battle_inst_id = @intCast(net_id), + .part_inst_info = .init, + .skill_list = try packCharacterSkills(arena.interface, assets, template_id), + }, + }; + + const char_data = assets.table(.character).getPtr(template_id).?; + for (char_data.attributes[0].Attribute.attrs) |attr| { + try scene_char.attrs.append(arena.interface, .{ + .attr_type = @intFromEnum(attr.attrType), + .basic_value = attr.attrValue, + .value = attr.attrValue, + }); + } + + try self_scene_info.detail.?.char_list.append(arena.interface, scene_char); + }, } - - scene_char.battle_info.?.skill_list = try packCharacterSkills( - arena.interface, - assets, - char_template_id, - ); - - try self_scene_info.detail.?.char_list.append(arena.interface, scene_char); } try session.send(self_scene_info);