feat(logic): introduce logic.Level

This commit is contained in:
xeon
2026-02-07 15:59:19 +03:00
parent 21dbc5f82f
commit 3be92b7b11
6 changed files with 282 additions and 44 deletions

136
gamesv/src/logic/Level.zig Normal file
View File

@@ -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);
}

View File

@@ -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.
};

View File

@@ -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(),

View File

@@ -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);