Release 0.1.0

This commit is contained in:
xeon
2026-02-02 20:53:22 +03:00
commit 25660300dd
152 changed files with 882089 additions and 0 deletions

View File

@@ -0,0 +1,45 @@
const Player = @This();
const std = @import("std");
const meta = std.meta;
const Allocator = std.mem.Allocator;
pub const Base = @import("Player/Base.zig");
pub const GameVars = @import("Player/GameVars.zig");
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");
base: Base,
game_vars: GameVars,
unlock: Unlock,
char_bag: CharBag,
item_bag: ItemBag,
bitset: Bitset,
pub fn deinit(player: *Player, gpa: Allocator) void {
player.game_vars.deinit(gpa);
player.unlock.deinit(gpa);
player.char_bag.deinit(gpa);
player.item_bag.deinit(gpa);
}
// Describes the dependency on an individual player component.
pub fn Component(comptime tag: meta.FieldEnum(Player)) type {
return struct {
pub const player_component_tag = tag;
data: *@FieldType(Player, @tagName(tag)),
};
}
pub fn isComponent(comptime T: type) bool {
if (!@hasDecl(T, "player_component_tag")) return false;
return T == Component(T.player_component_tag);
}
pub fn getComponentByType(player: *Player, comptime T: type) T {
return .{ .data = &@field(player, @tagName(T.player_component_tag)) };
}

View File

@@ -0,0 +1,37 @@
const Base = @This();
const common = @import("common");
const mem = common.mem;
pub const max_role_name_length: usize = 15;
pub const init: Base = .{
.create_ts = 0,
.role_name = .constant("xeondev"),
.role_id = 1,
.level = .first,
.exp = 0,
.create_ts_display = 0,
.gender = .default,
};
pub const Gender = enum(u8) {
pub const default: Gender = .male;
invalid = 0,
male = 1,
female = 2,
};
pub const Level = enum(u8) {
first = 1,
last = 60,
_,
};
create_ts: i64,
role_name: mem.LimitedString(max_role_name_length),
role_id: u64,
level: Level,
exp: u32,
create_ts_display: i64,
gender: Gender,

View File

@@ -0,0 +1,113 @@
const Bitset = @This();
const std = @import("std");
const Allocator = std.mem.Allocator;
pub const max_value = 512;
const Set = std.bit_set.ArrayBitSet(u64, max_value);
pub const init: Bitset = .{};
sets: [Type.count]Set = @splat(.initEmpty()),
pub fn set(b: *Bitset, t: Type, value: u64) error{ValueOutOfRange}!void {
if (value > max_value) return error.ValueOutOfRange;
b.sets[@intFromEnum(t) - 1].set(@intCast(value));
}
pub const Type = enum(u32) {
pub const count: usize = blk: {
const values = std.enums.values(Type);
break :blk @as(usize, @intFromEnum(values[values.len - 1])) + 1;
};
found_item = 1,
wiki = 2,
unread_wiki = 3,
monster_drop = 4,
got_item = 5,
area_first_view = 6,
unread_got_item = 7,
prts = 8,
unread_prts = 9,
prts_first_lv = 10,
prts_terminal_content = 11,
level_have_been = 12,
level_map_first_view = 13,
unread_formula = 14,
new_char = 15,
elog_channel = 16,
fmv_watched = 17,
time_line_watched = 18,
map_filter = 19,
friend_has_request = 20,
equip_tech_formula = 21,
radio_trigger = 22,
remote_communication_finish = 23,
unlock_server_dungeon_series = 24,
chapter_first_view = 25,
adventure_level_reward_done = 26,
dungeon_entrance_touched = 27,
equip_tech_tier = 28,
char_doc = 30,
char_voice = 31,
reading_pop = 32,
reward_id_done = 33,
prts_investigate = 34,
racing_received_bp_node = 35,
racing_complete_achievement = 36,
racing_received_achievement = 37,
interactive_active = 39,
mine_point_first_time_collect = 40,
unread_char_doc = 41,
unread_char_voice = 42,
area_toast_once = 44,
unread_equip_tech_formula = 45,
prts_investigate_unread_note = 46,
prts_investigate_note = 47,
game_mechanic_read = 48,
read_active_blackbox = 49,
read_level = 50,
factroy_placed_building = 51,
interactive_two_state = 52,
unread_unlock_spaceship_room_type = 53,
unlock_spaceship_room_type = 54,
unlock_user_avatar = 55,
unlock_user_avatar_frame = 56,
unlock_business_card_topic = 57,
special_game_event = 58,
radio_id = 59,
got_weapon = 60,
read_new_version_equip_tech_formula = 61,
mist_map_unlocked = 62,
read_achive = 63,
camera_volume = 64,
read_fac_tech_tree_unhidden_tech = 65,
read_fac_tech_tree_unhidden_category = 66,
mist_map_mv_watched = 67,
remote_communication_wait_for_play = 68,
mission_completed_once = 69,
psn_cup_unlocked = 70,
unread_week_raid_mission = 71,
unlock_game_entrance_activity_series = 72,
unlock_domain_depot = 73,
unlock_recycle_bin = 74,
manual_crafted_item = 75,
un_read_new_activity_notify = 76,
read_picture_ids = 77,
read_shop_id = 78,
read_shop_goods_id = 79,
read_bp_season_id = 80,
read_bp_task_id = 81,
read_cash_shop_goods_id = 82,
new_avatar_unlock = 83,
new_avatar_frame_unlock = 84,
new_theme_unlock = 85,
read_char_potential_pic_ids = 86,
read_high_difficulty_dungeon_series = 87,
reported_client_log_types = 88,
activated_factory_inst = 89,
read_max_world_level = 90,
got_formula_unlock_item = 91,
};

View File

