mirror of
https://git.xeondev.com/LR/S.git
synced 2026-03-21 23:22:21 +01:00
feat(logic): introduce logic.Level
This commit is contained in:
@@ -123,7 +123,7 @@ pub fn process(
|
|||||||
.{ stream.socket.address, result.uid.view() },
|
.{ 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;
|
receive_timeout = subsequent_request_timeout;
|
||||||
|
|
||||||
logic.systems.triggerEvent(.{ .login = .{} }, &world.?, gpa) catch |err| switch (err) {
|
logic.systems.triggerEvent(.{ .login = .{} }, &world.?, gpa) catch |err| switch (err) {
|
||||||
|
|||||||
@@ -5,3 +5,4 @@ pub const messaging = @import("logic/messaging.zig");
|
|||||||
pub const event = @import("logic/event.zig");
|
pub const event = @import("logic/event.zig");
|
||||||
pub const systems = @import("logic/systems.zig");
|
pub const systems = @import("logic/systems.zig");
|
||||||
pub const queries = @import("logic/queries.zig");
|
pub const queries = @import("logic/queries.zig");
|
||||||
|
pub const Level = @import("logic/Level.zig");
|
||||||
|
|||||||
136
gamesv/src/logic/Level.zig
Normal file
136
gamesv/src/logic/Level.zig
Normal 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);
|
||||||
|
}
|
||||||
36
gamesv/src/logic/Level/Object.zig
Normal file
36
gamesv/src/logic/Level/Object.zig
Normal 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.
|
||||||
|
};
|
||||||
@@ -19,16 +19,15 @@ player_id: PlayerId,
|
|||||||
session: *Session, // TODO: should it be here this way? Do we need an abstraction?
|
session: *Session, // TODO: should it be here this way? Do we need an abstraction?
|
||||||
res: logic.Resource,
|
res: logic.Resource,
|
||||||
player: logic.Player,
|
player: logic.Player,
|
||||||
|
level: logic.Level = .init,
|
||||||
|
|
||||||
pub fn init(
|
pub fn init(
|
||||||
session: *Session,
|
session: *Session,
|
||||||
assets: *const Assets,
|
assets: *const Assets,
|
||||||
uid: mem.LimitedString(PlayerId.max_length),
|
uid: mem.LimitedString(PlayerId.max_length),
|
||||||
player: logic.Player,
|
player: logic.Player,
|
||||||
gpa: Allocator,
|
|
||||||
io: Io,
|
io: Io,
|
||||||
) World {
|
) World {
|
||||||
_ = gpa;
|
|
||||||
return .{
|
return .{
|
||||||
.player_id = .{ .uid = uid },
|
.player_id = .{ .uid = uid },
|
||||||
.session = session,
|
.session = session,
|
||||||
@@ -39,6 +38,7 @@ pub fn init(
|
|||||||
|
|
||||||
pub fn deinit(world: *World, gpa: Allocator) void {
|
pub fn deinit(world: *World, gpa: Allocator) void {
|
||||||
world.player.deinit(gpa);
|
world.player.deinit(gpa);
|
||||||
|
world.level.deinit(gpa);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const GetComponentError = error{
|
pub const GetComponentError = error{
|
||||||
@@ -49,6 +49,7 @@ pub fn getComponentByType(world: *World, comptime T: type) GetComponentError!T {
|
|||||||
switch (T) {
|
switch (T) {
|
||||||
PlayerId => return world.player_id,
|
PlayerId => return world.player_id,
|
||||||
*Session => return world.session,
|
*Session => return world.session,
|
||||||
|
*logic.Level => return &world.level,
|
||||||
*logic.Resource.PingTimer => return &world.res.ping_timer,
|
*logic.Resource.PingTimer => return &world.res.ping_timer,
|
||||||
*const Assets => return world.res.assets,
|
*const Assets => return world.res.assets,
|
||||||
Io => return world.res.io(),
|
Io => return world.res.io(),
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ const logic = @import("../../logic.zig");
|
|||||||
const Session = @import("../../Session.zig");
|
const Session = @import("../../Session.zig");
|
||||||
const Assets = @import("../../Assets.zig");
|
const Assets = @import("../../Assets.zig");
|
||||||
|
|
||||||
|
const Level = logic.Level;
|
||||||
const Player = logic.Player;
|
const Player = logic.Player;
|
||||||
const ArrayList = std.ArrayList;
|
const ArrayList = std.ArrayList;
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
@@ -13,17 +14,76 @@ pub fn enterSceneOnLogin(
|
|||||||
tx: logic.event.Sender(.change_scene_begin),
|
tx: logic.event.Sender(.change_scene_begin),
|
||||||
) !void {
|
) !void {
|
||||||
_ = rx;
|
_ = rx;
|
||||||
|
|
||||||
try tx.send(.{});
|
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(
|
pub fn beginChangingScene(
|
||||||
rx: logic.event.Receiver(.change_scene_begin),
|
rx: logic.event.Receiver(.change_scene_begin),
|
||||||
|
gpa: logic.Resource.Allocator(.gpa),
|
||||||
session: *Session,
|
session: *Session,
|
||||||
base: Player.Component(.base),
|
base: Player.Component(.base),
|
||||||
scene: Player.Component(.scene),
|
scene: Player.Component(.scene),
|
||||||
|
char_bag: Player.Component(.char_bag),
|
||||||
|
level: *Level,
|
||||||
) !void {
|
) !void {
|
||||||
_ = rx;
|
_ = rx;
|
||||||
|
|
||||||
|
level.reset();
|
||||||
|
try respawnCharTeam(gpa.interface, char_bag.data, &scene.data.current, level);
|
||||||
|
|
||||||
const position: pb.VECTOR = .{
|
const position: pb.VECTOR = .{
|
||||||
.X = scene.data.current.position[0],
|
.X = scene.data.current.position[0],
|
||||||
.Y = scene.data.current.position[1],
|
.Y = scene.data.current.position[1],
|
||||||
@@ -46,13 +106,17 @@ pub fn beginChangingScene(
|
|||||||
|
|
||||||
pub fn refreshCharTeam(
|
pub fn refreshCharTeam(
|
||||||
rx: logic.event.Receiver(.char_bag_team_modified),
|
rx: logic.event.Receiver(.char_bag_team_modified),
|
||||||
|
gpa: logic.Resource.Allocator(.gpa),
|
||||||
char_bag: Player.Component(.char_bag),
|
char_bag: Player.Component(.char_bag),
|
||||||
|
scene: Player.Component(.scene),
|
||||||
|
level: *Level,
|
||||||
sync_tx: logic.event.Sender(.sync_self_scene),
|
sync_tx: logic.event.Sender(.sync_self_scene),
|
||||||
) !void {
|
) !void {
|
||||||
switch (rx.payload.modification) {
|
switch (rx.payload.modification) {
|
||||||
.set_leader => return, // Doesn't require any action from server.
|
.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) {
|
.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.
|
// 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 });
|
try sync_tx.send(.{ .reason = .team_modified });
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -64,6 +128,7 @@ pub fn syncSelfScene(
|
|||||||
arena: logic.Resource.Allocator(.arena),
|
arena: logic.Resource.Allocator(.arena),
|
||||||
char_bag: Player.Component(.char_bag),
|
char_bag: Player.Component(.char_bag),
|
||||||
scene: Player.Component(.scene),
|
scene: Player.Component(.scene),
|
||||||
|
level: *Level,
|
||||||
assets: *const Assets,
|
assets: *const Assets,
|
||||||
) !void {
|
) !void {
|
||||||
const reason: pb.SELF_INFO_REASON_TYPE = switch (rx.payload.reason) {
|
const reason: pb.SELF_INFO_REASON_TYPE = switch (rx.payload.reason) {
|
||||||
@@ -71,12 +136,6 @@ pub fn syncSelfScene(
|
|||||||
.team_modified => .SLR_CHANGE_TEAM,
|
.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 team_index = char_bag.data.meta.curr_team_index;
|
||||||
const leader_index = char_bag.data.teams.items(.leader_index)[team_index];
|
const leader_index = char_bag.data.teams.items(.leader_index)[team_index];
|
||||||
|
|
||||||
@@ -93,46 +152,51 @@ pub fn syncSelfScene(
|
|||||||
.detail = .{},
|
.detail = .{},
|
||||||
};
|
};
|
||||||
|
|
||||||
for (char_bag.data.teams.items(.char_team)[team_index]) |slot| {
|
const objects = level.objects.slice();
|
||||||
const char_index = slot.charIndex() orelse continue;
|
for (0..objects.len) |i| {
|
||||||
const char_template_id_num = char_bag.data.chars.items(.template_id)[@intFromEnum(char_index)];
|
const position = objects.items(.position)[i];
|
||||||
const char_template_id = assets.numToStr(.char_id, char_template_id_num).?;
|
const rotation = objects.items(.rotation)[i];
|
||||||
const char_data = assets.table(.character).getPtr(char_template_id).?;
|
const net_id = @intFromEnum(objects.items(.net_id)[i]);
|
||||||
|
|
||||||
var scene_char: pb.SCENE_CHARACTER = .{
|
var common_info: pb.SCENE_OBJECT_COMMON_INFO = .{
|
||||||
.level = 1,
|
.id = net_id,
|
||||||
.battle_info = .{
|
.position = .{ .X = position.x, .Y = position.y, .Z = position.z },
|
||||||
.msg_generation = @intCast(char_index.objectId()),
|
.rotation = .{ .X = rotation.x, .Y = rotation.y, .Z = rotation.z },
|
||||||
.battle_inst_id = @intCast(char_index.objectId()),
|
.scene_num_id = scene.data.current.level_id,
|
||||||
.part_inst_info = .{},
|
.hp = objects.items(.hp)[i],
|
||||||
},
|
|
||||||
.common_info = .{
|
|
||||||
.id = char_index.objectId(),
|
|
||||||
.templateid = char_template_id,
|
|
||||||
.position = position,
|
|
||||||
.rotation = .{},
|
|
||||||
.scene_num_id = scene.data.current.level_id,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
for (char_data.attributes[0].Attribute.attrs) |attr| {
|
switch (objects.items(.extra)[i]) {
|
||||||
if (attr.attrType == .max_hp)
|
.character => |extra| {
|
||||||
scene_char.common_info.?.hp = attr.attrValue;
|
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, .{
|
common_info.type = 0;
|
||||||
.attr_type = @intFromEnum(attr.attrType),
|
common_info.templateid = template_id;
|
||||||
.basic_value = attr.attrValue,
|
|
||||||
.value = attr.attrValue,
|
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);
|
try session.send(self_scene_info);
|
||||||
|
|||||||
Reference in New Issue
Block a user