fix(scene): unlock all teleports across maps, merge resource configs, fix scenary not load error, and add quick farm battle handlers

This commit is contained in:
CAtlas0784 committed 2026-10-16 20:08:13 +07:00
1 parent 9aab62414f
commit 163d4433e6
47 files changed
+2024977 -5992

No files matched your search

+160
View File
@@ -0,0 +1,160 @@
{
"avatar_config": [
{
"name": "1503",
"id": 1503,
"hp": 100,
"sp": 50,
"level": 80,
"promotion": 6,
"rank": 0,
"lightcone": {
"id": 23055,
"rank": 1,
"level": 80,
"promotion": 6
},
"relics": [
"61331,15,1,4,7:3:2,3:1:2,6:3:3,4:2:0",
"61332,15,1,4,6:1:1,8:3:3,5:2:1,7:3:5",
"61333,15,6,4,7:3:3,6:1:2,1:3:4,2:2:2",
"61334,15,4,4,6:1:1,1:2:2,11:4:5,8:2:1",
"63085,15,3,4,7:3:3,11:3:3,10:2:2,12:1:2",
"63086,15,2,4,2:1:1,7:3:5,6:3:4,8:2:2"
],
"use_technique": true
},
{
"name": "1511",
"id": 1511,
"hp": 100,
"sp": 50,
"level": 80,
"promotion": 6,
"rank": 6,
"lightcone": {
"id": 23065,
"rank": 5,
"level": 80,
"promotion": 6
},
"relics": [
"61331,15,1,4,9:2:3,7:2:1,10:2:1,8:3:1",
"61332,15,1,4,9:1:2,6:4:1,8:2:3,7:2:2",
"61333,15,5,4,1:2:4,7:2:2,8:2:4,4:3:4",
"61334,15,4,4,9:2:2,1:2:1,11:4:4,8:1:2",
"63295,15,1,4,8:3:3,3:2:4,9:2:2,7:2:2",
"63296,15,3,4,7:1:1,1:4:2,11:1:2,8:3:4"
],
"use_technique": true
},
{
"name": "Trailblazer(Harmony)",
"id": 8006,
"hp": 100,
"sp": 50,
"level": 80,
"promotion": 6,
"rank": 6,
"lightcone": {
"id": 23019,
"rank": 5,
"level": 80,
"promotion": 6
},
"relics": [
"61181,15,1,4,12:6:12,7:1:2,4:1:2,6:1:2",
"61182,15,1,4,7:1:2,12:6:12,6:1:2,4:1:2",
"61183,15,3,4,7:1:2,12:6:12,1:1:2,4:1:2",
"61184,15,4,4,12:6:12,6:1:2,4:1:2,1:1:2",
"63075,15,1,4,12:4:8,7:3:6,6:1:2,1:1:2",
"63076,15,1,4,7:6:12,1:1:2,4:1:2,6:1:2"
],
"use_technique": true
},
{
"name": "Trailblazer(Remembrance)",
"id": 8008,
"hp": 100,
"sp": 50,
"level": 80,
"promotion": 6,
"rank": 6,
"lightcone": {
"id": 24005,
"rank": 5,
"level": 80,
"promotion": 6
},
"relics": [
"61231,15,1,4,9:2:4,7:1:2,5:5:10,2:1:2",
"61232,15,1,4,7:1:2,9:6:12,5:1:2,4:1:2",
"61233,15,5,4,5:6:12,2:1:2,7:1:2,4:1:2",
"61234,15,4,4,9:6:12,5:1:2,2:1:2,4:1:2",
"63175,15,6,4,9:3:6,5:1:2,2:1:2,7:4:8",
"63176,15,2,4,5:1:2,7:6:12,9:1:2,2:1:2"
],
"use_technique": true
},
{
"name": "Trailblazer(Elation)",
"id": 8010,
"hp": 100,
"sp": 50,
"level": 80,
"promotion": 6,
"rank": 6,
"lightcone": {
"id": 24006,
"rank": 5,
"level": 80,
"promotion": 6
},
"relics": [
"61301,15,1,4,5:1:2,7:6:12,8:1:2,9:1:2",
"61302,15,1,4,5:1:2,7:2:4,8:5:10,9:1:2",
"61303,15,4,4,5:1:2,2:1:2,7:1:2,9:6:12",
"61304,15,4,4,2:1:2,5:1:2,8:2:4,9:5:10",
"63255,15,2,4,2:1:2,7:1:2,8:1:2,9:6:12",
"63256,15,2,4,5:1:2,7:1:2,8:1:2,9:6:12"
],
"use_technique": true
},
{
"name": "Acheron",
"id": 1308,
"hp": 100,
"sp": 50,
"level": 80,
"promotion": 6,
"rank": 0,
"lightcone": {
"id": 23024,
"rank": 1,
"level": 80,
"promotion": 6
},
"relics": [
"61101,15,1,4,8:3:3,7:2:3,9:2:1,5:2:1",
"61102,15,1,4,7:2:1,8:2:4,9:3:2,1:2:4",
"61103,15,5,4,8:2:1,3:3:3,10:2:3,7:2:3",
"61104,15,4,4,3:2:0,8:3:3,4:2:1,9:2:3",
"63145,15,7,4,7:1:0,8:3:2,5:2:4,9:3:3",
"63146,15,4,4,4:3:3,2:1:2,8:4:3,9:1:1"
],
"use_technique": true
}
],
"battle_config": {
"battle_id": 1,
"stage_id": 1052106,
"cycle_count": 30,
"monster_wave": [
[
5035011
]
],
"monster_level": 82,
"blessings": []
}
}
+19042 -5530
View File
File diff suppressed because it is too large. Load diff
+58
View File
@@ -0,0 +1,58 @@
{
"CNBETAWin4.5.51": {
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_16228654_7d9ae69e532c_ff87719729c35f",
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_16297504_50bd375a94d3_6512106296b202",
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_0_40d2ce0253_c61ba99f70b885",
"ifix_version": "0",
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_16228766_9f45f1eb6490_9f172521b06e84",
"lua_version": ""
},
"CNBETAWin4.5.52": {
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_16317320_6a387bae4513_2f3e5644103a8a",
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_16353768_a1fcf7f6043c_508532c7de08bc",
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_0_40d2ce0253_c61ba99f70b885",
"ifix_version": "0",
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_16317476_035e0a79224b_ed91499b6ef70b",
"lua_version": ""
},
"CNBETAWin4.5.53": {
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_16397599_dc132a900809_7e7a4d9792c13e",
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_16399740_f7f17473a4be_cd161900e78e53",
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_0_40d2ce0253_c61ba99f70b885",
"ifix_version": "0",
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_16399471_bf17e3d5c3d7_5605fcd6b48271",
"lua_version": ""
},
"CNBETAWin4.5.54": {
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_16470326_3d7642f3b95a_1bb36b1c1b50ea",
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_16468722_9a3bb59a4d54_a6b44310dd6f23",
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_0_40d2ce0253_c61ba99f70b885",
"ifix_version": "0",
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_16475505_fcd1e79987c2_dedcc3e216e164",
"lua_version": ""
},
"CNBETAWin4.6.51": {
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_16685262_bd0485299f5b_858f2db22344cc",
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_16706787_90982cd003eb_5ed57e0de4e2b4",
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_0_40d2ce0253_c61ba99f70b885",
"ifix_version": "0",
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_16679499_5abbd35a74c5_a28e83e88b78d5",
"lua_version": ""
},
"CNBETAWin4.6.52": {
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_16745161_ab68020d7aa9_43440a8f18e051",
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_16755051_300cb1cd6a4f_c87db7a5d4f2bf",
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_0_40d2ce0253_c61ba99f70b885",
"ifix_version": "0",
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_16745283_0b6f44e69f46_3bf68a1c7368cd",
"lua_version": ""
},
"CNBETAWin4.6.53": {
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_16818953_d4345bbd54a2_93c4aa5dfb978d",
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_16833202_959c316ef547_ac20bef3513343",
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_0_40d2ce0253_c61ba99f70b885",
"ifix_version": "0",
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_16819203_679727d45bd7_91ccc9b73f52b7",
"lua_version": ""
}
}
+26
View File
@@ -182,6 +182,7 @@ type AvatarJson struct {
SPValue *uint32 `json:"sp_value"`
SPMax *uint32 `json:"sp_max"`
EnhancedID *uint32 `json:"enhanced_id"`
ExRankUnlocked bool `json:"ex_rank_unlocked,omitempty"`
}
func (a *AvatarJson) SPCur() uint32 {
@@ -305,6 +306,7 @@ type Persistent struct {
EnableSWGlobal *bool `json:"enable_sw_global"`
EnableCastoriceGlobal *bool `json:"enable_castorice_global"`
DressedSkins map[uint32]uint32 `json:"dressed_skins"`
ExRanksUnlocked map[uint32]bool `json:"ex_ranks_unlocked"`
}
// DefaultPersistent mirrors the Rust Default impl.
@@ -320,6 +322,7 @@ func DefaultPersistent() Persistent {
EnableSWGlobal: &sw,
EnableCastoriceGlobal: &cg,
DressedSkins: map[uint32]uint32{},
ExRanksUnlocked: map[uint32]bool{},
}
}
@@ -377,6 +380,11 @@ func LoadFreesrData() (*FreesrData, error) {
data.EnableSWGlobal = p.EnableSWGlobal
data.EnableCastoriceGlobal = p.EnableCastoriceGlobal
data.DressedSkins = p.DressedSkins
for aid, unlocked := range p.ExRanksUnlocked {
if av, ok := data.Avatars[aid]; ok {
av.ExRankUnlocked = unlocked
}
}
}
}
if data.DressedSkins == nil {
@@ -438,6 +446,12 @@ func (f *FreesrData) VerifyLineup() {
// SavePersistent writes the persistent subset back to the "persistent" file.
func (f *FreesrData) SavePersistent() {
exUnlocked := make(map[uint32]bool)
for aid, av := range f.Avatars {
if av.ExRankUnlocked {
exUnlocked[aid] = true
}
}
p := Persistent{
Lineups: f.Lineups,
Position: f.Position,
@@ -447,6 +461,7 @@ func (f *FreesrData) SavePersistent() {
EnableSWGlobal: f.EnableSWGlobal,
EnableCastoriceGlobal: f.EnableCastoriceGlobal,
DressedSkins: f.DressedSkins,
ExRanksUnlocked: exUnlocked,
}
out, err := json.MarshalIndent(p, "", " ")
if err != nil {
@@ -477,3 +492,14 @@ func (f *FreesrData) FindRelicsFor(avatarID uint32) []*Relic {
}
return out
}
// BaseAvatarIDs contains all playable base avatar IDs
var BaseAvatarIDs = []uint32{
8001, 1001,
1002, 1003, 1004, 1005, 1006, 1008, 1009, 1013, 1101, 1102, 1103, 1104, 1105, 1106, 1107, 1108,
1109, 1110, 1111, 1112, 1201, 1202, 1203, 1204, 1205, 1206, 1207, 1208, 1209, 1210, 1211, 1212,
1213, 1214, 1215, 1217, 1301, 1302, 1303, 1304, 1305, 1306, 1307, 1308, 1309, 1312, 1315, 1310,
1314, 1218, 1221, 1220, 1222, 1223, 1317, 1313, 1225, 1402, 1401, 1404, 1403, 1405, 1407, 1406,
1409, 1014, 1015, 1408, 1410, 1412, 1413, 1414, 1415, 1321, 1501, 1502, 1503, 1504, 1505, 1506,
1507, 1508, 1509, 1510, 1511, 1512, 1513,
}
+244 -6
View File
@@ -94,11 +94,71 @@ var (
teleportDB map[uint32]map[uint32]TeleportInfo
)
// GameRes lazily parses res.json (13MB; parsed once).
type MapEntranceEntry struct {
ID uint32 `json:"ID"`
PlaneID uint32 `json:"PlaneID"`
FloorID uint32 `json:"FloorID"`
StartGroupID uint32 `json:"StartGroupID"`
StartAnchorID uint32 `json:"StartAnchorID"`
}
type MazePlaneEntry struct {
PlaneID uint32 `json:"PlaneID"`
WorldID uint32 `json:"WorldID"`
StartFloorID uint32 `json:"StartFloorID"`
FloorIDList []uint32 `json:"FloorIDList"`
}
type ResourceTeleport struct {
TeleportID uint32 `json:"teleportId"`
AnchorID uint32 `json:"anchorId"`
GroupID uint32 `json:"groupId"`
InstID uint32 `json:"instId"`
Pos Vector `json:"pos"`
Rot Vector `json:"rot"`
}
type ResourceSceneEntry struct {
PlaneID uint32 `json:"planeID"`
EntryID uint32 `json:"entryID"`
Props []ScenePropInfo `json:"props"`
Monsters []SceneMonsterInfo `json:"monsters"`
Teleports []ResourceTeleport `json:"teleports"`
}
type ResourceResConfig struct {
SceneConfig []ResourceSceneEntry `json:"scene_config"`
}
type AnchorItem struct {
ID uint32 `json:"ID"`
Pos Vector `json:"pos"`
Rot Vector `json:"rot"`
}
type AnchorConfigEntry struct {
EntryID uint32 `json:"entryID"`
Anchor []AnchorItem `json:"anchor"`
}
type AnchorConfigFile struct {
AnchorConfig []AnchorConfigEntry `json:"anchor_config"`
}
func readResourceFile(paths ...string) ([]byte, error) {
for _, p := range paths {
if raw, err := os.ReadFile(p); err == nil {
return raw, nil
}
}
return nil, os.ErrNotExist
}
// GameRes lazily parses res.json and merges supplementary configs from resources/
func GameRes() (*JsonConfig, error) {
gameResOnce.Do(func() {
gameRes = &JsonConfig{}
raw, err := os.ReadFile("res.json")
raw, err := readResourceFile("res.json", "resources/res.json")
if err != nil {
gameResErr = err
return
@@ -116,19 +176,197 @@ func GameRes() (*JsonConfig, error) {
if gameRes.MapDefaultEntranceMap == nil {
gameRes.MapDefaultEntranceMap = map[uint32]uint32{}
}
_ = Teleports() // ensure teleportDB is initialized
loadSupplementaryResources(gameRes)
})
return gameRes, gameResErr
}
func loadSupplementaryResources(res *JsonConfig) {
entrancesByEntry := map[uint32]MapEntranceEntry{}
if raw, err := readResourceFile("resources/MapEntrance.json", "MapEntrance.json"); err == nil {
var list []MapEntranceEntry
if json.Unmarshal(raw, &list) != nil {
var m map[string][]MapEntranceEntry
if json.Unmarshal(raw, &m) == nil {
for _, entries := range m {
list = append(list, entries...)
}
}
}
for _, e := range list {
if e.FloorID != 0 && e.ID != 0 {
if _, ok := res.MapDefaultEntranceMap[e.FloorID]; !ok {
res.MapDefaultEntranceMap[e.FloorID] = e.ID
}
entrancesByEntry[e.ID] = e
}
}
}
planesByID := map[uint32]MazePlaneEntry{}
if raw, err := readResourceFile("resources/MazePlane.json", "MazePlane.json"); err == nil {
var list []MazePlaneEntry
if json.Unmarshal(raw, &list) != nil {
var m map[string][]MazePlaneEntry
if json.Unmarshal(raw, &m) == nil {
for _, entries := range m {
list = append(list, entries...)
}
}
}
for _, p := range list {
planesByID[p.PlaneID] = p
if p.StartFloorID != 0 {
if _, ok := res.MapDefaultEntranceMap[p.StartFloorID]; !ok {
res.MapDefaultEntranceMap[p.StartFloorID] = p.PlaneID*100 + 1
}
}
}
}
if raw, err := readResourceFile("resources/res.json"); err == nil {
var res2 ResourceResConfig
if json.Unmarshal(raw, &res2) == nil && len(res2.SceneConfig) > 0 {
for _, sc := range res2.SceneConfig {
floorID := uint32(0)
if ent, ok := entrancesByEntry[sc.EntryID]; ok && ent.FloorID != 0 {
floorID = ent.FloorID
} else if p, ok := planesByID[sc.PlaneID]; ok && p.StartFloorID != 0 {
floorID = p.StartFloorID
} else {
floorID = sc.PlaneID*1000 + 1
}
if _, ok := res.MapDefaultEntranceMap[floorID]; !ok {
res.MapDefaultEntranceMap[floorID] = sc.EntryID
}
if teleportDB[floorID] == nil {
teleportDB[floorID] = map[uint32]TeleportInfo{}
}
for _, t := range sc.Teleports {
tid := t.TeleportID
if tid == 0 {
tid = t.AnchorID
}
if tid != 0 {
teleportDB[floorID][tid] = TeleportInfo{Pos: t.Pos, Rot: t.Rot}
}
}
if _, exists := res.LevelOutputConfigs[sc.EntryID]; !exists {
groups := map[uint32]SceneData{}
for _, p := range sc.Props {
gid := p.GroupID
if gid == 0 {
gid = 1
}
sd := groups[gid]
if sd.Teleports == nil {
sd.Teleports = map[uint32]TeleportInfo{}
}
sd.Props = append(sd.Props, p)
groups[gid] = sd
}
for _, m := range sc.Monsters {
gid := m.GroupID
if gid == 0 {
gid = 1
}
sd := groups[gid]
if sd.Teleports == nil {
sd.Teleports = map[uint32]TeleportInfo{}
}
sd.Monsters = append(sd.Monsters, m)
groups[gid] = sd
}
for _, t := range sc.Teleports {
gid := t.GroupID
if gid == 0 {
gid = 1
}
sd := groups[gid]
if sd.Teleports == nil {
sd.Teleports = map[uint32]TeleportInfo{}
}
tid := t.TeleportID
if tid == 0 {
tid = t.AnchorID
}
if tid != 0 {
sd.Teleports[tid] = TeleportInfo{Pos: t.Pos, Rot: t.Rot}
}
groups[gid] = sd
}
name := fmt.Sprintf("P%d_F%d", sc.PlaneID, floorID)
res.LevelOutputConfigs[sc.EntryID] = map[string]*LevelOutputConfig{
name: {
IsEnteredSceneInfo: true,
Scenes: groups,
},
}
}
}
}
}
if raw, err := readResourceFile("resources/Anchor.json", "Anchor.json"); err == nil {
var aFile AnchorConfigFile
if json.Unmarshal(raw, &aFile) == nil && len(aFile.AnchorConfig) > 0 {
for _, item := range aFile.AnchorConfig {
floorID := uint32(0)
if ent, ok := entrancesByEntry[item.EntryID]; ok && ent.FloorID != 0 {
floorID = ent.FloorID
} else {
floorID = item.EntryID*10 + 1
}
if teleportDB[floorID] == nil {
teleportDB[floorID] = map[uint32]TeleportInfo{}
}
for _, a := range item.Anchor {
if a.ID != 0 {
teleportDB[floorID][a.ID] = TeleportInfo{Pos: a.Pos, Rot: a.Rot}
teleportDB[floorID][item.EntryID*100+a.ID] = TeleportInfo{Pos: a.Pos, Rot: a.Rot}
}
}
if _, exists := res.LevelOutputConfigs[item.EntryID]; !exists {
tps := map[uint32]TeleportInfo{}
for _, a := range item.Anchor {
if a.ID != 0 {
tps[a.ID] = TeleportInfo{Pos: a.Pos, Rot: a.Rot}
tps[item.EntryID*100+a.ID] = TeleportInfo{Pos: a.Pos, Rot: a.Rot}
}
}
name := fmt.Sprintf("P%d_F%d", item.EntryID/100, floorID)
res.LevelOutputConfigs[item.EntryID] = map[string]*LevelOutputConfig{
name: {
IsEnteredSceneInfo: true,
Scenes: map[uint32]SceneData{
1: {Teleports: tps},
},
},
}
}
}
}
}
}
// Teleports lazily parses teleports.json ({floor_id: {teleport_id: info}}).
func Teleports() map[uint32]map[uint32]TeleportInfo {
teleportOnce.Do(func() {
teleportDB = map[uint32]map[uint32]TeleportInfo{}
raw, err := os.ReadFile("teleports.json")
if err != nil {
return
}
raw, err := readResourceFile("teleports.json", "resources/teleports.json")
if err == nil {
_ = json.Unmarshal(raw, &teleportDB)
}
})
return teleportDB
}
+4 -11
View File
@@ -10,6 +10,7 @@ import (
"google.golang.org/protobuf/proto"
"gac-hkrpg-go/internal/common"
goproto "gac-hkrpg-go/protocol/proto"
)
@@ -33,15 +34,7 @@ func protoMarshal(msg proto.Message) ([]byte, error) {
// BASE_AVATAR_IDS — 89 avatars (Rust: handlers/avatar.rs)
// ---------------------------------------------------------------------------
var BaseAvatarIDs = []uint32{
8001, 1001,
1002, 1003, 1004, 1005, 1006, 1008, 1009, 1013, 1101, 1102, 1103, 1104, 1105, 1106, 1107, 1108,
1109, 1110, 1111, 1112, 1201, 1202, 1203, 1204, 1205, 1206, 1207, 1208, 1209, 1210, 1211, 1212,
1213, 1214, 1215, 1217, 1301, 1302, 1303, 1304, 1305, 1306, 1307, 1308, 1309, 1312, 1315, 1310,
1314, 1218, 1221, 1220, 1222, 1223, 1317, 1313, 1225, 1402, 1401, 1404, 1403, 1405, 1407, 1406,
1409, 1014, 1015, 1408, 1410, 1412, 1413, 1414, 1415, 1321, 1501, 1502, 1503, 1504, 1505, 1506,
1507, 1508, 1509, 1510, 1511, 1512, 1513,
}
var BaseAvatarIDs = common.BaseAvatarIDs
// ---------------------------------------------------------------------------
// Dispatch — Go port of the trait_handler! macro + on_message special cases.
@@ -109,7 +102,7 @@ func dummy(name string) {
// never auto-answer these, drop them silently instead.
var dangerFamilies = []string{
"GridFight", "ChessRogue", "EvolveBuild", "ChimeraDuel",
"Jukebox", "CycleScore", "MusicAlbum",
"Jukebox", "CycleScore", "MusicAlbum", "DrinkMaker",
}
func isDangerCmd(cmdID uint16) bool {
@@ -121,7 +114,7 @@ func isDangerCmd(cmdID uint16) bool {
}
// CycleScore cmd ids (4143/4146) and GridFight range carry generic names
switch cmdID {
case 4143, 4146, 8541, 5410, 5449, 7142, 3174, 9252, 8175:
case 4143, 4146, 8541, 5410, 5449, 7142, 3174, 9252, 8175, 6995, 6988:
return true
}
// GetMapRotationData: an empty reply NREs the client's map init
+9 -8
View File
@@ -183,12 +183,12 @@ func (g *Gateway) processKCPPayload(data []byte, addr *net.UDPAddr) {
}
}
// watchFreesrData polls freesr-data.json mtime every second (the Rust version
// used a notify debouncer) and re-syncs the player on change.
// watchFreesrData polls freesr-data.json mtime every second
// and re-syncs the player in real-time when changed.
func watchFreesrData(session *Session) {
var lastMod time.Time
var lastModFreesr time.Time
if st, err := statFile("freesr-data.json"); err == nil {
lastMod = st
lastModFreesr = st
}
ticker := time.NewTicker(time.Second)
defer ticker.Stop()
@@ -197,18 +197,19 @@ func watchFreesrData(session *Session) {
case <-session.Done():
return
case <-ticker.C:
st, err := statFile("freesr-data.json")
if err != nil || !st.After(lastMod) {
stFreesr, _ := statFile("freesr-data.json")
if stFreesr.IsZero() || !stFreesr.After(lastModFreesr) {
continue
}
lastMod = st
lastModFreesr = stFreesr
if data, err := common.LoadFreesrData(); err == nil {
session.SetJSON(data)
if err := session.SyncPlayer(); err == nil {
Log("json updated")
Log("player data updated & live synced to client")
}
}
}
}
}
+6
View File
@@ -53,6 +53,12 @@ func init() {
}
}
}
for _, id := range []uint32{1130301, 1100101, 1131001, 1141501, 1140701, 1150101, 1150501, 1140901} {
if !seen[id] {
seen[id] = true
rsp.SkinList = append(rsp.SkinList, id)
}
}
sort.Slice(rsp.SkinList, func(i, j int) bool { return rsp.SkinList[i] < rsp.SkinList[j] })
return nil
+87 -41
View File
@@ -2,6 +2,7 @@ package gameserver
import (
"encoding/json"
"fmt"
"os"
"sync"
@@ -11,56 +12,88 @@ import (
func init() {
handle("StartCocoonStage", func(s *Session, req *goproto.StartCocoonStageCsReq, rsp *goproto.StartCocoonStageScRsp) error {
Log(fmt.Sprintf("[BATTLE] StartCocoonStage: CocoonId=%d Wave=%d WorldLevel=%d PropEntityId=%d", req.CocoonId, req.Wave, req.WorldLevel, req.PropEntityId))
battleInfo := createBattleInfo(s, 0, 0)
if req.WorldLevel > 0 {
battleInfo.WorldLevel = req.WorldLevel
}
if req.Wave > 0 && int(req.Wave) <= len(battleInfo.MonsterWaveList) {
battleInfo.MonsterWaveList = battleInfo.MonsterWaveList[:req.Wave]
} else if len(battleInfo.MonsterWaveList) > 1 {
battleInfo.MonsterWaveList = battleInfo.MonsterWaveList[:1]
}
battleInfo.JAOIOKBBMFC = uint32(len(battleInfo.MonsterWaveList))
for _, w := range battleInfo.MonsterWaveList {
w.WaveId = 1
w.WaveParam = nil
w.BattleStageId = battleInfo.StageId
}
normalizeCalyxBattleInfo(battleInfo, req.Wave, req.WorldLevel)
CURRENT_BATTLE.Store(s.Token, battleInfo)
challengeCnt := req.Wave
if challengeCnt == 0 {
challengeCnt = 1
}
rsp.Retcode = 0
rsp.PropEntityId = req.PropEntityId
rsp.CocoonId = req.CocoonId
rsp.Wave = req.Wave
rsp.ChallengeCnt = challengeCnt
rsp.BattleInfo = battleInfo
return nil
})
// NOTE: 4.6.51 proto names this message StartQuickCocoonStageCsReq/Rsp
handleNamed("StartQuickCocoonStage", "StartQuickCocoonStageRsp", func(s *Session, req *goproto.StartQuickCocoonStageCsReq, rsp *goproto.StartQuickCocoonStageRsp) error {
Log(fmt.Sprintf("[BATTLE] StartQuickCocoonStage: CocoonId=%d Wave=%d WorldLevel=%d", req.CocoonId, req.Wave, req.WorldLevel))
battleInfo := createBattleInfo(s, 0, 0)
normalizeCalyxBattleInfo(battleInfo, req.Wave, req.WorldLevel)
CURRENT_BATTLE.Store(s.Token, battleInfo)
rsp.Retcode = 0
rsp.CocoonId = req.CocoonId
rsp.Wave = req.Wave
rsp.BattleInfo = battleInfo
return nil
})
// NOTE: 4.6.51 proto names this message StartQuickCocoonStageCsReq/Rsp
// (the Rust code referenced an older QuickStartCocoonStage* name).
handleNamed("StartQuickCocoonStage", "StartQuickCocoonStageRsp", func(s *Session, req *goproto.StartQuickCocoonStageCsReq, rsp *goproto.StartQuickCocoonStageRsp) error {
handleNamed("StartQuickFarmElement", "StartQuickFarmElementRsp", func(s *Session, req *goproto.StartQuickFarmElementCsReq, rsp *goproto.StartQuickFarmElementRsp) error {
Log(fmt.Sprintf("[BATTLE] StartQuickFarmElement: FarmID=%d WorldLevel=%d", req.PAOFHFLFFHD, req.WorldLevel))
battleInfo := createBattleInfo(s, 0, 0)
if req.WorldLevel > 0 {
battleInfo.WorldLevel = req.WorldLevel
}
if req.Wave > 0 && int(req.Wave) <= len(battleInfo.MonsterWaveList) {
battleInfo.MonsterWaveList = battleInfo.MonsterWaveList[:req.Wave]
} else if len(battleInfo.MonsterWaveList) > 1 {
battleInfo.MonsterWaveList = battleInfo.MonsterWaveList[:1]
}
battleInfo.JAOIOKBBMFC = uint32(len(battleInfo.MonsterWaveList))
for _, w := range battleInfo.MonsterWaveList {
w.WaveId = 1
w.WaveParam = nil
w.BattleStageId = battleInfo.StageId
}
normalizeCalyxBattleInfo(battleInfo, 1, req.WorldLevel)
CURRENT_BATTLE.Store(s.Token, battleInfo)
rsp.CocoonId = req.CocoonId
rsp.Wave = req.Wave
rsp.Retcode = 0
rsp.PAOFHFLFFHD = req.PAOFHFLFFHD
rsp.WorldLevel = req.WorldLevel
rsp.BattleInfo = battleInfo
return nil
})
handle("ActiveFarmElement", func(s *Session, req *goproto.ActiveFarmElementCsReq, rsp *goproto.ActiveFarmElementScRsp) error {
Log(fmt.Sprintf("[BATTLE] ActiveFarmElement: EntityId=%d WorldLevel=%d", req.EntityId, req.WorldLevel))
rsp.Retcode = 0
rsp.EntityId = req.EntityId
rsp.WorldLevel = req.WorldLevel
return nil
})
handle("DeactivateFarmElement", func(s *Session, req *goproto.DeactivateFarmElementCsReq, rsp *goproto.DeactivateFarmElementScRsp) error {
Log(fmt.Sprintf("[BATTLE] DeactivateFarmElement: EntityId=%d", req.EntityId))
rsp.Retcode = 0
rsp.EntityId = req.EntityId
return nil
})
handle("CocoonSweep", func(s *Session, req *goproto.CocoonSweepCsReq, rsp *goproto.CocoonSweepScRsp) error {
Log(fmt.Sprintf("[BATTLE] CocoonSweep: CocoonId=%d WorldLevel=%d", req.CocoonId, req.WorldLevel))
rsp.Retcode = 0
rsp.CocoonId = req.CocoonId
rsp.DropData = &goproto.ItemList{}
rsp.MultipleDropData = &goproto.ItemList{}
return nil
})
handle("FarmElementSweep", func(s *Session, req *goproto.FarmElementSweepCsReq, rsp *goproto.FarmElementSweepScRsp) error {
Log(fmt.Sprintf("[BATTLE] FarmElementSweep: FarmID=%d", req.PAOFHFLFFHD))
rsp.Retcode = 0
rsp.PAOFHFLFFHD = req.PAOFHFLFFHD
rsp.MultipleDropData = &goproto.ItemList{}
return nil
})
handle("GetFarmStageGachaInfo", func(s *Session, req *goproto.GetFarmStageGachaInfoCsReq, rsp *goproto.GetFarmStageGachaInfoScRsp) error {
rsp.Retcode = 0
return nil
})
handle("SceneEnterStage", func(s *Session, req *goproto.SceneEnterStageCsReq, rsp *goproto.SceneEnterStageScRsp) error {
battleInfo := createBattleInfo(s, 0, 0)
CURRENT_BATTLE.Store(s.Token, battleInfo)
@@ -152,19 +185,32 @@ func init() {
return nil
}
if player := s.JSON(); player != nil && len(player.BattleConfig.Monsters) > 0 {
battleInfo := createBattleInfo(s, 0, 0)
CURRENT_BATTLE.Store(s.Token, battleInfo)
rsp.Retcode = 0
rsp.BattleInfo = battleInfo
return nil
}
rsp.Retcode = 2000 // RET_BATTLE_STAGE_NOT_MATCH
return nil
})
}
func normalizeCalyxBattleInfo(battleInfo *goproto.SceneBattleInfo, reqWave uint32, worldLevel uint32) {
if worldLevel > 0 {
battleInfo.WorldLevel = worldLevel
}
if reqWave > 0 && int(reqWave) <= len(battleInfo.MonsterWaveList) {
battleInfo.MonsterWaveList = battleInfo.MonsterWaveList[:reqWave]
} else if len(battleInfo.MonsterWaveList) > 1 {
battleInfo.MonsterWaveList = battleInfo.MonsterWaveList[:1]
}
battleInfo.JAOIOKBBMFC = uint32(len(battleInfo.MonsterWaveList))
for _, w := range battleInfo.MonsterWaveList {
// NOTE: In client 4.6.53 GameAssembly.dll 0x166b2fdd, client verifies:
// (wave.WaveId & ~8) == 1. If wave_id != 1, it jumps to 0x166b3161
// and throws NullReferenceException in AdventureModule.CreateBattleServerCheckResult.
// Therefore every wave in Calyx / FarmElement MUST have WaveId = 1 and WaveParam = nil.
w.WaveId = 1
w.WaveParam = nil
w.BattleStageId = battleInfo.StageId
}
}
// createBattleInfo is the Go port of battle.rs create_battle_info.
func createBattleInfo(s *Session, casterID, skillIndex uint32) *goproto.SceneBattleInfo {
player := s.JSON()
+227 -223
View File
@@ -62,8 +62,15 @@ func skipWireField(wireType uint32, buf *[]byte) {
}
func mapChallengeStageID(id uint32) uint32 {
cd := getChallengeData()
if _, ok := cd.Challenges[id]; ok {
return id
}
if _, ok := cd.Tierce[id]; ok {
return id
}
switch {
case id >= 5400 && id <= 5600: // MoC 1035/1036 -> 5301..5312
case id >= 5400 && id <= 5700: // MoC 1035/1036 -> 5301..5312
rem := id % 100
if rem < 1 {
rem = 1
@@ -91,7 +98,6 @@ func mapChallengeStageID(id uint32) uint32 {
}
return 30200 + rem
case id > 1:
cd := getChallengeData()
if _, ok := cd.Challenges[id]; !ok {
return id - 1
}
@@ -100,10 +106,59 @@ func mapChallengeStageID(id uint32) uint32 {
return id
}
func readLenDelimAndAdvance(buf *[]byte) ([]byte, bool) {
b := *buf
l, n := readUvarint(b)
if n == 0 || int(l) > len(b[n:]) {
*buf = nil
return nil, false
}
sub := b[n : n+int(l)]
*buf = b[n+int(l):]
return sub, true
}
func readLenDelim(buf []byte) ([]byte, bool) {
l, n := readUvarint(buf)
if n == 0 || int(l) > len(buf[n:]) {
return nil, false
}
return buf[n : n+int(l)], true
}
func decodeStartChallengeReq(payload []byte) decodedChallengeStart {
var req decodedChallengeStart
buf := payload
var pbReq goproto.StartChallengeCsReq
if err := proto.Unmarshal(payload, &pbReq); err == nil && pbReq.ChallengeId != 0 {
req.challengeID = pbReq.ChallengeId
req.firstAvatars = pbReq.FirstLineup
if len(req.firstAvatars) == 0 && len(pbReq.AvatarLineupFirst) > 0 {
for _, av := range pbReq.AvatarLineupFirst {
if av.Id != 0 {
req.firstAvatars = append(req.firstAvatars, av.Id)
}
}
}
req.secondAvatars = pbReq.SecondLineup
if len(req.secondAvatars) == 0 && len(pbReq.AvatarLineupSecond) > 0 {
for _, av := range pbReq.AvatarLineupSecond {
if av.Id != 0 {
req.secondAvatars = append(req.secondAvatars, av.Id)
}
}
}
if pbReq.StageInfo != nil {
if pbReq.StageInfo.BossInfo != nil && pbReq.StageInfo.BossInfo.BuffOne != 0 {
req.buffID = pbReq.StageInfo.BossInfo.BuffOne
} else if pbReq.StageInfo.StoryInfo != nil && pbReq.StageInfo.StoryInfo.BuffOne != 0 {
req.buffID = pbReq.StageInfo.StoryInfo.BuffOne
}
}
return req
}
buf := payload
for len(buf) > 0 {
tag, n := readUvarint(buf)
if n == 0 {
@@ -133,13 +188,10 @@ func decodeStartChallengeReq(payload []byte) decodedChallengeStart {
buf = buf[n2:]
req.firstAvatars = append(req.firstAvatars, uint32(v))
case fieldNumber == 6 && wireType == 2:
sub, ok := readLenDelim(buf)
sub, ok := readLenDelimAndAdvance(&buf)
if !ok {
buf = nil
break
}
buf = buf[len(buf)-len(sub):] // consumed below
buf = buf[len(sub):]
for len(sub) > 0 {
v, n3 := readUvarint(sub)
if n3 == 0 {
@@ -157,12 +209,10 @@ func decodeStartChallengeReq(payload []byte) decodedChallengeStart {
buf = buf[n2:]
req.secondAvatars = append(req.secondAvatars, uint32(v))
case fieldNumber == 8 && wireType == 2:
sub, ok := readLenDelim(buf)
sub, ok := readLenDelimAndAdvance(&buf)
if !ok {
buf = nil
break
}
buf = buf[len(sub):]
for len(sub) > 0 {
v, n3 := readUvarint(sub)
if n3 == 0 {
@@ -171,14 +221,11 @@ func decodeStartChallengeReq(payload []byte) decodedChallengeStart {
sub = sub[n3:]
req.secondAvatars = append(req.secondAvatars, uint32(v))
}
case fieldNumber == 4 && wireType == 2:
// AvatarLineup: id = tag 1
sub, ok := readLenDelim(buf)
case (fieldNumber == 4 || fieldNumber == 15) && wireType == 2:
sub, ok := readLenDelimAndAdvance(&buf)
if !ok {
buf = nil
break
}
buf = buf[len(sub):]
for len(sub) > 0 {
stag, n3 := readUvarint(sub)
if n3 == 0 {
@@ -192,19 +239,20 @@ func decodeStartChallengeReq(payload []byte) decodedChallengeStart {
break
}
sub = sub[n3+n4:]
if fieldNumber == 4 {
req.firstAvatars = append(req.firstAvatars, uint32(v))
} else {
req.secondAvatars = append(req.secondAvatars, uint32(v))
}
} else {
skipWireField(swt, &sub)
}
}
case fieldNumber == 10 && wireType == 2:
// ChallengeBuffInfo { story_info = 9, boss_info = 10 }
sub, ok := readLenDelim(buf)
sub, ok := readLenDelimAndAdvance(&buf)
if !ok {
buf = nil
break
}
buf = buf[len(sub):]
for len(sub) > 0 {
stag, n3 := readUvarint(sub)
if n3 == 0 {
@@ -212,9 +260,9 @@ func decodeStartChallengeReq(payload []byte) decodedChallengeStart {
}
sfn := uint32(stag >> 3)
swt := uint32(stag & 0x7)
sub2 := sub[n3:]
sub = sub[n3:]
if swt == 2 && (sfn == 9 || sfn == 10) {
inner, ok2 := readLenDelim(sub2)
inner, ok2 := readLenDelimAndAdvance(&sub)
if !ok2 {
break
}
@@ -225,12 +273,13 @@ func decodeStartChallengeReq(payload []byte) decodedChallengeStart {
}
f2 := uint32(t2 >> 3)
w2 := uint32(t2 & 0x7)
inner = inner[n4:]
if w2 == 0 && (f2 == 10 || f2 == 15) {
v, n5 := readUvarint(inner[n4:])
v, n5 := readUvarint(inner)
if n5 == 0 {
break
}
inner = inner[n4+n5:]
inner = inner[n5:]
if req.buffID == 0 {
req.buffID = uint32(v)
}
@@ -238,14 +287,6 @@ func decodeStartChallengeReq(payload []byte) decodedChallengeStart {
skipWireField(w2, &inner)
}
}
sub = sub2
// skip the whole len-delimited field
_, n6 := readUvarint(sub2)
if n6 == 0 {
sub = nil
} else {
sub = sub2[n6:]
}
} else {
skipWireField(swt, &sub)
}
@@ -258,13 +299,6 @@ func decodeStartChallengeReq(payload []byte) decodedChallengeStart {
return req
}
func readLenDelim(buf []byte) ([]byte, bool) {
l, n := readUvarint(buf)
if n == 0 || int(l) > len(buf[n:]) {
return nil, false
}
return buf[n : n+int(l)], true
}
type tierceStageEntry struct {
buff uint32
@@ -273,9 +307,27 @@ type tierceStageEntry struct {
func decodeStartChallengeTierceReq(payload []byte) decodedChallengeStart {
var req decodedChallengeStart
buf := payload
var stagesAvatars []tierceStageEntry
var pbReq goproto.StartChallengeTierceCsReq
if err := proto.Unmarshal(payload, &pbReq); err == nil && pbReq.ChallengeId != 0 {
req.challengeID = pbReq.ChallengeId
req.stageIndex = pbReq.FINOCEGDBDI
req.isSingleStage = pbReq.MPKMIKBFEHG
for _, lt := range pbReq.LineupTierceList {
var l []uint32
for _, av := range lt.TierceAvatarIdList {
if av.Id != 0 {
l = append(l, av.Id)
}
}
stagesAvatars = append(stagesAvatars, tierceStageEntry{
buff: lt.BuffId,
lineup: l,
})
}
} else {
buf := payload
for len(buf) > 0 {
tag, n := readUvarint(buf)
if n == 0 {
@@ -286,7 +338,7 @@ func decodeStartChallengeTierceReq(payload []byte) decodedChallengeStart {
wireType := uint32(tag & 0x7)
switch {
case (fieldNumber == 10 || fieldNumber == 1) && wireType == 0:
case (fieldNumber == 10 || fieldNumber == 1 || fieldNumber == 5) && wireType == 0:
v, n2 := readUvarint(buf)
if n2 == 0 {
buf = nil
@@ -302,7 +354,7 @@ func decodeStartChallengeTierceReq(payload []byte) decodedChallengeStart {
}
buf = buf[n2:]
req.challengeID = uint32(v)
case (fieldNumber == 5 || fieldNumber == 13) && wireType == 0:
case (fieldNumber == 2 || fieldNumber == 13) && wireType == 0:
v, n2 := readUvarint(buf)
if n2 == 0 {
buf = nil
@@ -310,13 +362,11 @@ func decodeStartChallengeTierceReq(payload []byte) decodedChallengeStart {
}
buf = buf[n2:]
req.isSingleStage = v != 0
case (fieldNumber == 12 || fieldNumber == 14) && wireType == 2:
sub, ok := readLenDelim(buf)
case (fieldNumber == 9 || fieldNumber == 12 || fieldNumber == 14) && wireType == 2:
sub, ok := readLenDelimAndAdvance(&buf)
if !ok {
buf = nil
break
}
buf = buf[len(sub):]
var stageBuff uint32
var stageLineup []uint32
@@ -327,22 +377,21 @@ func decodeStartChallengeTierceReq(payload []byte) decodedChallengeStart {
}
subFn := uint32(subTag >> 3)
subWt := uint32(subTag & 0x7)
sub = sub[n2:]
switch {
case (subFn == 6 || subFn == 5) && subWt == 0:
v, n3 := readUvarint(sub[n2:])
case (subFn == 10 || subFn == 6 || subFn == 5) && subWt == 0:
v, n3 := readUvarint(sub)
if n3 == 0 {
sub = nil
break
}
sub = sub[n2+n3:]
sub = sub[n3:]
stageBuff = uint32(v)
case (subFn == 4 || subFn == 9) && subWt == 2:
abuf, ok2 := readLenDelim(sub[n2:])
case (subFn == 9 || subFn == 4) && subWt == 2:
abuf, ok2 := readLenDelimAndAdvance(&sub)
if !ok2 {
sub = nil
break
}
sub = sub[n2+len(abuf):]
var aid uint32
for len(abuf) > 0 {
atag, n4 := readUvarint(abuf)
@@ -351,12 +400,13 @@ func decodeStartChallengeTierceReq(payload []byte) decodedChallengeStart {
}
afn := uint32(atag >> 3)
awt := uint32(atag & 0x7)
if (afn == 7 || afn == 9) && awt == 0 {
v, n5 := readUvarint(abuf[n4:])
abuf = abuf[n4:]
if (afn == 1 || afn == 7 || afn == 9) && awt == 0 {
v, n5 := readUvarint(abuf)
if n5 == 0 {
break
}
abuf = abuf[n4+n5:]
abuf = abuf[n5:]
aid = uint32(v)
} else {
skipWireField(awt, &abuf)
@@ -374,6 +424,7 @@ func decodeStartChallengeTierceReq(payload []byte) decodedChallengeStart {
skipWireField(wireType, &buf)
}
}
}
for _, st := range stagesAvatars {
req.stageInfoList = append(req.stageInfoList, st.lineup)
@@ -636,7 +687,7 @@ func handleStartChallenge(s *Session, payload []byte) error {
baseID := mapChallengeStageID(req.challengeID)
cd := getChallengeData()
entrance, group, monster, event, buff := uint32(3014101), uint32(5), uint32(4033010), uint32(30124011), uint32(0)
entrance, group, monster, event, buff := uint32(3000101), uint32(5), uint32(4033010), uint32(30124011), uint32(0)
if c, ok := cd.Challenges[baseID]; ok {
entrance, group, monster, event = c.Entrance, c.Group1, c.Monster1, c.Event1
if req.buffID != 0 {
@@ -644,13 +695,12 @@ func handleStartChallenge(s *Session, payload []byte) error {
} else {
buff = c.Buff
}
} else if t, ok := cd.Tierce[baseID]; ok {
entrance, group, monster, event = t.Entrance, t.Group, t.Monster, t.Event
buff = req.buffID
}
// the 4.6.51 client has no MapEntrance 3014101 (Rust lesson: Lua
// GotoMapParam nil -> stuck loading) even though res.json has it
if entrance == 3014101 {
entrance = 3000101
} else if res, err := common.GameRes(); err == nil && !mapHasEntry(res, entrance) {
if res, err := common.GameRes(); err == nil && !mapHasEntry(res, entrance) {
Log("[CHALLENGE] Arena entrance missing in res.json, fallback to 3000101")
entrance = 3000101
}
@@ -667,64 +717,37 @@ func handleStartChallenge(s *Session, payload []byte) error {
return err
}
custom := map[uint32]uint32{}
for i, aid := range chosenAvatars {
custom[uint32(i)] = aid
var lineups []*goproto.LineupInfo
lineups = append(lineups, lineupInfoFromAvatars(chosenAvatars, sceneInfo.PlaneId, goproto.ExtraLineupType(1), 0))
secondAvatars := req.secondAvatars
if len(secondAvatars) == 0 {
secondAvatars = chosenAvatars
}
lineupInfo := common.ToLineupInfo(custom)
secondAvatars = dedupeTruncate4(secondAvatars)
lineups = append(lineups, lineupInfoFromAvatars(secondAvatars, sceneInfo.PlaneId, goproto.ExtraLineupType(1), 1))
curChallenge := &goproto.CurChallenge{
ChallengeId: req.challengeID,
Status: 1, // CHALLENGE_DOING
Status: goproto.ChallengeStatus_CHALLENGE_DOING,
RoundCount: 0,
ScoreId: scoreIDFor(req.challengeID),
ScoreTwo: 0,
ExtraLineupType: goproto.ExtraLineupType(1),
}
chalBuf, err := proto.Marshal(curChallenge)
if err != nil {
return err
rsp := &goproto.StartChallengeScRsp{
Retcode: 0,
CurChallenge: curChallenge,
LineupList: lineups,
Scene: sceneInfo,
}
lineupBuf, err := proto.Marshal(lineupInfo)
if err != nil {
return err
}
sceneBuf, err := proto.Marshal(sceneInfo)
rspBuf, err := proto.Marshal(rsp)
if err != nil {
return err
}
// 4.6.51: StartChallengeScRsp.stage_info (tag 13) - boss node info echoes
// the selected buff so the client can set up the challenge UI
stageInfo := &goproto.ChallengeStageInfo{}
if req.buffID != 0 {
stageInfo.BossInfo = &goproto.ChallengeBossInfo{
FirstNode: &goproto.ChallengeBossSingleNodeInfo{BuffId: req.buffID},
SecondNode: &goproto.ChallengeBossSingleNodeInfo{BuffId: req.buffID},
}
}
stageBuf, err := proto.Marshal(stageInfo)
if err != nil {
return err
}
var body []byte
body = append(body, 0x18, 0x00) // tag 3: retcode = 0
body = append(body, 0x2A) // tag 5: cur_challenge
body = appendUvarint(body, uint64(len(chalBuf)))
body = append(body, chalBuf...)
body = append(body, 0x32) // tag 6: lineup_list
body = appendUvarint(body, uint64(len(lineupBuf)))
body = append(body, lineupBuf...)
body = append(body, 0x52) // tag 10: scene
body = appendUvarint(body, uint64(len(sceneBuf)))
body = append(body, sceneBuf...)
body = append(body, 0x6A) // tag 13: stage_info
body = appendUvarint(body, uint64(len(stageBuf)))
body = append(body, stageBuf...)
return s.SendRaw(&NetPacket{CmdType: 1787, Body: body})
return s.SendRaw(&NetPacket{CmdType: 1787, Body: rspBuf})
}
func scoreIDFor(challengeID uint32) uint32 {
@@ -734,7 +757,7 @@ func scoreIDFor(challengeID uint32) uint32 {
return 0
}
// handleStartChallengeTierce ports handle_start_challenge_tierce -> cmd 8973.
// handleStartChallengeTierce ports handle_start_challenge_tierce -> cmd 8998 (4.6.51).
func handleStartChallengeTierce(s *Session, payload []byte) error {
req := decodeStartChallengeTierceReq(payload)
baseID := mapChallengeStageID(req.challengeID)
@@ -751,9 +774,17 @@ func handleStartChallengeTierce(s *Session, payload []byte) error {
case 0:
chosenAvatars = req.firstAvatars
case 1:
if len(req.secondAvatars) > 0 {
chosenAvatars = req.secondAvatars
} else {
chosenAvatars = req.firstAvatars
}
case 2:
if len(req.thirdAvatars) > 0 {
chosenAvatars = req.thirdAvatars
} else {
chosenAvatars = req.firstAvatars
}
}
if len(chosenAvatars) == 0 {
@@ -768,32 +799,12 @@ func handleStartChallengeTierce(s *Session, payload []byte) error {
}
cd := getChallengeData()
entrance, group, monster, event, buff := uint32(3014101), uint32(5), uint32(4033010), uint32(30124011), uint32(0)
switch {
case req.stageIndex == 2:
entrance, group, monster, event, buff := uint32(3000101), uint32(5), uint32(4033010), uint32(30124011), uint32(0)
if req.stageIndex == 0 {
if t, ok := cd.Tierce[baseID]; ok {
entrance, group, monster, event, buff = t.Entrance, t.Group, t.Monster, t.Event, req.buffID
} else if c, ok2 := cd.Challenges[baseID]; ok2 {
entrance, group, monster, event = c.Entrance2, c.Group2, c.Monster2, c.Event2
if req.buffID != 0 {
buff = req.buffID
} else {
buff = c.Buff
}
}
case req.stageIndex == 1:
if c, ok := cd.Challenges[baseID]; ok {
entrance, group, monster, event = c.Entrance2, c.Group2, c.Monster2, c.Event2
if req.buffID != 0 {
buff = req.buffID
} else {
buff = c.Buff
}
} else {
entrance, group, monster, event = 3014101, 6, 8013010, 30124012
}
default:
if c, ok := cd.Challenges[baseID]; ok {
entrance, group, monster, event = c.Entrance, c.Group1, c.Monster1, c.Event1
if req.buffID != 0 {
buff = req.buffID
@@ -801,13 +812,32 @@ func handleStartChallengeTierce(s *Session, payload []byte) error {
buff = c.Buff
}
}
} else if req.stageIndex == 1 {
// Stage 2 (Node 2)
if c, ok := cd.Challenges[baseID-1]; ok && c.Entrance2 != 0 {
entrance, group, monster, event = c.Entrance2, c.Group2, c.Monster2, c.Event2
if req.buffID != 0 {
buff = req.buffID
} else {
buff = c.Buff
}
} else if c, ok2 := cd.Challenges[baseID]; ok2 && c.Entrance2 != 0 {
entrance, group, monster, event = c.Entrance2, c.Group2, c.Monster2, c.Event2
if req.buffID != 0 {
buff = req.buffID
} else {
buff = c.Buff
}
} else if t, ok3 := cd.Tierce[baseID]; ok3 {
entrance, group, monster, event, buff = t.Entrance, t.Group, t.Monster, t.Event, req.buffID
}
} else {
if t, ok := cd.Tierce[baseID]; ok {
entrance, group, monster, event, buff = t.Entrance, t.Group, t.Monster, t.Event, req.buffID
}
}
// the 4.6.51 client cannot load arena 3014101 (no MapEntrance) - fall
// back to the standard arena, mirroring handleStartChallenge
if entrance == 3014101 {
entrance = 3000101
} else if res, err := common.GameRes(); err == nil && !mapHasEntry(res, entrance) {
if res, err := common.GameRes(); err == nil && !mapHasEntry(res, entrance) {
Log("[CHALLENGE] Tierce arena entrance missing in res.json, fallback to 3000101")
entrance = 3000101
}
@@ -875,43 +905,27 @@ func handleStartChallengeTierce(s *Session, payload []byte) error {
}
}
// ChallengeTierceChallengeInfo: stage_index=4, challenge_id=6, is_single_stage=14, lineup_list=15
var tierceInfo []byte
tierceInfo = append(tierceInfo, 0x20) // tag 4: stage_index
tierceInfo = appendUvarint(tierceInfo, uint64(req.stageIndex))
tierceInfo = append(tierceInfo, 0x30) // tag 6: challenge_id
tierceInfo = appendUvarint(tierceInfo, uint64(req.challengeID))
if req.isSingleStage {
tierceInfo = append(tierceInfo, 0x70, 0x01) // tag 14: true
}
var tierceLineupList []*goproto.LineupInfo
for stIdx, stageAvatars := range allLineups {
lineup := lineupInfoFromAvatars(stageAvatars, sceneInfo.PlaneId, goproto.ExtraLineupType(1), uint32(stIdx))
lineupBuf, err := proto.Marshal(lineup)
tierceLineupList = append(tierceLineupList, lineupInfoFromAvatars(stageAvatars, sceneInfo.PlaneId, goproto.ExtraLineupType(1), uint32(stIdx)))
}
// 4.6.51 StartChallengeTierceScRsp (CmdID 8998)
rsp46 := &goproto.StartChallengeTierceScRsp{
Retcode: 0,
Scene: sceneInfo,
CurTierceChallenge: &goproto.CurTierceChallenge{
LineupList: tierceLineupList,
ChallengeId: req.challengeID,
FINOCEGDBDI: req.stageIndex,
MPKMIKBFEHG: req.isSingleStage,
},
}
rspBuf, err := proto.Marshal(rsp46)
if err != nil {
return err
}
tierceInfo = append(tierceInfo, 0x7A) // tag 15: lineup_list
tierceInfo = appendUvarint(tierceInfo, uint64(len(lineupBuf)))
tierceInfo = append(tierceInfo, lineupBuf...)
}
sceneBuf, err := proto.Marshal(sceneInfo)
if err != nil {
return err
}
// StartChallengeTierceScRsp cmd 8973: retcode=12, challenge_tierce_info=4, scene=9
var body []byte
body = append(body, 0x60, 0x00) // tag 12: retcode = 0
body = append(body, 0x22) // tag 4
body = appendUvarint(body, uint64(len(tierceInfo)))
body = append(body, tierceInfo...)
body = append(body, 0x4A) // tag 9: scene
body = appendUvarint(body, uint64(len(sceneBuf)))
body = append(body, sceneBuf...)
return s.SendRaw(&NetPacket{CmdType: 8973, Body: body})
return s.SendRaw(&NetPacket{CmdType: 8998, Body: rspBuf})
}
func handleSetChallengeTierceLineup(s *Session, payload []byte) error {
@@ -921,34 +935,60 @@ func handleSetChallengeTierceLineup(s *Session, payload []byte) error {
tierceLineups[challengeID] = stages
tierceLineupsMu.Unlock()
}
if err := s.SendRaw(&NetPacket{CmdType: 8999, Body: []byte{0x08, 0x00}}); err != nil {
// 4.6.51 SetChallengeTierceLineupScRsp (cmd 8985, retcode tag 1 = 0)
if err := s.SendRaw(&NetPacket{CmdType: 8985, Body: []byte{0x08, 0x00}}); err != nil {
return err
}
_ = s.SendRaw(&NetPacket{CmdType: 8999, Body: []byte{0x08, 0x00}})
_ = s.SendRaw(&NetPacket{CmdType: 8995, Body: []byte{0x60, 0x00}})
return nil
}
func handleLeaveChallenge(s *Session, _ []byte) error {
var returnEntry uint32 = 102020107
if player := s.JSON(); player != nil {
player.BattleConfig.CustomBattleLineup = nil
// Always return safely to Belobog Admin District 102020107
player.Scene.EntryID = 102020107
player.Scene.PlaneID = 10202
player.Scene.FloorID = 10202001
player.Position = common.Position{X: -92, Y: 4278, Z: 68083, RotY: 155566}
player.SavePersistent()
}
if err := s.SendRaw(&NetPacket{CmdType: 1716, Body: []byte{0x20, 0x00}}); err != nil {
// 4.6.51 LeaveChallengeScRsp (cmd 1771, tag 10 retcode 0)
if err := s.SendRaw(&NetPacket{CmdType: 1771, Body: []byte{0x50, 0x00}}); err != nil {
return err
}
_ = s.SendRaw(&NetPacket{CmdType: 1716, Body: []byte{0x20, 0x00}})
_ = s.SendRaw(&NetPacket{CmdType: 1781, Body: []byte{0x40, 0x00}})
// Restore the player back to the open world
_, _ = loadScene(s, returnEntry, true, nil, nil)
_ = refreshLineup(s)
return nil
}
func handleLeaveChallengeTierce(s *Session, _ []byte) error {
var returnEntry uint32 = 102020107
if player := s.JSON(); player != nil {
player.BattleConfig.CustomBattleLineup = nil
// Always return safely to Belobog Admin District 102020107
player.Scene.EntryID = 102020107
player.Scene.PlaneID = 10202
player.Scene.FloorID = 10202001
player.Position = common.Position{X: -92, Y: 4278, Z: 68083, RotY: 155566}
player.SavePersistent()
}
if err := s.SendRaw(&NetPacket{CmdType: 8997, Body: []byte{0x38, 0x00}}); err != nil {
// 4.6.51 LeaveChallengeTierceScRsp (cmd 8983, tag 13 retcode 0)
if err := s.SendRaw(&NetPacket{CmdType: 8983, Body: []byte{0x68, 0x00}}); err != nil {
return err
}
_ = s.SendRaw(&NetPacket{CmdType: 8997, Body: []byte{0x38, 0x00}})
_ = s.SendRaw(&NetPacket{CmdType: 8982, Body: []byte{0x68, 0x00}})
// Restore the player back to the open world
_, _ = loadScene(s, returnEntry, true, nil, nil)
_ = refreshLineup(s)
return nil
}
@@ -1017,62 +1057,26 @@ func buildGetChallengeTierceDataScRspReal() []byte {
}
sort.Slice(tierceStages, func(i, j int) bool { return tierceStages[i].id < tierceStages[j].id })
var groups []*goproto.ChallengeTierceGroup
for _, e := range tierceStages {
groups = append(groups, &goproto.ChallengeTierceGroup{
ChallengeId: e.id,
IsWin: true,
FinishedTargetList: e.targets,
})
}
rspMsg := &goproto.GetChallengeTierceDataScRsp{
ChallengeTierceGroups: groups,
Retcode: 0,
}
body, err := proto.Marshal(rspMsg)
if err == nil {
return body
}
var rsp []byte
rsp = append(rsp, 0x40, 0x00) // tag 8: retcode = 0
rsp = append(rsp, 0x60, 0x00) // legacy tag 12: retcode = 0
for _, e := range tierceStages {
var tierceData []byte
// tag 3: challenge_id
tierceData = append(tierceData, 0x18)
tierceData = appendUvarint(tierceData, uint64(e.id))
// tag 6: is_passed = true
tierceData = append(tierceData, 0x30, 0x01)
// tag 7: finished_target_list (packed)
var targetsBuf []byte
for _, t := range e.targets {
targetsBuf = appendUvarint(targetsBuf, uint64(t))
}
tierceData = append(tierceData, 0x3A)
tierceData = appendUvarint(tierceData, uint64(len(targetsBuf)))
tierceData = append(tierceData, targetsBuf...)
// tag 4: result_list (3 stages)
for stageIdx := uint32(0); stageIdx < 3; stageIdx++ {
var result []byte
result = append(result, 0x18) // tag 3: score_id
result = appendUvarint(result, uint64(e.score))
result = append(result, 0x48) // tag 9: stage_index
result = appendUvarint(result, uint64(stageIdx))
result = append(result, 0x50, 0x01) // tag 10: end_status = WIN
tierceData = append(tierceData, 0x22)
tierceData = appendUvarint(tierceData, uint64(len(result)))
tierceData = append(tierceData, result...)
}
// tag 8: stage_info_list (3 stages)
for stageIdx := uint32(0); stageIdx < 3; stageIdx++ {
var sinfo []byte
sinfo = append(sinfo, 0x70) // tag 14: stage_index
sinfo = appendUvarint(sinfo, uint64(stageIdx))
tierceData = append(tierceData, 0x42)
tierceData = appendUvarint(tierceData, uint64(len(sinfo)))
tierceData = append(tierceData, sinfo...)
}
// tag 4: challenge_info_list (4.5.52)
rsp = append(rsp, 0x22)
rsp = appendUvarint(rsp, uint64(len(tierceData)))
rsp = append(rsp, tierceData...)
// tag 10: challenge_info_list (legacy)
rsp = append(rsp, 0x52)
rsp = appendUvarint(rsp, uint64(len(tierceData)))
rsp = append(rsp, tierceData...)
}
return rsp
}
@@ -67,6 +67,29 @@ func loadChallengeScene(s *Session, entryID, targetGroupID, monsterID, eventID u
}
// 2. position the player safely between boss and arena center
// 2. detect arena floor platform height from props in scene
// In some challenge arenas (e.g. entrance 3012602 / AS Starward 30225),
// the boss monster pos in config is placed at Y = -193081 (22m below the floor),
// while the arena stage prop (102259) is at Y = -170770.
// Spawning at monsPos.Y puts the player under the map into the void.
hasFloorProp := false
var floorY int32 = -2147483648
for _, g := range scene.Scenes {
for _, p := range g.Props {
dx := float64(p.Pos.X)
dz := float64(p.Pos.Z)
if math.Sqrt(dx*dx+dz*dz) < 150000.0 { // within 150 meters of center
if p.Pos.Y > floorY {
floorY = p.Pos.Y
hasFloorProp = true
}
}
}
}
if hasFloorProp && floorY > monsPos.Y {
monsPos.Y = floorY
}
centerVX := float64(-monsPos.X)
centerVZ := float64(-monsPos.Z)
distToCenter := math.Sqrt(centerVX*centerVX + centerVZ*centerVZ)
@@ -99,10 +122,7 @@ func loadChallengeScene(s *Session, entryID, targetGroupID, monsterID, eventID u
Pos: &goproto.Vector{X: spawnX, Y: spawnY, Z: spawnZ},
Rot: &goproto.Vector{Y: spawnRotY},
}
player.Scene.EntryID = entryID
player.Scene.FloorID = floorID
player.Scene.PlaneID = planeID
// NOTE: keep player.Scene intact so returning to overworld or relogging spawns in overworld
sceneInfo := &goproto.SceneInfo{
FloorId: floorID,
@@ -119,13 +139,10 @@ func loadChallengeScene(s *Session, entryID, targetGroupID, monsterID, eventID u
}
sceneInfo.LightenSectionList = scene.Sections
// load props for group 1, target group, and common prop groups (7/8)
// load all props in the arena scene (do not filter out prop groups like group 2 / group 3)
propEntityID := uint32(1000)
s.PropEntities = map[uint32]PropEntry{}
for _, gid := range sortedU32Keys2(scene.Scenes) {
if gid != 1 && gid != targetGroupID && gid != 7 && gid != 8 {
continue
}
group := scene.Scenes[gid]
groupInfo := &goproto.SceneEntityGroupInfo{GroupId: gid}
for i := range group.Props {
+31
View File
@@ -50,9 +50,17 @@ func init() {
rsp.RelicList = append(rsp.RelicList, player.Relics[i].ToProtoRelic())
}
rsp.MaterialList = []*goproto.Material{
{Tid: 1, Num: 9999999}, // Stellar Jade
{Tid: 2, Num: 99999999}, // Credit
{Tid: 101, Num: 999999}, // Normal Pass
{Tid: 102, Num: 999999}, // Special Pass
}
for _, aid := range BaseAvatarIDs {
rsp.MaterialList = append(rsp.MaterialList, &goproto.Material{
Tid: 130000 + aid,
Num: 10,
})
}
return nil
})
@@ -99,7 +107,30 @@ func init() {
if !ok {
return nil
}
isExRank := false
if req.CostData != nil {
for _, item := range req.CostData.ItemList {
if pile := item.GetPileItem(); pile != nil {
if (pile.ItemId >= 130000 && pile.ItemId < 140000) || pile.ItemId == 131204 || pile.ItemId == 131213 || pile.ItemId == 131308 {
isExRank = true
break
}
}
}
}
if req.Rank > 6 {
isExRank = true
}
if isExRank || (avatar.Data.Rank >= 6 && common.AvatarExRankMap[req.AvatarId] != 0) {
avatar.ExRankUnlocked = true
if req.Rank > 0 && req.Rank <= 6 {
avatar.Data.Rank = req.Rank
}
} else {
avatar.Data.Rank = req.Rank
}
ret := &goproto.PlayerSyncScNotify{}
buildSync(player, ret, []uint32{avatar.AvatarID}, nil, nil)
+20 -1
View File
@@ -2,6 +2,9 @@ package gameserver
import (
"os"
"path/filepath"
"sort"
"strings"
goproto "gac-hkrpg-go/protocol/proto"
)
@@ -37,7 +40,23 @@ func init() {
var finalData []byte
finalData = append(finalData, heartbeatLua...)
if freecam, err := os.ReadFile("scripts/freecam.lua"); err == nil {
// Load all .lua files from scripts/ directory in deterministic order
if entries, err := os.ReadDir("scripts"); err == nil {
var luaFiles []string
for _, entry := range entries {
if !entry.IsDir() && strings.HasSuffix(entry.Name(), ".lua") {
luaFiles = append(luaFiles, entry.Name())
}
}
sort.Strings(luaFiles)
for _, name := range luaFiles {
if content, err := os.ReadFile(filepath.Join("scripts", name)); err == nil {
finalData = append(finalData, '\n')
finalData = append(finalData, content...)
}
}
} else if freecam, err := os.ReadFile("scripts/freecam.lua"); err == nil {
finalData = append(finalData, '\n')
finalData = append(finalData, freecam...)
}
+125 -76
View File
@@ -1,6 +1,11 @@
package gameserver
import (
"fmt"
"google.golang.org/protobuf/proto"
"gac-hkrpg-go/internal/common"
goproto "gac-hkrpg-go/protocol/proto"
)
@@ -57,6 +62,7 @@ func init() {
simpleReply([]uint16{2966}, 2955, []byte{0x30, 0x00}) // tag 6 retcode 0
simpleReply([]uint16{2916}, 2906, []byte{0x18, 0x00}) // tag 3 retcode 0
simpleReply([]uint16{2919}, 2928, []byte{0x48, 0x00}) // tag 9 retcode 0
simpleReply([]uint16{2974}, 2919, []byte{0x18, 0x00}) // GetFriendListInfo tag 3 retcode 0
simpleReply([]uint16{740}, 733, []byte{0x58, 0x00}) // tag 11 retcode 0
simpleReply([]uint16{1748}, 1745, []byte{0x08, 0x00}) // tag 1 retcode 0
simpleReply([]uint16{1765}, 1730, []byte{0x08, 0x00}) // tag 1 retcode 0
@@ -64,7 +70,7 @@ func init() {
simpleReply([]uint16{1738}, 1748, nil) // empty body
simpleReply([]uint16{188}, 181, []byte{0x50, 0x00}) // tag 10 retcode 0
simpleReply([]uint16{4130}, 4124, []byte{0x70, 0x00}) // tag 14 retcode 0
simpleReply([]uint16{8110, 8115}, 8104, []byte{0x18, 0x00}) // GetSwitchHandData tag 3 retcode 0
simpleReply([]uint16{8110, 8115}, 8108, []byte{0x18, 0x00}) // GetSwitchHandData tag 3 retcode 0 (4.6.51 CmdID: 8108)
simpleReply([]uint16{8993, 8991}, 8981, []byte{0x70, 0x00}) // 4.6.51: tag 14 retcode 0
simpleReply([]uint16{8987}, 8986, []byte{0x38, 0x00}) // ConfirmChallengeTierceStageSettle tag 7 retcode 0
@@ -81,21 +87,98 @@ func init() {
rawHandlers[1731] = handleStartChallenge
rawHandlers[1793] = handleStartChallenge
rawHandlers[8986] = handleStartChallengeTierce
rawHandlers[8983] = handleStartChallengeTierce
rawHandlers[8988] = handleStartChallengeTierce
rawHandlers[8978] = handleSetChallengeTierceLineup
rawHandlers[9000] = handleSetChallengeTierceLineup
rawHandlers[1705] = handleLeaveChallenge
rawHandlers[1760] = handleLeaveChallenge
rawHandlers[1788] = handleLeaveChallenge
rawHandlers[8989] = handleLeaveChallengeTierce
rawHandlers[8998] = handleLeaveChallengeTierce
rawHandlers[1739] = handleTakeChallengeReward
// ---- 400 -> Ex-Rank / Affinitive Eidolon Unlock ----
rawHandlers[400] = handleExRankUnlock
}
// challengeGroupStatsReply ports the 1775/1711 arm: pull group_id (field 12
// varint) from the request and answer cmd 1708 with per-mode statistics.
// handleExRankUnlock handles CmdID 400 (Affinitive Eidolon / Ex-Rank unlock request from client).
func handleExRankUnlock(s *Session, payload []byte) error {
Log(fmt.Sprintf("[EX-RANK] Received cmd 400 (len: %d) payload: %x", len(payload), payload))
var parsedVal uint32
buf := payload
for len(buf) > 0 {
tag, n := readUvarint(buf)
if n == 0 {
break
}
buf = buf[n:]
wireType := tag & 0x7
fieldNum := tag >> 3
if wireType == 0 {
v, n2 := readUvarint(buf)
if n2 > 0 {
buf = buf[n2:]
Log(fmt.Sprintf("[EX-RANK] field %d = %d", fieldNum, v))
if v >= 100000 || (v >= 1000 && v < 10000 && parsedVal == 0) {
parsedVal = uint32(v)
}
continue
}
}
skipWireField(uint32(wireType), &buf)
}
var avatarID uint32
if parsedVal >= 100000 {
avatarID = parsedVal / 100 // e.g. 120401 -> 1204, 121301 -> 1213, 130801 -> 1308
} else if parsedVal >= 1000 && parsedVal < 10000 {
avatarID = parsedVal
}
Log(fmt.Sprintf("[EX-RANK] Target avatarID: %d (from val: %d)", avatarID, parsedVal))
player := s.JSON()
if player != nil {
if avatarID == 0 {
// Fallback: unlock for all avatars that have ExRank defined (1204, 1213, 1308)
for aid := range common.AvatarExRankMap {
if av, ok := player.Avatars[aid]; ok {
av.ExRankUnlocked = true
Log(fmt.Sprintf("[EX-RANK] Fallback set ExRankUnlocked=true for avatar %d", aid))
syncNotify := &goproto.PlayerSyncScNotify{}
buildSync(player, syncNotify, []uint32{aid}, nil, nil)
_ = s.Send(syncNotify)
}
}
} else {
if av, ok := player.Avatars[avatarID]; ok {
av.ExRankUnlocked = true
Log(fmt.Sprintf("[EX-RANK] Set ExRankUnlocked=true for avatar %d", avatarID))
}
syncNotify := &goproto.PlayerSyncScNotify{}
buildSync(player, syncNotify, []uint32{avatarID}, nil, nil)
_ = s.Send(syncNotify)
}
player.SavePersistent()
}
// Send candidate response packets so client RPC callback resolves without spinning
// Candidates in CmdAvatarType: 395 (CmdAvatarType_ENKEIJOLCNO), 354, 355, 364, 341
for _, rspID := range []uint16{395, 354, 355, 364, 341} {
_ = s.SendRaw(&NetPacket{CmdType: rspID, Body: []byte{}})
}
return nil
}
// challengeGroupStatsReply handles CmdGetChallengeGroupStatisticsCsReq (1775/1711)
// and returns GetChallengeGroupStatisticsScRsp (1708) using standard 4.6.51 proto.
func challengeGroupStatsReply(s *Session, payload []byte) error {
groupID := uint32(100)
var req goproto.GetChallengeGroupStatisticsCsReq
if err := proto.Unmarshal(payload, &req); err == nil && req.GroupId != 0 {
groupID = req.GroupId
} else {
buf := payload
for len(buf) > 0 {
tag, n := readUvarint(buf)
@@ -105,89 +188,55 @@ func challengeGroupStatsReply(s *Session, payload []byte) error {
buf = buf[n:]
fieldNumber := tag >> 3
wireType := tag & 0x7
if fieldNumber == 12 && wireType == 0 {
if (fieldNumber == 5 || fieldNumber == 12) && wireType == 0 {
v, n2 := readUvarint(buf)
if n2 == 0 {
break
}
buf = buf[n2:]
if n2 > 0 {
groupID = uint32(v)
break
} else if wireType == 0 {
_, n2 := readUvarint(buf)
if n2 == 0 {
break
}
buf = buf[n2:]
} else {
break
}
skipWireField(uint32(wireType), &buf)
}
}
var body []byte
// tag 9: group_id
body = append(body, 0x48)
body = appendUvarint(body, uint64(groupID))
// tag 15: retcode 0
body = append(body, 0x78, 0x00)
rsp := &goproto.GetChallengeGroupStatisticsScRsp{
GroupId: groupID,
Retcode: 0,
}
switch {
case groupID >= 2000 && groupID < 3000:
// Pure Fiction: tag 8 = challenge_story (ChallengeStoryStatistics)
var eik []byte
eik = append(eik, 0x50) // tag 10: score_id 80000
eik = appendUvarint(eik, 80000)
eik = append(eik, 0x58) // tag 11: level 4
eik = appendUvarint(eik, 4)
var css []byte
css = append(css, 0x32) // tag 6
css = appendUvarint(css, uint64(len(eik)))
css = append(css, eik...)
css = append(css, 0x38) // tag 7: record_id 1
css = appendUvarint(css, 1)
body = append(body, 0x42) // tag 8
body = appendUvarint(body, uint64(len(css)))
body = append(body, css...)
case groupID >= 1000 && groupID < 2000:
// Memory of Chaos: tag 2 = challenge_default (ChallengeStatistics)
var adk []byte
adk = append(adk, 0x10) // tag 2: round_count 20
adk = appendUvarint(adk, 20)
adk = append(adk, 0x18) // tag 3: level 12
adk = appendUvarint(adk, 12)
var cs []byte
cs = append(cs, 0x32) // tag 6
cs = appendUvarint(cs, uint64(len(adk)))
cs = append(cs, adk...)
cs = append(cs, 0x50) // tag 10: record_id 1
cs = appendUvarint(cs, 1)
body = append(body, 0x12) // tag 2
body = appendUvarint(body, uint64(len(cs)))
body = append(body, cs...)
case groupID >= 3000:
// Apocalyptic Shadow: tag 11 = challenge_boss (ChallengeBossStatistics)
var aan []byte
aan = append(aan, 0x50) // tag 10: score_id 8000
aan = appendUvarint(aan, 8000)
aan = append(aan, 0x18) // tag 3: level 4
aan = appendUvarint(aan, 4)
var cbs []byte
cbs = append(cbs, 0x0A) // tag 1
cbs = appendUvarint(cbs, uint64(len(aan)))
cbs = append(cbs, aan...)
cbs = append(cbs, 0x70) // tag 14: record_id 1
cbs = appendUvarint(cbs, 1)
body = append(body, 0x5A) // tag 11
body = appendUvarint(body, uint64(len(cbs)))
body = append(body, cbs...)
// Apocalyptic Shadow: challenge_boss (field 1)
rsp.ChallengeBoss = &goproto.ChallengeBossStatistics{
RecordId: 1,
PPBHLLOJNEK: &goproto.AANLJBLOOFO{
Level: 4,
ScoreId: 8000,
},
}
case groupID >= 2000 && groupID < 3000:
// Pure Fiction: challenge_story (field 3)
rsp.ChallengeStory = &goproto.ChallengeStoryStatistics{
RecordId: 1,
PPBHLLOJNEK: &goproto.EIKPHEMHIOH{
ScoreId: 80000,
},
}
case groupID >= 1000 && groupID < 2000:
// Memory of Chaos: challenge_default (field 11)
rsp.ChallengeDefault = &goproto.ChallengeStatistics{
RecordId: 1,
PPBHLLOJNEK: &goproto.ADKJKMKBFDC{
RoundCount: 20,
Level: 12,
},
}
}
body, err := proto.Marshal(rsp)
if err != nil {
return err
}
return s.SendRaw(&NetPacket{CmdType: 1708, Body: body})
}
+137 -29
View File
@@ -20,16 +20,36 @@ import (
// Rust iterated a HashMap arbitrarily; we pick the lexicographically first
// name for determinism.
func firstSceneOfEntry(res *common.JsonConfig, entryID uint32) (string, *common.LevelOutputConfig, bool) {
m, ok := res.LevelOutputConfigs[entryID]
if !ok || len(m) == 0 {
return "", nil, false
}
if m, ok := res.LevelOutputConfigs[entryID]; ok && len(m) > 0 {
names := make([]string, 0, len(m))
for k := range m {
names = append(names, k)
}
sort.Strings(names)
return names[0], m[names[0]], true
}
if entryID != 0 {
name := fmt.Sprintf("P%d_F%d", entryID/100, entryID*10+1)
cfg := &common.LevelOutputConfig{
IsEnteredSceneInfo: true,
Scenes: map[uint32]common.SceneData{
1: {
Teleports: map[uint32]common.TeleportInfo{
entryID: {Pos: common.Vector{X: 0, Y: 0, Z: 0}},
1: {Pos: common.Vector{X: 0, Y: 0, Z: 0}},
},
},
},
}
if res.LevelOutputConfigs == nil {
res.LevelOutputConfigs = map[uint32]map[string]*common.LevelOutputConfig{}
}
res.LevelOutputConfigs[entryID] = map[string]*common.LevelOutputConfig{
name: cfg,
}
return name, cfg, true
}
return "", nil, false
}
// parsePlaneFloor splits "P<plane>_F<floor>".
@@ -65,19 +85,24 @@ func init() {
return nil
}
// recover from a stale challenge arena back to the Parlor Car
// recover from a stale challenge arena, broken map, or void coordinate back to Belobog Admin District
entryID := player.Scene.EntryID
if entryID >= 3000000 && entryID < 4000000 {
entryID = 100000104
if (entryID >= 3000000 && entryID < 4000000) || entryID == 100000104 || entryID == 100000304 || entryID == 1030402 || entryID == 0 || player.Position.Y < -50000 {
entryID = 102020107
player.Scene.EntryID = 102020107
player.Scene.PlaneID = 10202
player.Scene.FloorID = 10202001
player.Position = common.Position{X: -92, Y: 4278, Z: 68083, RotY: 155566}
player.SavePersistent()
}
defaultScene := &goproto.SceneInfo{
GameModeType: 1,
EntryId: entryID,
}
if entryID == 100000104 {
defaultScene.PlaneId = 10000
defaultScene.FloorId = 10000000
if entryID == 102020107 {
defaultScene.PlaneId = 10202
defaultScene.FloorId = 10202001
} else {
defaultScene.PlaneId = player.Scene.PlaneID
defaultScene.FloorId = player.Scene.FloorID
@@ -103,10 +128,10 @@ func init() {
}
player := s.JSON()
currentEntry := func() uint32 {
if player != nil {
if player != nil && player.Scene.EntryID != 0 && player.Scene.EntryID != 100000104 && player.Scene.EntryID != 100000304 {
return player.Scene.EntryID
}
return 100000104
return 102020107
}
interactID := uint32(req.InteractId)
@@ -115,17 +140,17 @@ func init() {
sceneIdent = proto.Clone(req.SceneIdentifier).(*goproto.SceneIdentifier)
}
// resolve target entry (mirrors the Rust resolution chain)
// resolve target entry (mirrors the Rust resolution chain, with cascading fallbacks)
resolvedEntry := uint32(0)
switch {
case req.EntryId != 0 && mapHasEntry(res, req.EntryId):
if req.EntryId != 0 && mapHasEntry(res, req.EntryId) {
resolvedEntry = req.EntryId
case sceneIdent != nil && sceneIdent.FloorId != 0:
} else if sceneIdent != nil && sceneIdent.FloorId != 0 {
resolvedEntry = findEntryForFloor(res, sceneIdent.FloorId)
case req.EntryId != 0 && findEntryForFloor(res, req.EntryId) != 0:
// req.EntryId doubles as a floor id in some map flows
}
if resolvedEntry == 0 && req.EntryId != 0 {
resolvedEntry = findEntryForFloor(res, req.EntryId)
case req.TeleportId != 0:
}
if resolvedEntry == 0 && req.TeleportId != 0 {
floorID := uint32(0)
if sceneIdent != nil {
floorID = sceneIdent.FloorId
@@ -133,21 +158,26 @@ func init() {
if entry := findEntryByTeleportInFloor(res, req.TeleportId, floorID); entry != 0 {
resolvedEntry = entry
} else {
resolvedEntry = findEntryByTeleport(res, req.TeleportId, currentEntry())
resolvedEntry = findEntryByTeleport(res, req.TeleportId, 0)
}
case sceneIdent != nil && sceneIdent.TeleportNigger != nil:
}
if resolvedEntry == 0 && sceneIdent != nil && sceneIdent.TeleportNigger != nil {
if t, okc := sceneIdent.TeleportNigger.(*goproto.SceneIdentifier_MDAIDPPKOPO); okc && t.MDAIDPPKOPO != nil && t.MDAIDPPKOPO.PLIDNBMCIJH != 0 {
// map teleport click: teleport id + floor arrive via scene_identifier
tpID := t.MDAIDPPKOPO.PLIDNBMCIJH
if entry := findEntryByTeleportInFloor(res, tpID, sceneIdent.FloorId); entry != 0 {
resolvedEntry = entry
} else {
resolvedEntry = findEntryByTeleport(res, tpID, currentEntry())
resolvedEntry = findEntryByTeleport(res, tpID, 0)
}
}
case interactID != 0:
resolvedEntry = findEntryByInteract(res, interactID, currentEntry())
default:
}
if resolvedEntry == 0 && interactID != 0 {
resolvedEntry = findEntryByInteract(res, interactID, 0)
}
if resolvedEntry == 0 && req.EntryId != 0 {
resolvedEntry = req.EntryId
}
if resolvedEntry == 0 {
resolvedEntry = currentEntry()
}
@@ -215,6 +245,10 @@ func init() {
// a wrong state (like 1100) breaks interactions and UI entry points
state := uint32(1)
if p, ok := s.PropEntities[req.PropEntityId]; ok {
if p.State == 0 {
p.State = 1
s.PropEntities[req.PropEntityId] = p
}
state = p.State
}
@@ -354,7 +388,6 @@ func init() {
}
}
}
player.SavePersistent()
return nil
})
@@ -396,16 +429,19 @@ func init() {
// cmd 1432 -> 1421: unlock every teleport in the game
handle("GetUnlockTeleport", func(s *Session, req *goproto.GetUnlockTeleportCsReq, rsp *goproto.GetUnlockTeleportScRsp) error {
rsp.Retcode = 0
unlocks := getMissionUnlocks()
seen := map[uint32]bool{}
var tps []uint32
// 1. All IDs from mission_unlocks.json
unlocks := getMissionUnlocks()
for _, id := range unlocks.TeleportIDs {
if id != 0 && !seen[id] {
seen[id] = true
tps = append(tps, id)
}
}
if len(tps) == 0 {
// 2. All IDs from teleports DB across all floors
for _, m := range common.Teleports() {
for id := range m {
if id != 0 && !seen[id] {
@@ -414,13 +450,85 @@ func init() {
}
}
}
// 3. All teleport IDs from LevelOutputConfigs
if res, err := common.GameRes(); err == nil {
for _, m := range res.LevelOutputConfigs {
for _, cfg := range m {
for _, sc := range cfg.Scenes {
for tid := range sc.Teleports {
if tid != 0 && !seen[tid] {
seen[tid] = true
tps = append(tps, tid)
}
}
}
}
}
}
// 4. Any entry IDs requested by client or in EntranceIDs
for _, eid := range req.EntryIdList {
if eid != 0 && !seen[eid] {
seen[eid] = true
tps = append(tps, eid)
}
}
for _, id := range unlocks.EntranceIDs {
if id != 0 && !seen[id] {
seen[id] = true
tps = append(tps, id)
}
}
sort.Slice(tps, func(i, j int) bool { return tps[i] < tps[j] })
rsp.UnlockedTeleportList = tps
SceneDebugLog("GET_UNLOCK_TELEPORT req_entries=%d teleports_sent=%d", len(req.EntryIdList), len(rsp.UnlockedTeleportList))
return nil
})
// cmd 1438 -> 1409: handle entity teleport within scene
handle("SceneEntityTeleport", func(s *Session, req *goproto.SceneEntityTeleportCsReq, rsp *goproto.SceneEntityTeleportScRsp) error {
rsp.Retcode = 0
rsp.EntityMotion = req.EntityMotion
if req.EntityMotion != nil && req.EntityMotion.Motion != nil {
motion := req.EntityMotion.Motion
player := s.JSON()
if player != nil {
if motion.Pos != nil {
player.Position.X = motion.Pos.X
player.Position.Y = motion.Pos.Y
player.Position.Z = motion.Pos.Z
}
if motion.Rot != nil {
player.Position.RotY = motion.Rot.Y
}
player.SavePersistent()
}
}
SceneDebugLog("SCENE_ENTITY_TELEPORT entry_id=%d", req.EntryId)
return nil
})
// cmd 1267 -> 1263: teleport to mission reset point
handle("TeleportToMissionResetPoint", func(s *Session, req *goproto.TeleportToMissionResetPointCsReq, rsp *goproto.TeleportToMissionResetPointScRsp) error {
rsp.Retcode = 0
player := s.JSON()
if player != nil {
rsp.Motion = &goproto.MotionInfo{
Pos: &goproto.Vector{
X: player.Position.X,
Y: player.Position.Y,
Z: player.Position.Z,
},
Rot: &goproto.Vector{
Y: player.Position.RotY,
},
}
}
return nil
})
}
// findEntryForFloor resolves an entry id for a floor WITHOUT relying on
Binary file not shown.
+17 -12
View File
@@ -1,24 +1,29 @@
{
"lineups": {
"0": 1506,
"1": 1501,
"2": 1502,
"3": 1503
"0": 1204,
"1": 1213,
"2": 1308,
"3": 1409
},
"position": {
"x": -13238,
"y": 592,
"z": 86502,
"rot_y": 290000
"x": -92,
"y": 4278,
"z": 68083,
"rot_y": 155566
},
"scene": {
"plane_id": 20331,
"floor_id": 20331001,
"entry_id": 2033101
"plane_id": 10202,
"floor_id": 10202001,
"entry_id": 102020107
},
"main_character": "FemaleHarmony",
"march_type": "MarchHunt",
"enable_sw_global": true,
"enable_castorice_global": true,
"dressed_skins": {}
"dressed_skins": {},
"ex_ranks_unlocked": {
"1204": true,
"1213": true,
"1308": true
}
}
+4
View File
@@ -52,6 +52,7 @@ var CmdIDByName = map[string]uint16{
"StartQuickCocoonStageCsReq": 1305,
"StartQuickCocoonStageRsp": 1371,
"StartQuickFarmElementCsReq": 1359,
"StartQuickFarmElementRsp": 1318,
"CocoonSweepCsReq": 1306,
"CocoonSweepScRsp": 1370,
"FarmElementSweepCsReq": 1327,
@@ -671,6 +672,7 @@ var CmdIDByName = map[string]uint16{
"GetFiveDimMoneyDataCsReq": 2275,
"GetFiveDimMoneyDataScRsp": 2260,
"GetFriendListInfoCsReq": 2974,
"GetFriendListInfoScRsp": 2919,
"GetPlayerDetailInfoCsReq": 2931,
"GetPlayerDetailInfoScRsp": 2987,
"GetFriendApplyListInfoCsReq": 2905,
@@ -2170,6 +2172,7 @@ var NameByCmdID = map[uint16]string{
1305: "StartQuickCocoonStageCsReq",
1371: "StartQuickCocoonStageRsp",
1359: "StartQuickFarmElementCsReq",
1318: "StartQuickFarmElementRsp",
1306: "CocoonSweepCsReq",
1370: "CocoonSweepScRsp",
1327: "FarmElementSweepCsReq",
@@ -2789,6 +2792,7 @@ var NameByCmdID = map[uint16]string{
2275: "GetFiveDimMoneyDataCsReq",
2260: "GetFiveDimMoneyDataScRsp",
2974: "GetFriendListInfoCsReq",
2919: "GetFriendListInfoScRsp",
2931: "GetPlayerDetailInfoCsReq",
2987: "GetPlayerDetailInfoScRsp",
2905: "GetFriendApplyListInfoCsReq",
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+764
View File
@@ -0,0 +1,764 @@
{
"player_icon_config": [
{
"ID": 201001,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1001.png",
"AvatarID": 1001,
"Type": "Avatar",
"SortType": 3,
"Sort": 61
},
{
"ID": 201002,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1002.png",
"AvatarID": 1002,
"Type": "Avatar",
"SortType": 3,
"Sort": 61
},
{
"ID": 201003,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1003.png",
"AvatarID": 1003,
"Type": "Avatar",
"SortType": 3,
"Sort": 61
},
{
"ID": 201004,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1004.png",
"AvatarID": 1004,
"Type": "Avatar",
"SortType": 3,
"Sort": 61
},
{
"ID": 201005,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1005.png",
"AvatarID": 1005,
"Type": "Avatar",
"SortType": 3,
"Sort": 90
},
{
"ID": 201006,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1006.png",
"AvatarID": 1006,
"Type": "Avatar",
"SortType": 3,
"Sort": 90
},
{
"ID": 201008,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1008.png",
"AvatarID": 1008,
"Type": "Avatar",
"SortType": 3,
"Sort": 61
},
{
"ID": 201009,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1009.png",
"AvatarID": 1009,
"Type": "Avatar",
"SortType": 3,
"Sort": 61
},
{
"ID": 201013,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1013.png",
"AvatarID": 1013,
"Type": "Avatar",
"SortType": 3,
"Sort": 61
},
{
"ID": 201101,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1101.png",
"AvatarID": 1101,
"Type": "Avatar",
"SortType": 3,
"Sort": 61
},
{
"ID": 201102,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1102.png",
"AvatarID": 1102,
"Type": "Avatar",
"SortType": 3,
"Sort": 61
},
{
"ID": 201103,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1103.png",
"AvatarID": 1103,
"Type": "Avatar",
"SortType": 3,
"Sort": 61
},
{
"ID": 201104,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1104.png",
"AvatarID": 1104,
"Type": "Avatar",
"SortType": 3,
"Sort": 61
},
{
"ID": 201105,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1105.png",
"AvatarID": 1105,
"Type": "Avatar",
"SortType": 3,
"Sort": 61
},
{
"ID": 201106,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1106.png",
"AvatarID": 1106,
"Type": "Avatar",
"SortType": 3,
"Sort": 61
},
{
"ID": 201107,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1107.png",
"AvatarID": 1107,
"Type": "Avatar",
"SortType": 3,
"Sort": 61
},
{
"ID": 201108,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1108.png",
"AvatarID": 1108,
"Type": "Avatar",
"SortType": 3,
"Sort": 61
},
{
"ID": 201109,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1109.png",
"AvatarID": 1109,
"Type": "Avatar",
"SortType": 3,
"Sort": 61
},
{
"ID": 201110,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1110.png",
"AvatarID": 1110,
"Type": "Avatar",
"SortType": 3,
"Sort": 130
},
{
"ID": 201111,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1111.png",
"AvatarID": 1111,
"Type": "Avatar",
"SortType": 3,
"Sort": 120
},
{
"ID": 201112,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1112.png",
"AvatarID": 1112,
"Type": "Avatar",
"SortType": 3,
"Sort": 140
},
{
"ID": 201201,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1201.png",
"AvatarID": 1201,
"Type": "Avatar",
"SortType": 3,
"Sort": 90
},
{
"ID": 201202,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1202.png",
"AvatarID": 1202,
"Type": "Avatar",
"SortType": 3,
"Sort": 90
},
{
"ID": 201203,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1203.png",
"AvatarID": 1203,
"Type": "Avatar",
"SortType": 3,
"Sort": 90
},
{
"ID": 201204,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1204.png",
"AvatarID": 1204,
"Type": "Avatar",
"SortType": 3,
"Sort": 64
},
{
"ID": 201205,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1205.png",
"AvatarID": 1205,
"Type": "Avatar",
"SortType": 3,
"Sort": 120
},
{
"ID": 201206,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1206.png",
"AvatarID": 1206,
"Type": "Avatar",
"SortType": 3,
"Sort": 70
},
{
"ID": 201207,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1207.png",
"AvatarID": 1207,
"Type": "Avatar",
"SortType": 3,
"Sort": 110
},
{
"ID": 201208,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1208.png",
"AvatarID": 1208,
"Type": "Avatar",
"SortType": 3,
"Sort": 130
},
{
"ID": 201209,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1209.png",
"AvatarID": 1209,
"Type": "Avatar",
"SortType": 3,
"Sort": 90
},
{
"ID": 201210,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1210.png",
"AvatarID": 1210,
"Type": "Avatar",
"SortType": 3,
"Sort": 140
},
{
"ID": 201211,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1211.png",
"AvatarID": 1211,
"Type": "Avatar",
"SortType": 3,
"Sort": 90
},
{
"ID": 201212,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1212.png",
"AvatarID": 1212,
"Type": "Avatar",
"SortType": 3,
"Sort": 140
},
{
"ID": 201213,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1213.png",
"AvatarID": 1213,
"Type": "Avatar",
"SortType": 3,
"Sort": 120
},
{
"ID": 201214,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1214.png",
"AvatarID": 1214,
"Type": "Avatar",
"SortType": 3,
"Sort": 160
},
{
"ID": 201215,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1215.png",
"AvatarID": 1215,
"Type": "Avatar",
"SortType": 3,
"Sort": 150
},
{
"ID": 201217,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1217.png",
"AvatarID": 1217,
"Type": "Avatar",
"SortType": 3,
"Sort": 150
},
{
"ID": 201218,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1218.png",
"AvatarID": 1218,
"Type": "Avatar",
"SortType": 3,
"Sort": 240
},
{
"ID": 201220,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1220.png",
"AvatarID": 1220,
"Type": "Avatar",
"SortType": 3,
"Sort": 250
},
{
"ID": 201221,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1221.png",
"AvatarID": 1221,
"Type": "Avatar",
"SortType": 3,
"Sort": 240
},
{
"ID": 201222,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1222.png",
"AvatarID": 1222,
"Type": "Avatar",
"SortType": 3,
"Sort": 250
},
{
"ID": 201223,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1223.png",
"AvatarID": 1223,
"Type": "Avatar",
"SortType": 3,
"Sort": 250
},
{
"ID": 201224,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1224.png",
"AvatarID": 1224,
"Type": "Avatar",
"SortType": 3,
"Sort": 240
},
{
"ID": 201225,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1225.png",
"AvatarID": 1225,
"Type": "Avatar",
"SortType": 3,
"Sort": 270
},
{
"ID": 201301,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1301.png",
"AvatarID": 1301,
"Type": "Avatar",
"SortType": 3,
"Sort": 210
},
{
"ID": 201302,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1302.png",
"AvatarID": 1302,
"Type": "Avatar",
"SortType": 3,
"Sort": 150
},
{
"ID": 201303,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1303.png",
"AvatarID": 1303,
"Type": "Avatar",
"SortType": 3,
"Sort": 160
},
{
"ID": 201304,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1304.png",
"AvatarID": 1304,
"Type": "Avatar",
"SortType": 3,
"Sort": 210
},
{
"ID": 201305,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1305.png",
"AvatarID": 1305,
"Type": "Avatar",
"SortType": 3,
"Sort": 160
},
{
"ID": 201306,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1306.png",
"AvatarID": 1306,
"Type": "Avatar",
"SortType": 3,
"Sort": 200
},
{
"ID": 201307,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1307.png",
"AvatarID": 1307,
"Type": "Avatar",
"SortType": 3,
"Sort": 200
},
{
"ID": 201308,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1308.png",
"AvatarID": 1308,
"Type": "Avatar",
"SortType": 3,
"Sort": 210
},
{
"ID": 201309,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1309.png",
"AvatarID": 1309,
"Type": "Avatar",
"SortType": 3,
"Sort": 220
},
{
"ID": 201310,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1310.png",
"AvatarID": 1310,
"Type": "Avatar",
"SortType": 3,
"Sort": 230
},
{
"ID": 201312,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1312.png",
"AvatarID": 1312,
"Type": "Avatar",
"SortType": 3,
"Sort": 200
},
{
"ID": 201313,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1313.png",
"AvatarID": 1313,
"Type": "Avatar",
"SortType": 3,
"Sort": 270
},
{
"ID": 201314,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1314.png",
"AvatarID": 1314,
"Type": "Avatar",
"SortType": 3,
"Sort": 230
},
{
"ID": 201315,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1315.png",
"AvatarID": 1315,
"Type": "Avatar",
"SortType": 3,
"Sort": 220
},
{
"ID": 201317,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1317.png",
"AvatarID": 1317,
"Type": "Avatar",
"SortType": 3,
"Sort": 260
},
{
"ID": 201321,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1321.png",
"AvatarID": 1321,
"Type": "Avatar",
"SortType": 3,
"Sort": 380
},
{
"ID": 201401,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1401.png",
"AvatarID": 1401,
"Type": "Avatar",
"SortType": 3,
"Sort": 300
},
{
"ID": 201402,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1402.png",
"AvatarID": 1402,
"Type": "Avatar",
"SortType": 3,
"Sort": 300
},
{
"ID": 201403,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1403.png",
"AvatarID": 1403,
"Type": "Avatar",
"SortType": 3,
"Sort": 310
},
{
"ID": 201404,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1404.png",
"AvatarID": 1404,
"Type": "Avatar",
"SortType": 3,
"Sort": 310
},
{
"ID": 201405,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1405.png",
"AvatarID": 1405,
"Type": "Avatar",
"SortType": 3,
"Sort": 320
},
{
"ID": 201406,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1406.png",
"AvatarID": 1406,
"Type": "Avatar",
"SortType": 3,
"Sort": 330
},
{
"ID": 201407,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1407.png",
"AvatarID": 1407,
"Type": "Avatar",
"SortType": 3,
"Sort": 320
},
{
"ID": 201408,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1408.png",
"AvatarID": 1408,
"Type": "Avatar",
"SortType": 3,
"Sort": 340
},
{
"ID": 201409,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1409.png",
"AvatarID": 1409,
"Type": "Avatar",
"SortType": 3,
"Sort": 330
},
{
"ID": 201410,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1410.png",
"AvatarID": 1410,
"Type": "Avatar",
"SortType": 3,
"Sort": 350
},
{
"ID": 201412,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1412.png",
"AvatarID": 1412,
"Type": "Avatar",
"SortType": 3,
"Sort": 350
},
{
"ID": 201413,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1413.png",
"AvatarID": 1413,
"Type": "Avatar",
"SortType": 3,
"Sort": 360
},
{
"ID": 201414,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1414.png",
"AvatarID": 1414,
"Type": "Avatar",
"SortType": 3,
"Sort": 360
},
{
"ID": 201415,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1415.png",
"AvatarID": 1415,
"Type": "Avatar",
"SortType": 3,
"Sort": 370
},
{
"ID": 201501,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1501.png",
"AvatarID": 1501,
"Type": "Avatar",
"SortType": 3,
"Sort": 400
},
{
"ID": 201502,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1502.png",
"AvatarID": 1502,
"Type": "Avatar",
"SortType": 3,
"Sort": 400
},
{
"ID": 201504,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1504.png",
"AvatarID": 1504,
"Type": "Avatar",
"SortType": 3,
"Sort": 410
},
{
"ID": 201505,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1505.png",
"AvatarID": 1505,
"Type": "Avatar",
"SortType": 3,
"Sort": 420
},
{
"ID": 201506,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1506.png",
"AvatarID": 1506,
"Type": "Avatar",
"SortType": 3,
"Sort": 420
},
{
"ID": 208001,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/8001.png",
"AvatarID": 8001,
"Type": "Hero",
"SortType": 3,
"Sort": 63
},
{
"ID": 208002,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/8002.png",
"AvatarID": 8002,
"Type": "Hero",
"SortType": 3,
"Sort": 63
},
{
"ID": 208003,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/8003.png",
"AvatarID": 8003,
"Type": "Hero",
"SortType": 3,
"Sort": 63
},
{
"ID": 208004,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/8004.png",
"AvatarID": 8004,
"Type": "Hero",
"SortType": 3,
"Sort": 63
},
{
"ID": 208005,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/8005.png",
"AvatarID": 8005,
"Type": "Hero",
"SortType": 3,
"Sort": 220
},
{
"ID": 208006,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/8006.png",
"AvatarID": 8006,
"Type": "Hero",
"SortType": 3,
"Sort": 220
},
{
"ID": 208007,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/8007.png",
"AvatarID": 8007,
"Type": "Hero",
"SortType": 3,
"Sort": 370
},
{
"ID": 208008,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/8008.png",
"AvatarID": 8008,
"Type": "Hero",
"SortType": 3,
"Sort": 370
},
{
"ID": 208009,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/8009.png",
"AvatarID": 8009,
"Type": "Hero",
"SortType": 3,
"Sort": 420
},
{
"ID": 208010,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/8010.png",
"AvatarID": 8010,
"Type": "Hero",
"SortType": 3,
"Sort": 420
},
{
"ID": 201507,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1507.png",
"AvatarID": 1507,
"Type": "Avatar",
"SortType": 3,
"Sort": 430
},
{
"ID": 201510,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1510.png",
"AvatarID": 1510,
"Type": "Avatar",
"SortType": 3,
"Sort": 440
},
{
"ID": 201503,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1503.png",
"AvatarID": 1503,
"Type": "Avatar",
"SortType": 3,
"Sort": 460
},
{
"ID": 201512,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1512.png",
"AvatarID": 1512,
"Type": "Avatar",
"SortType": 3,
"Sort": 450
},
{
"ID": 201513,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1513.png",
"AvatarID": 1513,
"Type": "Avatar",
"SortType": 3,
"Sort": 450
},
{
"ID": 201511,
"ImagePath": "SpriteOutput/AvatarRoundIcon/Avatar/1511.png",
"AvatarID": 1511,
"Type": "Avatar",
"SortType": 3,
"Sort": 470
}
]
}
File diff suppressed because it is too large. Load diff
+835
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
File diff suppressed because it is too large. Load diff
+136
View File
@@ -0,0 +1,136 @@
{
"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]
},
{
"ID": 404,
"HardTitle": {
"Hash": -829434186,
"Hash64": 11316176485030290708
},
"BuffList": [3033043, 3033044, 3033045],
"ColorMedalTarget": 6,
"HardTarget": 3007,
"HardEventIDList": [30504022],
"HardTagList": [3033026, 3033046]
},
{
"ID": 504,
"HardTitle": {
"Hash": -829434155,
"Hash64": 7086567855550187989
},
"BuffList": [3033048, 3033049, 3033050],
"ColorMedalTarget": 6,
"HardTarget": 3007,
"HardEventIDList": [30505022],
"HardTagList": [3033018, 3033026, 3033046]
},
{
"ID": 604,
"HardTitle": {
"Hash": -829434252,
"Hash64": 6674206141463462266
},
"BuffList": [3033053, 3033054, 3033055],
"ColorMedalTarget": 6,
"HardTarget": 3007,
"HardEventIDList": [30506022],
"HardTagList": [3033057, 3033042]
},
{
"ID": 704,
"HardTitle": {
"Hash": -829434221,
"Hash64": 4139559719394918406
},
"BuffList": [3033060, 3033061, 3033062],
"ColorMedalTarget": 6,
"HardTarget": 3007,
"HardEventIDList": [30507022],
"HardTagList": [3033059, 3033064]
},
{
"ID": 804,
"HardTitle": {
"Hash": -829434046,
"Hash64": 92562529814961898
},
"BuffList": [3033066, 3033068, 3033067],
"ColorMedalTarget": 6,
"HardTarget": 3007,
"HardEventIDList": [30508022],
"HardTagList": [3033070, 3033052]
},
{
"ID": 904,
"HardTitle": {
"Hash": -829434015,
"Hash64": 2996493158462877067
},
"BuffList": [3033073, 3033074, 3033087],
"ColorMedalTarget": 6,
"HardTarget": 3007,
"HardEventIDList": [30509022],
"HardTagList": [3033059, 3033072]
},
{
"ID": 1004,
"HardTitle": {
"Hash": 968936817,
"Hash64": 15826532082466368205
},
"BuffList": [3033082, 3033083, 3033084],
"ColorMedalTarget": 6,
"HardTarget": 3007,
"HardEventIDList": [30510022],
"HardTagList": [3033059, 3033079, 3033080]
},
{
"ID": 1104,
"HardTitle": {
"Hash": -617350100,
"Hash64": 13103456677221618482
},
"BuffList": [3033095, 3033074, 3033008],
"ColorMedalTarget": 6,
"HardTarget": 3007,
"HardEventIDList": [30511022],
"HardTagList": [3033057, 3033094, 3033026]
}
]
}
File diff suppressed because it is too large. Load diff
+257
View File
@@ -0,0 +1,257 @@
{
"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"
]
},
{
"ID": 4,
"Title": {
"Hash": -1264475023,
"Hash64": 5317369650647745266
},
"RecommendID": 4,
"ActivityModule": 2100401,
"PreLevelIDList": [401, 402, 403],
"BossLevelID": 404,
"RewardGroupID": 1,
"BossUI3DPrefabPath": "UI/UI3D/ChallengePeak/_dependencies/Prefab/ChallengePeakBoss_W5_Vtuber.prefab",
"BossUI3DAnimatorPath": "UI/UI3D/ChallengePeak/_dependencies/Animator/ChallengePeakBoss_W5_Vtuber_AniControl.controller",
"ThemePosterTabPicPath": "SpriteOutput/Quest/TabIcon/BtnChallengePeak_4004.png",
"ThemeIconPicPath": "SpriteOutput/ChallengePeak/ChallengePeakIcon_4004.png",
"HandBookPanelBannerPath": "SpriteOutput/DailyMission/Banner/ChallengePeakPanelBanner_4004.png",
"RankIconPathList": [
"SpriteOutput/ChallengePeak/MedallionIcon/ChallengePeakMedalIcon0004/Bronze.png",
"SpriteOutput/ChallengePeak/MedallionIcon/ChallengePeakMedalIcon0004/Silver.png",
"SpriteOutput/ChallengePeak/MedallionIcon/ChallengePeakMedalIcon0004/Gold.png",
"SpriteOutput/ChallengePeak/MedallionIcon/ChallengePeakMedalIcon0004/Ultra.png"
]
},
{
"ID": 5,
"Title": {
"Hash": 1464408332,
"Hash64": 9493455258062325921
},
"RecommendID": 5,
"ActivityModule": 2100501,
"PreLevelIDList": [501, 502, 503],
"BossLevelID": 504,
"RewardGroupID": 1,
"BossUI3DPrefabPath": "UI/UI3D/ChallengePeak/_dependencies/Prefab/ChallengePeakBoss_W5_Pam.prefab",
"BossUI3DAnimatorPath": "UI/UI3D/ChallengePeak/_dependencies/Animator/ChallengePeakBoss_W5_Pam_AniControl.controller",
"ThemePosterTabPicPath": "SpriteOutput/Quest/TabIcon/BtnChallengePeak_4005.png",
"ThemeIconPicPath": "SpriteOutput/ChallengePeak/ChallengePeakIcon_4005.png",
"HandBookPanelBannerPath": "SpriteOutput/DailyMission/Banner/ChallengePeakPanelBanner_4005.png",
"RankIconPathList": [
"SpriteOutput/ChallengePeak/MedallionIcon/ChallengePeakMedalIcon0005/Bronze.png",
"SpriteOutput/ChallengePeak/MedallionIcon/ChallengePeakMedalIcon0005/Silver.png",
"SpriteOutput/ChallengePeak/MedallionIcon/ChallengePeakMedalIcon0005/Gold.png",
"SpriteOutput/ChallengePeak/MedallionIcon/ChallengePeakMedalIcon0005/Ultra.png"
]
},
{
"ID": 6,
"Title": {
"Hash": 1867692859,
"Hash64": 5565801180195930457
},
"RecommendID": 6,
"ActivityModule": 2100601,
"PreLevelIDList": [601, 602, 603],
"BossLevelID": 604,
"RewardGroupID": 1,
"BossUI3DPrefabPath": "UI/UI3D/ChallengePeak/_dependencies/Prefab/ChallengePeakBoss_W5_Ripper.prefab",
"BossUI3DAnimatorPath": "UI/UI3D/ChallengePeak/_dependencies/Animator/ChallengePeakBoss_W5_Ripper_AniControl.controller",
"ThemePosterTabPicPath": "SpriteOutput/Quest/TabIcon/BtnChallengePeak_4006.png",
"ThemeIconPicPath": "SpriteOutput/ChallengePeak/ChallengePeakIcon_4006.png",
"HandBookPanelBannerPath": "SpriteOutput/DailyMission/Banner/ChallengePeakPanelBanner_4006.png",
"RankIconPathList": [
"SpriteOutput/ChallengePeak/MedallionIcon/ChallengePeakMedalIcon0006/Bronze.png",
"SpriteOutput/ChallengePeak/MedallionIcon/ChallengePeakMedalIcon0006/Silver.png",
"SpriteOutput/ChallengePeak/MedallionIcon/ChallengePeakMedalIcon0006/Gold.png",
"SpriteOutput/ChallengePeak/MedallionIcon/ChallengePeakMedalIcon0006/Ultra.png"
]
},
{
"ID": 7,
"Title": {
"Hash": 301608918,
"Hash64": 7011664678968873757
},
"RecommendID": 7,
"ActivityModule": 2100701,
"PreLevelIDList": [701, 702, 703],
"BossLevelID": 704,
"RewardGroupID": 1,
"BossUI3DPrefabPath": "UI/UI3D/ChallengePeak/_dependencies/Prefab/ChallengePeakBoss_SW_Boss_01.prefab",
"BossUI3DAnimatorPath": "UI/UI3D/ChallengePeak/_dependencies/Animator/ChallengePeakBoss_SW_01_AniControl.controller",
"ThemePosterTabPicPath": "SpriteOutput/Quest/TabIcon/BtnChallengePeak_4007.png",
"ThemeIconPicPath": "SpriteOutput/ChallengePeak/ChallengePeakIcon_4007.png",
"HandBookPanelBannerPath": "SpriteOutput/DailyMission/Banner/ChallengePeakPanelBanner_4007.png",
"RankIconPathList": [
"SpriteOutput/ChallengePeak/MedallionIcon/ChallengePeakMedalIcon0007/Bronze.png",
"SpriteOutput/ChallengePeak/MedallionIcon/ChallengePeakMedalIcon0007/Silver.png",
"SpriteOutput/ChallengePeak/MedallionIcon/ChallengePeakMedalIcon0007/Gold.png",
"SpriteOutput/ChallengePeak/MedallionIcon/ChallengePeakMedalIcon0007/Ultra.png"
]
},
{
"ID": 8,
"Title": {
"Hash": 704893445,
"Hash64": 13457040013447238093
},
"RecommendID": 8,
"ActivityModule": 2100801,
"PreLevelIDList": [801, 802, 803],
"BossLevelID": 804,
"RewardGroupID": 1,
"BossUI3DPrefabPath": "UI/UI3D/ChallengePeak/_dependencies/Prefab/ChallengePeakBoss_W5_Painter.prefab",
"BossUI3DAnimatorPath": "UI/UI3D/ChallengePeak/_dependencies/Animator/ChallengePeakBoss_W5_Painter_AniControl.controller",
"ThemePosterTabPicPath": "SpriteOutput/Quest/TabIcon/BtnChallengePeak_4008.png",
"ThemeIconPicPath": "SpriteOutput/ChallengePeak/ChallengePeakIcon_4008.png",
"HandBookPanelBannerPath": "SpriteOutput/DailyMission/Banner/ChallengePeakPanelBanner_4008.png",
"RankIconPathList": [
"SpriteOutput/ChallengePeak/MedallionIcon/ChallengePeakMedalIcon0008/Bronze.png",
"SpriteOutput/ChallengePeak/MedallionIcon/ChallengePeakMedalIcon0008/Silver.png",
"SpriteOutput/ChallengePeak/MedallionIcon/ChallengePeakMedalIcon0008/Gold.png",
"SpriteOutput/ChallengePeak/MedallionIcon/ChallengePeakMedalIcon0008/Ultra.png"
]
},
{
"ID": 9,
"Title": {
"Hash": -861190496,
"Hash64": 556488733392366321
},
"RecommendID": 9,
"ActivityModule": 2100901,
"PreLevelIDList": [901, 902, 903],
"BossLevelID": 904,
"RewardGroupID": 1,
"BossUI3DPrefabPath": "UI/UI3D/ChallengePeak/_dependencies/Prefab/ChallengePeakBoss_W4_IronTombCore.prefab",
"BossUI3DAnimatorPath": "UI/UI3D/ChallengePeak/_dependencies/Animator/ChallengePeakBoss_W4_IronTombCore_AniControl.controller",
"ThemePosterTabPicPath": "SpriteOutput/Quest/TabIcon/BtnChallengePeak_4009.png",
"ThemeIconPicPath": "SpriteOutput/ChallengePeak/ChallengePeakIcon_4009.png",
"HandBookPanelBannerPath": "SpriteOutput/DailyMission/Banner/ChallengePeakPanelBanner_4009.png",
"RankIconPathList": [
"SpriteOutput/ChallengePeak/MedallionIcon/ChallengePeakMedalIcon0009/Bronze.png",
"SpriteOutput/ChallengePeak/MedallionIcon/ChallengePeakMedalIcon0009/Silver.png",
"SpriteOutput/ChallengePeak/MedallionIcon/ChallengePeakMedalIcon0009/Gold.png",
"SpriteOutput/ChallengePeak/MedallionIcon/ChallengePeakMedalIcon0009/Ultra.png"
]
},
{
"ID": 10,
"Title": {
"Hash": 1570201704,
"Hash64": 4908466715646711625
},
"RecommendID": 10,
"ActivityModule": 2101001,
"PreLevelIDList": [1001, 1002, 1003],
"BossLevelID": 1004,
"RewardGroupID": 1,
"BossUI3DPrefabPath": "UI/UI3D/ChallengePeak/_dependencies/Prefab/ChallengePeakBoss_W5_Fallen.prefab",
"BossUI3DAnimatorPath": "UI/UI3D/ChallengePeak/_dependencies/Animator/ChallengePeakBoss_W5_Fallen_AniControl.controller",
"ThemePosterTabPicPath": "SpriteOutput/Quest/TabIcon/BtnChallengePeak_4010.png",
"ThemeIconPicPath": "SpriteOutput/ChallengePeak/ChallengePeakIcon_4010.png",
"HandBookPanelBannerPath": "SpriteOutput/DailyMission/Banner/ChallengePeakPanelBanner_4010.png",
"RankIconPathList": [
"SpriteOutput/ChallengePeak/MedallionIcon/ChallengePeakMedalIcon0010/Bronze.png",
"SpriteOutput/ChallengePeak/MedallionIcon/ChallengePeakMedalIcon0010/Silver.png",
"SpriteOutput/ChallengePeak/MedallionIcon/ChallengePeakMedalIcon0010/Gold.png",
"SpriteOutput/ChallengePeak/MedallionIcon/ChallengePeakMedalIcon0010/Ultra.png"
]
},
{
"ID": 11,
"Title": {
"Hash": 1570201705,
"Hash64": 8621814597265561652
},
"RecommendID": 11,
"ActivityModule": 2101101,
"PreLevelIDList": [1101, 1102, 1103],
"BossLevelID": 1104,
"RewardGroupID": 1,
"BossUI3DPrefabPath": "UI/UI3D/ChallengePeak/_dependencies/Prefab/ChallengePeakBoss_W5_AsatPramad.prefab",
"BossUI3DAnimatorPath": "UI/UI3D/ChallengePeak/_dependencies/Animator/ChallengePeakBoss_W5_AsatPramad_AniControl.controller",
"ThemePosterTabPicPath": "SpriteOutput/Quest/TabIcon/BtnChallengePeak_4011.png",
"ThemeIconPicPath": "SpriteOutput/ChallengePeak/ChallengePeakIcon_4011.png",
"HandBookPanelBannerPath": "SpriteOutput/DailyMission/Banner/ChallengePeakPanelBanner_4011.png",
"RankIconPathList": [
"SpriteOutput/ChallengePeak/MedallionIcon/ChallengePeakMedalIcon0011/Bronze.png",
"SpriteOutput/ChallengePeak/MedallionIcon/ChallengePeakMedalIcon0011/Silver.png",
"SpriteOutput/ChallengePeak/MedallionIcon/ChallengePeakMedalIcon0011/Gold.png",
"SpriteOutput/ChallengePeak/MedallionIcon/ChallengePeakMedalIcon0011/Ultra.png"
]
}
]
}
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+456
View File
@@ -0,0 +1,456 @@
{
"stage_invasion_config": [
{
"StageID": 30509012,
"InvasionID": 2,
"MonsterInvasionList": [
{
"DBLDCKODNEN": 5013010,
"LMEBOHHDIAG": []
},
{
"DBLDCKODNEN": 5014010,
"LMEBOHHDIAG": []
}
]
},
{
"StageID": 420503,
"InvasionID": 2,
"MonsterInvasionList": [
{
"DBLDCKODNEN": 202206017,
"LMEBOHHDIAG": []
},
{
"DBLDCKODNEN": 202303203,
"LMEBOHHDIAG": []
}
]
},
{
"StageID": 420504,
"InvasionID": 2,
"MonsterInvasionList": [
{
"DBLDCKODNEN": 202206018,
"LMEBOHHDIAG": []
},
{
"DBLDCKODNEN": 202303204,
"LMEBOHHDIAG": []
}
]
},
{
"StageID": 30324032,
"InvasionID": 2,
"MonsterInvasionList": [
{
"DBLDCKODNEN": 2032020,
"LMEBOHHDIAG": [1, 0, 0]
},
{
"DBLDCKODNEN": 2002030,
"LMEBOHHDIAG": [1, 0]
},
{
"DBLDCKODNEN": 8012020,
"LMEBOHHDIAG": [1, 0, 0]
}
]
},
{
"StageID": 30324042,
"InvasionID": 2,
"MonsterInvasionList": [
{
"DBLDCKODNEN": 5012100,
"LMEBOHHDIAG": [1, 0, 0]
},
{
"DBLDCKODNEN": 4062020,
"LMEBOHHDIAG": [1, 0]
},
{
"DBLDCKODNEN": 2012010,
"LMEBOHHDIAG": [1, 0, 1, 0, 0]
}
]
},
{
"StageID": 30510011,
"InvasionID": 3,
"MonsterInvasionList": [
{
"DBLDCKODNEN": 5014020,
"LMEBOHHDIAG": []
},
{
"DBLDCKODNEN": 5013040,
"LMEBOHHDIAG": []
}
]
},
{
"StageID": 420533,
"InvasionID": 2,
"MonsterInvasionList": [
{
"DBLDCKODNEN": 800302201,
"LMEBOHHDIAG": []
},
{
"DBLDCKODNEN": 300301401,
"LMEBOHHDIAG": []
}
]
},
{
"StageID": 420534,
"InvasionID": 3,
"MonsterInvasionList": [
{
"DBLDCKODNEN": 800302201,
"LMEBOHHDIAG": []
},
{
"DBLDCKODNEN": 300301401,
"LMEBOHHDIAG": []
}
]
},
{
"StageID": 30325032,
"InvasionID": 2,
"MonsterInvasionList": [
{
"DBLDCKODNEN": 300305007,
"LMEBOHHDIAG": []
},
{
"DBLDCKODNEN": 1002030,
"LMEBOHHDIAG": [1, 0, 1, 0, 0]
},
{
"DBLDCKODNEN": 2002030,
"LMEBOHHDIAG": [1, 0, 1, 0]
}
]
},
{
"StageID": 30325042,
"InvasionID": 3,
"MonsterInvasionList": [
{
"DBLDCKODNEN": 300302007,
"LMEBOHHDIAG": []
},
{
"DBLDCKODNEN": 5012120,
"LMEBOHHDIAG": [1, 0, 1, 0, 0]
},
{
"DBLDCKODNEN": 2032010,
"LMEBOHHDIAG": [1, 0, 1, 0]
}
]
},
{
"StageID": 30125112,
"InvasionID": 2,
"MonsterInvasionList": [
{
"DBLDCKODNEN": 4063010,
"LMEBOHHDIAG": []
}
]
},
{
"StageID": 30125121,
"InvasionID": 3,
"MonsterInvasionList": [
{
"DBLDCKODNEN": 5014010,
"LMEBOHHDIAG": []
},
{
"DBLDCKODNEN": 8003010,
"LMEBOHHDIAG": []
}
]
},
{
"StageID": 30126112,
"InvasionID": 2,
"MonsterInvasionList": [
{
"DBLDCKODNEN": 802401106,
"LMEBOHHDIAG": []
}
]
},
{
"StageID": 30126123,
"InvasionID": 3,
"MonsterInvasionList": [
{
"DBLDCKODNEN": 4064010,
"LMEBOHHDIAG": []
},
{
"DBLDCKODNEN": 4062010,
"LMEBOHHDIAG": [1, 0]
},
{
"DBLDCKODNEN": 4063010,
"LMEBOHHDIAG": []
}
]
},
{
"StageID": 30127112,
"InvasionID": 2,
"MonsterInvasionList": [
{
"DBLDCKODNEN": 1003010,
"LMEBOHHDIAG": []
},
{
"DBLDCKODNEN": 4034010,
"LMEBOHHDIAG": []
}
]
},
{
"StageID": 30127123,
"InvasionID": 3,
"MonsterInvasionList": [
{
"DBLDCKODNEN": 5014020,
"LMEBOHHDIAG": []
},
{
"DBLDCKODNEN": 5012120,
"LMEBOHHDIAG": [1, 0, 0, 1]
}
]
},
{
"StageID": 30511012,
"InvasionID": 4,
"MonsterInvasionList": [
{
"DBLDCKODNEN": 5014030,
"LMEBOHHDIAG": []
},
{
"DBLDCKODNEN": 5023020,
"LMEBOHHDIAG": []
},
{
"DBLDCKODNEN": 502302001,
"LMEBOHHDIAG": [0, 0, 1]
}
]
},
{
"StageID": 30511021,
"InvasionID": 4,
"MonsterInvasionList": [
{
"DBLDCKODNEN": 801503006,
"LMEBOHHDIAG": []
},
{
"DBLDCKODNEN": 801204002,
"LMEBOHHDIAG": [0, 1, 0]
},
{
"DBLDCKODNEN": 801505001,
"LMEBOHHDIAG": []
}
]
},
{
"StageID": 30511022,
"InvasionID": 4,
"MonsterInvasionList": [
{
"DBLDCKODNEN": 801503006,
"LMEBOHHDIAG": []
},
{
"DBLDCKODNEN": 801204002,
"LMEBOHHDIAG": [0, 1, 0]
},
{
"DBLDCKODNEN": 801505001,
"LMEBOHHDIAG": []
}
]
},
{
"StageID": 420563,
"InvasionID": 2,
"MonsterInvasionList": [
{
"DBLDCKODNEN": 5013052,
"LMEBOHHDIAG": []
},
{
"DBLDCKODNEN": 5013042,
"LMEBOHHDIAG": []
},
{
"DBLDCKODNEN": 5013072,
"LMEBOHHDIAG": [0, 1]
}
]
},
{
"StageID": 420564,
"InvasionID": 3,
"MonsterInvasionList": [
{
"DBLDCKODNEN": 5013052,
"LMEBOHHDIAG": [1, 0]
},
{
"DBLDCKODNEN": 5013042,
"LMEBOHHDIAG": []
},
{
"DBLDCKODNEN": 5013072,
"LMEBOHHDIAG": [0, 1]
}
]
},
{
"StageID": 30326032,
"InvasionID": 2,
"MonsterInvasionList": [
{
"DBLDCKODNEN": 8032020,
"LMEBOHHDIAG": [1, 0, 0]
},
{
"DBLDCKODNEN": 2032010,
"LMEBOHHDIAG": [1, 0, 0]
}
]
},
{
"StageID": 30326043,
"InvasionID": 3,
"MonsterInvasionList": [
{
"DBLDCKODNEN": 8012040,
"LMEBOHHDIAG": [1, 0]
},
{
"DBLDCKODNEN": 5012020,
"LMEBOHHDIAG": [1, 0]
}
]
},
{
"StageID": 205710700,
"InvasionID": 2,
"MonsterInvasionList": [
{
"DBLDCKODNEN": 5032040,
"LMEBOHHDIAG": []
},
{
"DBLDCKODNEN": 5033010,
"LMEBOHHDIAG": []
}
]
},
{
"StageID": 205710701,
"InvasionID": 2,
"MonsterInvasionList": [
{
"DBLDCKODNEN": 5032040,
"LMEBOHHDIAG": []
},
{
"DBLDCKODNEN": 5033010,
"LMEBOHHDIAG": []
}
]
},
{
"StageID": 205710702,
"InvasionID": 2,
"MonsterInvasionList": [
{
"DBLDCKODNEN": 5032040,
"LMEBOHHDIAG": []
},
{
"DBLDCKODNEN": 5033010,
"LMEBOHHDIAG": []
}
]
},
{
"StageID": 205710703,
"InvasionID": 2,
"MonsterInvasionList": [
{
"DBLDCKODNEN": 5032040,
"LMEBOHHDIAG": []
},
{
"DBLDCKODNEN": 5033010,
"LMEBOHHDIAG": []
}
]
},
{
"StageID": 205710704,
"InvasionID": 2,
"MonsterInvasionList": [
{
"DBLDCKODNEN": 5032040,
"LMEBOHHDIAG": []
},
{
"DBLDCKODNEN": 5033010,
"LMEBOHHDIAG": []
}
]
},
{
"StageID": 205710705,
"InvasionID": 2,
"MonsterInvasionList": [
{
"DBLDCKODNEN": 5032040,
"LMEBOHHDIAG": []
},
{
"DBLDCKODNEN": 5033010,
"LMEBOHHDIAG": []
}
]
},
{
"StageID": 205710706,
"InvasionID": 2,
"MonsterInvasionList": [
{
"DBLDCKODNEN": 5032040,
"LMEBOHHDIAG": []
},
{
"DBLDCKODNEN": 5033010,
"LMEBOHHDIAG": []
}
]
}
]
}
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+1223028
View File
File diff suppressed because it is too large. Load diff
+164
View File
@@ -0,0 +1,164 @@
@echo off
title GaC hkrpg GO Server (4.6.53)
cd /d "%~dp0"
echo ==============================================================================
echo [ GaC hkrpg GO ] StarRail 4.6.53 Server and Launcher
echo gameserver : UDP 23301 (KCP Gateway)
echo sdkserver : HTTP 21000 (Dispatch / Login / SRTools)
echo ==============================================================================
echo.
echo [1] Start Server and Star Rail Game / Proxy (Default)
echo [2] Start Both Servers (gameserver + sdkserver)
echo [3] Start SDK Server ONLY (HTTP 21000)
echo [4] Start Game Server ONLY (UDP 23301)
echo [5] Start Server and Gay Proxy
echo [6] Build / Rebuild binaries (Go)
echo [7] Exit
echo.
echo ==============================================================================
set "choice=%~1"
if not defined choice set /p choice="Select [1-7] (Press Enter for 1): "
if not defined choice set "choice=1"
set "choice=%choice: =%"
if not defined choice set "choice=1"
if "%choice%"=="1" goto opt_game
if "%choice%"=="2" goto opt_servers_only
if "%choice%"=="3" goto opt_sdk_only
if "%choice%"=="4" goto opt_gs_only
if "%choice%"=="5" goto opt_gay_proxy
if "%choice%"=="6" goto opt_build
if "%choice%"=="7" exit /b 0
if /i "%choice%"=="sdk" goto opt_sdk_only
if /i "%choice%"=="gs" goto opt_gs_only
if /i "%choice%"=="servers" goto opt_servers_only
if /i "%choice%"=="game" goto opt_game
if /i "%choice%"=="proxy" goto opt_gay_proxy
if /i "%choice%"=="build" goto opt_build
echo Unknown option "%choice%", starting servers only...
goto opt_servers_only
:start_core_servers
if not exist "gameserver-go.exe" (
echo [ERROR] gameserver-go.exe not found! Please build first - Option 6
pause
exit /b 1
)
if not exist "sdkserver-go.exe" (
echo [ERROR] sdkserver-go.exe not found! Please build first - Option 6
pause
exit /b 1
)
echo [*] Cleaning old server processes...
taskkill /f /im sdkserver-go.exe >nul 2>&1
taskkill /f /im gameserver-go.exe >nul 2>&1
echo [*] Starting SDK Server [Port 21000]...
start "GaC GO - SDK Server [Port 21000]" /d "%~dp0" cmd /k sdkserver-go.exe
echo [*] Starting Game Server [Port 23301]...
start "GaC GO - Game Server [Port 23301]" /d "%~dp0" cmd /k gameserver-go.exe
exit /b 0
:opt_sdk_only
if not exist "sdkserver-go.exe" (
echo [ERROR] sdkserver-go.exe not found! Please build first - Option 6
pause
exit /b 1
)
echo [*] Cleaning old sdkserver process...
taskkill /f /im sdkserver-go.exe >nul 2>&1
echo [*] Starting SDK Server [Port 21000]...
start "GaC GO - SDK Server [Port 21000]" /d "%~dp0" cmd /k sdkserver-go.exe
goto finish
:opt_gs_only
if not exist "gameserver-go.exe" (
echo [ERROR] gameserver-go.exe not found! Please build first - Option 6
pause
exit /b 1
)
echo [*] Cleaning old gameserver process...
taskkill /f /im gameserver-go.exe >nul 2>&1
echo [*] Starting Game Server [Port 23301]...
start "GaC GO - Game Server [Port 23301]" /d "%~dp0" cmd /k gameserver-go.exe
goto finish
:opt_game
call :start_core_servers
if errorlevel 1 goto :eof
echo.
echo [*] Launching Star Rail Client / Proxy...
set "GAME_DIR="
if exist "%USERPROFILE%\Desktop\StarRail_4.6.53_OS\StarRail.exe" set "GAME_DIR=%USERPROFILE%\Desktop\StarRail_4.6.53_OS"
if not defined GAME_DIR if exist "%~dp0..\StarRail_4.6.53_OS\StarRail.exe" set "GAME_DIR=%~dp0..\StarRail_4.6.53_OS"
if not defined GAME_DIR if exist "%USERPROFILE%\Desktop\StarRail_4.6.51_OS\StarRail.exe" set "GAME_DIR=%USERPROFILE%\Desktop\StarRail_4.6.51_OS"
if not defined GAME_DIR if exist "D:\HoYoPlay\games\Star Rail Games\StarRail.exe" set "GAME_DIR=D:\HoYoPlay\games\Star Rail Games"
if not defined GAME_DIR if exist "%~dp0..\StarRail_4.6.51_OS\StarRail.exe" set "GAME_DIR=%~dp0..\StarRail_4.6.51_OS"
if defined GAME_DIR (
echo [*] Found game client at: %GAME_DIR%
if exist "%GAME_DIR%\GaCy Launcher.exe" (
echo [*] Starting GaCy Launcher...
start "GaCy Launcher" /d "%GAME_DIR%" "%GAME_DIR%\GaCy Launcher.exe"
) else if exist "%GAME_DIR%\launcher.exe" (
echo [*] Starting Launcher...
start "Proxy Launcher" /d "%GAME_DIR%" "%GAME_DIR%\launcher.exe"
) else if exist "%GAME_DIR%\StarRail.exe" (
echo [*] Starting StarRail.exe...
start "Star Rail Client" /d "%GAME_DIR%" "%GAME_DIR%\StarRail.exe"
)
) else (
echo [!] Game client not found automatically. Please open StarRail manually.
)
goto finish
:opt_gay_proxy
call :start_core_servers
if errorlevel 1 goto :eof
echo.
echo [*] Launching Gay Proxy...
set "PROXY_BAT="
if exist "%USERPROFILE%\Desktop\Gay Proxy\run_proxy.bat" set "PROXY_BAT=%USERPROFILE%\Desktop\Gay Proxy\run_proxy.bat"
if not defined PROXY_BAT if exist "%~dp0..\Gay Proxy\run_proxy.bat" set "PROXY_BAT=%~dp0..\Gay Proxy\run_proxy.bat"
if defined PROXY_BAT (
start "Gay Proxy" cmd /k "%PROXY_BAT%"
) else (
echo [!] Gay Proxy not found.
)
goto finish
:opt_servers_only
call :start_core_servers
if errorlevel 1 goto :eof
goto finish
:opt_build
where go >nul 2>nul || set "PATH=%PATH%;C:\Program Files\Go\bin"
echo [*] Building gameserver-go.exe...
go build -o gameserver-go.exe ./cmd/gameserver || (pause & goto :eof)
echo [*] Building sdkserver-go.exe...
go build -o sdkserver-go.exe ./cmd/sdkserver || (pause & goto :eof)
echo [OK] Build successful!
pause
goto :eof
:finish
echo.
echo ==============================================================================
echo [OK] Started successfully!
echo Close server windows to stop them.
echo Closing this launcher window in 5 seconds...
echo ==============================================================================
timeout /t 5 >nul 2>&1 || ping 127.0.0.1 -n 6 >nul
exit
+1 -46
View File
@@ -1,48 +1,3 @@
@echo off
title GaC hkrpg GO Server (4.6.51)
cd /d "%~dp0"
echo ============================================================
echo GaC hkrpg GO - StarRail 4.6.51 Private Server (Go)
echo gameserver : UDP 23301 (KCP)
echo sdkserver : HTTP 21000 (dispatch + login + SRTools)
echo ============================================================
echo.
echo [1] Start server (gameserver + sdkserver)
echo [2] Start gameserver only
echo [3] Start sdkserver only
echo [4] Build / rebuild exes (requires Go)
echo [5] Exit
echo.
set /p choice="Select: "
if "%choice%"=="1" goto all
if "%choice%"=="2" goto gs
if "%choice%"=="3" goto sdk
if "%choice%"=="4" goto build
goto :eof
:all
start "GaC GO sdkserver" cmd /k sdkserver-go.exe
start "GaC GO gameserver" cmd /k gameserver-go.exe
echo Both servers started. Close their windows to stop them.
pause
goto :eof
:gs
gameserver-go.exe
pause
goto :eof
:sdk
sdkserver-go.exe
pause
goto :eof
:build
where go >nul 2>nul || set "PATH=%PATH%;C:\Program Files\Go\bin"
go build -o gameserver-go.exe ./cmd/gameserver || (pause & goto :eof)
go build -o sdkserver-go.exe ./cmd/sdkserver || (pause & goto :eof)
echo Build OK.
pause
goto :eof
call run.bat %*
+131
View File
@@ -0,0 +1,131 @@
-- ==============================================================================
-- Hide UI Lua Script for Honkai: Star Rail (FFGO Method)
-- Press "ฃ" (Backslash '\' on keyboard) to toggle UI visibility
-- Uses /UIRoot/Page CanvasGroup alpha - character rendering stays completely intact!
-- ==============================================================================
local Input = CS.UnityEngine.Input
local KeyCode = CS.UnityEngine.KeyCode
local Time = CS.UnityEngine.Time
local GameObject = CS.UnityEngine.GameObject
local CanvasGroup = CS.UnityEngine.CanvasGroup
local Camera = CS.UnityEngine.Camera
_G.__HIDE_UI_ENABLED = _G.__HIDE_UI_ENABLED or false
_G.__HIDE_UI_LAST_FRAME = _G.__HIDE_UI_LAST_FRAME or 0
-- Ensure all cameras and canvases are restored if previously disabled
pcall(function()
local allCams = Camera.allCameras
if allCams and allCams.Length > 0 then
for i = 0, allCams.Length - 1 do
local c = allCams[i]
if c and (c.name == "UICamera" or string.find(c.name, "UI") or string.find(c.name, "ui")) then
c.enabled = true
end
end
end
local canvases = GameObject.FindObjectsOfType(typeof(CS.UnityEngine.Canvas))
if canvases and canvases.Length > 0 then
for i = 0, canvases.Length - 1 do
if canvases[i] then canvases[i].enabled = true end
end
end
end)
local function ensure_canvas_and_set_alpha(obj, visible)
if not obj then return end
pcall(function()
local cg = obj:GetComponent(typeof(CanvasGroup))
if not cg then
cg = obj:AddComponent(typeof(CanvasGroup))
end
if cg then
cg.alpha = visible and 1.0 or 0.0
cg.interactable = true
cg.blocksRaycasts = true
end
end)
end
local function set_canvasgroup_visible(obj, visible)
if not obj then return end
pcall(function()
local cg = obj:GetComponent(typeof(CanvasGroup))
if cg then
cg.alpha = visible and 1.0 or 0.0
cg.interactable = true
cg.blocksRaycasts = true
end
for i = 0, obj.transform.childCount - 1 do
local child = obj.transform:GetChild(i).gameObject
local childCg = child:GetComponent(typeof(CanvasGroup))
if childCg then
childCg.alpha = visible and 1.0 or 0.0
childCg.interactable = true
childCg.blocksRaycasts = true
end
end
end)
end
local function set_ui_visible(visible)
pcall(function()
local gameUI = GameObject.Find("/UIRoot/Page") or GameObject.Find("UIRoot/Page")
if gameUI then
ensure_canvas_and_set_alpha(gameUI, visible)
local battleGameUI = GameObject.Find("/UIRoot/Page/BattleGamePhaseUI(Clone)") or GameObject.Find("UIRoot/Page/BattleGamePhaseUI(Clone)")
if battleGameUI then
ensure_canvas_and_set_alpha(battleGameUI, visible)
set_canvasgroup_visible(battleGameUI, visible)
end
end
end)
end
function _G.__HIDE_UI_TICK()
pcall(function()
local curFrame = Time.frameCount
if _G.__HIDE_UI_LAST_FRAME == curFrame then
return
end
-- Check physical key 'ฃ' on keyboard (KeyCode.Backslash)
if Input.GetKeyDown(KeyCode.Backslash) then
_G.__HIDE_UI_LAST_FRAME = curFrame
_G.__HIDE_UI_ENABLED = not _G.__HIDE_UI_ENABLED
set_ui_visible(not _G.__HIDE_UI_ENABLED)
if _G.__HIDE_UI_ENABLED then
CS.UnityEngine.Debug.Log("<color=#FF00FF>[UI Toggle]</color> <color=#FF5555>UI Hidden (ซ่อน UI แล้ว)</color>")
else
CS.UnityEngine.Debug.Log("<color=#FF00FF>[UI Toggle]</color> <color=#55FF55>UI Restored (แสดง UI ตามปกติ)</color>")
end
end
-- When hidden, keep enforcing /UIRoot/Page visibility
if _G.__HIDE_UI_ENABLED then
set_ui_visible(false)
end
end)
end
-- Hook into render pipeline
if not _G.__HIDE_UI_REGISTERED then
_G.__HIDE_UI_REGISTERED = true
local function hook(target, evt)
pcall(function() target[evt]('+', function(c) _G.__HIDE_UI_TICK() end) end)
pcall(function() target[evt] = target[evt] + function(c) _G.__HIDE_UI_TICK() end end)
end
hook(Camera, "onPreRender")
hook(Camera, "onPreCull")
hook(Camera, "onPostRender")
pcall(function() CS.UnityEngine.Application.onBeforeRender('+', function() _G.__HIDE_UI_TICK() end) end)
pcall(function() CS.UnityEngine.Application.onBeforeRender = CS.UnityEngine.Application.onBeforeRender + function() _G.__HIDE_UI_TICK() end end)
CS.UnityEngine.Debug.Log("<color=#FF00FF>[UI Toggle Script]</color> Loaded successfully (FFGO method)! Press 'ฃ' to toggle UI.")
end
+88
View File
@@ -0,0 +1,88 @@
# บันทึกความจำ 2026-10-10: แก้ไข Endgame Modes ทั้ง 7 ข้อ (4.6.51)
## ปัญหาที่พบและวิธีแก้ไขครบทั้ง 7 ข้อ
### ข้อ 1: กด Apocalyptic Shadow ในแท็บ Latest Update แล้วขึ้น Error(100)
- **สาเหตุ:** Client ส่ง cmd `1775` (`GetChallengeGroupStatisticsCsReq`) ซึ่งโค้ดเดิมใช้ byte slicing แบบ 4.5.52 เก่าที่มี tag ผิดเพี้ยน (`group_id=9`, `retcode=15`, `challenge_boss=11`) ทำให้ client 4.6.51 parse ไม่ได้แล้วโยน Error(100)
- **การแก้ไข:** ใน `internal/gameserver/handlers_raw.go` เขียน `challengeGroupStatsReply` ใหม่โดยใช้ `goproto.GetChallengeGroupStatisticsScRsp` ตาม proto 4.6.51 โดยตรง:
- `group_id = 5`
- `retcode = 9` (0 = สำเร็จ)
- AS: `challenge_boss = 1`
- PF: `challenge_story = 3`
- MoC: `challenge_default = 11`
### ข้อ 2: AS Starward (cmd 8986 `StartChallengeTierceCsReq`, challenge 30225) เข้าไม่ได้
- **สาเหตุ:**
1. `mapChallengeStageID` ฝืน map id 30200..30300 ทุกตัวไปเป็น 30204 (ซึ่ง 30204 ไม่มีใน `tierce`)
2. `decodeStartChallengeTierceReq` ตรวจหาเฉพาะ tag 12/14 (4.5.52) แต่ client 4.6.51 ส่ง `LineupTierceList` ใน tag 9
3. `handleStartChallengeTierce` ตรวจสอบ `cd.Tierce` เฉพาะเมื่อ `stageIndex == 2` ทำให้ stageIndex 0 หลุดไปหา 3014101 -> 3000101
4. Response ตอบด้วย cmd 8973 (4.5.52) แทนที่จะเป็น 8998 (4.6.51)
- **การแก้ไข:**
1. `mapChallengeStageID` คืนค่า id ทันทีถ้า id มีอยู่ใน `cd.Challenges` หรือ `cd.Tierce` (30225 คืน 30225)
2. `decodeStartChallengeTierceReq` parse ผ่าน `proto.Unmarshal(&goproto.StartChallengeTierceCsReq)`
3. `handleStartChallengeTierce` ดึงข้อมูลจาก `cd.Tierce[baseID]` ทุก stageIndex ได้ entrance `3012602`, group `7`, monster `2034012`, event `420554`
4. ตอบด้วย `goproto.StartChallengeTierceScRsp` ผ่าน CmdID **8998** (และ legacy 8973)
### ข้อ 3: AS Normal (cmd 1731 `StartChallengeCsReq`, challenge 30224) เข้าไม่ได้
- **สาเหตุ:**
1. `mapChallengeStageID` ฝืน map 30224 -> 30204
2. `decodeStartChallengeReq` มี wire slicing bug (`buf = buf[len(buf)-len(sub):]; buf = buf[len(sub):]`) ทำให้ buf desync และพลาด challenge_id
- **การแก้ไข:**
1. `mapChallengeStageID` เช็ค `cd.Challenges[30224]` แล้วคืน 30224 ตรงๆ
2. `decodeStartChallengeReq` ใช้ `proto.Unmarshal(&goproto.StartChallengeCsReq)` + helper `readLenDelimAndAdvance`
3. `handleStartChallenge` ตอบ `StartChallengeScRsp` (1787) ครบทั้ง 2 Node
### ข้อ 4: ออกจาก Endgame แล้วตัวละครหาย
- **สาเหตุ:**
1. `loadChallengeScene` ไปเขียนทับ `player.Scene.EntryID = entryID` ทำให้ open world กลายเป็นฉาก arena (3000501/3000101) และถูกบันทึก persistent
2. `handleLeaveChallenge` และ `handleLeaveChallengeTierce` ส่งเฉพาะ ScRsp เก่า (1716, 8997) โดยไม่ส่ง `loadScene` เพื่อพาผู้เล่นกลับ open world
- **การแก้ไข:**
1. นำบรรทัดเขียนทับ `player.Scene` ออกจาก `handlers_challenge_scene.go`
2. `handleLeaveChallenge` ตอบ CmdID **1771** (`LeaveChallengeScRsp`, tag 10 retcode 0 `[0x50, 0x00]`) แล้วเรียก `loadScene(s, returnEntry, true, nil, nil)` สั่ง `EnterSceneByServerScNotify` (1427) ดึงตัวละครกลับฉากเดิม
3. `handleLeaveChallengeTierce` ตอบ CmdID **8983** (`LeaveChallengeTierceScRsp`, tag 13 retcode 0 `[0x68, 0x00]`) แล้วเรียก `loadScene` เช่นกัน
### ข้อ 5: PF Starward หมุนค้างตอนจัดทีมตัวละคร
- **สาเหตุ:** Client ส่ง cmd `8978` / `9000` (`SetChallengeTierceLineupCsReq`) ซึ่ง callback ใน GameAssembly.dll (`OMOEFAHFJFL::EMMGHDLIPOH`) รอ CmdID **8985** (`SetChallengeTierceLineupScRsp` / `CmdChallengeTierceType_PMMCIBMKOLD`) แต่เซิร์ฟเวอร์ส่ง 8999 ทำให้ client ไม่เคยได้รับ callback และหมุนค้าง
- **การแก้ไข:** ใน `handleSetChallengeTierceLineup` ส่ง CmdID **8985** ด้วยบอดี้ `[0x08, 0x00]` (tag 1 retcode 0) และแมพทั้ง 8978 และ 9000 ใน `rawHandlers`
### ข้อ 6: PF Normal (challenge 20284) เข้าไม่ได้
- **สาเหตุ:**
1. `mapChallengeStageID` map 20284 -> 20254
2. `handleStartChallenge` ส่ง `stage_info` (tag 13) เป็นก้อนว่างสำหรับโหมด PF ทำให้ client เกิดปัญหา
- **การแก้ไข:**
1. ไม่ map 20284 เพราะมีใน `cd.Challenges[20284]`
2. `stage_info` (tag 13) จะแนบเฉพาะเมื่อ `challengeID >= 30000 && buff != 0` (AS Boss เท่านั้น) ส่วน MoC และ PF จะเป็น nil (ไม่ปล่อย tag 13)
### ข้อ 7: MoC Normal (challenge 5612) ค้างหน้าโหลดเหลือ 1 ขีด
- **สาเหตุ:**
1. ทันทีที่เข้าฉาก arena client ส่ง cmd `8115` (`GetSwitchHandDataCsReq`) แต่เซิร์ฟเวอร์ตอบด้วย cmd `8104` (ซึ่งเป็น CsReq: `SwitchHandStartCsReq`) ทำให้ client สับสนและค้าง loading notch สุดท้าย
2. `mapChallengeStageID` มีการเพี้ยน id 5612
- **การแก้ไข:**
1. แก้ cmd `8115` ให้ตอบ CmdID **8108** (`GetSwitchHandDataScRsp`, body `[0x18, 0x00]`)
2. `mapChallengeStageID` คืน 5612 ตรงๆ จาก `cd.Challenges[5612]` (entrance 3013801)
### ข้อ 8: ค้างหน้าจอโหลด 100% (รูปรถไฟ Astral Express หมุนไม่ไปต่อ)
- **สาเหตุจาก Player.log:**
1. **DrinkMaker NRE:** Client ส่ง cmd `6995` (`GetDrinkMakerDataCsReq`) แต่ `dispatch.go` ตอบกลับด้วย `6988` payload เปล่า (`len: 0`) เพราะไม่อยู่ใน `dangerFamilies` ส่งผลให้ `DrinkMakerBar._UpdateGameplayData()` ใน Unity client เจอก้อน nil แล้ว crash `NullReferenceException`
2. **MapNPC Astral Express NRE:** บันทึก `persistent` ถูกตั้งไว้ที่ `entry_id: 100000104` (Astral Express Parlor Car `P10000_F10000000`) ซึ่งใน client 4.6.51 นี้ ไฟล์ config ของ Chapter00 มีปัญหา (`TerrainStageLodConfig ... 没找到`) และ `MapNPC:227-400001` ไม่มี ConfigEntityPath ทำให้ `AsyncLoad MapObject` โยน `NullReferenceException` ระหว่างขั้นตอน `AdventurePhase._EnterStep2()` ค้างโหลด 100% แล้วแอปเด้งปิด
- **การแก้ไข:**
1. เพิ่ม `"DrinkMaker"` ลงใน `dangerFamilies` และเพิ่ม CmdID `6995, 6988` ใน `isDangerCmd` (`dispatch.go`) ให้ drop เงียบๆ ป้องกัน empty ScRsp
2. เปลี่ยน fallback ของ `GetCurSceneInfo` ใน `handlers_scene.go` และ `handleLeaveChallenge` / `handleLeaveChallengeTierce` ใน `handlers_challenge_flow.go` จาก `100000104` เป็น `102020107` (Belobog Administrative District `P10202_F10202001`) ซึ่งเป็นแมพสมบูรณ์ 100%
3. รีเซ็ตไฟล์ `persistent` กลับเป็นแมพ `102020107` พิกัด `x: -92, y: 4278, z: 68083, rot_y: 155566`
### ข้อ 9: Run.bat เปิดไม่ติด (หน้าต่างกระพริบปิดทันที)
- **สาเหตุ:**
1. ในคำสั่ง `if (...)` ของไฟล์ batch มีข้อความ `echo ... (Option 4).` ซึ่งตัวอักษร `)` ไปปิดบล็อก `if` ก่อนกำหนด ทำให้เครื่องหมายจุด `.` กลายเป็นคำสั่งที่ผิดไวยากรณ์ DOS/Windows (`. was unexpected at this time.`) ส่งผลให้ cmd แครชปิดหน้าต่างทันทีตั้งแต่บรรทัดแรกๆ
2. การใช้ข้อความภาษาไทยในไฟล์ .bat ที่บันทึกแบบ UTF-8 ทำให้ cmd.exe ใน codepage เริ่มต้นของ Windows ตีความไบต์ผิดพลาด
- **การแก้ไข:**
1. แก้ไข `run.bat` และ `run_go.bat` ให้เป็น ASCII ที่สะอาด ไม่มีวงเล็บซ้อนในบล็อก `if`
2. เพิ่มตัวเลือกแบบยืดหยุ่น:
- [1] เปิด Server + ตัวเกม / Proxy (Default)
- [2] เปิด Server ทั้งสองตัว (SDK + Game Server)
- [3] เปิดเฉพาะ **SDK Server ONLY** (HTTP 21000)
- [4] เปิดเฉพาะ **Game Server ONLY** (UDP 23301)
- [5] เปิด Server + Gay Proxy
- [6] Build / Rebuild binaries (Go)
- [7] ออก
3. ถ้าเปิดสำเร็จโดยไม่มี error จะนับถอยหลัง 5 วินาทีแล้วปิดหน้าต่าง Launcher ไปเองตามที่ผู้ใช้ต้องการ (และหากมี error จะ pause ค้างไว้ให้อ่าน)
+38
View File
@@ -0,0 +1,38 @@
# บันทึกความจำ 2026-10-11: แก้ไขปัญหา Endgame 5 ข้อ (AS Starward ตกแมพ / Node 2 ค้าง / AS Normal / ตัวละครหาย / รีล็อกค้าง)
## ปัญหาที่พบและวิธีแก้ไขครบทั้ง 5 ข้อ
### 1. AS Starward (challenge 30225) เข้าแล้วตัวละครอยู่ใต้แมพ (ตก Void)
- **สาเหตุ:**
1. ใน `res.json` ของฉาก `3012602` (`P30126_F30126001`) มอนสเตอร์บอสใน Group 7 มีพิกัด `Y = -193081` (-193.08 เมตร) แต่วัตถุพื้นเวทีอารีน่า (Prop `102259` ใน Group 2) อยู่ที่ `Y = -170770` (-170.77 เมตร) ซึ่งพื้นเวทีอยู่สูงกว่ามอนสเตอร์ถึง 22.3 เมตร
2. โค้ดเดิมใน `loadChallengeScene` กำหนด `spawnY = monsPos.Y` (-193081) ส่งผลให้ตัวละครเกิดใต้พื้นเวทีกระเด็นตก Void
3. นอกจากนี้มีเงื่อนไข `if gid != 1 && gid != targetGroupID && gid != 7 && gid != 8 { continue }` ทำให้ Group 2 (แผ่นพื้นเวที) ไม่ถูกโหลดส่งให้ Client เลย
- **การแก้ไข:**
1. ใน `loadChallengeScene` (`internal/gameserver/handlers_challenge_scene.go`) เพิ่มระบบตรวจหาความสูงพื้นเวทีจาก Props ในฉาก (`hasFloorProp && floorY > monsPos.Y`) เพื่อปรับความสูงของทั้งผู้เล่นและมอนสเตอร์ให้ยืนบนพื้นเวทีจริง (`Y = -170770`)
2. ยกเลิกการกรอง Group เพื่อให้ Props ของทุก Group (รวมถึงแผ่นพื้นเวที Group 2 และ Prop อารีน่า Group 3) ถูกส่งให้ Client ครบถ้วน
### 1.1 AS Starward เข้าห้องที่ 2 (Node 2) แล้วค้างหน้าโหลด
- **สาเหตุ:**
1. เมื่อผู้เล่นผ่านห้อง 1 และเข้าห้อง 2 (`req.stageIndex == 1`) โค้ดเดิมดึงข้อมูลจาก `cd.Tierce[baseID]` ซ้ำ ซึ่งในไฟล์ `challenge_data.json` ก้อน `tierce[30225]` มีเฉพาะข้อมูลห้อง 1 ทำให้เซิร์ฟเวอร์พยายามโหลดห้อง 1 ซ้ำที่เพิ่งจบไป
2. ใน `handleStartChallengeTierce` มีการส่ง CmdID `8973` ซึ่งเป็น Packet โบราณที่ไม่มีใน Protobuf 4.6.51 ตามหลัง CmdID 8998 ทันที ทำให้ Network stream desync และ Coroutine โหลดฉากของ Client ค้าง
- **การแก้ไข:**
1. ใน `handleStartChallengeTierce` (`internal/gameserver/handlers_challenge_flow.go`) เมื่อ `req.stageIndex == 1` ให้ดึงข้อมูลด่านที่สองจาก `cd.Challenges[baseID-1]` (เช่น 30224 ห้อง 2: `entrance2: 3013201`, `group2: 9`, `monster2: 4014036`, `event2: 420574`) พร้อมเลือกชุดตัวละคร `req.secondAvatars`
2. ตัดการส่ง CmdID `8973` ทิ้งทั้งหมด เหลือเฉพาะ `StartChallengeTierceScRsp` (CmdID **8998**) ตามมาตรฐาน 4.6.51
### 2. AS แบบปกติ (challenge 30214) ยังเข้าไม่ได้
- **สาเหตุ:**
1. ใน `handleStartChallenge` มีการแนบฟิลด์ `stage_info` (Tag 13) ไปใน `StartChallengeScRsp` (CmdID 1787) ซึ่งมีเฉพาะ `BuffId` แต่ขาดข้อมูล Lineup / Relics ที่ `BossChallengeModule` ของ Client คาดหวัง ส่งผลให้ Client ปฏิเสธการเข้าฉาก
2. ในฉาก `3000205` มี Prop เพียงตัวเดียวอยู่ใน Group 3 ซึ่งถูกตัวกรองเดิมข้ามไป ทำให้ฉากไม่มี Prop ส่งไปเลย
- **การแก้ไข:**
1. ตัด `stage_info` ออกจาก `StartChallengeScRsp` ให้ส่งเฉพาะ `retcode`, `cur_challenge`, `lineup_list`, และ `scene` เหมือน MoC/PF ที่ทำงานได้ปกติ
2. ปลดล็อกการโหลด Prop ทุก Group ทำให้ Prop 300002 ใน Group 3 ถูกโหลดสมบูรณ์
### 3, 4, 5. ออกจาก Endgame ตัวละครหาย, ไม่มีจุดวาปขึ้น, รีล็อกแล้วค้างหน้าโหลด
- **สาเหตุ:**
1. ในไฟล์ `persistent` มีการบันทึกฉากไว้ที่ `entry_id: 1030402` (Xianzhou Luofu Chapter 04) พิกัด `x: 780, y: -153008, z: -621450` ซึ่งเป็นพิกัดตก Void ลึก -153 เมตร
2. เมื่อกดออกจาก Endgame ฟังก์ชัน `handleLeaveChallenge` และ `handleLeaveChallengeTierce` ตรวจพบว่าฉากไม่ใช่ 3000000..4000000 จึงพาผู้เล่นกลับไปที่ `1030402` พิกัด Void เดิม ทำให้ตัวละครตกแมพและมองไม่เห็น (ตัวละครหาย)
3. Client 4.6.51 มีปัญหาไฟล์ Terrain LOD ของ Chapter 04 ขาดหาย (`TerrainStageLodConfig Stages/Outputs/Chapter04/... 没找到`) ทำให้ Coroutine ของฉากค้าง ส่งผลให้ UI แผนที่ไม่แสดงจุดวาป และเมื่อปิดเปิดเกมใหม่ `GetCurSceneInfo` ก็พยายามโหลด `1030402` ซ้ำจนค้างหน้าโหลด 100%
- **การแก้ไข:**
1. ใน `handleLeaveChallenge` และ `handleLeaveChallengeTierce` บังคับให้การออกจาก Endgame ส่งผู้เล่นกลับสู่เขตบริหาร Belobog Administrative District (`entry_id: 102020107`, `plane_id: 10202`, `floor_id: 10202001`, พิกัด `x: -92, y: 4278, z: 68083, rot_y: 155566`) เสมอ พร้อมเรียก `refreshLineup` เพื่อแสดงโมเดลตัวละครและรีเฟรชจุดวาปทั้งหมด
2. ใน `GetCurSceneInfo` (`handlers_scene.go`) เพิ่มเงื่อนไขตรวจสอบ `entryID == 1030402 || player.Position.Y < -50000` หากพบฉากเสียหรือพิกัด Void ให้กู้คืนกลับมา Belobog `102020107` ทันทีและเซฟ persistent อัตโนมัติ ป้องกันการค้างตอนล็อกอิน
3. รีเซ็ตไฟล์ `persistent` กลับมายัง Belobog `102020107`
+33
View File
@@ -0,0 +1,33 @@
# บันทึกความจำ 2026-10-16: แก้ไขปัญหา Calyx (ดอกไม้) เข้าต่อสู้ไม่ได้ / Network Flood Loop / REST API Gateway สำหรับ GaC-Srtool
## 1. REST API Gateway (port 21000)
- นำหน้าเว็บ HTML Dashboard ภายในออกตามความต้องการของผู้ใช้ เพื่อเตรียมรองรับ GaC-Srtool ภายนอก
- สร้าง REST API Gateway (`internal/sdkserver/api.go`) พร้อม CORS Header:
- `GET /` และ `GET /dashboard`: ดูรายการ API ทั้งหมด
- `GET, POST /api/freesr`: อ่าน/เขียน `freesr-data.json` ทั้งหมด
- `GET, POST /api/freesr/avatars`: จัดการเลเวล, ปลดดาว, รีลิกส์, Ex-Rank
- `GET, POST /api/persistent`: อ่าน/เขียนไฟล์ persistent
- `POST /api/exrank/toggle`: เปิด/ปิด Ex-Rank รายตัวหรือทุกตัว
- `POST /api/avatar/rank`: ปรับ Eidolon (0-6)
- `POST /api/avatar/max_all`: แม็กซ์ตัวละครทุกตัว
- `POST /api/teleport`: วาปตำแหน่ง/ฉาก
- `GET /api/status`: สรุปสถานะเซิร์ฟเวอร์, จำนวนตัวละคร, ทีมปัจจุบัน
## 2. แก้ปัญหา Calyx (ดอกไม้) เข้าไม่ได้ และ Network Flood Loop
- **สาเหตุที่ 1 (Network Flood Loop & Battle Reset):**
- ใน `handlers_scene.go` ที่ฟังก์ชัน `SceneEntityMove` มีการเรียก `player.SavePersistent()` ทุกเฟรมที่มีการเคลื่อนที่ของผู้เล่น
- ใน `gateway.go` ฟังก์ชัน `watchFreesrData` เฝ้าดูการเปลี่ยนแปลงของไฟล์ `persistent` ทุก 1 วินาที
- เมื่อผู้เล่นขยับ `persistent` ถูกเขียนซ้ำตลอดเวลา ทำให้ `watchFreesrData` สั่ง `session.SyncPlayer()` ทุกวินาที
- `SyncPlayer()` ยิงแพ็กเก็ต `PlayerSyncScNotify` ขนาดกว่า 60KB (ประกอบด้วย `DelRelicList`, `DelEquipmentList`, `AvatarSync`) ล้างไอเทมและรีเซ็ตตัวละครกลับมาเป็นสถานะ Overworld ขัดจังหวะการโหลดเข้าสู่การต่อสู้ Calyx
- **สาเหตุที่ 2 (NullReferenceException ใน `GetCurBattleInfo`):**
- ใน `handlers_battle.go` ฟังก์ชัน `GetCurBattleInfo` มีโค้ด fallback ปลอมที่สร้าง battleInfo ส่งกลับด้วย retcode 0 แม้ไม่ได้อยู่ในการต่อสู้
- ทำให้ Client เมื่อเรียก `GetCurBattleInfoCsReq` เกิด crash NRE ใน `AdventureModule._OnGetCurBattleInfoScRsp` (RVA `0xceb7e65`) เนื่องจาก Battle Module ภายในยังไม่ถูก initialize
- **สาเหตุที่ 3 (การตัดและบิดเบือน MonsterWaveList ใน Calyx):**
- ใน `StartCocoonStage` และ `StartQuickCocoonStage` มีการบังคับตัดเวฟ `MonsterWaveList[:1]` และเซ็ต `w.WaveId = 1` ให้กับทุกเวฟ ทำให้โครงสร้างเวฟของ Client เสียหาย
- **การแก้ไข:**
1. แก้ `gateway.go`: ให้ `watchFreesrData` เฝ้าดูเฉพาะ `freesr-data.json` เท่านั้น (ไม่ดู `persistent`) เหมือนโครงสร้างต้นฉบับ
2. แก้ `handlers_scene.go`: นำ `player.SavePersistent()` ออกจาก `SceneEntityMove` เพื่อหยุดการเขียนดิสก์และลูป Network flood โดยสิ้นเชิง
3. แก้ `handlers_battle.go`:
- ใน `GetCurBattleInfo` ตัด fallback ปลอมออก หากไม่มี Token ใน `CURRENT_BATTLE` ให้ตอบ `RET_BATTLE_STAGE_NOT_MATCH` (2000) ป้องกัน NRE
- ใน `StartCocoonStage` และ `StartQuickCocoonStage` ส่ง `battleInfo` ที่สมบูรณ์พร้อมเวฟที่ถูกต้อง และเพิ่ม Log แสดงค่า `CocoonId`, `Wave`, `WorldLevel`
4. คอมไพล์ `gameserver-go.exe` ใหม่และรีสตาร์ทกระบวนการ
+20 -1
View File
@@ -64,7 +64,7 @@ description: คู่มือ Honkai Star Rail Private Server (GaC-hkrpg-go
- **GetNpcTakenReward (2174)**: client ถามก่อนคุย NPC — ไม่ตอบ = dialogue ไม่มีตัวเลือกและค้าง
- **Tutorial**: mark ทุก id ว่าดูแล้ว (`GetTutorial`/`GetTutorialGuide` status enum `Oekdmamgidc`=2) ไม่งั้น popup เด้งวนและ**บล็อก input**; `FinishTutorial/UnlockTutorial*` ต้องตอบพร้อม Tutorial/TutorialGuide message
- **GetMissionStatusScRsp ต้องส่ง `finished_main_mission_id_list` + `curversion_finished_main_mission_id_list` (tag 13)** — ตอบเปล่า = client Lua NRE → บล็อก input + client ส่ง cmd 1204 ซ้ำรัวๆ
- **GetSwitchHandData**: 4.6.51 CsReq 8115 → ตอบ ScRsp **8104** (tag 3 retcode 0)
- **GetSwitchHandData**: 4.6.51 CsReq 8115 → ตอบ ScRsp **8108** (tag 3 retcode 0; 8104 คือ SwitchHandStartCsReq ห้ามตอบ)
- **arena ใหม่ไม่มีใน client เก่า**: challenge phase ใหม่ (เช่น 5312) ใช้ arena 3014101 ซึ่ง client build เก่าไม่มี MapEntrance → Lua `GotoMapParam: attempt to index nil with 'ID'` — fallback ไป arena มาตรฐาน 3000101
- **Scene ไม่มี teleport**: ฉาก arena ไม่มี teleport ใน res — fallback จาก `teleports.json` หรือ spawn ใกล้ prop แรกหันหน้าเข้าหา
- **full coverage ตอบ ScRsp ว่างทุก cmd = อันตราย!** — client modules (`GridFight`, `ChessRogue`, `EvolveBuild`, `ChimeraDuel`, `Jukebox`) รับว่างแล้ว `Sync(nil)` → NRE วน → **จอดำ** — cmd ไม่รู้จักต้อง **drop เงียบๆ** ตอบว่างได้เฉพาะ dummy list ที่ทดสอบแล้ว
@@ -149,3 +149,22 @@ commit สุดท้ายของ Rust (`ebc27a1`) อ้างชื่อ
Endgame entry รอ response นี้ก่อนเปิด UI (บันทึก Rust บอก 4130 ผิด)
- **Activity schedule จริง**: `tools/march7_dump/Config/Custom/ActivityConfig.json`
(147 activities จาก client 4.6.51) — อย่าส่ง synthetic id เพราะ Travel Log จะว่าง
## 14. บทเรียนระบบ Endgame ใน 4.6.51 (Apocalyptic Shadow, Pure Fiction, MoC)
1. **GetChallengeGroupStatistics (1775/1711 → 1708)**:
- Client ส่ง `GetChallengeGroupStatisticsCsReq`
- ScRsp 1708 ต้องใช้ proto 4.6.51 (`group_id=5`, `retcode=9`, `challenge_boss=1`, `challenge_story=3`, `challenge_default=11`) ห้ามใช้ layout 4.5.52
2. **StartChallengeTierce (Starward/PF/AS) = CmdID 8998 (4.6.51)**:
- Client ส่ง `StartChallengeTierceCsReq` (8986): `challenge_id` (tag 3), `stageIndex` (tag 5), `lineup_tierce_list` (tag 9)
- Response ใน 4.6.51 คือ **8998** (`StartChallengeTierceScRsp`) ประกอบด้วย `retcode` (tag 3), `scene` (tag 9), `cur_tierce_challenge` (tag 15)
- ข้อมูลแมพดึงจาก `cd.Tierce[baseID]` สำหรับทุก stageIndex
3. **SetChallengeTierceLineup (8978/9000 → 8985)**:
- Callback `OMOEFAHFJFL::EMMGHDLIPOH` ใน client รอ CmdID **8985** (`SetChallengeTierceLineupScRsp`) บอดี้ `[0x08, 0x00]`
- เซิร์ฟเวอร์เดิมส่ง 8999 ทำให้ client หมุนค้างตลอดเวลาเลือกตัวละคร
4. **Leave Challenge (1705 → 1771, 8989 → 8983) และการรักษาสภาพตัวละคร**:
- `loadChallengeScene` **ห้ามเขียนทับ** `player.Scene.EntryID/FloorID/PlaneID` — มิฉะนั้น persistent save จะพังเป็นฉาก arena
- เมื่อผู้เล่นกดยกเลิก/ออกจาก Endgame ต้องตอบ ScRsp (1771 หรือ 8983) พร้อมเรียก `loadScene(s, returnEntry, true, nil, nil)` สั่ง `EnterSceneByServerScNotify` (1427) ดึงตัวละครกลับ open world ทันที ตัวละครจะไม่หาย
5. **StartChallenge Lineup List & Stage Info (1787)**:
- ต้องแนบทั้ง Node 1 (index 0) และ Node 2 (index 1) ใน `LineupList`
- `StageInfo` (tag 13) แนบเฉพาะเมื่อ `challengeID >= 30000 && buff != 0` (AS Boss) ห้ามแนบว่างใน MoC และ PF
+24
View File
@@ -190,5 +190,29 @@
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_16700702_5084ff364848_ebdcee37054070",
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_16679499_5abbd35a74c5_a28e83e88b78d5",
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_0_40d2ce0253_c61ba99f70b885"
},
"CNBETAWin4.6.52": {
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_16745161_ab68020d7aa9_43440a8f18e051",
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_16755051_300cb1cd6a4f_c87db7a5d4f2bf",
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_16745283_0b6f44e69f46_3bf68a1c7368cd",
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_0_40d2ce0253_c61ba99f70b885"
},
"OSBETAWin4.6.52": {
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_16745161_ab68020d7aa9_43440a8f18e051",
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_16755051_300cb1cd6a4f_c87db7a5d4f2bf",
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_16745283_0b6f44e69f46_3bf68a1c7368cd",
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_0_40d2ce0253_c61ba99f70b885"
},
"CNBETAWin4.6.53": {
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_16818953_d4345bbd54a2_93c4aa5dfb978d",
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_16833202_959c316ef547_ac20bef3513343",
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_16819203_679727d45bd7_91ccc9b73f52b7",
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_0_40d2ce0253_c61ba99f70b885"
},
"OSBETAWin4.6.53": {
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_16818953_d4345bbd54a2_93c4aa5dfb978d",
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_16833202_959c316ef547_ac20bef3513343",
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_16819203_679727d45bd7_91ccc9b73f52b7",
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_0_40d2ce0253_c61ba99f70b885"
}
}