@@ -0,0 +1,102 @@
const CharBag = @This();
const std = @import("std");
const common = @import("common");
const Player = @import("../Player.zig");
const Allocator = std.mem.Allocator;
teams: std.MultiArrayList(Team),
chars: std.MultiArrayList(Char),
meta: Meta,
pub const CharIndex = enum(u64) {
_,
// Returns an 'objectId' for network serialization.
pub fn objectId(i: CharIndex) u64 {
return @intFromEnum(i) + 1;
}
pub fn fromObjectId(id: u64) CharIndex {
return @enumFromInt(id - 1);
}
};
pub fn deinit(bag: *CharBag, gpa: Allocator) void {
bag.teams.deinit(gpa);
bag.chars.deinit(gpa);
}
pub fn charIndexById(bag: *const CharBag, template_id: i32) ?CharIndex {
const idx: u64 = @intCast(
std.mem.findScalar(i32, bag.chars.items(.template_id), template_id) orelse
return null,
);
return @enumFromInt(idx);
}
pub fn charIndexWithWeapon(bag: *const CharBag, weapon: Player.ItemBag.WeaponIndex) ?CharIndex {
const idx: u64 = @intCast(
std.mem.findScalar(Player.ItemBag.WeaponIndex, bag.chars.items(.weapon_id), weapon) orelse
return null,
);
return @enumFromInt(idx);
}
// Checks:
// 1. Existence of the team.
// 2. Existence of the specified character index in the team.
pub fn ensureTeamMember(bag: *const CharBag, team_index: usize, char_index: CharIndex) error{
InvalidTeamIndex,
NotTeamMember,
}!void {
if (team_index < 0 or team_index >= bag.teams.len) {
return error.InvalidTeamIndex;
}
const char_team = &bag.teams.items(.char_team)[team_index];
_ = std.mem.findScalar(u64, @ptrCast(char_team), @intFromEnum(char_index)) orelse
return error.NotTeamMember;
}
pub const Meta = struct {
curr_team_index: u32,
};
pub const Team = struct {
pub const slots_count: usize = 4;
pub const SlotArray = [Team.slots_count]Team.Slot;
pub const Slot = enum(u64) {
empty = std.math.maxInt(u64),
_,
pub fn charIndex(s: Slot) ?CharIndex {
return if (s != .empty) @enumFromInt(@intFromEnum(s)) else null;
}
pub fn fromCharIndex(i: CharIndex) Slot {
return @enumFromInt(@intFromEnum(i));
}
};
name: common.mem.LimitedString(15) = .empty,
char_team: [slots_count]Slot = @splat(Slot.empty),
leader_index: CharIndex,
};
pub const Char = struct {
template_id: i32,
level: i32,
exp: i32,
is_dead: bool,
hp: f64,
ultimate_sp: f32,
weapon_id: Player.ItemBag.WeaponIndex,
own_time: i64,
equip_medicine_id: i32,
potential_level: u32,
};

View File

@@ -0,0 +1,71 @@
const GameVars = @This();
const std = @import("std");
const Allocator = std.mem.Allocator;
pub const default_server_vars: []const ServerVar = &.{
.{ .key = .already_set_gender, .value = 1 },
.{ .key = .dash_energy_limit, .value = 100 },
.{ .key = .already_set_name, .value = 1 },
};
pub const default_client_vars: []const ClientVar = &.{
.{ .key = 43, .value = 1 },
.{ .key = 78, .value = 1 },
.{ .key = 82, .value = 1 },
.{ .key = 125, .value = 1 },
.{ .key = 126, .value = 1 },
};
pub const ClientVar = packed struct {
key: i32,
value: i64,
};
pub const ServerVar = packed struct {
key: ServerVarType,
value: i64,
};
client_vars: []ClientVar,
server_vars: []ServerVar,
pub fn deinit(gv: *GameVars, gpa: Allocator) void {
gpa.free(gv.client_vars);
gpa.free(gv.server_vars);
}
pub const ServerVarType = enum(i32) {
const common_begin: i32 = 100000;
const common_end: i32 = 109999;
const daily_refresh_begin: i32 = 110000;
const daily_refresh_end: i32 = 119999;
pub const Kind = enum(i32) {
common = 10,
daily_refresh = 11,
weekly_refresh = 12,
monthly_refresh = 13,
};
server_test_1 = 100001,
server_test_2 = 100002,
already_set_gender = 100003,
enhance_bean = 100004,
enhance_bean_last_replenish_time = 100005,
dash_energy_limit = 100006,
already_set_name = 100007,
social_share_control = 100008,
db_config_version = 100009,
client_debug_mode_end_time = 100010,
recover_ap_by_money_count = 110001,
poop_cow_interact_count = 110002,
stamina_reduce_used_count = 110003,
space_ship_daily_credit_reward = 110004,
daily_enemy_drop_mod_reward_count = 110005,
daily_enemy_exp_count = 110006,
pub inline fn kind(vt: ServerVarType) Kind {
return @enumFromInt(@intFromEnum(vt) / 10_000);
}
};

View File

@@ -0,0 +1,28 @@
const ItemBag = @This();
const std = @import("std");
const common = @import("common");
const Allocator = std.mem.Allocator;
weapon_depot: std.MultiArrayList(Weapon),
pub fn deinit(bag: *ItemBag, gpa: Allocator) void {
bag.weapon_depot.deinit(gpa);
}
pub const WeaponIndex = enum(u64) {
_,
pub fn instId(i: WeaponIndex) u64 {
return @intFromEnum(i) + 1;
}
};
pub const Weapon = struct {
template_id: i32,
exp: u32,
weapon_lv: u32,
refine_lv: u32,
breakthrough_lv: u32,
attach_gem_id: u64,
};

View File

@@ -0,0 +1,126 @@
const Unlock = @This();
const std = @import("std");
const Allocator = std.mem.Allocator;
pub const default_unlocked_systems: []const SystemType = blk: {
const fields = @typeInfo(SystemType).@"enum".fields;
var list: []const SystemType = &.{};
for (fields) |field| {
if (field.value != @intFromEnum(SystemType.none)) {
list = list ++ .{@field(SystemType, field.name)};
}
}
break :blk list;
};
unlocked_systems: []SystemType,
pub fn deinit(unlock: *Unlock, gpa: Allocator) void {
gpa.free(unlock.unlocked_systems);
}
pub const SystemType = enum(i32) {
none = 10000000,
map = 0,
inventory = 1,
watch = 2,
valuable_depot = 3,
shop = 4,
char_team = 5,
gacha = 51,
dungeon = 52,
bloc_mission = 53,
mail = 54,
wiki = 55,
prts = 56,
submit_ether = 57,
scan = 58,
char_ui = 59,
friend = 60,
daily_mission = 61,
general_ability_bomb = 62,
general_ability_fluid_interact = 63,
general_ability = 64,
sns = 65,
equip_tech = 66,
equip_produce = 67,
dungeon_factory = 69,
enemy_spawner = 70,
general_ability_water_gun = 71,
general_ability_snapshot = 72,
fac_building_pin = 101,
fac_craft_pin = 102,
fac_mode = 103,
fac_tech_tree = 104,
fac_overview = 105,
fac_yield_stats = 106,
fac_conveyor = 107,
fac_transfer_port = 108,
fac_bridge = 109,
fac_splitter = 110,
fac_merger = 111,
fac_bus = 112,
fac_zone = 113,
fac_system = 114,
fac_pipe = 115,
fac_pipe_splitter = 116,
fac_pipe_connector = 117,
fac_pipe_converger = 118,
fac_hub = 119,
fac_bus_free = 120,
fac_top_view = 121,
fac_blueprint = 122,
fac_underground_pipe = 123,
fac_social = 124,
fac_valve = 125,
fac_pipe_valve = 126,
fac_panel_store = 127,
fac_fertilize = 128,
manual_craft = 201,
item_use = 202,
item_quick_bar = 203,
product_manual = 204,
manual_craft_soil = 205,
weapon = 251,
equip = 252,
equip_enhance = 253,
gem_enhance = 254,
normal_attack = 301,
normal_skill = 302,
ultimate_skill = 303,
team_skill = 304,
combo_skill = 305,
team_switch = 306,
dash = 307,
jump = 308,
lock_target = 309,
spaceship_present_gift = 401,
spaceship_manufacturing_station = 402,
spaceship_control_center = 403,
spaceship_system = 404,
spaceship_grow_cabin = 405,
spaceship_shop = 406,
spaceship_guest_room = 407,
settlement = 501,
domain_development = 502,
domain_development_domain_depot = 503,
settlement_defense = 504,
kite_station = 511,
domain_shop = 512,
racing_dungeon = 601,
battle_training = 602,
week_raid = 603,
week_raid_intro = 604,
water_drone_can_use_xiranite = 605,
adventure_exp_and_lv = 651,
adventure_book = 652,
guidance_manul = 661,
ai_bark = 670,
achievement = 701,
minigame_puzzle = 801,
bp_system = 802,
activity = 1100,
check_in = 1113,
};

