First push

This commit is contained in:
HuLiNap
2025-11-06 00:55:42 +07:00
parent 067490137f
commit c5780207ac
80 changed files with 1882404 additions and 1 deletions

View File

@@ -1,2 +1,61 @@
# dahlia-sr-0.14.1
# DahliaSR (0.14.1)
#### Honkai: Star Rail server emulator (3.8 beta) written in Zig.
![Screenshot](Screenshot.png)
## Requirements
- [Zig 0.14.1 x64](https://ziglang.org/download/0.14.1/zig-x86_64-windows-0.14.1.zip)
- [Cyrene patch](https://git.xeondev.com/HonkaiSlopRail/sr-launcher/releases)
## Running
### Setup client patch
You have to apply the necessary [Cyrene patch](https://git.xeondev.com/HonkaiSlopRail/sr-launcher/releases). It allows you to connect to the local server and replaces encryption keys with custom ones. After you download patch, extract it to client folder.
### From source
Windows:
```
git clone https://git.xeondev.com/HonkaiSlopRail/dahlia-sr-0.14.1
cd dahlia-sr-0.14.1
zig build run-dispatch
zig build run-gameserver
```
### Using Pre-built Binaries
Navigate to the [Releases](https://git.xeondev.com/HonkaiSlopRail/dahlia-sr-0.14.1/releases)
page and download the latest release for your platform.
## Connecting
[Get 3.7.51 CN client](https://gofile.io/d/Yul9Nm),
After you extract client package, delete `AccountPlatNative.dll` inside \StarRail_Data\Plugins\x86_64 in order to make this PS work
(recommend rename it instead in case something go wrong)
## Functionality (work in progress)
- Login and player spawn
- Test battle via calyx
- MOC/PF/AS simulator with custom stage sellection
- Anomaly Arbitration (Challenge Peak)
- Gacha simulator
- Support command for Sillyism
## Contributing
Pull requests are welcome. For major changes, please open an issue first to discuss
what you would like to change, and why.
## Important note
Due to the missing cmd ids and the massive amount of events haven't had dummy handled so that you will expect to have some loading at the begining after first login. After that it will work normally.
## Bug Reports
If you find a bug, please open an issue with as much detail as possible. If you
can, please include steps to reproduce the bug.
Bad issues such as "This doesn't work" will be closed immediately, be _sure_ to
provide exact detailed steps to reproduce your bug. If it's hard to reproduce, try
to explain it and write a reproducer as best as you can.

BIN
Screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 MiB

64
build.zig Normal file
View File

@@ -0,0 +1,64 @@
const std = @import("std");
const protobuf = @import("protobuf");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const dep_opts = .{ .target = target, .optimize = optimize };
const protobuf_dep = b.dependency("protobuf", dep_opts);
if (std.fs.cwd().access("protocol/StarRail.proto", .{})) {
const protoc_step = protobuf.RunProtocStep.create(b, protobuf_dep.builder, target, .{
.destination_directory = b.path("protocol/src"),
.source_files = &.{
"protocol/StarRail.proto",
},
.include_directories = &.{},
});
b.getInstallStep().dependOn(&protoc_step.step);
} else |_| {} // don't invoke protoc if proto definition doesn't exist
const dispatch = b.dependency("dispatch", dep_opts);
b.installArtifact(dispatch.artifact("dispatch"));
const gameserver = b.dependency("gameserver", dep_opts);
b.installArtifact(gameserver.artifact("gameserver"));
// "run-dispatch" command
const dispatch_cmd = b.addRunArtifact(dispatch.artifact("dispatch"));
dispatch_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| {
dispatch_cmd.addArgs(args);
}
const dispatch_step = b.step("run-dispatch", "Run the dispatch server");
dispatch_step.dependOn(&dispatch_cmd.step);
// "run-gameserver" command
const gameserver_cmd = b.addRunArtifact(gameserver.artifact("gameserver"));
gameserver_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| {
gameserver_cmd.addArgs(args);
}
const gameserver_step = b.step("run-gameserver", "Run the game server");
gameserver_step.dependOn(&gameserver_cmd.step);
// "gen-proto"
const gen_proto = b.step("gen-proto", "generates zig files from protocol buffer definitions");
const protoc_step = protobuf.RunProtocStep.create(b, protobuf_dep.builder, target, .{
// out directory for the generated zig files
.destination_directory = b.path("protocol/src"),
.source_files = &.{
"protocol/StarRail.proto",
},
.include_directories = &.{},
});
gen_proto.dependOn(&protoc_step.step);
}

16
build.zig.zon Normal file
View File

@@ -0,0 +1,16 @@
.{
.name = .DahliaSR,
.version = "0.14.1",
.fingerprint = 0x46996485ef0dc154,
.minimum_zig_version = "0.14.0",
.dependencies = .{
.dispatch = .{ .path = "dispatch" },
.gameserver = .{ .path = "gameserver" },
.protocol = .{ .path = "protocol" },
.protobuf = .{
.url = "https://github.com/Arwalk/zig-protobuf/archive/da4d17264fcfdd793e71b3a8b466fb0f05bc6fd3.tar.gz",
.hash = "122052e8e9e4233621ebeba2215df92dbb78387be6193bdc24da3f44532ddeeb25ab",
},
},
.paths = .{""},
}

40
config.json Normal file
View File

@@ -0,0 +1,40 @@
{
"avatar_config": [
{
"name": "The Dahlia",
"id": 1321,
"hp": 100,
"sp": 50,
"level": 80,
"promotion": 6,
"rank": 0,
"lightcone": {
"id": 23050,
"rank": 1,
"level": 80,
"promotion": 6
},
"relics": [
"61191,15,1,4,9:1:1,6:2:2,10:3:3,4:3:3",
"61192,15,1,4,6:2:2,4:1:1,9:3:3,12:3:3",
"61193,15,1,4,11:4:4,1:2:2,9:1:1,12:2:2",
"61194,15,4,4,1:2:2,6:5:5,8:1:1,10:1:1",
"63165,15,1,4,8:2:2,7:2:2,3:3:3,5:2:2",
"63166,15,1,4,5:3:3,1:3:3,6:1:1,7:2:2"
],
"use_technique": true
}
],
"battle_config": {
"battle_id": 1,
"stage_id": 1052086,
"cycle_count": 30,
"monster_wave": [
[
4035010
]
],
"monster_level": 82,
"blessings": []
}
}

46
dispatch/build.zig Normal file
View File

@@ -0,0 +1,46 @@
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const exe = b.addExecutable(.{
.name = "dispatch",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
const httpz_dep = b.dependency("httpz", .{
.target = target,
.optimize = optimize,
});
exe.root_module.addImport("httpz", httpz_dep.module("httpz"));
const protocol_dep = b.dependency("protocol", .{
.target = target,
.optimize = optimize,
});
exe.root_module.addImport("protocol", protocol_dep.module("protocol"));
const tls_dep = b.dependency("tls", .{
.target = target,
.optimize = optimize,
});
exe.root_module.addImport("tls", tls_dep.module("tls"));
b.installArtifact(exe);
const run_cmd = b.addRunArtifact(exe);
run_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| {
run_cmd.addArgs(args);
}
const run_step = b.step("run", "Run the app");
run_step.dependOn(&run_cmd.step);
const run_dispatch_step = b.step("run-dispatch", "Run dispatch");
run_dispatch_step.dependOn(&run_cmd.step);
}

24
dispatch/build.zig.zon Normal file
View File

@@ -0,0 +1,24 @@
.{
.name = .dispatch,
.version = "0.0.1",
.fingerprint = 0x8612665a108f7ff7,
.minimum_zig_version = "0.14.0",
.dependencies = .{
.httpz = .{
.url = "https://github.com/karlseguin/http.zig/archive/ef50c6878c3c512786e3311df657eb425b87ba91.tar.gz",
.hash = "1220cf9b8944def0907a43f208a004d9322c6f1972f86792d7f82527965020eb5fba",
},
.protocol = .{
.path = "../protocol",
},
.tls = .{
.url = "https://github.com/ianic/tls.zig/archive/602f05b.tar.gz",
.hash = "12201a2aeb55d0c4a112c5a1806e4d90d734e0e4c21301d4b289f734c3412a883312",
},
},
.paths = .{
"build.zig",
"build.zig.zon",
"src",
},
}

View File

@@ -0,0 +1,219 @@
const std = @import("std");
const httpz = @import("httpz");
pub fn onShieldLogin(req: *httpz.Request, res: *httpz.Response) !void {
std.log.debug("onShieldLogin: {any}", .{req.body_len});
//std.log.debug("onShieldLogin BODY: {any}", .{req}); game breaking
try res.json(.{
.data = .{
.account = .{
.area_code = "**",
.email = "ReversedRoooms@StarRail.com",
.country = "RU",
.is_email_verify = "1",
.token = "aa",
.uid = "1337",
},
.device_grant_required = false,
.reactivate_required = false,
.realperson_required = false,
.safe_mobilerequired = false,
},
.message = "OK",
.retcode = 0,
}, .{});
}
pub fn onShieldVerify(req: *httpz.Request, res: *httpz.Response) !void {
std.log.debug("onShieldVerify: {any}", .{req.body_len});
try res.json(.{
.data = .{
.account = .{
.area_code = "**",
.email = "ReversedRoooms@StarRail.com",
.country = "RU",
.is_email_verify = "1",
.token = "aa",
.uid = "1337",
},
.device_grant_required = false,
.reactivate_required = false,
.realperson_required = false,
.safe_mobilerequired = false,
},
.message = "OK",
.retcode = 0,
}, .{});
}
pub fn onVerifyLogin(req: *httpz.Request, res: *httpz.Response) !void {
std.log.debug("onVerifyLogin: {any}", .{req.body_len});
var token: []const u8 = "aa";
var uid: []const u8 = "1337";
if (try req.jsonObject()) |t| {
if (t.get("token")) |token_value| {
token = token_value.string;
}
if (t.get("uid")) |uid_value| {
uid = uid_value.string;
}
}
try res.json(.{
.retcode = 0,
.message = "OK",
.data = .{
.account = .{
.area_code = "**",
.country = "CN",
.is_email_verify = "1",
.email = "ReversedRoooms@StarRail.com",
.token = token,
.uid = uid,
},
},
}, .{});
}
pub fn onComboTokenReq(req: *httpz.Request, res: *httpz.Response) !void {
std.log.debug("onComboTokenReq: {any}", .{req.body_len});
try res.json(.{
.data = .{
.account_type = 1,
.open_id = "1337",
.combo_id = "1337",
.combo_token = "9065ad8507d5a1991cb6fddacac5999b780bbd92",
.heartbeat = false,
.data = "{\"guest\": false}",
},
.message = "OK",
.retcode = 0,
}, .{});
}
pub fn onRiskyApiCheck(req: *httpz.Request, res: *httpz.Response) !void {
std.log.debug("onRiskyApiCheck: {any}", .{req.body_len});
try res.json(.{
.retcode = 0,
.message = "OK",
.data = .{
.id = "06611ed14c3131a676b19c0d34c0644b",
.action = "ACTION_NONE",
.geetest = null,
},
}, .{});
}
pub fn onGetConfig(_: *httpz.Request, res: *httpz.Response) !void {
std.log.debug("onGetConfig: ", .{});
try res.json(.{
.retcode = 0,
.message = "OK",
.data = .{
.protocol = true,
.qr_enabled = false,
.log_level = "INFO",
.announce_url = "",
.push_alias_type = 0,
.disable_ysdk_guard = true,
.enable_announce_pic_popup = false,
.app_name = "崩<EFBFBD>??RPG",
.qr_enabled_apps = .{
.bbs = false,
.cloud = false,
},
.qr_app_icons = .{
.app = "",
.bbs = "",
.cloud = "",
},
.qr_cloud_display_name = "",
.enable_user_center = true,
.functional_switch_configs = .{},
},
}, .{});
}
pub fn onLoadConfig(_: *httpz.Request, res: *httpz.Response) !void {
std.log.debug("onLoadConfig: ", .{});
try res.json(.{
.retcode = 0,
.message = "OK",
.data = .{
.id = 24,
.game_key = "hkrpg_global",
.client = "PC",
.identity = "I_IDENTITY",
.guest = false,
.ignore_versions = "",
.scene = "S_NORMAL",
.name = "崩<EFBFBD>??RPG",
.disable_regist = false,
.enable_email_captcha = false,
.thirdparty = .{ "fb", "tw", "gl", "ap" },
.disable_mmt = false,
.server_guest = false,
.thirdparty_ignore = .{},
.enable_ps_bind_account = false,
.thirdparty_login_configs = .{
.tw = .{
.token_type = "TK_GAME_TOKEN",
.game_token_expires_in = 2592000,
},
.ap = .{
.token_type = "TK_GAME_TOKEN",
.game_token_expires_in = 604800,
},
.fb = .{
.token_type = "TK_GAME_TOKEN",
.game_token_expires_in = 2592000,
},
.gl = .{
.token_type = "TK_GAME_TOKEN",
.game_token_expires_in = 604800,
},
},
.initialize_firebase = false,
.bbs_auth_login = false,
.bbs_auth_login_ignore = {},
.fetch_instance_id = false,
.enable_flash_login = false,
},
}, .{});
}
pub fn onappLoginByPassword(req: *httpz.Request, res: *httpz.Response) !void {
std.log.debug("onappLoginByPassword: {any}", .{req.body_len});
try res.json(.{
.retcode = 0,
.message = "OK",
.data = .{
.bind_email_action_ticket = "",
.ext_user_info = .{
.birth = "0",
.guardian_email = "",
},
.reactivate_action_token = "",
.token = .{
.token = "aa",
.token_type = "1",
},
.user_info = .{
.account_name = "ReversedRooms",
.aid = "1337",
.area_code = "**",
.country = "RU",
.email = "ReversedRoooms@StarRail.com",
.is_email_verify = "1",
},
},
}, .{});
}

355
dispatch/src/dispatch.zig Normal file
View File

@@ -0,0 +1,355 @@
const std = @import("std");
const httpz = @import("httpz");
const protocol = @import("protocol");
const tls = @import("tls");
const hotfixInfo = @import("hotfix.zig");
const Base64Encoder = @import("std").base64.standard.Encoder;
const Base64Decoder = @import("std").base64.standard.Decoder;
const HotfixInfo = struct {
asset_bundle_url: []const u8,
ex_resource_url: []const u8,
lua_url: []const u8,
ifix_url: []const u8,
pub fn init() HotfixInfo {
return HotfixInfo{
.asset_bundle_url = "",
.ex_resource_url = "",
.lua_url = "",
.ifix_url = "",
};
}
};
pub fn onQueryDispatch(_: *httpz.Request, res: *httpz.Response) !void {
var proto = protocol.Dispatch.init(res.arena);
proto.retcode = 0;
try proto.region_list.append(.{
.name = .{ .Const = "DahliaSR (0.14.1)" },
.display_name = .{ .Const = "DahliaSR (0.14.1)" },
.env_type = .{ .Const = "9" },
.title = .{ .Const = "DahliaSR (0.14.1)" },
.dispatch_url = .{ .Const = "http://127.0.0.1:21000/query_gateway" },
});
const data = try proto.encode(res.arena);
const size = Base64Encoder.calcSize(data.len);
const output = try res.arena.alloc(u8, size);
_ = Base64Encoder.encode(output, data);
res.body = output;
}
pub fn onQueryGateway(req: *httpz.Request, res: *httpz.Response) !void {
var proto = protocol.GateServer.init(res.arena);
proto.port = 23301;
proto.ip = .{ .Const = "127.0.0.1" };
const query = try req.query();
const version = query.get("version") orelse "";
const dispatch_seed = query.get("dispatch_seed") orelse "";
var asset_bundle_url: []const u8 = "";
var ex_resource_url: []const u8 = "";
var lua_url: []const u8 = "";
var ifix_url: []const u8 = "";
// Step 1: Try to fetch from hotfix.json first
const hotfix_from_file = hotfixInfo.Parser(res.arena, "hotfix.json", version) catch null;
if (hotfix_from_file) |hotfix| {
if (hotfix.assetBundleUrl.len > 0) {
asset_bundle_url = hotfix.assetBundleUrl;
ex_resource_url = hotfix.exResourceUrl;
lua_url = hotfix.luaUrl;
ifix_url = hotfix.iFixUrl;
}
}
// Step 2: If hotfix.json doesn't have URLs, try to fetch from server
if (asset_bundle_url.len == 0 and version.len > 0) {
std.log.info("Found new hotfix version: {s}, fetching from server...", .{version});
// Try multiple times for reliability
const max_retries = 3;
var retry_count: u32 = 0;
var server_hotfix: ?HotfixInfo = null;
while (retry_count < max_retries and server_hotfix == null) {
retry_count += 1;
server_hotfix = fetchHotfixInfo(res.arena, version, dispatch_seed) catch blk: {
if (retry_count < max_retries) {
std.time.sleep(100 * std.time.ns_per_ms); // 100ms delay
}
break :blk null;
};
}
if (server_hotfix) |hotfix| {
if (hotfix.asset_bundle_url.len > 0) {
// Save to hotfix.json
hotfixInfo.putValue(version, hotfix.asset_bundle_url, hotfix.ex_resource_url, hotfix.lua_url, hotfix.ifix_url) catch {};
asset_bundle_url = hotfix.asset_bundle_url;
ex_resource_url = hotfix.ex_resource_url;
lua_url = hotfix.lua_url;
ifix_url = hotfix.ifix_url;
}
}
}
// Step 3: If still no URLs, show error message
if (asset_bundle_url.len == 0) {
std.log.err("No suitable hotfix found for version: {s}. Please check your client version or contact https://discord.gg/reversedrooms", .{version});
// Use placeholder URLs to avoid crashes
asset_bundle_url = "UNSUPPORTED_VERSION";
ex_resource_url = "UNSUPPORTED_VERSION";
lua_url = "UNSUPPORTED_VERSION";
} else {
// Success - log the final URLs
std.log.info("Hotfix for {s}:", .{version});
std.log.info(" Asset Bundle: {s}", .{asset_bundle_url});
std.log.info(" Ex Resource: {s}", .{ex_resource_url});
std.log.info(" Lua: {s}", .{lua_url});
}
proto.asset_bundle_url = .{ .Const = asset_bundle_url };
proto.ex_resource_url = .{ .Const = ex_resource_url };
proto.lua_url = .{ .Const = lua_url };
proto.unk1 = true;
proto.unk2 = true;
proto.unk3 = true;
proto.unk4 = true;
proto.unk5 = true;
proto.unk6 = true;
proto.unk7 = true;
proto.unk8 = true;
proto.unk9 = true;
proto.unk10 = true;
proto.NMOCNDHBPNF = true;
proto.FDLIDHBPDJP = true;
proto.EDEPKFDHKGH = true;
proto.DBBDEFFFFNG = true;
proto.JHDAMHIBPDF = true;
proto.OAHFBCEIIEA = true;
proto.KGOGMMCNOBP = true;
const data = try proto.encode(res.arena);
const size = Base64Encoder.calcSize(data.len);
const output = try res.arena.alloc(u8, size);
_ = Base64Encoder.encode(output, data);
res.body = output;
}
pub fn selectHost(version: []const u8) []const u8 {
if (std.mem.startsWith(u8, version, "CNPROD")) {
return "prod-gf-cn-dp01.bhsr.com";
} else if (std.mem.startsWith(u8, version, "CNBETA")) {
return "beta-release01-cn.bhsr.com";
} else if (std.mem.startsWith(u8, version, "OSPROD")) {
return "prod-official-asia-dp01.starrails.com";
} else if (std.mem.startsWith(u8, version, "OSBETA")) {
return "beta-release01-asia.starrails.com";
} else {
return "";
}
}
pub fn constructUrl(allocator: std.mem.Allocator, host: []const u8, version: []const u8, dispatch_seed: []const u8) ![]const u8 {
return std.fmt.allocPrint(allocator, "https://{s}/query_gateway?version={s}&dispatch_seed={s}&language_type=1&platform_type=2&channel_id=1&sub_channel_id=1&is_need_url=1&account_type=1", .{ host, version, dispatch_seed });
}
pub fn fetchHotfixInfo(allocator: std.mem.Allocator, version: []const u8, dispatch_seed: []const u8) !HotfixInfo {
const host = selectHost(version);
if (host.len == 0) return error.UnknownVersion;
const gateway_url = try constructUrl(allocator, host, version, dispatch_seed);
defer allocator.free(gateway_url);
const uri = std.Uri.parse(gateway_url) catch return error.InvalidUrl;
const hostname = if (uri.host) |h| h.percent_encoded else return error.NoHost;
const port: u16 = uri.port orelse 443;
const path = if (uri.path.percent_encoded.len > 0) uri.path.percent_encoded else "/";
const query_string = if (uri.query) |q| q.percent_encoded else "";
const address_list = try std.net.getAddressList(allocator, hostname, port);
defer address_list.deinit();
if (address_list.addrs.len == 0) return error.NoAddressFound;
const socket = try std.net.tcpConnectToAddress(address_list.addrs[0]);
defer socket.close();
var tls_conn = tls.client(socket, .{
.host = hostname,
.root_ca = .{},
.insecure_skip_verify = true,
}) catch return error.TlsHandshakeFailed;
const full_path = if (query_string.len > 0)
try std.fmt.allocPrint(allocator, "{s}?{s}", .{ path, query_string })
else
try allocator.dupe(u8, path);
defer allocator.free(full_path);
const request = try std.fmt.allocPrint(allocator, "GET {s} HTTP/1.1\r\n" ++
"Host: {s}\r\n" ++
"User-Agent: UnityPlayer/2021.3.21f1\r\n" ++
"Accept: */*\r\n" ++
"Connection: close\r\n" ++
"\r\n", .{ full_path, hostname });
defer allocator.free(request);
try tls_conn.writeAll(request);
const response_data = try readAllTlsData(&tls_conn, allocator);
defer allocator.free(response_data);
const body_start = std.mem.indexOf(u8, response_data, "\r\n\r\n") orelse return error.InvalidResponse;
const raw_body = response_data[body_start + 4 ..];
const body = if (std.mem.indexOf(u8, response_data, "Transfer-Encoding: chunked") != null)
try dechunkHttpBody(allocator, raw_body)
else
try allocator.dupe(u8, raw_body);
defer allocator.free(body);
if (body.len == 0) return error.EmptyResponse;
const decoded_len = Base64Decoder.calcSizeForSlice(body) catch return error.InvalidBase64;
const decoded_data = try allocator.alloc(u8, decoded_len);
defer allocator.free(decoded_data);
Base64Decoder.decode(decoded_data, body) catch return error.Base64DecodeError;
const gateserver_proto = protocol.GateServer.decode(decoded_data, allocator) catch return error.ProtobufDecodeError;
var hotfix = HotfixInfo.init();
try setUrl(allocator, &hotfix.asset_bundle_url, gateserver_proto.asset_bundle_url);
try setUrl(allocator, &hotfix.ex_resource_url, gateserver_proto.ex_resource_url);
try setUrl(allocator, &hotfix.lua_url, gateserver_proto.lua_url);
try setUrl(allocator, &hotfix.ifix_url, gateserver_proto.ifix_url);
if (hotfix.asset_bundle_url.len == 0 or hotfix.ex_resource_url.len == 0 or hotfix.lua_url.len == 0) {
return error.EmptyUrls;
}
return hotfix;
}
fn readAllTlsData(tls_conn: anytype, allocator: std.mem.Allocator) ![]u8 {
var response_buffer = std.ArrayList(u8).init(allocator);
errdefer response_buffer.deinit();
var buffer: [4096]u8 = undefined;
var total_attempts: u32 = 0;
const max_attempts = 1000;
var consecutive_empty_reads: u32 = 0;
const max_empty_reads = 10;
while (total_attempts < max_attempts) {
total_attempts += 1;
const bytes_read = tls_conn.read(buffer[0..]) catch |err| {
if (response_buffer.items.len > 0) break;
return err;
};
if (bytes_read == 0) {
consecutive_empty_reads += 1;
if (consecutive_empty_reads >= max_empty_reads) break;
std.time.sleep(1 * std.time.ns_per_ms);
continue;
}
consecutive_empty_reads = 0;
try response_buffer.appendSlice(buffer[0..bytes_read]);
if (std.mem.indexOf(u8, response_buffer.items, "\r\n\r\n")) |headers_end_idx| {
const headers = response_buffer.items[0..headers_end_idx];
if (std.mem.indexOf(u8, headers, "Content-Length:")) |cl_pos| {
const after = headers[cl_pos + "Content-Length:".len ..];
var it = std.mem.tokenizeScalar(u8, after, '\r');
if (it.next()) |len_str| {
const content_len = try std.fmt.parseInt(usize, std.mem.trim(u8, len_str, " \t"), 10);
const body_len = response_buffer.items.len - (headers_end_idx + 4);
if (body_len < content_len) {
continue;
}
}
}
}
}
if (response_buffer.items.len == 0) return error.NoDataReceived;
return response_buffer.toOwnedSlice();
}
fn isChunkedDataComplete(chunked_body: []const u8) bool {
var pos: usize = 0;
while (pos < chunked_body.len) {
const chunk_size_end = std.mem.indexOfScalarPos(u8, chunked_body, pos, '\n') orelse return false;
var chunk_size_str = chunked_body[pos..chunk_size_end];
if (chunk_size_str.len > 0 and chunk_size_str[chunk_size_str.len - 1] == '\r') {
chunk_size_str = chunk_size_str[0 .. chunk_size_str.len - 1];
}
const chunk_size = std.fmt.parseInt(usize, chunk_size_str, 16) catch return false;
if (chunk_size == 0) return true;
pos = chunk_size_end + 1;
if (pos + chunk_size > chunked_body.len) return false;
pos += chunk_size;
if (pos < chunked_body.len and chunked_body[pos] == '\r') pos += 1;
if (pos < chunked_body.len and chunked_body[pos] == '\n') pos += 1;
}
return false;
}
fn dechunkHttpBody(allocator: std.mem.Allocator, chunked_body: []const u8) ![]u8 {
var result = std.ArrayList(u8).init(allocator);
errdefer result.deinit();
var pos: usize = 0;
while (pos < chunked_body.len) {
const chunk_size_end = std.mem.indexOfScalarPos(u8, chunked_body, pos, '\n') orelse break;
var chunk_size_str = chunked_body[pos..chunk_size_end];
if (chunk_size_str.len > 0 and chunk_size_str[chunk_size_str.len - 1] == '\r') {
chunk_size_str = chunk_size_str[0 .. chunk_size_str.len - 1];
}
const chunk_size = std.fmt.parseInt(usize, chunk_size_str, 16) catch break;
if (chunk_size == 0) break;
pos = chunk_size_end + 1;
if (pos + chunk_size <= chunked_body.len) {
try result.appendSlice(chunked_body[pos .. pos + chunk_size]);
pos += chunk_size;
if (pos < chunked_body.len and chunked_body[pos] == '\r') pos += 1;
if (pos < chunked_body.len and chunked_body[pos] == '\n') pos += 1;
} else {
break;
}
}
return result.toOwnedSlice();
}
inline fn setUrl(allocator: std.mem.Allocator, field: *[]const u8, proto_url: anytype) !void {
switch (proto_url) {
.Const => |url| field.* = try allocator.dupe(u8, url),
.Owned => |owned| field.* = try allocator.dupe(u8, owned.str),
.Empty => {},
}
}

95
dispatch/src/hotfix.zig Normal file
View File

@@ -0,0 +1,95 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const hotfixInfo = struct {
clientVersion: []const u8,
assetBundleUrl: []const u8,
exResourceUrl: []const u8,
luaUrl: []const u8,
iFixUrl: []const u8,
};
pub fn Parser(allocator: Allocator, filename: []const u8, version: []const u8) !hotfixInfo {
const file = try std.fs.cwd().openFile(filename, .{});
defer file.close();
const file_size = try file.getEndPos();
const buffer = try file.readToEndAlloc(allocator, file_size);
defer allocator.free(buffer);
var json_tree = try std.json.parseFromSlice(std.json.Value, allocator, buffer, .{ .ignore_unknown_fields = true });
defer json_tree.deinit();
const version_node = json_tree.value.object.get(version) orelse {
return hotfixInfo{
.clientVersion = version,
.assetBundleUrl = "",
.exResourceUrl = "",
.luaUrl = "",
.iFixUrl = "",
};
};
return getValue(version_node, version);
}
fn getValue(node: std.json.Value, client_version: []const u8) !hotfixInfo {
if (node != .object) return error.InvalidJsonStructure;
const obj = node.object;
const assetBundleUrl = obj.get("asset_bundle_url") orelse return error.MissingAssetBundleUrl;
const exResourceUrl = obj.get("ex_resource_url") orelse return error.MissingExResourceUrl;
const luaUrl = obj.get("lua_url") orelse return error.MissingLuaUrl;
const iFixUrl = obj.get("ifix_url") orelse return error.MissingIFixUrl;
if (assetBundleUrl != .string or
exResourceUrl != .string or
luaUrl != .string or
iFixUrl != .string) return error.InvalidUrlFormat;
return hotfixInfo{
.clientVersion = client_version,
.assetBundleUrl = assetBundleUrl.string,
.exResourceUrl = exResourceUrl.string,
.luaUrl = luaUrl.string,
.iFixUrl = iFixUrl.string,
};
}
pub fn putValue(version: []const u8, assetBundleUrl: []const u8, exResourceUrl: []const u8, luaUrl: []const u8, iFixUrl: []const u8) !void {
const file = try std.fs.cwd().openFile("hotfix.json", .{ .mode = .read_write });
defer file.close();
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
const allocator = arena.allocator();
const file_size = try file.getEndPos();
const buffer0 = try file.readToEndAlloc(allocator, file_size);
defer allocator.free(buffer0);
var json_tree = try std.json.parseFromSlice(std.json.Value, allocator, buffer0, .{ .ignore_unknown_fields = true });
defer json_tree.deinit();
var root = json_tree.value.object;
var new_version = std.json.ObjectMap.init(allocator);
try new_version.put("asset_bundle_url", .{ .string = assetBundleUrl });
try new_version.put("ex_resource_url", .{ .string = exResourceUrl });
try new_version.put("ifix_url", .{ .string = iFixUrl });
try new_version.put("ifix_version", .{ .string = "0" });
try new_version.put("lua_url", .{ .string = luaUrl });
try new_version.put("lua_version", .{ .string = "" });
try root.put(version, .{ .object = new_version });
const json_value = std.json.Value{ .object = root };
var buffer = std.ArrayList(u8).init(allocator);
try std.json.stringify(json_value, .{ .whitespace = .indent_4 }, buffer.writer());
const new_file = try std.fs.cwd().createFile("hotfix.json", .{ .truncate = true });
defer new_file.close();
try new_file.writeAll(buffer.items);
}

35
dispatch/src/main.zig Normal file
View File

@@ -0,0 +1,35 @@
const std = @import("std");
const builtin = @import("builtin");
const httpz = @import("httpz");
const protocol = @import("protocol");
const authentication = @import("authentication.zig");
const dispatch = @import("dispatch.zig");
const PORT = 21000;
pub const std_options = std.Options{
.log_level = switch (builtin.mode) {
.Debug => .debug,
else => .info,
},
};
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
var server = try httpz.Server(void).init(allocator, .{ .port = PORT }, {});
defer server.stop();
defer server.deinit();
var router = try server.router(.{});
router.get("/query_dispatch", dispatch.onQueryDispatch, .{});
router.get("/query_gateway", dispatch.onQueryGateway, .{});
router.post("/account/risky/api/check", authentication.onRiskyApiCheck, .{});
router.post("/:product_name/mdk/shield/api/login", authentication.onShieldLogin, .{});
router.post("/:product_name/mdk/shield/api/verify", authentication.onVerifyLogin, .{});
router.post("/:product_name/combo/granter/login/v2/login", authentication.onComboTokenReq, .{});
std.log.info("Dispatch is listening at localhost:{?}", .{server.config.port});
try server.listen();
}

28
gameserver/build.zig Normal file
View File

@@ -0,0 +1,28 @@
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const protocol = b.dependency("protocol", .{});
const exe = b.addExecutable(.{
.name = "gameserver",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
exe.root_module.addImport("protocol", protocol.module("protocol"));
b.installArtifact(exe);
const run_cmd = b.addRunArtifact(exe);
run_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| {
run_cmd.addArgs(args);
}
const run_step = b.step("run", "Run the app");
run_step.dependOn(&run_cmd.step);
}

19
gameserver/build.zig.zon Normal file
View File

@@ -0,0 +1,19 @@
.{
.name = .gameserver,
.fingerprint = 0xe26640e562392392,
.version = "0.0.0",
.minimum_zig_version = "0.14.0",
.dependencies = .{
.protocol = .{
.path = "../protocol",
},
},
.paths = .{
"build.zig",
"build.zig.zon",
"src",
// For example...
//"LICENSE",
//"README.md",
},
}

77
gameserver/src/Packet.zig Normal file
View File

@@ -0,0 +1,77 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const Reader = std.net.Stream.Reader;
const Self = @This();
cmd_id: u16,
head: []u8,
body: []u8,
allocator: Allocator,
const head_magic: u32 = 0x9D74C714;
const tail_magic: u32 = 0xD7A152C8;
pub const DecodeError = error{
HeadMagicMismatch,
TailMagicMismatch,
PayloadTooBig,
};
pub fn read(reader: *Reader, allocator: Allocator) !Self {
if (try reader.readInt(u32, .big) != Self.head_magic) {
return Self.DecodeError.HeadMagicMismatch;
}
const cmd_id = try reader.readInt(u16, .big);
const head_len = try reader.readInt(u16, .big);
const body_len = try reader.readInt(u32, .big);
if (body_len > 0xFFFFFF) {
return Self.DecodeError.PayloadTooBig;
}
const head = try allocator.alloc(u8, head_len);
errdefer allocator.free(head);
const body = try allocator.alloc(u8, body_len);
errdefer allocator.free(body);
_ = try reader.readAll(head);
_ = try reader.readAll(body);
if (try reader.readInt(u32, .big) != Self.tail_magic) {
return Self.DecodeError.TailMagicMismatch;
}
return .{
.cmd_id = cmd_id,
.head = head,
.body = body,
.allocator = allocator,
};
}
pub fn getProto(self: *const Self, comptime T: type, allocator: Allocator) !T {
return try T.decode(self.body, allocator);
}
pub fn encode(cmd_id: u16, head: []u8, body: []u8, allocator: Allocator) ![]u8 {
var buf = try allocator.alloc(u8, 16 + head.len + body.len);
std.mem.writeInt(u32, buf[0..4], Self.head_magic, .big);
std.mem.writeInt(u16, buf[4..6], cmd_id, .big);
std.mem.writeInt(u16, buf[6..8], @intCast(head.len), .big);
std.mem.writeInt(u32, buf[8..12], @intCast(body.len), .big);
@memcpy(buf[12..(12 + head.len)], head);
@memcpy(buf[(12 + head.len)..(12 + head.len + body.len)], body);
std.mem.writeInt(u32, buf[(12 + head.len + body.len)..][0..4], Self.tail_magic, .big);
return buf;
}
pub fn deinit(self: *Self) void {
self.allocator.free(self.head);
self.allocator.free(self.body);
}

View File

@@ -0,0 +1,63 @@
const std = @import("std");
const protocol = @import("protocol");
const handlers = @import("handlers.zig");
const Packet = @import("Packet.zig");
const ConfigManager = @import("../src/manager/config_mgr.zig");
const Allocator = std.mem.Allocator;
const Stream = std.net.Stream;
const Address = std.net.Address;
const Self = @This();
const log = std.log.scoped(.session);
address: Address,
stream: Stream,
allocator: Allocator,
main_allocator: Allocator,
game_config_cache: *ConfigManager.GameConfigCache,
pub fn init(
address: Address,
stream: Stream,
session_allocator: Allocator,
main_allocator: Allocator,
game_config_cache: *ConfigManager.GameConfigCache,
) Self {
return .{
.address = address,
.stream = stream,
.allocator = session_allocator,
.main_allocator = main_allocator,
.game_config_cache = game_config_cache,
};
}
pub fn run(self: *Self) !void {
defer self.stream.close();
var reader = self.stream.reader();
while (true) {
var packet = Packet.read(&reader, self.allocator) catch break;
defer packet.deinit();
try handlers.handle(self, &packet);
}
}
pub fn send(self: *Self, cmd_id: protocol.CmdID, proto: anytype) !void {
const data = try proto.encode(self.allocator);
defer self.allocator.free(data);
const packet = try Packet.encode(@intFromEnum(cmd_id), &.{}, data, self.allocator);
defer self.allocator.free(packet);
_ = try self.stream.write(packet);
}
pub fn send_empty(self: *Self, cmd_id: protocol.CmdID) !void {
const packet = try Packet.encode(@intFromEnum(cmd_id), &.{}, &.{}, self.allocator);
defer self.allocator.free(packet);
_ = try self.stream.write(packet);
log.debug("sent EMPTY packet with id {}", .{cmd_id});
}

View File

@@ -0,0 +1,65 @@
const std = @import("std");
const protocol = @import("protocol");
const Session = @import("Session.zig");
const Packet = @import("Packet.zig");
const Allocator = std.mem.Allocator;
const CmdID = protocol.CmdID;
const value_command = @import("./commands/value.zig");
const help_command = @import("./commands/help.zig");
const tp_command = @import("./commands/tp.zig");
const unstuck_command = @import("./commands/unstuck.zig");
const sync_command = @import("./commands/sync.zig");
const refill_command = @import("./commands/refill.zig");
const CommandFn = *const fn (session: *Session, args: []const u8, allocator: Allocator) anyerror!void;
const Command = struct {
name: []const u8,
action: []const u8,
func: CommandFn,
};
const commandList = [_]Command{
.{ .name = "help", .action = "", .func = help_command.handle },
.{ .name = "test", .action = "", .func = value_command.handle },
.{ .name = "node", .action = "", .func = value_command.challengeNode },
.{ .name = "set", .action = "", .func = value_command.setGachaCommand },
.{ .name = "tp", .action = "", .func = tp_command.handle },
.{ .name = "unstuck", .action = "", .func = unstuck_command.handle },
.{ .name = "sync", .action = "", .func = sync_command.onGenerateAndSync },
.{ .name = "refill", .action = "", .func = refill_command.onRefill },
.{ .name = "id", .action = "", .func = value_command.onBuffId },
.{ .name = "funmode", .action = "", .func = value_command.FunMode },
};
pub fn handleCommand(session: *Session, msg: []const u8, allocator: Allocator) !void {
if (msg.len < 1 or msg[0] != '/') {
std.debug.print("Message Text 2: {any}\n", .{msg});
return sendMessage(session, "Commands must start with a '/'", allocator);
}
const input = msg[1..]; // Remove leading '/'
var tokenizer = std.mem.tokenizeAny(u8, input, " ");
const command = tokenizer.next() orelse return sendMessage(session, "Invalid command", allocator);
const args = tokenizer.rest();
for (commandList) |cmd| {
if (std.mem.eql(u8, cmd.name, command)) {
return try cmd.func(session, args, allocator);
}
}
return sendMessage(session, "Invalid command", allocator);
}
pub fn sendMessage(session: *Session, msg: []const u8, allocator: Allocator) !void {
var chat = protocol.RevcMsgScNotify.init(allocator);
chat.message_type = protocol.MsgType.MSG_TYPE_CUSTOM_TEXT;
chat.chat_type = protocol.ChatType.CHAT_TYPE_PRIVATE;
chat.source_uid = 2000;
chat.message_text = .{ .Const = msg };
chat.target_uid = 1;
try session.send(CmdID.CmdRevcMsgScNotify, chat);
}

View File

@@ -0,0 +1,16 @@
const commandhandler = @import("../command.zig");
const std = @import("std");
const Session = @import("../Session.zig");
const Allocator = std.mem.Allocator;
pub fn handle(session: *Session, _: []const u8, allocator: Allocator) !void {
try commandhandler.sendMessage(session, "/tp to teleport, /sync to sync data from config\n", allocator);
try commandhandler.sendMessage(session, "/refill to refill technique point after battle\n", allocator);
try commandhandler.sendMessage(session, "/set to set gacha banner\n", allocator);
try commandhandler.sendMessage(session, "/node to chage node in PF, AS, MoC\n", allocator);
try commandhandler.sendMessage(session, "/id to turn ON custom mode for challenge mode. /id info to check current challenge id. /id off to turn OFF\n", allocator);
try commandhandler.sendMessage(session, "/funmode to Sillyism\n", allocator);
try commandhandler.sendMessage(session, "You can enter MoC, PF, AS via F4 menu\n", allocator);
try commandhandler.sendMessage(session, "(If your Castorice technique enabled, you must enter battle by using Castorice's technique)\n", allocator);
}

View File

@@ -0,0 +1,19 @@
const commandhandler = @import("../command.zig");
const std = @import("std");
const Session = @import("../Session.zig");
const protocol = @import("protocol");
const Packet = @import("../Packet.zig");
const LineupManager = @import("../manager/lineup_mgr.zig");
const ArrayList = std.ArrayList;
const Allocator = std.mem.Allocator;
const CmdID = protocol.CmdID;
pub fn onRefill(session: *Session, _: []const u8, allocator: Allocator) !void {
try commandhandler.sendMessage(session, "Refill skill point\n", allocator);
var sync = protocol.SyncLineupNotify.init(allocator);
var lineup_mgr = LineupManager.LineupManager.init(allocator);
const lineup = try lineup_mgr.createLineup();
sync.lineup = lineup;
try session.send(CmdID.CmdSyncLineupNotify, sync);
}

View File

@@ -0,0 +1,65 @@
const commandhandler = @import("../command.zig");
const std = @import("std");
const Session = @import("../Session.zig");
const protocol = @import("protocol");
const Packet = @import("../Packet.zig");
const Data = @import("../data.zig");
const Uid = @import("../utils/uid.zig");
const AvatarManager = @import("../manager/avatar_mgr.zig");
const ConfigManager = @import("../manager/config_mgr.zig");
const ArrayList = std.ArrayList;
const Allocator = std.mem.Allocator;
const CmdID = protocol.CmdID;
pub fn syncItems(session: *Session, allocator: Allocator, equip_avatar: bool) !void {
Uid.resetGlobalUidGens();
var sync = protocol.PlayerSyncScNotify.init(allocator);
const config = &ConfigManager.global_game_config_cache.game_config;
for (config.avatar_config.items) |avatarConf| {
const dress_avatar_id: u32 = if (equip_avatar) avatarConf.id else 0;
const lc = try AvatarManager.createEquipment(avatarConf.lightcone, dress_avatar_id);
try sync.equipment_list.append(lc);
for (avatarConf.relics.items) |input| {
const r = try AvatarManager.createRelic(allocator, input, dress_avatar_id);
try sync.relic_list.append(r);
}
}
if (!equip_avatar) {
try ConfigManager.UpdateGameConfig();
Uid.updateInitialUid();
}
try session.send(CmdID.CmdPlayerSyncScNotify, sync);
}
pub fn onSyncAvatar(session: *Session, _: []const u8, allocator: Allocator) !void {
Uid.resetGlobalUidGens();
var sync = protocol.PlayerSyncScNotify.init(allocator);
const config = &ConfigManager.global_game_config_cache.game_config;
var char = protocol.AvatarSync.init(allocator);
for (Data.AllAvatars) |id| {
const avatar = try AvatarManager.createAllAvatar(allocator, id);
try char.avatar_list.append(avatar);
}
for (config.avatar_config.items) |avatarConf| {
const avatar = try AvatarManager.createAvatar(allocator, avatarConf);
try char.avatar_list.append(avatar);
}
sync.avatar_sync = char;
try session.send(CmdID.CmdPlayerSyncScNotify, sync);
}
pub fn onSyncMultiPath(session: *Session, _: []const u8, allocator: Allocator) !void {
var sync = protocol.PlayerSyncScNotify.init(allocator);
const config = &ConfigManager.global_game_config_cache.game_config;
const multis = try AvatarManager.createAllMultiPath(allocator, config);
try sync.multi_path_avatar_info_list.appendSlice(multis.items);
try session.send(CmdID.CmdPlayerSyncScNotify, sync);
}
pub fn onGenerateAndSync(session: *Session, placeholder: []const u8, allocator: Allocator) !void {
try commandhandler.sendMessage(session, "Sync items with config\n", allocator);
try syncItems(session, allocator, false);
try syncItems(session, allocator, true);
try onSyncAvatar(session, placeholder, allocator);
try onSyncMultiPath(session, placeholder, allocator);
}

View File

@@ -0,0 +1,58 @@
const commandhandler = @import("../command.zig");
const std = @import("std");
const Session = @import("../Session.zig");
const protocol = @import("protocol");
const LineupManager = @import("../manager/lineup_mgr.zig");
const SceneManager = @import("../manager/scene_mgr.zig");
const Allocator = std.mem.Allocator;
const CmdID = protocol.CmdID;
pub fn handle(session: *Session, args: []const u8, allocator: Allocator) !void {
var arg_iter = std.mem.splitSequence(u8, args, " ");
const entry_id_str = arg_iter.next() orelse {
try commandhandler.sendMessage(session, "Error: Missing arguments.\nUsage: /tp <entry_id> [plane_id] [floor_id]", allocator);
return;
};
const entry_id = std.fmt.parseInt(u32, entry_id_str, 10) catch {
try commandhandler.sendMessage(session, "Error: Invalid entry ID. Please provide a valid unsigned 32-bit integer.", allocator);
return;
};
var plane_id: ?u32 = null;
if (arg_iter.next()) |plane_id_str| {
plane_id = std.fmt.parseInt(u32, plane_id_str, 10) catch {
try commandhandler.sendMessage(session, "Error: Invalid plane ID. Please provide a valid unsigned 32-bit integer.", allocator);
return;
};
}
var floor_id: ?u32 = null;
if (arg_iter.next()) |floor_id_str| {
floor_id = std.fmt.parseInt(u32, floor_id_str, 10) catch {
try commandhandler.sendMessage(session, "Error: Invalid floor ID. Please provide a valid unsigned 32-bit integer.", allocator);
return;
};
}
var tp_msg = try std.fmt.allocPrint(allocator, "Teleporting to entry ID: {d}", .{entry_id});
if (plane_id) |pid| {
tp_msg = try std.fmt.allocPrint(allocator, "{s}, plane ID: {d}", .{ tp_msg, pid });
}
if (floor_id) |fid| {
tp_msg = try std.fmt.allocPrint(allocator, "{s}, floor ID: {d}", .{ tp_msg, fid });
}
try commandhandler.sendMessage(session, std.fmt.allocPrint(allocator, "Teleporting to entry ID: {d} {any} {any}\n", .{ entry_id, plane_id, floor_id }) catch "Error formatting message", allocator);
var planeID: u32 = 0;
var floorID: u32 = 0;
if (plane_id) |pid| planeID = pid;
if (floor_id) |fid| floorID = fid;
var scene_manager = SceneManager.SceneManager.init(allocator);
const scene_info = try scene_manager.createScene(planeID, floorID, entry_id, 0);
var lineup_mgr = LineupManager.LineupManager.init(allocator);
const lineup = try lineup_mgr.createLineup();
try session.send(CmdID.CmdEnterSceneByServerScNotify, protocol.EnterSceneByServerScNotify{
.reason = protocol.EnterSceneReason.ENTER_SCENE_REASON_NONE,
.lineup = lineup,
.scene = scene_info,
});
}

View File

@@ -0,0 +1,22 @@
const commandhandler = @import("../command.zig");
const std = @import("std");
const Session = @import("../Session.zig");
const protocol = @import("protocol");
const LineupManager = @import("../manager/lineup_mgr.zig");
const SceneManager = @import("../manager/scene_mgr.zig");
const Allocator = std.mem.Allocator;
const CmdID = protocol.CmdID;
pub fn handle(session: *Session, _: []const u8, allocator: Allocator) !void {
var lineup_mgr = LineupManager.LineupManager.init(allocator);
const lineup = try lineup_mgr.createLineup();
var scene_manager = SceneManager.SceneManager.init(allocator);
const scene_info = try scene_manager.createScene(20421, 20421001, 2042101, 2042106);
try session.send(CmdID.CmdEnterSceneByServerScNotify, protocol.EnterSceneByServerScNotify{
.reason = protocol.EnterSceneReason.ENTER_SCENE_REASON_DIMENSION_MERGE,
.lineup = lineup,
.scene = scene_info,
});
}

View File

@@ -0,0 +1,341 @@
const commandhandler = @import("../command.zig");
const std = @import("std");
const Session = @import("../Session.zig");
const Config = @import("../data/stage_config.zig");
const ConfigManager = @import("../manager/config_mgr.zig");
const LineupManager = @import("../manager/lineup_mgr.zig");
const AvatatarManager = @import("../manager/avatar_mgr.zig");
const Logic = @import("../utils/logic.zig");
const Allocator = std.mem.Allocator;
const ArrayList = std.ArrayList;
pub fn handle(session: *Session, _: []const u8, allocator: Allocator) !void {
try commandhandler.sendMessage(session, "Test Command for Chat\n", allocator);
}
pub fn challengeNode(session: *Session, _: []const u8, allocator: Allocator) !void {
try commandhandler.sendMessage(session, Logic.CustomMode().ChangeNode(), allocator);
}
pub fn FunMode(session: *Session, input: []const u8, allocator: Allocator) !void {
var args = std.mem.tokenizeAny(u8, input, " ");
if (args.next()) |subcmd| {
if (std.mem.eql(u8, subcmd, "on")) {
Logic.FunMode().SetFunMode(true);
const config = &ConfigManager.global_game_config_cache.game_config;
var ids = std.ArrayList(u32).init(allocator);
defer ids.deinit();
var picked_mc = false;
var picked_m7th = false;
for (config.avatar_config.items) |avatarConf| {
const id = switch (avatarConf.id) {
8001...8008 => if (!picked_mc) blk: {
picked_mc = true;
break :blk AvatatarManager.mc_id;
} else continue,
1224, 1001 => if (!picked_m7th) blk: {
picked_m7th = true;
break :blk AvatatarManager.m7th;
} else continue,
else => avatarConf.id,
};
try ids.append(id);
}
try LineupManager.getFunModeAvatarID(ids.items);
try commandhandler.sendMessage(session, "Fun mode ON\n", allocator);
} else if (std.mem.eql(u8, subcmd, "off")) {
Logic.FunMode().SetFunMode(false);
try commandhandler.sendMessage(session, "Fun mode OFF\n", allocator);
} else if (std.mem.eql(u8, subcmd, "hp")) {
if (args.next()) |hp_arg| {
if (std.mem.eql(u8, hp_arg, "max")) {
Logic.FunMode().SetHp(std.math.maxInt(i32));
try commandhandler.sendMessage(session, "Set HP = MAX (2,147,483,647)\n", allocator);
try commandhandler.sendMessage(session, "Remember to set it back to 0 to use actual HP value\n", allocator);
} else {
const parsed = try std.fmt.parseInt(i64, hp_arg, 10);
if (parsed < 0 or parsed > std.math.maxInt(i32)) {
try commandhandler.sendMessage(session, "Error: HP out of range (0 - 2147483647)\n", allocator);
} else {
Logic.FunMode().SetHp(@intCast(parsed));
try commandhandler.sendMessage(
session,
try std.fmt.allocPrint(allocator, "Set HP = {d}\n", .{parsed}),
allocator,
);
try commandhandler.sendMessage(session, "Remember to set it back to 0 to use actual HP value\n", allocator);
}
}
} else {
try commandhandler.sendMessage(session, "Usage: /funmode hp <max|number>\n", allocator);
}
} else {
try commandhandler.sendMessage(session, "Unknown funmode subcommand\n", allocator);
}
} else {
try commandhandler.sendMessage(session, "Usage: /funmode <on|off|hp>\n", allocator);
}
}
pub fn setGachaCommand(session: *Session, args: []const u8, allocator: Allocator) !void {
var arg_iter = std.mem.splitSequence(u8, args, " ");
const command = arg_iter.next() orelse {
try commandhandler.sendMessage(session, "Error: Missing sub-command. Usage: /set <sub-command> [arguments]", allocator);
return;
};
if (std.mem.eql(u8, command, "standard")) {
try standard(session, &arg_iter, allocator);
} else if (std.mem.eql(u8, command, "rateup")) {
const next = arg_iter.next();
if (next) |rateup_number| {
if (std.mem.eql(u8, rateup_number, "5")) {
try gacha5Stars(session, &arg_iter, allocator);
} else if (std.mem.eql(u8, rateup_number, "4")) {
try gacha4Stars(session, &arg_iter, allocator);
} else {
try commandhandler.sendMessage(session, "Error: Invalid rateup number. Please use 4 (four stars) or 5 (5 stars).", allocator);
}
} else {
try commandhandler.sendMessage(session, "Error: Missing number for rateup. Usage: /set rateup <number>", allocator);
}
} else {
try commandhandler.sendMessage(session, "Error: Unknown sub-command. Available: standard, rateup 5, rateup 4", allocator);
}
}
fn standard(session: *Session, arg_iter: *std.mem.SplitIterator(u8, .sequence), allocator: Allocator) !void {
var avatar_ids: [6]u32 = undefined;
var count: usize = 0;
while (count < 6) {
if (arg_iter.next()) |avatar_id_str| {
const id = std.fmt.parseInt(u32, avatar_id_str, 10) catch {
return sendErrorMessage(session, "Error: Invalid avatar ID. Please provide a valid unsigned 32-bit integer.", allocator);
};
if (!isValidAvatarId(id)) {
return sendErrorMessage(session, "Error: Invalid Avatar ID format.", allocator);
}
avatar_ids[count] = id;
count += 1;
} else {
break;
}
}
if (arg_iter.next() != null or count != 6) {
return sendErrorMessage(session, "Error: You must provide exactly 6 avatar IDs.", allocator);
}
@memcpy(Logic.Banner().SetStandardBanner(), &avatar_ids);
const msg = try std.fmt.allocPrint(allocator, "Set standard banner ID to: {d}, {d}, {d}, {d}, {d}, {d}", .{ avatar_ids[0], avatar_ids[1], avatar_ids[2], avatar_ids[3], avatar_ids[4], avatar_ids[5] });
try commandhandler.sendMessage(session, msg, allocator);
}
fn gacha4Stars(session: *Session, arg_iter: *std.mem.SplitIterator(u8, .sequence), allocator: Allocator) !void {
var avatar_ids: [3]u32 = undefined;
var count: usize = 0;
while (count < 3) {
if (arg_iter.next()) |avatar_id_str| {
const id = std.fmt.parseInt(u32, avatar_id_str, 10) catch {
return sendErrorMessage(session, "Error: Invalid avatar ID. Please provide a valid unsigned 32-bit integer.", allocator);
};
if (!isValidAvatarId(id)) {
return sendErrorMessage(session, "Error: Invalid Avatar ID format.", allocator);
}
avatar_ids[count] = id;
count += 1;
} else {
break;
}
}
if (arg_iter.next() != null or count != 3) {
return sendErrorMessage(session, "Error: You must provide exactly 3 avatar IDs.", allocator);
}
@memcpy(Logic.Banner().SetRateUpFourStar(), &avatar_ids);
const msg = try std.fmt.allocPrint(allocator, "Set 4 star rate up ID to: {d}, {d}, {d}", .{ avatar_ids[0], avatar_ids[1], avatar_ids[2] });
try commandhandler.sendMessage(session, msg, allocator);
}
fn gacha5Stars(session: *Session, arg_iter: *std.mem.SplitIterator(u8, .sequence), allocator: Allocator) !void {
var avatar_ids: [1]u32 = undefined;
if (arg_iter.next()) |avatar_id_str| {
const id = std.fmt.parseInt(u32, avatar_id_str, 10) catch {
return sendErrorMessage(session, "Error: Invalid avatar ID. Please provide a valid unsigned 32-bit integer.", allocator);
};
if (!isValidAvatarId(id)) {
return sendErrorMessage(session, "Error: Invalid Avatar ID format.", allocator);
}
avatar_ids[0] = id;
} else {
return sendErrorMessage(session, "Error: You must provide a rate-up avatar ID.", allocator);
}
if (arg_iter.next() != null) {
return sendErrorMessage(session, "Error: Only one rate-up avatar ID is allowed.", allocator);
}
@memcpy(Logic.Banner().SetRateUp(), &avatar_ids);
const msg = try std.fmt.allocPrint(allocator, "Set rate up ID to: {d}", .{avatar_ids[0]});
try commandhandler.sendMessage(session, msg, allocator);
}
fn sendErrorMessage(session: *Session, message: []const u8, allocator: Allocator) !void {
try commandhandler.sendMessage(session, message, allocator);
}
fn isValidAvatarId(avatar_id: u32) bool {
return avatar_id >= 1000 and avatar_id <= 9999;
}
pub fn onBuffId(session: *Session, input: []const u8, allocator: Allocator) !void {
if (std.ascii.eqlIgnoreCase(std.mem.trim(u8, input, " "), "info")) {
return try onBuffInfo(session, allocator);
}
var tokens = std.ArrayList([]const u8).init(allocator);
defer tokens.deinit();
var iter = std.mem.tokenizeScalar(u8, input, ' ');
while (iter.next()) |tok| {
try tokens.append(tok);
}
if (tokens.items.len == 0) {
return sendErrorMessage(session, "Error: Missing command arguments.", allocator);
}
if (std.ascii.eqlIgnoreCase(tokens.items[0], "off")) {
Logic.CustomMode().SetCustomMode(false);
_ = try commandhandler.sendMessage(session, "Custom mode OFF.", allocator);
return;
}
if (tokens.items.len < 6) {
return sendErrorMessage(session, "Error: Usage: /id <group_id> floor <n> node <1|2>", allocator);
}
const group_id = std.fmt.parseInt(u32, tokens.items[0], 10) catch return sendErrorMessage(session, "Error: Invalid group ID.", allocator);
if (!std.ascii.eqlIgnoreCase(tokens.items[1], "floor")) return sendErrorMessage(session, "Error: Expected 'floor' keyword.", allocator);
const floor = std.fmt.parseInt(u32, tokens.items[2], 10) catch return sendErrorMessage(session, "Error: Invalid floor number.", allocator);
if (!std.ascii.eqlIgnoreCase(tokens.items[3], "node")) return sendErrorMessage(session, "Error: Expected 'node' keyword.", allocator);
const node = std.fmt.parseInt(u8, tokens.items[4], 10) catch return sendErrorMessage(session, "Error: Invalid node number.", allocator);
if (node != 1 and node != 2) return sendErrorMessage(session, "Error: Node must be 1 or 2.", allocator);
Logic.CustomMode().SelectCustomNode(node);
const challenge_mode = switch (group_id / 1000) {
1 => "MoC",
2 => "PF",
3 => "AS",
else => "Unknown",
};
try commandhandler.sendMessage(session, try std.fmt.allocPrint(allocator, "Challenge mode: {s}", .{challenge_mode}), allocator);
const challenge_config = &ConfigManager.global_game_config_cache.challenge_maze_config;
const stage_config = &ConfigManager.global_game_config_cache.stage_config;
const challenge_entry = for (challenge_config.challenge_config.items) |entry| {
if (entry.group_id == group_id and entry.floor == floor)
break entry;
} else {
return sendErrorMessage(session, "Error: Could not find matching challenge ID.", allocator);
};
if (tokens.items.len > 5) {
const keyword = tokens.items[5];
if (std.ascii.eqlIgnoreCase(keyword, "buff")) {
if (tokens.items.len < 7) {
return sendErrorMessage(session, "Error: Missing buff sub-command (info/set <index>).", allocator);
}
const sub = tokens.items[6];
if (std.ascii.eqlIgnoreCase(sub, "info")) {
return sendBuffInfo(session, allocator, group_id, node);
} else if (std.ascii.eqlIgnoreCase(sub, "set")) {
return sendErrorMessage(session, "Error: Missing buff index for 'set' command.", allocator);
} else {
const buff_index = std.fmt.parseInt(usize, sub, 10) catch {
return sendErrorMessage(session, "Error: Invalid buff index.", allocator);
};
if (tokens.items.len < 8 or !std.ascii.eqlIgnoreCase(tokens.items[7], "set")) {
return sendErrorMessage(session, "Error: Expected 'set' after buff index.", allocator);
}
return handleBuffSetCommand(session, allocator, group_id, node, buff_index, challenge_entry.id);
}
} else if (std.ascii.eqlIgnoreCase(keyword, "set")) {
if ((group_id / 1000) != 1) {
return sendErrorMessage(session, "Error: Unexpected 'set' command. Did you mean 'buff <index> set' ?", allocator);
}
try handleMoCSelectChallenge(session, allocator, challenge_entry.id);
return;
}
}
var event_id: ?u32 = null;
if (node == 1 and challenge_entry.event_id_list1.items.len > 0) {
event_id = challenge_entry.event_id_list1.items[0];
} else if (node == 2 and challenge_entry.event_id_list2.items.len > 0) {
event_id = challenge_entry.event_id_list2.items[0];
}
if (event_id == null) {
return sendErrorMessage(session, "Error: Could not find matching EventID.", allocator);
}
if ((group_id / 1000) == 1) {
try handleMoCSelectChallenge(session, allocator, challenge_entry.id);
return;
}
for (stage_config.stage_config.items) |stage| {
if (stage.stage_id == event_id.?) {
try sendStageInfo(session, allocator, group_id, floor, node, stage);
return;
}
}
return sendErrorMessage(session, "Error: Stage not found for given EventID.", allocator);
}
fn handleMoCSelectChallenge(session: *Session, allocator: Allocator, challenge_id: u32) !void {
const line = try std.fmt.allocPrint(allocator, "Selected MoC Challenge ID: {d}", .{challenge_id});
try commandhandler.sendMessage(session, line, allocator);
Logic.CustomMode().SetCustomChallengeID(challenge_id);
Logic.CustomMode().SetCustomBuffID(0);
Logic.CustomMode().SetCustomMode(true);
}
fn handleBuffSetCommand(session: *Session, allocator: Allocator, group_id: u32, node: u8, buff_index: usize, challenge_id: u32) !void {
const buff_config = &ConfigManager.global_game_config_cache.buff_info_config;
for (buff_config.text_map_config.items) |entry| {
if (entry.group_id == group_id) {
const list = if (node == 1) &entry.buff_list1 else &entry.buff_list2;
if (buff_index == 0 or buff_index > list.items.len) {
return sendErrorMessage(session, "Error: Buff index out of range.", allocator);
}
const buff = list.items[buff_index - 1];
const line = try std.fmt.allocPrint(allocator, "Selected Challenge ID: {d}, Buff ID: {d} - {s}", .{ challenge_id, buff.id, buff.name });
try commandhandler.sendMessage(session, line, allocator);
Logic.CustomMode().SetCustomChallengeID(challenge_id);
Logic.CustomMode().SetCustomBuffID(buff.id);
Logic.CustomMode().SetCustomMode(true);
return;
}
}
return sendErrorMessage(session, "Error: Buff group ID not found.", allocator);
}
fn sendStageInfo(session: *Session, allocator: Allocator, group_id: u32, floor: u32, node: u8, stage: Config.Stage) !void {
const header = try std.fmt.allocPrint(allocator, "GroupID: {d}, Floor: {d}, Node: {d}, StageID: {d}", .{ group_id, floor, node, stage.stage_id });
try commandhandler.sendMessage(session, header, allocator);
for (stage.monster_list.items, 0..) |wave, i| {
var msg = try std.fmt.allocPrint(allocator, "wave {d}:", .{i + 1});
for (wave.items) |monster_id| {
msg = try std.fmt.allocPrint(allocator, "{s} {d},", .{ msg, monster_id });
}
try commandhandler.sendMessage(session, msg, allocator);
}
}
fn sendBuffInfo(session: *Session, allocator: Allocator, group_id: u32, node: u8) !void {
const buff_config = &ConfigManager.global_game_config_cache.buff_info_config;
for (buff_config.text_map_config.items) |entry| {
if (entry.group_id == group_id) {
const list = if (node == 1) &entry.buff_list1 else &entry.buff_list2;
for (list.items) |buff| {
const line = try std.fmt.allocPrint(allocator, "id: {d} - {s}", .{ buff.id, buff.name });
try commandhandler.sendMessage(session, line, allocator);
}
return;
}
}
return sendErrorMessage(session, "Error: Buff group ID not found.", allocator);
}
pub fn onBuffInfo(session: *Session, allocator: Allocator) !void {
const challenge_config = &ConfigManager.global_game_config_cache.challenge_maze_config;
var max_moc: u32 = 0;
var max_pf: u32 = 0;
var max_as: u32 = 0;
for (challenge_config.challenge_config.items) |entry| {
const id = entry.group_id;
if (id >= 1000 and id < 2000 and id > max_moc) {
max_moc = id;
} else if (id >= 2000 and id < 3000 and id > max_pf) {
max_pf = id;
} else if (id >= 3000 and id < 4000 and id > max_as) {
max_as = id;
}
}
const msg = try std.fmt.allocPrint(allocator, "Current Challenge IDs: MoC: {d}, PF: {d}, AS: {d}", .{ max_moc, max_pf, max_as });
try commandhandler.sendMessage(session, msg, allocator);
}

44
gameserver/src/data.zig Normal file
View File

@@ -0,0 +1,44 @@
const std = @import("std");
const ConfigManager = @import("./manager/config_mgr.zig");
// Avatar group
pub const MultiAvatar = [_]u32{
8001, 8002, 8003, 8004, 8005, 8006, 8007, 8008, 1001, 1224,
};
pub var EnhanceAvatarID = [_]u32{ 1005, 1006, 1205, 1212 };
pub var AllAvatars: []const u32 = &.{};
pub var AvatarSkinMap = [_]struct {
avatar_id: u32,
skin_id: u32,
}{
.{ .avatar_id = 1001, .skin_id = 1100101 },
.{ .avatar_id = 1310, .skin_id = 1131001 },
};
// Battle group
pub const IgnoreBattle = [_]u32{ 1414, 1405, 1404, 1225, 1314, 1312, 1305, 1302, 1217 };
pub const SkipBattle = [_]u32{ 1408, 1308 };
//TODO: update id for characters have ignore toughness in their technique in future
pub const IgnoreToughness = [_]u32{ 1006, 1308, 1317 };
// Profile group
pub const OwnedChatBubbles = [_]u32{ 220000, 220001, 220002, 220003, 220004, 220005, 220006, 220007, 220008 };
pub const OwnedPhoneThemes = [_]u32{ 221000, 221001, 221002, 221003, 221004, 221005, 221006, 221007, 221008, 221009, 221010, 221011, 221012 };
pub const OwnedPhoneCases = [_]u32{ 254000, 254001 };
pub const OwnedPersonalCardSkin = [_]u32{ 253000, 253001, 253002 };
pub const ItemList = [_]u32{ 251001, 251002, 251003, 101, 238, 239, 227002, 227001 };
pub const SkinList = [_]u32{ 1100101, 1131001 };
pub const LightconeList_3 = [_]u32{
20000, 20001, 20002, 20003, 20004, 20005, 20006, 20007, 20008, 20009, 20010, 20011, 20012, 20013, 20014, 20015, 20016, 20017, 20018, 20019, 20020, 20021, 20022,
};
pub const LightconeList_4 = [_]u32{
21000, 21001, 21002, 21003, 21004, 21005, 21006, 21007, 21008, 21009, 21010, 21011, 21012, 21013, 21014, 21015, 21016, 21017, 21018, 21019, 21020, 21021, 21022,
21023, 21024, 21025, 21026, 21027, 21028, 21029, 21030, 21031, 21032, 21033, 21034, 21035, 21036, 21037, 21038, 21039, 21040, 21041, 21042, 21043, 21044, 21045,
21046, 21047, 21048, 21050, 21051, 21052, 22000, 22001, 22002, 22003, 22004,
};
pub var AvatarList: []const u32 = &.{};

View File

@@ -0,0 +1,91 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const ArrayList = std.ArrayList;
pub const AvatarSkillTree = struct {
point_id: u32,
avatar_id: u32,
level: u32,
max_level: u32,
};
pub const AvatarSkillTreeConfig = struct {
avatar_skill_tree_config: ArrayList(AvatarSkillTree),
pub fn deinit(self: *AvatarSkillTreeConfig) void {
self.avatar_skill_tree_config.deinit();
}
};
pub const Element = enum(u8) {
Physical,
Fire,
Ice,
Thunder,
Wind,
Quantum,
Imaginary,
None,
};
pub const Avatar = struct {
avatar_id: u32,
adventure_player_id: u32,
damage_type: Element,
rarity: u32,
};
pub const AvatarConfig = struct {
avatar_config: std.ArrayList(Avatar),
pub fn deinit(self: *AvatarConfig) void {
self.avatar_config.deinit();
}
};
pub fn parseAvatarConfig(root: std.json.Value, allocator: std.mem.Allocator) !AvatarConfig {
var avatar_config = std.ArrayList(Avatar).init(allocator);
const avatars = root.object.get("avatar_config").?.array.items;
for (avatars) |avatar_json| {
const avatar = Avatar{
.avatar_id = @intCast(avatar_json.object.get("AvatarID").?.integer),
.adventure_player_id = @intCast(avatar_json.object.get("AdventurePlayerID").?.integer),
.damage_type = parseElement(avatar_json.object.get("DamageType").?.string),
.rarity = parseRarity(avatar_json.object.get("Rarity").?.string),
};
try avatar_config.append(avatar);
}
return AvatarConfig{ .avatar_config = avatar_config };
}
pub fn parseAvatarSkillTreeConfig(root: std.json.Value, allocator: Allocator) anyerror!AvatarSkillTreeConfig {
var avatar_skill_tree = ArrayList(AvatarSkillTree).init(allocator);
for (root.object.get("avatar_skill_tree").?.array.items) |skill_json| {
const skill = AvatarSkillTree{
.point_id = @intCast(skill_json.object.get("PointID").?.integer),
.avatar_id = @intCast(skill_json.object.get("AvatarID").?.integer),
.level = @intCast(skill_json.object.get("Level").?.integer),
.max_level = @intCast(skill_json.object.get("MaxLevel").?.integer),
};
try avatar_skill_tree.append(skill);
}
return AvatarSkillTreeConfig{
.avatar_skill_tree_config = avatar_skill_tree,
};
}
pub fn parseElement(str: []const u8) Element {
if (std.mem.eql(u8, str, "Physical")) return .Physical;
if (std.mem.eql(u8, str, "Fire")) return .Fire;
if (std.mem.eql(u8, str, "Ice")) return .Ice;
if (std.mem.eql(u8, str, "Thunder")) return .Thunder;
if (std.mem.eql(u8, str, "Wind")) return .Wind;
if (std.mem.eql(u8, str, "Quantum")) return .Quantum;
if (std.mem.eql(u8, str, "Imaginary")) return .Imaginary;
return .None;
}
fn parseRarity(str: []const u8) u32 {
return if (std.mem.eql(u8, str, "CombatPowerAvatarRarityType4")) 4 else 5;
}

View File

@@ -0,0 +1,183 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const ArrayList = std.ArrayList;
pub const ChallengeConfig = struct {
id: u32,
group_id: u32,
floor: ?u32,
npc_monster_id_list1: ArrayList(u32),
npc_monster_id_list2: ArrayList(u32),
event_id_list1: ArrayList(u32),
event_id_list2: ArrayList(u32),
map_entrance_id: u32,
map_entrance_id2: u32,
maze_group_id1: u32,
maze_group_id2: ?u32,
maze_buff_id: u32,
};
pub const ChallengePeak = struct {
id: u32,
maze_group_id: u32,
map_entrance_id: u32,
npc_monster_id_list: ArrayList(u32),
event_id_list: ArrayList(u32),
tag_list: ArrayList(u32),
};
pub const ChallengePeakGroup = struct {
id: u32,
boss_level_id: u32,
};
pub const ChallengePeakBoss = struct {
id: u32,
hard_tag_list: ArrayList(u32),
buff_list: ArrayList(u32),
};
pub const ChallengeMazeConfig = struct {
challenge_config: ArrayList(ChallengeConfig),
pub fn deinit(self: *ChallengeMazeConfig) void {
for (self.challenge_config.items) |*challenge| {
challenge.npc_monster_id_list1.deinit();
challenge.npc_monster_id_list2.deinit();
challenge.event_id_list1.deinit();
challenge.event_id_list2.deinit();
}
self.challenge_config.deinit();
}
};
pub const ChallengePeakConfig = struct {
challenge_peak: ArrayList(ChallengePeak),
pub fn deinit(self: *ChallengePeakConfig) void {
for (self.challenge_peak.items) |*challenge| {
challenge.npc_monster_id_list.deinit();
challenge.event_id_list.deinit();
}
self.challenge_peak.deinit();
}
};
pub const ChallengePeakGroupConfig = struct {
challenge_peak_group: ArrayList(ChallengePeakGroup),
pub fn deinit(self: *ChallengePeakGroupConfig) void {
self.challenge_peak_group.deinit();
}
};
pub const ChallengePeakBossConfig = struct {
challenge_peak_boss_config: ArrayList(ChallengePeakBoss),
pub fn deinit(self: *ChallengePeakBossConfig) void {
for (self.challenge_peak_boss_config.items) |*challenge| {
challenge.buff_list.deinit();
challenge.hard_tag_list.deinit();
}
self.challenge_peak_boss_config.deinit();
}
};
pub fn parseChallengeConfig(root: std.json.Value, allocator: Allocator) anyerror!ChallengeMazeConfig {
var challenge_config = ArrayList(ChallengeConfig).init(allocator);
for (root.object.get("challenge_config").?.array.items) |challenge_json| {
var challenge = ChallengeConfig{
.id = @intCast(challenge_json.object.get("ID").?.integer),
.group_id = @intCast(challenge_json.object.get("GroupID").?.integer),
.floor = if (challenge_json.object.get("Floor")) |val| @intCast(val.integer) else null,
.maze_buff_id = @intCast(challenge_json.object.get("MazeBuffID").?.integer),
.npc_monster_id_list1 = ArrayList(u32).init(allocator),
.npc_monster_id_list2 = ArrayList(u32).init(allocator),
.event_id_list1 = ArrayList(u32).init(allocator),
.event_id_list2 = ArrayList(u32).init(allocator),
.map_entrance_id = @intCast(challenge_json.object.get("MapEntranceID").?.integer),
.map_entrance_id2 = @intCast(challenge_json.object.get("MapEntranceID2").?.integer),
.maze_group_id1 = @intCast(challenge_json.object.get("MazeGroupID1").?.integer),
.maze_group_id2 = if (challenge_json.object.get("MazeGroupID2")) |val| @intCast(val.integer) else null,
};
for (challenge_json.object.get("NpcMonsterIDList1").?.array.items) |npc1| {
try challenge.npc_monster_id_list1.append(@intCast(npc1.integer));
}
for (challenge_json.object.get("NpcMonsterIDList2").?.array.items) |npc2| {
try challenge.npc_monster_id_list2.append(@intCast(npc2.integer));
}
for (challenge_json.object.get("EventIDList1").?.array.items) |event1| {
try challenge.event_id_list1.append(@intCast(event1.integer));
}
for (challenge_json.object.get("EventIDList2").?.array.items) |event2| {
try challenge.event_id_list2.append(@intCast(event2.integer));
}
try challenge_config.append(challenge);
}
return ChallengeMazeConfig{
.challenge_config = challenge_config,
};
}
pub fn parseChallengePeakConfig(root: std.json.Value, allocator: Allocator) anyerror!ChallengePeakConfig {
var challenge_config = ArrayList(ChallengePeak).init(allocator);
for (root.object.get("challenge_peak_config").?.array.items) |challenge_json| {
var challenge = ChallengePeak{
.id = @intCast(challenge_json.object.get("ID").?.integer),
.npc_monster_id_list = ArrayList(u32).init(allocator),
.event_id_list = ArrayList(u32).init(allocator),
.tag_list = ArrayList(u32).init(allocator),
.map_entrance_id = @intCast(challenge_json.object.get("MapEntranceID").?.integer),
.maze_group_id = @intCast(challenge_json.object.get("MazeGroupID").?.integer),
};
for (challenge_json.object.get("TagList").?.array.items) |tag| {
try challenge.tag_list.append(@intCast(tag.integer));
}
for (challenge_json.object.get("NpcMonsterIDList").?.array.items) |npc| {
try challenge.npc_monster_id_list.append(@intCast(npc.integer));
}
for (challenge_json.object.get("EventIDList").?.array.items) |event| {
try challenge.event_id_list.append(@intCast(event.integer));
}
try challenge_config.append(challenge);
}
return ChallengePeakConfig{
.challenge_peak = challenge_config,
};
}
pub fn parseChallengePeakGroupConfig(root: std.json.Value, allocator: Allocator) anyerror!ChallengePeakGroupConfig {
var group_config = ArrayList(ChallengePeakGroup).init(allocator);
for (root.object.get("challenge_peak_group_config").?.array.items) |group_json| {
const group = ChallengePeakGroup{
.id = @intCast(group_json.object.get("ID").?.integer),
.boss_level_id = @intCast(group_json.object.get("BossLevelID").?.integer),
};
try group_config.append(group);
}
return ChallengePeakGroupConfig{
.challenge_peak_group = group_config,
};
}
pub fn parseChallengePeakBossConfig(root: std.json.Value, allocator: Allocator) anyerror!ChallengePeakBossConfig {
var boss_config = ArrayList(ChallengePeakBoss).init(allocator);
for (root.object.get("challenge_peak_boss_config").?.array.items) |boss_json| {
var boss = ChallengePeakBoss{
.id = @intCast(boss_json.object.get("ID").?.integer),
.hard_tag_list = ArrayList(u32).init(allocator),
.buff_list = ArrayList(u32).init(allocator),
};
for (boss_json.object.get("HardTagList").?.array.items) |tag| {
try boss.hard_tag_list.append(@intCast(tag.integer));
}
for (boss_json.object.get("BuffList").?.array.items) |buff| {
try boss.buff_list.append(@intCast(buff.integer));
}
try boss_config.append(boss);
}
return ChallengePeakBossConfig{
.challenge_peak_boss_config = boss_config,
};
}

View File

@@ -0,0 +1,192 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const ArrayList = std.ArrayList;
pub const BattleConfig = struct {
battle_id: u32,
stage_id: u32,
cycle_count: u32,
monster_wave: ArrayList(ArrayList(u32)),
monster_level: u32,
blessings: ArrayList(u32),
};
pub const Lightcone = struct {
id: u32,
rank: u32,
level: u32,
promotion: u32,
};
pub const Relic = struct {
id: u32,
level: u32,
main_affix_id: u32,
sub_count: u32,
stat1: u32,
cnt1: u32,
step1: u32,
stat2: u32,
cnt2: u32,
step2: u32,
stat3: u32,
cnt3: u32,
step3: u32,
stat4: u32,
cnt4: u32,
step4: u32,
};
pub const Avatar = struct {
id: u32,
hp: u32,
sp: u32,
level: u32,
promotion: u32,
rank: u32,
lightcone: Lightcone,
relics: ArrayList(Relic),
use_technique: bool,
};
const StatCount = struct {
stat: u32,
count: u32,
step: u32,
};
pub const GameConfig = struct {
battle_config: BattleConfig,
avatar_config: ArrayList(Avatar),
pub fn deinit(self: *GameConfig) void {
for (self.battle_config.monster_wave.items) |*wave| {
wave.deinit();
}
self.battle_config.monster_wave.deinit();
self.battle_config.blessings.deinit();
for (self.avatar_config.items) |*avatar| {
avatar.relics.deinit();
}
self.avatar_config.deinit();
}
};
pub fn parseConfig(root: std.json.Value, allocator: Allocator) anyerror!GameConfig {
const battle_config_json = root.object.get("battle_config").?;
var battle_config = BattleConfig{
.battle_id = @intCast(battle_config_json.object.get("battle_id").?.integer),
.stage_id = @intCast(battle_config_json.object.get("stage_id").?.integer),
.cycle_count = @intCast(battle_config_json.object.get("cycle_count").?.integer),
.monster_wave = ArrayList(ArrayList(u32)).init(allocator),
.monster_level = @intCast(battle_config_json.object.get("monster_level").?.integer),
.blessings = ArrayList(u32).init(allocator),
};
for (battle_config_json.object.get("monster_wave").?.array.items) |wave| {
var wave_list = ArrayList(u32).init(allocator);
for (wave.array.items) |monster| {
try wave_list.append(@intCast(monster.integer));
}
try battle_config.monster_wave.append(wave_list);
}
for (battle_config_json.object.get("blessings").?.array.items) |blessing| {
try battle_config.blessings.append(@intCast(blessing.integer));
}
var avatar_config = ArrayList(Avatar).init(allocator);
for (root.object.get("avatar_config").?.array.items) |avatar_json| {
var avatar = Avatar{
.id = @intCast(avatar_json.object.get("id").?.integer),
.hp = @intCast(avatar_json.object.get("hp").?.integer),
.sp = @intCast(avatar_json.object.get("sp").?.integer),
.level = @intCast(avatar_json.object.get("level").?.integer),
.promotion = @intCast(avatar_json.object.get("promotion").?.integer),
.rank = @intCast(avatar_json.object.get("rank").?.integer),
.lightcone = undefined,
.relics = ArrayList(Relic).init(allocator),
.use_technique = avatar_json.object.get("use_technique").?.bool,
};
const lightcone_json = avatar_json.object.get("lightcone").?;
avatar.lightcone = Lightcone{
.id = @intCast(lightcone_json.object.get("id").?.integer),
.rank = @intCast(lightcone_json.object.get("rank").?.integer),
.level = @intCast(lightcone_json.object.get("level").?.integer),
.promotion = @intCast(lightcone_json.object.get("promotion").?.integer),
};
for (avatar_json.object.get("relics").?.array.items) |relic_str| {
const relic = try parseRelic(relic_str.string, allocator);
try avatar.relics.append(relic);
}
try avatar_config.append(avatar);
}
return GameConfig{
.battle_config = battle_config,
.avatar_config = avatar_config,
};
}
fn parseRelic(relic_str: []const u8, allocator: Allocator) !Relic {
var tokens = ArrayList([]const u8).init(allocator);
defer tokens.deinit();
var iterator = std.mem.tokenizeScalar(u8, relic_str, ',');
while (iterator.next()) |token| {
try tokens.append(token);
}
const tokens_slice = tokens.items;
if (tokens_slice.len < 5) {
std.debug.print("relic parsing critical error (too few fields): {s}\n", .{relic_str});
return error.InsufficientTokens;
}
const stat1 = try parseStatCount(tokens_slice[4]);
const stat2 = if (tokens_slice.len > 5) try parseStatCount(tokens_slice[5]) else StatCount{ .stat = 0, .count = 0, .step = 0 };
const stat3 = if (tokens_slice.len > 6) try parseStatCount(tokens_slice[6]) else StatCount{ .stat = 0, .count = 0, .step = 0 };
const stat4 = if (tokens_slice.len > 7) try parseStatCount(tokens_slice[7]) else StatCount{ .stat = 0, .count = 0, .step = 0 };
const relic = Relic{
.id = try std.fmt.parseInt(u32, tokens_slice[0], 10),
.level = try std.fmt.parseInt(u32, tokens_slice[1], 10),
.main_affix_id = try std.fmt.parseInt(u32, tokens_slice[2], 10),
.sub_count = try std.fmt.parseInt(u32, tokens_slice[3], 10),
.stat1 = stat1.stat,
.cnt1 = stat1.count,
.step1 = stat1.step,
.stat2 = stat2.stat,
.cnt2 = stat2.count,
.step2 = stat2.step,
.stat3 = stat3.stat,
.cnt3 = stat3.count,
.step3 = stat3.step,
.stat4 = stat4.stat,
.cnt4 = stat4.count,
.step4 = stat4.step,
};
return relic;
}
fn parseStatCount(token: []const u8) !StatCount {
if (std.mem.indexOfScalar(u8, token, ':')) |first_colon| {
if (std.mem.indexOfScalar(u8, token[first_colon + 1 ..], ':')) |second_colon_offset| {
const second_colon = first_colon + 1 + second_colon_offset;
const stat = try std.fmt.parseInt(u32, token[0..first_colon], 10);
const count = try std.fmt.parseInt(u32, token[first_colon + 1 .. second_colon], 10);
const step = try std.fmt.parseInt(u32, token[second_colon + 1 ..], 10);
return StatCount{ .stat = stat, .count = count, .step = step };
} else {
return error.InvalidFormat;
}
} else {
return error.InvalidFormat;
}
}

View File

@@ -0,0 +1,320 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const ArrayList = std.ArrayList;
pub const PlayerIcon = struct {
id: u32,
};
pub const MainMission = struct {
main_mission_id: u32,
};
pub const Quest = struct {
quest_id: u32,
};
pub const TutorialGuide = struct {
guide_group_id: u32,
};
pub const Tutorial = struct {
tutorial_id: u32,
};
pub const Activity = struct {
activity_module_list: ArrayList(u32),
activity_id: u32,
panel_id: ?u32,
};
pub const MapEntrance = struct {
floor_id: u32,
id: u32,
plane_id: u32,
begin_main_mission_idlist: ArrayList(u32),
finish_main_mission_idlist: ArrayList(u32),
finish_sub_mission_idlist: ArrayList(u32),
};
pub const MazePlane = struct {
floor_id_list: ArrayList(u32),
start_floor_id: u32,
challenge_plane_id: u32,
world_id: u32,
};
pub const BuffList = struct {
id: u32,
name: []u8,
};
pub const TextMap = struct {
group_id: u32,
buff_list1: std.ArrayList(BuffList),
buff_list2: std.ArrayList(BuffList),
};
pub const PlayerIconConfig = struct {
player_icon_config: ArrayList(PlayerIcon),
pub fn deinit(self: *PlayerIconConfig) void {
self.player_icon_config.deinit();
}
};
pub const MainMissionConfig = struct {
main_mission_config: ArrayList(MainMission),
pub fn deinit(self: *MainMissionConfig) void {
self.main_mission_config.deinit();
}
};
pub const QuestConfig = struct {
quest_config: ArrayList(Quest),
pub fn deinit(self: *QuestConfig) void {
self.quest_config.deinit();
}
};
pub const TutorialGuideConfig = struct {
tutorial_guide_config: ArrayList(TutorialGuide),
pub fn deinit(self: *TutorialGuideConfig) void {
self.tutorial_guide_config.deinit();
}
};
pub const TutorialConfig = struct {
tutorial_config: ArrayList(Tutorial),
pub fn deinit(self: *TutorialConfig) void {
self.tutorial_config.deinit();
}
};
pub const ActivityConfig = struct {
activity_config: ArrayList(Activity),
pub fn deinit(self: *ActivityConfig) void {
for (self.activity_config.items) |*activity| {
activity.activity_module_list.deinit();
}
self.activity_config.deinit();
}
};
pub const MapEntranceConfig = struct {
map_entrance_config: ArrayList(MapEntrance),
pub fn deinit(self: *MapEntranceConfig) void {
for (self.map_entrance_config.items) |*entrance| {
entrance.begin_main_mission_idlist.deinit();
entrance.finish_main_mission_idlist.deinit();
entrance.finish_sub_mission_idlist.deinit();
}
self.map_entrance_config.deinit();
}
};
pub const MazePlaneConfig = struct {
maze_plane_config: ArrayList(MazePlane),
pub fn deinit(self: *MazePlaneConfig) void {
for (self.maze_plane_config.items) |*maze| {
maze.floor_id_list.deinit();
}
self.maze_plane_config.deinit();
}
};
pub const TextMapConfig = struct {
text_map_config: ArrayList(TextMap),
pub fn deinit(self: *TextMapConfig, allocator: Allocator) void {
for (self.text_map_config.items) |*text| {
for (text.buff_list1.items) |b| {
allocator.free(b.name);
}
for (text.buff_list2.items) |b| {
allocator.free(b.name);
}
text.buff_list1.deinit();
text.buff_list2.deinit();
}
self.text_map_config.deinit();
}
};
pub fn parsePlayerIconConfig(root: std.json.Value, allocator: Allocator) anyerror!PlayerIconConfig {
var player_icon_config = ArrayList(PlayerIcon).init(allocator);
for (root.object.get("player_icon_config").?.array.items) |icon_json| {
const icon = PlayerIcon{
.id = @intCast(icon_json.object.get("ID").?.integer),
};
try player_icon_config.append(icon);
}
return PlayerIconConfig{
.player_icon_config = player_icon_config,
};
}
pub fn parseMainMissionConfig(root: std.json.Value, allocator: Allocator) anyerror!MainMissionConfig {
var main_mission_config = ArrayList(MainMission).init(allocator);
for (root.object.get("main_mission_config").?.array.items) |main_json| {
const main_mission = MainMission{
.main_mission_id = @intCast(main_json.object.get("MainMissionID").?.integer),
};
try main_mission_config.append(main_mission);
}
return MainMissionConfig{
.main_mission_config = main_mission_config,
};
}
pub fn parseQuestConfig(root: std.json.Value, allocator: Allocator) anyerror!QuestConfig {
var quest_config = ArrayList(Quest).init(allocator);
for (root.object.get("quest_config").?.array.items) |quest_json| {
const quest = Quest{
.quest_id = @intCast(quest_json.object.get("QuestID").?.integer),
};
try quest_config.append(quest);
}
return QuestConfig{
.quest_config = quest_config,
};
}
pub fn parseTutorialGuideConfig(root: std.json.Value, allocator: Allocator) anyerror!TutorialGuideConfig {
var tutorial_guide_config = ArrayList(TutorialGuide).init(allocator);
for (root.object.get("tutorial_guide_config").?.array.items) |guide_json| {
const tutorial_guide = TutorialGuide{
.guide_group_id = @intCast(guide_json.object.get("GroupID").?.integer),
};
try tutorial_guide_config.append(tutorial_guide);
}
return TutorialGuideConfig{
.tutorial_guide_config = tutorial_guide_config,
};
}
pub fn parseTutorialConfig(root: std.json.Value, allocator: Allocator) anyerror!TutorialConfig {
var tutorial_config = ArrayList(Tutorial).init(allocator);
for (root.object.get("tutorial_config").?.array.items) |tutorial_json| {
const tutorial = Tutorial{
.tutorial_id = @intCast(tutorial_json.object.get("TutorialID").?.integer),
};
try tutorial_config.append(tutorial);
}
return TutorialConfig{
.tutorial_config = tutorial_config,
};
}
pub fn parseActivityConfig(root: std.json.Value, allocator: Allocator) anyerror!ActivityConfig {
var activity_config = ArrayList(Activity).init(allocator);
for (root.object.get("activity_config").?.array.items) |activity_json| {
var activity = Activity{
//.panel_id = @intCast(activity_json.object.get("ActivityPanelID").?.integer),
.panel_id = if (activity_json.object.get("ActivityPanelID")) |val| @intCast(val.integer) else null,
.activity_module_list = ArrayList(u32).init(allocator),
.activity_id = @intCast(activity_json.object.get("ActivityID").?.integer),
};
for (activity_json.object.get("ActivityModuleIDList").?.array.items) |id| {
try activity.activity_module_list.append(@intCast(id.integer));
}
try activity_config.append(activity);
}
return ActivityConfig{
.activity_config = activity_config,
};
}
pub fn parseMapEntranceConfig(root: std.json.Value, allocator: Allocator) anyerror!MapEntranceConfig {
var map_entrance_config = ArrayList(MapEntrance).init(allocator);
for (root.object.get("map_entrance_config").?.array.items) |mapEntrance| {
var entrance = MapEntrance{
.id = @intCast(mapEntrance.object.get("ID").?.integer),
.floor_id = @intCast(mapEntrance.object.get("FloorID").?.integer),
.plane_id = @intCast(mapEntrance.object.get("PlaneID").?.integer),
.begin_main_mission_idlist = ArrayList(u32).init(allocator),
.finish_main_mission_idlist = ArrayList(u32).init(allocator),
.finish_sub_mission_idlist = ArrayList(u32).init(allocator),
};
for (mapEntrance.object.get("BeginMainMissionList").?.array.items) |id| {
try entrance.begin_main_mission_idlist.append(@intCast(id.integer));
}
for (mapEntrance.object.get("FinishMainMissionList").?.array.items) |id| {
try entrance.finish_main_mission_idlist.append(@intCast(id.integer));
}
for (mapEntrance.object.get("FinishSubMissionList").?.array.items) |id| {
try entrance.finish_sub_mission_idlist.append(@intCast(id.integer));
}
try map_entrance_config.append(entrance);
}
return MapEntranceConfig{
.map_entrance_config = map_entrance_config,
};
}
pub fn parseMazePlaneConfig(root: std.json.Value, allocator: Allocator) anyerror!MazePlaneConfig {
var maze_plane_config = ArrayList(MazePlane).init(allocator);
for (root.object.get("maze_plane_config").?.array.items) |id| {
var maze = MazePlane{
.start_floor_id = @intCast(id.object.get("StartFloorID").?.integer),
.challenge_plane_id = @intCast(id.object.get("PlaneID").?.integer),
.world_id = @intCast(id.object.get("WorldID").?.integer),
.floor_id_list = ArrayList(u32).init(allocator),
};
for (id.object.get("FloorIDList").?.array.items) |list| {
try maze.floor_id_list.append(@intCast(list.integer));
}
try maze_plane_config.append(maze);
}
return MazePlaneConfig{
.maze_plane_config = maze_plane_config,
};
}
pub fn parseTextMapConfig(root: std.json.Value, allocator: Allocator) !TextMapConfig {
var text_map_config = ArrayList(TextMap).init(allocator);
const arr = root.object.get("text_map_config");
if (arr == null or arr.? == .null) {
return TextMapConfig{ .text_map_config = text_map_config };
}
for (arr.?.array.items) |entry| {
var buff_list1 = ArrayList(BuffList).init(allocator);
var buff_list2 = ArrayList(BuffList).init(allocator);
if (entry.object.get("BuffList1")) |buffs1| {
if (buffs1 != .null) {
for (buffs1.array.items) |buff| {
const parsed_name = buff.object.get("name").?.string;
const name_dup = try allocator.dupe(u8, parsed_name);
try buff_list1.append(BuffList{
.id = @intCast(buff.object.get("id").?.integer),
.name = name_dup,
});
}
}
}
if (entry.object.get("BuffList2")) |buffs2| {
if (buffs2 != .null) {
for (buffs2.array.items) |buff| {
const parsed_name = buff.object.get("name").?.string;
const name_dup = try allocator.dupe(u8, parsed_name);
try buff_list2.append(BuffList{
.id = @intCast(buff.object.get("id").?.integer),
.name = name_dup,
});
}
}
}
try text_map_config.append(TextMap{
.group_id = @intCast(entry.object.get("GroupID").?.integer),
.buff_list1 = buff_list1,
.buff_list2 = buff_list2,
});
}
return TextMapConfig{
.text_map_config = text_map_config,
};
}

View File

@@ -0,0 +1,255 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const ArrayList = std.ArrayList;
pub const Vector = struct {
x: i32,
y: i32,
z: i32,
};
pub const Teleports = struct {
anchorId: u32,
groupId: u32,
instId: u32,
pos: Vector,
rot: Vector,
teleportId: u32,
};
pub const Monsters = struct {
groupId: u32,
instId: u32,
eventId: u32,
pos: Vector,
rot: Vector,
monsterId: u32,
};
pub const Props = struct {
groupId: u32,
instId: u32,
propState: u32,
pos: Vector,
rot: Vector,
propId: u32,
};
pub const Anchor = struct {
id: u32,
pos: Vector,
rot: Vector,
};
pub const Values = struct {
name: ?[]const u8,
max_value: ?i32,
};
pub const SavedValues = struct {
floor_id: u32,
saved_values: ArrayList(Values),
pub fn deinit(self: *SavedValues) void {
self.saved_values.deinit();
}
};
pub const FloorSavedValuesConfig = struct {
floor_saved_values: ArrayList(SavedValues),
pub fn deinit(self: *FloorSavedValuesConfig) void {
for (self.floor_saved_values.items) |*res| {
res.deinit();
}
self.floor_saved_values.deinit();
}
};
pub const ResConfig = struct {
planeID: u32,
entryID: u32,
props: ArrayList(Props),
monsters: ArrayList(Monsters),
teleports: ArrayList(Teleports),
pub fn deinit(self: *ResConfig) void {
self.props.deinit();
self.monsters.deinit();
self.teleports.deinit();
}
};
pub const AnchorConfig = struct {
entryID: u32,
anchor: ArrayList(Anchor),
pub fn deinit(self: *AnchorConfig) void {
self.anchor.deinit();
}
};
pub const SceneConfig = struct {
scene_config: ArrayList(ResConfig),
pub fn deinit(self: *SceneConfig) void {
for (self.scene_config.items) |*res| {
res.deinit();
}
self.scene_config.deinit();
}
};
pub const SceneAnchorConfig = struct {
anchor_config: ArrayList(AnchorConfig),
pub fn deinit(self: *SceneAnchorConfig) void {
for (self.anchor_config.items) |*res| {
res.deinit();
}
self.anchor_config.deinit();
}
};
pub fn parseAnchor(root: std.json.Value, allocator: Allocator) anyerror!SceneConfig {
var res_config = ArrayList(ResConfig).init(allocator);
for (root.object.get("scene_config").?.array.items) |res_json| {
var res = ResConfig{
.planeID = @intCast(res_json.object.get("planeID").?.integer),
.entryID = @intCast(res_json.object.get("entryID").?.integer),
.props = ArrayList(Props).init(allocator),
.monsters = ArrayList(Monsters).init(allocator),
.teleports = ArrayList(Teleports).init(allocator),
};
for (res_json.object.get("props").?.array.items) |scene_prop| {
var prop = Props{
.groupId = @intCast(scene_prop.object.get("groupId").?.integer),
.instId = @intCast(scene_prop.object.get("instId").?.integer),
.propState = @intCast(scene_prop.object.get("propState").?.integer),
.pos = undefined,
.rot = undefined,
.propId = @intCast(scene_prop.object.get("propId").?.integer),
};
const pos_json = scene_prop.object.get("pos").?;
prop.pos = Vector{
.x = @intCast(pos_json.object.get("x").?.integer),
.y = @intCast(pos_json.object.get("y").?.integer),
.z = @intCast(pos_json.object.get("z").?.integer),
};
const rot_json = scene_prop.object.get("rot").?;
prop.rot = Vector{
.x = @intCast(rot_json.object.get("x").?.integer),
.y = @intCast(rot_json.object.get("y").?.integer),
.z = @intCast(rot_json.object.get("z").?.integer),
};
try res.props.append(prop);
}
for (res_json.object.get("monsters").?.array.items) |monster_json| {
var monster = Monsters{
.groupId = @intCast(monster_json.object.get("groupId").?.integer),
.instId = @intCast(monster_json.object.get("instId").?.integer),
.eventId = @intCast(monster_json.object.get("eventId").?.integer),
.monsterId = @intCast(monster_json.object.get("monsterId").?.integer),
.pos = undefined,
.rot = undefined,
};
const pos_json = monster_json.object.get("pos").?;
monster.pos = Vector{
.x = @intCast(pos_json.object.get("x").?.integer),
.y = @intCast(pos_json.object.get("y").?.integer),
.z = @intCast(pos_json.object.get("z").?.integer),
};
const rot_json = monster_json.object.get("rot").?;
monster.rot = Vector{
.x = @intCast(rot_json.object.get("x").?.integer),
.y = @intCast(rot_json.object.get("y").?.integer),
.z = @intCast(rot_json.object.get("z").?.integer),
};
try res.monsters.append(monster);
}
for (res_json.object.get("teleports").?.array.items) |teleport_json| {
var teleport = Teleports{
.anchorId = @intCast(teleport_json.object.get("anchorId").?.integer),
.groupId = @intCast(teleport_json.object.get("groupId").?.integer),
.instId = @intCast(teleport_json.object.get("instId").?.integer),
.teleportId = @intCast(teleport_json.object.get("teleportId").?.integer),
.pos = undefined,
.rot = undefined,
};
const pos_json = teleport_json.object.get("pos").?;
teleport.pos = Vector{
.x = @intCast(pos_json.object.get("x").?.integer),
.y = @intCast(pos_json.object.get("y").?.integer),
.z = @intCast(pos_json.object.get("z").?.integer),
};
const rot_json = teleport_json.object.get("rot").?;
teleport.rot = Vector{
.x = @intCast(rot_json.object.get("x").?.integer),
.y = @intCast(rot_json.object.get("y").?.integer),
.z = @intCast(rot_json.object.get("z").?.integer),
};
try res.teleports.append(teleport);
}
try res_config.append(res);
}
return SceneConfig{
.scene_config = res_config,
};
}
pub fn parseAnchorConfig(root: std.json.Value, allocator: Allocator) anyerror!SceneAnchorConfig {
var anchor_config = ArrayList(AnchorConfig).init(allocator);
for (root.object.get("anchor_config").?.array.items) |anchor_json| {
var anchor = AnchorConfig{
.entryID = @intCast(anchor_json.object.get("entryID").?.integer),
.anchor = ArrayList(Anchor).init(allocator),
};
for (anchor_json.object.get("anchor").?.array.items) |scene_anchor| {
var anchor_list = Anchor{
.id = @intCast(scene_anchor.object.get("ID").?.integer),
.pos = undefined,
.rot = undefined,
};
const pos_json = scene_anchor.object.get("pos").?;
anchor_list.pos = Vector{
.x = @intCast(pos_json.object.get("x").?.integer),
.y = @intCast(pos_json.object.get("y").?.integer),
.z = @intCast(pos_json.object.get("z").?.integer),
};
const rot_json = scene_anchor.object.get("rot").?;
anchor_list.rot = Vector{
.x = @intCast(rot_json.object.get("x").?.integer),
.y = @intCast(rot_json.object.get("y").?.integer),
.z = @intCast(rot_json.object.get("z").?.integer),
};
try anchor.anchor.append(anchor_list);
}
try anchor_config.append(anchor);
}
return SceneAnchorConfig{
.anchor_config = anchor_config,
};
}
pub fn parseFloorSavedValuesConfig(root: std.json.Value, allocator: Allocator) anyerror!FloorSavedValuesConfig {
var saved_values_config = ArrayList(SavedValues).init(allocator);
for (root.object.get("saved_values").?.array.items) |json| {
var saved_values = SavedValues{
.floor_id = @intCast(json.object.get("FloorID").?.integer),
.saved_values = ArrayList(Values).init(allocator),
};
for (json.object.get("SavedValues").?.array.items) |saved| {
const value = Values{
.max_value = if (saved.object.get("MaxValue")) |val| @intCast(val.integer) else null,
.name = if (saved.object.get("Name")) |val| try allocator.dupe(u8, val.string) else null,
};
try saved_values.saved_values.append(value);
}
try saved_values_config.append(saved_values);
}
return FloorSavedValuesConfig{
.floor_saved_values = saved_values_config,
};
}

View File

@@ -0,0 +1,48 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const ArrayList = std.ArrayList;
pub const Stage = struct {
level: u32,
stage_id: u32,
monster_list: ArrayList(ArrayList(u32)),
};
pub const StageConfig = struct {
stage_config: ArrayList(Stage),
pub fn deinit(self: *StageConfig) void {
for (self.stage_config.items) |*stage| {
for (stage.monster_list.items) |*wave| {
wave.deinit();
}
stage.monster_list.deinit();
}
self.stage_config.deinit();
}
};
pub fn parseStageConfig(root: std.json.Value, allocator: Allocator) anyerror!StageConfig {
var stage_config = ArrayList(Stage).init(allocator);
for (root.object.get("stage_config").?.array.items) |stage_json| {
var stage = Stage{
.level = @intCast(stage_json.object.get("Level").?.integer),
.stage_id = @intCast(stage_json.object.get("StageID").?.integer),
.monster_list = ArrayList(ArrayList(u32)).init(allocator),
};
for (stage_json.object.get("MonsterList").?.array.items) |wave| {
var wave_list = ArrayList(u32).init(allocator);
for (wave.array.items) |monster| {
try wave_list.append(@intCast(monster.integer));
}
try stage.monster_list.append(wave_list);
}
try stage_config.append(stage);
}
return StageConfig{
.stage_config = stage_config,
};
}

220
gameserver/src/handlers.zig Normal file
View File

@@ -0,0 +1,220 @@
const std = @import("std");
const protocol = @import("protocol");
const Session = @import("Session.zig");
const Packet = @import("Packet.zig");
const avatar = @import("services/avatar.zig");
const chat = @import("services/chat.zig");
const gacha = @import("services/gacha.zig");
const item = @import("services/item.zig");
const battle = @import("services/battle.zig");
const login = @import("services/login.zig");
const lineup = @import("services/lineup.zig");
const mail = @import("services/mail.zig");
const misc = @import("services/misc.zig");
const mission = @import("services/mission.zig");
const pet = @import("services/pet.zig");
const profile = @import("services/profile.zig");
const scene = @import("services/scene.zig");
const events = @import("services/events.zig");
const challenge = @import("services/challenge.zig");
const Allocator = std.mem.Allocator;
const ArenaAllocator = std.heap.ArenaAllocator;
const CmdID = protocol.CmdID;
const log = std.log.scoped(.handlers);
const Action = *const fn (*Session, *const Packet, Allocator) anyerror!void;
pub const HandlerList = [_]struct { CmdID, Action }{
.{ CmdID.CmdPlayerGetTokenCsReq, login.onPlayerGetToken },
.{ CmdID.CmdPlayerLoginCsReq, login.onPlayerLogin },
.{ CmdID.CmdPlayerHeartBeatCsReq, misc.onPlayerHeartBeat },
.{ CmdID.CmdPlayerLoginFinishCsReq, login.onPlayerLoginFinish },
.{ CmdID.CmdContentPackageGetDataCsReq, login.onContentPackageGetData },
.{ CmdID.CmdSetClientPausedCsReq, login.onSetClientPaused },
.{ CmdID.CmdGetArchiveDataCsReq, login.onGetArchiveData },
.{ CmdID.CmdGetUpdatedArchiveDataCsReq, login.onGetUpdatedArchiveData },
//avatar
.{ CmdID.CmdGetAvatarDataCsReq, avatar.onGetAvatarData },
.{ CmdID.CmdSetAvatarPathCsReq, avatar.onSetAvatarPath },
.{ CmdID.CmdGetBasicInfoCsReq, avatar.onGetBasicInfo },
.{ CmdID.CmdTakeOffAvatarSkinCsReq, avatar.onTakeOffAvatarSkin },
.{ CmdID.CmdDressAvatarSkinCsReq, avatar.onDressAvatarSkin },
.{ CmdID.CmdGetBigDataAllRecommendCsReq, avatar.onGetBigDataAll },
.{ CmdID.CmdGetBigDataRecommendCsReq, avatar.onGetBigData },
.{ CmdID.CmdGetPreAvatarGrowthInfoCsReq, avatar.onGetPreAvatarGrowthInfo },
.{ CmdID.CmdSetPlayerOutfitCsReq, avatar.onSetPlayerOutfit },
.{ CmdID.CmdSetAvatarEnhancedIdCsReq, avatar.onSetAvatarEnhancedId },
//bag
.{ CmdID.CmdGetBagCsReq, item.onGetBag },
.{ CmdID.CmdUseItemCsReq, item.onUseItem },
//lineup
.{ CmdID.CmdChangeLineupLeaderCsReq, lineup.onChangeLineupLeader },
.{ CmdID.CmdReplaceLineupCsReq, lineup.onReplaceLineup },
.{ CmdID.CmdGetCurLineupDataCsReq, lineup.onGetCurLineupData },
//battle
.{ CmdID.CmdStartCocoonStageCsReq, battle.onStartCocoonStage },
.{ CmdID.CmdPVEBattleResultCsReq, battle.onPVEBattleResult },
.{ CmdID.CmdSceneCastSkillCsReq, battle.onSceneCastSkill },
.{ CmdID.CmdSceneCastSkillCostMpCsReq, battle.onSceneCastSkillCostMp },
.{ CmdID.CmdQuickStartCocoonStageCsReq, battle.onQuickStartCocoonStage },
.{ CmdID.CmdQuickStartFarmElementCsReq, battle.onQuickStartFarmElement },
.{ CmdID.CmdStartBattleCollegeCsReq, battle.onStartBattleCollege },
.{ CmdID.CmdGetCurBattleInfoCsReq, battle.onGetCurBattleInfo },
.{ CmdID.CmdSyncClientResVersionCsReq, battle.onSyncClientResVersion },
//gacha
.{ CmdID.CmdGetGachaInfoCsReq, gacha.onGetGachaInfo },
.{ CmdID.CmdBuyGoodsCsReq, gacha.onBuyGoods },
.{ CmdID.CmdGetShopListCsReq, gacha.onGetShopList },
.{ CmdID.CmdExchangeHcoinCsReq, gacha.onExchangeHcoin },
.{ CmdID.CmdDoGachaCsReq, gacha.onDoGacha },
//mail
.{ CmdID.CmdGetMailCsReq, mail.onGetMail },
.{ CmdID.CmdTakeMailAttachmentCsReq, mail.onTakeMailAttachment },
//pet
.{ CmdID.CmdGetPetDataCsReq, pet.onGetPetData },
.{ CmdID.CmdRecallPetCsReq, pet.onRecallPet },
.{ CmdID.CmdSummonPetCsReq, pet.onSummonPet },
//profile
.{ CmdID.CmdGetPhoneDataCsReq, profile.onGetPhoneData },
.{ CmdID.CmdSelectPhoneThemeCsReq, profile.onSelectPhoneTheme },
.{ CmdID.CmdSelectChatBubbleCsReq, profile.onSelectChatBubble },
.{ CmdID.CmdGetPlayerBoardDataCsReq, profile.onGetPlayerBoardData },
.{ CmdID.CmdSetDisplayAvatarCsReq, profile.onSetDisplayAvatar },
.{ CmdID.CmdSetAssistAvatarCsReq, profile.onSetAssistAvatar },
.{ CmdID.CmdSetSignatureCsReq, profile.onSetSignature },
.{ CmdID.CmdSetGameplayBirthdayCsReq, profile.onSetGameplayBirthday },
.{ CmdID.CmdSetHeadIconCsReq, profile.onSetHeadIcon },
.{ CmdID.CmdSelectPhoneCaseCsReq, profile.onSelectPhoneCase },
.{ CmdID.CmdUpdatePlayerSettingCsReq, profile.onUpdatePlayerSetting },
.{ CmdID.CmdGetPlayerDetailInfoCsReq, profile.onGetPlayerDetailInfo },
.{ CmdID.CmdSetPersonalCardCsReq, profile.onSetPersonalCard },
//mission
.{ CmdID.CmdGetTutorialGuideCsReq, mission.onGetTutorialGuideStatus },
.{ CmdID.CmdGetMissionStatusCsReq, mission.onGetMissionStatus },
.{ CmdID.CmdGetTutorialCsReq, mission.onGetTutorialStatus },
.{ CmdID.CmdUnlockTutorialGuideCsReq, mission.onUnlockTutorialGuide },
.{ CmdID.CmdUnlockTutorialCsReq, mission.onUnlockTutorial },
.{ CmdID.CmdFinishTalkMissionCsReq, mission.onFinishTalkMission },
.{ CmdID.CmdGetQuestDataCsReq, mission.onGetQuestData },
//chat
.{ CmdID.CmdGetFriendListInfoCsReq, chat.onGetFriendListInfo },
.{ CmdID.CmdGetPrivateChatHistoryCsReq, chat.onPrivateChatHistory },
.{ CmdID.CmdGetChatEmojiListCsReq, chat.onChatEmojiList },
.{ CmdID.CmdSendMsgCsReq, chat.onSendMsg },
//scene
.{ CmdID.CmdGetCurSceneInfoCsReq, scene.onGetCurSceneInfo },
.{ CmdID.CmdSceneEntityMoveCsReq, scene.onSceneEntityMove },
.{ CmdID.CmdEnterSceneCsReq, scene.onEnterScene },
.{ CmdID.CmdGetSceneMapInfoCsReq, scene.onGetSceneMapInfo },
.{ CmdID.CmdGetUnlockTeleportCsReq, scene.onGetUnlockTeleport },
.{ CmdID.CmdEnterSectionCsReq, scene.onEnterSection },
.{ CmdID.CmdSceneEntityTeleportCsReq, scene.onSceneEntityTeleport },
.{ CmdID.CmdGetFirstTalkNpcCsReq, scene.onGetFirstTalkNpc },
.{ CmdID.CmdGetFirstTalkByPerformanceNpcCsReq, scene.onGetFirstTalkByPerformanceNp },
.{ CmdID.CmdGetNpcTakenRewardCsReq, scene.onGetNpcTakenReward },
.{ CmdID.CmdUpdateGroupPropertyCsReq, scene.onUpdateGroupProperty },
.{ CmdID.CmdChangePropTimelineInfoCsReq, scene.onChangePropTimeline },
.{ CmdID.CmdDeactivateFarmElementCsReq, scene.onDeactivateFarmElement },
.{ CmdID.CmdGetEnteredSceneCsReq, scene.onGetEnteredScene },
.{ CmdID.CmdInteractPropCsReq, scene.onInteractProp },
.{ CmdID.CmdChangeEraFlipperDataCsReq, scene.onChangeEraFlipperData },
//events
.{ CmdID.CmdGetActivityScheduleConfigCsReq, events.onGetActivity },
.{ CmdID.CmdUpdateServerPrefsDataCsReq, events.onUpdateServerPrefsData },
//challenge
.{ CmdID.CmdGetChallengeCsReq, challenge.onGetChallenge },
.{ CmdID.CmdGetChallengeGroupStatisticsCsReq, challenge.onGetChallengeGroupStatistics },
.{ CmdID.CmdStartChallengeCsReq, challenge.onStartChallenge },
.{ CmdID.CmdLeaveChallengeCsReq, challenge.onLeaveChallenge },
.{ CmdID.CmdLeaveChallengePeakCsReq, challenge.onLeaveChallengePeak },
.{ CmdID.CmdGetCurChallengeCsReq, challenge.onGetCurChallengeScRsp },
.{ CmdID.CmdGetChallengePeakDataCsReq, challenge.onGetChallengePeakData },
.{ CmdID.CmdGetCurChallengePeakCsReq, challenge.onGetCurChallengePeak },
.{ CmdID.CmdTakeChallengeRewardCsReq, challenge.onTakeChallengeReward },
.{ CmdID.CmdStartChallengePeakCsReq, challenge.onStartChallengePeak },
.{ CmdID.CmdReStartChallengePeakCsReq, challenge.onReStartChallengePeak },
.{ CmdID.CmdSetChallengePeakMobLineupAvatarCsReq, challenge.onSetChallengePeakMobLineupAvatar },
.{ CmdID.CmdSetChallengePeakBossHardModeCsReq, challenge.onSetChallengePeakBossHardMode },
.{ CmdID.CmdGetFriendBattleRecordDetailCsReq, challenge.onGetFriendBattleRecordDetail },
};
const DummyCmdList = [_]struct { CmdID, CmdID }{
.{ CmdID.CmdGetMarkItemListCsReq, CmdID.CmdGetMarkItemListScRsp },
.{ CmdID.CmdGetPlayerBoardDataCsReq, CmdID.CmdGetPlayerBoardDataScRsp },
.{ CmdID.CmdGetCurAssistCsReq, CmdID.CmdGetCurAssistScRsp },
.{ CmdID.CmdGetAllLineupDataCsReq, CmdID.CmdGetAllLineupDataScRsp },
.{ CmdID.CmdGetAllServerPrefsDataCsReq, CmdID.CmdGetAllServerPrefsDataScRsp },
.{ CmdID.CmdGetMissionDataCsReq, CmdID.CmdGetMissionDataScRsp },
.{ CmdID.CmdDailyFirstMeetPamCsReq, CmdID.CmdDailyFirstMeetPamScRsp },
.{ CmdID.CmdGetBattleCollegeDataCsReq, CmdID.CmdGetBattleCollegeDataScRsp },
.{ CmdID.CmdGetNpcStatusCsReq, CmdID.CmdGetNpcStatusScRsp },
.{ CmdID.CmdGetSecretKeyInfoCsReq, CmdID.CmdGetSecretKeyInfoScRsp },
.{ CmdID.CmdGetHeartDialInfoCsReq, CmdID.CmdGetHeartDialInfoScRsp },
.{ CmdID.CmdGetVideoVersionKeyCsReq, CmdID.CmdGetVideoVersionKeyScRsp },
.{ CmdID.CmdGetMapRotationDataCsReq, CmdID.CmdGetMapRotationDataScRsp },
.{ CmdID.CmdGetLevelRewardTakenListCsReq, CmdID.CmdGetLevelRewardTakenListScRsp },
.{ CmdID.CmdGetMaterialSubmitActivityDataCsReq, CmdID.CmdGetMaterialSubmitActivityDataScRsp },
.{ CmdID.CmdMusicRhythmDataCsReq, CmdID.CmdMusicRhythmDataScRsp },
//friendlist
.{ CmdID.CmdGetFriendApplyListInfoCsReq, CmdID.CmdGetFriendApplyListInfoScRsp },
.{ CmdID.CmdGetChatFriendHistoryCsReq, CmdID.CmdGetChatFriendHistoryScRsp },
.{ CmdID.CmdGetFriendLoginInfoCsReq, CmdID.CmdGetFriendLoginInfoScRsp },
.{ CmdID.CmdGetFriendDevelopmentInfoCsReq, CmdID.CmdGetFriendDevelopmentInfoScRsp },
.{ CmdID.CmdGetFriendRecommendListInfoCsReq, CmdID.CmdGetFriendRecommendListInfoScRsp },
//add
.{ CmdID.CmdGetMissionMessageInfoCsReq, CmdID.CmdGetMissionMessageInfoScRsp },
.{ CmdID.CmdTrainPartyGetDataCsReq, CmdID.CmdTrainPartyGetDataScRsp },
.{ CmdID.CmdQueryProductInfoCsReq, CmdID.CmdQueryProductInfoScRsp },
.{ CmdID.CmdGetPamSkinDataCsReq, CmdID.CmdGetPamSkinDataScRsp },
.{ CmdID.CmdGetQuestRecordCsReq, CmdID.CmdGetQuestRecordScRsp },
.{ CmdID.CmdGetDailyActiveInfoCsReq, CmdID.CmdGetDailyActiveInfoScRsp },
.{ CmdID.CmdGetFightActivityDataCsReq, CmdID.CmdGetFightActivityDataScRsp },
.{ CmdID.CmdGetTreasureDungeonActivityDataCsReq, CmdID.CmdGetTreasureDungeonActivityDataScRsp },
.{ CmdID.CmdGetOfferingInfoCsReq, CmdID.CmdGetOfferingInfoScRsp },
.{ CmdID.CmdClockParkGetInfoCsReq, CmdID.CmdClockParkGetInfoScRsp },
.{ CmdID.CmdGetExpeditionDataCsReq, CmdID.CmdGetExpeditionDataScRsp },
.{ CmdID.CmdRaidCollectionDataCsReq, CmdID.CmdRaidCollectionDataScRsp },
.{ CmdID.CmdGetRaidInfoCsReq, CmdID.CmdGetRaidInfoScRsp },
.{ CmdID.CmdGetLoginActivityCsReq, CmdID.CmdGetLoginActivityScRsp },
.{ CmdID.CmdGetTrialActivityDataCsReq, CmdID.CmdGetTrialActivityDataScRsp },
.{ CmdID.CmdGetJukeboxDataCsReq, CmdID.CmdGetJukeboxDataScRsp },
.{ CmdID.CmdGetLoginChatInfoCsReq, CmdID.CmdGetLoginChatInfoScRsp },
.{ CmdID.CmdGetMarkChestCsReq, CmdID.CmdGetMarkChestScRsp },
.{ CmdID.CmdUpdateTrackMainMissionIdCsReq, CmdID.CmdUpdateTrackMainMissionIdScRsp },
.{ CmdID.CmdGetNpcMessageGroupCsReq, CmdID.CmdGetNpcMessageGroupScRsp },
.{ CmdID.CmdGetAllSaveRaidCsReq, CmdID.CmdGetAllSaveRaidScRsp },
.{ CmdID.CmdGetAssistHistoryCsReq, CmdID.CmdGetAssistHistoryScRsp },
.{ CmdID.CmdRelicSmartWearGetPlanCsReq, CmdID.CmdRelicSmartWearGetPlanScRsp },
.{ CmdID.CmdRelicSmartWearGetPinRelicCsReq, CmdID.CmdRelicSmartWearGetPinRelicScRsp },
.{ CmdID.CmdSetGrowthTargetAvatarCsReq, CmdID.CmdSetGrowthTargetAvatarScRsp },
.{ CmdID.CmdGetPreAvatarActivityListCsReq, CmdID.CmdGetPreAvatarActivityListScRsp },
};
const SuppressLogList = [_]CmdID{CmdID.CmdSceneEntityMoveCsReq};
pub fn handle(session: *Session, packet: *const Packet) !void {
var arena = ArenaAllocator.init(session.allocator);
defer arena.deinit();
const cmd_id: CmdID = @enumFromInt(packet.cmd_id);
inline for (HandlerList) |handler| {
if (handler[0] == cmd_id) {
try handler[1](session, packet, arena.allocator());
if (!std.mem.containsAtLeast(CmdID, &SuppressLogList, 1, &[_]CmdID{cmd_id})) {
log.debug("packet {} was handled", .{cmd_id});
}
return;
}
}
inline for (DummyCmdList) |pair| {
if (pair[0] == cmd_id) {
try session.send_empty(pair[1]);
return;
}
}
log.warn("packet {} was ignored", .{cmd_id});
}

21
gameserver/src/main.zig Normal file
View File

@@ -0,0 +1,21 @@
const std = @import("std");
const builtin = @import("builtin");
const network = @import("network.zig");
const ConfigManager = @import("../src/manager/config_mgr.zig");
pub const std_options: std.Options = .{
.log_level = switch (builtin.mode) {
.Debug => .debug,
else => .info,
},
};
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
try ConfigManager.initGameGlobals(allocator);
defer ConfigManager.deinitGameGlobals();
try network.listen();
std.log.info("Server listening for connections.", .{});
}

View File

@@ -0,0 +1,253 @@
const std = @import("std");
const protocol = @import("protocol");
const Config = @import("../data/game_config.zig");
const Session = @import("../Session.zig");
const Data = @import("../data.zig");
const Logic = @import("../utils/logic.zig");
const ConfigManager = @import("../manager/config_mgr.zig");
const Uid = @import("../utils/uid.zig");
const ArrayList = std.ArrayList;
const Allocator = std.mem.Allocator;
const CmdID = protocol.CmdID;
const config = &ConfigManager.global_game_config_cache.game_config;
const skill_config = &ConfigManager.global_game_config_cache.avatar_skill_config;
pub var m7th: u32 = 1224;
pub var mc_id: u32 = 8008;
pub fn createAvatar(
allocator: Allocator,
avatarConf: Config.Avatar,
) !protocol.Avatar {
var avatar = protocol.Avatar.init(allocator);
avatar.base_avatar_id = switch (avatarConf.id) {
8001...8008 => 8001,
1224 => 1001,
else => avatarConf.id,
};
avatar.level = avatarConf.level;
avatar.promotion = avatarConf.promotion;
avatar.rank = avatarConf.rank;
avatar.dressed_skin_id = getSkinId(avatar.base_avatar_id);
if (Logic.inlist(avatar.base_avatar_id, &Data.EnhanceAvatarID)) {
avatar.unk_enhanced_id = 1;
}
avatar.has_taken_promotion_reward_list = ArrayList(u32).init(allocator);
for (1..6) |i| {
try avatar.has_taken_promotion_reward_list.append(@intCast(i));
}
avatar.equipment_unique_id = Uid.nextGlobalId();
avatar.equip_relic_list = ArrayList(protocol.EquipRelic).init(allocator);
for (0..6) |i| {
try avatar.equip_relic_list.append(.{
.relic_unique_id = Uid.nextGlobalId(),
.type = @intCast(i),
});
}
try createSkillTree(avatar.base_avatar_id, &avatar.skilltree_list);
return avatar;
}
pub fn createAllAvatar(
allocator: Allocator,
Avatar_id: u32,
) !protocol.Avatar {
var avatar = protocol.Avatar.init(allocator);
avatar.base_avatar_id = Avatar_id;
avatar.level = 80;
avatar.promotion = 6;
avatar.rank = 6;
avatar.dressed_skin_id = getSkinId(avatar.base_avatar_id);
if (Logic.inlist(avatar.base_avatar_id, &Data.EnhanceAvatarID)) {
avatar.unk_enhanced_id = 1;
}
avatar.has_taken_promotion_reward_list = ArrayList(u32).init(allocator);
for (1..6) |i| {
try avatar.has_taken_promotion_reward_list.append(@intCast(i));
}
try createSkillTree(avatar.base_avatar_id, &avatar.skilltree_list);
return avatar;
}
fn createSkillTree(
base_avatar_id: u32,
skilltree_list: *std.ArrayList(protocol.AvatarSkillTree),
) !void {
for (skill_config.avatar_skill_tree_config.items) |skill| {
if (skill.avatar_id == base_avatar_id) {
if (skill.level == skill.max_level) {
try skilltree_list.append(.{
.point_id = skill.point_id,
.level = skill.max_level,
});
}
}
}
}
pub fn createEquipment(
lightconeConf: Config.Lightcone,
dress_avatar_id: u32,
) !protocol.Equipment {
return protocol.Equipment{
.unique_id = Uid.nextGlobalId(),
.tid = lightconeConf.id,
.is_protected = true,
.level = lightconeConf.level,
.rank = lightconeConf.rank,
.promotion = lightconeConf.promotion,
.dress_avatar_id = dress_avatar_id,
};
}
pub fn createRelic(
allocator: Allocator,
relicConf: Config.Relic,
dress_avatar_id: u32,
) !protocol.Relic {
var r = protocol.Relic{
.tid = relicConf.id,
.main_affix_id = relicConf.main_affix_id,
.unique_id = Uid.nextGlobalId(),
.exp = 0,
.dress_avatar_id = dress_avatar_id,
.is_protected = true,
.level = relicConf.level,
.sub_affix_list = ArrayList(protocol.RelicAffix).init(allocator),
.reforge_sub_affix_list = ArrayList(protocol.RelicAffix).init(allocator),
};
try r.sub_affix_list.append(protocol.RelicAffix{ .affix_id = relicConf.stat1, .cnt = relicConf.cnt1, .step = relicConf.step1 });
try r.sub_affix_list.append(protocol.RelicAffix{ .affix_id = relicConf.stat2, .cnt = relicConf.cnt2, .step = relicConf.step2 });
try r.sub_affix_list.append(protocol.RelicAffix{ .affix_id = relicConf.stat3, .cnt = relicConf.cnt3, .step = relicConf.step3 });
try r.sub_affix_list.append(protocol.RelicAffix{ .affix_id = relicConf.stat4, .cnt = relicConf.cnt4, .step = relicConf.step4 });
return r;
}
pub fn createAllMultiPath(
allocator: std.mem.Allocator,
game_config: *const Config.GameConfig,
) !ArrayList(protocol.MultiPathAvatarInfo) {
var multis = ArrayList(protocol.MultiPathAvatarInfo).init(allocator);
const avatar_types = [_]protocol.MultiPathAvatarType{
.GirlWarriorType,
.GirlKnightType,
.GirlShamanType,
.GirlMemoryType,
.BoyWarriorType,
.BoyKnightType,
.BoyShamanType,
.BoyMemoryType,
.Mar_7thKnightType,
.Mar_7thRogueType,
};
const TYPE_COUNT = avatar_types.len;
var groups: [TYPE_COUNT]ArrayList(u32) = undefined;
for (0..TYPE_COUNT) |i| groups[i] = ArrayList(u32).init(allocator);
var counts: [TYPE_COUNT]u32 = [_]u32{0} ** TYPE_COUNT;
var indexes: [TYPE_COUNT]u32 = [_]u32{0} ** TYPE_COUNT;
var ranks: [TYPE_COUNT]u32 = [_]u32{0} ** TYPE_COUNT;
const total = @as(u32, @intCast(game_config.avatar_config.items.len));
for (game_config.avatar_config.items) |avatar| {
for (0..TYPE_COUNT) |i| counts[i] += 1;
const typ = getAvatarType(avatar.id);
var found_index: usize = TYPE_COUNT;
for (0..TYPE_COUNT) |i| {
if (avatar_types[i] == typ) {
found_index = i;
break;
}
}
if (found_index != TYPE_COUNT) {
try groups[found_index].append(avatar.id);
ranks[found_index] = avatar.rank;
indexes[found_index] = total + 1 - counts[found_index];
}
}
for (0..TYPE_COUNT) |i| {
var multi = protocol.MultiPathAvatarInfo.init(allocator);
multi.avatar_id = avatar_types[i];
multi.rank = ranks[i];
multi.dressed_skin_id = getSkinId(@intCast(@intFromEnum(avatar_types[i])));
const seed_start = Uid.getCurrentGlobalId() - (indexes[i] * 7);
var gen = Uid.UidGenerator().init(seed_start);
multi.path_equipment_id = gen.nextId();
multi.equip_relic_list = ArrayList(protocol.EquipRelic).init(allocator);
for (0..6) |slot| {
try multi.equip_relic_list.append(.{
.relic_unique_id = gen.nextId(),
.type = @intCast(slot),
});
}
multi.multi_path_skill_tree = ArrayList(protocol.AvatarSkillTree).init(allocator);
for (groups[i].items) |avatar_id| {
var skill_list = ArrayList(protocol.AvatarSkillTree).init(allocator);
try createSkillTree(avatar_id, &skill_list);
try multi.multi_path_skill_tree.appendSlice(skill_list.items);
skill_list.deinit();
}
try multis.append(multi);
}
for (0..TYPE_COUNT) |i| groups[i].deinit();
return multis;
}
fn getAvatarType(id: u32) protocol.MultiPathAvatarType {
return switch (id) {
1001 => .Mar_7thKnightType,
1224 => .Mar_7thRogueType,
else => {
if (id < 8001 or id > 8008) return .MultiPathAvatarTypeNone; // fallback
const base = (id - 8001) / 2;
const is_boy = (id % 2) == 1;
return switch (base) {
0 => if (is_boy) .BoyWarriorType else .GirlWarriorType,
1 => if (is_boy) .BoyKnightType else .GirlKnightType,
2 => if (is_boy) .BoyShamanType else .GirlShamanType,
3 => if (is_boy) .BoyMemoryType else .GirlMemoryType,
else => .GirlMemoryType,
};
},
};
}
pub fn getSkinId(avatar_id: u32) u32 {
for (Data.AvatarSkinMap) |entry| {
if (entry.avatar_id == avatar_id) return entry.skin_id;
}
return 0;
}
pub fn updateSkinId(avatar_id: u32, new_skin_id: u32) void {
for (&Data.AvatarSkinMap) |*entry| {
if (entry.avatar_id == avatar_id) {
entry.skin_id = new_skin_id;
return;
}
}
}
pub fn syncAvatarData(session: *Session, allocator: Allocator) !void {
var sync = protocol.PlayerSyncScNotify.init(allocator);
defer sync.deinit();
Uid.resetGlobalUidGens();
var char = protocol.AvatarSync.init(allocator);
for (Data.AllAvatars) |id| {
const avatar = try createAllAvatar(allocator, id);
try char.avatar_list.append(avatar);
}
for (config.avatar_config.items) |avatarConf| {
const avatar = try createAvatar(allocator, avatarConf);
try char.avatar_list.append(avatar);
}
const multis = try createAllMultiPath(allocator, config);
defer multis.deinit();
sync.avatar_sync = char;
try sync.multi_path_avatar_info_list.appendSlice(multis.items);
try session.send(CmdID.CmdPlayerSyncScNotify, sync);
}

View File

@@ -0,0 +1,522 @@
const std = @import("std");
const protocol = @import("protocol");
const Config = @import("../data/game_config.zig");
const Data = @import("../data.zig");
const Lineup = @import("../services/lineup.zig");
const ConfigManager = @import("config_mgr.zig");
const Logic = @import("../utils/logic.zig");
const AvatarConfig = @import("../data/avatar_config.zig");
const ArrayList = std.ArrayList;
const Allocator = std.mem.Allocator;
const CmdID = protocol.CmdID;
const skill_config = &ConfigManager.global_game_config_cache.avatar_skill_config;
const config = &ConfigManager.global_game_config_cache.game_config;
pub var selectedAvatarID = [_]u32{ 1304, 1313, 1406, 1004 };
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
pub var funmodeAvatarID = std.ArrayList(u32).init(gpa.allocator());
fn getAvatarElement(avatar_id: u32) AvatarConfig.Element {
const avatars = &ConfigManager.global_game_config_cache.avatar_config;
for (avatars.avatar_config.items) |avatar| {
if (avatar.avatar_id == avatar_id) {
return avatar.damage_type;
}
}
return .None;
}
fn getAttackerBuffId() u32 {
const avatar_id = if (!Logic.FunMode().FunMode()) selectedAvatarID[Lineup.leader_slot] else funmodeAvatarID.items[Lineup.leader_slot];
const element = getAvatarElement(avatar_id);
return switch (element) {
.Physical => 1000111,
.Fire => 1000112,
.Ice => 1000113,
.Thunder => 1000114,
.Wind => 1000115,
.Quantum => 1000116,
.Imaginary => 1000117,
.None => 0,
};
}
fn createBattleRelic(allocator: Allocator, id: u32, level: u32, main_affix_id: u32, stat1: u32, cnt1: u32, step1: u32, stat2: u32, cnt2: u32, step2: u32, stat3: u32, cnt3: u32, step3: u32, stat4: u32, cnt4: u32, step4: u32) !protocol.BattleRelic {
var relic = protocol.BattleRelic{ .id = id, .main_affix_id = main_affix_id, .level = level, .sub_affix_list = ArrayList(protocol.RelicAffix).init(allocator) };
if (stat1 != 0) try relic.sub_affix_list.append(protocol.RelicAffix{ .affix_id = stat1, .cnt = cnt1, .step = step1 });
if (stat2 != 0) try relic.sub_affix_list.append(protocol.RelicAffix{ .affix_id = stat2, .cnt = cnt2, .step = step2 });
if (stat3 != 0) try relic.sub_affix_list.append(protocol.RelicAffix{ .affix_id = stat3, .cnt = cnt3, .step = step3 });
if (stat4 != 0) try relic.sub_affix_list.append(protocol.RelicAffix{ .affix_id = stat4, .cnt = cnt4, .step = step4 });
return relic;
}
fn createBattleAvatar(allocator: Allocator, avatarConf: Config.Avatar) !protocol.BattleAvatar {
var avatar = protocol.BattleAvatar.init(allocator);
avatar.id = avatarConf.id;
avatar.hp = avatarConf.hp * 100;
avatar.sp_bar = .{ .cur_sp = avatarConf.sp * 100, .max_sp = 10000 };
avatar.level = avatarConf.level;
avatar.rank = avatarConf.rank;
avatar.promotion = avatarConf.promotion;
avatar.avatar_type = .AVATAR_FORMAL_TYPE;
if (Logic.inlist(avatar.id, &Data.EnhanceAvatarID)) avatar.enhanced_id = 1;
for (avatarConf.relics.items) |relic| {
const r = try createBattleRelic(allocator, relic.id, relic.level, relic.main_affix_id, relic.stat1, relic.cnt1, relic.step1, relic.stat2, relic.cnt2, relic.step2, relic.stat3, relic.cnt3, relic.step3, relic.stat4, relic.cnt4, relic.step4);
try avatar.relic_list.append(r);
}
const lc = protocol.BattleEquipment{
.id = avatarConf.lightcone.id,
.rank = avatarConf.lightcone.rank,
.level = avatarConf.lightcone.level,
.promotion = avatarConf.lightcone.promotion,
};
try avatar.equipment_list.append(lc);
for (skill_config.avatar_skill_tree_config.items) |skill| {
if (skill.avatar_id == avatar.id) {
if (skill.level == skill.max_level) {
try avatar.skilltree_list.append(.{
.point_id = skill.point_id,
.level = skill.max_level,
});
}
}
}
return avatar;
}
const BuffRule = struct {
avatar_id: u32,
buffs: []const struct {
id: u32,
owner_is_avatar: bool = true,
dynamic_values: []const protocol.BattleBuff.DynamicValuesEntry = &.{},
},
};
const technique_buffs = [_]BuffRule{
.{
.avatar_id = 1208,
.buffs = &.{
.{ .id = 120801, .dynamic_values = &.{
.{ .key = .{ .Const = "SkillIndex" }, .value = 0 },
} },
.{ .id = 120802, .dynamic_values = &.{
.{ .key = .{ .Const = "SkillIndex" }, .value = 0 },
} },
},
},
.{
.avatar_id = 1224,
.buffs = &.{
.{ .id = 122402, .dynamic_values = &.{
.{ .key = .{ .Const = "SkillIndex" }, .value = 0 },
} },
.{ .id = 122403, .dynamic_values = &.{
.{ .key = .{ .Const = "SkillIndex" }, .value = 0 },
} },
.{ .id = 122401, .dynamic_values = &.{
.{ .key = .{ .Const = "#ADF_1" }, .value = 3 },
.{ .key = .{ .Const = "#ADF_2" }, .value = 3 },
} },
},
},
.{
.avatar_id = 1304,
.buffs = &.{.{ .id = 130403, .dynamic_values = &.{
.{ .key = .{ .Const = "SkillIndex" }, .value = 3 },
} }},
},
.{
.avatar_id = 1306,
.buffs = &.{
.{ .id = 130601, .dynamic_values = &.{
.{ .key = .{ .Const = "SkillIndex" }, .value = 0 },
} },
.{ .id = 130602, .dynamic_values = &.{
.{ .key = .{ .Const = "SkillIndex" }, .value = 0 },
} },
},
},
.{
.avatar_id = 1308,
.buffs = &.{
.{ .id = 130803, .dynamic_values = &.{
.{ .key = .{ .Const = "SkillIndex" }, .value = 0 },
} },
},
},
.{
.avatar_id = 1309,
.buffs = &.{
.{ .id = 130901, .dynamic_values = &.{
.{ .key = .{ .Const = "SkillIndex" }, .value = 0 },
} },
.{ .id = 130902, .dynamic_values = &.{
.{ .key = .{ .Const = "SkillIndex" }, .value = 0 },
} },
},
},
.{
.avatar_id = 1310,
.buffs = &.{
.{ .id = 131001, .dynamic_values = &.{
.{ .key = .{ .Const = "SkillIndex" }, .value = 0 },
} },
.{ .id = 131002, .dynamic_values = &.{
.{ .key = .{ .Const = "SkillIndex" }, .value = 0 },
} },
.{ .id = 1000112, .dynamic_values = &.{
.{ .key = .{ .Const = "SkillIndex" }, .value = 0 },
} },
},
},
.{
.avatar_id = 1412,
.buffs = &.{
.{ .id = 141201, .dynamic_values = &.{
.{ .key = .{ .Const = "SkillIndex" }, .value = 0 },
} },
.{
.id = 1000121,
.owner_is_avatar = false,
.dynamic_values = &.{
.{ .key = .{ .Const = "SkillIndex" }, .value = 0 },
},
},
},
},
.{
.avatar_id = 1414,
.buffs = &.{
.{ .id = 141401, .dynamic_values = &.{
.{ .key = .{ .Const = "SkillIndex" }, .value = 0 },
} },
.{
.id = 1000121,
.owner_is_avatar = false,
.dynamic_values = &.{
.{ .key = .{ .Const = "SkillIndex" }, .value = 0 },
},
},
},
},
};
fn addTechniqueBuffs(allocator: Allocator, battle: *protocol.SceneBattleInfo, avatar: protocol.BattleAvatar, avatarConf: Config.Avatar, avatar_index: u32) !void {
if (!avatarConf.use_technique) return;
var targetIndexList = ArrayList(u32).init(allocator);
errdefer targetIndexList.deinit();
try targetIndexList.append(0);
const buffedAvatarId = switch (avatar.id) {
8004 => 8003,
8006 => 8005,
8008 => 8007,
else => avatar.id,
};
if (Logic.inlist(buffedAvatarId, &Data.IgnoreToughness)) {
var buff = protocol.BattleBuff{
.id = 1000119,
.level = 1,
.owner_index = @intCast(avatar_index),
.wave_flag = 0xFFFFFFFF,
.target_index_list = try targetIndexList.clone(),
.dynamic_values = ArrayList(protocol.BattleBuff.DynamicValuesEntry).init(allocator),
};
try buff.dynamic_values.append(.{ .key = .{ .Const = "SkillIndex" }, .value = 0 });
try battle.buff_list.append(buff);
}
var found_custom_buff = false;
for (technique_buffs) |rule| {
if (rule.avatar_id == buffedAvatarId) {
found_custom_buff = true;
for (rule.buffs) |buff_def| {
var buff = protocol.BattleBuff{
.id = buff_def.id,
.level = 1,
.owner_index = if (buff_def.owner_is_avatar) @intCast(avatar_index) else Lineup.leader_slot,
.wave_flag = 0xFFFFFFFF,
.target_index_list = try targetIndexList.clone(),
.dynamic_values = ArrayList(protocol.BattleBuff.DynamicValuesEntry).init(allocator),
};
try buff.dynamic_values.appendSlice(buff_def.dynamic_values);
try battle.buff_list.append(buff);
}
break;
}
}
if (!found_custom_buff) {
var buff = protocol.BattleBuff{
.id = buffedAvatarId * 100 + 1,
.level = 1,
.owner_index = @intCast(avatar_index),
.wave_flag = 0xFFFFFFFF,
.target_index_list = try targetIndexList.clone(),
.dynamic_values = ArrayList(protocol.BattleBuff.DynamicValuesEntry).init(allocator),
};
try buff.dynamic_values.append(.{ .key = .{ .Const = "SkillIndex" }, .value = 0 });
try battle.buff_list.append(buff);
}
targetIndexList.deinit();
}
fn addGolbalPassive(allocator: Allocator, battle: *protocol.SceneBattleInfo) !void {
if (Logic.inlist(1407, Data.AllAvatars)) {
var targetIndexList = ArrayList(u32).init(allocator);
errdefer targetIndexList.deinit();
try targetIndexList.append(0);
var mazebuff_data = protocol.BattleBuff{
.id = 140703,
.level = 1,
.owner_index = 1,
.wave_flag = 0xFFFFFFFF,
.target_index_list = try targetIndexList.clone(),
.dynamic_values = ArrayList(protocol.BattleBuff.DynamicValuesEntry).init(allocator),
};
try mazebuff_data.dynamic_values.append(.{ .key = .{ .Const = "SkillIndex" }, .value = 0 });
try battle.buff_list.append(mazebuff_data);
targetIndexList.deinit();
}
}
fn addTriggerAttack(allocator: Allocator, battle: *protocol.SceneBattleInfo) !void {
var targetIndexList = ArrayList(u32).init(allocator);
errdefer targetIndexList.deinit();
try targetIndexList.append(0);
var attack = protocol.BattleBuff{
.id = getAttackerBuffId(),
.level = 1,
.owner_index = Lineup.leader_slot,
.wave_flag = 0xFFFFFFFF,
.target_index_list = try targetIndexList.clone(),
.dynamic_values = ArrayList(protocol.BattleBuff.DynamicValuesEntry).init(allocator),
};
try attack.dynamic_values.append(.{ .key = .{ .Const = "SkillIndex" }, .value = 1 });
try battle.buff_list.append(attack);
targetIndexList.deinit();
}
fn createBattleInfo(allocator: Allocator, game_config: *const Config.GameConfig, stage_monster_wave_len: u32, stage_id: u32, rounds_limit: u32) protocol.SceneBattleInfo {
var battle = protocol.SceneBattleInfo.init(allocator);
battle.battle_id = game_config.battle_config.battle_id;
battle.stage_id = stage_id;
battle.logic_random_seed = @intCast(@mod(std.time.timestamp(), 0xFFFFFFFF));
battle.rounds_limit = rounds_limit;
battle.monster_wave_length = @intCast(stage_monster_wave_len);
battle.world_level = 6;
return battle;
}
fn addMonsterWaves(allocator: Allocator, battle: *protocol.SceneBattleInfo, monster_wave_configs: std.ArrayList(std.ArrayList(u32)), monster_level: u32) !void {
for (monster_wave_configs.items) |wave| {
var monster_wave = protocol.SceneMonsterWave.init(allocator);
monster_wave.monster_param = protocol.SceneMonsterWaveParam{ .level = monster_level };
for (wave.items) |mob_id| {
try monster_wave.monster_list.append(.{ .monster_id = mob_id, .max_hp = Logic.FunMode().GetHp() });
}
try battle.monster_wave_list.append(monster_wave);
}
}
fn addStageBlessings(allocator: Allocator, battle: *protocol.SceneBattleInfo, blessings: []const u32) !void {
for (blessings) |blessing| {
var targetIndexList = ArrayList(u32).init(allocator);
errdefer targetIndexList.deinit();
try targetIndexList.append(0);
var buff = protocol.BattleBuff{
.id = blessing,
.level = 1,
.owner_index = 0xffffffff,
.wave_flag = 0xffffffff,
.target_index_list = try targetIndexList.clone(),
.dynamic_values = ArrayList(protocol.BattleBuff.DynamicValuesEntry).init(allocator),
};
try buff.dynamic_values.append(.{ .key = .{ .Const = "SkillIndex" }, .value = 0 });
try battle.buff_list.append(buff);
targetIndexList.deinit();
}
}
fn addBattleTargets(allocator: Allocator, battle: *protocol.SceneBattleInfo) !void {
battle.battle_target_info = ArrayList(protocol.SceneBattleInfo.BattleTargetInfoEntry).init(allocator);
var pfTargetHead = protocol.BattleTargetList{ .battle_target_list = ArrayList(protocol.BattleTarget).init(allocator) };
if (Logic.Challenge().ChallengeMode()) {
if (Logic.CustomMode().FirstNode()) {
try pfTargetHead.battle_target_list.append(.{ .id = 10003, .progress = 0, .total_progress = 80000 });
} else {
try pfTargetHead.battle_target_list.append(.{ .id = 10003, .progress = 40000, .total_progress = 80000 });
}
} else {
try pfTargetHead.battle_target_list.append(.{ .id = 10002, .progress = 0, .total_progress = 80000 });
}
var pfTargetTail = protocol.BattleTargetList{ .battle_target_list = ArrayList(protocol.BattleTarget).init(allocator) };
try pfTargetTail.battle_target_list.append(.{ .id = 2001, .progress = 0, .total_progress = 0 });
try pfTargetTail.battle_target_list.append(.{ .id = 2002, .progress = 0, .total_progress = 0 });
var asTargetHead = protocol.BattleTargetList{ .battle_target_list = ArrayList(protocol.BattleTarget).init(allocator) };
try asTargetHead.battle_target_list.append(.{ .id = 90005, .progress = 2000, .total_progress = 0 });
switch (battle.stage_id) {
30019000...30019100, 30021000...30021100, 30301000...30319000 => { // PF
try battle.battle_target_info.append(.{ .key = 1, .value = pfTargetHead });
for (2..4) |i| {
try battle.battle_target_info.append(.{ .key = @intCast(i), .value = protocol.BattleTargetList{ .battle_target_list = ArrayList(protocol.BattleTarget).init(allocator) } });
}
try battle.battle_target_info.append(.{ .key = 5, .value = pfTargetTail });
},
420100...420900 => { // AS
try battle.battle_target_info.append(.{ .key = 1, .value = asTargetHead });
},
else => {},
}
}
fn commonBattleSetup(
allocator: Allocator,
battle: *protocol.SceneBattleInfo,
selected_avatar_ids: []const u32,
avatar_configs: []const Config.Avatar,
monster_wave_configs: std.ArrayList(std.ArrayList(u32)),
monster_level: u32,
stage_blessings: []const u32,
) !void {
var avatarIndex: u32 = 0;
for (selected_avatar_ids) |selected_id| {
for (avatar_configs) |avatarConf| {
if (avatarConf.id == selected_id) {
const avatar = try createBattleAvatar(allocator, avatarConf);
try addTechniqueBuffs(allocator, battle, avatar, avatarConf, avatarIndex);
try battle.battle_avatar_list.append(avatar);
avatarIndex += 1;
}
}
}
try addMonsterWaves(allocator, battle, monster_wave_configs, monster_level);
try addTriggerAttack(allocator, battle);
try addStageBlessings(allocator, battle, stage_blessings);
try addGolbalPassive(allocator, battle);
try addBattleTargets(allocator, battle);
}
pub const BattleManager = struct {
allocator: Allocator,
pub fn init(allocator: Allocator) BattleManager {
return BattleManager{ .allocator = allocator };
}
pub fn createBattle(self: *BattleManager) !protocol.SceneBattleInfo {
try ConfigManager.UpdateGameConfig();
var battle = createBattleInfo(
self.allocator,
config,
@intCast(config.battle_config.monster_wave.items.len),
config.battle_config.stage_id,
config.battle_config.cycle_count,
);
try commonBattleSetup(
self.allocator,
&battle,
if (!Logic.FunMode().FunMode()) &selectedAvatarID else funmodeAvatarID.items,
config.avatar_config.items,
config.battle_config.monster_wave,
config.battle_config.monster_level,
config.battle_config.blessings.items,
);
return battle;
}
};
pub const ChallegeStageManager = struct {
allocator: Allocator,
game_config_cache: *ConfigManager.GameConfigCache,
pub fn init(allocator: Allocator, cache: *ConfigManager.GameConfigCache) ChallegeStageManager {
return ChallegeStageManager{
.allocator = allocator,
.game_config_cache = cache,
};
}
pub fn createChallegeStage(self: *ChallegeStageManager) !protocol.SceneBattleInfo {
if (!Logic.Challenge().FoundStage()) {
std.log.info("Challenge stage ID is 0, skipping challenge battle creation and returning an empty battle info.", .{});
return protocol.SceneBattleInfo.init(self.allocator);
}
try ConfigManager.UpdateGameConfig();
const stage_config = &self.game_config_cache.stage_config;
var battle: protocol.SceneBattleInfo = undefined;
var found_stage = false;
for (stage_config.stage_config.items) |stageConf| {
if (stageConf.stage_id == Logic.Challenge().GetChallengeStageID()) {
battle = createBattleInfo(
self.allocator,
config,
@intCast(stageConf.monster_list.items.len),
stageConf.stage_id,
if (Logic.Challenge().GetChallengeMode() != 1) 30 else 4,
);
found_stage = true;
try commonBattleSetup(
self.allocator,
&battle,
if (!Logic.FunMode().FunMode()) &selectedAvatarID else funmodeAvatarID.items,
config.avatar_config.items,
stageConf.monster_list,
stageConf.level,
Logic.Challenge().GetChallengeBlessingID(),
);
break;
}
}
if (!found_stage) {
std.log.err("Challenge stage with ID {d} not found in config.", .{Logic.Challenge().GetChallengeStageID()});
return error.StageNotFound;
}
return battle;
}
};
pub fn deinitSceneBattleInfo(battle: *protocol.SceneBattleInfo) void {
for (battle.battle_avatar_list.items) |*avatar| {
for (avatar.relic_list.items) |*relic| {
relic.sub_affix_list.deinit();
}
avatar.relic_list.deinit();
avatar.equipment_list.deinit();
avatar.skilltree_list.deinit();
}
battle.battle_avatar_list.deinit();
for (battle.buff_list.items) |*buff| {
buff.target_index_list.deinit();
buff.dynamic_values.deinit();
}
battle.buff_list.deinit();
for (battle.monster_wave_list.items) |*wave| {
wave.monster_list.deinit();
}
battle.monster_wave_list.deinit();
for (battle.battle_target_info.items) |*entry| {
if (entry.value) |*v| {
v.battle_target_list.deinit();
}
}
battle.battle_target_info.deinit();
}

View File

@@ -0,0 +1,192 @@
const std = @import("std");
const protocol = @import("protocol");
const Session = @import("../Session.zig");
const Packet = @import("../Packet.zig");
const Data = @import("../data.zig");
const ConfigManager = @import("../manager/config_mgr.zig");
const Logic = @import("../utils/logic.zig");
const ArrayList = std.ArrayList;
const Allocator = std.mem.Allocator;
const CmdID = protocol.CmdID;
const challenge_config = &ConfigManager.global_game_config_cache.challenge_maze_config;
const entrance_config = &ConfigManager.global_game_config_cache.map_entrance_config;
const maze_config = &ConfigManager.global_game_config_cache.maze_config;
const peak_config = &ConfigManager.global_game_config_cache.challenge_peak_config;
const peak_boss_config = &ConfigManager.global_game_config_cache.challenge_peak_boss_config;
pub const ChallengeManager = struct {
allocator: std.mem.Allocator,
pub fn init(allocator: std.mem.Allocator) ChallengeManager {
return ChallengeManager{ .allocator = allocator };
}
pub fn createChallenge(
self: *ChallengeManager,
challenge_id: u32,
buff_id: u32,
) !protocol.CurChallenge {
var cur_challenge_info = protocol.CurChallenge.init(self.allocator);
cur_challenge_info.challenge_id = challenge_id;
cur_challenge_info.score_id = if (challenge_id > 20000 and challenge_id < 30000) 40000 else 0;
cur_challenge_info.score_two = 0;
cur_challenge_info.status = protocol.ChallengeStatus.CHALLENGE_DOING;
cur_challenge_info.extra_lineup_type = if (Logic.CustomMode().FirstNode()) protocol.ExtraLineupType.LINEUP_CHALLENGE else protocol.ExtraLineupType.LINEUP_CHALLENGE_2;
if (Logic.CustomMode().FirstNode()) {
for (challenge_config.challenge_config.items) |challengeConf| {
if (challengeConf.id == challenge_id) {
std.debug.print("TRACING CONFIG ID {} WITH CHALLENGE ID {}\n", .{ challengeConf.id, challenge_id });
for (entrance_config.map_entrance_config.items) |entrance| {
if (entrance.id == challengeConf.map_entrance_id) {
for (maze_config.maze_plane_config.items) |maze| {
if (Logic.contains(&maze.floor_id_list, entrance.floor_id)) {
if (challenge_id > 20000 and challenge_id < 30000) {
var story_buff = protocol.ChallengeStoryBuffList{
.buff_list = ArrayList(u32).init(self.allocator),
};
try story_buff.buff_list.append(challengeConf.maze_buff_id);
try story_buff.buff_list.append(buff_id);
try Logic.Challenge().AddBlessing(story_buff.buff_list.items);
cur_challenge_info.stage_info = .{
.MLNEDDHOJGC = .{
.cur_story_buffs = story_buff,
},
};
Logic.Challenge().SetChallengeMode(1);
} else if (challenge_id > 30000) {
var boss_buff = protocol.ChallengeBossBuffList{
.buff_list = ArrayList(u32).init(self.allocator),
.challenge_boss_const = 1,
};
try boss_buff.buff_list.append(challengeConf.maze_buff_id);
try boss_buff.buff_list.append(buff_id);
try Logic.Challenge().AddBlessing(boss_buff.buff_list.items);
cur_challenge_info.stage_info = .{
.MLNEDDHOJGC = .{
.cur_boss_buffs = boss_buff,
},
};
Logic.Challenge().SetChallengeMode(2);
}
Logic.Challenge().SetChallengeInfo(
entrance.floor_id,
maze.world_id,
challengeConf.npc_monster_id_list1.items[challengeConf.npc_monster_id_list1.items.len - 1],
challengeConf.event_id_list1.items[challengeConf.event_id_list1.items.len - 1],
challengeConf.maze_group_id1,
challengeConf.maze_group_id1,
maze.challenge_plane_id,
challengeConf.map_entrance_id,
);
}
}
}
}
}
}
} else {
for (challenge_config.challenge_config.items) |challengeConf| {
if (challengeConf.id == challenge_id) {
std.debug.print("TRACING CONFIG ID {} WITH CHALLENGE ID {}\n", .{ challengeConf.id, challenge_id });
for (entrance_config.map_entrance_config.items) |entrance| {
if (entrance.id == challengeConf.map_entrance_id2) {
for (maze_config.maze_plane_config.items) |maze| {
if (Logic.contains(&maze.floor_id_list, entrance.floor_id)) {
if (challengeConf.maze_group_id2) |id| {
if (challenge_id > 20000 and challenge_id < 30000) {
var story_buff = protocol.ChallengeStoryBuffList{
.buff_list = ArrayList(u32).init(self.allocator),
};
try story_buff.buff_list.append(challengeConf.maze_buff_id);
try story_buff.buff_list.append(buff_id);
try Logic.Challenge().AddBlessing(story_buff.buff_list.items);
cur_challenge_info.stage_info = .{
.MLNEDDHOJGC = .{
.cur_story_buffs = story_buff,
},
};
Logic.Challenge().SetChallengeMode(1);
} else if (challenge_id > 30000) {
var boss_buff = protocol.ChallengeBossBuffList{
.buff_list = ArrayList(u32).init(self.allocator),
.challenge_boss_const = 1,
};
try boss_buff.buff_list.append(challengeConf.maze_buff_id);
try boss_buff.buff_list.append(buff_id);
try Logic.Challenge().AddBlessing(boss_buff.buff_list.items);
cur_challenge_info.stage_info = .{
.MLNEDDHOJGC = .{
.cur_boss_buffs = boss_buff,
},
};
Logic.Challenge().SetChallengeMode(2);
}
Logic.Challenge().SetChallengeInfo(
entrance.floor_id,
maze.world_id,
challengeConf.npc_monster_id_list2.items[challengeConf.npc_monster_id_list2.items.len - 1],
challengeConf.event_id_list2.items[challengeConf.event_id_list2.items.len - 1],
id,
id,
maze.challenge_plane_id,
challengeConf.map_entrance_id2,
);
} else {
std.debug.print("THIS CHALLENGE ID: {} DOES NOT SUPPORT 2ND NODE. PLEASE DO COMMAND /node TO SWITCH BACK TO FIRST NODE\n", .{challenge_id});
}
}
}
}
}
}
}
}
return cur_challenge_info;
}
pub fn createChallengePeak(
_: *ChallengeManager,
challenge_peak_id: u32,
buff_id: u32,
) !void {
for (peak_config.challenge_peak.items) |peak| {
if (peak.id == challenge_peak_id) {
for (entrance_config.map_entrance_config.items) |entrance| {
if (entrance.id == peak.map_entrance_id) {
if (buff_id != 0) try Logic.Challenge().AddBlessing(&[_]u32{buff_id});
if (Logic.Challenge().ChallengePeakHard()) {
for (peak_boss_config.challenge_peak_boss_config.items) |boss| {
if (boss.id == challenge_peak_id) try Logic.Challenge().AddBlessing(boss.hard_tag_list.items);
}
} else {
try Logic.Challenge().AddBlessing(peak.tag_list.items);
}
Logic.Challenge().SetChallengePeakInfo(
entrance.floor_id,
peak.npc_monster_id_list.items[peak.npc_monster_id_list.items.len - 1],
Logic.Challenge().CalChallengePeakEventID(peak.event_id_list.items[peak.event_id_list.items.len - 1]),
peak.maze_group_id,
peak.maze_group_id,
entrance.plane_id,
peak.map_entrance_id,
);
}
}
}
}
}
};
pub fn deinitCurChallenge(challenge: *protocol.CurChallenge) void {
if (challenge.stage_info) |*stage_info| {
if (stage_info.MLNEDDHOJGC) |*union_val| {
switch (union_val.*) {
.cur_story_buffs => |*story_buffs| {
story_buffs.buff_list.deinit();
},
.cur_boss_buffs => |*boss_buffs| {
boss_buffs.buff_list.deinit();
},
}
}
}
}

View File

@@ -0,0 +1,197 @@
const std = @import("std");
const protocol = @import("protocol");
const Session = @import("../session.zig");
const Packet = @import("../Packet.zig");
pub const GameConfig = @import("../data/game_config.zig");
pub const StageConfig = @import("../data/stage_config.zig");
pub const ChallengeConfig = @import("../data/challenge_config.zig");
pub const MiscConfig = @import("../data/misc_config.zig");
pub const ResConfig = @import("../data/res_config.zig");
pub const AvatarConfig = @import("../data/avatar_config.zig");
const ArrayList = std.ArrayList;
const Allocator = std.mem.Allocator;
const Data = @import("../data.zig");
pub const GameConfigCache = struct {
allocator: Allocator,
game_config: GameConfig.GameConfig,
res_config: ResConfig.SceneConfig,
saved_values_config: ResConfig.FloorSavedValuesConfig,
avatar_skill_config: AvatarConfig.AvatarSkillTreeConfig,
avatar_config: AvatarConfig.AvatarConfig,
map_entrance_config: MiscConfig.MapEntranceConfig,
maze_config: MiscConfig.MazePlaneConfig,
stage_config: StageConfig.StageConfig,
anchor_config: ResConfig.SceneAnchorConfig,
quest_config: MiscConfig.QuestConfig,
challenge_maze_config: ChallengeConfig.ChallengeMazeConfig,
challenge_peak_config: ChallengeConfig.ChallengePeakConfig,
challenge_peak_group_config: ChallengeConfig.ChallengePeakGroupConfig,
challenge_peak_boss_config: ChallengeConfig.ChallengePeakBossConfig,
activity_config: MiscConfig.ActivityConfig,
main_mission_config: MiscConfig.MainMissionConfig,
tutorial_guide_config: MiscConfig.TutorialGuideConfig,
tutorial_config: MiscConfig.TutorialConfig,
player_icon_config: MiscConfig.PlayerIconConfig,
buff_info_config: MiscConfig.TextMapConfig,
pub fn init(allocator: Allocator) !GameConfigCache {
var game_cfg = try loadConfig(GameConfig.GameConfig, GameConfig.parseConfig, allocator, "config.json");
errdefer game_cfg.deinit();
var res_cfg = try loadConfig(ResConfig.SceneConfig, ResConfig.parseAnchor, allocator, "resources/res.json");
errdefer res_cfg.deinit();
var saved_values_cfg = try loadConfig(ResConfig.FloorSavedValuesConfig, ResConfig.parseFloorSavedValuesConfig, allocator, "resources/FloorSavedValuesConfig.json");
errdefer saved_values_cfg.deinit();
var avatar_skill_cfg = try loadConfig(AvatarConfig.AvatarSkillTreeConfig, AvatarConfig.parseAvatarSkillTreeConfig, allocator, "resources/AvatarSkillTreeConfig.json");
errdefer avatar_skill_cfg.deinit();
var avatar_cfg = try loadConfig(AvatarConfig.AvatarConfig, AvatarConfig.parseAvatarConfig, allocator, "resources/AvatarConfig.json");
errdefer avatar_cfg.deinit();
var map_entr_cfg = try loadConfig(MiscConfig.MapEntranceConfig, MiscConfig.parseMapEntranceConfig, allocator, "resources/MapEntrance.json");
errdefer map_entr_cfg.deinit();
var maze_cfg = try loadConfig(MiscConfig.MazePlaneConfig, MiscConfig.parseMazePlaneConfig, allocator, "resources/MazePlane.json");
errdefer maze_cfg.deinit();
var stage_cfg = try loadConfig(StageConfig.StageConfig, StageConfig.parseStageConfig, allocator, "resources/StageConfig.json");
errdefer stage_cfg.deinit();
var anchor_cfg = try loadConfig(ResConfig.SceneAnchorConfig, ResConfig.parseAnchorConfig, allocator, "resources/Anchor.json");
errdefer anchor_cfg.deinit();
var quest_cfg = try loadConfig(MiscConfig.QuestConfig, MiscConfig.parseQuestConfig, allocator, "resources/QuestData.json");
errdefer quest_cfg.deinit();
var challenge_maze_cfg = try loadConfig(ChallengeConfig.ChallengeMazeConfig, ChallengeConfig.parseChallengeConfig, allocator, "resources/ChallengeMazeConfig.json");
errdefer challenge_maze_cfg.deinit();
var challenge_peak_cfg = try loadConfig(ChallengeConfig.ChallengePeakConfig, ChallengeConfig.parseChallengePeakConfig, allocator, "resources/ChallengePeakConfig.json");
errdefer challenge_peak_cfg.deinit();
var challenge_peak_group_cfg = try loadConfig(ChallengeConfig.ChallengePeakGroupConfig, ChallengeConfig.parseChallengePeakGroupConfig, allocator, "resources/ChallengePeakGroupConfig.json");
errdefer challenge_peak_group_cfg.deinit();
var challenge_peak_boss_cfg = try loadConfig(ChallengeConfig.ChallengePeakBossConfig, ChallengeConfig.parseChallengePeakBossConfig, allocator, "resources/ChallengePeakBossConfig.json");
errdefer challenge_peak_boss_cfg.deinit();
var activity_cfg = try loadConfig(MiscConfig.ActivityConfig, MiscConfig.parseActivityConfig, allocator, "resources/ActivityConfig.json");
errdefer activity_cfg.deinit();
var main_mission_cfg = try loadConfig(MiscConfig.MainMissionConfig, MiscConfig.parseMainMissionConfig, allocator, "resources/MainMission.json");
errdefer main_mission_cfg.deinit();
var tutorial_guide_cfg = try loadConfig(MiscConfig.TutorialGuideConfig, MiscConfig.parseTutorialGuideConfig, allocator, "resources/TutorialGuideGroup.json");
errdefer tutorial_guide_cfg.deinit();
var tutorial_cfg = try loadConfig(MiscConfig.TutorialConfig, MiscConfig.parseTutorialConfig, allocator, "resources/TutorialData.json");
errdefer tutorial_cfg.deinit();
var player_icon_cfg = try loadConfig(MiscConfig.PlayerIconConfig, MiscConfig.parsePlayerIconConfig, allocator, "resources/AvatarPlayerIcon.json");
errdefer player_icon_cfg.deinit();
var buff_info_cfg = try loadConfig(MiscConfig.TextMapConfig, MiscConfig.parseTextMapConfig, allocator, "resources/BuffInfoConfig.json");
errdefer buff_info_cfg.deinit(allocator);
return .{
.allocator = allocator,
.game_config = game_cfg,
.res_config = res_cfg,
.saved_values_config = saved_values_cfg,
.avatar_skill_config = avatar_skill_cfg,
.avatar_config = avatar_cfg,
.map_entrance_config = map_entr_cfg,
.maze_config = maze_cfg,
.stage_config = stage_cfg,
.anchor_config = anchor_cfg,
.quest_config = quest_cfg,
.challenge_maze_config = challenge_maze_cfg,
.challenge_peak_config = challenge_peak_cfg,
.challenge_peak_group_config = challenge_peak_group_cfg,
.challenge_peak_boss_config = challenge_peak_boss_cfg,
.activity_config = activity_cfg,
.main_mission_config = main_mission_cfg,
.tutorial_guide_config = tutorial_guide_cfg,
.tutorial_config = tutorial_cfg,
.player_icon_config = player_icon_cfg,
.buff_info_config = buff_info_cfg,
};
}
pub fn deinit(self: *GameConfigCache) void {
self.game_config.deinit();
self.res_config.deinit();
self.saved_values_config.deinit();
self.avatar_skill_config.deinit();
self.avatar_config.deinit();
self.map_entrance_config.deinit();
self.maze_config.deinit();
self.stage_config.deinit();
self.anchor_config.deinit();
self.quest_config.deinit();
self.challenge_maze_config.deinit();
self.challenge_peak_config.deinit();
self.challenge_peak_group_config.deinit();
self.challenge_peak_boss_config.deinit();
self.activity_config.deinit();
self.main_mission_config.deinit();
self.tutorial_guide_config.deinit();
self.tutorial_config.deinit();
self.player_icon_config.deinit();
self.buff_info_config.deinit(global_main_allocator);
}
};
pub var global_game_config_cache: GameConfigCache = undefined;
pub var global_main_allocator: Allocator = undefined;
pub fn initGameGlobals(main_allocator: Allocator) !void {
global_main_allocator = main_allocator;
global_game_config_cache = try GameConfigCache.init(main_allocator);
const avatars = &global_game_config_cache.avatar_config.avatar_config.items;
var all = ArrayList(u32).init(main_allocator);
var four_star = ArrayList(u32).init(main_allocator);
for (avatars.*) |avatar| {
if (avatar.avatar_id == avatar.adventure_player_id and avatar.avatar_id <= 8001 and avatar.avatar_id != 1224 and (avatar.avatar_id == 1001 or avatar.avatar_id != 1001)) {
try all.append(avatar.avatar_id);
if (avatar.rarity == 4) try four_star.append(avatar.avatar_id);
}
}
Data.AllAvatars = try all.toOwnedSlice();
Data.AvatarList = try four_star.toOwnedSlice();
}
pub fn deinitGameGlobals() void {
global_game_config_cache.deinit();
}
var game_config_mtime: i128 = 0;
pub fn UpdateGameConfig() !void {
const stat = try std.fs.cwd().statFile("config.json");
if (stat.mtime > game_config_mtime) {
global_game_config_cache.game_config.deinit();
global_game_config_cache.game_config = try loadConfig(GameConfig.GameConfig, GameConfig.parseConfig, global_main_allocator, "config.json");
game_config_mtime = stat.mtime;
}
}
pub fn loadConfig(
comptime ConfigType: type,
comptime parseFn: fn (std.json.Value, Allocator) anyerror!ConfigType,
allocator: Allocator,
filename: []const u8,
) anyerror!ConfigType {
const file = try std.fs.cwd().openFile(filename, .{});
defer file.close();
const file_size = try file.getEndPos();
const buffer = try file.readToEndAlloc(allocator, file_size);
defer allocator.free(buffer);
var json_tree = try std.json.parseFromSlice(std.json.Value, allocator, buffer, .{});
defer json_tree.deinit();
const root = json_tree.value;
return try parseFn(root, allocator);
}

View File

@@ -0,0 +1,126 @@
const std = @import("std");
const protocol = @import("protocol");
const Session = @import("../Session.zig");
const Packet = @import("../Packet.zig");
const BattleManager = @import("../manager/battle_mgr.zig");
const AvatarManager = @import("../manager/avatar_mgr.zig");
const ConfigManager = @import("../manager/config_mgr.zig");
const Logic = @import("../utils/logic.zig");
const ArrayList = std.ArrayList;
const Allocator = std.mem.Allocator;
const CmdID = protocol.CmdID;
const config = &ConfigManager.global_game_config_cache.game_config;
pub const LineupManager = struct {
allocator: Allocator,
pub fn init(allocator: Allocator) LineupManager {
return LineupManager{ .allocator = allocator };
}
pub fn createLineup(self: *LineupManager) !protocol.LineupInfo {
var ids = ArrayList(u32).init(self.allocator);
defer ids.deinit();
var picked_mc = false;
var picked_m7th = false;
for (config.avatar_config.items) |avatarConf| {
if (ids.items.len >= 4) break;
const id = switch (avatarConf.id) {
8001...8008 => if (!picked_mc) blk: {
picked_mc = true;
break :blk AvatarManager.mc_id;
} else continue,
1224, 1001 => if (!picked_m7th) blk: {
picked_m7th = true;
break :blk AvatarManager.m7th;
} else continue,
else => avatarConf.id,
};
try ids.append(id);
}
return try buildLineup(self.allocator, ids.items, null);
}
};
pub const ChallengeLineupManager = struct {
allocator: Allocator,
pub fn init(allocator: Allocator) ChallengeLineupManager {
return ChallengeLineupManager{ .allocator = allocator };
}
pub fn createPeakLineup(self: *ChallengeLineupManager, avatar_list: ArrayList(u32)) !protocol.LineupInfo {
return try buildLineup(self.allocator, avatar_list.items, .LINEUP_CHALLENGE);
}
pub fn createLineup(self: *ChallengeLineupManager, avatar_list: ArrayList(u32)) !protocol.LineupInfo {
const t = if (Logic.CustomMode().FirstNode())
protocol.ExtraLineupType.LINEUP_CHALLENGE
else
protocol.ExtraLineupType.LINEUP_CHALLENGE_2;
return try buildLineup(self.allocator, avatar_list.items, t);
}
};
pub fn buildLineup(
allocator: Allocator,
ids: []const u32,
extra_type: ?protocol.ExtraLineupType,
) !protocol.LineupInfo {
var lineup = protocol.LineupInfo.init(allocator);
lineup.mp = 5;
lineup.max_mp = 5;
if (extra_type) |t| {
lineup.extra_lineup_type = t;
} else {
lineup.name = .{ .Const = "DahliaSR" };
}
for (ids, 0..) |id, idx| {
var avatar = protocol.LineupAvatar.init(allocator);
avatar.id = id;
if (id == 1408) {
lineup.mp = 7;
lineup.max_mp = 7;
}
avatar.slot = @intCast(idx);
avatar.satiety = 0;
avatar.hp = 10000;
avatar.sp_bar = .{ .cur_sp = 10000, .max_sp = 10000 };
avatar.avatar_type = protocol.AvatarType.AVATAR_FORMAL_TYPE;
try lineup.avatar_list.append(avatar);
}
var id_list = try allocator.alloc(u32, lineup.avatar_list.items.len);
defer allocator.free(id_list);
for (lineup.avatar_list.items, 0..) |ava, idx| {
id_list[idx] = ava.id;
}
try getSelectedAvatarID(allocator, id_list);
return lineup;
}
pub fn deinitLineupInfo(lineup: *protocol.LineupInfo) void {
lineup.avatar_list.deinit();
}
pub fn deinitChallengeLineupInfo(lineup: *protocol.LineupInfo) void {
lineup.avatar_list.deinit();
}
pub fn getSelectedAvatarID(allocator: Allocator, input: []const u32) !void {
var tempList = ArrayList(u32).init(allocator);
defer tempList.deinit();
try tempList.appendSlice(input);
for (tempList.items) |*item| {
if (item.* == 8001) item.* = AvatarManager.mc_id;
if (item.* == 1001) item.* = AvatarManager.m7th;
}
var i: usize = 0;
while (i < BattleManager.selectedAvatarID.len and i < tempList.items.len) : (i += 1) {
BattleManager.selectedAvatarID[i] = tempList.items[i];
}
while (i < BattleManager.selectedAvatarID.len) : (i += 1) {
BattleManager.selectedAvatarID[i] = 0;
}
}
pub fn getFunModeAvatarID(input: []const u32) !void {
BattleManager.funmodeAvatarID.clearRetainingCapacity();
try BattleManager.funmodeAvatarID.appendSlice(input);
}

View File

@@ -0,0 +1,381 @@
const std = @import("std");
const protocol = @import("protocol");
const Session = @import("../session.zig");
const Packet = @import("../Packet.zig");
const Config = @import("../data/game_config.zig");
const Res_config = @import("../data/res_config.zig");
const AvatarManager = @import("../manager/avatar_mgr.zig");
const ConfigManager = @import("../manager/config_mgr.zig");
const Uid = @import("../utils/uid.zig");
const ArrayList = std.ArrayList;
const Allocator = std.mem.Allocator;
const CmdID = protocol.CmdID;
const config = &ConfigManager.global_game_config_cache.game_config;
const res_config = &ConfigManager.global_game_config_cache.res_config;
const mission_config = &ConfigManager.global_game_config_cache.main_mission_config;
const saved_values_config = &ConfigManager.global_game_config_cache.saved_values_config;
const map_entrance_config = &ConfigManager.global_game_config_cache.map_entrance_config;
const anchors = &ConfigManager.global_game_config_cache.anchor_config;
fn getBaseAvatarId(id: u32) u32 {
return switch (id) {
8001...8008 => 8001,
1224 => 1001,
else => id,
};
}
inline fn toVector(v: anytype) protocol.Vector {
return .{ .x = v.x, .y = v.y, .z = v.z };
}
fn getOrCreateGroup(
group_map: *std.AutoHashMap(u32, protocol.SceneEntityGroupInfo),
group_id: u32,
allocator: Allocator,
) !*protocol.SceneEntityGroupInfo {
if (group_map.getPtr(group_id)) |g| return g;
var new_group = protocol.SceneEntityGroupInfo.init(allocator);
new_group.state = 1;
new_group.group_id = group_id;
try group_map.put(group_id, new_group);
return group_map.getPtr(group_id).?;
}
pub const SceneManager = struct {
allocator: Allocator,
pub fn init(allocator: Allocator) SceneManager {
return .{ .allocator = allocator };
}
fn addAvatarEntities(
scene_group: *protocol.SceneEntityGroupInfo,
avatar_configs: []const Config.Avatar,
tele_pos: protocol.Vector,
tele_rot: protocol.Vector,
uid: u32,
) !void {
for (avatar_configs) |avatarConf| {
const base_id = getBaseAvatarId(avatarConf.id);
try scene_group.entity_list.append(.{
.inst_id = 1,
.entity_id = @intCast(base_id + 100000),
.entity = .{
.actor = .{
.base_avatar_id = base_id,
.avatar_type = .AVATAR_FORMAL_TYPE,
.uid = uid,
.map_layer = 0,
},
},
.motion = .{ .pos = tele_pos, .rot = tele_rot },
});
}
}
fn addPropEntities(
allocator: Allocator,
group_map: *std.AutoHashMap(u32, protocol.SceneEntityGroupInfo),
prop_configs: []const Res_config.Props,
generator: *Uid.BaseUidGen(),
) !void {
for (prop_configs) |propConf| {
var scene_group = try getOrCreateGroup(group_map, propConf.groupId, allocator);
var prop_info = protocol.ScenePropInfo.init(allocator);
prop_info.prop_id = propConf.propId;
prop_info.prop_state = propConf.propState;
try scene_group.entity_list.append(.{
.entity = .{ .prop = prop_info },
.group_id = scene_group.group_id,
.inst_id = propConf.instId,
.entity_id = 1000 + generator.nextId(),
.motion = .{ .pos = toVector(propConf.pos), .rot = toVector(propConf.rot) },
});
}
}
fn addMonsterEntities(
allocator: Allocator,
group_map: *std.AutoHashMap(u32, protocol.SceneEntityGroupInfo),
monster_configs: []const Res_config.Monsters,
generator: *Uid.BaseUidGen(),
) !void {
for (monster_configs) |monsConf| {
var scene_group = try getOrCreateGroup(group_map, monsConf.groupId, allocator);
var monster_info = protocol.SceneNpcMonsterInfo.init(allocator);
monster_info.monster_id = monsConf.monsterId;
monster_info.event_id = monsConf.eventId;
monster_info.world_level = 6;
try scene_group.entity_list.append(.{
.entity = .{ .npc_monster = monster_info },
.group_id = scene_group.group_id,
.inst_id = monsConf.instId,
.entity_id = if ((monsConf.monsterId / 1000) % 10 == 3) monster_info.monster_id else generator.nextId(),
.motion = .{ .pos = toVector(monsConf.pos), .rot = toVector(monsConf.rot) },
});
}
}
pub fn createScene(
self: *SceneManager,
plane_id: u32,
floor_id: u32,
entry_id: u32,
teleport_id: u32,
) !protocol.SceneInfo {
var generator = Uid.BaseUidGen().init();
var scene_info = protocol.SceneInfo.init(self.allocator);
scene_info.game_mode_type = 1;
scene_info.plane_id = plane_id;
scene_info.floor_id = floor_id;
scene_info.entry_id = entry_id;
var main_mission = std.ArrayList(u32).init(self.allocator);
for (mission_config.main_mission_config.items) |id| {
try main_mission.append(id.main_mission_id);
}
var mission_status = protocol.MissionStatusBySceneInfo.init(self.allocator);
mission_status.finished_main_mission_id_list = main_mission;
scene_info.scene_mission_info = mission_status;
for (saved_values_config.floor_saved_values.items) |saved| {
if (saved.floor_id == floor_id) {
for (saved.saved_values.items) |values| {
try scene_info.floor_saved_data.append(.{
.key = .{ .Const = values.name orelse "" },
.value = values.max_value orelse 0,
});
}
}
}
scene_info.leader_entity_id = config.avatar_config.items[0].id + 100000;
scene_info.world_id = 501;
scene_info.client_pos_version = 1;
var group_map = std.AutoHashMap(u32, protocol.SceneEntityGroupInfo).init(self.allocator);
defer group_map.deinit();
for (res_config.scene_config.items) |sceneConf| {
for (sceneConf.teleports.items) |teleConf| {
if (teleConf.teleportId != teleport_id) continue;
var scene_group = protocol.SceneEntityGroupInfo.init(self.allocator);
scene_group.state = 1;
try addAvatarEntities(&scene_group, config.avatar_config.items, toVector(teleConf.pos), toVector(teleConf.rot), 0);
try scene_info.entity_group_list.append(scene_group);
break;
}
if (sceneConf.planeID == plane_id and sceneConf.entryID == entry_id) {
try addPropEntities(self.allocator, &group_map, sceneConf.props.items, &generator);
try addMonsterEntities(self.allocator, &group_map, sceneConf.monsters.items, &generator);
}
}
var iter = group_map.iterator();
while (iter.next()) |entry| {
const g = entry.value_ptr.*;
try scene_info.entity_group_list.append(g);
try scene_info.entity_list.appendSlice(g.entity_list.items);
try scene_info.LDDIGMLCGDB.append(g.group_id);
try scene_info.custom_data_list.append(.{ .group_id = g.group_id });
try scene_info.group_state_list.append(.{
.group_id = g.group_id,
.state = 0,
.is_default = true,
});
}
const ranges = [_][2]usize{ .{ 0, 101 }, .{ 10000, 10051 }, .{ 20000, 20001 }, .{ 30000, 30020 } };
for (ranges) |range| {
for (range[0]..range[1]) |i| try scene_info.lighten_section_list.append(@intCast(i));
}
return scene_info;
}
};
pub const ChallengeSceneManager = struct {
allocator: Allocator,
pub fn init(allocator: Allocator) ChallengeSceneManager {
return .{ .allocator = allocator };
}
pub fn getAnchorMotion(entry_id: u32) ?protocol.MotionInfo {
for (anchors.anchor_config.items) |anchorConf| {
if (anchorConf.entryID != entry_id) continue;
if (anchorConf.anchor.items.len == 0) break;
const a = anchorConf.anchor.items[0];
return protocol.MotionInfo{ .pos = toVector(a.pos), .rot = toVector(a.rot) };
}
return null;
}
fn createBaseScene(
self: *ChallengeSceneManager,
game_mode_type: u32,
avatar_list: ArrayList(u32),
plane_id: u32,
floor_id: u32,
entry_id: u32,
world_id: ?u32,
maze_group_id: u32,
) !protocol.SceneInfo {
var scene_info = protocol.SceneInfo.init(self.allocator);
scene_info.game_mode_type = game_mode_type;
scene_info.plane_id = plane_id;
scene_info.floor_id = floor_id;
scene_info.entry_id = entry_id;
scene_info.leader_entity_id = avatar_list.items[0];
if (world_id) |wid| scene_info.world_id = wid;
try scene_info.group_state_list.append(.{
.group_id = maze_group_id,
.is_default = true,
});
var scene_group = protocol.SceneEntityGroupInfo.init(self.allocator);
scene_group.state = 1;
scene_group.group_id = 0;
var main_mission = std.ArrayList(u32).init(self.allocator);
for (mission_config.main_mission_config.items) |id| {
try main_mission.append(id.main_mission_id);
}
var mission_status = protocol.MissionStatusBySceneInfo.init(self.allocator);
mission_status.finished_main_mission_id_list = main_mission;
scene_info.scene_mission_info = mission_status;
for (saved_values_config.floor_saved_values.items) |saved| {
if (saved.floor_id == floor_id) {
for (saved.saved_values.items) |values| {
try scene_info.floor_saved_data.append(.{
.key = .{ .Const = values.name orelse "" },
.value = values.max_value orelse 0,
});
}
}
}
for (avatar_list.items) |avatar_base_id| {
const base_id = getBaseAvatarId(avatar_base_id);
try scene_group.entity_list.append(.{
.inst_id = 1,
.entity_id = @intCast(base_id + 100000),
.entity = .{
.actor = .{
.base_avatar_id = base_id,
.avatar_type = .AVATAR_FORMAL_TYPE,
.uid = 1,
.map_layer = 0,
},
},
.motion = .{ .pos = .{}, .rot = .{} },
});
}
try scene_info.entity_group_list.append(scene_group);
return scene_info;
}
fn addChallengeEntities(
self: *ChallengeSceneManager,
scene_info: *protocol.SceneInfo,
group_id: u32,
monster_id: u32,
event_id: u32,
generator: *Uid.BaseUidGen(),
) !void {
for (res_config.scene_config.items) |sceneConf| {
if (sceneConf.planeID != scene_info.plane_id) continue;
for (sceneConf.monsters.items) |monsConf| {
if (monsConf.groupId != group_id) continue;
var scene_group = protocol.SceneEntityGroupInfo.init(self.allocator);
scene_group.state = 1;
scene_group.group_id = group_id;
var monster_info = protocol.SceneNpcMonsterInfo.init(self.allocator);
monster_info.monster_id = monster_id;
monster_info.event_id = event_id;
monster_info.world_level = 6;
try scene_group.entity_list.append(.{
.entity = .{ .npc_monster = monster_info },
.group_id = group_id,
.inst_id = monsConf.instId,
.entity_id = generator.nextId(),
.motion = .{ .pos = toVector(monsConf.pos), .rot = toVector(monsConf.rot) },
});
try scene_info.entity_group_list.append(scene_group);
break;
}
for (sceneConf.props.items) |propConf| {
if (propConf.groupId != group_id) continue;
var scene_group = protocol.SceneEntityGroupInfo.init(self.allocator);
scene_group.state = 1;
scene_group.group_id = group_id;
var prop_info = protocol.ScenePropInfo.init(self.allocator);
prop_info.prop_id = propConf.propId;
prop_info.prop_state = propConf.propState;
try scene_group.entity_list.append(.{
.entity = .{ .prop = prop_info },
.group_id = group_id,
.inst_id = propConf.instId,
.entity_id = generator.nextId(),
.motion = .{ .pos = toVector(propConf.pos), .rot = toVector(propConf.rot) },
});
try scene_info.entity_group_list.append(scene_group);
}
}
}
pub fn createScene(
self: *ChallengeSceneManager,
avatar_list: ArrayList(u32),
plane_id: u32,
floor_id: u32,
entry_id: u32,
world_id: u32,
monster_id: u32,
event_id: u32,
group_id: u32,
maze_group_id: u32,
) !protocol.SceneInfo {
var scene_info = try self.createBaseScene(4, avatar_list, plane_id, floor_id, entry_id, world_id, maze_group_id);
var generator = Uid.BaseUidGen().init();
try self.addChallengeEntities(&scene_info, group_id, monster_id, event_id, &generator);
return scene_info;
}
pub fn createPeakScene(
self: *ChallengeSceneManager,
avatar_list: ArrayList(u32),
plane_id: u32,
floor_id: u32,
entry_id: u32,
monster_id: u32,
event_id: u32,
group_id: u32,
maze_group_id: u32,
) !protocol.SceneInfo {
var scene_info = try self.createBaseScene(4, avatar_list, plane_id, floor_id, entry_id, null, maze_group_id);
var generator = Uid.BaseUidGen().init();
try self.addChallengeEntities(&scene_info, group_id, monster_id, event_id, &generator);
return scene_info;
}
};
pub const MazeMapManager = struct {
allocator: Allocator,
pub fn init(allocator: Allocator) MazeMapManager {
return .{ .allocator = allocator };
}
pub fn setMazeMapData(
self: *MazeMapManager,
map_info: *protocol.SceneMapInfo,
floor_id: u32,
) !void {
var plane_ids_map = std.AutoHashMap(u32, void).init(self.allocator);
defer plane_ids_map.deinit();
for (map_entrance_config.map_entrance_config.items) |entrConf| {
if (entrConf.floor_id == floor_id) {
try plane_ids_map.put(entrConf.plane_id, {});
}
}
map_info.maze_group_list = ArrayList(protocol.MazeGroup).init(self.allocator);
map_info.maze_prop_list = ArrayList(protocol.MazePropState).init(self.allocator);
for (res_config.scene_config.items) |sceneConf| {
if (!plane_ids_map.contains(sceneConf.planeID)) continue;
for (sceneConf.props.items) |propConf| {
try map_info.maze_group_list.append(protocol.MazeGroup{
.DDNOEGPCACF = ArrayList(u32).init(self.allocator),
.group_id = propConf.groupId,
});
try map_info.maze_prop_list.append(protocol.MazePropState{
.group_id = propConf.groupId,
.config_id = propConf.instId,
.state = propConf.propState,
});
}
}
}
};

View File

@@ -0,0 +1,51 @@
const std = @import("std");
const Session = @import("Session.zig");
const ConfigManager = @import("../src/manager/config_mgr.zig");
const Allocator = std.mem.Allocator;
pub fn listen() !void {
const addr = std.net.Address.parseIp4("0.0.0.0", 23301) catch unreachable;
var listener = try addr.listen(.{
.kernel_backlog = 100,
.reuse_address = true,
});
std.log.info("server is listening at {}", .{listener.listen_address});
while (true) {
const conn = listener.accept() catch continue;
runSession(
conn.address,
conn.stream,
ConfigManager.global_main_allocator,
&ConfigManager.global_game_config_cache,
) catch |err| {
std.log.err("session error: {}", .{err});
};
}
}
fn runSession(
address: std.net.Address,
stream: std.net.Stream,
main_allocator: Allocator,
game_config_cache: *ConfigManager.GameConfigCache,
) !void {
std.log.info("new connection from {}", .{address});
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer if (gpa.deinit() == .leak) std.log.err("memory leaks were detected for session at {}", .{address});
const session_allocator = gpa.allocator();
const session = try session_allocator.create(Session);
session.* = Session.init(address, stream, session_allocator, main_allocator, game_config_cache);
defer session_allocator.destroy(session);
if (session.*.run()) |_| {
std.log.info("client from {} disconnected", .{address});
} else |err| {
std.log.err("session disconnected with an error: {}", .{err});
}
}

View File

@@ -0,0 +1,178 @@
const std = @import("std");
const protocol = @import("protocol");
const Session = @import("../Session.zig");
const Packet = @import("../Packet.zig");
const Data = @import("../data.zig");
const Uid = @import("../utils/uid.zig");
const AvatarManager = @import("../manager/avatar_mgr.zig");
const LineupManager = @import("../manager/lineup_mgr.zig");
const ConfigManager = @import("../manager/config_mgr.zig");
const ArrayList = std.ArrayList;
const Allocator = std.mem.Allocator;
const CmdID = protocol.CmdID;
const config = &ConfigManager.global_game_config_cache.game_config;
pub fn onGetAvatarData(session: *Session, packet: *const Packet, allocator: Allocator) !void {
Uid.resetGlobalUidGen(0);
const req = try packet.getProto(protocol.GetAvatarDataCsReq, allocator);
defer req.deinit();
var rsp = protocol.GetAvatarDataScRsp.init(allocator);
try rsp.skin_list.appendSlice(&Data.SkinList);
rsp.is_get_all = req.is_get_all;
for (Data.AllAvatars) |id| {
const avatar = try AvatarManager.createAllAvatar(allocator, id);
try rsp.avatar_list.append(avatar);
}
for (config.avatar_config.items) |avatarConf| {
const avatar = try AvatarManager.createAvatar(allocator, avatarConf);
try rsp.avatar_list.append(avatar);
}
const multis = try AvatarManager.createAllMultiPath(allocator, config);
try rsp.multi_path_avatar_info_list.appendSlice(multis.items);
try rsp.basic_type_id_list.appendSlice(&Data.MultiAvatar);
try rsp.cur_avatar_path.append(.{
.key = 1001,
.value = switch (AvatarManager.m7th) {
1224 => .Mar_7thRogueType,
else => .Mar_7thKnightType,
},
});
const mc_type = @as(protocol.MultiPathAvatarType, switch (AvatarManager.mc_id) {
8001 => .BoyWarriorType,
8002 => .GirlWarriorType,
8003 => .BoyKnightType,
8004 => .GirlKnightType,
8005 => .BoyShamanType,
8006 => .GirlShamanType,
8007 => .BoyMemoryType,
8008 => .GirlMemoryType,
else => .GirlWarriorType, // fallback
});
try rsp.cur_avatar_path.append(.{
.key = 8001,
.value = mc_type,
});
try session.send(CmdID.CmdGetAvatarDataScRsp, rsp);
}
pub fn onGetBasicInfo(session: *Session, _: *const Packet, allocator: Allocator) !void {
var rsp = protocol.GetBasicInfoScRsp.init(allocator);
rsp.gender = 2;
rsp.is_gender_set = true;
rsp.player_setting_info = .{};
try session.send(CmdID.CmdGetBasicInfoScRsp, rsp);
}
pub fn onSetAvatarPath(session: *Session, packet: *const Packet, allocator: Allocator) !void {
var rsp = protocol.SetAvatarPathScRsp.init(allocator);
const req = try packet.getProto(protocol.SetAvatarPathCsReq, allocator);
defer req.deinit();
rsp.avatar_id = req.avatar_id;
switch (rsp.avatar_id) {
protocol.MultiPathAvatarType.Mar_7thKnightType => AvatarManager.m7th = 1001,
protocol.MultiPathAvatarType.Mar_7thRogueType => AvatarManager.m7th = 1224,
else => AvatarManager.mc_id = @intCast(@intFromEnum(rsp.avatar_id)),
}
var change = protocol.AvatarPathChangedNotify.init(allocator);
switch (req.avatar_id) {
protocol.MultiPathAvatarType.Mar_7thKnightType => change.base_avatar_id = 1001,
protocol.MultiPathAvatarType.Mar_7thRogueType => change.base_avatar_id = 1224,
else => change.base_avatar_id = @intCast(@intFromEnum(req.avatar_id)),
}
change.cur_multi_path_avatar_type = req.avatar_id;
try session.send(CmdID.CmdAvatarPathChangedNotify, change);
try AvatarManager.syncAvatarData(session, allocator);
var lineup = protocol.SyncLineupNotify.init(allocator);
var lineup_mgr = LineupManager.LineupManager.init(allocator);
const refresh = try lineup_mgr.createLineup();
lineup.lineup = refresh;
try session.send(CmdID.CmdSyncLineupNotify, lineup);
try session.send(CmdID.CmdSetAvatarPathScRsp, rsp);
}
pub fn onSetPlayerOutfit(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.SetPlayerOutfitCsReq, allocator);
defer req.deinit();
var sync = protocol.PlayerSyncScNotify.init(allocator);
sync.JLMNICAHJDE = req.JLMNICAHJDE;
try session.send(CmdID.CmdPlayerSyncScNotify, sync);
try session.send(CmdID.CmdSetPlayerOutfitScRsp, protocol.SetPlayerOutfitScRsp{
.retcode = 0,
});
}
pub fn onSetAvatarEnhancedId(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.SetAvatarEnhancedIdCsReq, allocator);
defer req.deinit();
if (req.enhanced_id == 0) {
for (&Data.EnhanceAvatarID) |*id| {
if (id.* == req.avatar_id) {
id.* = 0;
break;
}
}
} else {
const exists = for (&Data.EnhanceAvatarID) |id| {
if (id == req.avatar_id) break true;
} else false;
if (!exists) {
for (&Data.EnhanceAvatarID) |*id| {
if (id.* == 0) {
id.* = req.avatar_id;
break;
}
}
}
}
try AvatarManager.syncAvatarData(session, allocator);
try session.send(CmdID.CmdSetAvatarEnhancedIdScRsp, protocol.SetAvatarEnhancedIdScRsp{
.growth_avatar_id = req.avatar_id,
.retcode = 0,
.unk_enhanced_id = req.enhanced_id,
});
}
pub fn onDressAvatarSkin(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.DressAvatarSkinCsReq, allocator);
defer req.deinit();
AvatarManager.updateSkinId(req.avatar_id, req.skin_id);
try AvatarManager.syncAvatarData(session, allocator);
try session.send(CmdID.CmdDressAvatarSkinScRsp, protocol.DressAvatarSkinScRsp{
.retcode = 0,
});
}
pub fn onTakeOffAvatarSkin(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.TakeOffAvatarSkinCsReq, allocator);
defer req.deinit();
AvatarManager.updateSkinId(req.avatar_id, 0);
try AvatarManager.syncAvatarData(session, allocator);
try session.send(CmdID.CmdTakeOffAvatarSkinScRsp, protocol.TakeOffAvatarSkinScRsp{
.retcode = 0,
});
}
pub fn onGetBigDataAll(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.GetBigDataAllRecommendCsReq, allocator);
defer req.deinit();
var rsp = protocol.GetBigDataAllRecommendScRsp.init(allocator);
rsp.retcode = 0;
rsp.big_data_recommend_type = req.big_data_recommend_type;
try session.send(CmdID.CmdGetBigDataAllRecommendScRsp, rsp);
}
pub fn onGetBigData(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.GetBigDataRecommendCsReq, allocator);
defer req.deinit();
var rsp = protocol.GetBigDataRecommendScRsp.init(allocator);
rsp.retcode = 0;
rsp.big_data_recommend_type = req.big_data_recommend_type;
rsp.equip_avatar = req.equip_avatar;
try session.send(CmdID.CmdGetBigDataRecommendScRsp, rsp);
}
pub fn onGetPreAvatarGrowthInfo(session: *Session, _: *const Packet, allocator: Allocator) !void {
var rsp = protocol.GetPreAvatarGrowthInfoScRsp.init(allocator);
rsp.retcode = 0;
try session.send(CmdID.CmdGetPreAvatarGrowthInfoScRsp, rsp);
}

View File

@@ -0,0 +1,199 @@
const std = @import("std");
const protocol = @import("protocol");
const Session = @import("../Session.zig");
const Packet = @import("../Packet.zig");
const Data = @import("../data.zig");
const BattleManager = @import("../manager/battle_mgr.zig");
const ConfigManager = @import("../manager/config_mgr.zig");
const Logic = @import("../utils/logic.zig");
const ArrayList = std.ArrayList;
const Allocator = std.mem.Allocator;
const CmdID = protocol.CmdID;
const log = std.log.scoped(.scene_service);
pub var on_battle: bool = false;
pub fn onStartCocoonStage(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.StartCocoonStageCsReq, allocator);
defer req.deinit();
var battle_manager = BattleManager.BattleManager.init(allocator);
var battle = try battle_manager.createBattle();
_ = &battle;
on_battle = true;
try session.send(CmdID.CmdStartCocoonStageScRsp, protocol.StartCocoonStageScRsp{
.retcode = 0,
.cocoon_id = req.cocoon_id,
.prop_entity_id = req.prop_entity_id,
.wave = req.wave,
.battle_info = battle,
});
}
pub fn onQuickStartCocoonStage(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.QuickStartCocoonStageCsReq, allocator);
defer req.deinit();
var battle_manager = BattleManager.BattleManager.init(allocator);
var battle = try battle_manager.createBattle();
_ = &battle;
on_battle = true;
try session.send(CmdID.CmdQuickStartCocoonStageScRsp, protocol.QuickStartCocoonStageScRsp{
.retcode = 0,
.cocoon_id = req.cocoon_id,
.wave = req.wave,
.battle_info = battle,
});
}
pub fn onQuickStartFarmElement(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.QuickStartFarmElementCsReq, allocator);
defer req.deinit();
var battle_manager = BattleManager.BattleManager.init(allocator);
var battle = try battle_manager.createBattle();
_ = &battle;
on_battle = true;
try session.send(CmdID.CmdQuickStartFarmElementScRsp, protocol.QuickStartFarmElementScRsp{
.retcode = 0,
.world_level = req.world_level,
.LOFAFGIPDFP = req.LOFAFGIPDFP,
.battle_info = battle,
});
}
pub fn onStartBattleCollege(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.StartBattleCollegeCsReq, allocator);
defer req.deinit();
var battle_manager = BattleManager.BattleManager.init(allocator);
var battle = try battle_manager.createBattle();
_ = &battle;
on_battle = true;
try session.send(CmdID.CmdStartBattleCollegeScRsp, protocol.StartBattleCollegeScRsp{
.retcode = 0,
.id = req.id,
.battle_info = battle,
});
}
pub fn onSceneCastSkill(session: *Session, packet: *const Packet, allocator: Allocator) !void {
var battle_manager = BattleManager.BattleManager.init(allocator);
var battle = try battle_manager.createBattle();
defer BattleManager.deinitSceneBattleInfo(&battle);
var challenge_manager = BattleManager.ChallegeStageManager.init(allocator, &ConfigManager.global_game_config_cache);
var challenge = try challenge_manager.createChallegeStage();
defer BattleManager.deinitSceneBattleInfo(&challenge);
const req = try packet.getProto(protocol.SceneCastSkillCsReq, allocator);
defer req.deinit();
var battle_info: ?protocol.SceneBattleInfo = null;
var monster_battle_info_list = ArrayList(protocol.HitMonsterBattleInfo).init(allocator);
Highlight("SKILL INDEX: {}", .{req.skill_index});
Highlight("ATTACKED BY ENTITY ID: {}", .{req.attacked_by_entity_id});
const is_challenge = Logic.Challenge().ChallengeMode();
for (req.assist_monster_entity_id_list.items) |id| {
const attacker_id = req.attacked_by_entity_id;
const skill_index = req.skill_index;
const bt = getBattleType(id, attacker_id, skill_index, is_challenge);
if (is_challenge) {
if ((attacker_id <= 1000) or (id < 1000)) {
Highlight("CHALLENGE, MONSTER ENTITY ID: {} -> {}", .{ id, bt });
try monster_battle_info_list.append(.{
.target_monster_entity_id = id,
.monster_battle_type = bt,
});
if (bt == protocol.MonsterBattleType.MONSTER_BATTLE_TYPE_TRIGGER_BATTLE) {
battle_info = challenge;
}
}
} else {
if ((attacker_id <= 1000 or attacker_id > 1000000) or (id < 1000 or id > 1000000)) {
Highlight("BATTLE, MONSTER ENTITY ID: {} -> {}", .{ id, bt });
try monster_battle_info_list.append(.{
.target_monster_entity_id = id,
.monster_battle_type = bt,
});
if (bt == protocol.MonsterBattleType.MONSTER_BATTLE_TYPE_TRIGGER_BATTLE) {
battle_info = battle;
on_battle = true;
}
}
}
}
try session.send(CmdID.CmdSceneCastSkillScRsp, protocol.SceneCastSkillScRsp{
.retcode = 0,
.cast_entity_id = req.cast_entity_id,
.monster_battle_info = monster_battle_info_list,
.battle_info = battle_info,
});
}
pub fn onGetCurBattleInfo(session: *Session, _: *const Packet, allocator: Allocator) !void {
var battle_manager = BattleManager.BattleManager.init(allocator);
var battle = try battle_manager.createBattle();
defer BattleManager.deinitSceneBattleInfo(&battle);
var challenge_manager = BattleManager.ChallegeStageManager.init(allocator, &ConfigManager.global_game_config_cache);
var challenge = try challenge_manager.createChallegeStage();
defer BattleManager.deinitSceneBattleInfo(&challenge);
var rsp = protocol.GetCurBattleInfoScRsp.init(allocator);
rsp.battle_info = if (Logic.Challenge().ChallengeMode()) challenge else if (on_battle == true) battle else null;
rsp.retcode = 0;
try session.send(CmdID.CmdGetCurBattleInfoScRsp, rsp);
}
pub fn onPVEBattleResult(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.PVEBattleResultCsReq, allocator);
defer req.deinit();
var rsp = protocol.PVEBattleResultScRsp.init(allocator);
rsp.battle_id = req.battle_id;
rsp.end_status = req.end_status;
rsp.stage_id = req.stage_id;
on_battle = false;
try session.send(CmdID.CmdPVEBattleResultScRsp, rsp);
}
pub fn onSceneCastSkillCostMp(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.SceneCastSkillCostMpCsReq, allocator);
defer req.deinit();
try session.send(CmdID.CmdSceneCastSkillCostMpScRsp, protocol.SceneCastSkillCostMpScRsp{
.retcode = 0,
.cast_entity_id = req.cast_entity_id,
});
}
pub fn onSyncClientResVersion(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.SyncClientResVersionCsReq, allocator);
defer req.deinit();
std.debug.print("CLIENT RES VERSION: {}\n", .{req.client_res_version});
try session.send(CmdID.CmdSyncClientResVersionScRsp, protocol.SyncClientResVersionScRsp{
.retcode = 0,
.client_res_version = req.client_res_version,
});
}
fn Highlight(comptime msg: []const u8, args: anytype) void {
std.debug.print("\x1b[33m", .{});
std.debug.print(msg, args);
std.debug.print("\x1b[0m\n", .{});
}
fn getBattleType(id: u32, attacker_id: u32, skill_index: u32, is_challenge: bool) protocol.MonsterBattleType {
if (skill_index != 1) {
return protocol.MonsterBattleType.MONSTER_BATTLE_TYPE_TRIGGER_BATTLE;
}
if (attacker_id >= 1 and attacker_id <= 1000) {
return protocol.MonsterBattleType.MONSTER_BATTLE_TYPE_TRIGGER_BATTLE;
}
if (attacker_id >= 100000) {
const attacker_offset = attacker_id - 100000;
if (Logic.inlist(attacker_offset, &Data.IgnoreBattle)) {
return protocol.MonsterBattleType.MONSTER_BATTLE_TYPE_NO_BATTLE;
}
if (Logic.inlist(attacker_offset, &Data.SkipBattle)) {
if (is_challenge) {
return protocol.MonsterBattleType.MONSTER_BATTLE_TYPE_TRIGGER_BATTLE;
} else {
if (id > 1000000) {
return protocol.MonsterBattleType.MONSTER_BATTLE_TYPE_TRIGGER_BATTLE;
} else {
return protocol.MonsterBattleType.MONSTER_BATTLE_TYPE_DIRECT_DIE_SKIP_BATTLE;
}
}
}
}
return protocol.MonsterBattleType.MONSTER_BATTLE_TYPE_TRIGGER_BATTLE;
}

View File

@@ -0,0 +1,401 @@
const std = @import("std");
const protocol = @import("protocol");
const Session = @import("../Session.zig");
const Packet = @import("../Packet.zig");
const Data = @import("../data.zig");
const SceneManager = @import("../manager/scene_mgr.zig");
const LineupManager = @import("../manager/lineup_mgr.zig");
const ChallengeManager = @import("../manager/challenge_mgr.zig");
const ConfigManager = @import("../manager/config_mgr.zig");
const Logic = @import("../utils/logic.zig");
const ArrayList = std.ArrayList;
const Allocator = std.mem.Allocator;
const CmdID = protocol.CmdID;
const challenge_config = &ConfigManager.global_game_config_cache.challenge_maze_config;
const peak_group = &ConfigManager.global_game_config_cache.challenge_peak_group_config;
const peak_boss = &ConfigManager.global_game_config_cache.challenge_peak_boss_config;
pub fn onGetChallenge(session: *Session, _: *const Packet, allocator: Allocator) !void {
var rsp = protocol.GetChallengeScRsp.init(allocator);
rsp.retcode = 0;
var arena = std.heap.ArenaAllocator.init(allocator);
defer arena.deinit();
const a = arena.allocator();
try rsp.max_level_list.ensureTotalCapacity(challenge_config.challenge_config.items.len);
try rsp.challenge_list.ensureTotalCapacity(challenge_config.challenge_config.items.len);
for (challenge_config.challenge_config.items) |ids| {
var challenge = protocol.Challenge.init(a);
var history = protocol.ChallengeHistoryMaxLevel.init(a);
challenge.challenge_id = ids.id;
challenge.star = 7;
challenge.taken_reward = 42;
history.level = 12;
history.reward_display_type = 101212;
if (ids.id > 20000) {
history.level = 4;
history.reward_display_type = 101404;
if (ids.id < 30000) {
challenge.score_id = 40000;
challenge.score_two = 40000;
}
}
try rsp.max_level_list.append(history);
try rsp.challenge_list.append(challenge);
}
try session.send(CmdID.CmdGetChallengeScRsp, rsp);
}
pub fn onGetChallengeGroupStatistics(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.GetChallengeGroupStatisticsCsReq, allocator);
defer req.deinit();
var rsp = protocol.GetChallengeGroupStatisticsScRsp.init(allocator);
rsp.retcode = 0;
rsp.group_id = req.group_id;
try session.send(CmdID.CmdGetChallengeGroupStatisticsScRsp, rsp);
}
pub fn onLeaveChallenge(session: *Session, _: *const Packet, allocator: Allocator) !void {
var lineup_mgr = LineupManager.LineupManager.init(allocator);
var lineup = try lineup_mgr.createLineup();
_ = &lineup;
var scene_manager = SceneManager.SceneManager.init(allocator);
var scene_info = try scene_manager.createScene(20422, 20422001, 2042201, 1025);
_ = &scene_info;
try session.send(CmdID.CmdQuitBattleScNotify, protocol.QuitBattleScNotify{});
try session.send(CmdID.CmdEnterSceneByServerScNotify, protocol.EnterSceneByServerScNotify{
.reason = protocol.EnterSceneReason.ENTER_SCENE_REASON_NONE,
.lineup = lineup,
.scene = scene_info,
});
Logic.Challenge().resetChallengeState();
try session.send(CmdID.CmdLeaveChallengeScRsp, protocol.LeaveChallengeScRsp{
.retcode = 0,
});
}
pub fn onLeaveChallengePeak(session: *Session, _: *const Packet, allocator: Allocator) !void {
var lineup_mgr = LineupManager.LineupManager.init(allocator);
var lineup = try lineup_mgr.createLineup();
_ = &lineup;
var scene_manager = SceneManager.SceneManager.init(allocator);
var scene_info = try scene_manager.createScene(20422, 20422001, 2042201, 1025);
_ = &scene_info;
try session.send(CmdID.CmdQuitBattleScNotify, protocol.QuitBattleScNotify{});
try session.send(CmdID.CmdEnterSceneByServerScNotify, protocol.EnterSceneByServerScNotify{
.reason = protocol.EnterSceneReason.ENTER_SCENE_REASON_NONE,
.lineup = lineup,
.scene = scene_info,
});
Logic.Challenge().resetChallengeState();
try session.send(CmdID.CmdLeaveChallengePeakScRsp, protocol.LeaveChallengePeakScRsp{
.retcode = 0,
});
}
pub fn onGetCurChallengeScRsp(session: *Session, _: *const Packet, allocator: Allocator) !void {
var rsp = protocol.GetCurChallengeScRsp.init(allocator);
var lineup_manager = LineupManager.ChallengeLineupManager.init(allocator);
var lineup_info = try lineup_manager.createLineup(Logic.Challenge().GetAvatarIDs());
var challenge_manager = ChallengeManager.ChallengeManager.init(allocator);
var cur_challenge_info = try challenge_manager.createChallenge(
Logic.Challenge().GetChallengeID(),
Logic.Challenge().GetChallengeBuffID(),
);
rsp.retcode = 0;
if (Logic.Challenge().ChallengeMode()) {
rsp.cur_challenge = cur_challenge_info;
try rsp.lineup_list.append(lineup_info);
Logic.Challenge().GetCurChallengeStatus();
} else {
LineupManager.deinitLineupInfo(&lineup_info);
ChallengeManager.deinitCurChallenge(&cur_challenge_info);
std.debug.print("NOT ON CHALLENGE\n", .{});
}
try session.send(CmdID.CmdGetCurChallengeScRsp, rsp);
}
pub fn onStartChallenge(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.StartChallengeCsReq, allocator);
defer req.deinit();
var rsp = protocol.StartChallengeScRsp.init(allocator);
if (Logic.CustomMode().CustomMode()) {
Logic.Challenge().SetChallengeID(Logic.CustomMode().GetCustomChallengeID());
Logic.Challenge().SetChallengeBuffID(Logic.CustomMode().GetCustomBuffID());
if (Logic.CustomMode().FirstNode()) {
try Logic.Challenge().AddAvatar(req.first_lineup.items);
} else {
try Logic.Challenge().AddAvatar(req.second_lineup.items);
}
} else {
Logic.Challenge().SetChallengeID(req.challenge_id);
if (Logic.CustomMode().FirstNode()) {
try Logic.Challenge().AddAvatar(req.first_lineup.items);
if (Logic.Challenge().GameModePF())
Logic.Challenge().SetChallengeBuffID(req.stage_info.?.MLNEDDHOJGC.?.story_info.buff_one);
if (Logic.Challenge().GameModeAS())
Logic.Challenge().SetChallengeBuffID(req.stage_info.?.MLNEDDHOJGC.?.boss_info.buff_one);
} else {
try Logic.Challenge().AddAvatar(req.second_lineup.items);
if (Logic.Challenge().GameModePF())
Logic.Challenge().SetChallengeBuffID(req.stage_info.?.MLNEDDHOJGC.?.story_info.buff_two);
if (Logic.Challenge().GameModeAS())
Logic.Challenge().SetChallengeBuffID(req.stage_info.?.MLNEDDHOJGC.?.boss_info.buff_two);
}
}
var lineup_manager = LineupManager.ChallengeLineupManager.init(allocator);
var lineup_info = try lineup_manager.createLineup(Logic.Challenge().GetAvatarIDs());
_ = &lineup_info;
var challenge_manager = ChallengeManager.ChallengeManager.init(allocator);
var cur_challenge_info = try challenge_manager.createChallenge(
Logic.Challenge().GetChallengeID(),
Logic.Challenge().GetChallengeBuffID(),
);
_ = &cur_challenge_info;
const ids = Logic.Challenge().GetSceneIDs();
var scene_challenge_manager = SceneManager.ChallengeSceneManager.init(allocator);
var scene_info = try scene_challenge_manager.createScene(
Logic.Challenge().GetAvatarIDs(),
ids[0],
ids[1],
ids[2],
ids[3],
ids[4],
ids[5],
ids[6],
ids[7],
);
_ = &scene_info;
rsp.retcode = 0;
rsp.scene = scene_info;
rsp.cur_challenge = cur_challenge_info;
try rsp.lineup_list.append(lineup_info);
Logic.Challenge().SetChallenge();
try session.send(CmdID.CmdStartChallengeScRsp, rsp);
Logic.Challenge().GetCurSceneStatus();
const anchor_motion = SceneManager.ChallengeSceneManager.getAnchorMotion(scene_info.entry_id);
if (anchor_motion) |motion| {
for (scene_info.entity_group_list.items) |*group| {
for (group.entity_list.items) |*entity| {
if (entity.entity) |ent| if (ent == .actor) {
try session.send(
CmdID.CmdSceneEntityMoveScNotify,
protocol.SceneEntityMoveScNotify{
.entity_id = entity.entity_id,
.entry_id = scene_info.entry_id,
.motion = motion,
},
);
};
}
}
}
}
pub fn onTakeChallengeReward(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.TakeChallengeRewardCsReq, allocator);
defer req.deinit();
var rsp = protocol.TakeChallengeRewardScRsp.init(allocator);
var reward = protocol.TakenChallengeRewardInfo.init(allocator);
if (req.group_id > 2000) reward.star_count = 12 else reward.star_count = 36;
try rsp.taken_reward_list.append(reward);
rsp.retcode = 0;
rsp.group_id = req.group_id;
try session.send(CmdID.CmdTakeChallengeRewardScRsp, rsp);
}
// Peak challenge WIP
pub fn onGetCurChallengePeak(session: *Session, _: *const Packet, allocator: Allocator) !void {
var rsp = protocol.GetCurChallengePeakScRsp.init(allocator);
rsp.retcode = 0;
try session.send(CmdID.CmdGetCurChallengePeakScRsp, rsp);
}
pub fn onGetChallengePeakData(session: *Session, _: *const Packet, allocator: Allocator) !void {
var rsp = protocol.GetChallengePeakDataScRsp.init(allocator);
rsp.retcode = 0;
const target_king = [_]u32{ 3003, 3004, 3005 };
var reward = ArrayList(u32).init(allocator);
for (1..13) |i| {
try reward.append(@intCast(i));
}
var ava = ArrayList(u32).init(allocator);
try ava.appendSlice(&[_]u32{1321});
const BossType = @TypeOf(peak_boss.challenge_peak_boss_config.items[0]);
var boss_map = std.AutoHashMap(u32, *const BossType).init(allocator);
defer boss_map.deinit();
for (peak_boss.challenge_peak_boss_config.items) |*boss| {
try boss_map.put(boss.id, boss);
}
for (peak_group.challenge_peak_group.items) |id| {
if (boss_map.get(id.boss_level_id)) |boss| {
var data = protocol.ChallengePeakGroup.init(allocator);
const unk = ArrayList(protocol.BODCCJIMBAK).init(allocator);
data.peak_group_id = id.id;
data.taken_star_rewards = reward;
data.count_of_peaks = 3;
data.obtained_stars = 9;
data.peak_boss = .{
.finished_target_list = blk: {
var list = std.ArrayList(u32).init(allocator);
try list.appendSlice(&target_king);
break :blk list;
},
.hard_mode_has_passed = true,
.hard_mode = .{
.has_passed = true,
.best_cycle_count = 0,
.buff_id = boss.buff_list.items[0],
.peak_avatar_id_list = ava,
.IDHOKMBMKNJ = ava,
.LAKIANOCINI = unk,
},
};
try rsp.challenge_peak_groups.append(data);
rsp.current_peak_group_id = id.id;
}
}
try session.send(CmdID.CmdGetChallengePeakDataScRsp, rsp);
}
pub fn onReStartChallengePeak(session: *Session, _: *const Packet, _: Allocator) !void {
try session.send(CmdID.CmdReStartChallengePeakScRsp, protocol.ReStartChallengePeakScRsp{
.retcode = 0,
});
}
pub fn onSetChallengePeakMobLineupAvatar(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.SetChallengePeakMobLineupAvatarCsReq, allocator);
defer req.deinit();
var update = protocol.ChallengePeakGroup.init(allocator);
update.peak_group_id = req.peak_group_id;
update.count_of_peaks = 3;
update.obtained_stars = 9;
for (req.lineup_list.items) |list| {
var build = protocol.ChallengePeak.init(allocator);
build.peak_id = list.peak_id;
build.peak_avatar_id_list = list.peak_avatar_id_list;
try Logic.Challenge().SavePeakLineup(list.peak_id, list.peak_avatar_id_list.items);
try update.peaks.append(build);
}
var rsp = protocol.SetChallengePeakMobLineupAvatarScRsp.init(allocator);
rsp.retcode = 0;
try session.send(CmdID.CmdChallengePeakGroupDataUpdateScNotify, protocol.ChallengePeakGroupDataUpdateScNotify{
.challenge_peak_group = update,
});
try session.send(CmdID.CmdSetChallengePeakMobLineupAvatarScRsp, rsp);
}
pub fn onStartChallengePeak(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.StartChallengePeakCsReq, allocator);
defer req.deinit();
var rsp = protocol.StartChallengePeakScRsp.init(allocator);
rsp.retcode = 0;
if (req.peak_avatar_id_list.items.len != 0) {
Logic.Challenge().SetPeakBoss(true);
try Logic.Challenge().AddAvatar(req.peak_avatar_id_list.items);
} else {
Logic.Challenge().SetPeakBoss(false);
try Logic.Challenge().LoadPeakLineup(req.peak_id);
}
var lineup_manager = LineupManager.ChallengeLineupManager.init(allocator);
var lineup_info = try lineup_manager.createPeakLineup(Logic.Challenge().GetAvatarIDs());
_ = &lineup_info;
var challenge_manager = ChallengeManager.ChallengeManager.init(allocator);
var cur_challenge_info = try challenge_manager.createChallengePeak(req.peak_id, req.boss_buff_id);
_ = &cur_challenge_info;
const ids = Logic.Challenge().GetPeakSceneIDs();
var scene_challenge_manager = SceneManager.ChallengeSceneManager.init(allocator);
var scene_info = try scene_challenge_manager.createPeakScene(
Logic.Challenge().GetAvatarIDs(),
ids[0],
ids[1],
ids[2],
ids[3],
ids[4],
ids[5],
ids[6],
);
_ = &scene_info;
try session.send(CmdID.CmdEnterSceneByServerScNotify, protocol.EnterSceneByServerScNotify{
.reason = protocol.EnterSceneReason.ENTER_SCENE_REASON_NONE,
.lineup = lineup_info,
.scene = scene_info,
});
Logic.Challenge().SetChallenge();
Logic.Challenge().GetCurSceneStatus();
const anchor_motion = SceneManager.ChallengeSceneManager.getAnchorMotion(scene_info.entry_id);
if (anchor_motion) |motion| {
for (scene_info.entity_group_list.items) |*group| {
for (group.entity_list.items) |*entity| {
if (entity.entity) |ent| if (ent == .actor) {
try session.send(
CmdID.CmdSceneEntityMoveScNotify,
protocol.SceneEntityMoveScNotify{
.entity_id = entity.entity_id,
.entry_id = scene_info.entry_id,
.motion = motion,
},
);
};
}
}
}
try session.send(CmdID.CmdStartChallengePeakScRsp, rsp);
}
pub fn onSetChallengePeakBossHardMode(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.SetChallengePeakBossHardModeCsReq, allocator);
defer req.deinit();
var rsp = protocol.SetChallengePeakBossHardModeScRsp.init(allocator);
rsp.retcode = 0;
rsp.is_hard_mode = req.is_hard_mode;
rsp.peak_group_id = req.peak_group_id;
Logic.Challenge().SetChallengePeakHard(req.is_hard_mode);
try session.send(CmdID.CmdSetChallengePeakBossHardModeScRsp, rsp);
}
pub fn onGetFriendBattleRecordDetail(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.GetFriendBattleRecordDetailCsReq, allocator);
defer req.deinit();
var rsp = protocol.GetFriendBattleRecordDetailScRsp.init(allocator);
rsp.retcode = 0;
rsp.uid = req.uid;
var record_list = ArrayList(protocol.ChallengeAvatarInfo).init(allocator);
try record_list.appendSlice(&[_]protocol.ChallengeAvatarInfo{
.{ .level = 80, .index = 0, .id = 1321, .avatar_type = protocol.AvatarType.AVATAR_UPGRADE_AVAILABLE_TYPE },
});
const BossType = @TypeOf(peak_boss.challenge_peak_boss_config.items[0]);
var boss_map = std.AutoHashMap(u32, *const BossType).init(allocator);
defer boss_map.deinit();
for (peak_boss.challenge_peak_boss_config.items) |*boss| {
try boss_map.put(boss.id, boss);
}
for (peak_group.challenge_peak_group.items) |group| {
if (boss_map.get(group.boss_level_id)) |boss| {
var peak_record = protocol.EJHAMBODINL.init(allocator);
peak_record.group_id = group.id;
peak_record.OFAOOKEDCGB = .{
.buff_id = boss.buff_list.items[0],
.peak_id = group.boss_level_id,
.DCBDFGCJPJD = true,
.EOJFDBCGCOP = true,
.CMPNIBOCLNE = std.ArrayList(u32).init(allocator),
.lineup = .{ .avatar_list = record_list },
};
try rsp.PLCGIEMKGNA.append(peak_record);
}
}
try session.send(CmdID.CmdGetFriendBattleRecordDetailScRsp, rsp);
}

View File

@@ -0,0 +1,128 @@
const std = @import("std");
const protocol = @import("protocol");
const Session = @import("../Session.zig");
const Packet = @import("../Packet.zig");
const commandhandler = @import("../command.zig");
const ArrayList = std.ArrayList;
const Allocator = std.mem.Allocator;
const CmdID = protocol.CmdID;
const B64Decoder = std.base64.standard.Decoder;
const EmojiList = [_]u32{};
pub fn onGetFriendListInfo(session: *Session, _: *const Packet, allocator: Allocator) !void {
var rsp = protocol.GetFriendListInfoScRsp.init(allocator);
rsp.retcode = 0;
var assist_list = ArrayList(protocol.AssistSimpleInfo).init(allocator);
try assist_list.appendSlice(&[_]protocol.AssistSimpleInfo{
.{ .pos = 0, .level = 80, .avatar_id = 1410, .dressed_skin_id = 0 },
.{ .pos = 1, .level = 80, .avatar_id = 1415, .dressed_skin_id = 0 },
.{ .pos = 2, .level = 80, .avatar_id = 1321, .dressed_skin_id = 0 },
});
var friend = protocol.FriendSimpleInfo.init(allocator);
friend.playing_state = .PLAYING_CHALLENGE_PEAK;
friend.create_time = 0; //timestamp
friend.remark_name = .{ .Const = "ReversedRooms" }; //friend_custom_nickname
friend.is_marked = true;
friend.player_info = protocol.PlayerSimpleInfo{
.personal_card = 253001,
.signature = .{ .Const = "https://discord.gg/reversedrooms" },
.nickname = .{ .Const = "DahliaSR" },
.level = 70,
.uid = 2000,
.head_icon = 200140,
.head_frame_info = .{
.head_frame_expire_time = 4294967295,
.head_frame_item_id = 226004,
},
.chat_bubble_id = 220008,
.assist_simple_info_list = assist_list,
.platform = protocol.PlatformType.ANDROID,
.online_status = protocol.FriendOnlineStatus.FRIEND_ONLINE_STATUS_ONLINE,
};
try rsp.friend_list.append(friend);
try session.send(CmdID.CmdGetFriendListInfoScRsp, rsp);
}
pub fn onChatEmojiList(session: *Session, _: *const Packet, allocator: Allocator) !void {
var rsp = protocol.GetChatEmojiListScRsp.init(allocator);
rsp.retcode = 0;
try rsp.chat_emoji_list.appendSlice(&EmojiList);
try session.send(CmdID.CmdGetChatEmojiListScRsp, rsp);
}
pub fn onPrivateChatHistory(session: *Session, _: *const Packet, allocator: Allocator) !void {
var rsp = protocol.GetPrivateChatHistoryScRsp.init(allocator);
rsp.retcode = 0;
rsp.target_side = 1;
rsp.contact_side = 2000;
try rsp.chat_message_list.appendSlice(&[_]protocol.ChatMessageData{
.{
.content = .{ .Const = "Use https://relic-builder.vercel.app/ to setup config" },
.message_type = .MSG_TYPE_CUSTOM_TEXT,
.create_time = 0,
.sender_id = 2000,
},
.{
.content = .{ .Const = "/help for command list" },
.message_type = .MSG_TYPE_CUSTOM_TEXT,
.create_time = 0,
.sender_id = 2000,
},
.{
.content = .{ .Const = "to use command, use '/' first" },
.message_type = .MSG_TYPE_CUSTOM_TEXT,
.create_time = 0,
.sender_id = 2000,
},
.{
.extra_id = 122004,
.message_type = .MSG_TYPE_EMOJI,
.create_time = 0,
.sender_id = 2000,
},
});
try session.send(CmdID.CmdGetPrivateChatHistoryScRsp, rsp);
}
pub fn onSendMsg(session: *Session, packet: *const Packet, allocator: Allocator) !void {
std.debug.print("Received packet: {any}\n", .{packet});
const req = protocol.SendMsgCsReq.init(allocator);
defer req.deinit();
std.debug.print("Decoded request: {any}\n", .{req});
std.debug.print("Raw packet body: {any}\n", .{packet.body});
const msg_text = switch (req.message_text) {
.Empty => "",
.Owned => |owned| owned.str,
.Const => |const_str| const_str,
};
var msg_text2: []const u8 = "";
if (packet.body.len > 9 and packet.body[4] == 47) {
msg_text2 = packet.body[4 .. packet.body.len - 6];
}
std.debug.print("Manually extracted message text: '{s}'\n", .{msg_text2});
std.debug.print("Message Text 1: {any}\n", .{msg_text});
if (msg_text2.len > 0) {
if (std.mem.indexOf(u8, msg_text2, "/") != null) {
std.debug.print("Message contains a '/'\n", .{});
try commandhandler.handleCommand(session, msg_text2, allocator);
} else {
std.debug.print("Message does not contain a '/'\n", .{});
try commandhandler.sendMessage(session, msg_text2, allocator);
}
} else {
std.debug.print("Empty message received\n", .{});
}
var rsp = protocol.SendMsgScRsp.init(allocator);
rsp.retcode = 0;
try session.send(CmdID.CmdSendMsgScRsp, rsp);
}

View File

@@ -0,0 +1,39 @@
const std = @import("std");
const protocol = @import("protocol");
const Session = @import("../Session.zig");
const Packet = @import("../Packet.zig");
const ConfigManager = @import("../manager/config_mgr.zig");
const ArrayList = std.ArrayList;
const Allocator = std.mem.Allocator;
const CmdID = protocol.CmdID;
const activity_config = &ConfigManager.global_game_config_cache.activity_config;
pub fn onGetActivity(session: *Session, _: *const Packet, allocator: Allocator) !void {
var rsp = protocol.GetActivityScheduleConfigScRsp.init(allocator);
var activ_list = protocol.ActivityScheduleData.init(allocator);
//challenge mode pannel : 2100101
for (activity_config.activity_config.items) |activityConf| {
if (activityConf.panel_id != 30002) {
activ_list.panel_id = activityConf.activity_id;
for (activityConf.activity_module_list.items) |id| {
activ_list.begin_time = 1664308800;
activ_list.end_time = 4294967295;
activ_list.activity_id = id;
try rsp.schedule_data.append(activ_list);
}
}
}
rsp.retcode = 0;
try session.send(CmdID.CmdGetActivityScheduleConfigScRsp, rsp);
}
pub fn onUpdateServerPrefsData(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.UpdateServerPrefsDataCsReq, allocator);
defer req.deinit();
try session.send(CmdID.CmdUpdateServerPrefsDataScRsp, protocol.UpdateServerPrefsDataScRsp{
.retcode = 0,
.server_prefs_id = req.server_prefs.?.server_prefs_id,
});
}

View File

@@ -0,0 +1,217 @@
const std = @import("std");
const protocol = @import("protocol");
const Session = @import("../Session.zig");
const Packet = @import("../Packet.zig");
const Data = @import("../data.zig");
const Logic = @import("../utils/logic.zig");
const ArrayList = std.ArrayList;
const Allocator = std.mem.Allocator;
const CmdID = protocol.CmdID;
pub fn onGetGachaInfo(session: *Session, _: *const Packet, allocator: Allocator) !void {
var info = ArrayList(protocol.GachaCeilingAvatar).init(allocator);
for (Logic.Banner().GetStandardBanner()) |id| {
try info.appendSlice(&[_]protocol.GachaCeilingAvatar{
.{ .repeated_cnt = 300, .avatar_id = id },
});
}
var gacha_info = protocol.GachaInfo.init(allocator);
gacha_info.begin_time = 0;
gacha_info.end_time = 2524608000;
gacha_info.gacha_ceiling = .{
.avatar_list = info,
.is_claimed = false,
.ceiling_num = 200,
};
gacha_info.EOHEEGMKMOE = 1;
gacha_info.DKHDCPOOHKA = 3;
gacha_info.gacha_id = 1001; // standard banner
var rsp = protocol.GetGachaInfoScRsp.init(allocator);
rsp.retcode = 0;
rsp.FMCPOBKEMAG = 20;
rsp.FMCPOBKEMAG = 20;
rsp.APOCGAOJOJF = 900;
rsp.gacha_random = 0;
try rsp.gacha_info_list.append(gacha_info);
try session.send(CmdID.CmdGetGachaInfoScRsp, rsp);
}
pub fn onBuyGoods(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.BuyGoodsCsReq, allocator);
defer req.deinit();
var rsp = protocol.BuyGoodsScRsp.init(allocator);
var item = ArrayList(protocol.Item).init(allocator);
try item.appendSlice(&[_]protocol.Item{.{
.item_id = 101,
.num = 100,
}});
rsp.retcode = 0;
rsp.goods_id = req.goods_id;
rsp.goods_buy_times = req.goods_num;
rsp.shop_id = 0;
rsp.return_item_list = .{ .item_list = item };
try session.send(CmdID.CmdBuyGoodsScRsp, rsp);
}
pub fn onGetShopList(session: *Session, _: *const Packet, allocator: Allocator) !void {
var rsp = protocol.GetShopListScRsp.init(allocator);
var shop = ArrayList(protocol.Shop).init(allocator);
var goods = ArrayList(protocol.Goods).init(allocator);
try shop.appendSlice(&[_]protocol.Shop{.{
.shop_id = 1000,
.goods_list = goods,
}});
try goods.appendSlice(&[_]protocol.Goods{.{
.goods_id = 101001,
.item_id = 101,
.buy_times = 0,
}});
rsp.retcode = 0;
rsp.shop_type = 101;
rsp.shop_list = shop;
try session.send(CmdID.CmdGetShopListScRsp, rsp);
}
pub fn onExchangeHcoin(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.ExchangeHcoinCsReq, allocator);
defer req.deinit();
var rsp = protocol.ExchangeHcoinScRsp.init(allocator);
rsp.num = req.num;
rsp.retcode = 0;
try session.send(CmdID.CmdExchangeHcoinScRsp, rsp);
}
var five_star_pity: u32 = 0;
var four_star_pity: u32 = 0;
var guaranteed_five_star_rate_up: bool = false;
var guaranteed_four_star_rate_up: bool = false;
var avatar_list_cached: ?std.ArrayList(u32) = null;
var lightcone_list_3_cached: ?std.ArrayList(u32) = null;
var lightcone_list_4_cached: ?std.ArrayList(u32) = null;
fn pow(base: f64, exp: f64) f64 {
return @exp(exp * @log(base));
}
fn getFiveStarRate(gacha_count: u32) f64 {
if (gacha_count < 21) {
return 0.02;
}
if (gacha_count < 72) {
return 0.008;
}
const excess_pulls = @as(f64, @floatFromInt(gacha_count - 71));
return 0.008 + (1.0 - 0.008) * pow(excess_pulls / 18.0, 2.8);
}
fn getFourStarRate(gacha_count: u32) f64 {
if (gacha_count < 6) {
return 0.055;
}
const excess_pulls = @as(f64, @floatFromInt(gacha_count - 5));
return 0.055 + (1.0 - 0.055) * pow(excess_pulls / 3.5, 2.2);
}
fn pickRandomId(banner: []const u32) u32 {
const idx = std.crypto.random.intRangeLessThan(usize, 0, banner.len);
return banner[idx];
}
pub fn onDoGacha(session: *Session, packet: *const Packet, allocator: std.mem.Allocator) !void {
const req = try packet.getProto(protocol.DoGachaCsReq, allocator);
defer req.deinit();
var rsp = protocol.DoGachaScRsp.init(allocator);
const rnd = std.crypto.random;
var selected_ids = std.ArrayList(u32).init(allocator);
defer selected_ids.deinit();
var got_four_star = false;
for (0..req.gacha_num) |_| {
const five_star_rate = getFiveStarRate(five_star_pity);
const four_star_rate = getFourStarRate(four_star_pity);
const random_value = rnd.float(f64);
var selected_banner: []const u32 = &Data.LightconeList_3;
var is_five_star = false;
var is_four_star = false;
if (random_value < five_star_rate or five_star_pity == 89) {
is_five_star = true;
five_star_pity = 0;
if (guaranteed_five_star_rate_up) {
selected_banner = Logic.Banner().GetRateUp();
guaranteed_five_star_rate_up = false;
} else {
if (rnd.boolean()) {
selected_banner = Logic.Banner().GetRateUp();
} else {
selected_banner = Logic.Banner().GetStandardBanner();
guaranteed_five_star_rate_up = true;
}
}
} else if (four_star_pity == 9 or random_value < (five_star_rate + four_star_rate)) {
is_four_star = true;
four_star_pity = 0;
got_four_star = true;
if (guaranteed_four_star_rate_up or rnd.float(f64) < 0.70) {
selected_banner = Logic.Banner().GetRateUpFourStar();
guaranteed_four_star_rate_up = false;
} else {
if (rnd.boolean()) {
selected_banner = Data.AvatarList;
} else {
selected_banner = &Data.LightconeList_4;
}
guaranteed_four_star_rate_up = true;
}
} else {
four_star_pity += 1;
}
five_star_pity += 1;
try selected_ids.append(pickRandomId(selected_banner));
}
if (req.gacha_num > 1 and !got_four_star) {
selected_ids.items[
std.crypto.random.intRangeLessThan(usize, 0, selected_ids.items.len)
] = pickRandomId(Logic.Banner().GetRateUpFourStar());
}
for (selected_ids.items) |id| {
var gacha_item = protocol.GachaItem.init(allocator);
gacha_item.gacha_item = .{ .item_id = id };
gacha_item.is_new = false;
var back_item = std.ArrayList(protocol.Item).init(allocator);
var transfer_item = std.ArrayList(protocol.Item).init(allocator);
if (id < 10000) {
if (Logic.inlist(id, Logic.Banner().GetRateUp()) or Logic.inlist(id, Logic.Banner().GetStandardBanner())) {
try transfer_item.appendSlice(&[_]protocol.Item{
.{ .item_id = id + 10000, .num = 1 },
.{ .item_id = 252, .num = 20 },
});
} else {
try transfer_item.append(.{ .item_id = 252, .num = 20 });
}
}
try back_item.append(.{ .item_id = 252, .num = 20 });
gacha_item.transfer_item_list = .{ .item_list = transfer_item };
gacha_item.token_item = .{ .item_list = back_item };
try rsp.gacha_item_list.append(gacha_item);
}
rsp.gacha_num = req.gacha_num;
rsp.gacha_id = req.gacha_id;
rsp.ceiling_num = 200;
rsp.EOHEEGMKMOE = 1;
rsp.FMCPOBKEMAG = 20;
rsp.DKHDCPOOHKA = 3;
rsp.retcode = 0;
std.debug.print("FIVE STAR PITY: {}, (RATE: {d:.4}%)\n", .{ five_star_pity, getFiveStarRate(five_star_pity) * 100.0 });
std.debug.print("FOUR STAR PITY: {}, (RATE: {d:.4}%)\n", .{ four_star_pity, getFourStarRate(four_star_pity) * 100.0 });
try session.send(protocol.CmdID.CmdDoGachaScRsp, rsp);
}

View File

@@ -0,0 +1,48 @@
const std = @import("std");
const protocol = @import("protocol");
const Session = @import("../Session.zig");
const Packet = @import("../Packet.zig");
const Data = @import("../data.zig");
const LineupManager = @import("../manager/lineup_mgr.zig");
const Sync = @import("../commands/sync.zig");
const AvatarManager = @import("../manager/avatar_mgr.zig");
const ConfigManager = @import("../manager/config_mgr.zig");
const ArrayList = std.ArrayList;
const Allocator = std.mem.Allocator;
const CmdID = protocol.CmdID;
const config = &ConfigManager.global_game_config_cache.game_config;
pub fn onGetBag(session: *Session, _: *const Packet, allocator: Allocator) !void {
var rsp = protocol.GetBagScRsp.init(allocator);
rsp.equipment_list = ArrayList(protocol.Equipment).init(allocator);
rsp.relic_list = ArrayList(protocol.Relic).init(allocator);
for (Data.ItemList) |tid| {
try rsp.material_list.append(.{ .tid = tid, .num = 100 });
}
for (config.avatar_config.items) |avatarConf| {
const lc = try AvatarManager.createEquipment(avatarConf.lightcone, avatarConf.id);
try rsp.equipment_list.append(lc);
for (avatarConf.relics.items) |input| {
const r = try AvatarManager.createRelic(allocator, input, avatarConf.id);
try rsp.relic_list.append(r);
}
}
try session.send(CmdID.CmdGetBagScRsp, rsp);
}
pub fn onUseItem(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.UseItemCsReq, allocator);
defer req.deinit();
var rsp = protocol.UseItemScRsp.init(allocator);
rsp.use_item_id = req.use_item_id;
rsp.use_item_count = req.use_item_count;
rsp.retcode = 0;
var sync = protocol.SyncLineupNotify.init(allocator);
var lineup_mgr = LineupManager.LineupManager.init(allocator);
const lineup = try lineup_mgr.createLineup();
sync.lineup = lineup;
try session.send(CmdID.CmdSyncLineupNotify, sync);
try session.send(CmdID.CmdUseItemScRsp, rsp);
}

View File

@@ -0,0 +1,60 @@
const std = @import("std");
const protocol = @import("protocol");
const Session = @import("../Session.zig");
const Packet = @import("../Packet.zig");
const LineupManager = @import("../manager/lineup_mgr.zig");
const Allocator = std.mem.Allocator;
const CmdID = protocol.CmdID;
pub var leader_slot: u32 = 0;
pub fn onGetCurLineupData(session: *Session, _: *const Packet, allocator: Allocator) !void {
var lineup_mgr = LineupManager.LineupManager.init(allocator);
const lineup = try lineup_mgr.createLineup();
try session.send(CmdID.CmdGetCurLineupDataScRsp, protocol.GetCurLineupDataScRsp{
.retcode = 0,
.lineup = lineup,
});
}
pub fn onChangeLineupLeader(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.ChangeLineupLeaderCsReq, allocator);
defer req.deinit();
leader_slot = req.slot;
try session.send(CmdID.CmdChangeLineupLeaderScRsp, protocol.ChangeLineupLeaderScRsp{
.slot = req.slot,
.retcode = 0,
});
}
pub fn onReplaceLineup(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.ReplaceLineupCsReq, allocator);
defer req.deinit();
var ids = std.ArrayList(u32).init(allocator);
defer ids.deinit();
for (req.lineup_slot_list.items) |ok| {
try ids.append(ok.id);
}
const lineup = try LineupManager.buildLineup(allocator, ids.items, null);
var rsp = protocol.SyncLineupNotify.init(allocator);
rsp.lineup = lineup;
try session.send(CmdID.CmdSyncLineupNotify, rsp);
try session.send(CmdID.CmdReplaceLineupScRsp, protocol.ReplaceLineupScRsp{
.retcode = 0,
});
}
pub fn onSetLineupName(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.SetLineupNameCsReq, allocator);
defer req.deinit();
try session.send(CmdID.CmdSetLineupNameScRsp, protocol.SetLineupNameScRsp{
.index = req.index,
.name = req.name,
.retcode = 0,
});
}

View File

@@ -0,0 +1,108 @@
const std = @import("std");
const protocol = @import("protocol");
const Session = @import("../Session.zig");
const Packet = @import("../Packet.zig");
const Data = @import("../data.zig");
const Allocator = std.mem.Allocator;
const ArrayList = std.ArrayList;
const CmdID = protocol.CmdID;
const content = [_]u32{
200001, 200002, 200003, 200004, 200005, 200006, 200007,
150017, 150015, 150021, 150018, 130011, 130012, 130013,
150025, 140006, 150026, 130014, 150034, 150029, 150035,
150041, 150039, 150045, 150057, 150042, 150067, 150064,
150063, 150024, 171002, 150068,
};
pub fn onPlayerGetToken(session: *Session, _: *const Packet, allocator: Allocator) !void {
var rsp = protocol.PlayerGetTokenScRsp.init(allocator);
rsp.retcode = 0;
rsp.uid = 1;
try session.send(CmdID.CmdPlayerGetTokenScRsp, rsp);
}
pub fn onPlayerLogin(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.PlayerLoginCsReq, allocator);
defer req.deinit();
var basic_info = protocol.PlayerBasicInfo.init(allocator);
basic_info.stamina = 300;
basic_info.level = 70;
basic_info.nickname = .{ .Const = "ReversedRooms" };
basic_info.world_level = 6;
basic_info.mcoin = 99999990;
basic_info.hcoin = 99999990; //Jade
basic_info.scoin = 99999990; //Money
var rsp = protocol.PlayerLoginScRsp.init(allocator);
rsp.retcode = 0;
rsp.login_random = req.login_random;
rsp.stamina = 300;
rsp.basic_info = basic_info;
try session.send(CmdID.CmdPlayerLoginScRsp, rsp);
}
pub fn onPlayerLoginFinish(session: *Session, _: *const Packet, allocator: Allocator) !void {
var package_data = protocol.ContentPackageData.init(allocator);
package_data.cur_content_id = 0;
for (content) |id| {
try package_data.content_package_list.append(protocol.ContentPackageInfo{
.content_id = id,
.status = protocol.ContentPackageStatus.ContentPackageStatus_Finished,
});
}
try session.send(CmdID.CmdContentPackageSyncDataScNotify, protocol.ContentPackageSyncDataScNotify{
.data = package_data,
});
try session.send(CmdID.CmdPlayerLoginFinishScRsp, protocol.PlayerLoginFinishScRsp{
.retcode = 0,
});
}
pub fn onContentPackageGetData(session: *Session, _: *const Packet, allocator: Allocator) !void {
var rsp = protocol.ContentPackageGetDataScRsp.init(allocator);
rsp.retcode = 0;
var package_data = protocol.ContentPackageData.init(allocator);
package_data.cur_content_id = 0;
for (content) |id| {
try package_data.content_package_list.append(protocol.ContentPackageInfo{
.content_id = id,
.status = protocol.ContentPackageStatus.ContentPackageStatus_Finished,
});
}
try session.send(CmdID.CmdContentPackageGetDataScRsp, rsp);
}
pub fn onSetClientPaused(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.SetClientPausedCsReq, allocator);
defer req.deinit();
try session.send(CmdID.CmdSetClientPausedScRsp, protocol.SetClientPausedScRsp{
.retcode = 0,
.paused = req.paused,
});
}
pub fn onGetArchiveData(session: *Session, _: *const Packet, allocator: Allocator) !void {
var data = protocol.ArchiveData.init(allocator);
try data.archive_avatar_id_list.append(1321);
try data.archive_missing_equipment_id_list.append(23000);
try session.send(CmdID.CmdGetArchiveDataScRsp, protocol.GetArchiveDataScRsp{
.retcode = 0,
.archive_data = data,
});
}
pub fn onGetUpdatedArchiveData(session: *Session, _: *const Packet, allocator: Allocator) !void {
var data = protocol.ArchiveData.init(allocator);
try data.archive_avatar_id_list.append(1321);
try data.archive_missing_equipment_id_list.append(23000);
try session.send(CmdID.CmdGetUpdatedArchiveDataScRsp, protocol.GetUpdatedArchiveDataScRsp{
.retcode = 0,
.archive_data = data,
});
}

View File

@@ -0,0 +1,49 @@
const std = @import("std");
const protocol = @import("protocol");
const Session = @import("../Session.zig");
const Packet = @import("../Packet.zig");
const ArrayList = std.ArrayList;
const Allocator = std.mem.Allocator;
const CmdID = protocol.CmdID;
const B64Decoder = std.base64.standard.Decoder;
pub fn onGetMail(session: *Session, _: *const Packet, allocator: Allocator) !void {
var rsp = protocol.GetMailScRsp.init(allocator);
var item_attachment = ArrayList(protocol.Item).init(allocator);
try item_attachment.appendSlice(&[_]protocol.Item{
.{ .item_id = 1321, .num = 1 },
});
var mail = protocol.ClientMail.init(allocator);
mail.sender = .{ .Const = "March 7th" };
mail.title = .{ .Const = "Readme" };
mail.is_read = false;
mail.id = 1;
mail.content = .{ .Const = "DahliaSR (0.14.1) is a free and open-source sofware\nJoin our discord: https://discord.gg/reversedrooms\nUse https://relic-builder.vercel.app to setup relic :Đ" };
mail.time = 1723334400;
mail.expire_time = 17186330890;
mail.mail_type = protocol.MailType.MAIL_TYPE_STAR;
mail.attachment = .{ .item_list = item_attachment };
var mail_list = ArrayList(protocol.ClientMail).init(allocator);
try mail_list.append(mail);
rsp.total_num = 1;
rsp.is_end = true;
rsp.start = 0;
rsp.retcode = 0;
rsp.mail_list = mail_list;
try session.send(CmdID.CmdGetMailScRsp, rsp);
}
pub fn onTakeMailAttachment(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.TakeMailAttachmentCsReq, allocator);
defer req.deinit();
var rsp = protocol.TakeMailAttachmentScRsp.init(allocator);
rsp.retcode = 0;
rsp.succ_mail_id_list = req.mail_id_list;
try session.send(CmdID.CmdTakeMailAttachmentScRsp, rsp);
}

View File

@@ -0,0 +1,34 @@
const std = @import("std");
const protocol = @import("protocol");
const Session = @import("../Session.zig");
const Packet = @import("../Packet.zig");
const Allocator = std.mem.Allocator;
const CmdID = protocol.CmdID;
const script = blk: {
const encoded = "ICAgICAgICBsb2NhbCBmdW5jdGlvbiBzZXRUZXh0Q29tcG9uZW50KHBhdGgsIG5ld1RleHQpDQogICAgICAgICAgICBsb2NhbCBvYmogPSBDUy5Vbml0eUVuZ2luZS5HYW1lT2JqZWN0LkZpbmQocGF0aCkNCiAgICAgICAgICAgIGlmIG9iaiB0aGVuDQogICAgICAgICAgICAgICAgbG9jYWwgdGV4dENvbXBvbmVudCA9IG9iajpHZXRDb21wb25lbnRJbkNoaWxkcmVuKHR5cGVvZihDUy5SUEcuQ2xpZW50LkxvY2FsaXplZFRleHQpKQ0KICAgICAgICAgICAgICAgIGlmIHRleHRDb21wb25lbnQgdGhlbg0KICAgICAgICAgICAgICAgICAgICB0ZXh0Q29tcG9uZW50LnRleHQgPSBuZXdUZXh0DQogICAgICAgICAgICAgICAgZW5kDQogICAgICAgICAgICBlbmQNCiAgICAgICAgZW5kDQogICAgICAgIA0KICAgICAgICBzZXRUZXh0Q29tcG9uZW50KCJVSVJvb3QvQWJvdmVEaWFsb2cvQmV0YUhpbnREaWFsb2coQ2xvbmUpIiwgIjxjb2xvcj0jNEQ5M0Q5PkRhaGxpYVNSICgwLjE0LjEpIGlzIGEgZnJlZSBhbmQgb3BlbiBzb3VyY2Ugc29mdHdhcmUuPC9jb2xvcj4iKQ0KICAgICAgICBzZXRUZXh0Q29tcG9uZW50KCJWZXJzaW9uVGV4dCIsICI8Y29sb3I9IzREOTNEOT5WaXNpdCBkaXNjb3JkLmdnL3JldmVyc2Vkcm9vbXMgZm9yIG1vcmUgaW5mbyE8L2NvbG9yPiIpDQo=";
const decoded_len = std.base64.standard.Decoder.calcSizeForSlice(encoded) catch unreachable;
var decoded: [1024]u8 = undefined;
_ = std.base64.standard.Decoder.decode(decoded[0..decoded_len], encoded) catch unreachable;
break :blk decoded[0..decoded_len].*;
};
pub fn onPlayerHeartBeat(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.PlayerHeartBeatCsReq, allocator);
defer req.deinit();
const dest_buf = try allocator.dupe(u8, &script);
const managed_str = protocol.ManagedString.move(dest_buf, allocator);
const download_data = protocol.ClientDownloadData{
.version = 51,
.time = @intCast(std.time.milliTimestamp()),
.data = managed_str,
};
try session.send(CmdID.CmdPlayerHeartBeatScRsp, protocol.PlayerHeartBeatScRsp{
.retcode = 0,
.client_time_ms = req.client_time_ms,
.server_time_ms = @intCast(std.time.milliTimestamp()),
.download_data = download_data,
});
}

View File

@@ -0,0 +1,95 @@
const std = @import("std");
const protocol = @import("protocol");
const Session = @import("../Session.zig");
const Packet = @import("../Packet.zig");
const Data = @import("../data.zig");
const ConfigManager = @import("../manager/config_mgr.zig");
const ArrayList = std.ArrayList;
const Allocator = std.mem.Allocator;
const CmdID = protocol.CmdID;
pub fn onGetMissionStatus(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.GetMissionStatusCsReq, allocator);
defer req.deinit();
var rsp = protocol.GetMissionStatusScRsp.init(allocator);
const main_mission_config = &ConfigManager.global_game_config_cache.main_mission_config;
rsp.retcode = 0;
for (req.sub_mission_id_list.items) |id| {
try rsp.sub_mission_status_list.append(protocol.Mission{ .id = id, .status = protocol.MissionStatus.MISSION_FINISH, .progress = 1 });
}
for (main_mission_config.main_mission_config.items) |main_missionConf| {
try rsp.finished_main_mission_id_list.append(main_missionConf.main_mission_id);
try rsp.curversion_finished_main_mission_id_list.append(main_missionConf.main_mission_id);
}
try session.send(CmdID.CmdGetMissionStatusScRsp, rsp);
}
pub fn onGetTutorialGuideStatus(session: *Session, _: *const Packet, allocator: Allocator) !void {
var rsp = protocol.GetTutorialGuideScRsp.init(allocator);
const tutorial_guide_config = &ConfigManager.global_game_config_cache.tutorial_guide_config;
rsp.retcode = 0;
for (tutorial_guide_config.tutorial_guide_config.items) |guideConf| {
try rsp.tutorial_guide_list.append(protocol.TutorialGuide{ .id = guideConf.guide_group_id, .status = protocol.TutorialStatus.TUTORIAL_FINISH });
}
try session.send(CmdID.CmdGetTutorialGuideScRsp, rsp);
}
pub fn onGetTutorialStatus(session: *Session, _: *const Packet, allocator: Allocator) !void {
var rsp = protocol.GetTutorialScRsp.init(allocator);
const tutorial_guide_config = &ConfigManager.global_game_config_cache.tutorial_config;
rsp.retcode = 0;
for (tutorial_guide_config.tutorial_config.items) |tutorialConf| {
try rsp.tutorial_list.append(protocol.Tutorial{ .id = tutorialConf.tutorial_id, .status = protocol.TutorialStatus.TUTORIAL_FINISH });
}
try session.send(CmdID.CmdGetTutorialScRsp, rsp);
}
pub fn onFinishTalkMission(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.FinishTalkMissionCsReq, allocator);
defer req.deinit();
var rsp = protocol.FinishTalkMissionScRsp.init(allocator);
rsp.sub_mission_id = req.sub_mission_id;
rsp.custom_value_list = req.custom_value_list;
rsp.talk_str = req.talk_str;
rsp.retcode = 0;
try session.send(CmdID.CmdFinishTalkMissionScRsp, rsp);
}
pub fn onGetQuestData(session: *Session, _: *const Packet, allocator: Allocator) !void {
var rsp = protocol.GetQuestDataScRsp.init(allocator);
const quest_config = &ConfigManager.global_game_config_cache.quest_config;
for (quest_config.quest_config.items) |id| {
var quest = protocol.Quest.init(allocator);
quest.id = id.quest_id;
quest.progress = 200;
switch (id.quest_id) {
2200503, 2200504, 2200505, 2200506 => quest.status = protocol.QuestStatus.QUEST_FINISH,
else => quest.status = protocol.QuestStatus.QUEST_CLOSE,
}
try rsp.quest_list.append(quest);
}
rsp.retcode = 0;
try session.send(CmdID.CmdGetQuestDataScRsp, rsp);
}
// added this to auto detect new tutorial guide id
pub fn onUnlockTutorialGuide(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.UnlockTutorialGuideCsReq, allocator);
defer req.deinit();
var rsp = protocol.UnlockTutorialGuideScRsp.init(allocator);
rsp.retcode = 0;
std.debug.print("UNLOCK TUTORIAL GUIDE ID: {}\n", .{req.group_id});
try session.send(CmdID.CmdUnlockTutorialGuideScRsp, rsp);
}
// added this to auto detect new tutorial id
pub fn onUnlockTutorial(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.UnlockTutorialCsReq, allocator);
defer req.deinit();
var rsp = protocol.UnlockTutorialScRsp.init(allocator);
rsp.retcode = 0;
std.debug.print("UNLOCK TUTORIAL ID: {}\n", .{req.tutorial_id});
try session.send(CmdID.CmdUnlockTutorialScRsp, rsp);
}

View File

@@ -0,0 +1,40 @@
const std = @import("std");
const protocol = @import("protocol");
const Session = @import("../Session.zig");
const Packet = @import("../Packet.zig");
const Allocator = std.mem.Allocator;
const CmdID = protocol.CmdID;
const OwnedPet = [_]u32{ 251001, 251002, 251003 };
pub fn onGetPetData(session: *Session, _: *const Packet, allocator: Allocator) !void {
var rsp = protocol.GetPetDataScRsp.init(allocator);
rsp.retcode = 0;
rsp.cur_pet_id = 1003;
try rsp.unlocked_pet_id.appendSlice(&OwnedPet);
try session.send(CmdID.CmdGetPetDataScRsp, rsp);
}
pub fn onRecallPet(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.RecallPetCsReq, allocator);
defer req.deinit();
std.debug.print("REQUEST RECALL PET: {}\n", .{req.summoned_pet_id});
try session.send(CmdID.CmdRecallPetScRsp, protocol.RecallPetScRsp{
.retcode = 0,
.cur_pet_id = req.summoned_pet_id,
.select_pet_id = req.summoned_pet_id,
});
}
pub fn onSummonPet(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.SummonPetCsReq, allocator);
defer req.deinit();
std.debug.print("REQUEST SUMMON PET: {}\n", .{req.summoned_pet_id});
try session.send(CmdID.CmdCurPetChangedScNotify, protocol.CurPetChangedScNotify{
.cur_pet_id = req.summoned_pet_id,
});
try session.send(CmdID.CmdSummonPetScRsp, protocol.SummonPetScRsp{
.retcode = 0,
.cur_pet_id = req.summoned_pet_id,
.select_pet_id = req.summoned_pet_id,
});
}

View File

@@ -0,0 +1,172 @@
const std = @import("std");
const protocol = @import("protocol");
const Session = @import("../Session.zig");
const Packet = @import("../Packet.zig");
const Data = @import("../data.zig");
const ConfigManager = @import("../manager/config_mgr.zig");
const Uid = @import("../utils/uid.zig");
const ArrayList = std.ArrayList;
const Allocator = std.mem.Allocator;
const CmdID = protocol.CmdID;
// can change these id here for initial display
const SupportAvatar = [_]u32{
1407, 1403, 1402,
};
const ListAvatar = [_]u32{
1401, 1001, 1225, 1317, 1222,
};
pub fn onGetPhoneData(session: *Session, _: *const Packet, allocator: Allocator) !void {
var rsp = protocol.GetPhoneDataScRsp.init(allocator);
rsp.retcode = 0;
rsp.cur_chat_bubble = 0;
rsp.cur_phone_theme = 0;
rsp.cur_phone_case = 254001;
try rsp.owned_chat_bubbles.appendSlice(&Data.OwnedChatBubbles);
try rsp.owned_phone_themes.appendSlice(&Data.OwnedPhoneThemes);
try rsp.owned_phone_cases.appendSlice(&Data.OwnedPhoneCases);
try session.send(CmdID.CmdGetPhoneDataScRsp, rsp);
}
pub fn onSelectPhoneTheme(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.SelectPhoneThemeCsReq, allocator);
defer req.deinit();
var rsp = protocol.SelectPhoneThemeScRsp.init(allocator);
rsp.retcode = 0;
rsp.cur_phone_theme = req.theme_id;
try session.send(CmdID.CmdSelectPhoneThemeScRsp, rsp);
}
pub fn onSelectChatBubble(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.SelectChatBubbleCsReq, allocator);
defer req.deinit();
var rsp = protocol.SelectChatBubbleScRsp.init(allocator);
rsp.retcode = 0;
rsp.cur_chat_bubble = req.bubble_id;
try session.send(CmdID.CmdSelectChatBubbleScRsp, rsp);
}
pub fn onGetPlayerBoardData(session: *Session, _: *const Packet, allocator: Allocator) !void {
var rsp = protocol.GetPlayerBoardDataScRsp.init(allocator);
var generator = Uid.BaseUidGen().init();
var display_list = protocol.DisplayAvatarVec.init(allocator);
const player_icon_config = &ConfigManager.global_game_config_cache.player_icon_config;
display_list.is_display = true;
rsp.retcode = 0;
try rsp.unlocked_personal_card_list.appendSlice(&Data.OwnedPersonalCardSkin);
rsp.current_personal_card_id = 253001;
rsp.head_frame_info = .{
.head_frame_expire_time = 4294967295,
.head_frame_item_id = 226004,
};
rsp.signature = .{ .Const = "" };
try rsp.assist_avatar_id_list.appendSlice(&SupportAvatar);
for (ListAvatar) |id| {
var A_list = protocol.DisplayAvatarData.init(allocator);
A_list.avatar_id = id;
A_list.pos = generator.nextId();
try display_list.display_avatar_list.append(A_list);
}
rsp.display_avatar_vec = display_list;
for (player_icon_config.player_icon_config.items) |head_id| {
const head_icon = protocol.HeadIconData{
.id = head_id.id,
};
try rsp.unlocked_head_icon_list.append(head_icon);
}
try session.send(CmdID.CmdGetPlayerBoardDataScRsp, rsp);
}
pub fn onSetAssistAvatar(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.SetAssistAvatarCsReq, allocator);
defer req.deinit();
var rsp = protocol.SetAssistAvatarScRsp.init(allocator);
rsp.retcode = 0;
rsp.avatar_id = req.avatar_id;
rsp.avatar_id_list = req.avatar_id_list;
try session.send(CmdID.CmdSetAssistAvatarScRsp, rsp);
}
pub fn onSetDisplayAvatar(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.SetDisplayAvatarCsReq, allocator);
defer req.deinit();
var rsp = protocol.SetDisplayAvatarScRsp.init(allocator);
rsp.retcode = 0;
rsp.display_avatar_list = req.display_avatar_list;
try session.send(CmdID.CmdSetDisplayAvatarScRsp, rsp);
}
pub fn onSetSignature(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.SetSignatureCsReq, allocator);
defer req.deinit();
var rsp = protocol.SetSignatureScRsp.init(allocator);
rsp.retcode = 0;
rsp.signature = req.signature;
try session.send(CmdID.CmdSetSignatureScRsp, rsp);
}
pub fn onSetGameplayBirthday(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.SetGameplayBirthdayCsReq, allocator);
defer req.deinit();
var rsp = protocol.SetGameplayBirthdayScRsp.init(allocator);
rsp.retcode = 0;
rsp.birthday = req.birthday;
try session.send(CmdID.CmdSetGameplayBirthdayScRsp, rsp);
}
pub fn onSetHeadIcon(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.SetHeadIconCsReq, allocator);
defer req.deinit();
var rsp = protocol.SetHeadIconScRsp.init(allocator);
rsp.retcode = 0;
rsp.current_head_icon_id = req.id;
std.debug.print("SET HEAD ICON ID: {}\n", .{req.id});
try session.send(CmdID.CmdSetHeadIconScRsp, rsp);
}
pub fn onSelectPhoneCase(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.SelectPhoneCaseCsReq, allocator);
defer req.deinit();
var rsp = protocol.SelectPhoneCaseScRsp.init(allocator);
rsp.retcode = 0;
rsp.cur_phone_case = req.phone_case_id;
std.debug.print("SET PHONE CASE ID: {}\n", .{req.phone_case_id});
try session.send(CmdID.CmdSelectPhoneCaseScRsp, rsp);
}
pub fn onUpdatePlayerSetting(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.UpdatePlayerSettingCsReq, allocator);
defer req.deinit();
try session.send(CmdID.CmdUpdatePlayerSettingScRsp, protocol.UpdatePlayerSettingScRsp{
.player_setting = req.player_setting,
.retcode = 0,
});
}
pub fn onGetPlayerDetailInfo(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.GetPlayerDetailInfoCsReq, allocator);
defer req.deinit();
var rsp = protocol.GetPlayerDetailInfoScRsp.init(allocator);
rsp.retcode = 0;
var detail = protocol.PlayerDetailInfo.init(allocator);
detail.head_frame_info = .{
.head_frame_expire_time = 4294967295,
.head_frame_item_id = 226004,
};
detail.uid = req.uid;
detail.world_level = 6;
detail.level = 70;
rsp.detail_info = detail;
try session.send(CmdID.CmdGetPlayerDetailInfoScRsp, rsp);
}
pub fn onSetPersonalCard(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.SetPersonalCardCsReq, allocator);
defer req.deinit();
try session.send(CmdID.CmdSetPersonalCardScRsp, protocol.SetPersonalCardScRsp{
.current_personal_card_id = req.id,
.retcode = 0,
});
}

View File

@@ -0,0 +1,289 @@
const std = @import("std");
const protocol = @import("protocol");
const Session = @import("../Session.zig");
const Packet = @import("../Packet.zig");
const LineupManager = @import("../manager/lineup_mgr.zig");
const SceneManager = @import("../manager/scene_mgr.zig");
const ConfigManager = @import("../manager/config_mgr.zig");
const ArrayList = std.ArrayList;
const Allocator = std.mem.Allocator;
const CmdID = protocol.CmdID;
const log = std.log.scoped(.scene_service);
const entrance_config = &ConfigManager.global_game_config_cache.map_entrance_config;
const res_config = &ConfigManager.global_game_config_cache.res_config;
pub fn onGetCurSceneInfo(session: *Session, _: *const Packet, allocator: Allocator) !void {
var scene_manager = SceneManager.SceneManager.init(allocator);
const scene_info = try scene_manager.createScene(20422, 20422001, 2042201, 1025);
try session.send(CmdID.CmdGetCurSceneInfoScRsp, protocol.GetCurSceneInfoScRsp{
.scene = scene_info,
.retcode = 0,
});
}
pub fn onSceneEntityMove(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.SceneEntityMoveCsReq, allocator);
defer req.deinit();
for (req.entity_motion_list.items) |entity_motion| {
if (entity_motion.motion) |motion| {
if (entity_motion.entity_id > 99999 and entity_motion.entity_id < 1000000 or entity_motion.entity_id == 0)
log.debug("[POSITION] entity_id: {}, motion: {}", .{ entity_motion.entity_id, motion });
}
}
try session.send(CmdID.CmdSceneEntityMoveScRsp, protocol.SceneEntityMoveScRsp{
.retcode = 0,
.entity_motion_list = req.entity_motion_list,
.download_data = null,
});
}
pub fn onEnterScene(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.EnterSceneCsReq, allocator);
defer req.deinit();
var lineup_mgr = LineupManager.LineupManager.init(allocator);
const lineup = try lineup_mgr.createLineup();
var scene_manager = SceneManager.SceneManager.init(allocator);
var floorID: u32 = 0;
var planeID: u32 = 0;
var teleportID: u32 = 0;
for (entrance_config.map_entrance_config.items) |entrConf| {
if (entrConf.id == req.entry_id) {
floorID = entrConf.floor_id;
planeID = entrConf.plane_id;
teleportID = req.teleport_id;
}
}
try session.send(CmdID.CmdEnterSceneScRsp, protocol.EnterSceneScRsp{
.retcode = 0,
.game_story_line_id = req.game_story_line_id,
.is_close_map = req.is_close_map,
.content_id = req.content_id,
.is_over_map = false,
});
const scene_info = try scene_manager.createScene(planeID, floorID, req.entry_id, teleportID);
std.debug.print("ENTER SCENE ENTRY ID: {}, PLANE ID: {}, FLOOR ID: {}, TELEPORT ID: {}\n", .{ req.entry_id, planeID, floorID, teleportID });
try session.send(CmdID.CmdEnterSceneByServerScNotify, protocol.EnterSceneByServerScNotify{
.lineup = lineup,
.reason = protocol.EnterSceneReason.ENTER_SCENE_REASON_NONE,
.scene = scene_info,
});
}
pub fn onGetSceneMapInfo(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.GetSceneMapInfoCsReq, allocator);
defer req.deinit();
const ranges = [_][2]usize{
.{ 0, 101 },
.{ 10000, 10051 },
.{ 20000, 20001 },
.{ 30000, 30020 },
};
const chest_list = &[_]protocol.ChestInfo{
.{ .chest_type = protocol.ChestType.MAP_INFO_CHEST_TYPE_NORMAL },
.{ .chest_type = protocol.ChestType.MAP_INFO_CHEST_TYPE_CHALLENGE },
.{ .chest_type = protocol.ChestType.MAP_INFO_CHEST_TYPE_PUZZLE },
};
for (req.floor_id_list.items) |floor_id| {
var rsp = protocol.GetSceneMapInfoScRsp.init(allocator);
rsp.retcode = 0;
rsp.content_id = req.content_id;
rsp.entry_story_line_id = req.entry_story_line_id;
rsp.unk1 = true;
var map_info = protocol.SceneMapInfo.init(allocator);
try map_info.chest_list.appendSlice(chest_list);
map_info.entry_id = @intCast(floor_id);
map_info.floor_id = @intCast(floor_id);
map_info.cur_map_entry_id = @intCast(floor_id);
for (res_config.scene_config.items) |sceneConf| {
if (sceneConf.planeID != floor_id / 1000) continue;
try map_info.unlock_teleport_list.ensureUnusedCapacity(sceneConf.teleports.items.len);
try map_info.maze_prop_list.ensureUnusedCapacity(sceneConf.props.items.len);
try map_info.maze_group_list.ensureUnusedCapacity(sceneConf.props.items.len);
for (ranges) |range| {
for (range[0]..range[1]) |i| {
try map_info.lighten_section_list.append(@intCast(i));
}
}
for (sceneConf.teleports.items) |teleConf| {
try map_info.unlock_teleport_list.append(@intCast(teleConf.teleportId));
}
for (sceneConf.props.items) |propConf| {
try map_info.maze_prop_list.append(protocol.MazePropState{
.group_id = propConf.groupId,
.config_id = propConf.instId,
.state = propConf.propState,
});
try map_info.maze_prop_extra_state_list.append(protocol.MazePropExtraState{
.group_id = propConf.groupId,
.config_id = propConf.instId,
.state = propConf.propState,
});
try map_info.maze_group_list.append(protocol.MazeGroup{
.DDNOEGPCACF = std.ArrayList(u32).init(allocator),
.group_id = propConf.groupId,
});
}
}
try rsp.scene_map_info.append(map_info);
try session.send(protocol.CmdID.CmdGetSceneMapInfoScRsp, rsp);
}
}
pub fn onGetUnlockTeleport(session: *Session, _: *const Packet, allocator: Allocator) !void {
var rsp = protocol.GetUnlockTeleportScRsp.init(allocator);
var total_tps: usize = 0;
for (res_config.scene_config.items) |scene| {
total_tps += scene.teleports.items.len;
}
try rsp.unlock_teleport_list.ensureTotalCapacity(total_tps);
for (res_config.scene_config.items) |sceneCof| {
for (sceneCof.teleports.items) |tp| {
rsp.unlock_teleport_list.appendAssumeCapacity(tp.teleportId);
}
}
rsp.retcode = 0;
try session.send(CmdID.CmdGetUnlockTeleportScRsp, rsp);
}
pub fn onEnterSection(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.EnterSectionCsReq, allocator);
defer req.deinit();
var rsp = protocol.EnterSectionScRsp.init(allocator);
rsp.retcode = 0;
std.debug.print("ENTER SECTION Id: {}\n", .{req.section_id});
try session.send(CmdID.CmdEnterSectionScRsp, rsp);
}
pub fn onGetEnteredScene(session: *Session, _: *const Packet, allocator: Allocator) !void {
var rsp = protocol.GetEnteredSceneScRsp.init(allocator);
var noti = protocol.EnteredSceneChangeScNotify.init(allocator);
for (entrance_config.map_entrance_config.items) |entrance| {
try rsp.entered_scene_info_list.append(protocol.EnteredSceneInfo{
.floor_id = entrance.floor_id,
.plane_id = entrance.plane_id,
});
try noti.entered_scene_info_list.append(protocol.EnteredSceneInfo{
.floor_id = entrance.floor_id,
.plane_id = entrance.plane_id,
});
}
rsp.retcode = 0;
try session.send(CmdID.CmdEnteredSceneChangeScNotify, noti);
try session.send(CmdID.CmdGetEnteredSceneScRsp, rsp);
}
pub fn onSceneEntityTeleport(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.SceneEntityTeleportCsReq, allocator);
defer req.deinit();
var rsp = protocol.SceneEntityTeleportScRsp.init(allocator);
rsp.retcode = 0;
rsp.entity_motion = req.entity_motion;
std.debug.print("SCENE ENTITY TP ENTRY ID: {}\n", .{req.entry_id});
try session.send(CmdID.CmdSceneEntityTeleportScRsp, rsp);
}
pub fn onGetFirstTalkNpc(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.GetFirstTalkNpcCsReq, allocator);
defer req.deinit();
var rsp = protocol.GetFirstTalkNpcScRsp.init(allocator);
rsp.retcode = 0;
for (req.npc_id_list.items) |id| {
try rsp.npc_meet_status_list.append(protocol.FirstNpcTalkInfo{ .npc_id = id, .is_meet = true });
}
try session.send(CmdID.CmdGetFirstTalkNpcScRsp, rsp);
}
pub fn onGetFirstTalkByPerformanceNp(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.GetFirstTalkByPerformanceNpcCsReq, allocator);
defer req.deinit();
var rsp = protocol.GetFirstTalkByPerformanceNpcScRsp.init(allocator);
rsp.retcode = 0;
for (req.performance_id_list.items) |id| {
try rsp.npc_meet_status_list.append(
protocol.NpcMeetByPerformanceStatus{ .performance_id = id, .is_meet = true },
);
}
try session.send(CmdID.CmdGetFirstTalkByPerformanceNpcScRsp, rsp);
}
pub fn onGetNpcTakenReward(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.GetNpcTakenRewardCsReq, allocator);
defer req.deinit();
var rsp = protocol.GetNpcTakenRewardScRsp.init(allocator);
const EventList = [_]u32{ 2136, 2134 };
rsp.retcode = 0;
rsp.npc_id = req.npc_id;
try rsp.talk_event_list.appendSlice(&EventList);
try session.send(CmdID.CmdGetNpcTakenRewardScRsp, rsp);
}
pub fn onUpdateGroupProperty(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.UpdateGroupPropertyCsReq, allocator);
defer req.deinit();
var rsp = protocol.UpdateGroupPropertyScRsp.init(allocator);
rsp.retcode = 0;
rsp.floor_id = req.floor_id;
rsp.group_id = req.group_id;
rsp.dimension_id = req.dimension_id;
rsp.DELEKFMGGCM = req.DELEKFMGGCM;
try session.send(CmdID.CmdUpdateGroupPropertyScRsp, rsp);
}
pub fn onChangePropTimeline(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.ChangePropTimelineInfoCsReq, allocator);
defer req.deinit();
try session.send(CmdID.CmdChangePropTimelineInfoScRsp, protocol.ChangePropTimelineInfoScRsp{
.retcode = 0,
.prop_entity_id = req.prop_entity_id,
});
}
pub fn onDeactivateFarmElement(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.DeactivateFarmElementCsReq, allocator);
defer req.deinit();
std.debug.print("DEACTIVATE FARM ELEMENT ENTITY ID: {}\n", .{req.entity_id});
try session.send(CmdID.CmdDeactivateFarmElementScRsp, protocol.DeactivateFarmElementScRsp{
.retcode = 0,
.entity_id = req.entity_id,
});
}
pub fn onActivateFarmElement(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.ActivateFarmElementCsReq, allocator);
defer req.deinit();
std.debug.print("ACTIVATE FARM ELEMENT ENTITY ID: {}\n", .{req.entity_id});
try session.send(CmdID.CmdActivateFarmElementScRsp, protocol.ActivateFarmElementScRsp{
.retcode = 0,
.world_level = req.world_level,
.entity_id = req.entity_id,
});
}
pub fn onInteractProp(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.InteractPropCsReq, allocator);
defer req.deinit();
std.debug.print("INTERACT ID {} on PROP ENTITY ID: {}\n", .{ req.interact_id, req.prop_entity_id });
try session.send(CmdID.CmdInteractPropScRsp, protocol.InteractPropScRsp{
.retcode = 0,
.prop_entity_id = req.prop_entity_id,
.prop_state = 0,
});
}
pub fn onChangeEraFlipperData(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.ChangeEraFlipperDataCsReq, allocator);
defer req.deinit();
try session.send(CmdID.CmdChangeEraFlipperDataScRsp, protocol.ChangeEraFlipperDataScRsp{
.retcode = 0,
.data = req.data,
});
}

View File

@@ -0,0 +1,337 @@
const std = @import("std");
const ArrayList = std.ArrayList;
pub fn FunMode() type {
return struct {
var on_funmode: bool = false;
var max_Hp: u32 = 0;
pub fn FunMode() bool {
return on_funmode;
}
pub fn SetFunMode(set: bool) void {
on_funmode = set;
}
pub fn SetHp(Hp: u32) void {
max_Hp = Hp;
}
pub fn GetHp() u32 {
return max_Hp;
}
};
}
pub fn Challenge() type {
return struct {
var challenge_blessing: std.ArrayList(u32) = std.ArrayList(u32).init(std.heap.page_allocator);
var on_challenge: bool = false;
var challenge_mode: u32 = 0;
var challenge_planeID: u32 = 0;
var challenge_floorID: u32 = 0;
var challenge_entryID: u32 = 0;
var challenge_worldID: u32 = 0;
var challenge_monsterID: u32 = 0;
var challenge_eventID: u32 = 0;
var challenge_groupID: u32 = 0;
var challenge_maze_groupID: u32 = 0;
var challenge_stageID: u32 = 0;
var challengeID: u32 = 0;
var challenge_buffID: u32 = 0;
var challenge_peak_hard: bool = true;
var on_peak_king_stage: bool = false;
var avatar_list: std.ArrayList(u32) = std.ArrayList(u32).init(std.heap.page_allocator);
pub fn GetAvatarIDs() std.ArrayList(u32) {
return avatar_list;
}
pub fn AddAvatar(ids: []const u32) !void {
for (ids) |id| {
try avatar_list.append(id);
}
}
pub fn resetChallengeState() void {
on_challenge = false;
challenge_mode = 0;
challenge_planeID = 0;
challenge_floorID = 0;
challenge_entryID = 0;
challenge_worldID = 0;
challenge_monsterID = 0;
challenge_eventID = 0;
challenge_groupID = 0;
challenge_maze_groupID = 0;
challenge_stageID = 0;
challengeID = 0;
challenge_buffID = 0;
_ = challenge_blessing.clearRetainingCapacity();
_ = avatar_list.clearRetainingCapacity();
}
var saved_peak_lineups: std.AutoHashMap(u32, std.ArrayList(u32)) = std.AutoHashMap(u32, std.ArrayList(u32)).init(std.heap.page_allocator);
pub fn SavePeakLineup(peak_id: u32, list: []const u32) !void {
var avatar_copy = std.ArrayList(u32).init(std.heap.page_allocator);
try avatar_copy.appendSlice(list);
try saved_peak_lineups.put(peak_id, avatar_copy);
}
pub fn LoadPeakLineup(peak_id: u32) !void {
if (saved_peak_lineups.get(peak_id)) |saved_list| {
try AddAvatar(saved_list.items);
}
}
pub fn ChallengeMode() bool {
return on_challenge;
}
pub fn SetChallenge() void {
on_challenge = true;
}
pub fn SetPeakBoss(set: bool) void {
on_peak_king_stage = set;
}
pub fn ChallengePeakHard() bool {
return challenge_peak_hard and on_peak_king_stage;
}
pub fn SetChallengePeakHard(set: bool) void {
challenge_peak_hard = set;
}
pub fn GetChallengeID() u32 {
return challengeID;
}
pub fn SetChallengeID(id: u32) void {
challengeID = id;
}
pub fn SetChallengeMode(mode: u32) void {
challenge_mode = mode;
}
pub fn FoundStage() bool {
return challenge_stageID != 0;
}
pub fn GetChallengeStageID() u32 {
return challenge_stageID;
}
pub fn GetChallengeBuffID() u32 {
return challenge_buffID;
}
pub fn SetChallengeBuffID(id: u32) void {
challenge_buffID = id;
}
pub fn GetChallengeMode() u32 {
return challenge_mode;
}
pub fn GameModePF() bool {
return challengeID > 20000 and challengeID < 30000;
}
pub fn GameModeAS() bool {
return challengeID > 30000;
}
pub fn GetChallengeBlessingID() []const u32 {
return challenge_blessing.items;
}
pub fn AddBlessing(id: []const u32) !void {
try challenge_blessing.appendSlice(id);
}
pub fn GetCurChallengeStatus() void {
std.debug.print("CURRENT CHALLENGE STAGE ID:{}\n", .{challenge_stageID});
std.debug.print("CURRENT CHALLENGE LINEUP AVATAR ID:{}\n", .{GetAvatarIDs()});
std.debug.print("CURRENT CHALLENGE MONSTER ID:{}\n", .{challenge_monsterID});
switch (challenge_mode) {
0 => std.debug.print("CURRENT CHALLENGE: MOC\n", .{}),
1 => {
std.debug.print("CURRENT CHALLENGE: PF\n", .{});
if (challenge_blessing.items.len >= 2) {
std.debug.print("CURRENT CHALLENGE STAGE BLESSING ID:{}, SELECTED BLESSING ID:{}\n", .{ challenge_blessing.items[0], challenge_blessing.items[1] });
}
},
else => {
std.debug.print("CURRENT CHALLENGE: AS\n", .{});
if (challenge_blessing.items.len >= 2) {
std.debug.print("CURRENT CHALLENGE STAGE BLESSING ID:{}, SELECTED BLESSING ID:{}\n", .{ challenge_blessing.items[0], challenge_blessing.items[1] });
}
},
}
}
pub fn SetChallengeInfo(
floor: u32,
world: u32,
monster: u32,
event: u32,
group: u32,
maze: u32,
plane: u32,
entry: u32,
) void {
challenge_floorID = floor;
challenge_worldID = world;
challenge_monsterID = monster;
challenge_eventID = event;
challenge_groupID = group;
challenge_maze_groupID = maze;
challenge_planeID = plane;
challenge_entryID = entry;
challenge_stageID = challenge_eventID;
}
pub fn SetChallengePeakInfo(
floor: u32,
monster: u32,
event: u32,
group: u32,
maze: u32,
plane: u32,
entry: u32,
) void {
challenge_floorID = floor;
challenge_monsterID = monster;
challenge_eventID = event;
challenge_groupID = group;
challenge_maze_groupID = maze;
challenge_planeID = plane;
challenge_entryID = entry;
challenge_stageID = challenge_eventID;
}
pub fn CalChallengePeakEventID(event: u32) u32 {
return if (ChallengePeakHard())
event + 1
else
event;
}
pub fn GetSceneIDs() [8]u32 {
return .{
challenge_planeID,
challenge_floorID,
challenge_entryID,
challenge_worldID,
challenge_monsterID,
challenge_eventID,
challenge_groupID,
challenge_maze_groupID,
};
}
pub fn GetPeakSceneIDs() [7]u32 {
return .{
challenge_planeID,
challenge_floorID,
challenge_entryID,
challenge_monsterID,
challenge_eventID,
challenge_groupID,
challenge_maze_groupID,
};
}
pub fn GetCurSceneStatus() void {
std.debug.print("SEND PLANE ID {} FLOOR ID {} ENTRY ID {} GROUP ID {} MAZE GROUP ID {}\n", .{
challenge_planeID,
challenge_floorID,
challenge_entryID,
challenge_groupID,
challenge_maze_groupID,
});
}
};
}
pub fn CustomMode() type {
return struct {
var challenge_node: u32 = 0;
var selected_challenge_id: u32 = 0;
var selected_buff_id: u32 = 0;
var custom_mode: bool = false;
pub fn FirstNode() bool {
return challenge_node == 0;
}
pub fn ChangeNode() []const u8 {
if (FirstNode()) {
challenge_node += 1;
return "Change Challenge Node 2\n";
} else {
challenge_node -= 1;
return "Change Challenge Node 1\n";
}
}
pub fn SelectCustomNode(node: u32) void {
challenge_node = node - 1;
}
pub fn CustomMode() bool {
return custom_mode;
}
pub fn SetCustomMode(check: bool) void {
custom_mode = check;
}
pub fn GetCustomChallengeID() u32 {
return selected_challenge_id;
}
pub fn SetCustomChallengeID(id: u32) void {
selected_challenge_id = id;
}
pub fn GetCustomBuffID() u32 {
return selected_buff_id;
}
pub fn SetCustomBuffID(id: u32) void {
selected_buff_id = id;
}
};
}
pub fn Banner() type {
return struct {
var StandardBanner = [_]u32{ 1003, 1004, 1101, 1104, 1209, 1211 };
var RateUp = [_]u32{1321};
var RateUpFourStars = [_]u32{ 1210, 1108, 1207 };
pub fn GetStandardBanner() []const u32 {
return &StandardBanner;
}
pub fn SetStandardBanner() []u32 {
return &StandardBanner;
}
pub fn GetRateUp() []const u32 {
return &RateUp;
}
pub fn SetRateUp() []u32 {
return &RateUp;
}
pub fn GetRateUpFourStar() []const u32 {
return &RateUpFourStars;
}
pub fn SetRateUpFourStar() []u32 {
return &RateUpFourStars;
}
};
}
pub fn inlist(id: u32, list: []const u32) bool {
for (list) |item| {
if (item == id) {
return true;
}
}
return false;
}
pub fn contains(list: *const std.ArrayListAligned(u32, null), value: u32) bool {
for (list.items) |item| {
if (item == value) {
return true;
}
}
return false;
}

View File

@@ -0,0 +1,60 @@
var global_uid_gen = UidGenerator().init(0);
var initial_uid: u32 = 0;
pub fn UidGenerator() type {
return struct {
current_id: u32,
const Self = @This();
pub fn init(start_id: u32) Self {
return Self{ .current_id = start_id };
}
pub fn nextId(self: *Self) u32 {
self.current_id +%= 1;
return self.current_id;
}
pub fn getCurrentId(self: *const Self) u32 {
return self.current_id;
}
pub fn setCurrentId(self: *Self, new_id: u32) void {
self.current_id = new_id;
}
};
}
pub fn BaseUidGen() type {
return struct {
current_id: u32,
const Self = @This();
pub fn init() Self {
return Self{ .current_id = 0 };
}
pub fn nextId(self: *Self) u32 {
self.current_id +%= 1;
return self.current_id;
}
};
}
pub fn nextGlobalId() u32 {
return global_uid_gen.nextId();
}
pub fn resetGlobalUidGen(start_id: u32) void {
global_uid_gen = UidGenerator().init(start_id);
}
pub fn getCurrentGlobalId() u32 {
return global_uid_gen.getCurrentId();
}
pub fn getCurrentUid() u32 {
return global_uid_gen.getCurrentId();
}
pub fn setCurrentUid(new_id: u32) void {
global_uid_gen.setCurrentId(new_id);
}
pub fn resetGlobalUidGens() void {
global_uid_gen = UidGenerator().init(initial_uid);
}
pub fn updateInitialUid() void {
initial_uid = getCurrentUid();
}

10
hotfix.json Normal file
View File

@@ -0,0 +1,10 @@
{
"CNBETAWin3.7.51": {
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_12579793_48327ff319b5_1b794dd1071e3a",
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_12611332_5f583f2f54ae_c04979f13c950d",
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_0_40d2ce0253_c61ba99f70b885",
"ifix_version": "0",
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_12579929_9566349ee5fb_c6341faaf9b027",
"lua_version": ""
}
}

18
protocol/build.zig Normal file
View File

@@ -0,0 +1,18 @@
const std = @import("std");
const protobuf = @import("protobuf");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const protobuf_dep = b.dependency("protobuf", .{
.optimize = optimize,
.target = target,
});
const protocol = b.addModule("protocol", .{
.root_source_file = b.path("src/root.zig"),
});
//
protocol.addImport("protobuf", protobuf_dep.module("protobuf"));
}

16
protocol/build.zig.zon Normal file
View File

@@ -0,0 +1,16 @@
.{
.name = .protocol,
.fingerprint = 0xc8c0bc4cf2cfbf0c,
.version = "0.0.0",
.dependencies = .{
.protobuf = .{
.url = "https://github.com/Arwalk/zig-protobuf/archive/da4d17264fcfdd793e71b3a8b466fb0f05bc6fd3.tar.gz",
.hash = "122052e8e9e4233621ebeba2215df92dbb78387be6193bdc24da3f44532ddeeb25ab",
},
},
.paths = .{
"build.zig",
"build.zig.zon",
"src",
},
}

62043
protocol/src/protocol.pb.zig Normal file

File diff suppressed because it is too large Load Diff

2118
protocol/src/root.zig Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

3370
resources/Anchor.json Normal file

File diff suppressed because it is too large Load Diff

6854
resources/AvatarConfig.json Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,523 @@
{
"player_icon_config": [
{
"ID": 201001,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1001.png",
"IsVisible": true,
"AvatarID": 1001,
"SortType": 3
},
{
"ID": 201002,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1002.png",
"IsVisible": true,
"AvatarID": 1002,
"SortType": 3
},
{
"ID": 201003,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1003.png",
"IsVisible": true,
"AvatarID": 1003,
"SortType": 3
},
{
"ID": 201004,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1004.png",
"IsVisible": true,
"AvatarID": 1004,
"SortType": 3
},
{
"ID": 201005,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1005.png",
"IsVisible": true,
"AvatarID": 1005,
"SortType": 3
},
{
"ID": 201006,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1006.png",
"IsVisible": true,
"AvatarID": 1006,
"SortType": 3
},
{
"ID": 201008,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1008.png",
"IsVisible": true,
"AvatarID": 1008,
"SortType": 3
},
{
"ID": 201009,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1009.png",
"IsVisible": true,
"AvatarID": 1009,
"SortType": 3
},
{
"ID": 201013,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1013.png",
"IsVisible": true,
"AvatarID": 1013,
"SortType": 3
},
{
"ID": 201101,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1101.png",
"IsVisible": true,
"AvatarID": 1101,
"SortType": 3
},
{
"ID": 201102,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1102.png",
"IsVisible": true,
"AvatarID": 1102,
"SortType": 3
},
{
"ID": 201103,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1103.png",
"IsVisible": true,
"AvatarID": 1103,
"SortType": 3
},
{
"ID": 201104,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1104.png",
"IsVisible": true,
"AvatarID": 1104,
"SortType": 3
},
{
"ID": 201105,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1105.png",
"IsVisible": true,
"AvatarID": 1105,
"SortType": 3
},
{
"ID": 201106,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1106.png",
"IsVisible": true,
"AvatarID": 1106,
"SortType": 3
},
{
"ID": 201107,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1107.png",
"IsVisible": true,
"AvatarID": 1107,
"SortType": 3
},
{
"ID": 201108,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1108.png",
"IsVisible": true,
"AvatarID": 1108,
"SortType": 3
},
{
"ID": 201109,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1109.png",
"IsVisible": true,
"AvatarID": 1109,
"SortType": 3
},
{
"ID": 201110,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1110.png",
"IsVisible": true,
"AvatarID": 1110,
"SortType": 3
},
{
"ID": 201111,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1111.png",
"IsVisible": true,
"AvatarID": 1111,
"SortType": 3
},
{
"ID": 201203,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1203.png",
"IsVisible": true,
"AvatarID": 1203,
"SortType": 3
},
{
"ID": 201204,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1204.png",
"IsVisible": true,
"AvatarID": 1204,
"SortType": 3
},
{
"ID": 201205,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1205.png",
"IsVisible": true,
"AvatarID": 1205,
"SortType": 3
},
{
"ID": 201206,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1206.png",
"AvatarID": 1206,
"SortType": 3
},
{
"ID": 208002,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/8002.png",
"IsVisible": true,
"AvatarID": 8002,
"SortType": 3
},
{
"ID": 208004,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/8004.png",
"IsVisible": true,
"AvatarID": 8004,
"SortType": 3
},
{
"ID": 208005,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/8005.png",
"IsVisible": true,
"AvatarID": 8005,
"SortType": 3
},
{
"ID": 208006,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/8006.png",
"IsVisible": true,
"AvatarID": 8006,
"SortType": 3
},
{
"ID": 201201,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1201.png",
"AvatarID": 1201,
"SortType": 3
},
{
"ID": 201202,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1202.png",
"AvatarID": 1202,
"SortType": 3
},
{
"ID": 201207,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1207.png",
"AvatarID": 1207,
"SortType": 3
},
{
"ID": 201208,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1208.png",
"AvatarID": 1208,
"SortType": 3
},
{
"ID": 201209,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1209.png",
"AvatarID": 1209,
"SortType": 3
},
{
"ID": 201210,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1210.png",
"AvatarID": 1210,
"SortType": 3
},
{
"ID": 201211,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1211.png",
"AvatarID": 1211,
"SortType": 3
},
{
"ID": 201212,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1212.png",
"AvatarID": 1212,
"SortType": 3
},
{
"ID": 201213,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1213.png",
"AvatarID": 1213,
"SortType": 3
},
{
"ID": 201214,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1214.png",
"AvatarID": 1214,
"SortType": 3
},
{
"ID": 201215,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1215.png",
"AvatarID": 1215,
"SortType": 3
},
{
"ID": 201112,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1112.png",
"AvatarID": 1112,
"SortType": 3
},
{
"ID": 201301,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1301.png",
"AvatarID": 1301,
"SortType": 3
},
{
"ID": 201302,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1302.png",
"AvatarID": 1302,
"SortType": 3
},
{
"ID": 201217,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1217.png",
"AvatarID": 1217,
"SortType": 3
},
{
"ID": 201303,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1303.png",
"AvatarID": 1303,
"SortType": 3
},
{
"ID": 201304,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1304.png",
"AvatarID": 1304,
"SortType": 3
},
{
"ID": 201305,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1305.png",
"AvatarID": 1305,
"SortType": 3
},
{
"ID": 201307,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1307.png",
"AvatarID": 1307,
"SortType": 3
},
{
"ID": 201308,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1308.png",
"AvatarID": 1308,
"SortType": 3
},
{
"ID": 201309,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1309.png",
"AvatarID": 1309,
"SortType": 3
},
{
"ID": 201312,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1312.png",
"AvatarID": 1312,
"SortType": 3
},
{
"ID": 201314,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1314.png",
"AvatarID": 1314,
"SortType": 3
},
{
"ID": 201315,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1315.png",
"AvatarID": 1315,
"SortType": 3
},
{
"ID": 201310,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1310.png",
"AvatarID": 1310,
"SortType": 3
},
{
"ID": 201306,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1306.png",
"AvatarID": 1306,
"SortType": 3
},
{
"ID": 201218,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1218.png",
"AvatarID": 1218,
"SortType": 3
},
{
"ID": 201224,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1224.png",
"AvatarID": 1224,
"SortType": 3
},
{
"ID": 201313,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1313.png",
"AvatarID": 1313,
"SortType": 3
},
{
"ID": 201221,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1221.png",
"AvatarID": 1221,
"SortType": 3
},
{
"ID": 201220,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1220.png",
"AvatarID": 1220,
"SortType": 3
},
{
"ID": 201317,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1317.png",
"AvatarID": 1317,
"SortType": 3
},
{
"ID": 201223,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1223.png",
"AvatarID": 1223,
"SortType": 3
},
{
"ID": 201222,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1222.png",
"AvatarID": 1222,
"SortType": 3
},
{
"ID": 201225,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1225.png",
"AvatarID": 1225,
"SortType": 3
},
{
"ID": 201402,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1402.png",
"AvatarID": 1402,
"SortType": 3
},
{
"ID": 201401,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1401.png",
"AvatarID": 1401,
"SortType": 3
},
{
"ID": 201403,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1403.png",
"AvatarID": 1403,
"SortType": 3
},
{
"ID": 201404,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1404.png",
"AvatarID": 1404,
"SortType": 3
},
{
"ID": 201407,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1407.png",
"AvatarID": 1407,
"SortType": 3
},
{
"ID": 201408,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1408.png",
"AvatarID": 1408,
"SortType": 3
},
{
"ID": 201405,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1405.png",
"AvatarID": 1405,
"SortType": 3
},
{
"ID": 201409,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1409.png",
"AvatarID": 1409,
"SortType": 3
},
{
"ID": 201412,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1412.png",
"AvatarID": 1412,
"SortType": 3
},
{
"ID": 201406,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1406.png",
"AvatarID": 1406,
"SortType": 3
},
{
"ID": 201410,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1410.png",
"AvatarID": 1410,
"SortType": 3
},
{
"ID": 201415,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1415.png",
"AvatarID": 1415,
"SortType": 3
},
{
"ID": 201414,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1414.png",
"AvatarID": 1414,
"SortType": 3
},
{
"ID": 201413,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1413.png",
"AvatarID": 1413,
"SortType": 3
},
{
"ID": 201321,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1321.png",
"AvatarID": 1321,
"SortType": 3
},
{
"ID": 208007,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/8007.png",
"AvatarID": 8007,
"SortType": 3
},
{
"ID": 208008,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/8008.png",
"AvatarID": 8008,
"SortType": 3
},
{
"ID": 208001,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/8001.png",
"AvatarID": 8001,
"SortType": 3
},
{
"ID": 208003,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/8003.png",
"AvatarID": 8003,
"SortType": 3
}
]
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,835 @@
{
"text_map_config": [
{
"GroupID": 3001,
"BuffList1": [
{
"id": 3111008,
"name": "Increases all allies' Break Effect by 50%. After dealing Super Break DMG to enemies, increases SPD by an amount equal to 40% of current SPD, lasting until the next action."
},
{
"id": 3111010,
"name": "Increases DMG dealt by all allies' Ultimates by 50%. After defeating an enemy target, the attacker's action is Advanced Forward by 25%."
},
{
"id": 3111011,
"name": "Before enemy units with Steadfast Safeguard are Weakness Broken, tally the amount of DoT they receive. When a unit is Weakness Broken, deal to this unit Additional DMG equal to 170% of the tallied DoT."
}
],
"BuffList2": [
{
"id": 3111008,
"name": "Increases all allies' Break Effect by 50%. After dealing Super Break DMG to enemies, increases SPD by an amount equal to 40% of current SPD, lasting until the next action."
},
{
"id": 3111009,
"name": "Increases DMG dealt by all allies' Basic ATK and Skills by 50%. After defeating an enemy target, the attacker's action is Advanced Forward by 25%."
},
{
"id": 3111012,
"name": "Before enemy units with Steadfast Safeguard are Weakness Broken, tally the amount of Follow-up ATK DMG they receive. When the unit is Weakness Broken, deal to this unit Additional DMG equal to 200% of the tallied DMG."
}
]
},
{
"GroupID": 3002,
"BuffList1": [
{
"id": 3111015,
"name": "Follow-up ATKs/Counters will also reduce the Toughness of targets that do not have the corresponding Weakness, with the effect being equivalent to 50%/100% of the original Toughness Reduction from abilities."
},
{
"id": 3111013,
"name": "The DMG dealt by all allies' Basic ATK increases by 25%, with an additional increase of 25% against targets that are Weakness Broken."
},
{
"id": 3111014,
"name": "When an ally breaks an enemy target's Weakness, Advances Action Forward by 25%."
}
],
"BuffList2": [
{
"id": 3111015,
"name": "Follow-up ATKs/Counters will also reduce the Toughness of targets that do not have the corresponding Weakness, with the effect being equivalent to 50%/100% of the original Toughness Reduction from abilities."
},
{
"id": 3111016,
"name": "Increases Break DMG taken by all enemies by 20%."
},
{
"id": 3111017,
"name": "Increases CRIT DMG dealt by all allies' Skill by 40%, with an additional increase of 40% against targets who are Weakness Broken."
}
]
},
{
"GroupID": 3003,
"BuffList1": [
{
"id": 3111021,
"name": "Follow-up ATK DMG dealt by characters ignores 15% of the enemy target's All-Type RES and can also reduce the Toughness of targets that do not have the corresponding Weakness, with the effect being equivalent to 50% of the original Toughness Reduction from the ability."
},
{
"id": 3111022,
"name": "Character's Ice DMG dealt increases by 55%."
},
{
"id": 3111023,
"name": "When an ally breaks an enemy target's Weakness, Advance their Action Forward by 15%. This effect can trigger a max of #2[i] time(s) per turn."
}
],
"BuffList2": [
{
"id": 3111018,
"name": "When characters attack Weakness Broken enemies, all DoTs currently applied to the enemies will immediately deal DMG equal to 80% of the original DMG. This effect can trigger a max of #2[i] time(s) on each character in a single turn."
},
{
"id": 3111019,
"name": "After defeating enemy targets, increase Break DMG taken by all enemies by 5% to a max of #2[i] stack(s). This effect will also take effect on new enemies entering the battlefield."
},
{
"id": 3111020,
"name": "Increases the CRIT DMG of Ultimate DMG dealt by 30%, with an additional increase of 30% against enemy targets that are Weakness Broken."
}
]
},
{
"GroupID": 3004,
"BuffList1": [
{
"id": 3111028,
"name": "Increases the DMG dealt by all allies' Basic ATKs by 50%."
},
{
"id": 3111027,
"name": "After defeating any enemy target, advances all ally targets' actions by 10%."
},
{
"id": 3111021,
"name": "Follow-up ATK DMG dealt by characters ignores 15% of the enemy target's All-Type RES and can also reduce the Toughness of targets that do not have the corresponding Weakness, with the effect being equivalent to 50% of the original Toughness Reduction from the ability."
}
],
"BuffList2": [
{
"id": 3111024,
"name": "After a character uses Follow-up ATK, advances their action by 15%. This effect can be triggered a max of #2[i] time(s) per character before their next action."
},
{
"id": 3111025,
"name": "The Weakness Break Efficiency increases by 25% when characters deal Ultimate DMG."
},
{
"id": 3111026,
"name": "Increases all ally characters' Break Effect by 50%. When a character Breaks an enemy target's Weakness, regenerates #2[i] Energy to the character."
}
]
},
{
"GroupID": 3005,
"BuffList1": [
{
"id": 3111030,
"name": "Increases all enemies' Break DMG taken by 10%. After Breaking an enemy's Weakness, increases all allies' SPD by 20% for #3[i] turn(s)"
},
{
"id": 3111033,
"name": "Before enemy units with \"Steadfast Safeguard\" are Weakness Broken, tally the amount of DoT they receive. When a unit is Weakness Broken, deals Additional DMG to this unit equal to 200% of the tallied DoT."
},
{
"id": 3111034,
"name": "Increases Quantum DMG dealt by all allies' by 60%. After defeating an enemy target, advances the attacker's action by 15%."
}
],
"BuffList2": [
{
"id": 3111029,
"name": "When using Skill or Ultimate on one ally target, increases the ability target's ATK by 25%, lasting for #2[i] turn(s). This effect can stack up to #3[i] time(s)."
},
{
"id": 3111032,
"name": "Increases Ultimate DMG dealt by all allies to Weakness Broken enemies by 70%."
},
{
"id": 3111031,
"name": "Increases Basic ATK and Skill DMG dealt by all allies by 55%."
}
]
},
{
"GroupID": 3006,
"BuffList1": [
{
"id": 3111035,
"name": "When 2 or more characters following the Path of Erudition are in the team, increases all allies' ATK by 60%."
},
{
"id": 3111036,
"name": "Increases Physical DMG dealt by all allies by 50% and Effect RES by 50%."
},
{
"id": 3111037,
"name": "When an ally unit Breaks an Enemy target's Weakness, advances the ally unit's action by 20%."
}
],
"BuffList2": [
{
"id": 3111038,
"name": "When characters attack Weakness Broken enemies, all DoTs currently applied to the enemies will immediately deal DMG equal to 60% of the original DMG. This effect can trigger a max of #2[i] time(s) on each character in a single turn."
},
{
"id": 3111039,
"name": "After defeating enemy targets, increase Break DMG taken by all enemies by 5% to a max of #2[i] stack(s). This effect will also take effect on new enemies entering the battlefield."
},
{
"id": 3111040,
"name": "Increases CRIT DMG of all allies' Basic ATK and Ultimate DMG dealt by 30%, with an additional increase of 30% against enemy targets that are Weakness Broken."
}
]
},
{
"GroupID": 3007,
"BuffList1": [
{
"id": 3111041,
"name": "Increases DMG dealt by all allies' Basic ATK by 50%. After defeating an enemy target, advances the attacker's action by 10%."
},
{
"id": 3111042,
"name": "Increases the SPD of all allies by 15% and reduces their DMG taken by 15%."
},
{
"id": 3111043,
"name": "Follow-up ATK DMG dealt by characters ignores 15% of the enemy target's All-Type RES and can also reduce the Toughness of targets that do not have the corresponding Weakness, with the effect being equivalent to 50% of the original Toughness Reduction from the ability."
}
],
"BuffList2": [
{
"id": 3111044,
"name": "When using Skill or Ultimate on one ally target, increases the ability target's CRIT DMG by 35%, lasting for #2[i] turn(s). This effect can stack up to #3[i] time(s)."
},
{
"id": 3111045,
"name": "Increases all enemies' Break DMG taken by 10%. After Breaking an enemy's Weakness, increases all allies' SPD by 20% for #3[i] turn(s)."
},
{
"id": 3111046,
"name": "Increases all allies' Skill DMG and Ultimate DMG dealt by 50%."
}
]
},
{
"GroupID": 3008,
"BuffList1": [
{
"id": 3111047,
"name": "While any character following the Path of Erudition is in the team, increases all allies' All-Type RES PEN by 15%."
},
{
"id": 3111048,
"name": "Increases Ultimate DMG dealt by all allies by 50% and Effect RES by 50%."
},
{
"id": 3111049,
"name": "After any ally target consumes their own HP, increases their DMG dealt by 55% for #2[i] turn(s)."
}
],
"BuffList2": [
{
"id": 3111050,
"name": "When any ally target Breaks an enemy target's Weakness, increases all ally targets' SPD by 20% for #2[i] turn(s)."
},
{
"id": 3111051,
"name": "After any ally target's HP was consumed, they restore HP equal to 60% of the HP consumed. This can restore a maximum of 50% HP per turn."
},
{
"id": 3111052,
"name": "Increases ally targets' Follow-up ATK DMG dealt by 30%, with an additional increase of 30% against enemy targets that are Weakness Broken."
}
]
},
{
"GroupID": 3009,
"BuffList1": [
{
"id": 3111053,
"name": "Increases memosprite's Weakness Break Efficiency by 50%. Ignores 20% of the target's DEF when memosprite deals DMG."
},
{
"id": 3111018,
"name": "When characters attack Weakness Broken enemies, all DoTs currently applied to the enemies will immediately deal DMG equal to 80% of the original DMG. This effect can trigger a max of #2[i] time(s) on each character in a single turn."
},
{
"id": 3111020,
"name": "Increases the CRIT DMG of Ultimate DMG dealt by 30%, with an additional increase of 30% against enemy targets that are Weakness Broken."
}
],
"BuffList2": [
{
"id": 3111055,
"name": "Increases Physical DMG dealt by all allies by 50%. When a unit uses a Skill, restores 20% of that unit's HP."
},
{
"id": 3111021,
"name": "Follow-up ATK DMG dealt by characters ignores 15% of the enemy target's All-Type RES and can also reduce the Toughness of targets that do not have the corresponding Weakness, with the effect being equivalent to 50% of the original Toughness Reduction from the ability."
},
{
"id": 3111030,
"name": "Increases all enemies' Break DMG taken by 10%. After Breaking an enemy's Weakness, increases all allies' SPD by 20% for #3[i] turn(s)"
}
]
},
{
"GroupID": 3010,
"BuffList1": [
{
"id": 3111056,
"name": "The DMG dealt by the character in position 1 in the lineup increases by 60%. For every #2[i] Skill Points consumed, 1 Skill Point is recovered."
},
{
"id": 3111057,
"name": "After any ally target consumes their own HP, increases their DMG dealt by 40% for #2[i] turn(s)."
},
{
"id": 3111058,
"name": "After an ally target deals DoT to an enemy, #1[i] Energy is regenerated. DoT dealt by all allies ignores 30% of enemy targets' All-Type RES."
}
],
"BuffList2": [
{
"id": 3111046,
"name": "Increases all allies' Skill DMG and Ultimate DMG dealt by 50%."
},
{
"id": 3111014,
"name": "When an ally breaks an enemy target's Weakness, Advances Action Forward by 25%."
},
{
"id": 3111021,
"name": "Follow-up ATK DMG dealt by characters ignores 15% of the enemy target's All-Type RES and can also reduce the Toughness of targets that do not have the corresponding Weakness, with the effect being equivalent to 50% of the original Toughness Reduction from the ability."
}
]
},
{
"GroupID": 3011,
"BuffList1": [
{
"id": 3111062,
"name": "When an ally target consumes 1 Skill Point, increases the DMG dealt by all allies by 6%, stacking up to #2[i] time(s)."
},
{
"id": 3111063,
"name": "After defeating enemy targets, increases Break DMG taken by all enemies by 15% to a max of #2[i] stack(s). This effect will also take effect on new enemies entering the battlefield."
},
{
"id": 3111061,
"name": "After any ally target consumes their own HP, their CRIT DMG increases by 55%, lasting for #2[i] turn(s)."
}
],
"BuffList2": [
{
"id": 3111059,
"name": "Increases Ultimate DMG dealt by all allies by 30%, and additionally increases by 30% against enemy targets in Weakness Broken state."
},
{
"id": 3111060,
"name": "When allies use Basic ATK or Skill to attack Weakness Broken enemies, all DoTs currently applied to the enemies will immediately deal DMG equal to 15% of the original DMG."
},
{
"id": 3111064,
"name": "After the character in position 1 in the team uses Ultimate, their Weakness Break Efficiency increases by 50% and increases CRIT DMG by 50%, lasting for #3[i] turn(s)."
}
]
},
{
"GroupID": 3012,
"BuffList1": [
{
"id": 3111068,
"name": "Increases Physical DMG dealt by all allies by 50%, and decreases all enemies' Physical RES by 20%."
},
{
"id": 3111069,
"name": "When allies use Basic ATK or Skill to attack Weakness Broken enemies, all DoTs currently applied to the enemies will immediately produce DMG equal to 20% of the original DMG."
},
{
"id": 3111070,
"name": "Increases all enemies' Break DMG taken by 10%. After Breaking an enemy's Weakness, increases all allies' SPD by 20% for #3[i] turn(s)."
}
],
"BuffList2": [
{
"id": 3111065,
"name": "DMG dealt by memosprites ignores 20% of the target's DEF."
},
{
"id": 3111066,
"name": "Increases all allies' Skill DMG by 30%. At the start of their turn, if their current HP percentage is equal to or lower than 1%, restores HP equal to 15% of their Max HP."
},
{
"id": 3111067,
"name": "When a character uses Ultimate, their Weakness Break Efficiency increases by 100% and CRIT DMG increases by 40%, lasting for #3[i] turn(s)."
}
]
},
{
"GroupID": 3013,
"BuffList1": [
{
"id": 3111068,
"name": "Increases Physical DMG dealt by all allies by 50%, and decreases all enemies' Physical RES by 20%."
},
{
"id": 3111069,
"name": "When allies use Basic ATK or Skill to attack Weakness Broken enemies, all DoTs currently applied to the enemies will immediately produce DMG equal to 20% of the original DMG."
},
{
"id": 3111070,
"name": "Increases all enemies' Break DMG taken by 10%. After Breaking an enemy's Weakness, increases all allies' SPD by 20% for #3[i] turn(s)."
}
],
"BuffList2": [
{
"id": 3111065,
"name": "DMG dealt by memosprites ignores 20% of the target's DEF."
},
{
"id": 3111066,
"name": "Increases all allies' Skill DMG by 30%. At the start of their turn, if their current HP percentage is equal to or lower than 1%, restores HP equal to 15% of their Max HP."
},
{
"id": 3111067,
"name": "When a character uses Ultimate, their Weakness Break Efficiency increases by 100% and CRIT DMG increases by 40%, lasting for #3[i] turn(s)."
}
]
},
{
"GroupID": 2001,
"BuffList1": [
{
"id": 3031301,
"name": "When allies use an Ultimate to attack targets, inflicts 2 stacks of Shatter to the target."
},
{
"id": 3031302,
"name": "When enemies enter battle, there is a 80% chance to instantly receive 1 stack of Shatter."
},
{
"id": 3031303,
"name": "When enemy targets with Shatter are defeated, increases DMG dealt by Ultimate for all allies in this battle by 15%, stacking up to #2[i] times."
}
],
"BuffList2": []
},
{
"GroupID": 2002,
"BuffList1": [
{
"id": 3031304,
"name": "When an enemy takes DoT, adjacent targets also take DoT of the same type by an amount equal to 60% of the original DMG dealt."
},
{
"id": 3031305,
"name": "After allies attack enemies, inflicts Wind Shear on the target, causing the target to take a set amount of Wind DoT at the start of every turn."
},
{
"id": 3031306,
"name": "When an enemy takes DoT, the ally with the lowest HP percentage will heal for 10% of their Max HP, and their action will be Advanced Forward by 8%."
}
],
"BuffList2": []
},
{
"GroupID": 2003,
"BuffList1": [
{
"id": 3031307,
"name": "Characters' Follow-up ATKs deal 50% increased DMG, and this effect will be triggered 1 additional time after triggering Whimsicality's effect."
},
{
"id": 3031308,
"name": "When Ultimate deals DMG to the enemy target, it will be considered as a Follow-up ATK."
},
{
"id": 3031309,
"name": "Whenever a character hits an enemy with a Follow-up ATK, all allies' actions are Advanced Forward by 14%. This effect can only be triggered once for each attack."
}
],
"BuffList2": []
},
{
"GroupID": 2004,
"BuffList1": [
{
"id": 3031310,
"name": "When allies use a Skill to attack enemy targets, there is a 80% fixed chance to inflict Shatter to the target."
},
{
"id": 3031311,
"name": "When enemy targets use their abilities, inflicts 1 stack of Scalded on them and adjacent units. While in the Scalded state, targets receive 10% more DMG, stacking up to #2[i] stack(s). This effect is effective on the set amount of DMG from the Whimsicality effect."
},
{
"id": 3031312,
"name": "After characters use an Ultimate to attack, their action is Advanced Forward by 25% and deals 100% more DMG, lasting for #1[i] turn(s)."
}
],
"BuffList2": []
},
{
"GroupID": 2005,
"BuffList1": [
{
"id": 3031313,
"name": "Whimsicality no longer requires accumulated DMG dealt by Follow-up ATK. Instead, it triggers after allies use #1[i] instances of Follow-up ATK and the DMG dealt by this effect increases by 60%."
},
{
"id": 3031314,
"name": "When the Whimsicality effect is triggered, regenerates Energy for all allies equal to 35% of their respective Max Energy. Energy recovered via this effect can exceed the ally's Max Energy."
},
{
"id": 3031315,
"name": "Apart from Follow-up ATKs, all other kinds of DMG dealt by allies can also accumulate progress equal to 18% of the original DMG amount."
}
],
"BuffList2": []
},
{
"GroupID": 2006,
"BuffList1": [
{
"id": 3031316,
"name": "When enemy targets are defeated, the DoTs on them have a 100% base chance to be transferred to all enemies."
},
{
"id": 3031317,
"name": "After enemy targets enter battle, they become afflicted by Wind Shear. At the start of every turn, they receive a set amount of Wind DoT and inflict Wind Shear on themselves and adjacent enemy targets."
},
{
"id": 3031318,
"name": "When 3 or more characters following the Path of Nihility are in the team, increases all allies' DMG dealt by 60% and SPD by 30%."
}
],
"BuffList2": []
},
{
"GroupID": 2007,
"BuffList1": [
{
"id": 3031319,
"name": "Reduces the Charge points required to trigger Whimsicality by #1[i] and deals DMG #2[i] more time(s)."
},
{
"id": 3031320,
"name": "Increases DMG dealt from allies by Ultimate by 30%. After the Ultimate is used, Whimsicality additionally gains #2[i] point(s) of Charge."
},
{
"id": 3031321,
"name": "After allies attack enemy targets, Whimsicality additionally gains #1[i] point(s) of Charge."
}
],
"BuffList2": []
},
{
"GroupID": 2008,
"BuffList1": [
{
"id": 3031303,
"name": "When enemy targets with Shatter are defeated, increases DMG dealt by Ultimate for all allies in this battle by 15%, stacking up to #2[i] times."
},
{
"id": 3031322,
"name": "Increases DMG dealt with Shatter by 20%. When triggered, applies 1 stack of Shatter to adjacent targets."
},
{
"id": 3031323,
"name": "When allies' Follow-up ATKs hit enemy targets, there is a 60% fixed chance to apply 1 stack of Shatter to the target. This effect can only be triggered 1 time per attack per enemy target."
}
],
"BuffList2": []
},
{
"GroupID": 2009,
"BuffList1": [
{
"id": 3031324,
"name": "Increases the Break DMG taken by all enemies by 10%. If allies dealt Super Break DMG to enemies after using an attack, additionally Charges Whimsicality by #2[i] points."
},
{
"id": 3031325,
"name": "Follow-up ATK DMG dealt increases by 50%. Follow-up ATKs' Weakness Break Efficiency increases by 50%."
},
{
"id": 3031326,
"name": "When characters launch attacks by using their Ultimate, they will ignore the enemy targets' Weakness to cause Toughness Reduction."
}
],
"BuffList2": []
},
{
"GroupID": 2010,
"BuffList1": [
{
"id": 3031330,
"name": "Increases all allies' Break Effect by 30%. When Breaking enemy targets, all enemies become afflicted with Wind Shear, taking a set amount of Wind DoT at the start of every turn.\\nAll enemies become immune to Crowd Control debuffs."
},
{
"id": 3031331,
"name": "After any ally target uses Ultimate to attack any enemy target and if the target currently has 1/2/3 types of DoTs, each currently active DoT immediately deals DMG equal to 60% / 100% / 150% of the original DMG."
},
{
"id": 3031305,
"name": "After allies attack enemies, inflicts Wind Shear on the target, causing the target to take a set amount of Wind DoT at the start of every turn."
}
],
"BuffList2": []
},
{
"GroupID": 2011,
"BuffList1": [
{
"id": 3031327,
"name": "During Surging Grit, all ally Skills do not consume Skill Points. After characters use Skills or Ultimate, they gain 1 stack of \"Feverish Surge\" for every enemy target hit. These stacks will increase said characters' Skill and Ultimate DMG by 4% and SPD by 4% to a max of #3[i] stack(s)."
},
{
"id": 3031328,
"name": "During Surging Grit, increases all allies' Follow-up ATK DMG by 40%. After triggering a Follow-up ATK, deals a set amount of DMG to every attacked enemy target and adjacent target."
},
{
"id": 3031329,
"name": "During Surging Grit, increases Break DMG received by enemy targets by 30%. When characters Break enemy targets' Weaknesses, advances the character's action by 12%."
}
],
"BuffList2": [
{
"id": 3031202,
"name": "After allies use Skill to attack enemy targets, every enemy target hit additionally accumulates #1[i] Grit Value for allies."
},
{
"id": 3031203,
"name": "At the start of battle, after an ally uses their Skill to attack an enemy target, deals a set amount of DMG to all attacked enemy targets."
},
{
"id": 3031204,
"name": "Upon entering Surging Grit, advances all allies actions by #1[i]% and increases DMG received by enemy targets during this period by #2[i]%. Additionally, after an ally uses their Skill to attack an enemy target, deals an additional set amount of DMG to all attacked enemy targets and adjacent targets."
}
]
},
{
"GroupID": 2012,
"BuffList1": [
{
"id": 3031333,
"name": "During Surging Grit, all allies' SPD increases by 40%. When all \"Resound\" is consumed in an attack, recover 1 Skill Point."
},
{
"id": 3031332,
"name": "During Surging Grit, after an enemy target is defeated by any unit, there is a 50% chance for all allies to gain 1 stack of \"Resound.\""
},
{
"id": 3031334,
"name": "When entering Surging Grit, activates the Ultimate of the first character on the team lineup and increases their DMG dealt by Ultimate during Surging Grit by 50%."
}
],
"BuffList2": [
{
"id": 3031207,
"name": "After allies use Ultimate to attack enemy targets, every enemy target hit additionally accumulates #1[i] Grit Value for allies."
},
{
"id": 3031208,
"name": "After an enemy target is defeated by any unit, all allies gain 1 stack of \"Resound.\""
},
{
"id": 3031209,
"name": "When entering Surging Grit, consume all \"Resound.\" Each stack of \"Resound\" deals 1 instance of a set amount of DMG to a random enemy target. During Surging Grit, increases enemy targets' DMG taken by #3[i]%, and after they are defeated by any unit, all allies additionally gain 1 stack of \"Resound.\" When \"Resound\" stacks up to #1[i] or higher, consumes all \"Resound\" stacks and deals 1 instance of a set amount of DMG to a random enemy target per stack."
}
]
},
{
"GroupID": 2013,
"BuffList1": [
{
"id": 3031335,
"name": "During Surging Grit, after allies use their Skill or launch a Follow-up ATK against enemy targets with \"Shatter,\" \"Shatter\" can be immediately triggered."
},
{
"id": 3031336,
"name": "During Surging Grit, increases Break DMG taken by enemy targets by 30%. When targets in the \"Shatter\" state are defeated by any unit, deals #1[i] Toughness Reduction regardless of Weakness Type to adjacent enemy targets."
},
{
"id": 3031337,
"name": "During Surging Grit, when any character uses their Skill, consumes HP equal to 20% of their current HP. Additionally, after using Skill, restores HP equal to 20% of their Max HP."
}
],
"BuffList2": [
{
"id": 3031212,
"name": "After an ally uses Basic ATK or Ultimate to attack enemy targets, every enemy target hit additionally accumulates #1[i] Grit Value for allies."
},
{
"id": 3031213,
"name": "When allies use their Basic ATK or Ultimate to attack any enemy targets, inflicts the target with \"Shatter,\" stacking up to #1[i] time(s). At the start of the target's turn or when the target is defeated by any unit, deals True DMG to the target based on the number of \"Shatter\" stacks."
},
{
"id": 3031214,
"name": "Upon entering Surging Grit, immediately regenerates #1[i]% Energy for all allies. The Energy regenerated by this effect can exceed the target's Max Energy. Increases DMG taken by enemy targets during this period by #2[i]%. Additionally, triggering \"Shatter\" will deal True DMG to all attacked enemy targets and adjacent targets."
}
]
},
{
"GroupID": 2014,
"BuffList1": [
{
"id": 3031327,
"name": "During Surging Grit, all ally Skills do not consume Skill Points. After characters use Skills or Ultimate, they gain 1 stack of \"Feverish Surge\" for every enemy target hit. These stacks will increase said characters' Skill and Ultimate DMG by 4% and SPD by 4% to a max of #3[i] stack(s)."
},
{
"id": 3031338,
"name": "During Surging Grit, when any ally uses their Skill, consumes HP equal to 20% of their Max HP. Then, after using Skill, restores HP equal to 20% of their Max HP."
},
{
"id": 3031329,
"name": "During Surging Grit, increases Break DMG received by enemy targets by 30%. When characters Break enemy targets' Weaknesses, advances the character's action by 12%."
}
],
"BuffList2": [
{
"id": 3031202,
"name": "After allies use Skill to attack enemy targets, every enemy target hit additionally accumulates #1[i] Grit Value for allies."
},
{
"id": 3031203,
"name": "At the start of battle, after an ally uses their Skill to attack an enemy target, deals a set amount of DMG to all attacked enemy targets."
},
{
"id": 3031204,
"name": "Upon entering Surging Grit, advances all allies actions by #1[i]% and increases DMG received by enemy targets during this period by #2[i]%. Additionally, after an ally uses their Skill to attack an enemy target, deals an additional set amount of DMG to all attacked enemy targets and adjacent targets."
}
]
},
{
"GroupID": 2015,
"BuffList1": [
{
"id": 3031333,
"name": "During Surging Grit, all allies' SPD increases by 40%. When all \"Resound\" is consumed in an attack, recover 1 Skill Point."
},
{
"id": 3031343,
"name": "All allies can also reduce Toughness when attacking enemies that don't have the corresponding Weakness Type, with the effect equivalent to 50% of the original Toughness Reduction value. This cannot stack with other Toughness Reduction effects that also ignore Weakness Type. During Surging Grit, the Break DMG received by enemy targets increases by 30%."
},
{
"id": 3031339,
"name": "After consuming \"Resound\" to deal DMG, increases all allies' DMG dealt by 60% for #2[i] turn(s)."
}
],
"BuffList2": [
{
"id": 3031207,
"name": "After allies use Ultimate to attack enemy targets, every enemy target hit additionally accumulates #1[i] Grit Value for allies."
},
{
"id": 3031208,
"name": "After an enemy target is defeated by any unit, all allies gain 1 stack of \"Resound.\""
},
{
"id": 3031209,
"name": "When entering Surging Grit, consume all \"Resound.\" Each stack of \"Resound\" deals 1 instance of a set amount of DMG to a random enemy target. During Surging Grit, increases enemy targets' DMG taken by #3[i]%, and after they are defeated by any unit, all allies additionally gain 1 stack of \"Resound.\" When \"Resound\" stacks up to #1[i] or higher, consumes all \"Resound\" stacks and deals 1 instance of a set amount of DMG to a random enemy target per stack."
}
]
},
{
"GroupID": 2016,
"BuffList1": [
{
"id": 3031340,
"name": "During the \"Surging Grit\" period, the ally targets' DoT dealt ignores 20% of the target's All-Type RES. When the enemy target receives DoT, increases all allies DoT dealt by 1%. Up to a max of #3[i] stack(s). Lasts until \"Surging Grit\" ends."
},
{
"id": 3031341,
"name": "During Surging Grit, Skill DMG dealt by ally targets increases by 50%. When attacking targets with the special Wind Shear \"Echo Enigma,\" each stack of \"Echo Enigma\" additionally increases Skill DMG by 10%."
},
{
"id": 3031342,
"name": "Every stack of \"Echo Enigma\" increases enemy target's DMG received by 3%. During the Surging Grit period, when enemy targets enter combat, and after they take action, additionally adds #2[i] stack(s) of the special Wind Shear \"Echo Enigma.\""
}
],
"BuffList2": [
{
"id": 3031217,
"name": "Every time a DoT is received by an enemy target, additionally accumulates #1[i] Grit Value for allies."
},
{
"id": 3031218,
"name": "When an enemy target enters battle, they are inflicted with #1[i] stack(s) of \"Echo Enigma,\" which deals a special kind of Wind Shear, and they suffer #2[i] stack(s) of \"Echo Enigma\" after acting, receiving Wind DoT at the start of each turn. Goes up to #3[i] stack(s) at max for #4[i] turn(s)."
},
{
"id": 3031219,
"name": "Entering the \"Surging Grit\" state causes all enemies to suffer #5[i] stack(s) of the special Wind Shear \"Echo Enigma,\" and causes all currently suffered DoT to immediately produce DMG equal to #1[i]% of the original DMG. During the \"Surging Grit\" period, increases enemy targets' DMG received by #2[i]%, reduces DMG dealt by #3[i]%, and increases SPD by #4[i]%."
}
]
},
{
"GroupID": 2017,
"BuffList1": [
{
"id": 3031344,
"name": "During \"Surging Grit,\" memosprite's DMG increases by 30%, and when memosprite disappears, memomaster's action advances by 15%."
},
{
"id": 3031345,
"name": "Upon entering \"Surging Grit,\" immediately recover #1[i] Skill Point(s). During \"Surging Grit,\" enemy targets take 30% more DoT."
},
{
"id": 3031346,
"name": "During \"Surging Grit,\" after an ally uses a Skill to attack enemy targets, they deal additional True DMG equal to 30% of the original damage to each enemy target hit."
}
],
"BuffList2": [
{
"id": 3031202,
"name": "After allies use Skill to attack enemy targets, every enemy target hit additionally accumulates #1[i] Grit Value for allies."
},
{
"id": 3031203,
"name": "At the start of battle, after an ally uses their Skill to attack an enemy target, deals a set amount of DMG to all attacked enemy targets."
},
{
"id": 3031204,
"name": "Upon entering Surging Grit, advances all allies actions by #1[i]% and increases DMG received by enemy targets during this period by #2[i]%. Additionally, after an ally uses their Skill to attack an enemy target, deals an additional set amount of DMG to all attacked enemy targets and adjacent targets."
}
]
},
{
"GroupID": 2018,
"BuffList1": [
{
"id": 3031347,
"name": "During \"Surging Grit\", when an ally target uses their Ultimate, increases all allies DMG dealt by 12%, stacking up to #2[i] times. This effect lasts until \"Surging Grit\" ends."
},
{
"id": 3031348,
"name": "Upon entering \"Surging Grit,\" immediately recover #1[i] Skill Point(s). During \"Surging Grit,\" enemy targets take 30% more DoT."
},
{
"id": 3031349,
"name": "During \"Surging Grit\", increases all allies SPD by 20%, and after using a Skill, apply #2[i] stack(s) of \"Resound\" to all allies."
}
],
"BuffList2": [
{
"id": 3031207,
"name": "After allies use Ultimate to attack enemy targets, every enemy target hit additionally accumulates #1[i] Grit Value for allies."
},
{
"id": 3031208,
"name": "After an enemy target is defeated by any unit, all allies gain 1 stack of \"Resound.\""
},
{
"id": 3031209,
"name": "When entering Surging Grit, consume all \"Resound.\" Each stack of \"Resound\" deals 1 instance of a set amount of DMG to a random enemy target. During Surging Grit, increases enemy targets' DMG taken by #3[i]%, and after they are defeated by any unit, all allies additionally gain 1 stack of \"Resound.\" When \"Resound\" stacks up to #1[i] or higher, consumes all \"Resound\" stacks and deals 1 instance of a set amount of DMG to a random enemy target per stack."
}
]
}
]
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,69 @@
{
"challenge_peak_boss_config": [
{
"ID": 104,
"HardTitle": {
"Hash": -829434279,
"Hash64": 7760170859122248016
},
"BuffList": [
3033006,
3033007,
3033008
],
"ColorMedalTarget": 6,
"HardTarget": 3007,
"HardEventIDList": [
30501022
],
"HardTagList": [
3033010,
3033013,
3033019
]
},
{
"ID": 204,
"HardTitle": {
"Hash": -829434376,
"Hash64": 1050453030874697501
},
"BuffList": [
3033020,
3033021,
3033022
],
"ColorMedalTarget": 6,
"HardTarget": 3007,
"HardEventIDList": [
30502022
],
"HardTagList": [
3033013,
3033019
]
},
{
"ID": 304,
"HardTitle": {
"Hash": -829434345,
"Hash64": 18174992387478714442
},
"BuffList": [
3033032,
3033033,
3033034
],
"ColorMedalTarget": 6,
"HardTarget": 3007,
"HardEventIDList": [
30503022
],
"HardTagList": [
3033029,
3033040,
3033036
]
}
]
}

View File

@@ -0,0 +1,507 @@
{
"challenge_peak_config": [
{
"DamageType": [
"Fire",
"Imaginary"
],
"ProgressValueList": [
3,
5,
0,
0,
0
],
"HPProgressValueList": [
0,
0,
20,
60,
60
],
"EventIDList": [
30501011
],
"NpcMonsterIDList": [
3003041
],
"TagList": [
3033001
],
"NormalTargetList": [
3001,
3002,
3000
],
"ID": 101,
"Title": {
"hash": -1272192780,
"hash64": 9016368862888813841
},
"MapEntranceID": 3013501,
"MazeGroupID": 8
},
{
"DamageType": [
"Physical",
"Wind",
"Thunder"
],
"ProgressValueList": [
3,
5,
0,
0,
0
],
"HPProgressValueList": [
0,
0,
20,
60,
60
],
"EventIDList": [
30501012
],
"NpcMonsterIDList": [
3014020
],
"TagList": [
3033002
],
"NormalTargetList": [
3001,
3002,
3000
],
"ID": 102,
"Title": {
"hash": -1272192779,
"hash64": 1704275520309974869
},
"MapEntranceID": 3013501,
"MazeGroupID": 8
},
{
"DamageType": [
"Physical",
"Quantum"
],
"ProgressValueList": [
3,
5,
0,
0,
0
],
"HPProgressValueList": [
0,
0,
20,
60,
60
],
"EventIDList": [
30501013
],
"NpcMonsterIDList": [
4033030
],
"TagList": [
3033003,
3033004
],
"NormalTargetList": [
3001,
3002,
3000
],
"ID": 103,
"Title": {
"hash": -1272192778,
"hash64": 966156248142353937
},
"MapEntranceID": 3013501,
"MazeGroupID": 8
},
{
"DamageType": [
"Physical",
"Ice",
"Quantum"
],
"ProgressValueList": [
3,
5,
0,
0,
0
],
"HPProgressValueList": [
0,
0,
20,
60,
60
],
"EventIDList": [
30501021
],
"NpcMonsterIDList": [
4044010
],
"TagList": [
3033003,
3033005,
3033017
],
"NormalTargetList": [
3003,
3004,
3005
],
"ID": 104,
"Title": {
"hash": -1272192785,
"hash64": 4362815169327807403
},
"MapEntranceID": 3013501,
"MazeGroupID": 8
},
{
"DamageType": [
"Physical",
"Thunder"
],
"ProgressValueList": [
3,
5,
0,
0,
0
],
"HPProgressValueList": [
0,
0,
20,
60,
60
],
"EventIDList": [
30502011
],
"NpcMonsterIDList": [
2013020
],
"TagList": [
3033023
],
"NormalTargetList": [
3001,
3002,
3000
],
"ID": 201,
"Title": {
"hash": -1272192747,
"hash64": 9534971313111157091
},
"MapEntranceID": 3013501,
"MazeGroupID": 8
},
{
"DamageType": [
"Physical",
"Wind"
],
"ProgressValueList": [
3,
5,
0,
0,
0
],
"HPProgressValueList": [
0,
0,
20,
60,
60
],
"EventIDList": [
30502012
],
"NpcMonsterIDList": [
2034010
],
"TagList": [
3033025,
3033005
],
"NormalTargetList": [
3001,
3002,
3000
],
"ID": 202,
"Title": {
"hash": -1272192748,
"hash64": 16196939618065014687
},
"MapEntranceID": 3013501,
"MazeGroupID": 8
},
{
"DamageType": [
"Fire",
"Ice"
],
"ProgressValueList": [
3,
5,
0,
0,
0
],
"HPProgressValueList": [
0,
0,
20,
60,
60
],
"EventIDList": [
30502013
],
"NpcMonsterIDList": [
2023020
],
"TagList": [
3033001
],
"NormalTargetList": [
3001,
3002,
3000
],
"ID": 203,
"Title": {
"hash": -1272192749,
"hash64": 2595282763128539863
},
"MapEntranceID": 3013501,
"MazeGroupID": 8
},
{
"DamageType": [
"Physical",
"Ice",
"Quantum"
],
"ProgressValueList": [
3,
5,
0,
0,
0
],
"HPProgressValueList": [
0,
0,
20,
60,
60
],
"EventIDList": [
30502021
],
"NpcMonsterIDList": [
4015010
],
"TagList": [
3033023,
3033026,
3033017
],
"NormalTargetList": [
3003,
3004,
3005
],
"ID": 204,
"Title": {
"hash": -1272192750,
"hash64": 1721509265295027434
},
"MapEntranceID": 3013501,
"MazeGroupID": 8
},
{
"ID": 301,
"Title": {
"Hash": -1272192714,
"Hash64": 16740991519056524518
},
"NormalTargetList": [
3001,
3002,
3000
],
"DamageType": [
"Ice",
"Thunder"
],
"EventIDList": [
30503011
],
"NpcMonsterIDList": [
4053020
],
"TagList": [
3033003
],
"ProgressValueList": [
3,
5,
0,
0,
0
],
"HPProgressValueList": [
0,
0,
20,
60,
60
],
"MapEntranceID": 3013501,
"MazeGroupID": 8
},
{
"ID": 302,
"Title": {
"Hash": -1272192717,
"Hash64": 1007121766924267487
},
"NormalTargetList": [
3001,
3002,
3000
],
"DamageType": [
"Fire",
"Imaginary"
],
"EventIDList": [
30503012
],
"NpcMonsterIDList": [
8024010
],
"TagList": [
3033002
],
"ProgressValueList": [
3,
5,
0,
0,
0
],
"HPProgressValueList": [
0,
0,
20,
60,
60
],
"MapEntranceID": 3013501,
"MazeGroupID": 8
},
{
"ID": 303,
"Title": {
"Hash": -1272192716,
"Hash64": 6772486622480467397
},
"NormalTargetList": [
3001,
3002,
3000
],
"DamageType": [
"Physical",
"Imaginary"
],
"EventIDList": [
30503013
],
"NpcMonsterIDList": [
4064010
],
"TagList": [
3033023,
3033025
],
"ProgressValueList": [
3,
5,
0,
0,
0
],
"HPProgressValueList": [
0,
0,
20,
60,
60
],
"MapEntranceID": 3013501,
"MazeGroupID": 8
},
{
"ID": 304,
"Title": {
"Hash": -1272192719,
"Hash64": 9831062955247717184
},
"NormalTargetList": [
3003,
3004,
3005
],
"DamageType": [
"Fire"
],
"EventIDList": [
30503021
],
"NpcMonsterIDList": [
3025010
],
"TagList": [
3033028,
3033038,
3033035
],
"ProgressValueList": [
3,
5,
0,
0,
0
],
"HPProgressValueList": [
0,
0,
20,
60,
60
],
"MapEntranceID": 3013501,
"MazeGroupID": 8
}
]
}

View File

@@ -0,0 +1,85 @@
{
"challenge_peak_group_config": [
{
"ID": 1,
"Title": {
"Hash": -504960136,
"Hash64": 3550232802813737810
},
"RecommendID": 1,
"ActivityModule": 2100101,
"PreLevelIDList": [
101,
102,
103
],
"BossLevelID": 104,
"RewardGroupID": 1,
"BossUI3DPrefabPath": "UI/UI3D/ChallengePeak/_dependencies/Prefab/ChallengePeakBoss_W4_Theoroi.prefab",
"BossUI3DAnimatorPath": "UI/UI3D/ChallengePeak/_dependencies/Animator/ChallengePeakBoss_W4_Theoroi_AniControl.controller",
"ThemePosterTabPicPath": "SpriteOutput/Quest/TabIcon/BtnChallengePeak_4001.png",
"ThemeIconPicPath": "SpriteOutput/ChallengePeak/ChallengePeakIcon_4001.png",
"HandBookPanelBannerPath": "SpriteOutput/DailyMission/Banner/ChallengePeakPanelBanner.png",
"RankIconPathList": [
"SpriteOutput/ChallengePeak/MedallionIcon/ChallengePeakMedalIcon0001/Bronze.png",
"SpriteOutput/ChallengePeak/MedallionIcon/ChallengePeakMedalIcon0001/Silver.png",
"SpriteOutput/ChallengePeak/MedallionIcon/ChallengePeakMedalIcon0001/Gold.png",
"SpriteOutput/ChallengePeak/MedallionIcon/ChallengePeakMedalIcon0001/Ultra.png"
]
},
{
"ID": 2,
"Title": {
"Hash": -101675609,
"Hash64": 10349426151264090008
},
"RecommendID": 2,
"ActivityModule": 2100201,
"PreLevelIDList": [
201,
202,
203
],
"BossLevelID": 204,
"RewardGroupID": 1,
"BossUI3DPrefabPath": "UI/UI3D/ChallengePeak/_dependencies/Prefab/ChallengePeakBoss_W4_DawnsEye.prefab",
"BossUI3DAnimatorPath": "UI/UI3D/ChallengePeak/_dependencies/Animator/ChallengePeakBoss_W4_DawnsEye_AniControl.controller",
"ThemePosterTabPicPath": "SpriteOutput/Quest/TabIcon/BtnChallengePeak_4002.png",
"ThemeIconPicPath": "SpriteOutput/ChallengePeak/ChallengePeakIcon_4002.png",
"HandBookPanelBannerPath": "SpriteOutput/DailyMission/Banner/ChallengePeakPanelBanner_4002.png",
"RankIconPathList": [
"SpriteOutput/ChallengePeak/MedallionIcon/ChallengePeakMedalIcon0002/Bronze.png",
"SpriteOutput/ChallengePeak/MedallionIcon/ChallengePeakMedalIcon0002/Silver.png",
"SpriteOutput/ChallengePeak/MedallionIcon/ChallengePeakMedalIcon0002/Gold.png",
"SpriteOutput/ChallengePeak/MedallionIcon/ChallengePeakMedalIcon0002/Ultra.png"
]
},
{
"ID": 3,
"Title": {
"Hash": -1667759550,
"Hash64": 7638058374000144758
},
"RecommendID": 3,
"ActivityModule": 2100301,
"PreLevelIDList": [
301,
302,
303
],
"BossLevelID": 304,
"RewardGroupID": 1,
"BossUI3DPrefabPath": "UI/UI3D/ChallengePeak/_dependencies/Prefab/ChallengePeakBoss_W3_Sunday.prefab",
"BossUI3DAnimatorPath": "UI/UI3D/ChallengePeak/_dependencies/Animator/ChallengePeakBoss_W3_Sunday_AniControl.controller",
"ThemePosterTabPicPath": "SpriteOutput/Quest/TabIcon/BtnChallengePeak_4003.png",
"ThemeIconPicPath": "SpriteOutput/ChallengePeak/ChallengePeakIcon_4003.png",
"HandBookPanelBannerPath": "SpriteOutput/DailyMission/Banner/ChallengePeakPanelBanner_4003.png",
"RankIconPathList": [
"SpriteOutput/ChallengePeak/MedallionIcon/ChallengePeakMedalIcon0003/Bronze.png",
"SpriteOutput/ChallengePeak/MedallionIcon/ChallengePeakMedalIcon0003/Silver.png",
"SpriteOutput/ChallengePeak/MedallionIcon/ChallengePeakMedalIcon0003/Gold.png",
"SpriteOutput/ChallengePeak/MedallionIcon/ChallengePeakMedalIcon0003/Ultra.png"
]
}
]
}

File diff suppressed because it is too large Load Diff

52091
resources/MainMission.json Normal file

File diff suppressed because it is too large Load Diff

7656
resources/MapEntrance.json Normal file

File diff suppressed because it is too large Load Diff

5000
resources/MazePlane.json Normal file

File diff suppressed because it is too large Load Diff

55214
resources/QuestData.json Normal file

File diff suppressed because it is too large Load Diff

309761
resources/StageConfig.json Normal file

File diff suppressed because it is too large Load Diff

16515
resources/TutorialData.json Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

1096926
resources/res.json Normal file

File diff suppressed because it is too large Load Diff

4
run.bat Normal file
View File

@@ -0,0 +1,4 @@
@echo
start zig build run-dispatch
start zig build run-gameserver