Compare commits

...

1 commit
1.0.2 ... main

Author SHA1 Message Date
ca7eec0745 Update to v3:
- better respond time
- import BuffInfoConfig.json
- implemented custom mode for challenge mode to
sellect older MoC/PF/AS
2025-07-15 15:46:35 +07:00
19 changed files with 1511 additions and 326 deletions

View file

@ -10,7 +10,7 @@
### Setup launcher.exe
Copy and paste launcher.exe and hkprg.dll from launcher folder inside hysilens-sr to client folder.
Copy and paste launcher.exe and hkprg.dll from [launcher folder](https://git.xeondev.com/HonkaiSlopRail/hysilens-sr/src/branch/main/launcher) inside hysilens-sr to `client folder`.
### From source
@ -21,7 +21,7 @@ cd hysilens-sr
zig build run-dispatch
zig build run-gameserver
```
Then open your launcher.exe with administrator.
Then open your `launcher.exe` with administrator.
### Using Pre-built Binaries
Navigate to the [Releases](https://git.xeondev.com/HonkaiSlopRail/hysilens-sr/releases)
@ -33,7 +33,7 @@ page and download the latest release for your platform.
## Functionality (work in progress)
- Login and player spawn
- Test battle via calyx
- MOC/PF/AS simulator
- MOC/PF/AS simulator with custom stage sellection
- Gacha simulator
- Support command for Sillyism

View file

@ -1,11 +1,12 @@
// gameserver/src/Session.zig
const std = @import("std");
const protocol = @import("protocol");
const handlers = @import("handlers.zig");
const Packet = @import("Packet.zig");
const Cache = @import("../src/manager/scene_mgr.zig");
const Allocator = std.mem.Allocator;
const ArenaAllocator = std.heap.ArenaAllocator;
const Stream = std.net.Stream;
const Address = std.net.Address;
@ -15,12 +16,22 @@ const log = std.log.scoped(.session);
address: Address,
stream: Stream,
allocator: Allocator,
main_allocator: Allocator,
game_config_cache: *Cache.GameConfigCache,
pub fn init(address: Address, stream: Stream, allocator: Allocator) Self {
pub fn init(
address: Address,
stream: Stream,
session_allocator: Allocator,
main_allocator: Allocator,
game_config_cache: *Cache.GameConfigCache,
) Self {
return .{
.address = address,
.stream = stream,
.allocator = allocator,
.allocator = session_allocator,
.main_allocator = main_allocator,
.game_config_cache = game_config_cache,
};
}
@ -31,7 +42,6 @@ pub fn run(self: *Self) !void {
while (true) {
var packet = Packet.read(&reader, self.allocator) catch break;
defer packet.deinit();
try handlers.handle(self, &packet);
}
}

View file

@ -39,6 +39,7 @@ const commandList = [_]Command{
Command{ .name = "unstuck", .action = "", .func = unstuck_command.handle },
Command{ .name = "sync", .action = "", .func = sync_command.onGenerateAndSync },
Command{ .name = "refill", .action = "", .func = refill_command.onRefill },
Command{ .name = "id", .action = "", .func = value_command.onBuffId },
};
pub fn handleCommand(session: *Session, msg: []const u8, allocator: Allocator) Error!void {

View file

@ -10,6 +10,7 @@ pub fn handle(session: *Session, _: []const u8, allocator: Allocator) Error!void
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, "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

@ -1,6 +1,7 @@
const commandhandler = @import("../command.zig");
const std = @import("std");
const Session = @import("../Session.zig");
const Config = @import("../services/config.zig");
const Allocator = std.mem.Allocator;
const Error = commandhandler.Error;
@ -10,6 +11,11 @@ pub var StandardBanner = [_]u32{ 1003, 1004, 1101, 1104, 1209, 1211 };
pub var RateUp = [_]u32{1410};
pub var RateUpFourStars = [_]u32{ 1210, 1108, 1207 };
pub var selected_challenge_id: u32 = 0;
pub var selected_buff_id: u32 = 0;
pub var custom_mode: bool = false;
pub fn handle(session: *Session, _: []const u8, allocator: Allocator) Error!void {
try commandhandler.sendMessage(session, "Test Command for Chat\n", allocator);
}
@ -122,3 +128,166 @@ fn sendErrorMessage(session: *Session, message: []const u8, allocator: 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.tokenize(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")) {
custom_mode = 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);
challenge_node = node - 1;
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 = try Config.loadChallengeConfig(allocator, "resources/ChallengeMazeConfig.json");
const stage_config = try Config.loadStageConfig(allocator, "resources/StageConfig.json");
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);
selected_challenge_id = challenge_id;
selected_buff_id = 0;
custom_mode = true;
}
fn handleBuffSetCommand(session: *Session, allocator: Allocator, group_id: u32, node: u8, buff_index: usize, challenge_id: u32) !void {
const buff_config = try Config.loadTextMapConfig(allocator, "resources/BuffInfoConfig.json");
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);
selected_challenge_id = challenge_id;
selected_buff_id = buff.id;
custom_mode = 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 = try Config.loadTextMapConfig(allocator, "resources/BuffInfoConfig.json");
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 = try Config.loadChallengeConfig(allocator, "resources/ChallengeMazeConfig.json");
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);
}

View file

@ -30,22 +30,6 @@ pub const AllAvatars = &[_]u32{
1406, 1407, 1408, 1409, 1410, 1412,
};
// Battle group
//TODO: update every patch
pub const buffs_unlocked = &[_]u32{
100101, 100201, 100301, 100401, 100501, 100601, 100801, 100901, 101301,
110101, 110201, 110202, 110203, 110301, 110401, 110501, 110601, 110701,
110801, 110901, 111001, 111101, 111201, 120101, 120301, 120401, 120501,
120601, 120701, 120702, 120801, 120802, 120901, 121001, 121101, 121201,
121202, 121203, 121301, 121302, 121303, 121401, 121501, 121701, 121801,
122001, 122002, 122003, 122004, 122101, 122201, 122301, 122302, 122303,
122304, 122402, 122403, 122501, 130101, 130201, 130301, 130302, 130303,
130401, 130402, 130403, 130404, 130405, 130406, 130501, 130601, 130602,
130701, 130801, 130802, 130803, 130901, 130902, 130903, 131001, 131002,
131201, 131301, 131401, 131501, 131502, 131503, 131701, 131702, 140101,
140102, 140201, 140202, 140301, 140401, 140501, 140601, 140701, 140801,
140901, 200501, 200601, 220501, 221201, 800301, 800501, 101401, 101501,
141001,
};
//TODO: update more Remembrance character in future
pub const Rem = [_]u32{ 8007, 8008, 1402, 1407, 1409 };

View file

@ -26,7 +26,7 @@ const CmdID = protocol.CmdID;
const log = std.log.scoped(.handlers);
const Action = *const fn (*Session, *const Packet, Allocator) anyerror!void;
const HandlerList = [_]struct { CmdID, Action }{
pub const HandlerList = [_]struct { CmdID, Action }{
.{ CmdID.CmdPlayerGetTokenCsReq, login.onPlayerGetToken },
.{ CmdID.CmdPlayerLoginCsReq, login.onPlayerLogin },
.{ CmdID.CmdPlayerHeartBeatCsReq, misc.onPlayerHeartBeat },
@ -233,6 +233,7 @@ const DummyCmdList = [_]struct { CmdID, CmdID }{
.{ CmdID.CmdChimeraGetDataCsReq, CmdID.CmdChimeraGetDataScRsp },
.{ CmdID.CmdMarbleGetDataCsReq, CmdID.CmdMarbleGetDataScRsp },
.{ CmdID.CmdGetPreAvatarActivityListCsReq, CmdID.CmdGetPreAvatarActivityListScRsp },
.{ CmdID.CmdGetArchiveDataCsReq, CmdID.CmdGetArchiveDataScRsp },
};
const SuppressLogList = [_]CmdID{CmdID.CmdSceneEntityMoveCsReq};

View file

@ -1,7 +1,7 @@
const std = @import("std");
const builtin = @import("builtin");
const network = @import("network.zig");
const handlers = @import("handlers.zig");
const Cache = @import("../src/manager/scene_mgr.zig");
pub const std_options = .{
.log_level = switch (builtin.mode) {
@ -11,5 +11,11 @@ pub const std_options = .{
};
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
try Cache.initGameGlobals(allocator);
defer Cache.deinitGameGlobals();
try network.listen();
std.log.info("Server listening for connections.", .{});
}

View file

@ -1,12 +1,11 @@
const std = @import("std");
const protocol = @import("protocol");
const Session = @import("../Session.zig");
const Packet = @import("../Packet.zig");
const Config = @import("../services/config.zig");
const Data = @import("../data.zig");
const Lineup = @import("../services/lineup.zig");
const ChallengeData = @import("../services/challenge.zig");
const NodeCheck = @import("../commands/value.zig");
const scene_managers = @import("./scene_mgr.zig");
const ArrayList = std.ArrayList;
const Allocator = std.mem.Allocator;
@ -14,7 +13,6 @@ const CmdID = protocol.CmdID;
pub var selectedAvatarID = [_]u32{ 1304, 1313, 1406, 1004 };
// Function to check if an ID is in a list
fn isInList(id: u32, list: []const u32) bool {
for (list) |item| {
if (item == id) {
@ -34,6 +32,7 @@ const Element = enum {
Imaginary,
None,
};
fn getAvatarElement(avatar_id: u32) Element {
return switch (avatar_id) {
1105, 1107, 1111, 1206, 1215, 1221, 1302, 1309, 1315, 1408, 1410, 8001, 8002 => .Physical,
@ -46,6 +45,7 @@ fn getAvatarElement(avatar_id: u32) Element {
else => .None,
};
}
fn getAttackerBuffId() u32 {
const avatar_id = selectedAvatarID[Lineup.leader_slot];
const element = getAvatarElement(avatar_id);
@ -60,19 +60,16 @@ fn getAttackerBuffId() u32 {
.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),
};
try relic.sub_affix_list.append(protocol.RelicAffix{ .affix_id = stat1, .cnt = cnt1, .step = step1 });
try relic.sub_affix_list.append(protocol.RelicAffix{ .affix_id = stat2, .cnt = cnt2, .step = step2 });
try relic.sub_affix_list.append(protocol.RelicAffix{ .affix_id = stat3, .cnt = cnt3, .step = step3 });
try relic.sub_affix_list.append(protocol.RelicAffix{ .affix_id = stat4, .cnt = cnt4, .step = step4 });
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;
@ -84,13 +81,11 @@ fn createBattleAvatar(allocator: Allocator, avatarConf: Config.Avatar) !protocol
avatar.avatar_type = .AVATAR_FORMAL_TYPE;
if (isInList(avatar.id, &Data.EnhanceAvatarID)) avatar.enhanced_id = 1;
// Relics
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);
}
// Lightcone
const lc = protocol.BattleEquipment{
.id = avatarConf.lightcone.id,
.rank = avatarConf.lightcone.rank,
@ -99,7 +94,6 @@ fn createBattleAvatar(allocator: Allocator, avatarConf: Config.Avatar) !protocol
};
try avatar.equipment_list.append(lc);
// Skills
var talentLevel: u32 = 0;
const skill_list: []const u32 = if (isInList(avatar.id, &Data.Rem)) &Data.skills else &Data.skills_old;
for (skill_list) |elem| {
@ -117,27 +111,114 @@ fn createBattleAvatar(allocator: Allocator, avatarConf: Config.Avatar) !protocol
return avatar;
}
// Function to add technique buffs
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 = 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 = 141202, .owner_is_avatar = false, .dynamic_values = &.{
.{ .key = .{ .Const = "#ADF_1" }, .value = 1 },
.{ .key = .{ .Const = "#ADF_2" }, .value = 1 },
} },
},
},
};
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);
try targetIndexList.append(0);
var buffedAvatarId = avatar.id;
if (avatar.id == 8004) {
buffedAvatarId = 8003;
} else if (avatar.id == 8006) {
buffedAvatarId = 8005;
} else if (avatar.id == 8008) {
buffedAvatarId = 8007;
}
const buffedAvatarId = switch (avatar.id) {
8004 => 8003,
8006 => 8005,
8008 => 8007,
else => avatar.id,
};
for (Data.buffs_unlocked) |buffId| {
const idPrefix = buffId / 100;
if (idPrefix == buffedAvatarId) {
if (isInList(buffedAvatarId, &Data.IgnoreToughness)) {
var buff = protocol.BattleBuff{
.id = buffId,
.id = 1000119,
.level = 1,
.owner_index = @intCast(avatar_index),
.wave_flag = 0xFFFFFFFF,
@ -147,93 +228,42 @@ fn addTechniqueBuffs(allocator: Allocator, battle: *protocol.SceneBattleInfo, av
try buff.dynamic_values.append(.{ .key = .{ .Const = "SkillIndex" }, .value = 0 });
try battle.buff_list.append(buff);
}
}
if (isInList(buffedAvatarId, &Data.IgnoreToughness)) {
var buff_tough = protocol.BattleBuff{
.id = 1000119, //for is_ignore toughness
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 = targetIndexList,
.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 = targetIndexList,
.dynamic_values = ArrayList(protocol.BattleBuff.DynamicValuesEntry).init(allocator),
};
try buff_tough.dynamic_values.append(.{ .key = .{ .Const = "SkillIndex" }, .value = 0 });
try battle.buff_list.append(buff_tough);
}
if (buffedAvatarId == 1224) {
var buff_march = protocol.BattleBuff{
.id = 122401, //for hunt march 7th tech
.level = 1,
.owner_index = @intCast(avatar_index),
.wave_flag = 0xFFFFFFFF,
.target_index_list = targetIndexList,
.dynamic_values = ArrayList(protocol.BattleBuff.DynamicValuesEntry).init(allocator),
};
try buff_march.dynamic_values.appendSlice(&[_]protocol.BattleBuff.DynamicValuesEntry{
.{ .key = .{ .Const = "#ADF_1" }, .value = 3 },
.{ .key = .{ .Const = "#ADF_2" }, .value = 3 },
});
try battle.buff_list.append(buff_march);
}
if (buffedAvatarId == 1310) {
var buff_firefly = protocol.BattleBuff{
.id = 1000112, //for firefly tech
.level = 1,
.owner_index = @intCast(avatar_index),
.wave_flag = 0xFFFFFFFF,
.target_index_list = targetIndexList,
.dynamic_values = ArrayList(protocol.BattleBuff.DynamicValuesEntry).init(allocator),
};
try buff_firefly.dynamic_values.append(.{ .key = .{ .Const = "SkillIndex" }, .value = 0 });
try battle.buff_list.append(buff_firefly);
}
if (buffedAvatarId == 8007) {
var buff_rmc = protocol.BattleBuff{
.id = 800701, //for rmc tech
.level = 1,
.owner_index = @intCast(avatar_index),
.wave_flag = 0xFFFFFFFF,
.target_index_list = targetIndexList,
.dynamic_values = ArrayList(protocol.BattleBuff.DynamicValuesEntry).init(allocator),
};
try buff_rmc.dynamic_values.append(.{ .key = .{ .Const = "SkillIndex" }, .value = 0 });
try battle.buff_list.append(buff_rmc);
}
if (buffedAvatarId == 1412) {
var buff_ce = protocol.BattleBuff{
.id = 141201, //for cerydra core buff
.level = 1,
.owner_index = @intCast(avatar_index),
.wave_flag = 0xFFFFFFFF,
.target_index_list = targetIndexList,
.dynamic_values = ArrayList(protocol.BattleBuff.DynamicValuesEntry).init(allocator),
};
try buff_ce.dynamic_values.append(.{ .key = .{ .Const = "SkillIndex" }, .value = 0 });
var buff_target = protocol.BattleBuff{
.id = 141202, //for switch leader
.level = 1,
.owner_index = Lineup.leader_slot,
.wave_flag = 0xFFFFFFFF,
.target_index_list = targetIndexList,
.dynamic_values = ArrayList(protocol.BattleBuff.DynamicValuesEntry).init(allocator),
};
try buff_target.dynamic_values.appendSlice(&[_]protocol.BattleBuff.DynamicValuesEntry{
.{ .key = .{ .Const = "1" }, .value = 1 },
.{ .key = .{ .Const = "2" }, .value = 1 },
});
try battle.buff_list.append(buff_ce);
try battle.buff_list.append(buff_target);
try buff.dynamic_values.append(.{ .key = .{ .Const = "SkillIndex" }, .value = 0 });
try battle.buff_list.append(buff);
}
}
// Function to add future global buff.
fn addGolbalPassive(allocator: Allocator, battle: *protocol.SceneBattleInfo) !void {
if (isInList(1407, Data.AllAvatars)) { //support Castorice
if (isInList(1407, Data.AllAvatars)) {
var targetIndexList = ArrayList(u32).init(allocator);
try targetIndexList.append(0);
var mazebuff_data = protocol.BattleBuff{
@ -249,7 +279,6 @@ fn addGolbalPassive(allocator: Allocator, battle: *protocol.SceneBattleInfo) !vo
}
}
// Function to add elemental hit when trigger battle .
fn addTriggerAttack(allocator: Allocator, battle: *protocol.SceneBattleInfo) !void {
var targetIndexList = ArrayList(u32).init(allocator);
try targetIndexList.append(0);
@ -264,6 +293,7 @@ fn addTriggerAttack(allocator: Allocator, battle: *protocol.SceneBattleInfo) !vo
try attack.dynamic_values.append(.{ .key = .{ .Const = "SkillIndex" }, .value = 1 });
try battle.buff_list.append(attack);
}
fn createBattleInfo(allocator: Allocator, config: Config.GameConfig, stage_monster_wave_len: u32, stage_id: u32, rounds_limit: u32) protocol.SceneBattleInfo {
var battle = protocol.SceneBattleInfo.init(allocator);
battle.battle_id = config.battle_config.battle_id;
@ -274,7 +304,8 @@ fn createBattleInfo(allocator: Allocator, config: Config.GameConfig, stage_monst
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 { // Added monster_level
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 };
@ -284,6 +315,7 @@ fn addMonsterWaves(allocator: Allocator, battle: *protocol.SceneBattleInfo, mons
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);
@ -300,11 +332,10 @@ fn addStageBlessings(allocator: Allocator, battle: *protocol.SceneBattleInfo, bl
try battle.buff_list.append(buff);
}
}
fn addBattleTargets(allocator: Allocator, battle: *protocol.SceneBattleInfo) !void {
// PF/AS scoring
battle.battle_target_info = ArrayList(protocol.SceneBattleInfo.BattleTargetInfoEntry).init(allocator);
// target hardcode
var pfTargetHead = protocol.BattleTargetList{ .battle_target_list = ArrayList(protocol.BattleTarget).init(allocator) };
if (ChallengeData.on_challenge == true) {
if (NodeCheck.challenge_node == 0) {
@ -315,29 +346,57 @@ fn addBattleTargets(allocator: Allocator, battle: *protocol.SceneBattleInfo) !vo
} 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) {
// PF
30019000...30019100, 30021000...30021100, 30301000...30319000 => {
30019000...30019100, 30021000...30021100, 30301000...30319000 => { // PF
try battle.battle_target_info.append(.{ .key = 1, .value = pfTargetHead });
// fill blank target
for (2..4) |i| {
try battle.battle_target_info.append(.{ .key = @intCast(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 });
},
// AS
420100...420900 => {
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;
break;
}
}
if (avatarIndex >= 4) break;
}
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,
@ -347,83 +406,75 @@ pub const BattleManager = struct {
pub fn createBattle(self: *BattleManager) !protocol.SceneBattleInfo {
const config = try Config.loadGameConfig(self.allocator, "config.json");
var battle = createBattleInfo(self.allocator, config, @intCast(config.battle_config.monster_wave.items.len), config.battle_config.stage_id, config.battle_config.cycle_count);
var avatarIndex: u32 = 0;
var initial_mode = false;
while (true) {
if (!initial_mode) {
for (selectedAvatarID) |selected_id| {
for (config.avatar_config.items) |avatarConf| {
if (avatarConf.id == selected_id) {
const avatar = try createBattleAvatar(self.allocator, avatarConf);
try addTechniqueBuffs(self.allocator, &battle, avatar, avatarConf, avatarIndex);
try battle.battle_avatar_list.append(avatar);
avatarIndex += 1;
break;
}
}
if (avatarIndex >= 4) break;
}
}
if (avatarIndex == 0 and !initial_mode) {
initial_mode = true;
continue;
}
break;
}
try addMonsterWaves(self.allocator, &battle, config.battle_config.monster_wave, config.battle_config.monster_level);
try addTriggerAttack(self.allocator, &battle);
try addStageBlessings(self.allocator, &battle, config.battle_config.blessings.items);
try addGolbalPassive(self.allocator, &battle);
try addBattleTargets(self.allocator, &battle);
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,
&selectedAvatarID,
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: *scene_managers.GameConfigCache,
pub fn init(allocator: Allocator) ChallegeStageManager {
return ChallegeStageManager{ .allocator = allocator };
pub fn init(allocator: Allocator, cache: *scene_managers.GameConfigCache) ChallegeStageManager {
return ChallegeStageManager{
.allocator = allocator,
.game_config_cache = cache,
};
}
pub fn createChallegeStage(self: *ChallegeStageManager) !protocol.SceneBattleInfo {
if (ChallengeData.challenge_stageID == 0) {
std.log.info("Challenge stage ID is 0, skipping challenge battle creation and returning an empty battle info.", .{});
return protocol.SceneBattleInfo.init(self.allocator);
}
const config = try Config.loadGameConfig(self.allocator, "config.json");
const stage = try Config.loadStageConfig(self.allocator, "resources/StageConfig.json");
const stage_config = &self.game_config_cache.stage_config;
var battle: protocol.SceneBattleInfo = undefined;
for (stage.stage_config.items) |stageConf| {
var found_stage = false;
for (stage_config.stage_config.items) |stageConf| {
if (stageConf.stage_id == ChallengeData.challenge_stageID) {
battle = createBattleInfo(self.allocator, config, @intCast(stageConf.monster_list.items.len), stageConf.stage_id, if (ChallengeData.challenge_mode != 1) 30 else 4);
var avatarIndex: u32 = 0;
var initial_mode = false;
while (true) {
if (!initial_mode) {
for (selectedAvatarID) |selected_id| {
for (config.avatar_config.items) |avatarConf| {
if (avatarConf.id == selected_id) {
const avatar = try createBattleAvatar(self.allocator, avatarConf);
try addTechniqueBuffs(self.allocator, &battle, avatar, avatarConf, avatarIndex);
try battle.battle_avatar_list.append(avatar);
avatarIndex += 1;
battle = createBattleInfo(
self.allocator,
config,
@intCast(stageConf.monster_list.items.len),
stageConf.stage_id,
if (ChallengeData.challenge_mode != 1) 30 else 4,
);
found_stage = true;
try commonBattleSetup(
self.allocator,
&battle,
&selectedAvatarID,
config.avatar_config.items,
stageConf.monster_list,
stageConf.level,
ChallengeData.challenge_blessing,
);
break;
}
}
if (avatarIndex >= 4) break;
}
}
if (avatarIndex == 0 and !initial_mode) {
initial_mode = true;
continue;
}
break;
}
try addMonsterWaves(self.allocator, &battle, stageConf.monster_list, stageConf.level);
try addTriggerAttack(self.allocator, &battle);
try addStageBlessings(self.allocator, &battle, ChallengeData.challenge_blessing);
try addGolbalPassive(self.allocator, &battle);
try addBattleTargets(self.allocator, &battle);
break;
}
if (!found_stage) {
std.log.err("Challenge stage with ID {d} not found in config.", .{ChallengeData.challenge_stageID});
return error.StageNotFound;
}
return battle;
}

View file

@ -1,72 +1,51 @@
const std = @import("std");
const protocol = @import("protocol");
const Session = @import("../Session.zig");
const Session = @import("../session.zig");
const Packet = @import("../Packet.zig");
const Config = @import("../services/config.zig");
const Res_config = @import("../services/res_config.zig");
const Data = @import("../data.zig");
const UidGenerator = @import("../services/item.zig").UidGenerator;
const Item = @import("../services/item.zig");
const UidGenerator = Item.UidGenerator();
const ArrayList = std.ArrayList;
const Allocator = std.mem.Allocator;
const CmdID = protocol.CmdID;
pub const SceneManager = struct {
allocator: std.mem.Allocator,
allocator: Allocator,
pub fn init(allocator: std.mem.Allocator) SceneManager {
pub fn init(allocator: Allocator) SceneManager {
return SceneManager{ .allocator = allocator };
}
pub fn createScene(
self: *SceneManager,
plane_id: u32,
floor_id: u32,
entry_id: u32,
teleport_id: u32,
) !protocol.SceneInfo {
const config = try Config.loadGameConfig(self.allocator, "config.json");
const res_config = try Res_config.anchorLoader(self.allocator, "resources/res.json");
var generator = UidGenerator().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;
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) {
var scene_group = protocol.SceneEntityGroupInfo.init(self.allocator);
scene_group.state = 1;
for (config.avatar_config.items) |avatarConf| {
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| {
try scene_group.entity_list.append(.{
.inst_id = 1,
.entity_id = @intCast(avatarConf.id + 100000),
.entity = .{ .actor = .{
.base_avatar_id = avatarConf.id,
.avatar_type = .AVATAR_FORMAL_TYPE,
.uid = 0,
.uid = uid,
.map_layer = 0,
} },
.motion = .{
.pos = .{ .x = teleConf.pos.x, .y = teleConf.pos.y, .z = teleConf.pos.z },
.rot = .{ .x = teleConf.rot.x, .y = teleConf.rot.y, .z = teleConf.rot.z },
},
.motion = .{ .pos = tele_pos, .rot = tele_rot },
});
}
try scene_info.entity_group_list.append(scene_group);
}
}
if (scene_info.plane_id != 10000 and scene_info.plane_id != 10202 and sceneConf.planeID == scene_info.plane_id) {
for (sceneConf.props.items) |propConf| {
var scene_group = try getOrCreateGroup(&group_map, propConf.groupId, self.allocator);
var prop_info = protocol.ScenePropInfo.init(self.allocator);
fn addPropEntities(
allocator: Allocator,
group_map: *std.AutoHashMap(u32, protocol.SceneEntityGroupInfo),
prop_configs: []const Res_config.Props,
generator: *UidGenerator,
) !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(.{
@ -80,9 +59,16 @@ pub const SceneManager = struct {
},
});
}
for (sceneConf.monsters.items) |monsConf| {
var scene_group = try getOrCreateGroup(&group_map, monsConf.groupId, self.allocator);
var monster_info = protocol.SceneNpcMonsterInfo.init(self.allocator);
}
fn addMonsterEntities(
allocator: Allocator,
group_map: *std.AutoHashMap(u32, protocol.SceneEntityGroupInfo),
monster_configs: []const Res_config.Monsters,
generator: *UidGenerator,
) !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;
@ -98,7 +84,56 @@ pub const SceneManager = struct {
});
}
}
pub fn createScene(
self: *SceneManager,
plane_id: u32,
floor_id: u32,
entry_id: u32,
teleport_id: u32,
) !protocol.SceneInfo {
const config = try Config.loadGameConfig(self.allocator, "config.json");
const res_config = global_game_config_cache.res_config;
var generator = Item.UidGenerator().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;
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) {
var scene_group = protocol.SceneEntityGroupInfo.init(self.allocator);
scene_group.state = 1;
const proto_tele_pos = protocol.Vector{
.x = teleConf.pos.x,
.y = teleConf.pos.y,
.z = teleConf.pos.z,
};
const proto_tele_rot = protocol.Vector{
.x = teleConf.rot.x,
.y = teleConf.rot.y,
.z = teleConf.rot.z,
};
try addAvatarEntities(&scene_group, config.avatar_config.items, proto_tele_pos, proto_tele_rot, 0);
try scene_info.entity_group_list.append(scene_group);
break;
}
}
if (scene_info.plane_id != 10000 and scene_info.plane_id != 10202 and sceneConf.planeID == scene_info.plane_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| {
try scene_info.entity_group_list.append(entry.value_ptr.*);
@ -126,7 +161,7 @@ pub const SceneManager = struct {
}
return scene_info;
}
fn getOrCreateGroup(group_map: *std.AutoHashMap(u32, protocol.SceneEntityGroupInfo), group_id: u32, allocator: std.mem.Allocator) !*protocol.SceneEntityGroupInfo {
fn getOrCreateGroup(group_map: *std.AutoHashMap(u32, protocol.SceneEntityGroupInfo), group_id: u32, allocator: Allocator) !*protocol.SceneEntityGroupInfo {
if (group_map.getPtr(group_id)) |existing_group| {
return existing_group;
}
@ -137,9 +172,11 @@ pub const SceneManager = struct {
return group_map.getPtr(group_id).?;
}
};
pub const ChallengeSceneManager = struct {
allocator: std.mem.Allocator,
pub fn init(allocator: std.mem.Allocator) ChallengeSceneManager {
allocator: Allocator,
pub fn init(allocator: Allocator) ChallengeSceneManager {
return ChallengeSceneManager{ .allocator = allocator };
}
pub fn createScene(
@ -154,8 +191,8 @@ pub const ChallengeSceneManager = struct {
group_id: u32,
maze_group_id: u32,
) !protocol.SceneInfo {
const res_config = try Res_config.anchorLoader(self.allocator, "resources/res.json");
var generator = UidGenerator().init();
const res_config = global_game_config_cache.res_config;
var generator = Item.UidGenerator().init();
var scene_info = protocol.SceneInfo.init(self.allocator);
scene_info.game_mode_type = 4;
@ -168,17 +205,17 @@ pub const ChallengeSceneManager = struct {
.group_id = maze_group_id,
.is_default = true,
});
{ // Character
{ // Character Group
var scene_group = protocol.SceneEntityGroupInfo.init(self.allocator);
scene_group.state = 1;
scene_group.group_id = 0;
for (avatar_list.items) |avatarConf| {
for (avatar_list.items) |avatar_base_id| {
try scene_group.entity_list.append(.{
.inst_id = 1,
.entity_id = @intCast(avatarConf + 100000),
.entity_id = @intCast(avatar_base_id + 100000),
.entity = .{
.actor = .{
.base_avatar_id = avatarConf,
.base_avatar_id = avatar_base_id,
.avatar_type = .AVATAR_FORMAL_TYPE,
.uid = 1,
.map_layer = 0,
@ -191,10 +228,10 @@ pub const ChallengeSceneManager = struct {
}
for (res_config.scene_config.items) |sceneConf| {
if (sceneConf.planeID == scene_info.plane_id) {
for (sceneConf.monsters.items) |monsConf| { //create monster
for (sceneConf.monsters.items) |monsConf| {
if (monsConf.groupId == group_id) {
var scene_group = protocol.SceneEntityGroupInfo.init(self.allocator);
scene_group.state = 1;
if (monsConf.groupId == group_id) {
scene_group.group_id = group_id;
var monster_info = protocol.SceneNpcMonsterInfo.init(self.allocator);
@ -213,13 +250,10 @@ pub const ChallengeSceneManager = struct {
},
});
try scene_info.entity_group_list.append(scene_group);
break;
}
}
}
}
for (res_config.scene_config.items) |sceneConf| {
if (sceneConf.planeID == scene_info.plane_id) {
for (sceneConf.props.items) |propConf| { //create props
for (sceneConf.props.items) |propConf| {
var scene_group = protocol.SceneEntityGroupInfo.init(self.allocator);
scene_group.state = 1;
scene_group.group_id = group_id;
@ -232,7 +266,7 @@ pub const ChallengeSceneManager = struct {
.entity = .{ .prop = prop_info },
.group_id = group_id,
.inst_id = propConf.instId,
.entity_id = 0,
.entity_id = generator.nextId(),
.motion = .{
.pos = .{ .x = propConf.pos.x, .y = propConf.pos.y, .z = propConf.pos.z },
.rot = .{ .x = propConf.rot.x, .y = propConf.rot.y, .z = propConf.rot.z },
@ -245,10 +279,9 @@ pub const ChallengeSceneManager = struct {
return scene_info;
}
};
pub const MazeMapManager = struct {
allocator: std.mem.Allocator,
pub fn init(allocator: std.mem.Allocator) MazeMapManager {
allocator: Allocator,
pub fn init(allocator: Allocator) MazeMapManager {
return MazeMapManager{ .allocator = allocator };
}
pub fn setMazeMapData(
@ -256,21 +289,22 @@ pub const MazeMapManager = struct {
map_info: *protocol.SceneMapInfo,
floor_id: u32,
) !void {
const config = try Config.loadMapEntranceConfig(self.allocator, "resources/MapEntrance.json");
const res_config = try Res_config.anchorLoader(self.allocator, "resources/res.json");
const map_entrance_config = global_game_config_cache.map_entrance_config;
const res_config = global_game_config_cache.res_config;
var plane_ids = ArrayList(u32).init(self.allocator);
for (config.map_entrance_config.items) |entrConf| {
defer plane_ids.deinit();
for (map_entrance_config.map_entrance_config.items) |entrConf| {
if (entrConf.floor_id == floor_id) {
try plane_ids.append(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) |resConf| {
for (res_config.scene_config.items) |sceneConf| {
for (plane_ids.items) |plane_id| {
if (resConf.planeID == plane_id) {
for (resConf.props.items) |propConf| {
if (sceneConf.planeID == plane_id) {
for (sceneConf.props.items) |propConf| {
try map_info.maze_group_list.append(protocol.MazeGroup{
.NOBKEONAKLE = ArrayList(u32).init(self.allocator),
.group_id = propConf.groupId,
@ -286,3 +320,35 @@ pub const MazeMapManager = struct {
}
}
};
pub const GameConfigCache = struct {
allocator: Allocator,
res_config: Res_config.SceneConfig,
map_entrance_config: Config.MapEntranceConfig,
stage_config: Config.StageConfig,
pub fn init(allocator: Allocator) !GameConfigCache {
const res_cfg = try Res_config.anchorLoader(allocator, "resources/res.json");
const map_entr_cfg = try Config.loadMapEntranceConfig(allocator, "resources/MapEntrance.json");
const stage_cfg = try Config.loadStageConfig(allocator, "resources/StageConfig.json");
return GameConfigCache{
.allocator = allocator,
.res_config = res_cfg,
.map_entrance_config = map_entr_cfg,
.stage_config = stage_cfg,
};
}
pub fn deinit(_: *GameConfigCache) void {}
};
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);
}
pub fn deinitGameGlobals() void {
global_game_config_cache.deinit();
}

View file

@ -1,6 +1,6 @@
const std = @import("std");
const Session = @import("Session.zig");
const Cache = @import("../src/manager/scene_mgr.zig");
const Allocator = std.mem.Allocator;
pub fn listen() !void {
@ -16,21 +16,31 @@ pub fn listen() !void {
const conn = listener.accept() catch continue;
errdefer conn.stream.close();
const thread = try std.Thread.spawn(.{}, runSession, .{ conn.address, conn.stream });
const thread = try std.Thread.spawn(.{}, runSession, .{
conn.address,
conn.stream,
Cache.global_main_allocator,
&Cache.global_game_config_cache,
});
thread.detach();
}
}
fn runSession(address: std.net.Address, stream: std.net.Stream) !void {
fn runSession(
address: std.net.Address,
stream: std.net.Stream,
main_allocator: Allocator,
game_config_cache: *Cache.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 allocator = gpa.allocator();
const session_allocator = gpa.allocator();
const session = try allocator.create(Session);
session.* = Session.init(address, stream, allocator);
const session = try session_allocator.create(Session);
session.* = Session.init(address, stream, session_allocator, main_allocator, game_config_cache);
if (session.*.run()) |_| {
std.log.info("client from {} disconnected", .{address});
@ -38,5 +48,5 @@ fn runSession(address: std.net.Address, stream: std.net.Stream) !void {
std.log.err("session disconnected with an error: {}", .{err});
}
allocator.destroy(session);
session_allocator.destroy(session);
}

View file

@ -8,6 +8,7 @@ const ChallengeData = @import("challenge.zig");
const BattleManager = @import("../manager/battle_mgr.zig").BattleManager;
const ChallegeStageManager = @import("../manager/battle_mgr.zig").ChallegeStageManager;
const NodeCheck = @import("../commands/value.zig");
const scene_managers = @import("../manager/scene_mgr.zig");
const ArrayList = std.ArrayList;
const Allocator = std.mem.Allocator;
@ -78,7 +79,7 @@ pub fn onStartBattleCollege(session: *Session, packet: *const Packet, allocator:
pub fn onSceneCastSkill(session: *Session, packet: *const Packet, allocator: Allocator) !void {
var battle_mgr = BattleManager.init(allocator);
const battle = try battle_mgr.createBattle();
var challege_mgr = ChallegeStageManager.init(allocator);
var challege_mgr = ChallegeStageManager.init(allocator, &scene_managers.global_game_config_cache);
const challenge = try challege_mgr.createChallegeStage();
const req = try packet.getProto(protocol.SceneCastSkillCsReq, allocator);
var battle_info: ?protocol.SceneBattleInfo = null;
@ -126,7 +127,7 @@ pub fn onSceneCastSkill(session: *Session, packet: *const Packet, allocator: All
pub fn onGetCurBattleInfo(session: *Session, _: *const Packet, allocator: Allocator) !void {
var battle_mgr = BattleManager.init(allocator);
const battle = try battle_mgr.createBattle();
var challege_mgr = ChallegeStageManager.init(allocator);
var challege_mgr = ChallegeStageManager.init(allocator, &scene_managers.global_game_config_cache);
const challenge = try challege_mgr.createChallegeStage();
var rsp = protocol.GetCurBattleInfoScRsp.init(allocator);

View file

@ -11,7 +11,7 @@ const SceneManager = @import("../manager/scene_mgr.zig").SceneManager;
const ChallengeSceneManager = @import("../manager/scene_mgr.zig").ChallengeSceneManager;
const LineupManager = @import("../manager/lineup_mgr.zig").LineupManager;
const ChallengeLineupManager = @import("../manager/lineup_mgr.zig").ChallengeLineupManager;
const NodeCheck = @import("../commands/value.zig");
const Value = @import("../commands/value.zig");
const ArrayList = std.ArrayList;
const Allocator = std.mem.Allocator;
@ -159,10 +159,23 @@ pub fn onGetCurChallengeScRsp(session: *Session, _: *const Packet, allocator: Al
pub fn onStartChallenge(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.StartChallengeCsReq, allocator);
var rsp = protocol.StartChallengeScRsp.init(allocator);
if (Value.custom_mode == true) {
challengeID = Value.selected_challenge_id;
challenge_buffID = Value.selected_buff_id;
if (Value.challenge_node == 0) {
for (req.first_lineup.items) |id| {
try avatar_list.append(id);
}
} else {
{
for (req.second_lineup.items) |id| {
try avatar_list.append(id);
}
}
}
} else {
challengeID = req.challenge_id;
if (NodeCheck.challenge_node == 0) {
if (Value.challenge_node == 0) {
for (req.first_lineup.items) |id| {
try avatar_list.append(id);
}
@ -179,6 +192,7 @@ pub fn onStartChallenge(session: *Session, packet: *const Packet, allocator: All
if (challengeID > 30000)
challenge_buffID = req.stage_info.?.KFELKJLDKEH.?.boss_info.buff_two;
}
}
var lineup_manager = ChallengeLineupManager.init(allocator);
const lineup_info = try lineup_manager.createLineup(avatar_list);

View file

@ -74,8 +74,10 @@ const Activity = struct {
activity_module_list: ArrayList(u32),
panel_id: u32,
};
const ChallengeConfig = struct {
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),
@ -101,6 +103,15 @@ const MazePlane = struct {
challenge_plane_id: u32,
world_id: u32,
};
const BuffList = struct {
id: u32,
name: []const u8,
};
const TextMap = struct {
group_id: u32,
buff_list1: ArrayList(BuffList),
buff_list2: ArrayList(BuffList),
};
pub const GameConfig = struct {
battle_config: BattleConfig,
avatar_config: ArrayList(Avatar),
@ -132,6 +143,9 @@ pub const MapEntranceConfig = struct {
pub const MazePlaneConfig = struct {
maze_plane_config: ArrayList(MazePlane),
};
pub const TextMapConfig = struct {
text_map_config: ArrayList(TextMap),
};
const ErrorSet = error{ CommandError, SystemResources, Unexpected, AccessDenied, WouldBlock, ConnectionResetByPeer, OutOfMemory, DiskQuota, FileTooBig, InputOutput, NoSpaceLeft, DeviceBusy, InvalidArgument, BrokenPipe, OperationAborted, NotOpenForWriting, LockViolation, Overflow, InvalidCharacter, ProcessFdQuotaExceeded, SystemFdQuotaExceeded, SymLinkLoop, NameTooLong, FileNotFound, NotDir, NoDevice, SharingViolation, PathAlreadyExists, PipeBusy, InvalidUtf8, InvalidWtf8, BadPathName, NetworkNotFound, AntivirusInterference, IsDir, FileLocksNotSupported, FileBusy, ConnectionTimedOut, NotOpenForReading, SocketNotConnected, Unseekable, UnexpectedToken, InvalidNumber, InvalidEnumTag, DuplicateField, UnknownField, MissingField, LengthMismatch, SyntaxError, UnexpectedEndOfInput, BufferUnderrun, ValueTooLong, InsufficientTokens, InvalidFormat };
pub fn loadConfig(
@ -195,6 +209,10 @@ pub fn loadMazePlaneConfig(allocator: Allocator, filename: []const u8) ErrorSet!
return loadConfig(MazePlaneConfig, parseMazePlaneConfig, allocator, filename);
}
pub fn loadTextMapConfig(allocator: Allocator, filename: []const u8) ErrorSet!TextMapConfig {
return loadConfig(TextMapConfig, parseTextMapConfig, allocator, filename);
}
pub fn parseConfig(root: std.json.Value, allocator: Allocator) ErrorSet!GameConfig {
const battle_config_json = root.object.get("battle_config").?;
var battle_config = BattleConfig{
@ -345,6 +363,8 @@ fn parseChallengeConfig(root: std.json.Value, allocator: Allocator) ErrorSet!Cha
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),
@ -420,6 +440,39 @@ fn parseMazePlaneConfig(root: std.json.Value, allocator: Allocator) ErrorSet!Maz
.maze_plane_config = maze_plane_config,
};
}
fn parseTextMapConfig(root: std.json.Value, allocator: Allocator) !TextMapConfig {
var text_map_config = ArrayList(TextMap).init(allocator);
for (root.object.get("text_map_config").?.array.items) |entry| {
var buff_list1 = ArrayList(BuffList).init(allocator);
var buff_list2 = ArrayList(BuffList).init(allocator);
for (entry.object.get("BuffList1").?.array.items) |buff| {
try buff_list1.append(BuffList{
.id = @intCast(buff.object.get("id").?.integer),
.name = buff.object.get("name").?.string,
});
}
for (entry.object.get("BuffList2").?.array.items) |buff| {
try buff_list2.append(BuffList{
.id = @intCast(buff.object.get("id").?.integer),
.name = buff.object.get("name").?.string,
});
}
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,
};
}
fn parseRelic(relic_str: []const u8, allocator: Allocator) !Relic {
var tokens = ArrayList([]const u8).init(allocator);
defer tokens.deinit();

View file

@ -3,7 +3,7 @@ const ArrayList = std.ArrayList;
const std = @import("std");
const ResConfig = struct {
pub const ResConfig = struct {
planeID: u32,
props: ArrayList(Props),
monsters: ArrayList(Monsters),
@ -22,7 +22,7 @@ const Teleports = struct {
rot: Vector,
teleportId: u32,
};
const Monsters = struct {
pub const Monsters = struct {
groupId: u32,
instId: u32,
eventId: u32,
@ -30,7 +30,7 @@ const Monsters = struct {
rot: Vector,
monsterId: u32,
};
const Props = struct {
pub const Props = struct {
groupId: u32,
instId: u32,
propState: u32,

View file

@ -7,6 +7,7 @@ const Data = @import("../data.zig");
const Res_config = @import("res_config.zig");
const LineupManager = @import("../manager/lineup_mgr.zig").LineupManager;
const SceneManager = @import("../manager/scene_mgr.zig").SceneManager;
const CacheScene = @import("../manager/scene_mgr.zig");
const ArrayList = std.ArrayList;
const Allocator = std.mem.Allocator;
@ -38,7 +39,8 @@ pub fn onSceneEntityMove(session: *Session, packet: *const Packet, allocator: Al
}
pub fn onEnterScene(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const entrance_config = try Config.loadMapEntranceConfig(allocator, "resources/MapEntrance.json");
const entrance_config = &CacheScene.global_game_config_cache.map_entrance_config;
const req = try packet.getProto(protocol.EnterSceneCsReq, allocator);
var lineup_mgr = LineupManager.init(allocator);
const lineup = try lineup_mgr.createLineup();
@ -71,7 +73,7 @@ pub fn onEnterScene(session: *Session, packet: *const Packet, allocator: Allocat
pub fn onGetSceneMapInfo(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.GetSceneMapInfoCsReq, allocator);
const res_config = try Res_config.anchorLoader(allocator, "resources/res.json");
const cached_res_config = &CacheScene.global_game_config_cache.res_config;
const ranges = [_][2]usize{
.{ 0, 101 },
.{ 10000, 10051 },
@ -94,7 +96,7 @@ pub fn onGetSceneMapInfo(session: *Session, packet: *const Packet, allocator: Al
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| {
for (cached_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);
@ -125,7 +127,7 @@ pub fn onGetSceneMapInfo(session: *Session, packet: *const Packet, allocator: Al
}
}
try rsp.scene_map_info.append(map_info);
try session.send(CmdID.CmdGetSceneMapInfoScRsp, rsp);
try session.send(protocol.CmdID.CmdGetSceneMapInfoScRsp, rsp);
}
}
pub fn onGetUnlockTeleport(session: *Session, _: *const Packet, allocator: Allocator) !void {
@ -150,8 +152,7 @@ pub fn onEnterSection(session: *Session, packet: *const Packet, allocator: Alloc
pub fn onGetEnteredScene(session: *Session, _: *const Packet, allocator: Allocator) !void {
var rsp = protocol.GetEnteredSceneScRsp.init(allocator);
var noti = protocol.EnteredSceneChangeScNotify.init(allocator);
const entrance_config = try Config.loadMapEntranceConfig(allocator, "resources/MapEntrance.json");
const entrance_config = &CacheScene.global_game_config_cache.map_entrance_config;
for (entrance_config.map_entrance_config.items) |entrance| {
try rsp.entered_scene_info_list.append(protocol.EnteredSceneInfo{
.floor_id = entrance.floor_id,

View file

@ -6,5 +6,21 @@
"ifix_version": "0",
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_10872485_88661d25b99c_8af3232d484baf",
"lua_version": ""
},
"CNBETAWin3.4.52": {
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_10958287_efcd7e2ecb36_41640921a4a7ad",
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_10958287_6c138ff4c1b7_38b93698a31d0a",
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_10894308_758b6d45fde6_ac9089c9b1e0a0",
"ifix_version": "0",
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_10958408_e8e889b9db41_0897ed66246acc",
"lua_version": ""
},
"CNBETAWin3.4.53": {
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_11042804_ec670e5793f5_b9cbdedb0f66c3",
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_11042804_e1fd49abc9a3_c60b351cb94b11",
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_10894308_758b6d45fde6_ac9089c9b1e0a0",
"ifix_version": "0",
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_11043241_a4527ee0b70c_d6ce1726d05175",
"lua_version": ""
}
}

View file

@ -0,0 +1,773 @@
{
"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 of all allies have 30% All-Type RES against enemy targets."
}
],
"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": "Unknown"
},
{
"id": 3111063,
"name": "Unknown"
},
{
"id": 3111061,
"name": "Unknown"
}
],
"BuffList2": [
{
"id": 3111059,
"name": "Unknown"
},
{
"id": 3111060,
"name": "Unknown"
},
{
"id": 3111064,
"name": "Unknown"
}
]
},
{
"GroupID": 3012,
"BuffList1": [
{
"id": 3111062,
"name": "Unknown"
},
{
"id": 3111063,
"name": "Unknown"
},
{
"id": 3111061,
"name": "Unknown"
}
],
"BuffList2": [
{
"id": 3111059,
"name": "Unknown"
},
{
"id": 3111060,
"name": "Unknown"
},
{
"id": 3111064,
"name": "Unknown"
}
]
},
{
"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 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 battle, and after they take action, additionally adds #2[i] stack(s) of 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 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": "Unknown"
},
{
"id": 3031345,
"name": "Unknown"
},
{
"id": 3031346,
"name": "Unknown"
}
],
"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."
}
]
}
]
}

View file

@ -6076,6 +6076,34 @@
},
"RewardID": 201
},
{
"CanReview": true,
"FinishTriggerParams": [
{
"TriggerParam": "104061103",
"TriggerType": "FinishSubMission"
}
],
"GroupID": 2245,
"MessageText": {
"Hash": 530566966,
"hash64": 6926758272334550000
},
"Order": 433,
"RewardID": 201,
"TriggerParams": [
{
"TriggerParam": "20451060",
"TriggerType": "EnterBattle"
}
],
"TutorialGuideIDList": [
224501,
224502,
224503
],
"TutorialType": 1
},
{
"TutorialGuideIDList": [
932301,