View File

@@ -0,0 +1,46 @@
const Resource = @This();
const std = @import("std");
const mem = std.mem;
const Assets = @import("../Assets.zig");
const Io = std.Io;
pub const AllocatorKind = enum {
gpa,
arena,
};
pub const PingTimer = struct {
io: Io,
last_client_ts: u64 = 0,
pub fn serverTime(pt: PingTimer) u64 {
return if (Io.Clock.real.now(pt.io)) |ts|
@intCast(ts.toMilliseconds())
else |_|
pt.last_client_ts;
}
};
assets: *const Assets,
ping_timer: PingTimer,
pub fn init(assets: *const Assets, io_impl: Io) Resource {
return .{
.assets = assets,
.ping_timer = .{ .io = io_impl },
};
}
pub fn io(res: *const Resource) Io {
return res.ping_timer.io; // TODO: move to the root of resources.
}
// Describes the dependency on an allocator.
pub fn Allocator(comptime kind: AllocatorKind) type {
return struct {
pub const allocator_kind = kind;
interface: mem.Allocator,
};
}

View File

@@ -0,0 +1,58 @@
// Describes player-local state of the world.
const World = @This();
const std = @import("std");
const logic = @import("../logic.zig");
const Session = @import("../Session.zig");
const Assets = @import("../Assets.zig");
const Io = std.Io;
const Allocator = std.mem.Allocator;
pub const PlayerId = struct { uid: u64 };
player_id: PlayerId,
session: *Session, // TODO: should it be here this way? Do we need an abstraction?
res: logic.Resource,
player: logic.Player,
pub fn init(
session: *Session,
assets: *const Assets,
uid: u64,
player: logic.Player,
gpa: Allocator,
io: Io,
) World {
_ = gpa;
return .{
.player_id = .{ .uid = uid },
.session = session,
.player = player,
.res = .init(assets, io),
};
}
pub fn deinit(world: *World, gpa: Allocator) void {
world.player.deinit(gpa);
}
pub const GetComponentError = error{
ComponentUnavailable,
};
pub fn getComponentByType(world: *World, comptime T: type) GetComponentError!T {
switch (T) {
PlayerId => return world.player_id,
*Session => return world.session,
*logic.Resource.PingTimer => return &world.res.ping_timer,
*const Assets => return world.res.assets,
Io => return world.res.io(),
else => {
if (comptime logic.Player.isComponent(T)) {
return world.player.getComponentByType(T);
}
@compileError("World.getComponentByType(" ++ @typeName(T) ++ ") is unsupported");
},
}
}

View File

@@ -0,0 +1,80 @@
const std = @import("std");
pub const kinds = @import("event/kinds.zig");
const Allocator = std.mem.Allocator;
const meta = std.meta;
// Describes the event receiver
pub fn Receiver(comptime kind: meta.Tag(Kind)) type {
return struct {
pub const rx_event_kind = kind;
pub const Event = @field(
kinds,
@typeInfo(kinds).@"struct".decls[@intFromEnum(kind)].name,
);
payload: Event,
};
}
// Describes the event sender
pub fn Sender(comptime kind: meta.Tag(Kind)) type {
return struct {
pub const tx_event_kind = kind;
pub const Event = @field(
kinds,
@typeInfo(kinds).@"struct".decls[@intFromEnum(kind)].name,
);
event_queue: *Queue,
pub fn send(s: @This(), event: Event) Allocator.Error!void {
try s.event_queue.push(@unionInit(Kind, @tagName(kind), event));
}
};
}
pub const Queue = struct {
arena: Allocator,
deque: std.Deque(Kind),
pub fn init(arena: Allocator) Queue {
return .{ .arena = arena, .deque = .empty };
}
pub fn push(queue: *Queue, event: Kind) Allocator.Error!void {
try queue.deque.pushBack(queue.arena, event);
}
};
pub const Kind = blk: {
var types: []const type = &.{};
var indices: []const u16 = &.{};
var names: []const []const u8 = &.{};
for (@typeInfo(kinds).@"struct".decls, 0..) |decl, i| {
const declaration = @field(kinds, decl.name);
if (@TypeOf(declaration) != type) continue;
if (meta.activeTag(@typeInfo(declaration)) == .@"struct") {
indices = indices ++ .{@as(u16, @intCast(i))};
types = types ++ .{@field(kinds, decl.name)};
names = names ++ .{toSnakeCase(decl.name)};
}
}
const EventTag = @Enum(u16, .exhaustive, names, indices[0..names.len]);
break :blk @Union(.auto, EventTag, names, types[0..names.len], &@splat(.{}));
};
inline fn toSnakeCase(comptime name: []const u8) []const u8 {
var result: []const u8 = "";
for (name, 0..) |c, i| {
if (std.ascii.isUpper(c)) {
if (i != 0) result = result ++ "_";
result = result ++ .{std.ascii.toLower(c)};
} else result = result ++ .{c};
}
return result;
}

View File

@@ -0,0 +1,16 @@
pub const Login = struct {};
pub const CharBagTeamModified = struct {
team_index: usize,
modification: enum {
set_leader,
set_char_team,
},
};
pub const SyncSelfScene = struct {
reason: enum {
entrance,
team_modified,
},
};

View File

@@ -0,0 +1,116 @@
const std = @import("std");
const proto = @import("proto");
const logic = @import("../logic.zig");
const network = @import("../network.zig");
const Session = @import("../Session.zig");
const Io = std.Io;
const Allocator = std.mem.Allocator;
const log = std.log.scoped(.messaging);
const meta = std.meta;
const namespaces = &.{
@import("messaging/player.zig"),
@import("messaging/scene.zig"),
@import("messaging/char_bag.zig"),
@import("messaging/friend_chat.zig"),
};
pub fn Request(comptime CSType: type) type {
return struct {
pub const CSMessage = CSType;
message: *const CSMessage,
session: *Session,
};
}
const MsgID = blk: {
var msg_types: []const type = &.{};
for (namespaces) |namespace| {
for (@typeInfo(namespace).@"struct".decls) |decl_info| {
const decl = @field(namespace, decl_info.name);
const fn_info = switch (@typeInfo(@TypeOf(decl))) {
.@"fn" => |info| info,
else => continue,
};
if (fn_info.params.len == 0) continue;
const Param = fn_info.params[0].type.?;
if (!@hasDecl(Param, "CSMessage")) continue;
msg_types = msg_types ++ .{Param.CSMessage};
}
}
var msg_names: [msg_types.len][]const u8 = @splat("");
var msg_ids: [msg_types.len]i32 = @splat(0);
for (msg_types, 0..) |CSMsg, i| {
// Proven to exist by the code above.
msg_names[i] = CSMsg.message_name;
msg_ids[i] = @intFromEnum(proto.messageId(CSMsg));
}
break :blk @Enum(i32, .exhaustive, &msg_names, &msg_ids);
};
pub fn process(
gpa: Allocator,
world: *logic.World,
request: *const network.Request,
) !void {
const recv_msg_id = std.enums.fromInt(MsgID, request.head.msgid) orelse {
return error.MissingHandler;
};
switch (recv_msg_id) {
inline else => |msg_id| {
handler_lookup: inline for (namespaces) |namespace| {
inline for (@typeInfo(namespace).@"struct".decls) |decl_info| {
const decl = @field(namespace, decl_info.name);
const fn_info = switch (@typeInfo(@TypeOf(decl))) {
.@"fn" => |info| info,
else => continue,
};
if (fn_info.params.len == 0) continue;
const Param = fn_info.params[0].type.?;
if (!@hasDecl(Param, "CSMessage")) continue;
if (comptime !std.mem.eql(u8, @tagName(msg_id), Param.CSMessage.message_name))
continue;
var arena: std.heap.ArenaAllocator = .init(gpa);
defer arena.deinit();
var queue: logic.event.Queue = .init(arena.allocator());
var reader: Io.Reader = .fixed(request.body);
var message = proto.decodeMessage(&reader, arena.allocator(), Param.CSMessage) catch
return error.DecodeFailed;
var handler_args: meta.ArgsTuple(@TypeOf(decl)) = undefined;
handler_args[0] = .{
.message = &message,
.session = world.session,
};
inline for (fn_info.params[1..], 1..) |param, i| {
handler_args[i] = logic.queries.resolve(param.type.?, world, &queue, gpa, arena.allocator()) catch {
log.err("message handler for '{s}' requires an optional component", .{@typeName(Param.CSMessage)});
return;
};
}
try @call(.auto, decl, handler_args);
try logic.systems.run(world, &queue, gpa, arena.allocator());
break :handler_lookup;
}
} else comptime unreachable;
},
}
}

View File

@@ -0,0 +1,119 @@
const std = @import("std");
const pb = @import("proto").pb;
const logic = @import("../../logic.zig");
const Assets = @import("../../Assets.zig");
const event = logic.event;
const messaging = logic.messaging;
const Player = logic.Player;
pub fn onCharBagSetTeamLeader(
request: messaging.Request(pb.CS_CHAR_BAG_SET_TEAM_LEADER),
char_bag: Player.Component(.char_bag),
team_modified_tx: event.Sender(.char_bag_team_modified),
) !void {
const log = std.log.scoped(.char_bag_set_team_leader);
if ((request.message.team_type orelse .CHAR_BAG_TEAM_TYPE_MAIN) != .CHAR_BAG_TEAM_TYPE_MAIN)
return; // 'TEMP' teams are not supported.
const team_index = std.math.cast(usize, request.message.team_index) orelse {
log.err("invalid team index: {d}", .{request.message.team_index});
return;
};
const char_index: Player.CharBag.CharIndex = .fromObjectId(
request.message.leaderid,
);
char_bag.data.ensureTeamMember(team_index, char_index) catch |err| switch (err) {
error.InvalidTeamIndex => {
log.err(
"team index is out of range! {d}/{d}",
.{ team_index, char_bag.data.teams.len },
);
return;
},
error.NotTeamMember => {
log.err(
"character with index {d} is not a member of team {d}",
.{ @intFromEnum(char_index), team_index },
);
return;
},
};
const leader_index = &char_bag.data.teams.items(.leader_index)[team_index];
log.info(
"switching leader for team {d} ({d} -> {d})",
.{ team_index, leader_index.*, char_index },
);
leader_index.* = char_index;
try team_modified_tx.send(.{
.team_index = team_index,
.modification = .set_leader,
});
}
pub fn onCharBagSetTeam(
request: messaging.Request(pb.CS_CHAR_BAG_SET_TEAM),
char_bag: Player.Component(.char_bag),
team_modified_tx: event.Sender(.char_bag_team_modified),
) !void {
const log = std.log.scoped(.char_bag_set_team);
const team_index = std.math.cast(usize, request.message.team_index) orelse {
log.err("invalid team index: {d}", .{request.message.team_index});
return;
};
if (request.message.char_team.items.len > Player.CharBag.Team.slots_count) {
log.err(
"char_team exceeds slots count! {d}/{d}",
.{ request.message.char_team.items.len, Player.CharBag.Team.slots_count },
);
return;
}
if (std.mem.findScalar(u64, request.message.char_team.items, request.message.leader_id) == null) {
log.err("leader_id doesn't present in char_team", .{});
return;
}
var new_char_team: Player.CharBag.Team.SlotArray = @splat(.empty);
for (request.message.char_team.items, 0..) |char_id, i| {
if (std.mem.countScalar(u64, request.message.char_team.items, char_id) > 1) {
log.err("duplicated character id: {d}", .{char_id});
return;
}
const char_index: Player.CharBag.CharIndex = .fromObjectId(char_id);
if (@intFromEnum(char_index) >= char_bag.data.chars.len) {
log.err("invalid character object id: {d}", .{char_id});
return;
}
new_char_team[i] = .fromCharIndex(char_index);
}
const teams_slice = char_bag.data.teams.slice();
teams_slice.items(.char_team)[team_index] = new_char_team;
teams_slice.items(.leader_index)[team_index] = .fromObjectId(request.message.leader_id);
try team_modified_tx.send(.{
.team_index = team_index,
.modification = .set_char_team,
});
try request.session.send(pb.SC_CHAR_BAG_SET_TEAM{
.team_type = .CHAR_BAG_TEAM_TYPE_MAIN,
.team_index = request.message.team_index,
.char_team = request.message.char_team,
.scope_name = 1,
.leader_id = request.message.leader_id,
});
}

View File

@@ -0,0 +1,9 @@
const pb = @import("proto").pb;
const logic = @import("../../logic.zig");
const messaging = logic.messaging;
pub fn onCsFriendChatListSimpleSync(
request: messaging.Request(pb.CS_FRIEND_CHAT_LIST_SIMPLE_SYNC),
) !void {
try request.session.send(pb.SC_FRIEND_CHAT_LIST_SIMPLE_SYNC{});
}

View File

@@ -0,0 +1,27 @@
const pb = @import("proto").pb;
const logic = @import("../../logic.zig");
const messaging = logic.messaging;
pub fn onCsPing(
request: messaging.Request(pb.CS_PING),
timer: *logic.Resource.PingTimer,
) !void {
timer.last_client_ts = request.message.client_ts;
try request.session.send(pb.SC_PING{
.client_ts = request.message.client_ts,
.server_ts = timer.serverTime(),
});
}
pub fn onCsFlushSync(
request: messaging.Request(pb.CS_FLUSH_SYNC),
timer: *logic.Resource.PingTimer,
) !void {
timer.last_client_ts = request.message.client_ts;
try request.session.send(pb.SC_FLUSH_SYNC{
.client_ts = request.message.client_ts,
.server_ts = timer.serverTime(),
});
}

View File

@@ -0,0 +1,10 @@
const pb = @import("proto").pb;
const logic = @import("../../logic.zig");
const messaging = logic.messaging;
pub fn onSceneLoadFinish(
_: messaging.Request(pb.CS_SCENE_LOAD_FINISH),
sync_self_scene_tx: logic.event.Sender(.sync_self_scene),
) !void {
try sync_self_scene_tx.send(.{ .reason = .entrance });
}

View File

@@ -0,0 +1,27 @@
const std = @import("std");
const logic = @import("../logic.zig");
const meta = std.meta;
const Allocator = std.mem.Allocator;
pub fn resolve(
comptime Query: type,
world: *logic.World,
event_queue: *logic.event.Queue,
gpa: Allocator,
arena: Allocator,
) !Query {
if (comptime meta.activeTag(@typeInfo(Query)) == .@"struct") {
if (@hasDecl(Query, "allocator_kind")) {
switch (Query.allocator_kind) {
.gpa => return .{ .interface = gpa },
.arena => return .{ .interface = arena },
}
} else if (@hasDecl(Query, "tx_event_kind")) {
return .{ .event_queue = event_queue };
}
}
return world.getComponentByType(Query);
}

View File

@@ -0,0 +1,94 @@
const std = @import("std");
const logic = @import("../logic.zig");
const Session = @import("../Session.zig");
const meta = std.meta;
const event = logic.event;
const Io = std.Io;
const Allocator = std.mem.Allocator;
const namespaces = &.{
@import("systems/base.zig"),
@import("systems/game_vars.zig"),
@import("systems/unlock.zig"),
@import("systems/item_bag.zig"),
@import("systems/char_bag.zig"),
@import("systems/bitset.zig"),
@import("systems/dungeon.zig"),
@import("systems/domain_dev.zig"),
@import("systems/factory.zig"),
@import("systems/stubs.zig"),
@import("systems/friend.zig"),
@import("systems/scene.zig"),
@import("systems/player_saves.zig"),
};
pub const RunSystemsError = Io.Cancelable || Session.SendError || Allocator.Error;
// Initiate an event frame by triggering one.
pub fn triggerEvent(kind: event.Kind, world: *logic.World, gpa: Allocator) RunSystemsError!void {
var arena: std.heap.ArenaAllocator = .init(gpa); // Arena for the event frame.
defer arena.deinit();
var queue: event.Queue = .init(arena.allocator());
try queue.push(kind);
try run(world, &queue, gpa, arena.allocator());
}
// Execute the event frame.
pub fn run(world: *logic.World, queue: *event.Queue, gpa: Allocator, arena: Allocator) RunSystemsError!void {
while (queue.deque.popFront()) |event_kind| {
try dispatchEvent(event_kind, world, queue, gpa, arena);
}
}
// Process single event of the frame.
fn dispatchEvent(
kind: event.Kind,
world: *logic.World,
queue: *event.Queue,
gpa: Allocator,
arena: Allocator,
) RunSystemsError!void {
switch (kind) {
inline else => |payload, tag| inline for (namespaces) |namespace| {
inline for (@typeInfo(namespace).@"struct".decls) |decl_info| {
const decl = @field(namespace, decl_info.name);
const fn_info = switch (@typeInfo(@TypeOf(decl))) {
.@"fn" => |info| info,
else => continue,
};
if (fn_info.params.len == 0) continue;
const Param = fn_info.params[0].type.?;
if (!@hasDecl(Param, "rx_event_kind")) continue;
if (Param.rx_event_kind != tag) continue;
try invoke(payload, decl, world, queue, gpa, arena);
}
},
}
}
fn invoke(
payload: anytype,
decl: anytype,
world: *logic.World,
queue: *event.Queue,
gpa: Allocator,
arena: Allocator,
) !void {
var handler_args: meta.ArgsTuple(@TypeOf(decl)) = undefined;
handler_args[0] = .{ .payload = payload };
inline for (@typeInfo(@TypeOf(decl)).@"fn".params[1..], 1..) |param, i| {
handler_args[i] = logic.queries.resolve(param.type.?, world, queue, gpa, arena) catch {
return;
};
}
try @call(.auto, decl, handler_args);
}

View File

@@ -0,0 +1,21 @@
const pb = @import("proto").pb;
const logic = @import("../../logic.zig");
const Session = @import("../../Session.zig");
const Player = logic.Player;
pub fn syncBaseDataOnLogin(
rx: logic.event.Receiver(.login),
session: *Session,
base_comp: Player.Component(.base),
) !void {
_ = rx;
try session.send(pb.SC_SYNC_BASE_DATA{
.roleid = base_comp.data.role_id,
.role_name = base_comp.data.role_name.view(),
.level = @intFromEnum(base_comp.data.level),
.gender = @enumFromInt(@intFromEnum(base_comp.data.gender)),
.short_id = "1",
});
}

View File

@@ -0,0 +1,27 @@
const std = @import("std");
const pb = @import("proto").pb;
const logic = @import("../../logic.zig");
const Session = @import("../../Session.zig");
const Player = logic.Player;
pub fn syncAllBitset(
rx: logic.event.Receiver(.login),
session: *Session,
bitset: Player.Component(.bitset),
) !void {
_ = rx;
var sync_all_bitset: pb.SC_SYNC_ALL_BITSET = .init;
var sets_buf: [Player.Bitset.Type.count]pb.BITSET_DATA = undefined;
sync_all_bitset.bitset = .initBuffer(&sets_buf);
for (&bitset.data.sets, 1..) |*set, i| {
sync_all_bitset.bitset.appendAssumeCapacity(.{
.type = @intCast(i),
.value = .{ .items = @constCast(&set.masks) },
});
}
try session.send(sync_all_bitset);
}

View File

@@ -0,0 +1,100 @@
const std = @import("std");
const pb = @import("proto").pb;
const logic = @import("../../logic.zig");
const Assets = @import("../../Assets.zig");
const Session = @import("../../Session.zig");
const Player = logic.Player;
pub fn syncCharBag(
rx: logic.event.Receiver(.login),
assets: *const Assets,
session: *Session,
char_bag: Player.Component(.char_bag),
arena: logic.Resource.Allocator(.arena),
) !void {
_ = rx;
var sync_char_bag: pb.SC_SYNC_CHAR_BAG_INFO = .{
.curr_team_index = @intCast(char_bag.data.meta.curr_team_index),
.temp_team_info = .init,
.scope_name = 1,
.max_char_team_member_count = comptime @intCast(Player.CharBag.Team.slots_count),
};
const teams = char_bag.data.teams.slice();
try sync_char_bag.team_info.ensureTotalCapacity(arena.interface, teams.len);
const all_team_slots = try arena.interface.alloc([4]u64, teams.len);
for (
0..,
teams.items(.name),
teams.items(.char_team),
teams.items(.leader_index),
) |i, name, slots, leader_index| {
var char_team: std.ArrayList(u64) = .initBuffer(&all_team_slots[i]);
for (slots) |slot| if (slot != .empty) {
char_team.appendAssumeCapacity(@intFromEnum(slot) + 1);
};
sync_char_bag.team_info.appendAssumeCapacity(.{
.team_name = name.view(),
.char_team = .{ .items = char_team.items },
.leaderid = leader_index.objectId(),
});
}
const chars = char_bag.data.chars.slice();
try sync_char_bag.char_info.ensureTotalCapacity(arena.interface, chars.len);
for (0..chars.len) |i| {
const index: Player.CharBag.CharIndex = @enumFromInt(i);
const template_id = assets.numToStr(.char_id, chars.items(.template_id)[i]) orelse continue;
const skills = assets.char_skill_map.map.getPtr(template_id).?;
var char_info: pb.CHAR_INFO = .{
.objid = index.objectId(),
.templateid = template_id,
.char_type = .default_type,
.level = chars.items(.level)[i],
.exp = chars.items(.exp)[i],
.is_dead = chars.items(.is_dead)[i],
.weapon_id = chars.items(.weapon_id)[i].instId(),
.own_time = chars.items(.own_time)[i],
.equip_medicine_id = chars.items(.equip_medicine_id)[i],
.potential_level = chars.items(.potential_level)[i],
.normal_skill = skills.normal_skill,
.battle_info = .{ .hp = chars.items(.hp)[i], .ultimatesp = chars.items(.ultimate_sp)[i] },
.skill_info = .{
.normal_skill = skills.normal_skill,
.combo_skill = skills.combo_skill,
.ultimate_skill = skills.ultimate_skill,
.disp_normal_attack_skill = skills.attack_skill,
},
.talent = .{},
.battle_mgr_info = .{
.msg_generation = @truncate(index.objectId()),
.battle_inst_id = @truncate(index.objectId()),
.part_inst_info = .{},
},
.trial_data = .{},
};
try char_info.skill_info.?.level_info.ensureTotalCapacity(arena.interface, skills.all_skills.len);
for (skills.all_skills) |name| {
char_info.skill_info.?.level_info.appendAssumeCapacity(.{
.skill_id = name,
.skill_level = 1,
.skill_max_level = 1,
.skill_enhanced_level = 1,
});
}
try sync_char_bag.char_info.append(arena.interface, char_info);
}
try session.send(sync_char_bag);
}

View File

@@ -0,0 +1,23 @@
const pb = @import("proto").pb;
const logic = @import("../../logic.zig");
const Assets = @import("../../Assets.zig");
const Session = @import("../../Session.zig");
pub fn syncDomainDevSystem(
rx: logic.event.Receiver(.login),
session: *Session,
assets: *const Assets,
arena: logic.Resource.Allocator(.arena),
) !void {
_ = rx;
var domain_dev_sync: pb.SC_DOMAIN_DEVELOPMENT_SYSTEM_SYNC = .init;
for (assets.table(.domain_data).keys()) |chapter_id| {
try domain_dev_sync.domains.append(arena.interface, .{
.chapter_id = chapter_id,
.dev_degree = .{ .level = 1 },
});
}
try session.send(domain_dev_sync);
}

View File

@@ -0,0 +1,16 @@
const pb = @import("proto").pb;
const logic = @import("../../logic.zig");
const Session = @import("../../Session.zig");
pub fn syncFullDungeonStatus(
rx: logic.event.Receiver(.login),
session: *Session,
) !void {
_ = rx;
// TODO
try session.send(pb.SC_SYNC_FULL_DUNGEON_STATUS{
.cur_stamina = 200,
.max_stamina = 200,
});
}

View File

@@ -0,0 +1,53 @@
const pb = @import("proto").pb;
const logic = @import("../../logic.zig");
const Assets = @import("../../Assets.zig");
const Session = @import("../../Session.zig");
const default_chapter = "domain_2";
pub fn syncFactoryData(
rx: logic.event.Receiver(.login),
session: *Session,
assets: *const Assets,
arena: logic.Resource.Allocator(.arena),
) !void {
_ = rx;
try session.send(pb.SC_FACTORY_SYNC{
.stt = .init,
.formula_man = .init,
.progress_status = .init,
});
var factory_sync_scope: pb.SC_FACTORY_SYNC_SCOPE = .{
.scope_name = 1,
.current_chapter_id = default_chapter,
.transport_route = .init,
.book_mark = .init,
.sign_mgr = .init,
.shared_mgr = .init,
};
for (assets.table(.domain_data).keys()) |chapter_id| {
try factory_sync_scope.active_chapter_ids.append(arena.interface, chapter_id);
}
try session.send(factory_sync_scope);
for (assets.table(.domain_data).keys()) |chapter_id| {
try session.send(pb.SC_FACTORY_SYNC_CHAPTER{
.chapter_id = chapter_id,
.blackboard = .{ .power = .{ .is_stop_by_power = true } },
.pin_board = .{},
.statistic = .{},
.pending_place = .{},
});
try session.send(pb.SC_FACTORY_HS{
.blackboard = .{
.power = .{ .is_stop_by_power = true },
},
.chapter_id = chapter_id,
});
}
}

View File

@@ -0,0 +1,19 @@
const pb = @import("proto").pb;
const logic = @import("../../logic.zig");
const Session = @import("../../Session.zig");
pub fn syncPersonalData(
rx: logic.event.Receiver(.login),
session: *Session,
) !void {
_ = rx;
// TODO
try session.send(pb.SC_FRIEND_PERSONAL_DATA_SYNC{
.data = .{
.user_avatar_id = 7,
.User_avatar_frame_id = 3,
.business_card_topic_id = 11,
},
});
}

View File

@@ -0,0 +1,32 @@
const pb = @import("proto").pb;
const logic = @import("../../logic.zig");
const Session = @import("../../Session.zig");
const Player = logic.Player;
pub fn syncAllGameVars(
rx: logic.event.Receiver(.login),
session: *Session,
game_vars: Player.Component(.game_vars),
arena: logic.Resource.Allocator(.arena),
) !void {
_ = rx;
var sync_all_game_var: pb.SC_SYNC_ALL_GAME_VAR = .init;
try sync_all_game_var.server_vars.ensureTotalCapacity(arena.interface, game_vars.data.server_vars.len);
try sync_all_game_var.client_vars.ensureTotalCapacity(arena.interface, game_vars.data.client_vars.len);
for (game_vars.data.server_vars) |sv| {
sync_all_game_var.server_vars.appendAssumeCapacity(
.{ .key = @intFromEnum(sv.key), .value = sv.value },
);
}
for (game_vars.data.client_vars) |cv| {
sync_all_game_var.client_vars.appendAssumeCapacity(
.{ .key = cv.key, .value = cv.value },
);
}
try session.send(sync_all_game_var);
}

View File

@@ -0,0 +1,55 @@
const pb = @import("proto").pb;
const logic = @import("../../logic.zig");
const Session = @import("../../Session.zig");
const Player = logic.Player;
pub fn syncItemBagScopes(
rx: logic.event.Receiver(.login),
session: *Session,
item_bag: Player.Component(.item_bag),
char_bag: Player.Component(.char_bag),
arena: logic.Resource.Allocator(.arena),
) !void {
_ = rx;
var item_bag_scope_sync: pb.SC_ITEM_BAG_SCOPE_SYNC = .{
.bag = .init,
.quick_bar = .init,
.assistant = .init,
.scope_name = 1,
};
var weapon_depot: pb.SCD_ITEM_DEPOT = .init;
try weapon_depot.inst_list.ensureTotalCapacity(arena.interface, item_bag.data.weapon_depot.len);
const weapon_slice = item_bag.data.weapon_depot.slice();
for (0..weapon_slice.len) |i| {
const weapon_index: Player.ItemBag.WeaponIndex = @enumFromInt(i);
const weapon = weapon_slice.get(i);
weapon_depot.inst_list.appendAssumeCapacity(.{
.count = 1,
.inst = .{
.inst_id = weapon_index.instId(),
.inst_impl = .{ .weapon = .{
.inst_id = weapon_index.instId(),
.template_id = weapon.template_id,
.exp = weapon.exp,
.weapon_lv = weapon.weapon_lv,
.refine_lv = weapon.refine_lv,
.breakthrough_lv = weapon.breakthrough_lv,
.attach_gem_id = weapon.attach_gem_id,
.equip_char_id = if (char_bag.data.charIndexWithWeapon(weapon_index)) |char_index|
char_index.objectId()
else
0,
} },
},
});
}
try item_bag_scope_sync.depot.append(arena.interface, .{ .key = 1, .value = weapon_depot });
try session.send(item_bag_scope_sync);
}

View File

@@ -0,0 +1,38 @@
const std = @import("std");
const pb = @import("proto").pb;
const logic = @import("../../logic.zig");
const Session = @import("../../Session.zig");
const fs = @import("../../fs.zig");
const Io = std.Io;
const Player = logic.Player;
const log = std.log.scoped(.player_saves);
pub fn saveCharBagTeams(
_: logic.event.Receiver(.char_bag_team_modified),
char_bag: Player.Component(.char_bag),
player_id: logic.World.PlayerId,
io: Io,
) !void {
const data_dir = fs.persistence.openPlayerDataDir(io, player_id.uid) catch |err| switch (err) {
error.Canceled => |e| return e,
else => |e| {
log.err(
"failed to open data dir for player with uid {d}: {t}",
.{ player_id.uid, e },
);
return;
},
};
defer data_dir.close(io);
fs.persistence.saveCharBagComponent(io, data_dir, char_bag.data, .teams) catch |err| switch (err) {
error.Canceled => |e| return e,
else => |e| {
log.err("save failed: {t}", .{e});
return;
},
};
}

View File

@@ -0,0 +1,180 @@
const std = @import("std");
const pb = @import("proto").pb;
const logic = @import("../../logic.zig");
const Session = @import("../../Session.zig");
const Assets = @import("../../Assets.zig");
const Player = logic.Player;
const ArrayList = std.ArrayList;
const Allocator = std.mem.Allocator;
const default_level = "map02_lv001";
pub fn enterSceneOnLogin(
rx: logic.event.Receiver(.login),
session: *Session,
assets: *const Assets,
base_comp: Player.Component(.base),
) !void {
_ = rx;
const level_config = assets.level_config_table.getPtr(default_level).?;
const position: pb.VECTOR = .{
.X = level_config.playerInitPos.x,
.Y = level_config.playerInitPos.y,
.Z = level_config.playerInitPos.z,
};
try session.send(pb.SC_CHANGE_SCENE_BEGIN_NOTIFY{
.scene_num_id = level_config.idNum,
.position = position,
.pass_through_data = .init,
});
try session.send(pb.SC_ENTER_SCENE_NOTIFY{
.role_id = base_comp.data.role_id,
.scene_num_id = level_config.idNum,
.position = position,
.pass_through_data = .init,
});
}
pub fn refreshCharTeam(
rx: logic.event.Receiver(.char_bag_team_modified),
char_bag: Player.Component(.char_bag),
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 sync_tx.send(.{ .reason = .team_modified });
},
}
}
pub fn syncSelfScene(
rx: logic.event.Receiver(.sync_self_scene),
session: *Session,
arena: logic.Resource.Allocator(.arena),
char_bag: logic.Player.Component(.char_bag),
assets: *const Assets,
) !void {
const reason: pb.SELF_INFO_REASON_TYPE = switch (rx.payload.reason) {
.entrance => .SLR_ENTER_SCENE,
.team_modified => .SLR_CHANGE_TEAM,
};
const level_config = assets.level_config_table.getPtr(default_level).?;
const position: pb.VECTOR = .{
.X = level_config.playerInitPos.x,
.Y = level_config.playerInitPos.y,
.Z = level_config.playerInitPos.z,
};
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 = level_config.idNum,
.self_info_reason = @intFromEnum(reason),
.teamInfo = .{
.team_type = .CHAR_BAG_TEAM_TYPE_MAIN,
.team_index = @intCast(team_index),
.cur_leader_id = leader_index.objectId(),
.team_change_token = 0,
},
.scene_impl = .{ .empty = .{} },
.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).?;
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 = level_config.idNum,
},
};
for (char_data.attributes[0].Attribute.attrs) |attr| {
if (attr.attrType == .max_hp)
scene_char.common_info.?.hp = attr.attrValue;
try scene_char.attrs.append(arena.interface, .{
.attr_type = @intFromEnum(attr.attrType),
.basic_value = attr.attrValue,
.value = attr.attrValue,
});
}
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);
}
fn packCharacterSkills(
arena: Allocator,
assets: *const Assets,
template_id: []const u8,
) Allocator.Error!ArrayList(pb.SERVER_SKILL) {
const char_skills = assets.char_skill_map.map.getPtr(template_id).?.all_skills;
var list: ArrayList(pb.SERVER_SKILL) = try .initCapacity(
arena,
char_skills.len + assets.common_skill_config.config.Character.skillConfigs.len,
);
errdefer comptime unreachable;
for (char_skills, 1..) |name, i| {
list.appendAssumeCapacity(.{
.skill_id = .{
.id_impl = .{ .str_id = name },
.type = .BATTLE_ACTION_OWNER_TYPE_SKILL,
},
.blackboard = .{},
.inst_id = (100 + i),
.level = 1,
.source = .BATTLE_SKILL_SOURCE_DEFAULT,
.potential_lv = 1,
.is_enable = true,
});
}
for (assets.common_skill_config.config.Character.skillConfigs, char_skills.len + 1..) |config, i| {
list.appendAssumeCapacity(.{
.skill_id = .{
.id_impl = .{ .str_id = config.skillId },
.type = .BATTLE_ACTION_OWNER_TYPE_SKILL,
},
.blackboard = .{},
.inst_id = (100 + i),
.level = 1,
.source = .BATTLE_SKILL_SOURCE_DEFAULT,
.potential_lv = 1,
.is_enable = true,
});
}
return list;
}

View File

@@ -0,0 +1,50 @@
const pb = @import("proto").pb;
const logic = @import("../../logic.zig");
const Session = @import("../../Session.zig");
// Sends the dummy 'SYNC' messages for the components that aren't implemented yet.
pub fn loginSyncStub(
rx: logic.event.Receiver(.login),
session: *Session,
) !void {
_ = rx;
try session.send(pb.SC_ADVENTURE_SYNC_ALL{
.level = 1,
.world_level = 1,
.unlock_world_level = 1,
});
try session.send(pb.SC_ADVENTURE_BOOK_SYNC{
.adventure_book_stage = 1,
});
try session.send(pb.SC_SYNC_ALL_MINI_GAME.init);
try session.send(pb.SC_SYNC_ALL_MAIL.init);
try session.send(pb.SC_KITE_STATION_SYNC_ALL.init);
try session.send(pb.SC_SYNC_ALL_GUIDE.init);
try session.send(pb.SC_GLOBAL_EFFECT_SYNC_ALL.init);
try session.send(pb.SC_SYNC_ALL_DOODAD_GROUP.init);
try session.send(pb.SC_SETTLEMENT_SYNC_ALL.init);
try session.send(pb.SC_DOMAIN_DEPOT_SYNC_ALL_INFO.init);
try session.send(pb.SC_SYNC_ALL_DIALOG.init);
try session.send(pb.SC_SYNC_ALL_ROLE_SCENE.init);
try session.send(pb.SC_SYNC_ALL_WIKI.init);
try session.send(pb.SC_RECYCLE_BIN_SYSTEM_SYNC_ALL.init);
try session.send(pb.SC_SYNC_ALL_STAT.init);
try session.send(pb.SC_BP_SYNC_ALL{
.season_data = .init,
.level_data = .init,
.bp_track_mgr = .init,
.bp_task_mgr = .init,
});
try session.send(pb.SC_SYNC_ALL_MISSION.init);
try session.send(pb.SC_SPACESHIP_SYNC{
.assist_data = .init,
.expedition_data = .init,
});
try session.send(pb.SC_ACHIEVE_SYNC{
.achieve_display_info = .init,
});
}

View File

@@ -0,0 +1,22 @@
const pb = @import("proto").pb;
const logic = @import("../../logic.zig");
const Session = @import("../../Session.zig");
const Player = logic.Player;
pub fn syncAllUnlock(
rx: logic.event.Receiver(.login),
session: *Session,
unlock: Player.Component(.unlock),
arena: logic.Resource.Allocator(.arena),
) !void {
_ = rx;
var sync_all_unlock: pb.SC_SYNC_ALL_UNLOCK = .init;
try sync_all_unlock.unlock_systems.appendSlice(
arena.interface,
@ptrCast(unlock.data.unlocked_systems),
);
try session.send(sync_all_unlock);
}