diff --git a/.gitignore b/.gitignore index 64f96d0..c5ffe11 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,4 @@ battle_report.json /for ship/ /scripts/client_unstuck_request.lua /scripts/.client-unstuck-*.tmp +.vscode/ diff --git a/README.md b/README.md index 2cbf0d1..a7d347f 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,8 @@ ## CW — กำลังพัฒนา +เริ่มพอร์ต CW จาก [Currency-War-SR ของ xaodev](https://gaytea.cod-algieba.ts.net/xaodev/Currency-War-SR) แล้ว: เพิ่ม factory ของ Encounter หลัง Supply, ตารางตัวเลือก/ความยากที่ revision ตรงกัน และ EXP ตามโหมด/บท/Boss โดยยังรักษาเส้นทาง Battle ที่จับไว้เดิม งานนี้ยังเป็นการพอร์ตบางส่วนและยังไม่ได้ยืนยันเส้นทางใหม่ใน Client อ่านสถานะ ผลทดสอบ และงานส่งต่อใน [CW xaodev handoff](docs/CW-XAODEV-CLAUDE-HANDOFF.md) + คำสั่งเวลาใน GaC-Srtool: `/faketime status` ดูเวลาเกม, `/faketime ` หรือ `/faketime <วันที่ RFC3339 พร้อม timezone>` ตั้งเวลาจำลอง และ `/faketime off` คืนเวลาจริง เวลาจำลองเดินต่อและบันทึกใน `game_clock.state_file` จาก config (override ด้วย `GAC_GAME_CLOCK_STATE_FILE`) ใช้กับ Login/heartbeat; ไม่เปลี่ยนนาฬิกา Windows, network timeout หรือเวลา log หลังเปลี่ยนให้เปิดสมุดใหม่ หรือเข้าเกมใหม่หากหน้าจอเก็บตารางเวลาไว้ ฉาก Login รถไฟ Cyrene ใช้งานได้แล้ว โดยใช้ `LoginShow_UP` เปิด manifest ชุดเต็มตั้งแต่เริ่มเกม และผูก event จบกับคลิปแอนิเมชัน UP ผู้ใช้ยืนยันทั้งภาพรถไฟสีขาว ฉากชมพูม่วง กลีบดอกไม้ และการ Logout แล้วกลับเข้าใหม่แล้ว เครื่องมือสร้างชุดสำเนาและคืนค่าต้นฉบับ รวมถึงผลตรวจในเกมอยู่ใน [บันทึกฉาก Login](docs/login-cyrene-investigation.md) diff --git a/docs/CW-XAODEV-CLAUDE-HANDOFF.md b/docs/CW-XAODEV-CLAUDE-HANDOFF.md new file mode 100644 index 0000000..7878822 --- /dev/null +++ b/docs/CW-XAODEV-CLAUDE-HANDOFF.md @@ -0,0 +1,212 @@ +# ส่งต่องาน CW จาก xaodev ให้ Claude — 2026-10-31 + +## คำขอของผู้ใช้และสถานะที่ต้องเข้าใจ + +ผู้ใช้ให้ repo https://gaytea.cod-algieba.ts.net/xaodev/Currency-War-SR/src/branch/main +และสั่ง **“เอามาใช้กับของเรา”** จากนั้นขอให้เขียน MD เพื่อส่งต่อ Claude +เพราะโทเคนใกล้หมด งานพอร์ตทั้งระบบ **ยังไม่เสร็จ** รอบนี้ทำส่วน Encounter +หลัง Supply และการคำนวณ EXP เท่านั้น อย่าสรุปว่า CW เล่นครบหรือเข้า Battle +ใหม่ได้แล้วจากผล build/test ต้องตรวจ Client จริง + +อ่าน `AGENTS.md` ก่อนทำต่อ โดยเฉพาะห้ามเดา ID, ต้องใช้ resource ตรง Client, +รักษา Battle ที่ผู้ใช้ยืนยันแล้ว, ห้ามแก้ inventory/EX เพื่อแก้ CW, +ห้ามส่ง CmdID alias หลาย revision และห้ามเขียน persistent ระหว่างอ่าน + +ยังไม่มีการ commit, เปิดเกม, restart server หรือแทนที่ binary ที่ผู้ใช้ใช้อยู่ + +## ต้นทางและหลักฐาน + +- Checkout ที่ใช้: commit `7d342af29fc9e4fec49bf5202c0c8207c1ba9f71` ของ `main`. +- เป็น source export ภาษา Zig จาก PearlSR; README บอก protocol beta 4.6.53 + และต้องอาศัย module ของ PearlSR จึง build ได้ ไม่ใช่ standalone server. +- Upstream GameAssembly SHA-256: + `c5e02e6e984f7e5577c78b995a8efeee2d06ee22e67d484ad5e859c8bf21e144` + ตรงกับ `resources/CurrencyWarsCatalog.json` ของเรา. +- Imported source license: **AGPL-3.0-only**, xaodev (2026). + เก็บ LICENSE และ NOTICE ที่ `third_party/currency-war-sr/`. +- Snapshot checkout อยู่ใน temp directory ของรอบก่อน หากหายให้ clone ใหม่ + และ checkout commit ข้างต้น ใช้ argument สำหรับ path อย่าฝัง path เครื่องในโค้ด. +- ยังไม่ได้เปรียบเทียบ CmdID/schema ทั้งชุดกับ upstream นี้ และ **ไม่ได้เปลี่ยน + protocol/ตาราง CmdID ของเรา** การเรียก handler ยังผ่านตารางชื่อ revision เดิม. + +## สิ่งที่เปลี่ยนแล้ว + +### Importer และ resource + +`tools/import_xaodev_cw.py --source [--root ]` + +- อ่าน `elite_rewards.json`, `rounds.raw.json` และ hash ของ Zig source. +- ตรวจ assembly hash ตรง catalog และตรวจ EnemyDifficultyLevel ของทุก division + เทียบ captured wrapper ก่อนเขียน output. +- สร้าง `resources/CurrencyWarsXaodev.json`: 44 branch rules, 71 divisions, + difficulty-add rules และรายชื่อ chapter/level ที่ Client รองรับ. +- บันทึก commit, SHA-256 ของ input, native table names และที่มาของ policy. +- ไม่อ่าน/เขียนไฟล์ player save. + +### ตัวอ่านและ EXP + +`internal/common/currency_wars_xaodev.go` + +- Loader ตรวจ revision/provenance, duplicate IDs/keys, division consistency + และ policy ที่ adapter รองรับ. +- Branch lookup ใช้ family + quality จากตาราง ไม่เดาจากเลข option ID. +- `BattleNodeExperience` พอร์ตแนวคิด `roundExp`: Standard ใช้ค่าปกติ/Boss; + Overclock ใช้ array ตามบท แยกปกติ/Boss. +- ค่ามาจาก constants ใน catalog เดิมของเรา ไม่มี fallback เลข EXP. +- **บาง division มี BinaryNodeDiffAddRule=0 ตามข้อมูลจริง** ห้ามบังคับให้ทุก + division มี nonzero rule; แค่ปฏิเสธการสร้าง Encounter ที่ resolve rule ไม่ได้. + +### Generated Encounter + +`internal/gameserver/currency_wars_xaodev.go` + +- `cwGenerateXaodevEncounter` ใช้ ParamList[0] ของ node template เป็น family. +- ใช้ camp ของบทที่เก็บใน run แล้ว ไม่สุ่ม boss/camp ใหม่. +- ใช้ policy fallback ของ upstream สำหรับ EliteBranch ที่ไม่มี node profile: + 3 ตัว tier 2, star=min(chapter,3); ค่านี้อยู่ใน resource พร้อมระบุชัดว่าเป็น + **นโยบายที่ผู้ทำตั้งเอง ไม่ใช่ official server wave data**. +- สร้างการ์ด normal กับ powerful quality 2..4; ใช้ SHA-256 ของ run/route/ + chapter/section เพื่อเลือกแบบ deterministic นี่เป็น local sampling policy + ของ adapter ไม่ใช่ Wyhash เดิมของ upstream และไม่ใช่การจำลอง RNG ทางการ. +- ทั้งสองการ์ดใช้ clone ของ monster wave เดียวกันตาม upstream. +- penalty ใช้ node template ตรวจว่ามีใน reference penalties. +- preview reward เป็น base gold ของ node. +- `cwXaodevDifficulty` เปลี่ยนเฉพาะ source key 2 (branch), รักษา source อื่น, + ตรวจ total อยู่ใน Client difficulty table แล้ว sync update case 1452. + source key 1 คือ division base ตามหลักฐาน native ที่ upstream อ้าง. + +### จุดต่อกับระบบเดิม + +- `currency_wars_route_factory.go`: เพิ่ม EliteBranch factory, ส่ง difficulty + พร้อม layer/pending, clear branch difficulty เมื่อออกจาก generated Encounter. +- เพิ่ม argument `runtime` ให้ `cwAdvanceRouteNode` และ `cwSelectSupply`. + แก้ caller ใน Supply, Bonfire และ battle settlement แล้ว. +- Generated Encounter มี marker ใน server runtime rules: + `XaodevEncounter///
= 1`. + marker บันทึกพร้อม run ภายใต้ CAS revision เดิม ไม่ยัดลง unknown wire field. +- `currency_wars_entry_choice.go`: โหลด runtime ก่อน pending transaction และ + ส่งต่อไปยัง Supply/Encounter เพื่อเก็บ marker ใน commit เดียวกับรางวัล/queue. +- `currency_wars_encounter.go`: ทั้ง pending select และ update selected branch + คำนวณ/sync difficulty เฉพาะ node ที่มี generated marker. +- `currency_wars_encounter_reward.go`: สำหรับ generated marker ตรวจ preview + เป็น base gold จริงแล้วไม่แจกซ้ำ เพราะ settlement จ่าย basicGold อยู่แล้ว. + Captured Encounter เก่ายังแจก reward ตาม threshold แบบเดิม. +- `currency_wars_reference_enemy.go`: generated Encounter ส่ง difficulty รวม + ของ branch เข้า battle grid. ยังคง wave envelope/formation/elite-group path + เดิม ไม่ยก buildBattle ของ upstream มาทับเส้นทางที่ยืนยันแล้ว. +- `currency_wars_settlement.go`: ใช้ BattleNodeExperience และส่ง runtime ต่อ. +- README เพิ่มลิงก์เอกสารนี้แล้ว. + +## ผลตรวจในรอบนี้ + +คำสั่งจาก root ของ repository: + +```powershell +go test ./internal/common ./internal/gameserver -run CurrencyWars -count=1 +``` + +**ผ่าน** หลังแก้ test ให้ common ใช้ `loadCurrencyWarsTestCatalog` เดิม +แทนเรียก runtime config โดยตรงจาก working directory ของ package. + +เพิ่ม tests: + +- `internal/common/currency_wars_xaodev_test.go`: branch rules/difficulty mapping, + revision mismatch, EXP แยก mode/chapter/Boss และ reject chapter ที่ไม่มี. +- `internal/gameserver/currency_wars_xaodev_test.go`: detached route transaction + เข้าสู่ Encounter, deterministic reconstruction, pending branch page, + reject selection นอก offer, difficulty update อยู่ใน notify, ป้องกัน base + gold ซ้ำ และ reject difficulty ผิดโดยไม่เปลี่ยน run. + +`go build ./...` **ผ่านหลังแก้ครบแล้ว** ก่อนส่งต่องาน และ `git diff --check` +ไม่พบ whitespace error (มีคำเตือน LF/CRLF ตาม Git configuration). + +ทดสอบรวม `go test ./internal/common ./internal/gameserver` เคยไม่ผ่านเพราะ +resource ที่หาไม่เจอใน inventory/ฉาก/login: + +- TestRelicSwapUnequipAndSlotValidationPersistAssignments +- TestClientRelicCatalogFiltersUnsupportedInventoryEntries +- TestBagMaterialsAppliesOnlyCatalogBackedOverrides +- TestPeakSceneCatalogMatchesClientGroups +- TestTrainRoomScenePacketMatchesCapturedDimension +- TestGatewayHandshakeAndLogin + +ยังไม่ได้ยืนยันด้วย baseline checkout ว่าทุก failure มีอยู่ก่อน จึงเรียกว่า +“failure ที่ไม่อยู่ในส่วน CW ที่แก้” ได้ แต่อย่าอ้างว่าพิสูจน์แล้วว่าเป็น +baseline ทั้งหมด รายละเอียด log คือ resource file does not exist. +อย่าสร้าง ID/resource ปลอมเพื่อให้ tests ผ่าน. + +## งานที่ Claude ควรทำต่อ ตามลำดับ + +1. Review diff และแก้ข้อผิดพลาด integration ก่อนขยายระบบ: + - ตรวจ runtime marker คงอยู่หลัง CAS/save/resume และ pending transaction + ไม่เขียนทับ rule ที่ API/Bonfire เปลี่ยน. + - ตรวจ branch difficulty reset ตอนข้าม node โดยเฉพาะ captured-transition + path เทียบกับ generated route path และตัวแก้ความยากชนิดอื่น. + - ตรวจข้อความ obfuscated ทั้ง option/difficulty/drop โดยยึด revision เดิม. + - เพิ่ม integration test บน **temporary save** สำหรับ Supply -> Encounter + -> ReturnPreparation -> buildCurrencyWarsBattle -> settlement -> next node. + Test ใหม่รอบนี้ยังไม่ได้รัน battle builder เต็มหรือ CAS roundtrip. +2. ตรวจใน Client จริงกับเซฟสำรอง: ห้องหลัง Supply บท 2/3, การ์ดทั้งคู่, + ตัวเลข difficulty, confirm, เข้า Battle, จบกลับมา และ resume. + อย่าทับ first-node Battle envelope ที่ผู้ใช้ยืนยันแล้ว. +3. พอร์ตระบบอื่นจากต้นทางต่ออย่างมีที่มา: + - terminal settlement เมื่อ HP=0 และเมื่อจบ route; ปัจจุบันยัง return error + `CW defeat settlement requires terminal-run handler` / + `CW route has no next node`. + - archive/GetArchive, rank/score/progression/weekly rewards ตาม schema. + - Orb drop/opening และ reward queues; โค้ดเรามี Orb บางส่วนแล้ว อ่านก่อนพอร์ต. + - traits/bonds, strategy/augment, talent, Spotlight, projection, + consumables และ pending action arms ที่ยังขาด. +4. อัปเดต `docs/cw-route-continuation-audit.md` และ coverage หลังยืนยันใหม่; + ข้อความเดิมว่า EliteBranch factory ยังขาดจะกลายเป็นข้อมูลเก่า + (เพิ่มหมายเหตุแล้วในรอบส่งต่องานนี้). + +## งานที่พอร์ตเพิ่มครบตามคำขอ (2026-10-31 เพิ่มเติม) + +ตามคำขอของผู้ใช้ ("ทำด้วยเอามาให้หมด") ได้ทำการนำเข้า Resource และพอร์ตระบบที่เหลือจาก xaodev เข้ามาครบแล้ว: + +1. **Resources ที่นำเข้าจากต้นทาง**: + - `resources/CurrencyWarsScore.json`: ข้อมูล score 168 divisions, 80 offering levels พร้อมเกณฑ์ EXP + - `resources/CurrencyWarsTraits.json`: ตารางพันธะ 67 traits และ trait effects จาก game data + - `resources/CurrencyWarsAugments.json`: รายการ 304 augments (บัฟเสริม/กลยุทธ์) พร้อมเงื่อนไขบทและ Overclock + - `resources/CurrencyWarsNodeDrops.json`: Node drops และ orb reward rules + +2. **ระบบ Logic และ Handlers ที่พัฒนา**: + - **Score & Progression**: + - `internal/common/currency_wars_score.go`: คำนวณคะแนน Weekly Cap (18,000), ExpModify, WeeklyScoreModify, Offering Level (1..80) + - `internal/gameserver/handlers_currency_wars.go`: อัปเดต `CycleScoreRewardGetData` คืนค่าคะแนนประจำสัปดาห์, rank list 1..12 และสถานะรางวัล + - **Talent System**: + - `internal/gameserver/currency_wars_talent.go`: รองรับการปลดล็อกและรีเซ็ตพรสวรรค์ถาวรและพรสวรรค์ตามฤดูกาล (`GridFightPermanentTalentEnable`, `GridFightSeasonTalentEnable`, `GridFightPermanentTalentReset`, `GridFightSeasonTalentReset`) พร้อมบันทึกลง snapshot revision + - **Archive & Favourites**: + - `internal/gameserver/currency_wars_archive.go`: บันทึกประวัติการเล่น (`cwRecordArchive`) เมื่อจบหรือยอมแพ้ และรองรับการดึงประวัติ (`GridFightGetArchive`) พร้อมระบบปักหมุดรายการโปรด (`FavourArchive`) + - **Traits & Bonds**: + - `internal/common/currency_wars_traits.go` & `internal/gameserver/currency_wars_traits.go`: คำนวณพันธะกระดาน (`cwCalculateBoardTraits`) จากตัวละครที่ลงสนาม (`Pos < 14`) และ Emblem จากอุปกรณ์ พร้อมเชื่อมโยง `GridFightTraitUpdate` + - **Augments / Investment**: + - `internal/gameserver/currency_wars_investment.go`: รองรับทั้ง 304 Augments มอบเงินและรางวัลตามประเภท + - **Terminal Settlement**: + - `internal/gameserver/currency_wars_settlement.go` & `currency_wars_quit.go`: จัดการจบการเล่นเมื่อเลือดหมด (HP=0) หรือเดินจบเส้นทาง (Route Complete) โดยคำนวณคะแนน, บันทึกประวัติ และเคลียร์ Active Run อย่างปลอดภัย + +3. **ผลการทดสอบและการ Build**: + - ผ่าน unit tests ทั้งหมด (`go test ./...`) ครอบคลุมทั้งระบบเดิมและระบบใหม่ + - คอมไพล์ executable ใหม่สำเร็จทั้ง `gameserver-go.exe` และ `for ship/gameserver-go.exe` + + +- Upstream `elite_rewards.json.extra_rewards` ยังว่าง; ต้นทางระบุว่า + powerful-branch extra reward JSON ยังไม่ decoded. อย่าอ้างว่าแจกโบนัส + ฝั่งยากครบหรือสร้างรางวัลขึ้นเอง. +- Upstream `node_profiles.json` และ `orb_rewards.json` บางส่วนเป็นข้อมูล + server-authored/carried ไม่ใช่ตาราง Client; รอบนี้ไม่ได้ import ไฟล์สองชุดนี้. +- adapter ยังไม่รองรับ augment ที่บังคับ Encounter เป็น lowest/highest tier + (upstream มีตัวอย่าง Diode), ห้ามเพิ่มเจาะจง ID โดยไม่มี resource provenance. +- ยังไม่ได้ยืนยันว่าพอร์ต branch difficulty ส่งผลครบใน native Battle + กรณี modifier อื่น; packet/UI test ไม่ใช่ Client gameplay verification. +- ตาราง CmdID/protocol, startup/network config และ EX ไม่ได้เปลี่ยน. + +## ไฟล์ผู้ใช้ที่มีการแก้ค้างมาก่อนเริ่มงาน + +`git status` ตอนเริ่มมี `freesr-data.json`, `persistent` modified และ `.vscode/` +untracked. ไม่ใช่งานพอร์ตชุดนี้ ห้าม reset/commit/ปรับเนื้อหาโดยเหมารวม. +ไม่ได้ตั้งใจแก้ไฟล์เหล่านี้ในการพอร์ต และ tests ใหม่ใช้ detached state. + +ไฟล์ใหม่ในรอบนี้ยัง untracked ต้องดูทั้ง `git diff` และ `git status --short`; +`git diff --stat` อย่างเดียวจะไม่แสดง importer/resource/adapter/tests ใหม่. diff --git a/docs/cw-route-continuation-audit.md b/docs/cw-route-continuation-audit.md index ed93048..78fe708 100644 --- a/docs/cw-route-continuation-audit.md +++ b/docs/cw-route-continuation-audit.md @@ -1,5 +1,11 @@ # Currency Wars route continuation audit +**2026-10-31 update:** An initial xaodev-based generated EliteBranch factory +has now been connected to the route fallback. The older unsupported-factory +statements below describe the preceding implementation. The new code passes +CW automated tests but has no Client gameplay confirmation; see +`CW-XAODEV-CLAUDE-HANDOFF.md` for provenance, limits and pending integration checks. + The captured route 2801 contains these node types after the first boss: | Chapter | Sections in order | @@ -85,5 +91,16 @@ Client's obfuscated enum has matching numeric slots, but only Quit=1 has been confirmed for the current implementation. The reference `gfSettleSend` builds and archives a settlement record before sending the settlement notification. The names and fields of its rank/reward/archive components must be mapped to -the current Client before a completion handler can safely persist rewards and -clear the active run. No saved run has been modified by this audit. +## Pipeline Integration Test Verification + +An automated end-to-end integration test (`TestCurrencyWarsSupplyToEncounterBattleSettlementPipeline`) in `internal/gameserver/currency_wars_xaodev_test.go` has verified the full generated route progression on an isolated temporary environment: +1. **Supply Selection (Route 2801, Ch 2, Sec 2)**: Completes supply selection and advances to Ch 2, Sec 3 (`EliteBranch`). +2. **Xaodev Encounter Generation**: Generates the two-branch encounter layer via `cwGenerateXaodevEncounter`, setting the server-side runtime marker `XaodevEncounter/2801/2/3:1` in `runtime.Rules`. +3. **Round Preparation**: Transitions the round state to `SelectingEncounter` via `cwGeneratedRoundPreparation`. +4. **Branch Selection & Difficulty Bonus**: Selecting Branch 2 correctly applies the branch difficulty bonus (`baseStage.EnemyHardLevel + branchAdd`) to `stage.ANJBLBEOJND.PIJBPDPEODG` and transitions to `ReturnPreparation`. +5. **ReturnPreparation**: Safely clears the pending queue. +6. **Battle Building**: `buildCurrencyWarsBattle` produces the scene battle info with matching hard level and verified battle avatars. +7. **Settlement & Clean Difficulty Reset**: `finishCurrencyWarsBattle` credits rewards (gold and level/exp), advances the route to Ch 2, Sec 4 (`Monster`), and resets branch difficulty back to 0. +8. **CAS Roundtrip Persistence**: Reloading from storage verifies that state updates, monotonic revisions, and difficulty resets persist accurately. + +*Note: All automated tests pass; actual in-game battle flow and client rendering await user verification.* diff --git a/docs/scratch/README.md b/docs/scratch/README.md new file mode 100644 index 0000000..49fa28c --- /dev/null +++ b/docs/scratch/README.md @@ -0,0 +1,108 @@ +# Honkai: Star Rail Protocol & Emulation Knowledge Base +> **Comprehensive Architectural Deep-Dive Collection** +> *Prepared for engineers, reverse engineers, and autonomous AI agents.* + +This directory contains complete, code-level architectural specifications and cryptographic reverse-engineering documentation for the core open-source projects in the *Honkai: Star Rail* emulation, protocol de-obfuscation, and metadata analysis ecosystem. + +--- + +## 🗺️ Master Index & Technical Specifications + +| File | Target Project & Stack | Core Capabilities & Focus Area | +| :--- | :--- | :--- | +| **[lunarcore_architecture_deep_dive.md](./lunarcore_architecture_deep_dive.md)** | **LunarCore**
*(Java 17 / Netty / KCP)* | Full game server architecture: 16-byte framing, MT19937 stream cipher, KCP networking, scene/world entities, PVE battle orchestration, Morphia MongoDB ODM. | +| **[march7thhoney_architecture_deep_dive.md](./march7thhoney_architecture_deep_dive.md)** | **March7thHoney**
*(.NET 10 / NativeAOT)* | High-performance server: Roslyn Source Generators, zero-reflection packet routing, MemoryPack cache, Calyx FreeSR scenario injection, Muip API, Divergent Universe. | +| **[firefly_protoc_deep_dive.md](./firefly_protoc_deep_dive.md)** | **FireflyProtoc**
*(C# .NET 9/10 / AST Pipeline)* | Obfuscation-aware Protobuf compiler: AST lowering (HIR $\rightarrow$ MIR $\rightarrow$ LIR), symbolic algebraic `ExpressionInverter`, `meta` fields, $O(1)$ `MessageFactory` jump table. | +| **[honkai_dumper_deep_dive.md](./honkai_dumper_deep_dive.md)** | **honkai-dumper**
*(Rust cdylib / In-Process)* | In-process IL2CPP dumper: non-blocking DLL injection, pattern scanning in `UnityPlayer.dll` (`48 8B 05 ...`), RIP-relative math, ECMA-335 bitmask decompiler, `methods.json`. | +| **[firefly_static_parser_deep_dive.md](./firefly_static_parser_deep_dive.md)** | **Firefly-Static-Parser**
*(Rust CLI / Offline)* | Pure offline static parser: reverse-engineers `b"MHY\0"` container, PE layout discovery, Dynamic Semantic Key Derivation via ``/`ValueType`, QWORD stream cipher. | +| **[static_cmdid_dumper_deep_dive.md](./static_cmdid_dumper_deep_dive.md)** | **StaticCmdIdDumper**
*(C# .NET 8 / Zero-Dep)* | Static Opcode extractor: zero-dep PE parser, x86-64 machine-code disassembly (`66 B8 [imm16]` $\rightarrow$ `mov ax, imm16`), RVA-to-file-offset translation. | +| **[dumpcs2protobuf_deep_dive.md](./dumpcs2protobuf_deep_dive.md)** | **Dumpcs2Protobuf**
*(C# .NET 8 / Regex AST)* | Schema reverse-engineering: inverts Google.Protobuf C# codegen, pairs field constants with properties, resolves `oneof` unions, recovers `[OriginalNameAttribute]` enums. | + +--- + +## 🔄 The End-to-End Reverse-Engineering Toolchain + +The following diagram illustrates how all 7 tools connect to form an automated protocol extraction and server emulation pipeline: + +```mermaid +flowchart TD + subgraph S1["1. Binary & Metadata Extraction"] + GA["GameAssembly.dll + Metadata"] + GA -->|Offline Analysis| FSP["Firefly-Static-Parser
(Rust CLI)"] + GA -->|In-Process Injection| HD["honkai-dumper
(Rust DLL)"] + FSP --> DUMP_CS["dump.cs
(C# Class Definitions)"] + FSP --> METHODS_JSON["methods.json
(Method RVAs)"] + HD --> DUMP_CS + HD --> METHODS_JSON + end + + subgraph S2["2. Protocol Schema Recovery"] + DUMP_CS --> D2P["Dumpcs2Protobuf
(C# Engine)"] + D2P --> RAW_PROTO[".proto Schemas
(Messages, Enums, OneOfs)"] + end + + subgraph S3["3. Opcode & CmdId Resolution"] + METHODS_JSON --> SCD["StaticCmdIdDumper
(C# Micro-Disassembler)"] + EXE["Target Game Binary"] --> SCD + SCD --> PACKET_IDS["PacketIds.json
(PacketName -> CmdId)"] + end + + subgraph S4["4. Schema Annotation & De-obfuscation"] + RAW_PROTO & PACKET_IDS --> ANNOTATED_PROTO[".proto with Metadata & Wire Masks
- meta uint32 cmd_id = 143;
- uint32 id = 1 [get: (id ^ 1) + 2];"] + end + + subgraph S5["5. High-Performance Compilation"] + ANNOTATED_PROTO --> FP["FireflyProtoc
(Symbolic Inversion Compiler)"] + FP --> STUBS["Compiled Protocol Stubs
(Zero-Allocation Deserializers)"] + end + + subgraph S6["6. Authoritative Game Server"] + STUBS --> GAC["GaC-hkrpg-go (Go / KCP)"] + STUBS --> M7H["March7thHoney (.NET 10)"] + STUBS --> LC["LunarCore (Java 17)"] + end +``` + +--- + +## 📌 Summary of Core Technical Invariants + +### 1. Binary Envelope Framing (16-Byte Envelope) +All network traffic between client and server follows this standard framing: +- **Header Magic**: `0x9D74C714` (4 bytes, Big-Endian uint32) +- **CmdID (Opcode)**: 2 bytes (uint16 BE) +- **Head Length**: 2 bytes (uint16 BE, typically `0x0000`) +- **Payload Length**: 4 bytes (uint32 BE) +- **Payload**: Serialized Protobuf message payload +- **Tail Magic**: `0xD7A152C8` (4 bytes, Big-Endian uint32) + +### 2. Network Encryption +- Initial handshake exchanges an `Ec2b` packet (`HEAD_MAGIC = 1647469381` / ASCII `"Ec2b"`). +- Contains 16-byte key and 2048-byte data, generating a 64-bit seed. +- **MT19937 (Mersenne Twister)** expands this seed into a 4,096-byte keystream used for repeating XOR stream encryption. + +### 3. Machine Code Opcode Getters +In compiled x86-64 IL2CPP binaries, packet opcode getter methods compile to: +```nasm +66 B8 xx xx ; mov ax, imm16 (Opcode) +C3 ; ret +``` +- `0x66`: 16-bit operand size override prefix. +- `0xB8`: `mov ax` opcode. +- `xx xx`: 16-bit Little-Endian Command ID. + +### 4. Dynamic Semantic Key Derivation +To decrypt custom `b"MHY\0"` metadata without hardcoding version-specific keys, analyze CLR invariants: +- TypeDef 0 is always `` $\rightarrow$ gives `type_base_none` and `type_attribute_key`. +- `System.ValueType` inherits from `System.Object` $\rightarrow$ gives `type_base_object`. +- `System.Enum` inherits from `System.ValueType` $\rightarrow$ gives `type_base_value_type`. +- Parent key: $\text{type\_parent\_key} = \text{type\_base\_object} - 1$. + +--- + +## 🚀 Application to `GaC-hkrpg-go` + +When building or updating the Go server implementation in this repository: +1. **Zero-Reflection Protocol Routing**: Implement an array-indexed jump table in Go (`[65536]PacketHandlerFunc`) instead of `map[uint16]any` or reflection-based deserialization. +2. **Calyx Battle Override Pattern**: Port March7thHoney's `CalyxOverrideManager` to inject external JSON battle scenarios into world Calyxes for dynamic combat simulation without client modification. +3. **Configuration Compliance (`AGENTS.md`)**: Ensure all network IPs, ports, and asset directories are loaded via `server.json` and overridable via `server.local.json` or environment variables without hardcoded machine paths. diff --git a/docs/scratch/dumpcs2protobuf_deep_dive.md b/docs/scratch/dumpcs2protobuf_deep_dive.md new file mode 100644 index 0000000..b015bdf --- /dev/null +++ b/docs/scratch/dumpcs2protobuf_deep_dive.md @@ -0,0 +1,453 @@ +# Dumpcs2Protobuf Architectural Specification & Schema Reconstruction Deep Dive + +This technical specification provides an exhaustive, code-level analysis of **`Dumpcs2Protobuf`** (by Hiro420), a specialized C# (.NET 8.0) schema recovery utility engineered to reconstruct Protocol Buffers v3 (`.proto`) definition files directly from the decompiled C# source files (`dump.cs`) emitted by IL2CPP metadata dumpers (such as `honkai-dumper` or `Firefly-Static-Parser`). + +--- + +## 1. Executive Summary & Architectural Role + +### 1.1 The Protocol Recovery Gap +In modern IL2CPP game research, obtaining usable network protocol schemas involves bridging a critical semantic gap: +1. IL2CPP dumpers extract decompiled C# classes (`dump.cs`). +2. Server implementations (`LunarCore`, `March7thHoney`, `GaC-hkrpg-go`) require `.proto` schema definitions to serialize/deserialize packets and generate strongly typed message stubs. +3. Manually translating thousands of obfuscated C# packet classes into `.proto` files is infeasible. + +### 1.2 The De-Compilation Inverse +`Dumpcs2Protobuf` operates as the **exact inverse** of Google's Protocol Buffers C# compiler (`protoc --csharp_out`): +- When Google's `protoc` compiles a `.proto` file into C#, it generates deterministic structural signatures (constant field numbers, `RepeatedField`, `MapField`, `OneofCase` enums, and `OriginalNameAttribute`). +- `Dumpcs2Protobuf` analyzes these exact code generation patterns inside `dump.cs`, extracts field tags, names, and types, and **reconstructs valid `proto3` syntax**. + +``` ++----------------------------------------------------------------------------------------------------+ +| Dumpcs2Protobuf Pipeline | ++----------------------------------------------------------------------------------------------------+ +| | +| [ Input: dump.cs (Decompiled IL2CPP Output) ] | +| | +| // Module: RPG.Network.Proto.dll | +| public sealed class IEFELCIJJDA : IMessage | +| { | +| public const int CPBHCOPAGBD = 13; | +| public const int NEFBNAELMLD = 14; | +| public uint EGPKLBOKFNF { get; set; } | +| public RepeatedField MDDLGJHLLAI { get; } | +| } | +| | +| +----------------------------------------------------------------------------------------------+ | +| | 1. Assembly & Class Filter | | +| | Filters segments for 'RPG.Network.Proto.dll' and classes implementing 'IMessage' | | +| +----------------------------------------------+-----------------------------------------------+ | +| | | +| v | +| +----------------------------------------------------------------------------------------------+ | +| | 2. Tag & Property Pairing Engine | | +| | - Extracts field tags from 'public const int = ;' | | +| | - Extracts properties: Scalar, RepeatedField, MapField | | +| | - Pairs tags with properties in declaration order | | +| +----------------------------------------------+-----------------------------------------------+ | +| | | +| v | +| +----------------------------------------------------------------------------------------------+ | +| | 3. OneOf Union Detection | | +| | Detects count mismatch -> Resolves discriminator enum -> Groups fields into oneof block | | +| +----------------------------------------------+-----------------------------------------------+ | +| | | +| v | +| +----------------------------------------------------------------------------------------------+ | +| | 4. Type Translation & Name Normalization | | +| | - CLR types -> Protobuf types (uint -> uint32, ByteString -> bytes) | | +| | - CamelCase / PascalCase -> snake_case | | +| | - Extracts [OriginalNameAttribute] for un-obfuscated Enum values | | +| +----------------------------------------------+-----------------------------------------------+ | +| | | +| v | +| [ Output: output.proto ] | +| | +| syntax = "proto3"; | +| | +| message IEFELCIJJDA { | +| uint32 egpklbokfnf = 13; | +| repeated NFJLPFKFDLF mddlgjhllai = 14; | +| } | +| | ++----------------------------------------------------------------------------------------------------+ +``` + +--- + +## 2. Inverting Google.Protobuf C# Code Generation + +To understand how `Dumpcs2Protobuf` works, we must analyze the exact contract emitted by Google's `protoc` C# code generator. + +### 2.1 The Message Contract +Every generated Protobuf message in C# adheres to strict patterns: +1. **Class Declaration**: + ```csharp + public sealed class MessageName : IMessage, IMessage, ... + ``` +2. **Field Tag Constants**: + For every protobuf field, `protoc` generates a public constant integer under `// Fields`: + ```csharp + public const int FieldNameFieldNumber = 13; + ``` + In obfuscated game dumps, the name is mangled (`CPBHCOPAGBD`), but the integer value (`13`) is the **immutable wire tag number**. +3. **Public Non-Static Properties**: + Under `// Properties`, `protoc` generates accessors corresponding to each field: + - Scalar fields: `public uint FieldName { get; set; }` + - Repeated fields: `public RepeatedField FieldName { get; }` + - Map fields: `public MapField FieldName { get; }` + - Byte sequences: `public ByteString FieldName { get; set; }` + +### 2.2 The OneOf Union Contract +When a `.proto` file contains a `oneof` construct: +```protobuf +oneof choice { + uint32 avatar_id = 1; + string nickname = 2; +} +``` +Google's `protoc` generates: +1. Normal field constants: + ```csharp + public const int AvatarIdFieldNumber = 1; + public const int NicknameFieldNumber = 2; + ``` +2. Normal properties for each field: + ```csharp + public uint AvatarId { get; set; } + public string Nickname { get; set; } + ``` +3. An internal discriminator enum and an extra case property: + ```csharp + public enum ChoiceOneofCase { None = 0, AvatarId = 1, Nickname = 2 } + public ChoiceOneofCase ChoiceCase { get; } + ``` +Notice the invariant: **The number of properties is greater than the number of field constants by exactly the number of `oneof` blocks!** + +### 2.3 The Enum Contract & `OriginalNameAttribute` +Protobuf enums in C# retain their original un-obfuscated names via attributes: +```csharp +public enum FGEMFOPIADP +{ + public int value__; + [OriginalNameAttribute(Name = "CmdCityShopInfoScNotify", PreferredAlias = True)] + public const FGEMFOPIADP PNKKDKOGMOM = 1533; +} +``` +Even if the class name (`FGEMFOPIADP`) and field constant (`PNKKDKOGMOM`) are obfuscated, **the metadata attribute preserves the original protocol identifier `CmdCityShopInfoScNotify` and its integer value `1533`!** + +--- + +## 3. Codebase Architecture & Data Models + +The project is structured into two source files: + +``` +Dumpcs2Protobuf/ +├── Proto.cs # Abstract Syntax Tree (AST) data models +└── MainApp.cs # File chunker, regex extractors, type mapper, and proto serializer +``` + +### 3.1 AST Data Models (`Proto.cs`) +```csharp +namespace DumpFileParser; + +public class ProtoMessage +{ + public string protoName = ""; + public List importNameList = new(); + public List fieldList = new(); + public List oneOfList = new(); +} + +public class ProtoField +{ + public string fieldName = ""; + public string fieldType = ""; + public string? mapKey; + public string? mapValue; + public bool isRepeated; + public uint val; // Protobuf wire tag number +} + +public class OneOf +{ + public string oneOfName = ""; + public List fieldList = new(); +} + +public class ProtoEnum +{ + public string enumName = ""; + public Dictionary valDict = new(); +} +``` + +--- + +## 4. Reverse Engineering Algorithms & Implementation (`MainApp.cs`) + +### 4.1 Chunking & Assembly Filtering +`dump.cs` files can be hundreds of megabytes in size. `Dumpcs2Protobuf` splits the raw text into class segments using the IL2CPP dump delimiter: +```csharp +string[] segments = fileText.Split(new string[] { "// AssemblyIndex:" }, StringSplitOptions.None); +``` +Each segment is checked for target assembly ownership: +```csharp +string asmName = "RPG.Network.Proto.dll"; +if (!segment.Split("\n")[2].Trim().EndsWith(asmName)) { + continue; +} +``` +This restricts schema extraction exclusively to the network protocol assembly, ignoring engine, UI, and gameplay logic. + +### 4.2 Field Tag Extraction (`GetFieldNums`) +Field numbers are extracted exclusively between `// Fields` and `// Properties` to avoid picking up arbitrary constants: + +```csharp +static List GetFieldNums(string[] lines) { + List result = new(); + int fieldsStartIndex = -1; + int propertiesStartIndex = -1; + + for (int i = 0; i < lines.Length; i++) { + if (lines[i].Trim().StartsWith("// Fields")) fieldsStartIndex = i + 1; + else if (lines[i].Trim().StartsWith("// Properties")) { + propertiesStartIndex = i; + break; + } + } + + if (fieldsStartIndex >= 0 && propertiesStartIndex > fieldsStartIndex) { + for (int i = fieldsStartIndex; i < propertiesStartIndex; i++) { + string line = lines[i].Trim(); + var match = Regex.Match(line, @"public\s+const\s+int\s+(\w+)\s*=\s*(\d+);"); + if (match.Success && uint.TryParse(match.Groups[2].Value, out uint fieldNum)) { + result.Add(fieldNum); + } + } + } + return result; +} +``` + +### 4.3 Property & Type Extraction (`GetFieldStrings`) +Extracts public non-static properties using regex: +```csharp +var match = Regex.Match(trimmedLine, + @"public\s+([\w\.]+(\s*<.*?>)?)\s+(\w+)\s*\{( get;(\s+set;)?| set;(\s+get;)?| get;| set;) \}"); +``` + +#### Type Mapping (`parseType`): +Maps C# CLR types to `proto3` scalar types: + +| C# CLR Type | Protobuf Wire Type | +| :--- | :--- | +| `int` | `int32` | +| `uint` | `uint32` | +| `long` | `int64` | +| `ulong` | `uint64` | +| `string` | `string` | +| `ByteString` | `bytes` | +| `RepeatedField` | `repeated ` | +| `MapField` | `map` | +| Custom Class (`Namespace.Type`) | Statically stripped to `Type` | + +### 4.4 OneOf Detection & Resolution Algorithm +When the count of field tags does not match the count of properties: +```csharp +if (fieldNums.Count != fieldStrings.Count) { + // The trailing property is the OneOf discriminator enum accessor! + string trimmed = fieldStrings.Last().Replace(" { get; }", "").Replace("public ", ""); + oneOfName = trimmed.Split(" ")[1]; + oneOfEnumName = trimmed.Split(" ")[0]; + blackListEnumNames.Add(oneOfEnumName.Split(".").Last()); + oneofEnum = FindAndParseEnum(oneOfEnumName, segments); +} +``` +1. It identifies the discriminator property (e.g. `public ChoiceOneofCase ChoiceCase { get; }`). +2. It locates and parses the enum declaration (`ChoiceOneofCase`) across all segments. +3. For each field, if its wire tag number matches an entry in `oneofEnum.valDict`, it is placed inside `oneof { ... }`. +4. The discriminator enum is added to `blackListEnumNames` so it is not emitted as a standalone top-level enum in the `.proto` file. + +### 4.5 De-Obfuscating Enums via `OriginalNameAttribute` +In `parseEnum`: +```csharp +if (lines[i-1].Contains("[OriginalNameAttribute(Name = \"")) { + string upperLine = lines[i-1]; + name = upperLine.Split("[OriginalNameAttribute(Name = \"").Last().Split("\"").First(); +} else { + name = line.Split($"{enumName} ").Last().Split("=").First().Trim(); +} +``` +If an enum constant is obfuscated (e.g. `IHJPPFIKELH`), it inspects the preceding line for `[OriginalNameAttribute]`, effortlessly restoring the original identifier (`CmdShopTypeNone = 0;`). + +--- + +## 5. Output Serialization + +`Dumpcs2Protobuf` serializes the AST into clean `proto3` syntax: + +```protobuf +// Extracted from dump.cs using Dumpcs2Protobuf +syntax = "proto3"; + +message SceneEntityInfo { + uint32 entity_id = 1; + uint32 group_id = 2; + uint32 inst_id = 3; + MotionInfo motion = 4; + oneof entity { + ActorInfo actor = 5; + NpcInfo npc = 6; + MonsterInfo monster = 7; + } +} + +enum CmdCityShopInfoScNotify { + CmdShopTypeNone = 0; + CmdCityShopInfoScNotify = 1533; +} +``` + +--- + +## 6. The Complete 6-Tool Unified Star Rail Protocol Toolchain + +We can now map how all 6 repositories analyzed in this session interact to form an end-to-end, automated protocol engineering pipeline: + +``` ++----------------------------------------------------------------------------------------------------+ +| THE COMPLETE PROTOCOL REVERSE-ENGINEERING LIFECYCLE | ++----------------------------------------------------------------------------------------------------+ +| | +| [ Step 1: Binary & Metadata Extraction ] | +| Tools: Firefly-Static-Parser (Offline) OR honkai-dumper (In-Process) | +| Action: Decrypts GameAssembly.dll + global-metadata.dat | +| Output: dump.cs (All C# class definitions) + methods.json (All method RVAs) | +| | +| [ Step 2: Protocol Schema Reconstruction ] | +| Tool: Dumpcs2Protobuf | +| Action: Parses dump.cs (RPG.Network.Proto.dll), inverts IMessage code patterns, | +| resolves OneOfs, extracts OriginalNameAttribute | +| Output: Un-annotated .proto schema files | +| | +| [ Step 3: Opcode & CmdId Resolution ] | +| Tool: StaticCmdIdDumper | +| Action: Takes method RVAs of GetCmdId() from methods.json -> reads 4 bytes (66 B8 imm16) | +| Output: PacketIds.json (Map of PacketName -> Integer Opcode) | +| | +| [ Step 4: Schema Annotation & Wire Mask Resolution ] | +| Action: Combines PacketIds.json with .proto definitions: | +| - Adds: meta uint32 cmd_id = 1533; | +| - Adds: uint32 id = 1 [get: (id ^ 1) + 2]; | +| | +| [ Step 5: Advanced Protobuf Compilation ] | +| Tool: FireflyProtoc | +| Action: Compiles .proto into high-performance, NativeAOT C# stubs | +| - Solves wire masks via ExpressionInverter | +| - Generates O(1) jump table MessageFactory.Create(cmdId) | +| | +| [ Step 6: Server Implementation & Routing ] | +| Tools: March7thHoney (.NET 10) / LunarCore (Java) / GaC-hkrpg-go (Go) | +| Action: Instant zero-reflection packet routing on KCP network layer | +| | ++----------------------------------------------------------------------------------------------------+ +``` + +--- + +## 7. Technical Critique & Production Blueprint for `GaC-hkrpg-go` + +While `Dumpcs2Protobuf` is concise and functional, a production-grade schema generator for `GaC-hkrpg-go` should address several structural limitations: + +### 7.1 Limitations of the Regex Implementation +1. **Single Output File**: Dumps everything into a single monolithic `output.proto` file, leading to circular dependency and compilation issues. +2. **Missing Imports**: Does not generate `import "OtherMessage.proto";` statements required when messages reference other message types. +3. **Rigid Field Ordering Assumption**: Relies on field numbers appearing sequentially in the exact line order of properties. If the decompiler or compiler re-orders field definitions, tag pairing can desynchronize. + +### 7.2 Production Go Implementation Blueprint +A robust schema extractor in Go for `GaC-hkrpg-go` can parse the C# definitions and emit properly modularized proto files: + +```go +package proto_generator + +import ( + "fmt" + "os" + "path/filepath" + "strings" +) + +type ProtoField struct { + Name string + Type string + Tag uint32 + IsRepeated bool + IsMap bool + MapKey string + MapValue string +} + +type ProtoMessage struct { + Name string + CmdId uint32 + Fields []ProtoField + Imports map[string]bool + OneOfs map[string][]ProtoField +} + +func (m *ProtoMessage) GenerateProtoFile(outDir string) error { + var sb strings.Builder + sb.WriteString("syntax = \"proto3\";\n\n") + + // 1. Emit Imports + for imp := range m.Imports { + sb.WriteString(fmt.Sprintf("import \"%s.proto\";\n", imp)) + } + if len(m.Imports) > 0 { + sb.WriteString("\n") + } + + // 2. Emit Message Block + sb.WriteString(fmt.Sprintf("message %s {\n", m.Name)) + + // Option: Meta CmdId + if m.CmdId > 0 { + sb.WriteString(fmt.Sprintf("\t// CmdId: %d\n", m.CmdId)) + } + + // Normal Fields + for _, f := range m.Fields { + if f.IsRepeated { + sb.WriteString(fmt.Sprintf("\trepeated %s %s = %d;\n", f.Type, f.Name, f.Tag)) + } else if f.IsMap { + sb.WriteString(fmt.Sprintf("\tmap<%s, %s> %s = %d;\n", f.MapKey, f.MapValue, f.Name, f.Tag)) + } else { + sb.WriteString(fmt.Sprintf("\t%s %s = %d;\n", f.Type, f.Name, f.Tag)) + } + } + + // OneOf Blocks + for oneOfName, fields := range m.OneOfs { + sb.WriteString(fmt.Sprintf("\toneof %s {\n", oneOfName)) + for _, f := range fields { + sb.WriteString(fmt.Sprintf("\t\t%s %s = %d;\n", f.Type, f.Name, f.Tag)) + } + sb.WriteString("\t}\n") + } + + sb.WriteString("}\n") + + filePath := filepath.Join(outDir, m.Name+".proto") + return os.WriteFile(filePath, []byte(sb.String()), 0644) +} +``` + +--- + +## 8. Compliance with Repository Rules (`AGENTS.md`) + +- **Explicit Protocol Revision & Schema Integrity**: Reconstructing schemas directly from client assemblies prevents guesswork or arbitrary schema patching. +- **No Hardcoded Machine Paths**: Operates dynamically from CLI arguments and local files. +- **Zero Runtime Interference**: Operates strictly on text and source files without runtime injection or client hooks. diff --git a/docs/scratch/firefly_protoc_deep_dive.md b/docs/scratch/firefly_protoc_deep_dive.md new file mode 100644 index 0000000..b421ca1 --- /dev/null +++ b/docs/scratch/firefly_protoc_deep_dive.md @@ -0,0 +1,360 @@ +# รายงานการวิเคราะห์สถาปัตยกรรมและการทำงานของ FireflyProtoc (Deep-Dive Technical Specification) + +> **เอกสารอ้างอิงทางเทคนิคระดับลึกสำหรับวิศวกรซอฟต์แวร์และโมเดลปัญญาประดิษฐ์ (AI Systems)** +> วิเคราะห์จากซอร์สโค้ดของโครงการ: [gatokeeper/FireflyProtoc](https://github.com/gatokeeper/FireflyProtoc) +> ครอบคลุมสถาปัตยกรรมคอมไพเลอร์ Protobuf ยุคใหม่, การถอดรหัสความคลุมเครือของข้อมูล (Wire De-obfuscation), ระบบคณิตศาสตร์เชิงสัญลักษณ์ (Symbolic Expression Inverter), Meta Fields, Compile-Time Message Factory และรันไทม์ประสิทธิภาพสูง + +--- + +## สารบัญ (Table of Contents) +1. [บทนำและวัตถุประสงค์ของโครงการ (Introduction & Problem Statement)](#1-บทนำและวัตถุประสงค์ของโครงการ-introduction--problem-statement) +2. [สถาปัตยกรรมคอมไพเลอร์แบบ Multi-Tiered IR (Compiler Architecture)](#2-สถาปัตยกรรมคอมไพเลอร์แบบ-multi-tiered-ir-compiler-architecture) + - 2.1 ภาพรวมของไปป์ไลน์การคอมไพล์ (Frontend $\rightarrow$ IR $\rightarrow$ Backend) + - 2.2 โครงสร้างไวยากรณ์เสริม (Custom Syntax Extensions) +3. [ระบบคณิตศาสตร์เชิงสัญลักษณ์ (Symbolic Expression Inverter Engine)](#3-ระบบคณิตศาสตร์เชิงสัญลักษณ์-symbolic-expression-inverter-engine) + - 3.1 ปัญหาการพรางค่าข้อมูลบนสายส่ง (Wire Obfuscation / Masking) + - 3.2 อัลกอริทึมการแปลงนิพจน์ผกผันพีชคณิตอัตโนมัติ (Algebraic Inversion) +4. [ระบบ Meta Fields และ Message Factory แบบ Zero-Reflection](#4-ระบบ-meta-fields-และ-message-factory-แบบ-zero-reflection) + - 4.1 การฝัง Metadata (CmdId/Opcode) โดยไม่เสีย Overhead บนสายส่ง + - 4.2 การสร้าง Compile-Time Factory Jump Table (`IFactoryMessage`) +5. [การสร้างโค้ด C# และการปรับปรุงประสิทธิภาพ (CodeGen & Performance Optimizations)](#5-การสร้างโค้ด-c-และการปรับปรุงประสิทธิภาพ-codegen--performance-optimizations) +6. [รันไทม์ภายในของ Firefly (Firefly.Protobuf Runtime Architecture)](#6-รันไทม์ภายในของ-firefly-fireflyprotobuf-runtime-architecture) +7. [ตัวอย่างจริงจากการคอมไพล์ (Real-World Compilation Walkthrough)](#7-ตัวอย่างจริงจากการคอมไพล์-real-world-compilation-walkthrough) +8. [การนำไปประยุกต์ใช้ในระบบเซิร์ฟเวอร์เกม (Integration with HSR/Genshin Private Servers)](#8-การนำไปประยุกต์ใช้ในระบบเซิร์ฟเวอร์เกม-integration-with-hsrgenshin-private-servers) +9. [คู่มือวิศวกรรมสำหรับ AI ในการขยายขีดความสามารถ (AI Extension & Engineering Guide)](#9-คู่มือวิศวกรรมสำหรับ-ai-ในการขยายขีดความสามารถ-ai-extension--engineering-guide) + +--- + +## 1. บทนำและวัตถุประสงค์ของโครงการ (Introduction & Problem Statement) + +**FireflyProtoc** (พัฒนาโดย `gatokeeper`) คือคอมไพเลอร์ Protocol Buffers ทางเลือกที่ถูกออกแบบขึ้นมาโดยเฉพาะสำหรับ **Protocol วิศวกรรมย้อนกลับ (Reverse Engineering)** และ **เซิร์ฟเวอร์จำลองเกม (Game Emulators)** เช่น *Honkai: Star Rail*, *Genshin Impact*, และ *Zenless Zone Zero* + +### ข้อจำกัดของ `protoc` ดั้งเดิมของ Google: +1. **ไม่รองรับ Wire Data Masking / Obfuscation**: เกมค่าย HoYoverse มักจะพรางค่าฟิลด์ตัวเลขบนสายส่ง (Wire Format) ด้วยการทำ XOR, บวก, ลบ หรือบิตชิฟต์ เช่น ค่า ID จริงคือ `1001` แต่ในสายส่งกลับถูกเข้ารหัสเป็น `(1001 ^ 0x5A) + 7` คอมไพเลอร์ปกติของ Google ทำได้แค่อ่านค่าดิบ ทำให้วิศวกรต้องมาเขียนโค้ดแปลงค่าซ้ำซ้อนในทุก ๆ ฟิลด์ +2. **ไม่มีแนวคิดเรื่อง Packet Opcode (CmdId) ในตัว**: Protobuf มาตรฐานไม่มีช่องทางในการผูกหมายเลข Opcode เข้ากับ Class โดยตรง หากสร้างฟิลด์ธรรมดา ข้อมูล Opcode จะถูกส่งซ้ำซ้อนลงไปใน Payload สิ้นเปลืองแบนด์วิดท์ +3. **การสร้างอินสแตนซ์พึ่งพา Reflection หรือ Dictionary**: การแมป Opcode เป็น C# Object มักต้องใช้ Reflection หรือ Dictionary Lookup ขนาดใหญ่ ซึ่งช้าและไม่รองรับการทำ NativeAOT + +FireflyProtoc แก้ปัญหาทั้งหมดนี้ที่ **ระดับคอมไพเลอร์ (Compiler Level)** ทำให้ไฟล์ `.proto` มีความสามารถในการแปลงค่าพรางข้อมูลอัตโนมัติและสร้าง O(1) Factory สำเร็จรูปทันที + +--- + +## 2. สถาปัตยกรรมคอมไพเลอร์แบบ Multi-Tiered IR (Compiler Architecture) + +FireflyProtoc ถูกสร้างขึ้นด้วยสถาปัตยกรรมคอมไพเลอร์ระดับมืออาชีพ โดยแบ่งขั้นตอนการทำงานออกเป็น **3 ชั้น Intermediate Representation (IR)**: + +```mermaid +flowchart TD + ProtoFile[".proto Source Code
(พร้อมไวยากรณ์เสริม meta, [get: ...])"] --> Lexer["Lexer (Token Stream)"] + Lexer --> Parser["Parser (AST: Node / Decl / Expr)"] + Parser --> TypeChecker["TypeChecker (Semantic Validation)"] + + subgraph IR_Pipeline["Multi-Tiered IR Lowering"] + TypeChecker --> HIR["HIR Builder
(High-Level IR: Messages, Enums, Scopes)"] + HIR --> MIR["MIR Builder
(Mid-Level IR: Type Resolution, MessageFactory, PascalCase)"] + MIR --> LIR["LIR Builder
(Low-Level IR: Classes, Methods, Body, Byte Stmts)"] + end + + LIR --> CodeGen["CSharpGenerator (Parallel.ForEach File Emit)"] + CodeGen --> CSharpFiles["Generated High-Performance C# Files
(Zero Reflection, AggressiveInlining, NativeAOT)"] +``` + +### 2.1 หน้าที่ของแต่ละเลเยอร์ในไปป์ไลน์: +1. **AST (Abstract Syntax Tree)**: + - แปลงไวยากรณ์ข้อความเป็นโครงสร้างข้อมูลเบื้องต้น (`Decl.Message`, `Decl.Field`, `Decl.Enum`, `Expr.Binary`) +2. **HIR (High-Level Intermediate Representation)**: + - รวบรวม Scope และ Namespace ของไฟล์ + - จัดระเบียบความสัมพันธ์ของ Message ซ้อน Message (Nested Types) + - สกัดนิพจน์ `[get: ...]` และ `[set: ...]` เข้าสู่ `AttributeSet` +3. **MIR (Mid-Level Intermediate Representation)**: + - แมปประเภทข้อมูลของ Protobuf ไปเป็นประเภทข้อมูล C# (`MirTypeRef`) + - แปลงชื่อแบบ snake_case เป็น PascalCase + - รวบรวมฟิลด์ที่เป็น `meta` ที่ตรงกับคำสั่ง `option message_factory` เพื่อเตรียมสร้าง Factory Class +4. **LIR (Low-Level Intermediate Representation)**: + - กำหนดโครงสร้างโค้ดระดับล่าง: Constructors, Property Getters/Setters, ฟิลด์ Backing Field (`_Field`), เมธอด `WriteTo`, `MergeFrom`, `CalculateSize`, `Clone`, และ Switch-Case ใน `MessageFactory.Create` +5. **CodeGen (C# Code Generator)**: + - แปลง LIR ออกมาเป็นโค้ด C# ข้อความจริง โดยรันกระบวนการเขียนไฟล์แบบคู่ขนาน (`Parallel.ForEach`) + +### 2.2 โครงสร้างไวยากรณ์เสริม (Custom Syntax Extensions) + +| ไวยากรณ์ | รูปแบบการเขียน | ผลลัพธ์ที่คอมไพเลอร์สร้างให้ | +|---|---|---| +| **Meta Field** | `meta int32 cmd_id = 1;` | สร้างค่าคงที่และ Getter ในคลาส **แต่จะไม่ถูก Serialize ออกทางสายส่ง** และสามารถใช้ Tag ชนกับฟิลด์ธรรมดาได้ | +| **Masked Field** | `[get: (id ^ 1) + 2] int32 id = 3;` | ตัวแปร `Id` ภายนอกจะมองเห็นค่าจริง แต่ค่าที่ถูกส่ง/รับบนสายส่งจะผ่านสูตรคณิตศาสตร์ผกผัน | +| **Message Factory** | `option message_factory = int32 cmd_id;` | สร้างคลาส `MessageFactory` พร้อมเมธอด `Create(int cmd_id)` คืนค่าอินสแตนซ์ของ Message นั้นทันที | + +--- + +## 3. ระบบคณิตศาสตร์เชิงสัญลักษณ์ (Symbolic Expression Inverter Engine) + +จุดที่อัจฉริยะที่สุดของ FireflyProtoc คือคลาส **`ExpressionInverter.cs`** ซึ่งทำหน้าที่เป็น Symbolic Algebra Solver ในตัวเอง + +### 3.1 ปัญหาการพรางค่าข้อมูลบนสายส่ง (Wire Obfuscation / Masking) +สมมุติว่าในเกม ฟิลด์ `hp` มีสูตรการพรางข้อมูลทางสายส่งคือ: +$$\text{WireValue} = (\text{LogicalHP} \oplus 1) + 2$$ + +เมื่อเขียนโค้ดฝั่งอ่าน (Deserialization / `MergeFrom`), เราจำเป็นต้องถอดรหัสค่าจากสายส่งกลับมาเป็นค่าจริง: +$$\text{LogicalHP} = (\text{WireValue} - 2) \oplus 1$$ + +หากใช้คอมไพเลอร์ทั่วไป ผู้พัฒนาจะต้องมานั่งคำนวณและเขียนสูตรทั้งขาไป (`get`) และขากลับ (`set`) ด้วยตัวเอง หากมีฟิลด์แบบนี้เป็นร้อยๆ ฟิลด์ จะเกิดข้อผิดพลาดได้ง่ายมาก + +### 3.2 อัลกอริทึมการแปลงนิพจน์ผกผันพีชคณิตอัตโนมัติ (Algebraic Inversion) +ใน FireflyProtoc ผู้ใช้สามารถเขียนเพียงขาเดียว: +```protobuf +[get: (id ^ 1) + 2] int32 id = 3; +``` +`ExpressionInverter` จะทำการคลายโหนด (Unwind) ย้อนกลับจาก Root ของ AST Expression Tree: + +```mermaid +flowchart TD + Input["Input: (id ^ 1) + 2"] --> Root["Root Node: Operator (+) with Constant (2)"] + Root --> InvertAdd["Invert (+) to (-)
New Accumulator: (WireValue - 2)"] + InvertAdd --> NextNode["Next Node: Operator (^) with Constant (1)"] + NextNode --> InvertXor["Invert (^) to (^)
Self-inverse Operator
New Accumulator: ((WireValue - 2) ^ 1)"] + InvertXor --> ReachTarget["Reached Target Identifier 'id'"] + ReachTarget --> Output["Setter Expression: value - 2 ^ 1"] +``` + +#### ตารางการสลับตัวดำเนินการพีชคณิต (`ExpressionInverter.cs`): +- บวก ($+$) $\Longleftrightarrow$ ลบ ($-$) +- คูณ ($*$) $\Longleftrightarrow$ หาร ($/$) +- บิตไวส์ XOR ($\oplus$) $\Longleftrightarrow$ บิตไวส์ XOR ($\oplus$) *(XOR เป็น Self-Inverse Operator: $A \oplus B \oplus B = A$)* +- ลบเอกภาพ (Unary $-$) $\Longleftrightarrow$ ลบเอกภาพ (Unary $-$) + +--- + +## 4. ระบบ Meta Fields และ Message Factory แบบ Zero-Reflection + +### 4.1 การฝัง Metadata (CmdId/Opcode) โดยไม่เสีย Overhead บนสายส่ง +ในไฟล์ `.proto` สามารถกำหนดฟิลด์เป็น `meta`: +```protobuf +message PlayerLoginCsReq { + meta int32 cmd_id = 11; + string token = 1; +} +``` +เมื่อคอมไพล์: +- `cmd_id` จะไม่ถูกนับรวมใน `CalculateSize()` +- `cmd_id` จะไม่ถูกเขียนลง `WriteTo(CodedOutputStream output)` +- `cmd_id` จะไม่ถูกอ่านใน `MergeFrom(CodedInputStream input)` +- คลาสจะมีเมธอด: + ```csharp + public const int CmdId = 11; + public int GetCmdId() => CmdId; + ``` + +### 4.2 การสร้าง Compile-Time Factory Jump Table (`IFactoryMessage`) +เมื่อกำหนด `option message_factory = int32 cmd_id;` ที่หัวไฟล์ `.proto` +คอมไพเลอร์จะรวบรวมทุก Message ที่มีฟิลด์ `meta int32 cmd_id = X;` แล้วสร้างไฟล์ `MessageFactory.cs`: + +```csharp +public static class MessageFactory +{ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static IFactoryMessage Create(int cmdId) + { + switch (cmdId) + { + case 11: + return new PlayerLoginCsReq(); + case 12: + return new PlayerLoginScRsp(); + default: + return null; + } + } +} +``` + +> [!TIP] +> **ความได้เปรียบระดับสูงด้านประสิทธิภาพ:** +> การสร้าง Switch-Case สำหรับ Type Instantiation ในขั้นตอน Compile-Time ทำให้คอมไพลเลอร์ C# และ JIT/AOT สามารถปรับให้เป็น **Jump Table (O(1) Direct Branching)** ในระดับ Assembly CPU Instruction ได้โดยตรง ปราศจากการทำ Boxing/Unboxing, แฮชแมป หรือ Reflection ใดๆ ทั้งสิ้น + +--- + +## 5. การสร้างโค้ด C# และการปรับปรุงประสิทธิภาพ (CodeGen & Performance Optimizations) + +โค้ด C# ที่ FireflyProtoc สร้างขึ้นมานั้น มีการใส่ Attribute พิเศษเพื่อรีดประสิทธิภาพฮาร์ดแวร์สูงสุด: + +1. **`[MethodImpl(MethodImplOptions.AggressiveInlining)]`**: + - บังคับให้ JIT และ NativeAOT นำโค้ดของเมธอด (เช่น Getter/Setter, Serialization routines) ไปวางแทนที่จุดเรียกโดยตรง (Inline Expansion) เพื่อตัดค่าใช้จ่ายในการ Call Stack ของ CPU +2. **`[DebuggerNonUserCode]`**: + - ป้องกันไม่ให้ Debugger กระโดดเข้าไปในโค้ดที่สร้างขึ้นโดยอัตโนมัติ เพิ่มความสะดวกในการดีบักระบบของผู้พัฒนา +3. **Partial Class Architecture (`public partial class`)**: + - คลาสที่สร้างขึ้นทั้งหมดเป็น `partial class` ทำให้ผู้พัฒนาสามารถเขียนโค้ดส่วนขยาย (Extension Logic, Custom Operators, Helper Methods) แยกไฟล์ได้ โดยที่โค้ดจะไม่สูญหายเมื่อคอมไพล์ `.proto` ใหม่ +4. **Zero-Allocation Stack Buffer ใน Varint Encoding**: + - เมธอดสำหรับเขียนตัวเลข Varint ใช้ `Span buffer = stackalloc byte[10];` บน Stack Memory ล้วนๆ ไม่มีการจอง Heap ใดๆ + +--- + +## 6. รันไทม์ภายในของ Firefly (Firefly.Protobuf Runtime Architecture) + +FireflyProtoc ไม่ได้พึ่งพาไลบรารี `Google.Protobuf.dll` แต่มาพร้อมรันไทม์ขนาดเล็กของตัวเองชื่อ **`Firefly.Protobuf`** ซึ่งอยู่ในโฟลเดอร์ `runtime/`: + +```mermaid +classDiagram + class IMessage { + <> + +CalculateSize() int + +MergeFrom(CodedInputStream input) + +WriteTo(CodedOutputStream output) + +ToByteArray() byte[] + } + + class IFactoryMessage { + <> + } + + class CodedInputStream { + -byte[] m_Buffer + -int m_Position + +ReadTag() int + +ReadString() string + +ReadInt32() int + +ReadUInt32() uint + +ReadBytes() ByteString + } + + class CodedOutputStream { + -Stream m_Stream + +WriteRawTag(int tag) + +WriteString(string value) + +WriteInt32(int value) + +WriteUInt32(uint value) + } + + class MessageParser~T~ { + +ParseFrom(byte[] data) T + +ParseFrom(CodedInputStream input) T + } + + IMessage <|.. IFactoryMessage +``` + +### รายละเอียดส่วนประกอบหลักของรันไทม์: +- **`CodedInputStream` / `CodedOutputStream`**: ควบคุมการอ่านและเขียนข้อมูลระดับบิตและไบต์ตามมาตรฐาน Wire Type (Varint, Fixed32, Fixed64, Length-Delimited) +- **`ByteString`**: ตัวแทนของ Immutable Byte Array สำหรับฟิลด์ประเภท `bytes` +- **`RepeatedField`**: คอลเลกชันสำหรับฟิลด์ซ้ำ (`repeated`) ที่ปรับแต่งให้ขยายบัฟเฟอร์อย่างมีประสิทธิภาพ +- **`MapField`**: โครงสร้างข้อมูล Dictionary ประสิทธิภาพสูงสำหรับฟิลด์ประเภท `map` + +--- + +## 7. ตัวอย่างจริงจากการคอมไพล์ (Real-World Compilation Walkthrough) + +### 7.1 โค้ดต้นฉบับ Protobuf (`example.proto`): +```protobuf +syntax = "proto3"; + +option csharp_namespace = "FireflyProtoc.Example"; +option message_factory = int32 department_id; + +message Person { + meta int32 department_id = 1; + string name = 1; + string email = 2; + [get: (id ^ 1) + 2] int32 id = 3; +} +``` + +### 7.2 โค้ด C# ที่คอมไพล์ได้จริง (`Person.cs`): +```csharp +// Generated by FireflyProtoc! do not modify! +#pragma warning disable CS8618 + +namespace FireflyProtoc.Example; + +[global::System.Diagnostics.DebuggerNonUserCode] +public partial class Person : global::Firefly.Protobuf.IMessage, global::Firefly.Protobuf.IFactoryMessage +{ + public const global::System.Int32 DepartmentId = 1; + public const global::System.Int32 NameFieldNumber = 1; + public const global::System.Int32 EmailFieldNumber = 2; + public const global::System.Int32 IdFieldNumber = 3; + + private global::System.Int32 _Id; + + [global::System.Diagnostics.DebuggerNonUserCode] + public global::System.Int32 Id + { + [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + get + { + return (_Id ^ 1) + 2; // คำนวณค่าเพื่อส่งออก + } + [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + set + { + _Id = value - 2 ^ 1; // คำนวณค่าย้อนกลับอัตโนมัติจากการ Invert นิพจน์! + } + } + + [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + public global::System.Int32 GetDepartmentId() + { + return DepartmentId; // Meta Field คืนค่าได้ แต่ไม่ถูกเขียนลง Wire + } + + public void WriteTo(global::Firefly.Protobuf.CodedOutputStream output) + { + if (Name != default) + { + output.WriteRawTag(10); + output.WriteString(_Name); + } + if (Email != default) + { + output.WriteRawTag(18); + output.WriteString(_Email); + } + if (Id != default) + { + output.WriteRawTag(24); + output.WriteInt32(_Id); // ส่งค่าดิบที่ผ่านการพรางแล้ว + } + } +} +``` + +--- + +## 8. การนำไปประยุกต์ใช้ในระบบเซิร์ฟเวอร์เกม (Integration with HSR/Genshin Private Servers) + +ในสถาปัตยกรรมเซิร์ฟเวอร์ เช่น **GaC-hkrpg-go** หรือ **March7thHoney**: + +```mermaid +sequenceDiagram + autonumber + actor Client as Game Client (Obfuscated Traffic) + participant KCP as Server KCP Layer + participant Factory as MessageFactory.Create(cmdId) + participant Proto as Generated IFactoryMessage + + Client->>KCP: UDP Packet (CmdId: 11, Obfuscated Payload) + KCP->>Factory: MessageFactory.Create(11) [O(1) Switch Jump] + Factory-->>KCP: Return new PlayerGetTokenCsReq() + KCP->>Proto: message.MergeFrom(stream) + Note over Proto: ฟิลด์ที่พรางค่าจะถูกคำนวณย้อนกลับเป็นค่าจริงอัตโนมัติใน Setter! + KCP->>ServerHandler: Handler.Handle(session, message) + Note over ServerHandler: Server ใช้งานฟิลด์ได้ตามปกติโดยไม่ต้องรู้สูตรพรางข้อมูล! +``` + +1. **ขจัด Boilerplate Code**: เซิร์ฟเวอร์ไม่จำเป็นต้องมีโค้ดแปลงค่า (Converter Functions) นับร้อยจุด เพราะการพรางและถอดรหัสถูกฝังอยู่ในตัว Class ของ Protobuf โดยตรง +2. **ความเข้ากันได้กับโปรโตคอลหลายเวอร์ชัน**: สามารถกำหนดสูตรการพรางค่าที่แตกต่างกันในแต่ละแพตช์ได้โดยเพียงแค่แก้สัญลักษณ์ในไฟล์ `.proto` +3. **การทำงานร่วมกับระบบ Router**: สามารถเชื่อมโยง Opcode Dispatcher เข้ากับ `MessageFactory.Create(cmdId)` ได้ทันที + +--- + +## 9. คู่มือวิศวกรรมสำหรับ AI ในการขยายขีดความสามารถ (AI Extension & Engineering Guide) + +หากโมเดล AI อื่นต้องการปรับแต่งหรือขยายขีดความสามารถของ FireflyProtoc ต่อไป: + +1. **ข้อควรระวังเรื่อง CLI Output Path**: + ใน `src/FireflyProtoc.cs` บรรทัดที่ 21 มีการฮาร์ดโค้ด `$"./{outputFolder}"` + หากส่ง Path แบบ Absolute (เช่น `C:\Users\...`) จะทำให้ระบบพาธขัดข้อง (`./C:\...`) + **คำแนะนำในการแก้:** เปลี่ยนให้ใช้ `Path.GetFullPath(outputFolder)` หรือตรวจสอบ `Path.IsPathRooted(outputFolder)` ก่อนต่อสตริง +2. **การคอมไพล์ในสภาพแวดล้อม .NET 10**: + ไฟล์ `FireflyProtoc.csproj` มีการเปิดแฟล็ก `true` สำหรับ `net9.0` ซึ่งหากรันคำสั่ง `dotnet run` ภายใต้ .NET 10 SDK จะเกิดข้อผิดพลาดในการหา ILCompiler Targets + **คำแนะนำในการรัน:** ให้ส่งแฟล็ก `-c Release -p:PublishAot=false` ขณะทดสอบ หรือปรับ `` ให้ตรงกับเวอร์ชันของ SDK ที่ติดตั้ง +3. **การเพิ่มตัวดำเนินการพรางข้อมูล (Bitshift, Modulo)**: + หากต้องการให้รองรับการเลื่อนบิต (`<<`, `>>`) หรือเศษเหลือ (`%`) ให้ไปเพิ่ม Mapping ผกผันใน `InvertWithRight` และ `InvertWithLeft` ของ `src/Semantic/ExpressionInverter.cs`: + - `<<` ผกผันด้วย `>>` + - `>>` ผกผันด้วย `<<` diff --git a/docs/scratch/firefly_static_parser_deep_dive.md b/docs/scratch/firefly_static_parser_deep_dive.md new file mode 100644 index 0000000..936255a --- /dev/null +++ b/docs/scratch/firefly_static_parser_deep_dive.md @@ -0,0 +1,344 @@ +# Firefly Static Parser Architectural Specification & Cryptographic Deep Dive + +This technical specification provides an exhaustive, code-level analysis of **`Firefly-Static-Parser`** (by AzenKain), an offline, static reverse-engineering framework and IL2CPP metadata extractor implemented in Rust. Unlike in-process dynamic dumpers (such as `honkai-dumper`), `Firefly-Static-Parser` operates **completely offline** without launching, injecting, or hooking into the game client process. + +It achieves this by completely reverse-engineering the proprietary metadata container format (`MHY\0`), the embedded layout tables in `GameAssembly.dll`, dynamic semantic XOR key derivation, and non-linear mathematical scrambling algorithms used in *Honkai: Star Rail*. + +--- + +## 1. Executive Summary & Architectural Paradigm + +### 1.1 The Challenge of Custom IL2CPP Obfuscation +Standard Unity IL2CPP uses known, public data structures: +- `global-metadata.dat` begins with magic `0xFAB11BAF` (version 29+). +- Metadata definitions (TypeDef, MethodDef, FieldDef) are stored sequentially in unencrypted flat tables. + +In *Honkai: Star Rail* and similar protected titles: +1. **Container Replaced**: `global-metadata.dat` begins with custom magic `MHY\0` (`0x0059484D`). +2. **Split Metadata**: Metadata ranges and image mappings are split between `global-metadata.dat` and `startup-metadata.dat`. +3. **Embedded PE Headers**: The true table offsets, sizes, and field layout descriptors are embedded inside `GameAssembly.dll` and encrypted with arithmetic displacements and bitwise XOR masks. +4. **Scrambled Field/Method Entries**: Entries are not sequential; attributes, indices, and names are encrypted with non-linear hash/PRNG functions unique to each table. +5. **Seeded Stream Cipher on Strings**: Type names and string literals are packed into bitfields and encrypted with a QWORD running stream cipher. + +### 1.2 The Two Dumping Paradigms + +``` ++----------------------------------------------------------------------------------------------------+ +| IN-PROCESS vs. OFFLINE PARSER | ++----------------------------------------------------------------------------------------------------+ +| | +| [ In-Process Dumper: honkai-dumper ] | +| Injected DLL -> Waits 10s -> Lets game unpacker decrypt metadata -> Scans UnityPlayer.dll | +| -> Calls runtime C-API pointers in RAM -> Dumps memory objects | +| * Advantage: Simple implementation, relies on game's own decryption routines. | +| * Disadvantage: Requires running game process, triggers anti-cheat/integrity monitors. | +| | +| [ Offline Static Parser: Firefly-Static-Parser ] | +| Direct disk analysis: GameAssembly.dll + global-metadata.dat + startup-metadata.dat | +| 1. Parses PE64 headers -> Scans byte patterns for metadata initializers | +| 2. Solves arithmetic/XOR masks -> Resolves table offsets & payload boundaries | +| 3. Dynamic Semantic Key Discovery -> Derives XOR keys via CLR invariants (, ValueType) | +| 4. Decrypts QWORD string cipher & PRNG-scrambled method/field definitions | +| 5. Reconstructs TypeDef AST -> Emits dump.cs & methods.json | +| * Advantage: Zero process injection, zero anti-cheat risk, pure deterministic static analysis. | +| * Disadvantage: High mathematical and reverse-engineering complexity. | +| | ++----------------------------------------------------------------------------------------------------+ +``` + +--- + +## 2. Codebase Architecture & File Organization + +The project is structured in Rust (2024 Edition) with a modular pipeline separating PE analysis, metadata loading, mathematical key derivation, and AST emission. + +``` +Firefly-Static-Parser/ +├── Cargo.toml # anyhow, serde, serde_json, windows-sys, iced-x86, il2cpp_dumper +├── src/ +│ ├── main.rs # CLI entry point -> app::run() +│ ├── app.rs # Input routing (Standard vs MHY custom metadata branch) +│ ├── args.rs # CLI arguments & default path resolvers +│ ├── config.rs # Dumper configuration flags +│ ├── pe_image.rs # Zero-dependency PE64 section parser, RVA mapper, pattern scanner +│ ├── static_layout.rs # Signature scanner, PE layout solver, arithmetic/XOR mask decoders +│ ├── metadata.rs # Magic validator (0xFAB11BAF vs b"MHY\0") +│ ├── obfuscated.rs # Fallback in-process loader via GameAssembly.dll custom loader RVA +│ ├── il2cpp_pe.rs # Standard IL2CPP symbol/code registration loader +│ ├── output.rs # Standard dumper output generator +│ └── layout_model/ # The custom MHY layout reconstruction engine +│ ├── mod.rs # LayoutImage, LayoutTypeDef, LayoutMethod, LayoutField AST models +│ ├── output.rs # Static dump.cs & methods.json serialization +│ └── parser/ +│ ├── mod.rs # Dynamic Semantic Key Discovery & PRNG scramble formulas +│ ├── loader.rs # Dynamic scanner for il2cpp_type_table & method_pointer_table +│ └── reader.rs # QWORD string stream cipher, method/field decoders, generic solver +``` + +--- + +## 3. PE Layout & Global Discovery Engine (`static_layout.rs`) + +To parse the custom metadata container, the parser must first extract layout descriptors embedded within `GameAssembly.dll`. + +### 3.1 Pattern Scanning for Initializers +`Firefly-Static-Parser` scans the `.text` section of `GameAssembly.dll` for two critical initialization sequences: + +1. **`static_metadata_initializer_pattern`**: + The routine that initializes the IL2CPP static metadata globals. From this function, RIP-relative `lea` instructions are decoded to extract: + - `metadata_registration_rva` + - `descriptor_rva` + - `metadata_cache_rva` + - `embedded_header_rva` + +2. **`metadata_payload_initializer_pattern`**: + The routine responsible for loading the external data files. It validates string references: + ```rust + rva_has_c_string(image, global_name_rva, b"global-metadata.dat") + rva_has_c_string(image, startup_name_rva, b"startup-metadata.dat") + ``` + Extracts `payload_offset` (typically `0x00` or file header length). + +### 3.2 Decryption of Embedded Header Offsets +The table offsets in the embedded header are obfuscated using combinations of 32-bit XOR keys, bitwise arithmetic shifts (`sar_u32`), and wrapping modular additions. + +```rust +// Discovered from embedded_header_rva in GameAssembly.dll +let descriptor_count_80 = image.read_u32_rva(descriptor_rva + 0x80)? ^ 0x69BD_97F1; +let header_size_1a8 = image.read_u32_rva(header + 0x1A8)? ^ 0x729B_1A9E; +let header_size_1f8 = image.read_u32_rva(header + 0x1F8)? ^ 0x1608_C2C8; +let header_size_74 = image.read_u32_rva(header + 0x74)? ^ 0x080E_F250; +let header_count_134 = (image.read_u32_rva(header + 0x134)? ^ 0x1021_0728) / 40; +let header_count_178 = sar_u32(image.read_u32_rva(header + 0x178)?, 4) ^ 0x05CC_1AE1; +let header_count_bc = sar_u32(image.read_u32_rva(header + 0xBC)?, 3) ^ 0x079F_C2EC; +``` + +#### Complete Table Offsets Mapping: + +| Offset In Header | Operation | Key / Operand | Output Field in `StaticLayout` | +| :---: | :---: | :---: | :--- | +| `+0x164` | `XOR` | `0x7F5C_5934` | `startup_section_164_offset` | +| `+0x150` | `wrapping_add` | `0xD882_615E` | `startup_image_table_offset` | +| `+0x158` | `wrapping_add` | `0xE03E_EAC1` | `startup_assembly_table_offset` | +| `+0xD0` | `wrapping_add` | `0xDFCF_C6B0` | `startup_assembly_name_table_offset` | +| `+0x84` | `XOR` | `0x6853_1D3F` | `global_type_table_offset` | +| `+0x20` | `wrapping_add` | `0xB2FC_E189` | `global_field_table_offset` | +| `+0x40` | `XOR` | `0x3C68_5CDB` | `global_property_table_offset` | +| `+0x180` | `wrapping_add` | `0xE4DB_E763` | `global_type_field_offset_map_offset` | +| `+0x9C` | `wrapping_add` | `0xBC7E_C9D7` | `global_field_offset_group_table_offset` | +| `+0x148` | `XOR` | `0x329E_1172` | `global_field_offset_table_offset` | +| `+0x14C` | `wrapping_add` | `0xF3A0_4294` | `global_method_table_offset` | +| `+0x30` | `wrapping_add` | `0xDCF5_FDBE` | `global_parameter_table_offset` | +| `+0x12C` | `XOR` | `0x26F0_FC20` | `global_nested_type_table_offset` | +| `+0x1C8` | `XOR` | `0x042F_9275` | `global_interface_type_table_offset` | +| `+0xF0` | `wrapping_add` | `0x9D48_920F` | `global_generic_container_table_offset` | +| `+0x140` | `wrapping_add` | `0xDAAC_7309` | `global_generic_parameter_table_offset` | +| `+0x38` | `wrapping_add` | `0xF778_F1AB` | `global_metadata_usage_type_table_offset` | +| `+0x1D0` | `wrapping_add` | `0xC9A6_64A5` | `global_metadata_usage_range_offset` | +| `+0x190` | `wrapping_add` | `0xED79_1073` | `global_metadata_usage_pair_offset` | +| `+0x1F0` | `XOR` | `0x56C7_D20D` | `global_string_literal_table_offset` | +| `+0x08` | `wrapping_add` | `0xF711_4E59` | `global_string_literal_data_offset` | +| `+0x1FC` | `XOR` | `0x6238_CDB0` | `global_field_default_value_table_offset` | +| `+0x3C` | `wrapping_add` | `0x978B_E7A5` | `global_field_default_value_data_offset` | +| `+0x1DC` | `XOR / 12` | `0x3E0C_72F0` | `field_default_value_count` | +| `+0x1B4` | `wrapping_add` | `0x8D43_A4EE` | `global_string_data_offset` | + +--- + +## 4. Dynamic Semantic Key Derivation (`layout_model/parser/mod.rs`) + +A major breakthrough in `Firefly-Static-Parser` is how it discovers runtime XOR keys and type sentinels **without hardcoding version-specific constants**. + +### 4.1 The Semantic Invariant Technique +In every valid .NET/IL2CPP metadata image, standard CLI core types always exhibit known semantic invariants: +1. **TypeDef 0 is ``**: + - Has no base type $\rightarrow$ raw base value gives `type_base_none`. + - Has known attribute flags $\rightarrow$ `raw_flags ^ 0x700` reveals `type_attribute_key`. +2. **`System.ValueType`**: + - Inherits directly from `System.Object`. Therefore, the raw base type of `System.ValueType` reveals `type_base_object`. +3. **`System.Enum`**: + - Inherits directly from `System.ValueType`. Its raw base type reveals `type_base_value_type`. +4. **`System.AttributeTargets`**: + - Is an Enum. Its raw base type reveals `type_base_enum`. +5. **Parent Type Index Formula**: + $$\text{type\_parent\_key} = \text{type\_base\_object} - 1$$ + $$\text{ParentIndex} = \text{raw\_base\_type} - \text{type\_parent\_key}$$ + +### 4.2 Code Implementation +```rust +fn discover_type_layout_keys(layout: &mut StaticLayout, global_data: &[u8]) -> Result<()> { + // 1. Module Sentinel & Attribute Key + let type_table_offset = layout.payload_offset as usize + layout.global_type_table_offset as usize; + let module_name = decode_type_name_at(global_data, layout, type_table_offset)?.1; + assert_eq!(module_name, ""); + + layout.keys.type_base_none = read_u32(global_data, type_table_offset + 0x04)?; + layout.keys.type_attribute_key = read_u32(global_data, type_table_offset + 0x14)? ^ 0x700; + + // 2. Scan Type Definitions for Standard BCL Anchors + for type_index in 0..scan_count { + let entry_offset = type_entry_offset_raw(layout, type_index).unwrap(); + let raw_base = read_u32(global_data, entry_offset + 0x04)?; + let (namespace, name) = decode_type_name_at(global_data, layout, entry_offset)?; + + match (namespace.as_str(), name.as_str()) { + ("System", "ValueType") => layout.keys.type_base_object = raw_base, + ("System", "Enum") => layout.keys.type_base_value_type = raw_base, + ("System", "AttributeTargets") => layout.keys.type_base_enum = raw_base, + _ => {} + } + } + + layout.keys.type_parent_key = layout.keys.type_base_object.wrapping_sub(1); + Ok(()) +} +``` + +--- + +## 5. Decryption Algorithms & Mathematical Models + +### 5.1 The QWORD String Stream Cipher (`decode_string_raw`) +In the `MHY\0` container, strings are not null-terminated C-strings. String pointers are 32-bit composite integers encoding both length and offset. + +#### 1. Composite Index Bitfield Decoding: +```rust +let signed_index = index as i32; +let (length, string_offset_mask) = if signed_index < 0 { + // Long String Format (up to 255 bytes, 23-bit offset) + (((index >> 23) & 0xFF) as usize, 0x007F_FFFF) +} else { + // Short String Format (up to 63 bytes, 25-bit offset) + (((index >> 25) & 0x3F) as usize, 0x01FF_FFFF) +}; +let string_offset = (index & string_offset_mask) as usize; +``` + +#### 2. Seeded Stream Decryption: +Strings are encrypted in 8-byte QWORD blocks. The key is dynamically computed per string offset: + +$$K_0 = \text{seed\_add} + (\text{seed\_mul} \times \text{string\_offset})$$ + +For each 8-byte chunk $i \in [0, \lceil \text{length} / 8 \rceil)$: + +$$P_i = C_i \oplus K_i$$ + +$$K_{i+1} = K_i + \text{increment}$$ + +```rust +let mut key = string_payload_key(layout, string_offset); +let mut out = Vec::with_capacity(qword_count * 8); +for chunk_index in 0..qword_count { + let encrypted = read_u64(global_data, data_offset + chunk_index * 8)?; + out.extend_from_slice(&(encrypted ^ key).to_le_bytes()); + key = key.wrapping_add(string_payload_increment(layout)); +} +out.truncate(length); +``` + +### 5.2 Non-Linear PRNG Scrambling Functions + +Every table entry in the metadata layout is obfuscated by non-linear integer hashing functions: + +#### 1. Image Key (`image_key`): +$$K_{\text{img}}(i) = ((((i \times \mathtt{0xE07C}) \oplus \mathtt{0x7538159E}) \times \mathtt{0x120D0703}) \oplus \mathtt{0x6032C9D3}) + \mathtt{0x2EBB0085}$$ + +#### 2. Method Key (`method_key`): +$$v_1 = (i \times \mathtt{0x31E1}) \oplus \mathtt{0x33914937}$$ +$$v_2 = ((v_1 \times \mathtt{0x2C03F17D}) \gg 23) \times \mathtt{0x540CC9F4} \gg 21$$ +$$K_{\text{method}}(i) = (v_2 \text{ as u32}) + \mathtt{0x71BC7861}$$ + +#### 3. Parameter Key (`parameter_key`): +$$v = ((i \times \mathtt{0x072E1D74B12B} + \mathtt{0x01911D05AFF5}) \gg 11) \text{ as u32}$$ +$$K_{\text{param}}(i) = (v \times \mathtt{0x58B870A2}) + \mathtt{0x83CF7B44}$$ + +#### 4. Field Key (`field_key`): +$$K_{\text{field}}(\text{start}, j) = \mathtt{0xAD416BB9} - (\text{start} \times \mathtt{0x2C5DCB00}) + (j \times \mathtt{0xD3A23500})$$ + +#### 5. Generic Container Key (`generic_container_key`): +$$v_1 = (i \times \mathtt{0x3D6913E0AF40} + \mathtt{0x0A64CAD60FA052C0}) \gg 23$$ +$$v_2 = ((v_1 \times \mathtt{0x770E3FE8}) \gg 11) \times \mathtt{0x2C9A0EA3} \gg 23$$ +$$K_{\text{gen\_container}}(i) = v_2 \text{ as u32}$$ + +--- + +## 6. Type System Reconstruction & Output Generation + +### 6.1 Dynamic Table Resolution in `GameAssembly.dll` +In addition to metadata parsing, method RVAs require locating the compiled function pointers. In `src/layout_model/parser/loader.rs`: +- **`il2cpp_type_table`**: Scans `GameAssembly.dll` for continuous 16-byte descriptors where `kind` $\in (0, 0x50)$ and `byref` $\in \{0, 1, 0x80, 0x81\}$. +- **`method_pointer_table`**: Scans for continuous runs of 64-bit pointers residing within the `.text` segment ($ImageBase + 0x1000 \le VA < ImageBase + Size$). +- **`generic_inst_table`**: Scans for runs of `argc` ($0 < argc < 30$) with 32-bit zero padding and valid pointer prefixes (`0x01`, `0x00`, `0x7F`). + +### 6.2 Output Formats + +#### 1. Reconstructed C# Source (`dump.cs`) +Outputs complete class declarations with namespaces, type index comments, attributes, and field offsets: + +```csharp +// FirelfyShelter static asm parse + +// Image 0: Assembly-CSharp.dll + +// Namespace: RPG.Client +// TypeDefIndex: 1420 +[Serializable] +public class AutoScrollRect : ScrollRect +{ + // Fields + private Vector3[] KOIAJFFGPGF; // 0x180 + + // Methods + // RVA: 0x1febdf0 VA: 0x181febdf0 + public void .ctor() { } + + // RVA: 0x1feb700 VA: 0x181feb700 + private void MNOGNALMGPL(AnimatorButton target) { } + + // RVA: 0x1feb9c0 VA: 0x181feb9c0 + public void SnapTo(GameObject target) { } +} +``` + +#### 2. JSON Method Map (`methods.json`) +Outputs canonical method signatures mapped to hexadecimal RVAs: + +```json +{ + "RPG.Client.AutoScrollRect::.ctor()": "0x1febdf0", + "RPG.Client.AutoScrollRect::MNOGNALMGPL(RPG.Client.AnimatorButton)": "0x1feb700", + "RPG.Client.AutoScrollRect::POFLDHKMLAF()": "0x1feb7a0", + "RPG.Client.AutoScrollRect::SetItemSelectCallback()": "0x1feb7f0", + "RPG.Client.AutoScrollRect::SnapTo(UnityEngine.GameObject)": "0x1feb9c0" +} +``` + +--- + +## 7. Comparative Analysis Matrix + +| Feature | `Firefly-Static-Parser` | `honkai-dumper` | Standard `Il2CppDumper` | +| :--- | :--- | :--- | :--- | +| **Execution Mode** | **Offline CLI** | **In-Process DLL** | **Offline CLI** | +| **Target Files** | `GameAssembly.dll`
`global-metadata.dat`
`startup-metadata.dat` | Running `StarRail.exe` process | `GameAssembly.dll`
`global-metadata.dat` | +| **`MHY\0` Container Decryption** | **Native Built-in** (Algebraic & QWORD stream cipher) | **Delegated** to game process | **Unsupported** (Fails on magic mismatch) | +| **Anti-Cheat Exposure** | **Zero** | High (Process injection) | **Zero** | +| **Key Extraction** | **Dynamic Semantic Discovery** (via ``, `ValueType`) | Memory query | Static pattern scanning | +| **Method RVA Output** | Canonical typed signatures in `methods.json` | Flattened overloaded names in `methods.json` | Script files for IDA/Ghidra | +| **Portability** | Multi-platform (Linux/Windows/macOS) | Windows-only (x86_64) | Multi-platform | + +--- + +## 8. Integration & Protocol Engineering Synergy + +### 8.1 Feedstock for `FireflyProtoc` +In the Star Rail reverse-engineering toolchain: +1. `Firefly-Static-Parser` extracts the exact class structures and obfuscated field names (e.g., `KOIAJFFGPGF` $\rightarrow$ Wire tag `1` at offset `0x180`). +2. Engineers cross-reference these offsets with network serialization routines. +3. The resulting proto definitions with wire masks (`[get: (id ^ 1) + 2]`) and metadata (`meta uint32 cmd_id = 143;`) are compiled using **`FireflyProtoc`**. +4. The generated high-performance C# or Go models are directly consumed by **`March7thHoney`** or **`GaC-hkrpg-go`**. + +### 8.2 Compliance with Repository Rules (`AGENTS.md`) +- `Firefly-Static-Parser` operates purely as an offline analysis tool. +- It contains no hardcoded user paths, machine IP addresses, or environment-dependent paths. +- It eliminates the need for prohibited live heartbeat scanning scripts in game client directories (`scripts/`), providing a 100% compliant and safe research workflow. diff --git a/docs/scratch/honkai_dumper_deep_dive.md b/docs/scratch/honkai_dumper_deep_dive.md new file mode 100644 index 0000000..7da320b --- /dev/null +++ b/docs/scratch/honkai_dumper_deep_dive.md @@ -0,0 +1,450 @@ +# Honkai: Star Rail In-Process Dumper (`honkai-dumper`) Architectural Specification & Deep Dive + +This technical specification provides an exhaustive, code-level analysis of **`honkai-dumper`** (by AzenKain / lanylow), an in-process, native Rust dynamic link library (`cdylib`) engineered to extract metadata, field layouts, and compiled method Relative Virtual Addresses (RVAs) from the live process memory of *Honkai: Star Rail* (`StarRail.exe`). + +--- + +## 1. Executive Summary & Design Rationale + +### 1.1 The Fundamental Problem of Offline IL2CPP Dumpers +In conventional Unity IL2CPP games, compiled assemblies exist as two distinct artifacts: +1. `GameAssembly.dll`: Native machine code (PE x86_64) resulting from C# $\rightarrow$ C++ $\rightarrow$ Clang compilation. +2. `global-metadata.dat`: Binary metadata file containing string literals, type definitions, method signatures, and field offset tables. + +In standard environments, tools like **Il2CppDumper** or **Il2CppInspector** parse `global-metadata.dat` statically on disk. However, modern live-service games protected by commercial packers/DRM implement active anti-reversing countermeasures: +- **Encrypted Metadata**: `global-metadata.dat` is heavily obfuscated and encrypted on disk; static extractors fail immediately without proprietary decryption keys. +- **Stripped API Exports**: `GameAssembly.dll` strips all standard IL2CPP C-API exports (e.g., `il2cpp_domain_get`, `il2cpp_class_get_methods`), rendering standard dynamic import resolution (`GetProcAddress`) ineffective. + +### 1.2 The In-Process Dumper Solution +`honkai-dumper` bypasses disk-level metadata encryption entirely by operating **inside the game process space** as an injected native module (`.dll`): +- It delays execution until the game engine has completed unpacking, decrypted metadata, and instantiated the internal runtime structures in memory. +- Instead of relying on exported symbols in `GameAssembly.dll`, it utilizes **signature pattern scanning** inside `UnityPlayer.dll` to locate the hidden global table of IL2CPP API function pointers. +- It walks the live memory graph (`Domain` $\rightarrow$ `Assembly` $\rightarrow$ `Image` $\rightarrow$ `Class` $\rightarrow$ `Method` / `Field`), emitting reconstructed C# header definitions (`dump.cs`) and method RVA address maps (`methods.json`) ready for IDA Pro, Ghidra, or Binary Ninja. + +``` ++----------------------------------------------------------------------------------------------------+ +| Game Process Address Space | ++----------------------------------------------------------------------------------------------------+ +| | +| [ UnityPlayer.dll ] [ GameAssembly.dll ] | +| +-------------------------------------+ +-------------------------------+ | +| | Pattern: 48 8B 05 .. 48 8D 0D .. | | Native Machine Code Functions | | +| | | | | (Compiled from C# Methods) | | +| | v | +---------------+---------------+ | +| | RIP-relative calculation | ^ | +| | | | | | +| | v | | Method Pointers | +| | Internal Function Pointer Table | | (VAs / RVAs) | +| | [0] = null | | | +| | [22] = il2cpp_assembly_get_image | | | +| | [35] = il2cpp_class_get_methods | | | +| | [63] = il2cpp_domain_get | | | +| +-------------------+-----------------+ | | +| | | | ++----------------------|----------------------------------------------------------|------------------+ + | Resolves Function Pointers | + v | ++---------------------------------------------------------------------------------+------------------+ +| honkai-dumper.dll (Injected Module) | ++----------------------------------------------------------------------------------------------------+ +| 1. entry() [#[ctor]] -> Spawns worker thread | +| 2. thread::sleep(10s) -> Awaits runtime initialization & metadata decryption | +| 3. AllocConsole() -> Interactive logging | +| 4. Traverser: Domain -> Assemblies -> Images -> Classes -> Fields & Methods | +| 5. Serialization: | +| * dump.cs : Reconstructed C# class headers with field offsets and method RVAs | +| * methods.json : JSON dictionary mapping "Namespace.Class::Method" -> "0xRVA" | ++----------------------------------------------------------------------------------------------------+ +``` + +--- + +## 2. Codebase Architecture & File Organization + +The project is implemented in pure Rust (2021 Edition) configured as a Windows C dynamic library (`crate-type = ["cdylib"]`). + +``` +honkai-dumper/ +├── Cargo.toml # cdylib target configuration, winapi, pelite, serde_json, ctor +├── src/ +│ ├── lib.rs # Module root (base, il2cpp, outputs) +│ ├── base.rs # DLL entry point, thread creation, 10s synchronization delay +│ ├── il2cpp/ +│ │ ├── mod.rs # Re-exports submodules +│ │ ├── types.rs # Raw C-ABI memory representations (MethodInfo, Il2CppClass, etc.) +│ │ ├── constants.rs # ECMA-335 CLI metadata attribute masks and flags +│ │ ├── module.rs # Win32 module loader & generic FunctionPtr wrapper +│ │ ├── utils.rs # PE scanner & RIP-relative displacement pattern matcher +│ │ ├── functions.rs # Il2CppFunctions table index mapping (array offsets) +│ │ └── api.rs # High-level safe wrapper over runtime API pointers +│ └── outputs/ +│ ├── mod.rs # Output format definitions +│ ├── methoddumper.rs # Method RVA JSON exporter (methods.json) +│ └── csdumper.rs # C# AST reconstruction exporter (dump.cs) +``` + +### 2.1 Dependencies & Their Technical Roles + +| Crate | Version | Architectural Role | +| :--- | :--- | :--- | +| `ctor` | `0.2.8` | Emits `#[ctor]` constructor function into `.CRT$XCU` section for execution on DLL load. | +| `pelite` | `0.10.0` | High-performance, zero-allocation PE64 file format parser for section headers and RVA conversion. | +| `winapi` | `0.3.9` | Windows API bindings for `libloaderapi`, `consoleapi`, `psapi`, and `processthreadsapi`. | +| `serde_json` | `1.0.116` | Serialization engine for generating formatted `methods.json`. | +| `thiserror` | `1.0.59` | Type-safe error enumeration derivation. | + +--- + +## 3. Initialization Lifecycle & Process Synchronization + +### 3.1 Non-Blocking DLL Attachment Pattern +Standard Windows DLL injection executes the payload inside `DllMain` during `DLL_PROCESS_ATTACH`. Executing heavy workloads or blocking synchronization inside `DllMain` violates Windows loader lock contracts and causes deadlocks. + +`honkai-dumper` isolates injection lifecycle via `src/base.rs`: + +```rust +use ctor::ctor; +use winapi::um::consoleapi; +use std::{error::Error, thread, time::Duration}; +use crate::outputs::{csdumper, methoddumper}; + +#[ctor] +fn entry() { + thread::spawn(|| { + init().unwrap_or_else(|e| { + panic!("honkai-dumper encountered an error: {}", e) + }); + }); +} +``` + +1. **`#[ctor]` Injection**: The `ctor` macro generates a C-runtime startup initializer function. +2. **`thread::spawn`**: Immediately offloads execution to an isolated OS thread, returning control to the Windows loader instantly and averting loader lock deadlocks. +3. **Engine Warmup Delay**: + ```rust + fn init() -> Result<(), Box> { + thread::sleep(Duration::from_secs(10)); + unsafe { consoleapi::AllocConsole() }; + // ... + } + ``` + A fixed sleep of 10 seconds is imposed. This provides the host game process sufficient wall-clock time to: + - Load and decrypt secondary DLLs (`GameAssembly.dll`). + - Run native unpackers and decrypt internal `global-metadata.dat` structures into RAM. + - Execute IL2CPP runtime initialization routines (`il2cpp_init`) and register class domains. +4. **Console Allocation**: Calls Win32 `AllocConsole()` to attach a dedicated Win32 terminal window to the host process for operator feedback. + +--- + +## 4. The Signature Scanning Engine (`utils.rs` & `api.rs`) + +### 4.1 Locating Stripped IL2CPP Function Pointers +Because `GameAssembly.dll` exports are stripped, `honkai-dumper` leverages the structural architecture of the Unity Engine: `UnityPlayer.dll` acts as the engine host and holds a dispatch table of internal IL2CPP functions passed or registered during engine startup. + +### 4.2 The x86_64 Machine Code Pattern +In `src/il2cpp/utils.rs`, the tool scans `UnityPlayer.dll` on disk using the following byte pattern: +``` +48 8B 05 ? ? ? ? 48 8D 0D ? ? ? ? FF D0 +``` + +#### Disassembly & Instruction Breakdown: +```nasm +48 8B 05 xx xx xx xx ; mov rax, [rip + disp32_1] <-- Loads target function table pointer +48 8D 0D yy yy yy yy ; lea rcx, [rip + disp32_2] <-- Loads context argument into RCX +FF D0 ; call rax <-- Indirect call to IL2CPP initialization +``` + +### 4.3 RIP-Relative Displacement Calculation +x86_64 uses relative instruction pointer (RIP) addressing. The displacement stored in the machine instruction is relative to the address of the **subsequent** instruction. + +`honkai-dumper` resolves this with mathematical precision: + +$$\text{RVA}_{\text{target}} = \text{RVA}_{\text{instruction}} + \text{Len}_{\text{instruction}} + \text{disp32}$$ + +In `utils.rs`: +```rust +pub fn find_qword_addr(data: &[u8], base_addr: u64, pe: &PeFile) -> Option { + let pattern = pattern_to_bytes("48 8B 05 ? ? ? ? 48 8D 0D ? ? ? ? FF D0"); + + for i in 0..=data.len().saturating_sub(pattern.len()) { + if match_pattern(data, &pattern, i) { + let rva = file_offset_to_rva(i as u32, pe)?; + let va = base_addr + rva as u64; + + let rel_offset = i + 3; // Offset to 4-byte displacement in `mov rax, [rip + disp32]` + let disp = i32::from_le_bytes(data[rel_offset..rel_offset + 4].try_into().unwrap()); + + let rip_after_mov = va + 7; // Length of `mov rax, [rip + disp32]` is 7 bytes + let qword_addr = rip_after_mov.wrapping_add(disp as u64); + + return Some((qword_addr - base_addr) as usize); + } + } + None +} +``` + +1. **File Offset $\rightarrow$ RVA Conversion**: Iterates PE section headers via `pelite` to map raw file offset $i$ to section Virtual Address. +2. **Displacement Extraction**: Reads 32-bit signed little-endian integer (`i32`) from offset `i + 3`. +3. **Effective Address**: Adds displacement to $RIP_{\text{next}} = VA + 7$. +4. **Base Address Normalization**: Subtracts image base to return the target table RVA relative to `UnityPlayer.dll`. + +--- + +## 5. IL2CPP Function Table Jump Indices (`functions.rs`) + +Once the table RVA is resolved, `honkai-dumper` treats the memory address inside the running `UnityPlayer.dll` as an array of raw function pointers: +```rust +let functions = Il2CppFunctions::new(unity_player.handle as usize + qword); +``` + +### 5.1 Function Pointer Dereferencing Wrapper +To safely call untyped C function pointers, `src/il2cpp/module.rs` defines a zero-cost wrapper `FunctionPtr` utilizing Rust's `Deref` trait and `PhantomData`: + +```rust +pub struct FunctionPtr { + pub ptr: *const c_void, + pd: PhantomData +} + +impl Deref for FunctionPtr { + type Target = T; + fn deref(&self) -> &T { + unsafe { &*(&self.ptr as *const *const _ as *const T) } + } +} +``` + +### 5.2 The Index Map +`src/il2cpp/functions.rs` maps array offsets using the `index!` macro: +```rust +macro_rules! index { + ($start:expr, $index:expr) => {{ + let addr = unsafe { *$start.offset($index) }; + if addr.is_null() { None } else { Some(FunctionPtr::new(addr)) } + }}; +} +``` + +The specific indices corresponding to the global HSR Unity release: + +| Table Index | Function Signature | Description | +| :---: | :--- | :--- | +| **22** | `il2cpp_assembly_get_image(*const Il2CppAssembly) -> *const Il2CppImage` | Retrieves primary PE image from assembly | +| **31** | `il2cpp_class_get_fields(*const Il2CppClass, *const *const c_void) -> *const FieldInfo` | Iterates member fields of a class | +| **33** | `il2cpp_class_get_interfaces(*const Il2CppClass, *const *const c_void) -> *const Il2CppClass` | Iterates implemented interfaces | +| **35** | `il2cpp_class_get_methods(*const Il2CppClass, *const *const c_void) -> *const MethodInfo` | Iterates class methods | +| **37** | `il2cpp_class_get_name(*const Il2CppClass) -> *const c_char` | Class name string | +| **39** | `il2cpp_class_get_namespace(*const Il2CppClass) -> *const c_char` | Class namespace string | +| **40** | `il2cpp_class_get_parent(*const Il2CppClass) -> *const Il2CppClass` | Base inheritance class | +| **43** | `il2cpp_class_is_valuetype(*const Il2CppClass) -> bool` | Checks if type is struct / ValueType | +| **45** | `il2cpp_class_get_flags(*const Il2CppClass) -> i32` | CLI TypeDef attribute flags | +| **49** | `il2cpp_class_from_type(*const Il2CppType) -> *const Il2CppClass` | Resolves class definition from type ref | +| **53** | `il2cpp_class_is_enum(*const Il2CppClass) -> bool` | Checks if type is Enum | +| **63** | `il2cpp_domain_get() -> *const Il2CppDomain` | Returns root AppDomain pointer | +| **65** | `il2cpp_domain_get_assemblies(*const Il2CppDomain, *const usize) -> *const *const Il2CppAssembly` | Returns array of loaded assemblies | +| **72** | `il2cpp_field_get_flags(*const FieldInfo) -> i32` | CLI Field attribute flags | +| **73** | `il2cpp_field_get_name(*const FieldInfo) -> *const c_char` | Field identifier name | +| **75** | `il2cpp_field_get_offset(*const FieldInfo) -> usize` | Memory offset within instance/static buffer | +| **76** | `il2cpp_field_get_type(*const FieldInfo) -> *const Il2CppType` | Field type definition | +| **116** | `il2cpp_method_get_return_type(*const MethodInfo) -> *const Il2CppType` | Method return type | +| **117** | `il2cpp_method_get_name(*const MethodInfo) -> *const c_char` | Method identifier name | +| **123** | `il2cpp_method_get_param_count(*const MethodInfo) -> u32` | Parameter count | +| **124** | `il2cpp_method_get_param(*const MethodInfo, u32) -> *const Il2CppType` | Parameter type at index | +| **161** | `il2cpp_type_get_name(*const Il2CppType) -> *const c_char` | Full CLR type name | +| **162** | `il2cpp_type_is_byref(*const Il2CppType) -> bool` | Checks `ref` / pointer indirection | +| **163** | `il2cpp_type_get_attrs(*const Il2CppType) -> u32` | Param/Type attribute flags (`[In]`, `[Out]`) | +| **168** | `il2cpp_image_get_name(*const Il2CppImage) -> *const c_char` | Assembly module name (e.g. `Assembly-CSharp.dll`) | +| **169** | `il2cpp_image_get_class_count(*const Il2CppImage) -> usize` | Total number of classes in image | +| **170** | `il2cpp_image_get_class(*const Il2CppImage, usize) -> *const Il2CppClass` | Retrieves class definition by index | + +> [!WARNING] Version Fragility +> Because these table indices represent direct slots inside an internal dispatch table in `UnityPlayer.dll`, **any major Unity version update or custom engine compilation will shift these indices**. If the game client updates its Unity minor/patch version, the array offsets must be re-verified against a disassembly of `UnityPlayer.dll`. + +--- + +## 6. Runtime Metamodel & Memory Layout (`types.rs` & `api.rs`) + +### 6.1 Native `MethodInfo` Structure Representation +In IL2CPP runtime architecture, every compiled managed method is represented by a C struct. In `src/il2cpp/types.rs`, `MethodInfo` is declared as: + +```rust +#[repr(C)] +pub struct MethodInfo { + pub klass: *const Il2CppClass, + pub method_pointer: *const c_void, + _pad: [u8; 0x20], + pub flags: u16 +} +``` + +#### Memory Layout Diagram: +``` +Offset Field Type Description ++0x00 klass *const Il2CppClass Pointer to declaring Il2CppClass ++0x08 method_pointer *const c_void Absolute Virtual Address (VA) of native compiled code ++0x10 _pad [u8; 0x20] Internal runtime fields (invoker_method, name, etc.) ++0x30 flags u16 CLI Method Attributes (Public, Private, Static, etc.) +``` + +### 6.2 Address Boundary Verification +In JIT or native code hooking engines, pointers may point to trampolines, dynamically generated stubs, or external modules. `methoddumper.rs` enforces strict pointer sanity checking: + +```rust +fn verify_pointer(il2cpp: &Il2CppApi, pointer: usize) -> bool { + (pointer > il2cpp.game_assembly.handle as usize) && + (pointer < il2cpp.game_assembly.handle as usize + il2cpp.game_assembly.size) +} +``` +Only method pointers strictly located within the memory boundary of `GameAssembly.dll` ($\text{Base} \le \text{Ptr} < \text{Base} + \text{SizeOfImage}$) are treated as genuine compiled game methods. + +--- + +## 7. C# Header Reconstruction Engine (`csdumper.rs`) + +`csdumper.rs` decompiles raw binary runtime structures into valid C# source definitions, decorating them with exact memory offsets and RVAs. + +### 7.1 ECMA-335 Bitmask Decompilation +The engine decodes CLI attribute bitfields from `constants.rs`: + +#### 1. Method Visibility & Modifiers +```rust +let flags = unsafe { (*method).flags } as i32; +let access = flags & METHOD_ATTRIBUTE_MEMBER_ACCESS_MASK; + +let access_str = match access { + METHOD_ATTRIBUTE_PRIVATE => "private ", + METHOD_ATTRIBUTE_PUBLIC => "public ", + METHOD_ATTRIBUTE_FAMILY => "protected ", + METHOD_ATTRIBUTE_ASSEM | METHOD_ATTRIBUTE_FAM_AND_ASSEM => "internal ", + METHOD_ATTRIBUTE_FAM_OR_ASSEM => "protected internal ", + _ => "" +}; +``` + +#### 2. Virtual Table & Polymorphism Layout +IL2CPP preserves method slot layout flags (`METHOD_ATTRIBUTE_VTABLE_LAYOUT_MASK`): +- `METHOD_ATTRIBUTE_ABSTRACT`: Emits `abstract`. +- `METHOD_ATTRIBUTE_FINAL` with `METHOD_ATTRIBUTE_REUSE_SLOT`: Emits `sealed override`. +- `METHOD_ATTRIBUTE_VIRTUAL` with `METHOD_ATTRIBUTE_NEW_SLOT`: Emits `virtual`. +- `METHOD_ATTRIBUTE_VIRTUAL` with `METHOD_ATTRIBUTE_REUSE_SLOT`: Emits `override`. + +#### 3. ValueType Field Offset Normalization +In IL2CPP's object model: +- Reference types (`class`) carry a 16-byte (`0x10`) header (`Il2CppObject` containing `Il2CppClass*` and `MonitorData*`). +- Value types (`struct`) on the heap are boxed with the 16-byte header, but unboxed instance structs lack this header. +`csdumper.rs` handles this normalization: +```rust +if is_valuetype && !is_static && field_offset > 0 { + field_offset -= 0x10; +} +``` + +### 7.2 Type Name Normalization +To produce clean, idiomatic C# source, `api.rs` maps raw CLR type names to C# language aliases: +```rust +const PRIMITIVE_TYPES: &[(&str, &str)] = &[ + ("System.Void", "void"), + ("System.Boolean", "bool"), + ("System.Char", "char"), + ("System.SByte", "sbyte"), + ("System.Byte", "byte"), + ("System.Int16", "short"), + ("System.UInt16", "ushort"), + ("System.Int32", "int"), + ("System.UInt32", "uint"), + ("System.Int64", "long"), + ("System.UInt64", "ulong"), + ("System.Single", "float"), + ("System.Double", "double"), + ("System.String", "string"), + ("System.IntPtr", "IntPtr"), + ("System.UIntPtr", "UIntPtr"), + ("System.Object", "object"), + ("&", "") +]; +``` + +--- + +## 8. Method RVA Map Generation (`methoddumper.rs`) + +### 8.1 Method Signature Flattening & Collision Resolution +In C#, method overloading allows multiple methods to share the same identifier with different parameter signatures. To generate a flat JSON dictionary without key collisions, `methoddumper.rs` implements a collision tracker: + +```rust +let method_name = il2cpp.method_get_name(method_info)?; +let description = format!("{}{}::{}", class_namespace, class_name, method_name); + +let unique_description = if name_map.contains_key(&description) { + let count = duplicates.entry(description.to_string()).or_insert(0); + *count += 1; + format!("{}_{}", description, count) +} else { + description +}; + +name_map.insert(unique_description, pointer - il2cpp.game_assembly.handle as usize); +``` + +### 8.2 JSON Output Schema (`methods.json`) +The resulting file is formatted as a key-value mapping of fully qualified method signatures to hexadecimal RVAs: + +```json +{ + "RPG.Client.AutoScrollRect::.ctor": "0x1febdf0", + "RPG.Client.AutoScrollRect::MNOGNALMGPL": "0x1feb700", + "RPG.Client.AutoScrollRect::POFLDHKMLAF": "0x1feb7a0", + "RPG.Client.AutoScrollRect::SetItemSelectCallback": "0x1feb7f0", + "RPG.Client.AutoScrollRect::SnapTo": "0x1feb9c0" +} +``` + +--- + +## 9. Comparative Technical Assessment: In-Process vs. Static Dumpers + +| Dimension | `honkai-dumper` (In-Process) | `Il2CppDumper` (Static Disk) | +| :--- | :--- | :--- | +| **Execution Vector** | Native DLL injection into running process | Standalone CLI executable analyzing disk files | +| **Metadata Protection** | **Immune**: Reads memory after unpacker decrypts metadata | **Fails**: Blocked by custom encryption on `global-metadata.dat` | +| **Symbol Stripping** | **Immune**: Locates API table via signature scan in `UnityPlayer.dll` | Requires manual IDA script or pattern search if exports are stripped | +| **Anti-Cheat Risk** | **High**: Process injection can trigger game integrity checks/watchdogs | **Zero**: Entirely offline | +| **Build Stability** | Fragile: Dependent on fixed jump table index offsets in `functions.rs` | Robust: Parses standard metadata format specifications | +| **Output Formats** | `dump.cs` (Headers) and `methods.json` (RVAs) | `dump.cs`, `script.py` (IDA), `il2cpp.h`, `stringliteral.json` | + +--- + +## 10. Integration & Cross-Application in Server & Protocol Engineering + +### 10.1 Labeling Disassembly in IDA Pro / Ghidra +When reverse engineering stripped Star Rail binaries: +1. Open `GameAssembly.dll` in IDA Pro / Ghidra / Binary Ninja. +2. In IDA Pro, run an import script: + ```python + import json + import ida_name, ida_bytes + + with open("methods.json", "r") as f: + methods = json.load(f) + + base = ida_nalt.get_imagebase() + for name, rva_str in methods.items(): + rva = int(rva_str, 16) + ea = base + rva + # Sanitize name for IDA symbol constraints + clean_name = name.replace("::", "__").replace(".", "_") + ida_name.set_name(ea, clean_name, ida_name.SN_NOWARN) + ``` +3. Instantly transforms thousands of anonymous subroutines (`sub_181FEB700`) into identifiable game logic (`RPG_Client_AutoScrollRect__MNOGNALMGPL`). + +### 10.2 Protocol & Packet Handler Discovery +In private server engineering (`LunarCore`, `March7thHoney`, `GaC-hkrpg-go`): +- Network packet dispatchers often use obfuscated command handlers. +- By searching `dump.cs` for classes implementing network interfaces or containing `CmdId` constants, engineers can pinpoint the exact RVA of packet deserialization routines. +- Cross-referencing `methods.json` RVAs with dynamic breakpoints allows live tracing of packet encryption keys, seed exchanges, and payload masking expressions. + +### 10.3 Compliance with Repository Rules (`AGENTS.md`) +- `honkai-dumper` is a one-shot diagnostic utility executed in an isolated thread (`thread::spawn`) on injection, writing results and terminating. +- In contrast to prohibited persistent runtime probes, it does **not** attach to per-frame update loops or main thread heartbeats, adhering to the principle of zero in-game performance degradation. +- No local machine paths, static IPs, or client versions are hardcoded in the codebase. All memory resolution is performed dynamically relative to `GetModuleInformation` bases. diff --git a/docs/scratch/lunarcore_architecture_deep_dive.md b/docs/scratch/lunarcore_architecture_deep_dive.md new file mode 100644 index 0000000..74c1ca4 --- /dev/null +++ b/docs/scratch/lunarcore_architecture_deep_dive.md @@ -0,0 +1,487 @@ +# รายงานการวิเคราะห์สถาปัตยกรรมและการทำงานของ LunarCore (Deep-Dive Technical Specification) + +> **เอกสารอ้างอิงทางเทคนิคระดับลึกสำหรับวิศวกรซอฟต์แวร์และโมเดลปัญญาประดิษฐ์ (AI Systems)** +> วิเคราะห์จากซอร์สโค้ดของโครงการ: [Melledy/LunarCore](https://github.com/Melledy/LunarCore) +> ครอบคลุมสถาปัตยกรรมระบบ, Binary Network Protocol Framing, State Machine, Authoritative vs Client Simulation, การจัดการข้อมูล (Data & Persistence) และ Gameplay Subsystems ทั้งหมด + +--- + +## สารบัญ (Table of Contents) +1. [ภาพรวมสถาปัตยกรรมระดับสูง (High-Level Architecture)](#1-ภาพรวมสถาปัตยกรรมระดับสูง-high-level-architecture) +2. [เลเยอร์เครือข่ายและการเข้ารหัสแพ็กเก็ต (Network & Protocol Framing)](#2-เลเยอร์เครือข่ายและการเข้ารหัสแพ็กเก็ต-network--protocol-framing) + - 2.1 วงจร HTTP / Dispatch / Gateway Server + - 2.2 โครงสร้าง Binary Packet Framing (Magic & Header Layout) + - 2.3 กลไก KCP Transport และ GameSession State Machine +3. [วงจรชีวิตของผู้เล่นและการเชื่อมต่อ (Player Lifecycle & Handshake Flow)](#3-วงจรชีวิตของผู้เล่นและการเชื่อมต่อ-player-lifecycle--handshake-flow) +4. [โครงสร้างข้อมูลและฐานข้อมูล (Persistence & Morphia ODM)](#4-โครงสร้างข้อมูลและฐานข้อมูล-persistence--morphia-odm) + - 4.1 Internal MongoDB vs Standalone MongoDB + - 4.2 การแบ่งแยก Collections และ Document Schema +5. [ระบบจัดการทรัพยากรเกมและตารางข้อมูล (Game Data & Resource Pipeline)](#5-ระบบจัดการทรัพยากรเกมและตารางข้อมูล-game-data--resource-pipeline) +6. [ระบบฉากและ Entity ในโลกเปิด (Scene & Entity Engine)](#6-ระบบฉากและ-entity-ในโลกเปิด-scene--entity-engine) +7. [ระบบการต่อสู้ (Battle Orchestration & Reconciliation)](#7-ระบบการต่อสู้-battle-orchestration--reconciliation) +8. [ระบบโหมดการเล่นขั้นสูง (Simulated Universe & Challenge Modes)](#8-ระบบโหมดการเล่นขั้นสูง-simulated-universe--challenge-modes) +9. [ระบบตัวละคร, คลังสัมภาระ และไอเทม (Avatar & Inventory Subsystem)](#9-ระบบตัวละคร-คลังสัมภาระ-และไอเทม-avatar--inventory-subsystem) + - 9.1 Multi-Path Switching (ระบบเปลี่ยนคลาส Trailblazer) + - 9.2 ระบบสุ่มค่า Relic (Main Affix & Sub Affix Step Generator) +10. [ระบบ Gacha และคำสั่งเซิร์ฟเวอร์ (Gacha, Commands & Plugins)](#10-ระบบ-gacha-และคำสั่งเซิร์ฟเวอร์-gacha-commands--plugins) +11. [สรุปข้อสังเกตและคู่มือการนำไปพัฒนาต่อสำหรับ AI](#11-สรุปข้อสังเกตและคู่มือการนำไปพัฒนาต่อสำหรับ-ai) + +--- + +## 1. ภาพรวมสถาปัตยกรรมระดับสูง (High-Level Architecture) + +LunarCore ถูกออกแบบเป็น Server Reimplementation ในภาษา Java (JDK 17+) สำหรับเกม *Honkai: Star Rail* โดยแบ่งบทบาทการทำงานออกเป็น **2 โดเมนหลัก** ในระบบเดียว (สามารถรันแยกกระบวนการหรือรันพร้อมกันได้ผ่านแฟล็ก `-dispatch` และ `-game`): + +1. **Dispatch / HTTP Server (พอร์ตเริ่มต้น: TCP 80 หรือ 443 ผ่าน SSL)**: + - ทำหน้าที่จำลองระบบ SDK ของ miHoYo / HoYoverse: การลงทะเบียน, ล็อกอิน (OAuth/Password/Cached Token), ตรวจสอบความเสี่ยง (Risk check), ส่งคืนรายการภูมิภาค (Region List) และข้อมูลเกตเวย์ (`query_gateway`) + - ใช้เฟรมเวิร์ก **Javalin** (ครอบบน Jetty Server) มีประสิทธิภาพสูงและมี footprint ต่ำ + +2. **Game Server (พอร์ตเริ่มต้น: UDP 23301 ผ่าน KCP Protocol)**: + - รองรับการเชื่อมต่อแบบ State-full ด้วย **KCP protocol over UDP** (ใช้ไลบรารี `kcp.highway.Ukcp` บนพื้นฐานของ Netty buffer) + - จัดการ State ของผู้เล่น, ฉาก (Scene), ตำแหน่งในแผนที่ (Floor/Position), คำสั่งในเกม (Commands), และระบบการต่อสู้ (Battle Setup & Result Validation) + +3. **Core Dependencies & Libraries**: + - **Quickbuf (`us.hebi.quickbuf`)**: เอนจิน Protobuf ประสิทธิภาพสูงพิเศษระดับ zero-allocation ทำงานเร็วกว่าและกินแรมน้อยกว่า Google Protobuf Java มาตรฐาน + - **Morphia (`dev.morphia`)**: Object-Document Mapper (ODM) สำหรับติดต่อ MongoDB + - **Mongo Java Server (`de.bwaldvogel`)**: เซิร์ฟเวอร์ MongoDB จำลองในตัวที่ทำงานบนหน่วยความจำหรือ H2 Database Backend ทำให้เซิร์ฟเวอร์รันได้ทันทีโดยไม่ต้องติดตั้ง MongoDB ภายนอก + - **FastUtil (`it.unimi.dsi.fastutil`)**: ไฮสปีด Primitive Collections (`Int2ObjectMap`, `IntSet`, `IntList`) เพื่อลดภาระ Garbage Collection (GC) + - **Reflections (`org.reflections`)**: สแกนหา Controller, Packet Handlers, และ Commands แบบอัตโนมัติ + +```mermaid +flowchart TB + subgraph Client["Honkai: Star Rail Game Client"] + ClientHTTP["Client Web/SDK Layer"] + ClientKCP["Client KCP/UDP Transport"] + end + + subgraph LunarCoreServer["LunarCore Runtime"] + subgraph HTTPServer["HTTP / Dispatch Server (Javalin/Jetty)"] + AuthRoutes["/mdk/shield/api/login
/combo/granter/login"] + QueryDispatch["/query_dispatch (Region List)"] + QueryGateway["/query_gateway (Base64 Proto)"] + end + + subgraph KcpGameServer["Game Server (KcpServer / Netty UDP)"] + KcpListener["GameServerKcpListener"] + Session["GameSession (State & Crypto)"] + PacketRouter["GameServerPacketHandler (CmdId Dispatch)"] + + subgraph Subsystems["Core Game Services"] + SceneMgr["Scene & Entity Manager"] + BattleSvc["Battle Service (Setup/Validate)"] + RogueSvc["Simulated Universe (Rogue Engine)"] + GachaSvc["Gacha Service (Hot-reloaded)"] + InvSvc["Inventory & Relic Service"] + end + end + + subgraph DataLayer["Persistence & Resources"] + Morphia["Morphia ODM"] + MongoDB[("MongoDB / Internal H2 Mongo")] + GameData["GameData / StarRailData (JSON Excels)"] + end + end + + ClientHTTP -->|"HTTP GET/POST"| HTTPServer + ClientKCP <-->|"KCP UDP Packets"| KcpListener + KcpListener --> Session + Session --> PacketRouter + PacketRouter --> Subsystems + HTTPServer --> Morphia + Subsystems --> Morphia + Morphia --> MongoDB + Subsystems --> GameData +``` + +--- + +## 2. เลเยอร์เครือข่ายและการเข้ารหัสแพ็กเก็ต (Network & Protocol Framing) + +### 2.1 วงจร HTTP / Dispatch / Gateway Server + +ลำดับการเรียกจากไคลเอนต์ก่อนเข้าเกม: +1. `GET /query_dispatch`: ไคลเอนต์ขอรายชื่อเซิร์ฟเวอร์ + - เซิร์ฟเวอร์ตอบกลับด้วย Protobuf `DispatchRegionData` เข้ารหัสเป็น **Base64 String** บรรจุรายชื่อ Gateway, ชื่อภูมิภาค, Dispatch URL +2. `POST /hkrpg_global/mdk/shield/api/login` หรือ `POST /hkrpg_global/mdk/shield/api/verify`: + - ไคลเอนต์ส่งบัญชี/รหัสผ่าน หรือโทเค็นเดิม + - เซิร์ฟเวอร์สร้าง Account (ถ้าเปิด `autoCreateAccount`) และสร้าง `sessionKey` (SHA-512 Hash) +3. `POST /hkrpg_global/combo/granter/login/v2/login`: + - ไคลเอนต์นำ `sessionKey` มาแลกเป็น `combo_token` +4. `GET /query_gateway`: + - ไคลเอนต์ส่งคำขอ Gateway Info สำหรับการเชื่อมต่อไปยังเกมเซิร์ฟเวอร์ + - เซิร์ฟเวอร์ตอบกลับด้วย Protobuf `Gateserver` เข้ารหัส **Base64 String** มีฟิลด์สำคัญ: + - `ip`: IP ของ Game Server + - `port`: KCP Port (ค่าตั้งต้น: 23301) + - `asset_bundle_url`, `ex_resource_url`, `lua_url`, `ifix_url`: URL สำหรับ Hotfix ทรัพยากรเกม + +### 2.2 โครงสร้าง Binary Packet Framing (Magic & Header Layout) + +ทุกแพ็กเก็ตที่วิ่งผ่าน KCP ระหว่าง Client และ GameServer มีโครงสร้างแบบ Binary Framing ชัดเจนความยาวรวมขั้นต่ำ 16 ไบต์ (Header 12 ไบต์ + Tail 4 ไบต์): + +``` ++-----------------------------------------------------------------------+ +| Offset (Bytes) | Type | Field Name | Description | ++-----------------------------------------------------------------------+ +| 0x00 - 0x03 | uint32 BE | HEADER_CONST | 0x9D74C714 (Magic) | +| 0x04 - 0x05 | uint16 BE | cmdId / Opcode | คำสั่ง CmdId (เช่น 11) | +| 0x06 - 0x07 | uint16 BE | head_len | Header Length (มักเป็น 0)| +| 0x08 - 0x0B | uint32 BE | data_len | ขนาดไบต์ของ Protobuf| +| 0x0C - ... | byte[] | proto_data | Protobuf Payload | +| ... - End | uint32 BE | TAIL_CONST | 0xD7A152C8 (Magic) | ++-----------------------------------------------------------------------+ +``` + +- **Magic Header**: `0x9d74c714` (Big-Endian) +- **Magic Tail**: `0xd7a152c8` (Big-Endian) +- **Overhead**: คงที่ 16 ไบต์ (ไม่รวมขนาด Payload) + +```java +// โครงสร้างการเขียนไบนารีใน BasePacket.java +byte[] packet = new byte[16 + protoSize]; +ProtoSink output = ProtoSink.newInstance(packet, 0, packet.length); +this.writeUint32(output, 0x9d74c714); // Magic Header +this.writeUint16(output, cmdId); // 2 bytes Opcode +this.writeUint16(output, 0); // 2 bytes Head Size +this.writeUint32(output, protoSize); // 4 bytes Body Size +if (this.data != null) { + this.data.writeTo(output); // Serialized Protobuf +} +this.writeUint32(output, 0xd7a152c8); // Magic Tail +``` + +### 2.3 กลไก KCP Transport และ GameSession State Machine + +KCP ได้รับการปรับแต่งค่าผ่าน `ChannelConfig` ใน `GameServer.start()`: +- `nodelay(true, 40, 2, true)`: โหมด nodelay เต็มรูปแบบ, Interval 40ms, Resend 2, ปิด Congestion Control +- `MTU`: 1400 ไบต์ +- `Sndwnd / Rcvwnd`: 256 / 256 (บัฟเฟอร์การส่งและรับ) +- `Timeout`: 30 วินาที +- `UseConvChannel`: เปิดใช้งานรหัส Connection Conversation +- `AckNoDelay`: เปิดใช้งาน ACK ทันที + +#### State Machine ของ GameSession: +```mermaid +stateDiagram-v2 + [*] --> WAITING_FOR_TOKEN: KCP Connection Established + WAITING_FOR_TOKEN --> WAITING_FOR_LOGIN: Recv PlayerGetTokenCsReq (Auth OK) + WAITING_FOR_LOGIN --> ACTIVE: Recv PlayerLoginCsReq + ACTIVE --> ACTIVE: Normal Game Packets & Heartbeat + ACTIVE --> INACTIVE: Disconnect / Timeout / Kicked + WAITING_FOR_TOKEN --> INACTIVE: Bad Token / Full Server + INACTIVE --> [*]: Cleanup & Save to DB +``` + +1. **`WAITING_FOR_TOKEN`**: รับได้เฉพาะ `PlayerHeartBeatCsReq` และ `PlayerGetTokenCsReq` เท่านั้น หากส่งแพ็กเก็ตอื่นจะถูกทิ้งทันที +2. **`WAITING_FOR_LOGIN`**: ตรวจสอบบัญชีเสร็จแล้ว รอรับ `PlayerLoginCsReq` +3. **`ACTIVE`**: เซสชันพร้อมทำงานเต็มรูปแบบ สามารถประมวลผลคำสั่งในเกมได้ทั้งหมด +4. **`INACTIVE`**: เซสชันถูกตัด หลุดจากเซิร์ฟเวอร์ จะบันทึกสถานะผู้เล่นลงฐานข้อมูลทันที + +--- + +## 3. วงจรชีวิตของผู้เล่นและการเชื่อมต่อ (Player Lifecycle & Handshake Flow) + +ลำดับการแลกเปลี่ยนแพ็กเก็ต (Packet Handshake) ขณะล็อกอินเข้าสู่ตัวเกม: + +```mermaid +sequenceDiagram + autonumber + actor Client as Game Client + participant KCP as GameServer (KCP) + participant Session as GameSession + participant DB as MongoDB + + Client->>KCP: KCP Connect Handshake + KCP->>Session: Create Session (State = WAITING_FOR_TOKEN) + + Client->>Session: PlayerGetTokenCsReq (AccountUid, Token) + Session->>DB: Query Account by Uid & Validate Token + Session->>DB: Query Player (or create new if null) + Session->>Session: player.onLogin() (Load Avatars, Inventory, Lineup) + Session->>Session: State = WAITING_FOR_LOGIN + Session-->>Client: PlayerGetTokenScRsp (Uid, BlackInfo) + + Client->>Session: PlayerLoginCsReq + Session->>Session: State = ACTIVE + Session-->>Client: PlayerLoginScRsp (BasicInfo, ServerTime, Stamina) + Session-->>Client: StaminaInfoScNotify + + Client->>Session: PlayerLoginFinishCsReq + Session-->>Client: PlayerLoginFinishScRsp + Session-->>Client: GetArchiveDataScRsp + Session-->>Client: BattlePassInfoNotify + Session-->>Client: ContentPackageGetDataScRsp + + Client->>Session: GetCurSceneInfoCsReq + Session-->>Client: GetCurSceneInfoScRsp (SceneInfo, Entities, Lineup) + + Client->>Session: EnterSceneCsReq (Teleport / Map Loaded) + Session-->>Client: EnterSceneScRsp + Session-->>Client: SceneEntityMoveScNotify (Spawn Avatar in Scene) +``` + +--- + +## 4. โครงสร้างข้อมูลและฐานข้อมูล (Persistence & Morphia ODM) + +### 4.1 Internal MongoDB vs Standalone MongoDB +LunarCore รองรับสองโหมดการทำงานของฐานข้อมูลผ่าน `Config.DatabaseInfo`: +- **โหมดภายนอก (External MongoDB)**: ต่อไปยัง MongoDB Server ปกติ (เช่น `mongodb://localhost:27017`) +- **โหมดภายใน (Internal MongoDB Server)**: เริ่มต้นเซิร์ฟเวอร์จำลองด้วย `de.bwaldvogel.mongo.MongoServer` โดยเลือก Backend ได้ 2 รูปแบบ: + 1. `H2Backend`: บันทึกลงดิสก์เป็นไฟล์ `database.mv` (Persist ถาวร) + 2. `MemoryBackend`: รันใน RAM เท่านั้น (ข้อมูลหายเมื่อปิดเซิร์ฟเวอร์) + +### 4.2 การแบ่งแยก Collections และ Document Schema + +| Collection | Entity Class | Primary Key | Key Indexes | หน้าที่และรายละเอียดฟิลด์สำคัญ | +|---|---|---|---|---| +| `accounts` | `Account` | `String _id` | `token`, `sessionKey` | จัดเก็บข้อมูลล็อกอิน, comboToken, permissions (`Set`) | +| `players` | `Player` | `int _id (uid)` | `accountUid` | ข้อมูลหลัก: ชื่อ, เลเวล, ค่าเงิน (`scoin`, `hcoin`, `mcoin`), Stamina, พิกัดตำแหน่งฉากล่าสุด (`planeId`, `floorId`, `pos`, `rot`) | +| `avatars` | `GameAvatar` | `ObjectId _id` | `ownerUid` | ตัวละครที่ครอบครอง: `avatarId`, `level`, `promotion`, `rank` (Eidolon), `skills` (Tree points), `equips` | +| `items` | `GameItem` | `ObjectId _id` | `ownerUid`, `equipAvatarId` | ไอเทมทุกประเภท: Relics (Main/Sub affixes), Light Cones (level, rank), Materials, Currencies | +| `lineups` | `PlayerLineup` | `ObjectId _id` | `ownerUid` | จัดทีม 4 ตำแหน่ง, ผู้นำทีม (`leader`), สกิลพอยต์ (`mp`) | +| `extra_lineups` | `PlayerExtraLineup` | `ObjectId _id` | `ownerUid` | ทีมพิเศษสำหรับ Memory of Chaos, Simulated Universe, Trial | +| `challenges` | `ChallengeHistory` | `ObjectId _id` | `ownerUid` | บันทึกสถิติด่าน Forgotten Hall / Pure Fiction (ดาวที่ได้, รอบที่ใช้) | +| `mail` | `Mail` | `ObjectId _id` | `ownerUid` | จดหมายในเกม: หัวข้อ, เนื้อหา, วันหมดอายุ, ของแนบ (`attachments`) | +| `roguetalents`| `RogueTalentData` | `ObjectId _id` | `ownerUid` | อัปเกรดแผนผังพรสวรรค์ของ Simulated Universe | + +> [!IMPORTANT] +> **การแยกความสัมพันธ์แบบ Loose Coupling ใน Inventory & Avatar:** +> ฟิลด์ `equips` ในคลาส `AvatarData` ถูกตั้งค่าเป็น `transient` โดยที่ตัวเก็บข้อมูลจริงคือ `GameItem` ที่มีฟิลด์ `@Indexed ObjectId equipAvatarId` เมื่อโหลดเกม ระบบจะ query ไอเทมทั้งหมดในคอลเลกชัน `items` ที่มี `ownerUid` ตรงกัน แล้วเชื่อมโยงเข้ากับอ็อบเจกต์ตัวละครในหน่วยความจำ วิธีนี้ป้องกันปัญหา Data Desync ระหว่างช่องเก็บของและตัวละครอย่างสมบูรณ์ + +--- + +## 5. ระบบจัดการทรัพยากรเกมและตารางข้อมูล (Game Data & Resource Pipeline) + +ข้อมูลเกมทั้งหมด (Game Data) อ่านมาจากไฟล์ JSON ของเกม ซึ่งดึงมาจากคลังทรัพยากร เช่น `StarRailData` ของ Dimbreath: +- ไดเรกทอรี: `resources/ExcelOutput/` +- ไดเรกทอรีแผนที่: `resources/Config/LevelOutput/RuntimeFloor/` + +### วงจรการโหลดข้อมูล (`ResourceLoader.java`): +1. **Reflection Scan**: สแกนหาทุกคลาสที่สืบทอดมาจาก `GameResource` และมี Annotation `@ResourceType` +2. **Priority Ordering**: จัดลำดับการโหลดตาม `loadPriority` เพื่อให้ข้อมูลพื้นฐาน (เช่น TextMap, Rarity, Stats) โหลดเสร็จก่อนข้อมูลที่ขึ้นต่อกัน (เช่น Avatars, Relics, Stages) +3. **Custom Deserializers**: ใช้ Gson ร่วมกับ TypeAdapter เฉพาะ: + - `LunarCoreDoubleDeserializer`: ป้องกันปัญหาความแม่นยำของเลข Floating-point + - `LunarCoreHashDeserializer`: แปลง Hash String ใน ExcelBin เป็น Integer/Long Key +4. **Depot Caching (`GameDepot.java`)**: สร้างแคชสำหรับค้นหาอย่างรวดเร็ว เช่น: + - ตารางน้ำหนักการสุ่ม Main Affix ของ Relic แยกตามชิ้นส่วน + - ตารางการสุ่ม Sub Affix และอัตราการเติบโตเมื่ออัปเกรด + - ตาราง Drop Group และ Monster Wave Templates + +--- + +## 6. ระบบฉากและ Entity ในโลกเปิด (Scene & Entity Engine) + +ระบบฉากของ LunarCore ถูกควบคุมด้วยคลาส `Scene.java` โดยอิงตามแนวคิดของเกม: +- **Plane (`MazePlaneExcel`)**: ขอบเขตของแผนที่หลัก (เช่น Space Station, Jarilo-VI, Xianzhou Luofu, Penacony) +- **Floor (`FloorInfo`)**: ชั้นย่อยในฉากนั้นๆ (โหลดมาจาก `LevelOutput/RuntimeFloor/*.json`) +- **Groups (`GroupInfo`)**: กลุ่มของ Entity ที่อยู่ในฉาก (จุดเกิดของมอนสเตอร์, หีบสมบัติ, เสาฟื้นฟูเลือด, ประตูมิติ) + +```mermaid +classDiagram + class Scene { + -Player player + -int planeId + -int floorId + -Int2ObjectMap~GameEntity~ entities + -IntSet avatarEntityIds + +loadGroup(groupId) + +addEntity(entity) + +removeEntity(entity) + +onTick() + } + + class GameEntity { + <> + +getEntityId() int + +getPos() Position + +getRot() Position + +toProto() SceneEntityInfo + } + + class GameAvatar { + -int avatarId + -int currentHp + -int currentSp + } + + class EntityMonster { + -int monsterId + -int stageId + -int worldLevel + -boolean isAlive + } + + class EntityProp { + -int propId + -PropType propType + -PropState propState + } + + class EntityNpc { + -int npcId + -int talkId + } + + GameEntity <|.. GameAvatar + GameEntity <|.. EntityMonster + GameEntity <|.. EntityProp + GameEntity <|.. EntityNpc + Scene o-- GameEntity +``` + +### กลไก Entity ID และ World Sync: +- Entity แต่ละตัวในฉากจะได้ `entityId` แบบ Dynamic ซึ่งเริ่มต้นรันเลขเพิ่มขึ้นเรื่อยๆ ต่อฉาก +- ผู้เล่นส่งพิกัดการเคลื่อนที่ผ่านแพ็กเก็ต `SceneEntityMoveCsReq` +- เซิร์ฟเวอร์อัปเดตพิกัด `pos` และ `rot` ของ `Player` ในหน่วยความจำ และกระจายแพ็กเก็ต `SceneEntityMoveScNotify` +- **ระบบ Space Anchor (เสาฮีล)**: ค้นหาเสาที่ใกล้ที่สุดในฉากผ่าน `Scene.getNearestSpring()` เพื่อใช้เป็นจุดฟื้นฟู HP/MP หรือจุดวาปกลับเมื่อพ่ายแพ้ในการต่อสู้ + +--- + +## 7. ระบบการต่อสู้ (Battle Orchestration & Reconciliation) + +จุดเด่นของสถาปัตยกรรม Honkai: Star Rail คือ **การจำลองลูปการต่อสู้เทิร์นเบสเกิดขึ้นที่ Client-side ทั้งหมด** โดยเซิร์ฟเวอร์ทำหน้าที่เป็น Authoritative Orchestrator ในการเตรียมข้อมูลด่าน และตรวจสอบผลลัพธ์หลังสู้เสร็จ + +```mermaid +sequenceDiagram + autonumber + actor Client as Game Client + participant BattleSvc as BattleService + participant Battle as Battle Instance + participant DropSvc as DropService + + Note over Client,BattleSvc: 1. การเปิดฉากต่อสู้ในโลกเปิด + Client->>BattleSvc: SceneCastSkillCsReq (ใช้วิชา Technique หรือโจมตีก่อน) + alt เป็นมอนสเตอร์โจมตีผู้เล่น + BattleSvc->>Battle: สร้าง Battle พร้อม Ambush Buff (1000102) + else เป็นผู้เล่นโจมตีก่อน + BattleSvc->>Battle: สร้าง Battle พร้อม Element Weakness Debuff ของตัวละคร + end + + BattleSvc->>Battle: รวบรวม Monster Waves จาก StageExcel + BattleSvc->>Battle: รวบรวมค่าสถานะ HP/SP/Buffs ปัจจุบันของทีม + BattleSvc-->>Client: SceneCastSkillScRsp (SceneBattleInfo Proto) + + Note over Client: 2. ไคลเอนต์จำลองการต่อสู้แบบเทิร์นเบสในตัวเครื่อง + + Note over Client,BattleSvc: 3. การรายงานผลลัพธ์การต่อสู้ + Client->>BattleSvc: PVEBattleResultCsReq (EndStatus, BattleStatistics) + + alt ผู้เล่นชนะ (BATTLE_END_WIN) + BattleSvc->>DropSvc: คำนวณรางวัลและของดรอป (Drops) + BattleSvc->>BattleSvc: หักค่าพลังกาย Stamina (ถ้ามี) + BattleSvc->>Client: ลบมอนสเตอร์ตัวนั้นออกจาก Scene + else ผู้เล่นแพ้ (BATTLE_END_LOSE) + BattleSvc->>BattleSvc: รีเซ็ต HP ตัวละครเหลือ 20% + BattleSvc->>Client: วาปตัวละครกลับไปเสา Space Anchor ที่ใกล้ที่สุด + else ผู้เล่นกดยอมแพ้ (BATTLE_END_QUIT) + BattleSvc->>Client: คืนค่าสถานะเดิมและวาปกลับเสา + end + + BattleSvc->>BattleSvc: อัปเดต HP และ SP สุทธิของตัวละครลง DB + BattleSvc-->>Client: PVEBattleResultScRsp (รางวัลที่ได้รับ, EXP) +``` + +### รายละเอียดโครงสร้าง `SceneBattleInfo`: +- `battleId`: เลขรันประจำการต่อสู้ของผู้เล่น +- `stageId`: ID ด่านจาก `StageExcel` +- `logicRandomSeed`: เลขสุ่ม Seed สำหรับให้ Client ใช้คำนวณ Random Roll ในเทิร์น +- `monsterWaveList`: รายการ Wave ของมอนสเตอร์ (Level, Monster ID, Elite Affixes) +- `battleAvatarList`: ข้อมูลตัวละคร 4 ตัวในทีม (Level, Promotion, Relics, Light Cone, ค่า HP/SP ปัจจุบัน) +- `buffList`: บัฟที่ติดมาจากโลกเปิด เช่น ยาบัฟอาหาร, วิชา Technique ก่อนเข้าสู้ +- `battleTargetInfo`: เงื่อนไข 3 ดาว สำหรับด่าน Forgotten Hall / Pure Fiction + +--- + +## 8. ระบบโหมดการเล่นขั้นสูง (Simulated Universe & Challenge Modes) + +### 8.1 Simulated Universe (Rogue Engine) +ระบบจำลองจักรวาล (Simulated Universe) ถูกแยกการทำงานไว้ในแพ็กเกจ `emu.lunarcore.game.rogue`: +- **`RogueInstance`**: ตัวแทนของรอบการเล่นปัจจุบัน (Run State) + - เก็บค่า `RogueRoomData`: ห้องต่อสู้, ห้อง Elite, ห้องเหตุการณ์ (Event), ห้องพัก (Respite), ห้องบอส + - จัดการสกิลพอยต์จักรวาล Cosmic Fragments (Item ID: 31) +- **ระบบเลือกพร (Blessings / Buffs)**: + - ควบคุมผ่าน `RogueBuffSelectMenu` + - เมื่อชนะห้องต่อสู้ เซิร์ฟเวอร์สุ่มพรตาม Path ที่เลือก และส่ง `AddRogueBuffScNotify` +- **ระบบวัตถุหายาก (Curios / Miracles)**: + - ควบคุมผ่าน `RogueMiracleSelectMenu` ให้ผู้เล่นเลือกรับ Curio +- **ระบบเหตุการณ์โต้ตอบ (Interactive Events)**: + - ควบคุมผ่าน `RogueEventManager` และ `RogueEventInstance` + - ไคลเอนต์ส่ง `SelectRogueCommonDialogueOptionCsReq` เมื่อผู้เล่นเลือกตัวเลือกในเหตุการณ์ เซิร์ฟเวอร์คำนวณผลกระทบ (ได้เงิน, เสียเลือด, ได้พร) แล้วส่งผลลัพธ์กลับ + +### 8.2 Forgotten Hall & Pure Fiction (Challenge Subsystem) +- ควบคุมผ่าน `ChallengeManager.java` และ `ChallengeInstance.java` +- จัดการรอบ (Rounds Limit) เช่น กำหนดรอบเหลือไม่ต่ำกว่า 20 เพื่อรับ 3 ดาว +- โหลด Lineup แบบแยกอิสระ (ทีมครึ่งแรก และทีมครึ่งหลัง) ผ่าน `PlayerExtraLineup` +- บันทึกประวัติและดาวที่ทำได้ลงคอลเลกชัน `challenges` + +--- + +## 9. ระบบตัวละคร, คลังสัมภาระ และไอเทม (Avatar & Inventory Subsystem) + +### 9.1 Multi-Path Switching (ระบบเปลี่ยนคลาส Trailblazer) +ใน Honkai: Star Rail ตัวเอก (Trailblazer) สามารถเปลี่ยนเส้นทาง Path ได้ (ทำลายล้างกายภาพ, อนุรักษ์ไฟ, ประสานจินตภาพ, ล่าสังหารน้ำแข็ง): +- LunarCore ออกแบบผ่านคลาส `AvatarMultiPath.java` +- เมื่อผู้เล่นกดเปลี่ยน Path ไคลเอนต์จะส่ง `SetAvatarPathCsReq`: + 1. เซิร์ฟเวอร์ดึงข้อมูลจาก `MultiplePathAvatarExcel` + 2. โหลด `AvatarMultiPath` ที่บันทึกเลเวลสกิลของ Path นั้นๆ + 3. สลับข้อมูล `skills`, `equips`, และโมเดลเพศ (`gender`) ใน `GameAvatar` + 4. บันทึกลงตาราง `curAvatarPaths` ในเอกสาร `players` + 5. ส่งแพ็กเก็ต `PacketAvatarPathChangedNotify` และ `PacketPlayerSyncScNotify` ไปยัง Client + +### 9.2 ระบบสุ่มค่า Relic (Main Affix & Sub Affix Step Generator) +การสร้าง Relic ใน `GameItem.java` อิงตามหลักคณิตศาสตร์ของเกมจริง: + +``` +[Main Affix Allocation] +1. ตรวจสอบชิ้นส่วนของ Relic (Head, Hands, Body, Feet, Planar Sphere, Link Rope) +2. ดึงตาราง MainAffixGroup จาก RelicExcel +3. สุ่มค่า Stat หลักตาม Weight Distribution ใน GameDepot + +[Sub Affix Allocation] +1. คำนวณจำนวน Sub Affix เริ่มต้นตาม Rarity: + Base Count = clamp(Rarity - 2, 0, 3) + randomRange(0, 1) + (ตัวอย่างเช่น Relic 5 ดาว จะได้ Sub Affix เริ่มต้น 3 หรือ 4 แถว) +2. สุ่มเลือก Stat รองโดยห้ามซ้ำกับ Main Affix และห้ามซ้ำกับ Sub Affix อื่นที่มีอยู่แล้ว +3. สำหรับแต่ละ Sub Affix จะสุ่มค่า Step (Roll Quality): + Step มีค่า 0, 1, หรือ 2 (แทนค่า Small, Medium, Large Roll) +``` + +เมื่อมีการอัปเกรด Relic ทุกๆ 3 เลเวล (`HandlerExpUpRelicCsReq`): +- หากจำนวน Sub Affix ยังไม่ครบ 4 แถว ระบบจะเพิ่มแถวใหม่ +- หากครบ 4 แถวแล้ว ระบบจะสุ่มเลือก 1 แถวที่มีอยู่เพื่อเพิ่ม `count` และสุ่มค่า `step` ใหม่ + +--- + +## 10. ระบบ Gacha และคำสั่งเซิร์ฟเวอร์ (Gacha, Commands & Plugins) + +### 10.1 สถาปัตยกรรมตู้กาชา (`GachaService.java`) +- **การ Hot-Reload ด้วย `WatchService`**: ตัวบริการลงทะเบียน File Watcher ตรวจจับไฟล์ `data/Banners.json` หากผู้ดูแลแก้ไขไฟล์ระบบจะ reload ตู้กาชาทันทีโดยไม่ต้องรีสตาร์ตเซิร์ฟเวอร์ +- **การคำนวณการันตี (Pity Mechanics)**: + - 5-Star Soft Pity: อัตราพื้นฐาน 0.6% เมื่อเปิดถึงโรลที่ 74 อัตราจะเพิ่มขึ้นแบบเส้นตรงจนถึง 100% ที่โรล 90 + - ระบบหลุดเรท 50/50: หากเปิดได้ 5 ดาวที่ไม่ใช่หน้าตู้ ตัวแปร `isFeatured` จะถูกเซ็ตเป็น `true` สำหรับ 5 ดาวรอบถัดไป + - การันตี 4 ดาว: ทุกๆ 10 โรล + +### 10.2 เครื่องมือคำสั่งเซิร์ฟเวอร์ (Command Engine) +- ทำงานผ่านทั้งคอนโซลเซิร์ฟเวอร์ และช่องทางแชตในเกม +- ในเกมจะมีบัญชีปลอมชื่อ **"Server" (UID: 99)** ปรากฏในรายชื่อเพื่อนโดยอัตโนมัติ +- ผู้เล่นส่งข้อความที่ขึ้นต้นด้วย `/` หรือ `!` (เช่น `/give 1001`, `/heal`, `/scene 20001 20001001`) +- `ChatManager` จะดักจับข้อความและส่งต่อให้ `CommandManager.invoke(player, message)` +- ตรวจสอบสิทธิ์ผ่านระบบ RBAC: `account.hasPermission(command.permission())` + +### 10.3 ระบบปลั๊กอิน (Plugin Architecture) +- รองรับการโหลดไฟล์ JAR จากโฟลเดอร์ `plugins/` +- แต่ละปลั๊กอินมี `plugin.json` ระบุ Main Class, Version, Author +- โหลดผ่าน `URLClassLoader` อิสระ +- มี Lifecycle Hooks: `onLoad()`, `onEnable()`, `onDisable()` + +--- + +## 11. สรุปข้อสังเกตและคู่มือการนำไปพัฒนาต่อสำหรับ AI + +หากโมเดล AI ตัวอื่นต้องการนำข้อมูลสถาปัตยกรรมนี้ไปเขียนโค้ดต่อ หรือสร้างเซิร์ฟเวอร์ใหม่ในภาษาอื่น (เช่น Go, Rust, C#): + +1. **Protocol Framing**: ห้ามลืมกรอบ 16 ไบต์เด็ดขาด (`0x9D74C714` นำหน้า และ `0xD7A152C8` ปิดท้าย) หากไบต์ขาดหรือผิดแฮนด์เชกจะค้าง +2. **Gateway Encoding**: แพ็กเก็ต HTTP Gateway (`/query_dispatch` และ `/query_gateway`) ต้องแปลงเป็น Protobuf ไบนารีก่อน แล้วค่อยครอบด้วย **Base64 String** ส่งเป็นเนื้อหา HTTP Response +3. **Turn-based Battle Paradigm**: เซิร์ฟเวอร์ไม่ต้องสร้าง AI คำนวณดาเมจต่อเทิร์น แต่ต้องเตรียม `SceneBattleInfo` ให้ครบถ้วน (ตัวละคร, มอนสเตอร์แต่ละเวฟ, บัฟ, ค่าพลังกาย) แล้วรอรับผลสรุป `PVEBattleResultCsReq` จาก Client +4. **Data Synchronization (Syncable)**: หลังการเปลี่ยนแปลงไอเทมหรือตัวละคร ต้องส่ง `PacketPlayerSyncScNotify` เสมอ เพื่อให้หน้าต่าง UI ของเกมแสดงผลตรงกับฐานข้อมูล +5. **Session States**: รักษา State Flow ให้เคร่งครัดตามลำดับ `WAITING_FOR_TOKEN` -> `WAITING_FOR_LOGIN` -> `ACTIVE` อย่าตอบรับคำสั่งเกมก่อนเซสชันจะอยู่ในสถานะ `ACTIVE` diff --git a/docs/scratch/march7thhoney_architecture_deep_dive.md b/docs/scratch/march7thhoney_architecture_deep_dive.md new file mode 100644 index 0000000..2af1daa --- /dev/null +++ b/docs/scratch/march7thhoney_architecture_deep_dive.md @@ -0,0 +1,358 @@ +# รายงานการวิเคราะห์สถาปัตยกรรมและการทำงานของ March7thHoney (Deep-Dive Technical Specification) + +> **เอกสารอ้างอิงทางเทคนิคระดับลึกสำหรับวิศวกรซอฟต์แวร์และโมเดลปัญญาประดิษฐ์ (AI Systems)** +> วิเคราะห์จากซอร์สโค้ดของโครงการ: [Mar7thLover/March7thHoney-OpenSource](https://github.com/Mar7thLover/March7thHoney-OpenSource) +> ครอบคลุมสถาปัตยกรรม .NET 10, Roslyn Source Generators (NativeAOT), KcpSharp, SQLite/MySQL Storage Pipeline, Lua Client Injection, Calyx/FreeSR Scenario Injection, Muip GM API และโหมดเกมเวอร์ชันใหม่ (Apocalyptic Shadow, Divergent Universe, Currency War) + +--- + +## สารบัญ (Table of Contents) +1. [ภาพรวมสถาปัตยกรรมระดับสูง (High-Level Architecture & Tech Stack)](#1-ภาพรวมสถาปัตยกรรมระดับสูง-high-level-architecture--tech-stack) +2. [การออกแบบ NativeAOT และ Roslyn Source Generators](#2-การออกแบบ-nativeaot-และ-roslyn-source-generators) +3. [เลเยอร์เครือข่ายและการเข้ารหัสขั้นสูง (Network, KcpSharp & Crypto)](#3-เลเยอร์เครือข่ายและการเข้ารหัสขั้นสูง-network-kcpsharp--crypto) + - 3.1 KcpSharp Transport และการจัดการบัฟเฟอร์ ArrayPool + - 3.2 Binary Packet Framing & Buffer Overflow Protection + - 3.3 Ec2b Key Derivation และ MT19937 XOR Key Pipeline +4. [กลไกการฉีดสคริปต์เข้าไคลเอนต์ (Client Lua Script Injection Engine)](#4-กลไกการฉีดสคริปต์เข้าไคลเอนต์-client-lua-script-injection-engine) +5. [ระบบจัดเก็บข้อมูลและการจำลองฐานข้อมูล (Persistence & Database Pipeline)](#5-ระบบจัดเก็บข้อมูลและการจำลองฐานข้อมูล-persistence--database-pipeline) + - 5.1 Dual-Engine: SQLite & MySQL + - 5.2 In-Memory Dirty Cache & Asynchronous Periodic Flusher +6. [ระบบแคชทรัพยากรเกมความเร็วสูง (High-Speed Binary Resource Cache)](#6-ระบบแคชทรัพยากรเกมความเร็วสูง-high-speed-binary-resource-cache) +7. [ระบบการต่อสู้และการแทรกแซงสถานการณ์ (Battle & Calyx Override Engine)](#7-ระบบการต่อสู้และการแทรกแซงสถานการณ์-battle--calyx-override-engine) + - 7.1 ลูปการต่อสู้หลัก + - 7.2 Calyx Override & FreeSR JSON Scenario Injection +8. [ระบบโหมดการเล่นขั้นสูง (Modern End-Game Modes)](#8-ระบบโหมดการเล่นขั้นสูง-modern-end-game-modes) + - 8.1 ChallengePeak (Apocalyptic Shadow & Anomaly Arbitration) + - 8.2 Divergent Universe & Unknowable Domain + - 8.3 Currency War & GridFight (Aetherium Wars) +9. [การจัดการระยะไกลและหน้าต่างควบคุม (Muip Protocol & Admin Dashboard)](#9-การจัดการระยะไกลและหน้าต่างควบคุม-muip-protocol--admin-dashboard) + - 9.1 Muip GM Protocol (RSA-Encrypted Remote Command Execution) + - 9.2 Built-in Embedded Web Admin & Registration Portal + - 9.3 Cluster Deployment via ServerExchange Secret +10. [ตารางเปรียบเทียบเชิงลึก: LunarCore vs March7thHoney](#10-ตารางเปรียบเทียบเชิงลึก-lunarcore-vs-march7thhoney) +11. [คู่มือการต่อยอดเชิงวิศวกรรมสำหรับ AI (AI Implementation Guidelines)](#11-คู่มือการต่อยอดเชิงวิศวกรรมสำหรับ-ai-ai-implementation-guidelines) + +--- + +## 1. ภาพรวมสถาปัตยกรรมระดับสูง (High-Level Architecture & Tech Stack) + +**March7thHoney** เป็นโครงการ Server Reimplementation สำหรับเกม *Honkai: Star Rail* ที่เขียนขึ้นใหม่ทั้งหมดด้วยภาษา **C# บน .NET 10** โดยมุ่งเน้นที่: +1. **ประสิทธิภาพสูงสุด (Extreme Performance)**: ลด Allocation ของหน่วยความจำผ่าน `Span`, `Memory`, `ArrayPool`, และหลีกเลี่ยง Reflection +2. **NativeAOT Readiness**: คอมไพล์เป็นเนทีฟไบนารีเบ็ดเสร็จ (Single self-contained binary) ได้โดยไม่ต้องใช้ JIT Runtime +3. **ฟีเจอร์เวอร์ชันใหม่ (Modern Game Modes)**: รองรับคอนเทนต์เวอร์ชันใหม่ (2.x ถึง 4.x) เช่น Apocalyptic Shadow, Divergent Universe, Currency War, Penacony Clockie Dial, Critter Pick (Cat Cake Catch) +4. **ความสามารถในการปรับแต่ง (Developer & Testing Friendly)**: สามารถแทรกแซงการต่อสู้ผ่านไฟล์สคริปต์ FreeSR JSON (`CalyxOverrideManager`) และมีระบบเว็บแอดมิน Dashboard ในตัว + +### โครงสร้างโมดูลย่อย (Solution Structure): +- `Program/`: จุดเริ่มต้นเซิร์ฟเวอร์ (`EntryPoint.cs`), LifeCycle Management, CLI Console, การจัดการล็อก +- `GameServer/`: บริการเกม KCP, GameSession/Connection, ตัวจัดการเกม (50+ Game Managers), ตัวถอดรหัสแพ็กเก็ต (51 หมวดหมู่) +- `WebServer/`: ASP.NET Core Kestrel (Dispatch, Gateway, Muip API, Web Admin, Registration, SDK Shop) +- `Command/`: เครื่องมือคำสั่งเซิร์ฟเวอร์แบบ Compile-time Registry +- `Common/`: ตัวแบบข้อมูลหลัก, คอนฟิก, โมเดลฐานข้อมูล, รหัสข้อผิดพลาด และเครื่องมือยูทิลิตี +- `KcpSharp/`: ไคลเอนต์/เซิร์ฟเวอร์ KCP แบบ Pure C# Zero-allocation +- `Proto/`: Google Protobuf C# Generated Classes +- `SourceGen/`: C# Roslyn Incremental Source Generators สำหรับสร้างโค้ดแบบไร้ Reflection + +```mermaid +flowchart TB + subgraph Client["HSR Game Client (PC / Android / iOS)"] + HTTPClient["HTTP/HTTPS Requests"] + KCPClient["KCP/UDP Traffic"] + end + + subgraph March7thHoney[".NET 10 Server Architecture"] + subgraph WebServerModule["WebServer (ASP.NET Core Kestrel)"] + DispatchCtrl["DispatchRoutes (/query_dispatch)"] + GatewayCtrl["GateServerRoutes (/query_gateway)"] + AdminCtrl["AdminRoutes (Embedded Dashboard)"] + MuipCtrl["MuipServerRoutes (/muip/*)"] + ClusterCtrl["ServerExchangeRoutes (Distributed Sync)"] + end + + subgraph KcpNetworkLayer["KCP Transport (KcpSharp)"] + KcpListener["March7thHoneyListener"] + Connection["Connection (ArrayPool & MT19937 Xor)"] + HandlerMgr["HandlerManager (Generated Registry)"] + end + + subgraph GameplayEngine["Gameplay Engine & Services"] + PlayerInst["PlayerInstance"] + subgraph Managers["Player Subsystem Managers"] + AvatarMgr["AvatarManager & MultiPath"] + InvMgr["InventoryManager & Relic Generator"] + BattleMgr["BattleManager & FreeSR CalyxOverride"] + EndgameMgr["ChallengePeak (Apocalyptic Shadow)"] + RogueMgr["Divergent Universe / ChessRogue"] + end + end + + subgraph StorageLayer["Data & Persistence Layer"] + ResCache["ResourceCache (MemoryPack + GZip)"] + DbHelper["DatabaseHelper (In-Memory Dirty Cache)"] + SQLite[("SQLite (.db)")] + MySQL[("MySQL Server")] + end + end + + HTTPClient <-->|"REST / JSON"| WebServerModule + KCPClient <-->|"KCP UDP / Ec2b Framed Packets"| KcpListener + KcpListener --> Connection + Connection --> HandlerMgr + HandlerMgr --> PlayerInst + PlayerInst --> Managers + Managers --> DbHelper + DbHelper --> SQLite + DbHelper --> MySQL + Managers --> ResCache +``` + +--- + +## 2. การออกแบบ NativeAOT และ Roslyn Source Generators + +หนึ่งในปัญหาใหญ่ของเซิร์ฟเวอร์เกมที่เขียนด้วย Java หรือ C# ทั่วไปคือการพึ่งพา Reflection อย่างหนัก (เช่น การสแกนหา `@Opcodes`, การสร้าง ORM mapping) ซึ่งทำให้: +1. การบูตช้า +2. กินแรมมาก +3. **คอมไพล์ NativeAOT ไม่ได้** เพราะ Reflection รันไทม์ถูกตัดทิ้ง (Trimmed) + +March7thHoney แก้ปัญหานี้โดยสร้างคอมไพลเลอร์เสริม **Roslyn Source Generator (`SourceGen/`)**: + +```mermaid +flowchart LR + SourceCode["C# Entity & Handler Classes
[Handler(CmdIds.PlayerLoginCsReq)]"] --> Roslyn["Roslyn Source Generator"] + Roslyn --> GeneratedCode["Generated C# Partial Classes
- HandlerRegistry.g.cs
- DatabaseStores.g.cs
- CommandRegistry.g.cs"] + GeneratedCode --> NativeCompiler[".NET 10 NativeAOT Compiler"] + NativeCompiler --> NativeBinary["Single Native Executable
(Zero Reflection / Instant Boot)"] +``` + +1. **`HandlerRegistryGenerator.cs`**: + - ตรวจจับคลาสที่มีแอตทริบิวต์ `[Handler(CmdIds....)]` ในขณะคอมไพล์ + - สร้างสวิตช์เคสหรืออาร์เรย์ Lookup โดยตรง (`HandlerRegistry.g.cs`) เพื่อแมป Opcode เข้ากับ Concrete Handler Instance แบบ Zero-reflection +2. **`DatabaseStoreGenerator.cs`**: + - สร้างคลาส `EntityStores.g.cs` เพื่อจัดการ Table Creation, Insert, Update, Select สำหรับทั้ง SQLite และ MySQL ของแต่ละ Entity โดยตรง +3. **`CommandRegistryGenerator.cs`**: + - ตรวจจับคำสั่งในโฟลเดอร์ `Cmd/` แล้วสร้างตารางแมปชื่อคำสั่ง (Labels & Aliases) ตอนคอมไพล์ +4. **`MemoryPackRegistrarGenerator.cs`**: + - ลงทะเบียน Type Formatter ล่วงหน้าสำหรับ MemoryPack Binary Serializer + +--- + +## 3. เลเยอร์เครือข่ายและการเข้ารหัสขั้นสูง (Network, KcpSharp & Crypto) + +### 3.1 KcpSharp Transport และการจัดการบัฟเฟอร์ ArrayPool +March7thHoney ใช้ **`KcpSharp`** ซึ่งถูกปรับแต่งให้รันบน .NET 10: +- ในลูปรับข้อมูล `ReceiveLoop()` มีการใช้ `ArrayPool.Shared.Rent(result.BytesReceived)` +- ข้อมูลที่ประมวลผลเสร็จแล้วจะถูก `ArrayPool.Shared.Return(buffer)` คืนสู่พูลทันทีเพื่อป้องกันการจัดสรรแรมซ้ำซ้อน (Zero-Garbage Collection Pressure) + +### 3.2 Binary Packet Framing & Buffer Overflow Protection +โครงสร้างแพ็กเก็ตอิงตามข้อกำหนดของตัวเกมจริง: + +``` ++-----------------------------------------------------------------------------------+ +| Offset (Bytes) | Type | Field Name | Value / Description | ++-----------------------------------------------------------------------------------+ +| 0x00 - 0x03 | uint32 BE | HEADER_CONST | 0x9D74C714 (Magic 1) | +| 0x04 - 0x05 | uint16 BE | Opcode / CmdId | เลขรหัสคำสั่ง (ushort) | +| 0x06 - 0x07 | uint16 BE | Header Length | ขนาดส่วนหัว (ushort) | +| 0x08 - 0x0B | uint32 BE | Payload Length | ขนาด Protobuf Payload (uint) | +| 0x0C - ... | byte[] | Header Bytes | ส่วนหัวแพ็กเก็ต | +| ... - ... | byte[] | Payload Bytes | ข้อมูล Protobuf | +| End - 4 | uint32 BE | TAIL_CONST | 0xD7A152C8 (Magic 2) | ++-----------------------------------------------------------------------------------+ +``` + +> [!CAUTION] +> **การป้องกัน Memory Exhaustion Attack ใน BinaryReader (`Connection.cs`):** +> `BinaryReader.ReadBytes(n)` ใน .NET จะจัดสรร Memory ตามค่า `n` ที่อ่านได้ทันที หากไคลเอนต์ประสงค์ร้ายส่งค่า `payloadLength` สูงผิดปกติ (เช่น 2 GB) เซิร์ฟเวอร์จะเกิด Out-Of-Memory Crash ทันที +> March7thHoney จึงเพิ่มระบบป้องกัน: +> ```csharp +> var remaining = br.BaseStream.Length - br.BaseStream.Position; +> if (headerLength + (long)payloadLength > remaining) { +> Logger.Error("Bad Data Package: payloadLength exceeds remaining buffer"); +> return; +> } +> ``` + +### 3.3 Ec2b Key Derivation และ MT19937 XOR Key Pipeline +หากเปิดตัวเลือก `UsePacketEncryption = true` ในคอนฟิก: +1. เซิร์ฟเวอร์จะโหลดหรือสร้างไฟล์คีย์ `ClientSecretKey.ec2b` (`HEAD_MAGIC = 1647469381` หรือ ASCII `"Ec2b"`) +2. ตัวคีย์บรรจุ: + - 16 ไบต์คีย์หลัก (`Key`) + - 2048 ไบต์เวกเตอร์การถอดรหัส (`Data`) +3. ตัวสร้างสุ่ม Mersenne Twister **MT19937 (64-bit)** ถูกนำมาใช้สร้างตาราง XOR Key ขนาด 4,096 ไบต์ (`Crypto.GenerateXorKey(seed)`) +4. ทุกแพ็กเก็ตที่รับเข้าหรือส่งออกจะถูกทำการ XOR ด้วยตารางคีย์นี้ + +--- + +## 4. กลไกการฉีดสคริปต์เข้าไคลเอนต์ (Client Lua Script Injection Engine) + +ฟีเจอร์เด่นของ March7thHoney คือการสื่อสารกับ Unity Client โดยตรงผ่านการส่งโค้ดภาษา Lua ผ่าน `HandshakePacket` ซึ่งไคลเอนต์เกมจะรันสคริปต์นี้บนตัวเครื่องโดยอัตโนมัติ: + +### 1. ระบบ Watermark (`BuildWatermarkLua`): +เซิร์ฟเวอร์ส่งโค้ด Lua ไปค้นหา GameObject ของ Unity ในหน้าต่างเกม: +```lua +local function modify_texts() + local vGo = CS.UnityEngine.GameObject.Find("VersionText") + if vGo == nil then return end + local vt = vGo:GetComponent("Text") + vt.supportRichText = true + vt.text = "March7thHoney | UID: 10001" + vt.fontSize = 18 +end +pcall(modify_texts) +``` +ทำให้หน้าจอเกมแสดงชื่อเซิร์ฟเวอร์, UID, เวอร์ชัน หรือข้อความไล่เฉดสี (Color Gradient) โดยไม่ต้องแก้ไขไฟล์ตัวเกมของไคลเอนต์ + +### 2. ระบบจำลองสถานะเครือข่าย (`NetStatusLua`): +ส่งโค้ด Lua ไปควบคุมไอคอน Ping และแสดงค่า Latency คงที่ตามที่ตั้งไว้ในคอนฟิก + +--- + +## 5. ระบบจัดเก็บข้อมูลและการจำลองฐานข้อมูล (Persistence & Database Pipeline) + +### 5.1 Dual-Engine: SQLite & MySQL +March7thHoney รองรับทั้งฐานข้อมูลเดี่ยว (SQLite สำหรับ Local/Single-player) และฐานข้อมูลเซิร์ฟเวอร์ (MySQL สำหรับ Production): +- กำหนดใน `config.json` $\rightarrow$ `Database.DatabaseType`: `"sqlite"` หรือ `"mysql"` +- ตารางทั้งหมดถูกสร้างขึ้นอัตโนมัติเมื่อเริ่มระบบผ่าน `EntityStores.Map.Values.EnsureTable(conn)` + +### 5.2 In-Memory Dirty Cache & Asynchronous Periodic Flusher +เพื่อลด I/O คอขวด March7thHoney ไม่เขียนข้อมูลลงดิสก์ทุกครั้งที่มีการเปลี่ยนไอเทม แต่ใช้สถาปัตยกรรม **In-Memory Cache ร่วมกับ Dirty Tracking**: + +```mermaid +sequenceDiagram + autonumber + participant GameAction as Game Action (e.g. Add Item) + participant MemoryMap as UidInstanceMap (In-Memory) + participant DirtySet as DirtyUids (HashSet) + participant Flusher as SaveThread (Background Task) + participant Disk as SQLite / MySQL Database + + GameAction->>MemoryMap: Update Entity Object in Memory + GameAction->>DirtySet: DatabaseHelper.MarkDirty(playerUid) + Note over MemoryMap,DirtySet: ผู้เล่นเล่นเกมต่อได้ทันที โดยไม่มี I/O Latency + + loop ทุกๆ 1 วินาที (CalcSaveDatabase) + Flusher->>DirtySet: ดึงรายการ UIDs ที่มีการแก้ไข (DirtyLock) + Flusher->>MemoryMap: อ่าน Snapshot ข้อมูลของ UID นั้นๆ + Flusher->>Disk: ทำ Batch Transaction บันทึกลงฐานข้อมูล + Flusher->>DirtySet: ลบ UID ออกจาก DirtySet + end +``` + +> [!TIP] +> **NativeAOT Thread Safepoint Fix (`DatabaseHelper.cs`):** +> ใน .NET 10 โค้ดที่รันวนลูปแน่น (Tight Loop) ภายใต้ NativeAOT จะไม่มีจุด Safepoint สำหรับ Garbage Collector ทำให้เกิดสภาวะ Deadlock +> March7thHoney จึงใส่ `Thread.Sleep(1000)` ใน `SaveThread` เพื่อเปิดช่อง Safepoint ให้ GC ทำงานได้อย่างสมบูรณ์ + +--- + +## 6. ระบบแคชทรัพยากรเกมความเร็วสูง (High-Speed Binary Resource Cache) + +โดยทั่วไป เซิร์ฟเวอร์เกมต้องอ่านไฟล์ JSON นับร้อยไฟล์จาก `ExcelOutput/` ซึ่งใช้เวลา 30 ถึง 60 วินาทีในทุกครั้งที่เปิดเครื่อง +March7thHoney แก้ปัญหานี้ด้วย **`ResourceCache.cs`**: + +1. **First Boot (สร้างแคชครั้งแรก)**: + - อ่าน JSON จาก `Resources/ExcelOutput/` และ `Resources/ExcelOutputGameCore/` + - รวบรวมข้อมูลลงใน `GameDataSnapshot` + - ทำ Serialization ด้วย **`MemoryPack`** ซึ่งเป็น Zero-allocation Binary Serializer + - บีบอัดข้อมูลด้วย **`GZipStream(CompressionLevel.Optimal)`** บันทึกเป็นไฟล์ `Config/Resource.bin` (ขนาดประมาณ 20-30 MB) +2. **Subsequent Boots (การบูตครั้งถัดไป)**: + - ตรวจสอบความถูกต้องของ Magic Header GZip (`0x1F 0x8B`) + - คลายการบีบอัดและคืนค่าเข้า `GameData` ภายใน **ไม่เกิน 1 วินาที** +3. **Operator Hot-Reload สำหรับ Banners**: + - ไฟล์ `Banners.json` (ตู้กาชาที่ผู้ดูแลปรับแต่งได้เอง) จะไม่ถูกรวมเข้า `Resource.bin` แต่จะถูกอ่านแยกสด เพื่อให้แก้ไขและเปิด/ปิดตู้กาชาได้ทันทีโดยไม่ต้องล้างแคชเกมใหม่ + +--- + +## 7. ระบบการต่อสู้และการแทรกแซงสถานการณ์ (Battle & Calyx Override Engine) + +### 7.1 ลูปการต่อสู้หลัก +- เริ่มต้นจาก `BattleManager.StartBattle()` เมื่อมี Technique Cast หรือมอนสเตอร์โจมตี +- สร้าง `BattleInstance` ซึ่งรวบรวม Wave มอนสเตอร์, เลเวลตัวละคร, MazeBuff (บัฟจุดอ่อนของธาตุที่โจมตี) +- รองรับด่านหลากหลายรูปแบบ: Mainline, Cocoon (ฟาร์มของ), FarmElement, BattleCollege (บทเรียนการต่อสู้) + +### 7.2 Calyx Override & FreeSR JSON Scenario Injection +ฟีเจอร์ระดับสูงที่สำคัญที่สุดใน March7thHoney คือ **`CalyxOverrideManager.cs`** และ **`FreesrShared.cs`**: +- ผู้พัฒนาสามารถใส่ไฟล์สถานการณ์จำลองในรูปแบบ **FreeSR JSON** ไว้ในไดเรกทอรี `Config/Json/*.json` +- เมื่อเปิดใช้งานผ่านคำสั่ง `/calyx` หรือ `CalyxOverrideManager.Enable()`: + - เซิร์ฟเวอร์จะดักจับการเข้าด่าน Calyx (ดอกไม้ฟาร์มเงิน/EXP) ในโลกเปิด + - แทนที่มอนสเตอร์และด่านเดิมของ Calyx ด้วยการต่อสู้ที่ระบุในไฟล์ FreeSR JSON ทันที + - สามารถสลับทีมตัวละคร (`TrySetLineupOverride`), ปรับแต่งเลือดมอนสเตอร์เป็นรายตัวและรายเวฟ (`TrySetMonsterHp`) + - ทำให้สามารถทดสอบบอสใหม่, มอนสเตอร์จากแพตช์ล่วงหน้า หรือจัด Challenge พิเศษได้โดยไม่ต้องแก้เกม + +--- + +## 8. ระบบโหมดการเล่นขั้นสูง (Modern End-Game Modes) + +March7thHoney เป็นหนึ่งในเซิร์ฟเวอร์ไม่กี่แห่งที่รองรับโหมดการเล่นระดับ Endgame ยุคใหม่ของ Honkai: Star Rail: + +### 8.1 ChallengePeak (Apocalyptic Shadow & Anomaly Arbitration) +- ควบคุมผ่าน `ChallengePeakManager.cs` +- รองรับโหมด **Apocalyptic Shadow (เงามายาแห่งกลียุค)** และ **Anomaly Arbitration** +- จัดการบอสที่มีหลายเฟสและกลไกพิเศษ (เช่น บอสมีเกจ Toughness พิเศษ) +- รองรับการจัดทีมแบบ Tierce (ด่าน 3 เฟส/ทีมย่อย) + +### 8.2 Divergent Universe & Unknowable Domain +- ควบคุมผ่าน `ChessRogue` และ `RogueTourn` (TournRoomGenerator) +- จัดการระบบสมการ (Equations), ถ่วงน้ำหนักพร, Curio พิเศษ และวงล้อโชคชะตา +- รองรับห้องบันทึกการสังเคราะห์ (Archive Repository) + +### 8.3 Currency War & GridFight (Aetherium Wars) +- **`GridFightManager`**: จำลองระบบมินิเกมกระดานและการแข่งขันมอนสเตอร์ (Aetherium Wars) +- **`Currency War`**: จำลองเกมสงครามการเงิน มีระบบกระดาน, ซื้อ/ขายไอเทม และร้านค้าในมินิเกม + +--- + +## 9. การจัดการระยะไกลและหน้าต่างควบคุม (Muip Protocol & Admin Dashboard) + +### 9.1 Muip GM Protocol (RSA-Encrypted Remote Command Execution) +March7thHoney พัฒนาระบบ Muip (Management User Interface Protocol) เพื่อให้แอปพลิเคชันภายนอก, Bot Discord หรือเว็บแอดมิน ติดต่อสั่งการเซิร์ฟเวอร์ได้อย่างปลอดภัยผ่านการเข้ารหัส **RSA Public/Private Key (PKCS#1)**: + +1. `POST /muip/create_session`: + - เซิร์ฟเวอร์สร้าง RSA Keypair และส่ง `rsaPublicKey` พร้อม `sessionId` กลับมา (มีอายุจำกัด) +2. `POST /muip/auth_admin`: + - ไคลเอนต์ส่ง `admin_key` ที่ถูกเข้ารหัสด้วย `rsaPublicKey` เพื่อยืนยันสิทธิ์ผู้ดูแล +3. `POST /muip/exec_cmd`: + - ส่งคำสั่งเกม (เช่น `/give 1001 x1`) ที่เข้ารหัสด้วย RSA พร้อมระบุ `TargetUid` เซิร์ฟเวอร์ถอดรหัสและรันคำสั่งทันที +4. `GET /muip/server_information` & `/muip/player_information`: + - ขอข้อมูลสถิติเซิร์ฟเวอร์ (RAM, ผู้เล่นออนไลน์) และข้อมูลไอดีผู้เล่น + +### 9.2 Built-in Embedded Web Admin & Registration Portal +- ไม่จำเป็นต้องเปิด Web Server แยกต่างหาก เพราะ WebServer ของ March7thHoney มีเว็บ Dashboard ในตัว +- เขียนด้วย Embedded HTML/CSS/JS (`AdminPageRenderer.cs`, `AdminPageScripts.cs`) +- มีหน้าระบบลงทะเบียนผู้เล่น (`RegistrationPage.cs`), หน้ารีเซ็ตรหัสผ่านผ่านอีเมล (`PasswordForgotPage.cs`, `AccountEmailSender.cs`), และหน้าจัดการไอดี + +### 9.3 Cluster Deployment via ServerExchange Secret +- รองรับการแยกรัน Dispatch Server และ Game Server บนเครื่องเซิร์ฟเวอร์คนละเครื่อง +- สื่อสารระหว่างโหนดผ่าน `/get_account_info` และ `/validate_account_token` โดยยืนยันความปลอดภัยผ่าน HTTP Header `X-March7thHoney-Server-Secret` ตรวจสอบความเท่ากันแบบคงเวลา (`FixedTimeEquals`) เพื่อป้องกัน Timing Attack + +--- + +## 10. ตารางเปรียบเทียบเชิงลึก: LunarCore vs March7thHoney + +| มิติการเปรียบเทียบ | LunarCore (Melledy) | March7thHoney (Mar7thLover) | +|---|---|---| +| **ภาษาและรันไทม์** | Java 17+ (Gradle / JVM) | C# (.NET 10 SDK) | +| **สถาปัตยกรรมประสิทธิภาพ** | Reflection รันไทม์, Quickbuf Proto | Roslyn Source Generators, **NativeAOT**, MemoryPack | +| **เครือข่าย UDP/KCP** | Java KCP (Netty UDP Channel) | **KcpSharp** (Pure C# Zero-alloc + ArrayPool) | +| **การเข้ารหัสทราฟฟิก** | XOR แบบธรรมดา (ปิดไว้เป็นค่าตั้งต้น) | รองรับ **Ec2b** Key Derivation + MT19937 XOR 4KB | +| **การเก็บข้อมูล (Storage)** | MongoDB (ภายนอก) หรือ H2 Mongo (ภายใน) | **SQLite** และ **MySQL** (In-Memory Dirty Flusher) | +| **การแคชทรัพยากรเกม** | โหลดไฟล์ JSON สดทุกครั้งที่เปิด (~15-30 วิ) | **Resource.bin** (MemoryPack + GZip) บูต < 1 วินาที | +| **การฉีดโค้ดสู่ไคลเอนต์** | ไม่มี | **Server-Pushed Lua Injection** (Watermark, Ping UI) | +| **เครื่องมือทดสอบการต่อสู้** | ผ่านคำสั่ง `/spawn` และ Stage IDs ปกติ | **Calyx Override + FreeSR JSON Scenario Injection** | +| **โหมดเกมที่รองรับ** | พื้นฐาน: MoC, Pure Fiction, SU ปกติ | ขั้นสูง: **Apocalyptic Shadow**, **Divergent Universe**, **Currency War**, Aetherium Wars | +| **ระบบบริหารจัดการ (GM/Web)**| คอนโซล และ แชตในเกมผ่านบอท UID 99 | **Muip RSA API**, Web Admin Dashboard, Web Registration | + +--- + +## 11. คู่มือการต่อยอดเชิงวิศวกรรมสำหรับ AI (AI Implementation Guidelines) + +หากโมเดล AI อื่นต้องการสร้างส่วนต่อขยาย, แก้บั๊ก หรือเขียนตัวแปลงโปรโตคอลสำหรับ March7thHoney: + +1. **อย่าใช้ Reflection สำหรับ Handler และ Database Store**: + ต้องใช้ระบบ Source Generator เสมอ หากเพิ่ม Entity ใหม่ ให้สร้างคลาสที่สืบทอดจาก `BaseDatabaseDataHelper` เพื่อให้ `DatabaseStoreGenerator` สร้างโค้ด SQL ตารางและการแมปให้อัตโนมัติ เพื่อรักษาคุณสมบัติ NativeAOT +2. **การจัดการ Entity ในหน่วยความจำ**: + เมื่อแก้ไขข้อมูล Entity ของผู้เล่น ต้องเรียก `DatabaseHelper.MarkDirty(player.Uid)` เสมอ เพื่อให้ Background Flusher รับรู้และนำไปบันทึกลง SQLite/MySQL +3. **การทดสอบฉากต่อสู้แบบกำหนดเอง**: + ใช้โครงสร้าง FreeSR JSON ในโฟลเดอร์ `Config/Json/` ร่วมกับ `CalyxOverrideManager` จะช่วยให้จำลองการต่อสู้กับบอสใดๆ ได้ทันทีโดยไม่ต้องรื้อ Resource Excel +4. **ความปลอดภัยของ Buffer**: + ในส่วนการอ่าน Binary Packets ให้คงการตรวจสอบ `remaining buffer` ไว้เสมอ เพื่อป้องกันช่องโหว่ Buffer Overflow และการจัดสรรหน่วยความจำเกินขนาด diff --git a/docs/scratch/static_cmdid_dumper_deep_dive.md b/docs/scratch/static_cmdid_dumper_deep_dive.md new file mode 100644 index 0000000..0fccf8e --- /dev/null +++ b/docs/scratch/static_cmdid_dumper_deep_dive.md @@ -0,0 +1,379 @@ +# Static CmdId Dumper (`StaticCmdIdDumper`) Architectural Specification & Machine-Code Deep Dive + +This technical specification provides an exhaustive, code-level analysis of **`StaticCmdIdDumper`** (by Hiro420), an ultra-minimalist, zero-dependency C# (.NET 8.0) static binary analysis utility. The tool is engineered to extract network packet Command IDs (`CmdId` / `Opcode`) directly from compiled IL2CPP game executables (such as `YuanShen.exe`, `StarRail.exe`, or `GameAssembly.dll`) on disk without launching the game process or relying on third-party disassemblers (such as Capstone or Iced). + +--- + +## 1. Executive Summary & Architectural Purpose + +### 1.1 The Protocol Command ID Problem +In modern anime titles developed on Unity IL2CPP (Genshin Impact, Honkai: Star Rail, Zenless Zone Zero), client-server communication relies on an enumerated command opcode (`CmdId`) identifying each Protobuf packet. + +Inside the game client assemblies, each packet class contains a getter method returning its unique opcode: +```csharp +public class GetAvatarDataCsReq : IMessage { + public override ushort GetCmdId() { + return 384; // 0x0180 + } +} +``` + +In compiled releases: +1. Method names are stripped or obfuscated (e.g., `GetCmdId()` $\rightarrow$ `KOOKJNLBAMH()`). +2. Tools like `Firefly-Static-Parser` or `honkai-dumper` can identify the **Relative Virtual Address (RVA)** of these getter methods. +3. However, obtaining the actual integer value of the opcode traditionally required: + - Attaching a live debugger / Frida script to invoke the method at runtime, or + - Opening a 100MB+ binary in IDA Pro / Ghidra and manually decompiling thousands of tiny stub functions. + +### 1.2 The `StaticCmdIdDumper` Solution +`StaticCmdIdDumper` demonstrates an elegant, lightweight alternative: +- It exploits the deterministic machine-code pattern emitted by C++ compilers when compiling a constant 16-bit integer getter. +- It parses PE (Portable Executable) headers using pure, hand-written C# structs. +- It maps the getter method's RVA directly to disk file offset, reads **exactly 4 bytes**, and extracts the embedded immediate integer via a micro-disassembler. + +``` ++----------------------------------------------------------------------------------------------------+ +| StaticCmdIdDumper Architecture | ++----------------------------------------------------------------------------------------------------+ +| | +| [ Inputs ] | +| 1. Binary File (e.g. YuanShen.exe / GameAssembly.dll) | +| 2. list.txt: Mapping of | +| Example: "KOOKJNLBAMH 0x0CCA3780" | +| | +| [ Pipeline: Zero External Dependencies ] | +| | +| +----------------------------------------------------------------------------------------------+ | +| | 1. PEHeader.cs & Misc.cs | | +| | - Parse DOS Header ('MZ') -> e_lfanew -> PE Signature ('PE\0\0') | | +| | - Parse OptionalHeader64 -> Extract Section Table | | +| | - Locate Target Section ('il2cpp' or '.text') | | +| +----------------------------------------------+-----------------------------------------------+ | +| | | +| v | +| +----------------------------------------------------------------------------------------------+ | +| | 2. File Offset Translation | | +| | FileOffset = Section.PointerToRawData + (MethodRVA - Section.VirtualAddress) | | +| +----------------------------------------------+-----------------------------------------------+ | +| | | +| v | +| +----------------------------------------------------------------------------------------------+ | +| | 3. Targeted 4-Byte Seek & Read | | +| | fs.Seek(FileOffset) -> fs.Read(buffer, 0, 4) | | +| +----------------------------------------------+-----------------------------------------------+ | +| | | +| v | +| +----------------------------------------------------------------------------------------------+ | +| | 4. Micro-Disassembler (Disassembler.cs) | | +| | Matches x86-64 pattern: 66 B8 [imm16] --> "mov ax, imm16" | | +| | Extracts Little-Endian uint16 CmdId | | +| +----------------------------------------------+-----------------------------------------------+ | +| | | +| v | +| [ Output ] | +| PacketIds.json: | +| { | +| "KOOKJNLBAMH": 1234, | +| "MGDFLHGODLH": 4567 | +| } | +| | ++----------------------------------------------------------------------------------------------------+ +``` + +--- + +## 2. Codebase Organization & Engineering Philosophy + +The repository consists of a single C# project targeting `.NET 8.0`: + +``` +StaticCmdIdDumper/ +├── StaticCmdIdDumper.sln +└── StaticCmdIdDumper/ + ├── StaticCmdIdDumper.csproj # Zero external package references () + ├── PEHeader.cs # Complete Win32/PE64 data structure definitions & binary readers + ├── Misc.cs # Section search, header dumpers, and file parsing utilities + ├── Disassembler.cs # 50-line custom x86-64 machine-code instruction decoder + └── Program.cs # CLI coordinator & JSON serializer +``` + +### 2.1 The Zero-Dependency Design +Modern .NET reverse-engineering tools typically pull in large third-party dependencies: +- `AsmResolver.PE` or `PeNet` for PE parsing. +- `Iced` or `Gee.External.Capstone` for disassembly. + +`StaticCmdIdDumper` explicitly rejects external packages, implementing: +1. Hand-coded binary reader routines (`Read16LE`, `Read32LE`, `Read64LE`). +2. Hand-crafted PE structures mirroring Windows `winnt.h` (`IMAGE_DOS_HEADER`, `IMAGE_NT_HEADERS64`, `IMAGE_SECTION_HEADER`). +3. Direct byte pattern recognition for the specific instruction opcode emitted by MSVC / Clang. + +--- + +## 3. Machine Code Mechanics: IL2CPP Opcode Getters + +### 3.1 C++ to Native x86-64 Compilation +In Unity's IL2CPP output, a constant getter function in C++ is written as: +```cpp +uint16_t GetCmdId() { + return 1234; // 0x04D2 +} +``` + +When compiled by MSVC or Clang for x86-64 with standard optimizations (`/O2` or `-O3`), the compiler emits: +```nasm +mov ax, 0x04D2 +ret +``` + +### 3.2 Binary Instruction Encoding +The x86-64 instruction encoding for `mov ax, imm16` is: + +$$\begin{array}{|c|c|c|c|} +\hline +\mathbf{0x66} & \mathbf{0xB8} & \mathbf{\text{Byte}_0} & \mathbf{\text{Byte}_1} \\ +\hline +\text{Prefix (Operand Size)} & \text{Opcode (mov ax)} & \text{Imm16 (Low)} & \text{Imm16 (High)} \\ +\hline +\end{array}$$ + +- **`0x66` (Operand-Size Override Prefix)**: The default operand size in 64-bit mode is 32-bit. The `0x66` prefix instructs the processor to decode the instruction for the 16-bit register `ax` instead of 32-bit `eax`. +- **`0xB8`**: Opcode for `mov reg16/32, imm16/32` (specifically targeting accumulator register `ax`). +- **`Byte 0` & `Byte 1`**: 16-bit little-endian immediate value representing the `CmdId`. +- **`0xC3`**: The subsequent byte (at offset +4) is the `ret` opcode. + +### 3.3 The Custom Micro-Disassembler (`Disassembler.cs`) +Because the target functions follow this invariant structure, `Disassembler.cs` decodes the opcode in just a few lines: + +```csharp +public static int Disassemble(byte[] code) +{ + int ip = 0; + + while (ip < code.Length) + { + byte opcode = code[ip]; + + switch (opcode) + { + case 0x66: // 16-bit operand override + { + if (ip + 1 < code.Length && code[ip + 1] == 0xB8) + { + if (ip + 3 < code.Length) + { + // Read 16-bit unsigned little-endian integer + ushort imm16 = BitConverter.ToUInt16(code, ip + 2); + return imm16; + } + } + ip += 1; + break; + } + default: + ip += 1; + break; + } + } + return 0; +} +``` + +--- + +## 4. PE Header Parsing & RVA-to-Offset Translation + +### 4.1 PE64 Structural Traversal +In `PEHeader.cs` and `Misc.cs`, the program traverses the binary structure: + +1. **DOS Header**: + - Reads `dosHeader.magic` (must equal `0x5A4D` / `"MZ"`). + - Reads `dosHeader.e_lfanew` at offset `0x3C`, which points to the NT PE header. +2. **PE Header & Optional Header**: + - Validates `dosHeader.pe.signature == 0x00004550` (`"PE\0\0"`). + - Determines architecture from `optionalHeader.magic`: + - `0x10B`: 32-bit PE (`x86`) + - `0x20B`: 64-bit PE (`x86_64`) + - Reads `numberOfSections` and `optionalHeaderSize`. +3. **Section Table Traversal (`ReadSections`)**: + - Iterates through `IMAGE_SECTION_HEADER` entries (40 bytes each): + ```csharp + dosHeader.section_table[idx].name = ReadStr(reader, 8); + dosHeader.section_table[idx].virtualSize = Read32LE(reader); + dosHeader.section_table[idx].virtualAddr = Read32LE(reader); + dosHeader.section_table[idx].sizeOfRawData = Read32LE(reader); + dosHeader.section_table[idx].ptrToRawData = Read32LE(reader); + ``` + +### 4.2 Locating the IL2CPP Section (`Misc.GetBaseAdd`) +In certain compiled anime games (notably Genshin Impact's `YuanShen.exe`), the linker places compiled game code into a custom named section: +```csharp +for (int idx = 0; idx < dosHeader.pe.numberOfSections; idx++) +{ + if (sections![idx].name != "il2cpp") + { + continue; + } + return sections[idx]; +} +``` + +### 4.3 RVA to Disk File Offset Translation +Memory-mapped virtual addresses (VAs and RVAs) differ from raw disk offsets due to section alignment. The conversion formula is: + +$$\text{FileOffset} = \text{Section.PointerToRawData} + (\text{RVA}_{\text{target}} - \text{Section.VirtualAddress})$$ + +In `Program.cs`: +```csharp +ulong CmdIDTestAddr = Convert.ToUInt64(pair.Value.Substring(2), 16); +var fileoffset = il2cpp_section.ptrToRawData + (CmdIDTestAddr - il2cpp_section.virtualAddr); + +byte[] buffer = new byte[4]; +using (FileStream fs = new FileStream(args[0], FileMode.Open, FileAccess.Read)) +{ + fs.Seek((long)fileoffset, SeekOrigin.Begin); + fs.Read(buffer, 0, 4); +} + +int cmdid = Disassembler.Disassemble(buffer); +packetIds[pair.Key] = cmdid; +``` + +--- + +## 5. Execution Workflow & Serialization + +### 5.1 Input Format (`list.txt`) +The tool accepts a plain whitespace-delimited text file containing pairs of function identifiers and hexadecimal RVAs: +```text +KOOKJNLBAMH 0x0CCA3780 +MGDFLHGODLH 0x0BE5AA60 +CGKBCOJMLPC 0x082AB170 +OOMIIJFHPFN 0x0D16A280 +``` +> [!NOTE] Origin of the Input List +> This list is directly obtained from the output of `Firefly-Static-Parser` or `honkai-dumper`. In `methods.json`, getter methods for packet IDs appear as: +> `"KOOKJNLBAMH": "0xCCA3780"` + +### 5.2 Output Format (`PacketIds.json`) +The resulting dictionary is serialized to formatted JSON: +```json +{ + "KOOKJNLBAMH": 101, + "MGDFLHGODLH": 102, + "CGKBCOJMLPC": 143, + "OOMIIJFHPFN": 172 +} +``` + +--- + +## 6. The Complete 5-Stage Protocol Reverse-Engineering Toolchain + +By studying `LunarCore`, `March7thHoney`, `FireflyProtoc`, `honkai-dumper`, `Firefly-Static-Parser`, and `StaticCmdIdDumper`, we can now construct the entire open-source reverse-engineering pipeline: + +``` ++----------------------------------------------------------------------------------------------------+ +| THE UNIFIED PROTOCOL EXTRACTION PIPELINE | ++----------------------------------------------------------------------------------------------------+ +| | +| [ Stage 1: Metadata Extraction ] | +| Tool: Firefly-Static-Parser (Offline) OR honkai-dumper (In-Process) | +| Input: GameAssembly.dll + global-metadata.dat (b"MHY\0") | +| Output: dump.cs (class schemas) + methods.json (method RVAs) | +| | +| [ Stage 2: Static Opcode Extraction ] | +| Tool: StaticCmdIdDumper | +| Input: Executable binary + method RVAs of getter functions | +| Process: Reads 4-byte machine code (66 B8 imm16) -> extracts uint16 CmdIds | +| Output: PacketIds.json (Map of ObfuscatedPacketName -> Integer CmdId) | +| | +| [ Stage 3: Protobuf Schema Annotation ] | +| Tool: Schema Annotator / String Literal Correlator | +| Action: Injects metadata annotations into .proto files: | +| meta uint32 cmd_id = 143; | +| uint32 id = 1 [get: (id ^ 1) + 2]; | +| | +| [ Stage 4: Specialized Protobuf Compilation ] | +| Tool: FireflyProtoc | +| Action: Compiles .proto via AST pipeline (HIR -> MIR -> LIR) | +| - ExpressionInverter: Automatically derives setter logic for wire masks | +| - MessageFactory: Emits O(1) compile-time jump table switch(cmdId) | +| Output: High-performance C# / Go protobuf stubs | +| | +| [ Stage 5: Server Protocol Routing ] | +| Tool: March7thHoney (.NET 10) / LunarCore (Java) / GaC-hkrpg-go (Go) | +| Action: Instant zero-reflection packet dispatching on KCP connections | +| | ++----------------------------------------------------------------------------------------------------+ +``` + +--- + +## 7. Technical Critique & Improvements for `GaC-hkrpg-go` + +While `StaticCmdIdDumper` is compact and effective, an industrial-grade implementation (e.g. ported to Go for `GaC-hkrpg-go`) requires resolving several architectural limitations: + +### 7.1 Limitation 1: Hardcoded Section Name (`"il2cpp"`) +In `Misc.cs`: +```csharp +if (sections![idx].name != "il2cpp") continue; +``` +- **Issue**: In *Honkai: Star Rail* and standard Unity builds, code is placed in the standard `.text` section, **not** a section named `il2cpp`. +- **Solution**: Implement dynamic RVA-to-Section resolution across **all** sections: + $$\text{Section} = \{ s \in \text{Sections} \mid s.\text{VirtualAddress} \le \text{RVA} < s.\text{VirtualAddress} + s.\text{VirtualSize} \}$$ + +### 7.2 Limitation 2: 32-Bit Register Emptiness (`mov eax, imm32`) +- **Issue**: If the method return type is `int` (32-bit signed integer) rather than `ushort`, compilers emit `mov eax, imm32` without the `0x66` prefix: + `B8 xx xx 00 00` (5 bytes: `0xB8` followed by 4-byte integer). +- **Solution**: Support both instruction patterns: + - `66 B8 [imm16]` $\rightarrow$ 16-bit opcode getter. + - `B8 [imm32]` $\rightarrow$ 32-bit opcode getter. + +### 7.3 Limitation 3: Stream Seeking Overhead +- **Issue**: Opening and seeking `FileStream` individually for every single RVA creates heavy I/O overhead for thousands of packets. +- **Solution**: Memory-map the section or load the section into a contiguous `[]byte` slice in memory for instant $O(1)$ random access. + +### 7.4 Go Port Blueprint for `GaC-hkrpg-go` +A native, high-performance Go implementation for extracting opcodes directly within the `GaC-hkrpg-go` toolset: + +```go +package pe_dumper + +import ( + "debug/pe" + "encoding/binary" + "fmt" + "os" +) + +func ExtractCmdIdFromRVA(peFile *pe.File, rva uint32, f *os.File) (uint16, error) { + // 1. Locate the containing section dynamically + for _, sec := range peFile.Sections { + if rva >= sec.VirtualAddress && rva < sec.VirtualAddress+sec.VirtualSize { + fileOffset := int64(sec.Offset + (rva - sec.VirtualAddress)) + buf := make([]byte, 4) + if _, err := f.ReadAt(buf, fileOffset); err != nil { + return 0, err + } + // 2. Pattern: 66 B8 [imm16] + if buf[0] == 0x66 && buf[1] == 0xB8 { + return binary.LittleEndian.Uint16(buf[2:4]), nil + } + // 3. Pattern: B8 [imm32] (32-bit fallback) + if buf[0] == 0xB8 { + return binary.LittleEndian.Uint16(buf[1:3]), nil + } + return 0, fmt.Errorf("unknown instruction pattern: % X", buf) + } + } + return 0, fmt.Errorf("RVA 0x%X not found in any section", rva) +} +``` + +--- + +## 8. Compliance with Repository Rules (`AGENTS.md`) + +- **Documentation & Protocol Integrity**: `StaticCmdIdDumper` extracts Command IDs purely from the machine code representation of the target client binary. It adheres strictly to the rule requiring handlers to resolve `CmdID` from verified client protocol tables without guessing or hardcoding multi-version aliases. +- **No Machine-Specific Hardcoding**: All file paths and offsets are read dynamically from command-line arguments and PE headers. +- **Zero Runtime Interference**: Operates entirely offline on the executable file, with zero memory injection, hooks, or heartbeat overhead. diff --git a/freesr-data.json b/freesr-data.json index ceb7cb5..4cbb363 100644 --- a/freesr-data.json +++ b/freesr-data.json @@ -26,6 +26,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -34,15 +42,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -78,6 +78,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -86,15 +94,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -130,6 +130,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -138,15 +146,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -182,6 +182,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -190,15 +198,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -234,6 +234,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -242,15 +250,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -286,6 +286,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -294,15 +302,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -338,6 +338,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -346,15 +354,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 50, @@ -390,6 +390,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -398,15 +406,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -442,6 +442,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -450,15 +458,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -494,6 +494,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -502,15 +510,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -546,6 +546,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -554,15 +562,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -598,6 +598,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -606,15 +614,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -650,6 +650,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -658,15 +666,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "enhanced_id": 1, @@ -703,6 +703,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -711,15 +719,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -755,6 +755,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -763,15 +771,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -807,6 +807,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -815,15 +823,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -859,6 +859,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -867,15 +875,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -911,6 +911,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -919,15 +927,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -963,6 +963,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -971,15 +979,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -1015,6 +1015,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -1023,15 +1031,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -1067,6 +1067,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -1075,15 +1083,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -1119,6 +1119,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -1127,15 +1135,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -1171,6 +1171,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -1179,15 +1187,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -1223,6 +1223,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -1231,15 +1239,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -1275,6 +1275,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -1283,15 +1291,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -1327,6 +1327,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -1335,15 +1343,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -1382,6 +1382,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -1390,15 +1398,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -1434,6 +1434,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -1442,15 +1450,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -1486,6 +1486,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -1494,15 +1502,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -1538,6 +1538,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -1546,15 +1554,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -1590,6 +1590,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -1598,15 +1606,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -1642,6 +1642,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -1650,15 +1658,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -1694,6 +1694,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -1702,15 +1710,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -1746,6 +1746,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -1754,15 +1762,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -1798,6 +1798,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -1806,15 +1814,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -1853,6 +1853,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -1861,15 +1869,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -1905,6 +1905,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -1913,15 +1921,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -1957,6 +1957,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -1965,15 +1973,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -2009,6 +2009,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -2017,15 +2025,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -2061,6 +2061,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -2069,15 +2077,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -2113,6 +2113,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -2121,15 +2129,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -2165,6 +2165,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -2173,15 +2181,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -2217,6 +2217,14 @@ }, "skills_by_anchor_type": { "1": 1, + "2": 8, + "3": 10, + "4": 8, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -2225,15 +2233,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 8, - "3": 10, - "4": 8, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -2269,6 +2269,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -2277,15 +2285,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -2321,6 +2321,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -2329,15 +2337,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -2373,6 +2373,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -2381,15 +2389,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -2425,6 +2425,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -2433,15 +2441,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -2477,6 +2477,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -2485,15 +2493,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -2529,6 +2529,14 @@ }, "skills_by_anchor_type": { "1": 4, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -2537,15 +2545,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -2581,6 +2581,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -2589,15 +2597,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -2633,6 +2633,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -2641,15 +2649,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -2685,6 +2685,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -2693,15 +2701,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -2737,6 +2737,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -2745,15 +2753,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -2792,6 +2792,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -2800,15 +2808,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -2844,6 +2844,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -2852,15 +2860,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -2896,6 +2896,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -2904,15 +2912,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -2948,6 +2948,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -2956,15 +2964,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -3000,6 +3000,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -3008,15 +3016,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -3052,6 +3052,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -3060,15 +3068,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -3104,6 +3104,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -3112,15 +3120,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -3156,6 +3156,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -3164,15 +3172,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -3208,6 +3208,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -3216,15 +3224,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -3260,6 +3260,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -3268,15 +3276,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -3314,6 +3314,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -3324,15 +3332,7 @@ "17": 1, "18": 1, "19": 6, - "2": 10, - "20": 6, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "20": 6 } }, "level": 80, @@ -3368,6 +3368,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -3376,15 +3384,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -3420,6 +3420,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -3428,15 +3436,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -3472,6 +3472,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -3480,15 +3488,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -3524,6 +3524,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -3532,15 +3540,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -3578,6 +3578,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -3588,15 +3596,7 @@ "17": 1, "18": 1, "19": 6, - "2": 10, - "20": 6, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "20": 6 } }, "level": 80, @@ -3632,6 +3632,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -3640,15 +3648,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -3686,6 +3686,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -3696,15 +3704,7 @@ "17": 1, "18": 1, "19": 6, - "2": 10, - "20": 6, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "20": 6 } }, "level": 80, @@ -3740,6 +3740,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -3748,15 +3756,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -3792,6 +3792,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -3800,22 +3808,14 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, "owner_uid": 0, "promotion": 6, - "sp_max": 130, - "sp_value": 65, + "sp_max": 140, + "sp_value": 70, "techniques": [] }, "1413": { @@ -3846,6 +3846,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -3856,15 +3864,7 @@ "17": 1, "18": 1, "19": 6, - "2": 10, - "20": 6, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "20": 6 } }, "level": 80, @@ -3900,6 +3900,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -3908,15 +3916,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -3954,6 +3954,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -3964,15 +3972,7 @@ "17": 1, "18": 1, "19": 6, - "2": 10, - "20": 6, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "20": 6 } }, "level": 80, @@ -4009,6 +4009,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -4018,15 +4026,7 @@ "16": 1, "17": 1, "18": 1, - "2": 10, - "22": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "22": 10 } }, "level": 80, @@ -4063,6 +4063,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -4072,15 +4080,7 @@ "16": 1, "17": 1, "18": 1, - "2": 10, - "22": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "22": 10 } }, "level": 80, @@ -4117,6 +4117,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -4126,15 +4134,7 @@ "16": 1, "17": 1, "18": 1, - "2": 10, - "22": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "22": 10 } }, "level": 80, @@ -4170,6 +4170,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -4178,15 +4186,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -4223,6 +4223,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -4232,15 +4240,7 @@ "16": 1, "17": 1, "18": 1, - "2": 10, - "22": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "22": 10 } }, "level": 80, @@ -4277,6 +4277,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -4286,15 +4294,7 @@ "16": 1, "17": 1, "18": 1, - "2": 10, - "22": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "22": 10 } }, "level": 80, @@ -4330,6 +4330,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -4338,15 +4346,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -4382,6 +4382,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -4390,15 +4398,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -4434,6 +4434,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -4442,15 +4450,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -4486,6 +4486,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -4494,15 +4502,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -4540,6 +4540,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -4549,16 +4557,8 @@ "16": 1, "17": 1, "18": 1, - "2": 10, "22": 10, - "23": 1, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "23": 1 } }, "level": 80, @@ -4596,6 +4596,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -4606,15 +4614,7 @@ "17": 1, "18": 1, "19": 6, - "2": 10, - "20": 6, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "20": 6 } }, "level": 80, @@ -4651,6 +4651,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -4660,15 +4668,7 @@ "16": 1, "17": 1, "18": 1, - "2": 10, - "22": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "22": 10 } }, "level": 80, @@ -4704,6 +4704,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -4712,15 +4720,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -4756,6 +4756,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -4764,15 +4772,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -4808,6 +4808,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -4816,15 +4824,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -4860,6 +4860,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -4868,15 +4876,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -4912,6 +4912,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -4920,15 +4928,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -4964,6 +4964,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -4972,15 +4980,7 @@ "15": 1, "16": 1, "17": 1, - "18": 1, - "2": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "18": 1 } }, "level": 80, @@ -5019,6 +5019,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -5029,16 +5037,8 @@ "17": 1, "18": 1, "19": 6, - "2": 10, "20": 6, - "21": 1, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "21": 1 } }, "level": 80, @@ -5077,6 +5077,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -5087,16 +5095,8 @@ "17": 1, "18": 1, "19": 6, - "2": 10, "20": 6, - "21": 1, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "21": 1 } }, "level": 80, @@ -5133,6 +5133,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -5142,15 +5150,7 @@ "16": 1, "17": 1, "18": 1, - "2": 10, - "22": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "22": 10 } }, "level": 80, @@ -5187,6 +5187,14 @@ }, "skills_by_anchor_type": { "1": 6, + "2": 10, + "3": 10, + "4": 10, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, "10": 1, "11": 1, "12": 1, @@ -5196,15 +5204,7 @@ "16": 1, "17": 1, "18": 1, - "2": 10, - "22": 10, - "3": 10, - "4": 10, - "5": 1, - "6": 1, - "7": 1, - "8": 1, - "9": 1 + "22": 10 } }, "level": 80, @@ -6048,1852 +6048,1852 @@ "rank": 5 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 99, "item_id": 20000, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 99 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 100, "item_id": 20001, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 100 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 101, "item_id": 20002, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 101 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 102, "item_id": 20003, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 102 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 103, "item_id": 20004, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 103 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 104, "item_id": 20005, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 104 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 105, "item_id": 20006, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 105 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 106, "item_id": 20007, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 106 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 107, "item_id": 20008, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 107 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 108, "item_id": 20009, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 108 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 109, "item_id": 20010, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 109 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 110, "item_id": 20011, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 110 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 111, "item_id": 20012, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 111 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 112, "item_id": 20013, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 112 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 113, "item_id": 20014, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 113 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 114, "item_id": 20015, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 114 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 115, "item_id": 20016, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 115 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 116, "item_id": 20017, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 116 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 117, "item_id": 20018, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 117 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 118, "item_id": 20019, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 118 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 119, "item_id": 20020, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 119 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 120, "item_id": 20021, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 120 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 121, "item_id": 20022, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 121 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 122, "item_id": 20023, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 122 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 123, "item_id": 20024, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 123 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 124, "item_id": 21003, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 124 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 125, "item_id": 21006, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 125 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 126, "item_id": 21007, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 126 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 127, "item_id": 21008, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 127 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 128, "item_id": 21011, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 128 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 129, "item_id": 21012, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 129 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 130, "item_id": 21013, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 130 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 131, "item_id": 21014, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 131 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 132, "item_id": 21016, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 132 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 133, "item_id": 21017, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 133 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 134, "item_id": 21019, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 134 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 135, "item_id": 21022, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 135 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 136, "item_id": 21023, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 136 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 137, "item_id": 21024, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 137 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 138, "item_id": 21025, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 138 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 139, "item_id": 21026, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 139 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 140, "item_id": 21027, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 140 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 141, "item_id": 21028, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 141 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 142, "item_id": 21029, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 142 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 143, "item_id": 21030, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 143 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 144, "item_id": 21031, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 144 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 145, "item_id": 21032, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 145 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 146, "item_id": 21033, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 146 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 147, "item_id": 21034, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 147 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 148, "item_id": 21035, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 148 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 149, "item_id": 21036, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 149 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 150, "item_id": 21037, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 150 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 151, "item_id": 21038, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 151 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 152, "item_id": 21039, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 152 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 153, "item_id": 21040, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 153 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 154, "item_id": 21041, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 154 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 155, "item_id": 21043, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 155 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 156, "item_id": 21044, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 156 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 157, "item_id": 21045, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 157 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 158, "item_id": 21046, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 158 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 159, "item_id": 21047, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 159 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 160, "item_id": 21048, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 160 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 161, "item_id": 21050, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 161 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 162, "item_id": 21051, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 162 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 163, "item_id": 21052, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 163 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 164, "item_id": 21053, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 164 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 165, "item_id": 21054, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 165 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 166, "item_id": 21055, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 166 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 167, "item_id": 21056, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 167 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 168, "item_id": 21057, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 168 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 169, "item_id": 21058, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 169 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 170, "item_id": 21060, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 170 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 171, "item_id": 21061, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 171 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 172, "item_id": 21062, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 172 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 173, "item_id": 21065, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 173 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 174, "item_id": 21066, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 174 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 175, "item_id": 22000, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 175 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 176, "item_id": 22001, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 176 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 177, "item_id": 22002, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 177 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 178, "item_id": 22003, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 178 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 179, "item_id": 22004, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 179 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 180, "item_id": 22005, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 180 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 181, "item_id": 22007, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 181 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 182, "item_id": 22008, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 182 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 183, "item_id": 23004, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 183 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 184, "item_id": 23021, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 184 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 185, "item_id": 23038, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 185 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 186, "item_id": 23055, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 186 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 187, "item_id": 24002, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 187 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 188, "item_id": 24003, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 188 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 189, "item_id": 24005, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 189 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 190, "item_id": 20023, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 190 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 191, "item_id": 20010, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 191 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 192, "item_id": 20016, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 192 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 193, "item_id": 20014, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 193 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 194, "item_id": 20015, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 194 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 195, "item_id": 20000, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 195 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 196, "item_id": 20004, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 196 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 197, "item_id": 20012, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 197 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 198, "item_id": 20023, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 198 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 199, "item_id": 20000, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 199 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 200, "item_id": 20010, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 200 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 201, "item_id": 20020, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 201 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 202, "item_id": 20021, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 202 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 203, "item_id": 20020, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 203 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 204, "item_id": 20005, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 204 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 205, "item_id": 20013, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 205 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 206, "item_id": 20005, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 206 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 207, "item_id": 20020, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 207 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 208, "item_id": 20000, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 208 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 209, "item_id": 20006, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 209 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 210, "item_id": 20021, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 210 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 211, "item_id": 20021, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 211 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 212, "item_id": 20009, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 212 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 213, "item_id": 20000, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 213 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 214, "item_id": 20015, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 214 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 215, "item_id": 20012, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 215 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 216, "item_id": 20003, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 216 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 217, "item_id": 20020, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 217 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 218, "item_id": 20011, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 218 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 219, "item_id": 20013, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 219 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 220, "item_id": 20002, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 220 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 221, "item_id": 20001, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 221 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 222, "item_id": 20004, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 222 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 223, "item_id": 20018, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 223 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 224, "item_id": 20015, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 224 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 225, "item_id": 20018, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 225 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 226, "item_id": 20021, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 226 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 227, "item_id": 20021, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 227 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 228, "item_id": 20000, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 228 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 229, "item_id": 20011, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 229 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 230, "item_id": 20001, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 230 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 231, "item_id": 20003, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 231 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 232, "item_id": 20018, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 232 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 233, "item_id": 20006, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 233 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 234, "item_id": 20007, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 234 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 235, "item_id": 20017, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 235 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 236, "item_id": 20012, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 236 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 237, "item_id": 20009, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 237 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 238, "item_id": 20018, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 238 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 239, "item_id": 20017, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 239 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 240, "item_id": 20012, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 240 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 241, "item_id": 20023, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 241 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 242, "item_id": 20019, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 242 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 243, "item_id": 20017, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 243 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 244, "item_id": 20024, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 244 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 245, "item_id": 20002, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 245 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 246, "item_id": 20021, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 246 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 247, "item_id": 20016, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 247 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 248, "item_id": 20004, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 248 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 249, "item_id": 20017, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 249 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 250, "item_id": 20003, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 250 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 251, "item_id": 20016, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 251 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 252, "item_id": 20011, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 252 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 253, "item_id": 20020, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 253 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 254, "item_id": 20004, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 254 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 255, "item_id": 20002, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 255 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 256, "item_id": 20013, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 256 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 257, "item_id": 20011, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 257 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 258, "item_id": 20002, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 258 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 259, "item_id": 20000, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 259 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 260, "item_id": 20009, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 260 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 261, "item_id": 20017, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 261 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 262, "item_id": 20008, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 262 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 263, "item_id": 20009, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 263 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 264, "item_id": 20013, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 264 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 265, "item_id": 20006, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 265 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 266, "item_id": 20009, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 266 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 267, "item_id": 20024, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 267 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 268, "item_id": 20023, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 268 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 269, "item_id": 20023, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 269 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 270, "item_id": 20008, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 270 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 271, "item_id": 20000, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 271 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 272, "item_id": 20020, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 272 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 273, "item_id": 20015, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 273 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 274, "item_id": 20000, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 274 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 275, "item_id": 20019, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 275 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 276, "item_id": 20009, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 276 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 277, "item_id": 20023, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 277 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 278, "item_id": 20023, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 278 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 279, "item_id": 20021, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 279 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 280, "item_id": 20007, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 280 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 281, "item_id": 20014, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 281 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 282, "item_id": 20020, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 282 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 283, "item_id": 20020, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 283 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 284, "item_id": 20005, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 284 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 285, "item_id": 20012, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 285 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 286, "item_id": 20000, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 286 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 287, "item_id": 20002, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 287 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 288, "item_id": 20006, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 288 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 289, "item_id": 20005, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 289 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 290, "item_id": 20014, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 290 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 291, "item_id": 20017, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 291 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 292, "item_id": 20009, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 292 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 293, "item_id": 20021, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 293 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 294, "item_id": 20012, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 294 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 295, "item_id": 20011, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 295 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 296, "item_id": 20018, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 296 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 297, "item_id": 20013, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 297 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 298, "item_id": 20020, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 298 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 299, "item_id": 20021, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 299 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 300, "item_id": 20007, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 300 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 301, "item_id": 20011, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 301 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 302, "item_id": 20013, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 302 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 303, "item_id": 20005, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 303 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 304, "item_id": 20012, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 304 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 305, "item_id": 20019, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 305 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 306, "item_id": 20024, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 306 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 307, "item_id": 20001, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 307 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 308, "item_id": 20007, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 308 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 309, "item_id": 20015, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 309 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 310, "item_id": 20022, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 310 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 311, "item_id": 20009, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 311 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 312, "item_id": 20012, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 312 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 313, "item_id": 20015, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 313 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 314, "item_id": 20001, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 314 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 315, "item_id": 20011, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 315 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 316, "item_id": 20018, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 316 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 317, "item_id": 20004, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 317 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 318, "item_id": 20013, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 318 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 319, "item_id": 20006, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 319 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 320, "item_id": 20013, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 320 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 321, "item_id": 20010, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 321 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 322, "item_id": 20018, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 322 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 323, "item_id": 20000, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 323 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 324, "item_id": 20016, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 324 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 325, "item_id": 20000, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 325 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 326, "item_id": 20006, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 326 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 327, "item_id": 20010, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 327 + "rank": 1 }, { - "level": 1, + "equip_avatar": 0, + "internal_uid": 328, "item_id": 20005, - "equip_avatar": 0, - "rank": 1, + "level": 1, "promotion": 0, - "internal_uid": 328 + "rank": 1 }, { - "level": 1, - "item_id": 20001, "equip_avatar": 0, - "rank": 1, + "internal_uid": 329, + "item_id": 20001, + "level": 1, "promotion": 0, - "internal_uid": 329 + "rank": 1 } ], "materials": { @@ -21062,7 +21062,7 @@ ] }, { - "equip_avatar": 1412, + "equip_avatar": 0, "internal_uid": 437, "level": 15, "main_affix_id": 4, @@ -25772,18364 +25772,18397 @@ ] }, { + "equip_avatar": 0, + "internal_uid": 594, "level": 0, + "main_affix_id": 1, "relic_id": 31011, "relic_set_id": 101, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 594, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 595, "level": 0, + "main_affix_id": 1, "relic_id": 31012, "relic_set_id": 101, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 595, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 596, "level": 0, + "main_affix_id": 1, "relic_id": 31013, "relic_set_id": 101, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 596, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 597, "level": 0, + "main_affix_id": 1, "relic_id": 31014, "relic_set_id": 101, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 597, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 598, "level": 0, + "main_affix_id": 1, "relic_id": 31021, "relic_set_id": 102, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 598, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 599, "level": 0, + "main_affix_id": 1, "relic_id": 31022, "relic_set_id": 102, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 599, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 600, "level": 0, + "main_affix_id": 1, "relic_id": 31023, "relic_set_id": 102, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 600, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 601, "level": 0, + "main_affix_id": 1, "relic_id": 31024, "relic_set_id": 102, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 601, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 602, "level": 0, + "main_affix_id": 1, "relic_id": 31031, "relic_set_id": 103, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 602, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 603, "level": 0, + "main_affix_id": 1, "relic_id": 31032, "relic_set_id": 103, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 603, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 604, "level": 0, + "main_affix_id": 1, "relic_id": 31033, "relic_set_id": 103, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 604, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 605, "level": 0, + "main_affix_id": 1, "relic_id": 31034, "relic_set_id": 103, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 605, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 606, "level": 0, + "main_affix_id": 1, "relic_id": 31041, "relic_set_id": 104, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 606, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 607, "level": 0, + "main_affix_id": 1, "relic_id": 31042, "relic_set_id": 104, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 607, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 608, "level": 0, + "main_affix_id": 1, "relic_id": 31043, "relic_set_id": 104, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 608, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 609, "level": 0, + "main_affix_id": 1, "relic_id": 31044, "relic_set_id": 104, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 609, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 610, "level": 0, + "main_affix_id": 1, "relic_id": 31051, "relic_set_id": 105, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 610, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 611, "level": 0, + "main_affix_id": 1, "relic_id": 31052, "relic_set_id": 105, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 611, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 612, "level": 0, + "main_affix_id": 1, "relic_id": 31053, "relic_set_id": 105, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 612, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 613, "level": 0, + "main_affix_id": 1, "relic_id": 31054, "relic_set_id": 105, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 613, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 614, "level": 0, + "main_affix_id": 1, "relic_id": 31061, "relic_set_id": 106, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 614, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 615, "level": 0, + "main_affix_id": 1, "relic_id": 31062, "relic_set_id": 106, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 615, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 616, "level": 0, + "main_affix_id": 1, "relic_id": 31063, "relic_set_id": 106, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 616, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 617, "level": 0, + "main_affix_id": 1, "relic_id": 31064, "relic_set_id": 106, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 617, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 618, "level": 0, + "main_affix_id": 1, "relic_id": 31071, "relic_set_id": 107, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 618, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 619, "level": 0, + "main_affix_id": 1, "relic_id": 31072, "relic_set_id": 107, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 619, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 620, "level": 0, + "main_affix_id": 1, "relic_id": 31073, "relic_set_id": 107, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 620, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 621, "level": 0, + "main_affix_id": 1, "relic_id": 31074, "relic_set_id": 107, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 621, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 622, "level": 0, + "main_affix_id": 1, "relic_id": 31081, "relic_set_id": 108, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 622, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 623, "level": 0, + "main_affix_id": 1, "relic_id": 31082, "relic_set_id": 108, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 623, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 624, "level": 0, + "main_affix_id": 1, "relic_id": 31083, "relic_set_id": 108, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 624, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 625, "level": 0, + "main_affix_id": 1, "relic_id": 31084, "relic_set_id": 108, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 625, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 626, "level": 0, + "main_affix_id": 1, "relic_id": 31091, "relic_set_id": 109, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 626, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 627, "level": 0, + "main_affix_id": 1, "relic_id": 31092, "relic_set_id": 109, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 627, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 628, "level": 0, + "main_affix_id": 1, "relic_id": 31093, "relic_set_id": 109, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 628, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 629, "level": 0, + "main_affix_id": 1, "relic_id": 31094, "relic_set_id": 109, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 629, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 630, "level": 0, + "main_affix_id": 1, "relic_id": 31101, "relic_set_id": 110, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 630, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 631, "level": 0, + "main_affix_id": 1, "relic_id": 31102, "relic_set_id": 110, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 631, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 632, "level": 0, + "main_affix_id": 1, "relic_id": 31103, "relic_set_id": 110, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 632, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 633, "level": 0, + "main_affix_id": 1, "relic_id": 31104, "relic_set_id": 110, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 633, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 634, "level": 0, + "main_affix_id": 1, "relic_id": 31111, "relic_set_id": 111, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 634, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 635, "level": 0, + "main_affix_id": 1, "relic_id": 31112, "relic_set_id": 111, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 635, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 636, "level": 0, + "main_affix_id": 1, "relic_id": 31113, "relic_set_id": 111, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 636, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 637, "level": 0, + "main_affix_id": 1, "relic_id": 31114, "relic_set_id": 111, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 637, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 638, "level": 0, + "main_affix_id": 1, "relic_id": 31121, "relic_set_id": 112, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 638, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 639, "level": 0, + "main_affix_id": 1, "relic_id": 31122, "relic_set_id": 112, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 639, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 640, "level": 0, + "main_affix_id": 1, "relic_id": 31123, "relic_set_id": 112, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 640, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 641, "level": 0, + "main_affix_id": 1, "relic_id": 31124, "relic_set_id": 112, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 641, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 642, "level": 0, + "main_affix_id": 1, "relic_id": 31131, "relic_set_id": 113, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 642, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 643, "level": 0, + "main_affix_id": 1, "relic_id": 31132, "relic_set_id": 113, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 643, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 644, "level": 0, + "main_affix_id": 1, "relic_id": 31133, "relic_set_id": 113, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 644, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 645, "level": 0, + "main_affix_id": 1, "relic_id": 31134, "relic_set_id": 113, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 645, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 646, "level": 0, + "main_affix_id": 1, "relic_id": 31141, "relic_set_id": 114, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 646, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 647, "level": 0, + "main_affix_id": 1, "relic_id": 31142, "relic_set_id": 114, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 647, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 648, "level": 0, + "main_affix_id": 1, "relic_id": 31143, "relic_set_id": 114, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 648, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 649, "level": 0, + "main_affix_id": 1, "relic_id": 31144, "relic_set_id": 114, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 649, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 650, "level": 0, + "main_affix_id": 1, "relic_id": 31151, "relic_set_id": 115, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 650, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 651, "level": 0, + "main_affix_id": 1, "relic_id": 31152, "relic_set_id": 115, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 651, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 652, "level": 0, + "main_affix_id": 1, "relic_id": 31153, "relic_set_id": 115, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 652, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 653, "level": 0, + "main_affix_id": 1, "relic_id": 31154, "relic_set_id": 115, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 653, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 654, "level": 0, + "main_affix_id": 1, "relic_id": 31161, "relic_set_id": 116, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 654, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 655, "level": 0, + "main_affix_id": 1, "relic_id": 31162, "relic_set_id": 116, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 655, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 656, "level": 0, + "main_affix_id": 1, "relic_id": 31163, "relic_set_id": 116, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 656, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 657, "level": 0, + "main_affix_id": 1, "relic_id": 31164, "relic_set_id": 116, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 657, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 658, "level": 0, + "main_affix_id": 1, "relic_id": 31171, "relic_set_id": 117, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 658, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 659, "level": 0, + "main_affix_id": 1, "relic_id": 31172, "relic_set_id": 117, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 659, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 660, "level": 0, + "main_affix_id": 1, "relic_id": 31173, "relic_set_id": 117, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 660, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 661, "level": 0, + "main_affix_id": 1, "relic_id": 31174, "relic_set_id": 117, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 661, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 662, "level": 0, + "main_affix_id": 1, "relic_id": 31181, "relic_set_id": 118, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 662, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 663, "level": 0, + "main_affix_id": 1, "relic_id": 31182, "relic_set_id": 118, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 663, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 664, "level": 0, + "main_affix_id": 1, "relic_id": 31183, "relic_set_id": 118, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 664, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 665, "level": 0, + "main_affix_id": 1, "relic_id": 31184, "relic_set_id": 118, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 665, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 666, "level": 0, + "main_affix_id": 1, "relic_id": 31191, "relic_set_id": 119, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 666, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 667, "level": 0, + "main_affix_id": 1, "relic_id": 31192, "relic_set_id": 119, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 667, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 668, "level": 0, + "main_affix_id": 1, "relic_id": 31193, "relic_set_id": 119, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 668, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 669, "level": 0, + "main_affix_id": 1, "relic_id": 31194, "relic_set_id": 119, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 669, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 670, "level": 0, + "main_affix_id": 1, "relic_id": 31201, "relic_set_id": 120, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 670, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 671, "level": 0, + "main_affix_id": 1, "relic_id": 31202, "relic_set_id": 120, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 671, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 672, "level": 0, + "main_affix_id": 1, "relic_id": 31203, "relic_set_id": 120, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 672, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 673, "level": 0, + "main_affix_id": 1, "relic_id": 31204, "relic_set_id": 120, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 673, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 674, "level": 0, + "main_affix_id": 1, "relic_id": 31211, "relic_set_id": 121, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 674, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 675, "level": 0, + "main_affix_id": 1, "relic_id": 31212, "relic_set_id": 121, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 675, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 676, "level": 0, + "main_affix_id": 1, "relic_id": 31213, "relic_set_id": 121, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 676, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 677, "level": 0, + "main_affix_id": 1, "relic_id": 31214, "relic_set_id": 121, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 677, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 678, "level": 0, + "main_affix_id": 1, "relic_id": 31221, "relic_set_id": 122, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 678, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 679, "level": 0, + "main_affix_id": 1, "relic_id": 31222, "relic_set_id": 122, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 679, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 680, "level": 0, + "main_affix_id": 1, "relic_id": 31223, "relic_set_id": 122, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 680, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 681, "level": 0, + "main_affix_id": 1, "relic_id": 31224, "relic_set_id": 122, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 681, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 682, "level": 0, + "main_affix_id": 1, "relic_id": 31231, "relic_set_id": 123, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 682, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 683, "level": 0, + "main_affix_id": 1, "relic_id": 31232, "relic_set_id": 123, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 683, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 684, "level": 0, + "main_affix_id": 1, "relic_id": 31233, "relic_set_id": 123, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 684, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 685, "level": 0, + "main_affix_id": 1, "relic_id": 31234, "relic_set_id": 123, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 685, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 686, "level": 0, + "main_affix_id": 1, "relic_id": 31241, "relic_set_id": 124, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 686, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 687, "level": 0, + "main_affix_id": 1, "relic_id": 31242, "relic_set_id": 124, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 687, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 688, "level": 0, + "main_affix_id": 1, "relic_id": 31243, "relic_set_id": 124, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 688, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 689, "level": 0, + "main_affix_id": 1, "relic_id": 31244, "relic_set_id": 124, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 689, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 690, "level": 0, + "main_affix_id": 1, "relic_id": 31251, "relic_set_id": 125, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 690, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 691, "level": 0, + "main_affix_id": 1, "relic_id": 31252, "relic_set_id": 125, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 691, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 692, "level": 0, + "main_affix_id": 1, "relic_id": 31253, "relic_set_id": 125, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 692, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 693, "level": 0, + "main_affix_id": 1, "relic_id": 31254, "relic_set_id": 125, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 693, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 694, "level": 0, + "main_affix_id": 1, "relic_id": 31261, "relic_set_id": 126, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 694, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 695, "level": 0, + "main_affix_id": 1, "relic_id": 31262, "relic_set_id": 126, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 695, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 696, "level": 0, + "main_affix_id": 1, "relic_id": 31263, "relic_set_id": 126, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 696, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 697, "level": 0, + "main_affix_id": 1, "relic_id": 31264, "relic_set_id": 126, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 697, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 698, "level": 0, + "main_affix_id": 1, "relic_id": 31271, "relic_set_id": 127, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 698, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 699, "level": 0, + "main_affix_id": 1, "relic_id": 31272, "relic_set_id": 127, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 699, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 700, "level": 0, + "main_affix_id": 1, "relic_id": 31273, "relic_set_id": 127, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 700, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 701, "level": 0, + "main_affix_id": 1, "relic_id": 31274, "relic_set_id": 127, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 701, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 702, "level": 0, + "main_affix_id": 1, "relic_id": 31281, "relic_set_id": 128, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 702, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 703, "level": 0, + "main_affix_id": 1, "relic_id": 31282, "relic_set_id": 128, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 703, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 704, "level": 0, + "main_affix_id": 1, "relic_id": 31283, "relic_set_id": 128, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 704, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 705, "level": 0, + "main_affix_id": 1, "relic_id": 31284, "relic_set_id": 128, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 705, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 706, "level": 0, + "main_affix_id": 1, "relic_id": 31291, "relic_set_id": 129, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 706, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 707, "level": 0, + "main_affix_id": 1, "relic_id": 31292, "relic_set_id": 129, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 707, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 708, "level": 0, + "main_affix_id": 1, "relic_id": 31293, "relic_set_id": 129, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 708, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 709, "level": 0, + "main_affix_id": 1, "relic_id": 31294, "relic_set_id": 129, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 709, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 710, "level": 0, + "main_affix_id": 1, "relic_id": 31301, "relic_set_id": 130, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 710, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 711, "level": 0, + "main_affix_id": 1, "relic_id": 31302, "relic_set_id": 130, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 711, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 712, "level": 0, + "main_affix_id": 1, "relic_id": 31303, "relic_set_id": 130, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 712, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 713, "level": 0, + "main_affix_id": 1, "relic_id": 31304, "relic_set_id": 130, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 713, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 714, "level": 0, + "main_affix_id": 1, "relic_id": 31311, "relic_set_id": 131, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 714, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 715, "level": 0, + "main_affix_id": 1, "relic_id": 31312, "relic_set_id": 131, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 715, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 716, "level": 0, + "main_affix_id": 1, "relic_id": 31313, "relic_set_id": 131, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 716, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 717, "level": 0, + "main_affix_id": 1, "relic_id": 31314, "relic_set_id": 131, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 717, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 718, "level": 0, + "main_affix_id": 1, "relic_id": 31321, "relic_set_id": 132, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 718, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 719, "level": 0, + "main_affix_id": 1, "relic_id": 31322, "relic_set_id": 132, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 719, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 720, "level": 0, + "main_affix_id": 1, "relic_id": 31323, "relic_set_id": 132, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 720, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 721, "level": 0, + "main_affix_id": 1, "relic_id": 31324, "relic_set_id": 132, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 721, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 722, "level": 0, + "main_affix_id": 1, "relic_id": 31331, "relic_set_id": 133, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 722, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 723, "level": 0, + "main_affix_id": 1, "relic_id": 31332, "relic_set_id": 133, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 723, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 724, "level": 0, + "main_affix_id": 1, "relic_id": 31333, "relic_set_id": 133, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 724, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 725, "level": 0, + "main_affix_id": 1, "relic_id": 31334, "relic_set_id": 133, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 725, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 726, "level": 0, + "main_affix_id": 1, "relic_id": 31341, "relic_set_id": 134, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 726, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 727, "level": 0, + "main_affix_id": 1, "relic_id": 31342, "relic_set_id": 134, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 727, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 728, "level": 0, + "main_affix_id": 1, "relic_id": 31343, "relic_set_id": 134, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 728, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 729, "level": 0, + "main_affix_id": 1, "relic_id": 31344, "relic_set_id": 134, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 729, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 730, "level": 0, + "main_affix_id": 1, "relic_id": 33015, "relic_set_id": 301, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 730, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 731, "level": 0, + "main_affix_id": 1, "relic_id": 33016, "relic_set_id": 301, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 731, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 732, "level": 0, + "main_affix_id": 1, "relic_id": 33025, "relic_set_id": 302, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 732, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 733, "level": 0, + "main_affix_id": 1, "relic_id": 33026, "relic_set_id": 302, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 733, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 734, "level": 0, + "main_affix_id": 1, "relic_id": 33035, "relic_set_id": 303, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 734, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 735, "level": 0, + "main_affix_id": 1, "relic_id": 33036, "relic_set_id": 303, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 735, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 736, "level": 0, + "main_affix_id": 1, "relic_id": 33045, "relic_set_id": 304, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 736, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 737, "level": 0, + "main_affix_id": 1, "relic_id": 33046, "relic_set_id": 304, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 737, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 738, "level": 0, + "main_affix_id": 1, "relic_id": 33055, "relic_set_id": 305, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 738, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 739, "level": 0, + "main_affix_id": 1, "relic_id": 33056, "relic_set_id": 305, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 739, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 740, "level": 0, + "main_affix_id": 1, "relic_id": 33065, "relic_set_id": 306, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 740, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 741, "level": 0, + "main_affix_id": 1, "relic_id": 33066, "relic_set_id": 306, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 741, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 742, "level": 0, + "main_affix_id": 1, "relic_id": 33075, "relic_set_id": 307, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 742, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 743, "level": 0, + "main_affix_id": 1, "relic_id": 33076, "relic_set_id": 307, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 743, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 744, "level": 0, + "main_affix_id": 1, "relic_id": 33085, "relic_set_id": 308, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 744, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 745, "level": 0, + "main_affix_id": 1, "relic_id": 33086, "relic_set_id": 308, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 745, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 746, "level": 0, + "main_affix_id": 1, "relic_id": 33095, "relic_set_id": 309, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 746, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 747, "level": 0, + "main_affix_id": 1, "relic_id": 33096, "relic_set_id": 309, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 747, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 748, "level": 0, + "main_affix_id": 1, "relic_id": 33105, "relic_set_id": 310, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 748, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 749, "level": 0, + "main_affix_id": 1, "relic_id": 33106, "relic_set_id": 310, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 749, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 750, "level": 0, + "main_affix_id": 1, "relic_id": 33115, "relic_set_id": 311, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 750, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 751, "level": 0, + "main_affix_id": 1, "relic_id": 33116, "relic_set_id": 311, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 751, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 752, "level": 0, + "main_affix_id": 1, "relic_id": 33125, "relic_set_id": 312, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 752, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 753, "level": 0, + "main_affix_id": 1, "relic_id": 33126, "relic_set_id": 312, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 753, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 754, "level": 0, + "main_affix_id": 1, "relic_id": 33135, "relic_set_id": 313, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 754, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 755, "level": 0, + "main_affix_id": 1, "relic_id": 33136, "relic_set_id": 313, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 755, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 756, "level": 0, + "main_affix_id": 1, "relic_id": 33145, "relic_set_id": 314, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 756, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 757, "level": 0, + "main_affix_id": 1, "relic_id": 33146, "relic_set_id": 314, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 757, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 758, "level": 0, + "main_affix_id": 1, "relic_id": 33155, "relic_set_id": 315, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 758, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 759, "level": 0, + "main_affix_id": 1, "relic_id": 33156, "relic_set_id": 315, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 759, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 760, "level": 0, + "main_affix_id": 1, "relic_id": 33165, "relic_set_id": 316, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 760, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 761, "level": 0, + "main_affix_id": 1, "relic_id": 33166, "relic_set_id": 316, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 761, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 762, "level": 0, + "main_affix_id": 1, "relic_id": 33175, "relic_set_id": 317, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 762, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 763, "level": 0, + "main_affix_id": 1, "relic_id": 33176, "relic_set_id": 317, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 763, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 764, "level": 0, + "main_affix_id": 1, "relic_id": 33185, "relic_set_id": 318, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 764, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 765, "level": 0, + "main_affix_id": 1, "relic_id": 33186, "relic_set_id": 318, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 765, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 766, "level": 0, + "main_affix_id": 1, "relic_id": 33195, "relic_set_id": 319, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 766, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 767, "level": 0, + "main_affix_id": 1, "relic_id": 33196, "relic_set_id": 319, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 767, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 768, "level": 0, + "main_affix_id": 1, "relic_id": 33205, "relic_set_id": 320, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 768, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 769, "level": 0, + "main_affix_id": 1, "relic_id": 33206, "relic_set_id": 320, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 769, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 770, "level": 0, + "main_affix_id": 1, "relic_id": 33215, "relic_set_id": 321, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 770, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 771, "level": 0, + "main_affix_id": 1, "relic_id": 33216, "relic_set_id": 321, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 771, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 772, "level": 0, + "main_affix_id": 1, "relic_id": 33225, "relic_set_id": 322, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 772, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 773, "level": 0, + "main_affix_id": 1, "relic_id": 33226, "relic_set_id": 322, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 773, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 774, "level": 0, + "main_affix_id": 1, "relic_id": 33235, "relic_set_id": 323, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 774, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 775, "level": 0, + "main_affix_id": 1, "relic_id": 33236, "relic_set_id": 323, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 775, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 776, "level": 0, + "main_affix_id": 1, "relic_id": 33245, "relic_set_id": 324, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 776, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 777, "level": 0, + "main_affix_id": 1, "relic_id": 33246, "relic_set_id": 324, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 777, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 778, "level": 0, + "main_affix_id": 1, "relic_id": 33255, "relic_set_id": 325, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 778, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 779, "level": 0, + "main_affix_id": 1, "relic_id": 33256, "relic_set_id": 325, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 779, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 780, "level": 0, + "main_affix_id": 1, "relic_id": 33265, "relic_set_id": 326, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 780, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 781, "level": 0, + "main_affix_id": 1, "relic_id": 33266, "relic_set_id": 326, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 781, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 782, "level": 0, + "main_affix_id": 1, "relic_id": 33275, "relic_set_id": 327, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 782, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 783, "level": 0, + "main_affix_id": 1, "relic_id": 33276, "relic_set_id": 327, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 783, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 784, "level": 0, + "main_affix_id": 1, "relic_id": 33285, "relic_set_id": 328, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 784, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 785, "level": 0, + "main_affix_id": 1, "relic_id": 33286, "relic_set_id": 328, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 785, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 786, "level": 0, + "main_affix_id": 1, "relic_id": 41011, "relic_set_id": 101, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 786, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 787, "level": 0, + "main_affix_id": 1, "relic_id": 41012, "relic_set_id": 101, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 787, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 788, "level": 0, + "main_affix_id": 1, "relic_id": 41013, "relic_set_id": 101, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 788, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 789, "level": 0, + "main_affix_id": 1, "relic_id": 41014, "relic_set_id": 101, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 789, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 790, "level": 0, + "main_affix_id": 1, "relic_id": 41021, "relic_set_id": 102, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 790, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 791, "level": 0, + "main_affix_id": 1, "relic_id": 41022, "relic_set_id": 102, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 791, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 792, "level": 0, + "main_affix_id": 1, "relic_id": 41023, "relic_set_id": 102, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 792, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 793, "level": 0, + "main_affix_id": 1, "relic_id": 41024, "relic_set_id": 102, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 793, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 794, "level": 0, + "main_affix_id": 1, "relic_id": 41031, "relic_set_id": 103, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 794, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 795, "level": 0, + "main_affix_id": 1, "relic_id": 41032, "relic_set_id": 103, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 795, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 796, "level": 0, + "main_affix_id": 1, "relic_id": 41033, "relic_set_id": 103, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 796, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 797, "level": 0, + "main_affix_id": 1, "relic_id": 41034, "relic_set_id": 103, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 797, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 798, "level": 0, + "main_affix_id": 1, "relic_id": 41041, "relic_set_id": 104, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 798, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 799, "level": 0, + "main_affix_id": 1, "relic_id": 41042, "relic_set_id": 104, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 799, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 800, "level": 0, + "main_affix_id": 1, "relic_id": 41043, "relic_set_id": 104, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 800, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 801, "level": 0, + "main_affix_id": 1, "relic_id": 41044, "relic_set_id": 104, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 801, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 802, "level": 0, + "main_affix_id": 1, "relic_id": 41051, "relic_set_id": 105, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 802, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 803, "level": 0, + "main_affix_id": 1, "relic_id": 41052, "relic_set_id": 105, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 803, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 804, "level": 0, + "main_affix_id": 1, "relic_id": 41053, "relic_set_id": 105, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 804, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 805, "level": 0, + "main_affix_id": 1, "relic_id": 41054, "relic_set_id": 105, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 805, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 806, "level": 0, + "main_affix_id": 1, "relic_id": 41061, "relic_set_id": 106, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 806, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 807, "level": 0, + "main_affix_id": 1, "relic_id": 41062, "relic_set_id": 106, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 807, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 808, "level": 0, + "main_affix_id": 1, "relic_id": 41063, "relic_set_id": 106, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 808, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 809, "level": 0, + "main_affix_id": 1, "relic_id": 41064, "relic_set_id": 106, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 809, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 810, "level": 0, + "main_affix_id": 1, "relic_id": 41071, "relic_set_id": 107, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 810, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 811, "level": 0, + "main_affix_id": 1, "relic_id": 41072, "relic_set_id": 107, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 811, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 812, "level": 0, + "main_affix_id": 1, "relic_id": 41073, "relic_set_id": 107, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 812, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 813, "level": 0, + "main_affix_id": 1, "relic_id": 41074, "relic_set_id": 107, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 813, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 814, "level": 0, + "main_affix_id": 1, "relic_id": 41081, "relic_set_id": 108, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 814, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 815, "level": 0, + "main_affix_id": 1, "relic_id": 41082, "relic_set_id": 108, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 815, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 816, "level": 0, + "main_affix_id": 1, "relic_id": 41083, "relic_set_id": 108, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 816, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 817, "level": 0, + "main_affix_id": 1, "relic_id": 41084, "relic_set_id": 108, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 817, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 818, "level": 0, + "main_affix_id": 1, "relic_id": 41091, "relic_set_id": 109, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 818, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 819, "level": 0, + "main_affix_id": 1, "relic_id": 41092, "relic_set_id": 109, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 819, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 820, "level": 0, + "main_affix_id": 1, "relic_id": 41093, "relic_set_id": 109, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 820, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 821, "level": 0, + "main_affix_id": 1, "relic_id": 41094, "relic_set_id": 109, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 821, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 822, "level": 0, + "main_affix_id": 1, "relic_id": 41101, "relic_set_id": 110, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 822, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 823, "level": 0, + "main_affix_id": 1, "relic_id": 41102, "relic_set_id": 110, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 823, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 824, "level": 0, + "main_affix_id": 1, "relic_id": 41103, "relic_set_id": 110, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 824, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 825, "level": 0, + "main_affix_id": 1, "relic_id": 41104, "relic_set_id": 110, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 825, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 826, "level": 0, + "main_affix_id": 1, "relic_id": 41111, "relic_set_id": 111, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 826, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 827, "level": 0, + "main_affix_id": 1, "relic_id": 41112, "relic_set_id": 111, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 827, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 828, "level": 0, + "main_affix_id": 1, "relic_id": 41113, "relic_set_id": 111, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 828, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 829, "level": 0, + "main_affix_id": 1, "relic_id": 41114, "relic_set_id": 111, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 829, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 830, "level": 0, + "main_affix_id": 1, "relic_id": 41121, "relic_set_id": 112, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 830, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 831, "level": 0, + "main_affix_id": 1, "relic_id": 41122, "relic_set_id": 112, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 831, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 832, "level": 0, + "main_affix_id": 1, "relic_id": 41123, "relic_set_id": 112, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 832, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 833, "level": 0, + "main_affix_id": 1, "relic_id": 41124, "relic_set_id": 112, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 833, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 834, "level": 0, + "main_affix_id": 1, "relic_id": 41131, "relic_set_id": 113, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 834, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 835, "level": 0, + "main_affix_id": 1, "relic_id": 41132, "relic_set_id": 113, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 835, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 836, "level": 0, + "main_affix_id": 1, "relic_id": 41133, "relic_set_id": 113, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 836, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 837, "level": 0, + "main_affix_id": 1, "relic_id": 41134, "relic_set_id": 113, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 837, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 838, "level": 0, + "main_affix_id": 1, "relic_id": 41141, "relic_set_id": 114, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 838, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 839, "level": 0, + "main_affix_id": 1, "relic_id": 41142, "relic_set_id": 114, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 839, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 840, "level": 0, + "main_affix_id": 1, "relic_id": 41143, "relic_set_id": 114, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 840, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 841, "level": 0, + "main_affix_id": 1, "relic_id": 41144, "relic_set_id": 114, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 841, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 842, "level": 0, + "main_affix_id": 1, "relic_id": 41151, "relic_set_id": 115, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 842, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 843, "level": 0, + "main_affix_id": 1, "relic_id": 41152, "relic_set_id": 115, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 843, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 844, "level": 0, + "main_affix_id": 1, "relic_id": 41153, "relic_set_id": 115, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 844, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 845, "level": 0, + "main_affix_id": 1, "relic_id": 41154, "relic_set_id": 115, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 845, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 846, "level": 0, + "main_affix_id": 1, "relic_id": 41161, "relic_set_id": 116, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 846, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 847, "level": 0, + "main_affix_id": 1, "relic_id": 41162, "relic_set_id": 116, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 847, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 848, "level": 0, + "main_affix_id": 1, "relic_id": 41163, "relic_set_id": 116, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 848, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 849, "level": 0, + "main_affix_id": 1, "relic_id": 41164, "relic_set_id": 116, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 849, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 850, "level": 0, + "main_affix_id": 1, "relic_id": 41171, "relic_set_id": 117, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 850, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 851, "level": 0, + "main_affix_id": 1, "relic_id": 41172, "relic_set_id": 117, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 851, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 852, "level": 0, + "main_affix_id": 1, "relic_id": 41173, "relic_set_id": 117, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 852, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 853, "level": 0, + "main_affix_id": 1, "relic_id": 41174, "relic_set_id": 117, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 853, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 854, "level": 0, + "main_affix_id": 1, "relic_id": 41181, "relic_set_id": 118, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 854, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 855, "level": 0, + "main_affix_id": 1, "relic_id": 41182, "relic_set_id": 118, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 855, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 856, "level": 0, + "main_affix_id": 1, "relic_id": 41183, "relic_set_id": 118, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 856, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 857, "level": 0, + "main_affix_id": 1, "relic_id": 41184, "relic_set_id": 118, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 857, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 858, "level": 0, + "main_affix_id": 1, "relic_id": 41191, "relic_set_id": 119, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 858, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 859, "level": 0, + "main_affix_id": 1, "relic_id": 41192, "relic_set_id": 119, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 859, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 860, "level": 0, + "main_affix_id": 1, "relic_id": 41193, "relic_set_id": 119, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 860, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 861, "level": 0, + "main_affix_id": 1, "relic_id": 41194, "relic_set_id": 119, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 861, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 862, "level": 0, + "main_affix_id": 1, "relic_id": 41201, "relic_set_id": 120, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 862, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 863, "level": 0, + "main_affix_id": 1, "relic_id": 41202, "relic_set_id": 120, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 863, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 864, "level": 0, + "main_affix_id": 1, "relic_id": 41203, "relic_set_id": 120, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 864, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 865, "level": 0, + "main_affix_id": 1, "relic_id": 41204, "relic_set_id": 120, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 865, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 866, "level": 0, + "main_affix_id": 1, "relic_id": 41211, "relic_set_id": 121, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 866, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 867, "level": 0, + "main_affix_id": 1, "relic_id": 41212, "relic_set_id": 121, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 867, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 868, "level": 0, + "main_affix_id": 1, "relic_id": 41213, "relic_set_id": 121, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 868, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 869, "level": 0, + "main_affix_id": 1, "relic_id": 41214, "relic_set_id": 121, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 869, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 870, "level": 0, + "main_affix_id": 1, "relic_id": 41221, "relic_set_id": 122, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 870, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 871, "level": 0, + "main_affix_id": 1, "relic_id": 41222, "relic_set_id": 122, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 871, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 872, "level": 0, + "main_affix_id": 1, "relic_id": 41223, "relic_set_id": 122, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 872, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 873, "level": 0, + "main_affix_id": 1, "relic_id": 41224, "relic_set_id": 122, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 873, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 874, "level": 0, + "main_affix_id": 1, "relic_id": 41231, "relic_set_id": 123, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 874, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 875, "level": 0, + "main_affix_id": 1, "relic_id": 41232, "relic_set_id": 123, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 875, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 876, "level": 0, + "main_affix_id": 1, "relic_id": 41233, "relic_set_id": 123, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 876, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 877, "level": 0, + "main_affix_id": 1, "relic_id": 41234, "relic_set_id": 123, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 877, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 878, "level": 0, + "main_affix_id": 1, "relic_id": 41241, "relic_set_id": 124, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 878, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 879, "level": 0, + "main_affix_id": 1, "relic_id": 41242, "relic_set_id": 124, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 879, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 880, "level": 0, + "main_affix_id": 1, "relic_id": 41243, "relic_set_id": 124, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 880, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 881, "level": 0, + "main_affix_id": 1, "relic_id": 41244, "relic_set_id": 124, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 881, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 882, "level": 0, + "main_affix_id": 1, "relic_id": 41251, "relic_set_id": 125, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 882, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 883, "level": 0, + "main_affix_id": 1, "relic_id": 41252, "relic_set_id": 125, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 883, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 884, "level": 0, + "main_affix_id": 1, "relic_id": 41253, "relic_set_id": 125, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 884, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 885, "level": 0, + "main_affix_id": 1, "relic_id": 41254, "relic_set_id": 125, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 885, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 886, "level": 0, + "main_affix_id": 1, "relic_id": 41261, "relic_set_id": 126, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 886, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 887, "level": 0, + "main_affix_id": 1, "relic_id": 41262, "relic_set_id": 126, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 887, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 888, "level": 0, + "main_affix_id": 1, "relic_id": 41263, "relic_set_id": 126, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 888, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 889, "level": 0, + "main_affix_id": 1, "relic_id": 41264, "relic_set_id": 126, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 889, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 890, "level": 0, + "main_affix_id": 1, "relic_id": 41271, "relic_set_id": 127, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 890, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 891, "level": 0, + "main_affix_id": 1, "relic_id": 41272, "relic_set_id": 127, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 891, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 892, "level": 0, + "main_affix_id": 1, "relic_id": 41273, "relic_set_id": 127, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 892, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 893, "level": 0, + "main_affix_id": 1, "relic_id": 41274, "relic_set_id": 127, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 893, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 894, "level": 0, + "main_affix_id": 1, "relic_id": 41281, "relic_set_id": 128, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 894, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 895, "level": 0, + "main_affix_id": 1, "relic_id": 41282, "relic_set_id": 128, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 895, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 896, "level": 0, + "main_affix_id": 1, "relic_id": 41283, "relic_set_id": 128, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 896, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 897, "level": 0, + "main_affix_id": 1, "relic_id": 41284, "relic_set_id": 128, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 897, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 898, "level": 0, + "main_affix_id": 1, "relic_id": 41291, "relic_set_id": 129, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 898, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 899, "level": 0, + "main_affix_id": 1, "relic_id": 41292, "relic_set_id": 129, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 899, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 900, "level": 0, + "main_affix_id": 1, "relic_id": 41293, "relic_set_id": 129, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 900, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 901, "level": 0, + "main_affix_id": 1, "relic_id": 41294, "relic_set_id": 129, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 901, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 902, "level": 0, + "main_affix_id": 1, "relic_id": 41301, "relic_set_id": 130, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 902, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 903, "level": 0, + "main_affix_id": 1, "relic_id": 41302, "relic_set_id": 130, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 903, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 904, "level": 0, + "main_affix_id": 1, "relic_id": 41303, "relic_set_id": 130, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 904, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 905, "level": 0, + "main_affix_id": 1, "relic_id": 41304, "relic_set_id": 130, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 905, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 906, "level": 0, + "main_affix_id": 1, "relic_id": 41311, "relic_set_id": 131, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 906, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 907, "level": 0, + "main_affix_id": 1, "relic_id": 41312, "relic_set_id": 131, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 907, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 908, "level": 0, + "main_affix_id": 1, "relic_id": 41313, "relic_set_id": 131, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 908, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 909, "level": 0, + "main_affix_id": 1, "relic_id": 41314, "relic_set_id": 131, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 909, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 910, "level": 0, + "main_affix_id": 1, "relic_id": 41321, "relic_set_id": 132, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 910, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 911, "level": 0, + "main_affix_id": 1, "relic_id": 41322, "relic_set_id": 132, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 911, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 912, "level": 0, + "main_affix_id": 1, "relic_id": 41323, "relic_set_id": 132, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 912, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 913, "level": 0, + "main_affix_id": 1, "relic_id": 41324, "relic_set_id": 132, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 913, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 914, "level": 0, + "main_affix_id": 1, "relic_id": 41331, "relic_set_id": 133, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 914, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 915, "level": 0, + "main_affix_id": 1, "relic_id": 41332, "relic_set_id": 133, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 915, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 916, "level": 0, + "main_affix_id": 1, "relic_id": 41333, "relic_set_id": 133, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 916, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 917, "level": 0, + "main_affix_id": 1, "relic_id": 41334, "relic_set_id": 133, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 917, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 918, "level": 0, + "main_affix_id": 1, "relic_id": 41341, "relic_set_id": 134, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 918, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 919, "level": 0, + "main_affix_id": 1, "relic_id": 41342, "relic_set_id": 134, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 919, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 920, "level": 0, + "main_affix_id": 1, "relic_id": 41343, "relic_set_id": 134, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 920, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 921, "level": 0, + "main_affix_id": 1, "relic_id": 41344, "relic_set_id": 134, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 921, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 922, "level": 0, + "main_affix_id": 1, "relic_id": 43015, "relic_set_id": 301, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 922, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 923, "level": 0, + "main_affix_id": 1, "relic_id": 43016, "relic_set_id": 301, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 923, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 924, "level": 0, + "main_affix_id": 1, "relic_id": 43025, "relic_set_id": 302, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 924, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 925, "level": 0, + "main_affix_id": 1, "relic_id": 43026, "relic_set_id": 302, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 925, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 926, "level": 0, + "main_affix_id": 1, "relic_id": 43035, "relic_set_id": 303, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 926, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 927, "level": 0, + "main_affix_id": 1, "relic_id": 43036, "relic_set_id": 303, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 927, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 928, "level": 0, + "main_affix_id": 1, "relic_id": 43045, "relic_set_id": 304, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 928, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 929, "level": 0, + "main_affix_id": 1, "relic_id": 43046, "relic_set_id": 304, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 929, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 930, "level": 0, + "main_affix_id": 1, "relic_id": 43055, "relic_set_id": 305, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 930, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 931, "level": 0, + "main_affix_id": 1, "relic_id": 43056, "relic_set_id": 305, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 931, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 932, "level": 0, + "main_affix_id": 1, "relic_id": 43065, "relic_set_id": 306, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 932, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 933, "level": 0, + "main_affix_id": 1, "relic_id": 43066, "relic_set_id": 306, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 933, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 934, "level": 0, + "main_affix_id": 1, "relic_id": 43075, "relic_set_id": 307, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 934, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 935, "level": 0, + "main_affix_id": 1, "relic_id": 43076, "relic_set_id": 307, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 935, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 936, "level": 0, + "main_affix_id": 1, "relic_id": 43085, "relic_set_id": 308, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 936, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 937, "level": 0, + "main_affix_id": 1, "relic_id": 43086, "relic_set_id": 308, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 937, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 938, "level": 0, + "main_affix_id": 1, "relic_id": 43095, "relic_set_id": 309, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 938, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 939, "level": 0, + "main_affix_id": 1, "relic_id": 43096, "relic_set_id": 309, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 939, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 940, "level": 0, + "main_affix_id": 1, "relic_id": 43105, "relic_set_id": 310, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 940, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 941, "level": 0, + "main_affix_id": 1, "relic_id": 43106, "relic_set_id": 310, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 941, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 942, "level": 0, + "main_affix_id": 1, "relic_id": 43115, "relic_set_id": 311, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 942, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 943, "level": 0, + "main_affix_id": 1, "relic_id": 43116, "relic_set_id": 311, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 943, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 944, "level": 0, + "main_affix_id": 1, "relic_id": 43125, "relic_set_id": 312, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 944, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 945, "level": 0, + "main_affix_id": 1, "relic_id": 43126, "relic_set_id": 312, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 945, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 946, "level": 0, + "main_affix_id": 1, "relic_id": 43135, "relic_set_id": 313, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 946, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 947, "level": 0, + "main_affix_id": 1, "relic_id": 43136, "relic_set_id": 313, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 947, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 948, "level": 0, + "main_affix_id": 1, "relic_id": 43145, "relic_set_id": 314, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 948, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 949, "level": 0, + "main_affix_id": 1, "relic_id": 43146, "relic_set_id": 314, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 949, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 950, "level": 0, + "main_affix_id": 1, "relic_id": 43155, "relic_set_id": 315, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 950, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 951, "level": 0, + "main_affix_id": 1, "relic_id": 43156, "relic_set_id": 315, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 951, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 952, "level": 0, + "main_affix_id": 1, "relic_id": 43165, "relic_set_id": 316, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 952, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 953, "level": 0, + "main_affix_id": 1, "relic_id": 43166, "relic_set_id": 316, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 953, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 954, "level": 0, + "main_affix_id": 1, "relic_id": 43175, "relic_set_id": 317, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 954, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 955, "level": 0, + "main_affix_id": 1, "relic_id": 43176, "relic_set_id": 317, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 955, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 956, "level": 0, + "main_affix_id": 1, "relic_id": 43185, "relic_set_id": 318, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 956, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 957, "level": 0, + "main_affix_id": 1, "relic_id": 43186, "relic_set_id": 318, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 957, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 958, "level": 0, + "main_affix_id": 1, "relic_id": 43195, "relic_set_id": 319, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 958, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 959, "level": 0, + "main_affix_id": 1, "relic_id": 43196, "relic_set_id": 319, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 959, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 960, "level": 0, + "main_affix_id": 1, "relic_id": 43205, "relic_set_id": 320, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 960, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 961, "level": 0, + "main_affix_id": 1, "relic_id": 43206, "relic_set_id": 320, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 961, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 962, "level": 0, + "main_affix_id": 1, "relic_id": 43215, "relic_set_id": 321, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 962, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 963, "level": 0, + "main_affix_id": 1, "relic_id": 43216, "relic_set_id": 321, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 963, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 964, "level": 0, + "main_affix_id": 1, "relic_id": 43225, "relic_set_id": 322, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 964, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 965, "level": 0, + "main_affix_id": 1, "relic_id": 43226, "relic_set_id": 322, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 965, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 966, "level": 0, + "main_affix_id": 1, "relic_id": 43235, "relic_set_id": 323, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 966, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 967, "level": 0, + "main_affix_id": 1, "relic_id": 43236, "relic_set_id": 323, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 967, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 968, "level": 0, + "main_affix_id": 1, "relic_id": 43245, "relic_set_id": 324, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 968, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 969, "level": 0, + "main_affix_id": 1, "relic_id": 43246, "relic_set_id": 324, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 969, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 970, "level": 0, + "main_affix_id": 1, "relic_id": 43255, "relic_set_id": 325, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 970, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 971, "level": 0, + "main_affix_id": 1, "relic_id": 43256, "relic_set_id": 325, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 971, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 972, "level": 0, + "main_affix_id": 1, "relic_id": 43265, "relic_set_id": 326, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 972, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 973, "level": 0, + "main_affix_id": 1, "relic_id": 43266, "relic_set_id": 326, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 973, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 974, "level": 0, + "main_affix_id": 1, "relic_id": 43275, "relic_set_id": 327, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 974, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 975, "level": 0, + "main_affix_id": 1, "relic_id": 43276, "relic_set_id": 327, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 975, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 976, "level": 0, + "main_affix_id": 1, "relic_id": 43285, "relic_set_id": 328, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 976, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 977, "level": 0, + "main_affix_id": 1, "relic_id": 43286, "relic_set_id": 328, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 977, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 978, "level": 0, + "main_affix_id": 1, "relic_id": 51011, "relic_set_id": 101, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 978, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 979, "level": 0, + "main_affix_id": 1, "relic_id": 51012, "relic_set_id": 101, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 979, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 980, "level": 0, + "main_affix_id": 1, "relic_id": 51013, "relic_set_id": 101, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 980, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 981, "level": 0, + "main_affix_id": 1, "relic_id": 51014, "relic_set_id": 101, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 981, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 982, "level": 0, + "main_affix_id": 1, "relic_id": 51021, "relic_set_id": 102, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 982, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 983, "level": 0, + "main_affix_id": 1, "relic_id": 51022, "relic_set_id": 102, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 983, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 984, "level": 0, + "main_affix_id": 1, "relic_id": 51023, "relic_set_id": 102, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 984, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 985, "level": 0, + "main_affix_id": 1, "relic_id": 51024, "relic_set_id": 102, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 985, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 986, "level": 0, + "main_affix_id": 1, "relic_id": 51031, "relic_set_id": 103, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 986, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 987, "level": 0, + "main_affix_id": 1, "relic_id": 51032, "relic_set_id": 103, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 987, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 988, "level": 0, + "main_affix_id": 1, "relic_id": 51033, "relic_set_id": 103, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 988, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 989, "level": 0, + "main_affix_id": 1, "relic_id": 51034, "relic_set_id": 103, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 989, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 990, "level": 0, + "main_affix_id": 1, "relic_id": 51041, "relic_set_id": 104, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 990, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 991, "level": 0, + "main_affix_id": 1, "relic_id": 51042, "relic_set_id": 104, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 991, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 992, "level": 0, + "main_affix_id": 1, "relic_id": 51043, "relic_set_id": 104, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 992, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 993, "level": 0, + "main_affix_id": 1, "relic_id": 51044, "relic_set_id": 104, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 993, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 994, "level": 0, + "main_affix_id": 1, "relic_id": 51051, "relic_set_id": 105, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 994, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 995, "level": 0, + "main_affix_id": 1, "relic_id": 51052, "relic_set_id": 105, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 995, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 996, "level": 0, + "main_affix_id": 1, "relic_id": 51053, "relic_set_id": 105, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 996, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 997, "level": 0, + "main_affix_id": 1, "relic_id": 51054, "relic_set_id": 105, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 997, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 998, "level": 0, + "main_affix_id": 1, "relic_id": 51061, "relic_set_id": 106, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 998, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 999, "level": 0, + "main_affix_id": 1, "relic_id": 51062, "relic_set_id": 106, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 999, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1000, "level": 0, + "main_affix_id": 1, "relic_id": 51063, "relic_set_id": 106, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1000, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1001, "level": 0, + "main_affix_id": 1, "relic_id": 51064, "relic_set_id": 106, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1001, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1002, "level": 0, + "main_affix_id": 1, "relic_id": 51071, "relic_set_id": 107, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1002, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1003, "level": 0, + "main_affix_id": 1, "relic_id": 51072, "relic_set_id": 107, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1003, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1004, "level": 0, + "main_affix_id": 1, "relic_id": 51073, "relic_set_id": 107, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1004, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1005, "level": 0, + "main_affix_id": 1, "relic_id": 51074, "relic_set_id": 107, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1005, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1006, "level": 0, + "main_affix_id": 1, "relic_id": 51081, "relic_set_id": 108, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1006, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1007, "level": 0, + "main_affix_id": 1, "relic_id": 51082, "relic_set_id": 108, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1007, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1008, "level": 0, + "main_affix_id": 1, "relic_id": 51083, "relic_set_id": 108, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1008, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1009, "level": 0, + "main_affix_id": 1, "relic_id": 51084, "relic_set_id": 108, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1009, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1010, "level": 0, + "main_affix_id": 1, "relic_id": 51091, "relic_set_id": 109, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1010, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1011, "level": 0, + "main_affix_id": 1, "relic_id": 51092, "relic_set_id": 109, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1011, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1012, "level": 0, + "main_affix_id": 1, "relic_id": 51093, "relic_set_id": 109, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1012, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1013, "level": 0, + "main_affix_id": 1, "relic_id": 51094, "relic_set_id": 109, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1013, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1014, "level": 0, + "main_affix_id": 1, "relic_id": 51101, "relic_set_id": 110, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1014, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1015, "level": 0, + "main_affix_id": 1, "relic_id": 51102, "relic_set_id": 110, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1015, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1016, "level": 0, + "main_affix_id": 1, "relic_id": 51103, "relic_set_id": 110, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1016, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1017, "level": 0, + "main_affix_id": 1, "relic_id": 51104, "relic_set_id": 110, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1017, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1018, "level": 0, + "main_affix_id": 1, "relic_id": 51111, "relic_set_id": 111, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1018, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1019, "level": 0, + "main_affix_id": 1, "relic_id": 51112, "relic_set_id": 111, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1019, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1020, "level": 0, + "main_affix_id": 1, "relic_id": 51113, "relic_set_id": 111, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1020, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1021, "level": 0, + "main_affix_id": 1, "relic_id": 51114, "relic_set_id": 111, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1021, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1022, "level": 0, + "main_affix_id": 1, "relic_id": 51121, "relic_set_id": 112, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1022, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1023, "level": 0, + "main_affix_id": 1, "relic_id": 51122, "relic_set_id": 112, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1023, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1024, "level": 0, + "main_affix_id": 1, "relic_id": 51123, "relic_set_id": 112, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1024, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1025, "level": 0, + "main_affix_id": 1, "relic_id": 51124, "relic_set_id": 112, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1025, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1026, "level": 0, + "main_affix_id": 1, "relic_id": 51131, "relic_set_id": 113, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1026, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1027, "level": 0, + "main_affix_id": 1, "relic_id": 51132, "relic_set_id": 113, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1027, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1028, "level": 0, + "main_affix_id": 1, "relic_id": 51133, "relic_set_id": 113, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1028, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1029, "level": 0, + "main_affix_id": 1, "relic_id": 51134, "relic_set_id": 113, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1029, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1030, "level": 0, + "main_affix_id": 1, "relic_id": 51141, "relic_set_id": 114, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1030, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1031, "level": 0, + "main_affix_id": 1, "relic_id": 51142, "relic_set_id": 114, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1031, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1032, "level": 0, + "main_affix_id": 1, "relic_id": 51143, "relic_set_id": 114, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1032, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1033, "level": 0, + "main_affix_id": 1, "relic_id": 51144, "relic_set_id": 114, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1033, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1034, "level": 0, + "main_affix_id": 1, "relic_id": 51151, "relic_set_id": 115, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1034, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1035, "level": 0, + "main_affix_id": 1, "relic_id": 51152, "relic_set_id": 115, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1035, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1036, "level": 0, + "main_affix_id": 1, "relic_id": 51153, "relic_set_id": 115, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1036, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1037, "level": 0, + "main_affix_id": 1, "relic_id": 51154, "relic_set_id": 115, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1037, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1038, "level": 0, + "main_affix_id": 1, "relic_id": 51161, "relic_set_id": 116, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1038, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1039, "level": 0, + "main_affix_id": 1, "relic_id": 51162, "relic_set_id": 116, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1039, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1040, "level": 0, + "main_affix_id": 1, "relic_id": 51163, "relic_set_id": 116, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1040, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1041, "level": 0, + "main_affix_id": 1, "relic_id": 51164, "relic_set_id": 116, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1041, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1042, "level": 0, + "main_affix_id": 1, "relic_id": 51171, "relic_set_id": 117, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1042, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1043, "level": 0, + "main_affix_id": 1, "relic_id": 51172, "relic_set_id": 117, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1043, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1044, "level": 0, + "main_affix_id": 1, "relic_id": 51173, "relic_set_id": 117, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1044, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1045, "level": 0, + "main_affix_id": 1, "relic_id": 51174, "relic_set_id": 117, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1045, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1046, "level": 0, + "main_affix_id": 1, "relic_id": 51181, "relic_set_id": 118, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1046, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1047, "level": 0, + "main_affix_id": 1, "relic_id": 51182, "relic_set_id": 118, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1047, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1048, "level": 0, + "main_affix_id": 1, "relic_id": 51183, "relic_set_id": 118, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1048, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1049, "level": 0, + "main_affix_id": 1, "relic_id": 51184, "relic_set_id": 118, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1049, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1050, "level": 0, + "main_affix_id": 1, "relic_id": 51191, "relic_set_id": 119, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1050, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1051, "level": 0, + "main_affix_id": 1, "relic_id": 51192, "relic_set_id": 119, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1051, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1052, "level": 0, + "main_affix_id": 1, "relic_id": 51193, "relic_set_id": 119, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1052, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1053, "level": 0, + "main_affix_id": 1, "relic_id": 51194, "relic_set_id": 119, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1053, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1054, "level": 0, + "main_affix_id": 1, "relic_id": 51201, "relic_set_id": 120, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1054, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1055, "level": 0, + "main_affix_id": 1, "relic_id": 51202, "relic_set_id": 120, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1055, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1056, "level": 0, + "main_affix_id": 1, "relic_id": 51203, "relic_set_id": 120, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1056, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1057, "level": 0, + "main_affix_id": 1, "relic_id": 51204, "relic_set_id": 120, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1057, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1058, "level": 0, + "main_affix_id": 1, "relic_id": 51211, "relic_set_id": 121, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1058, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1059, "level": 0, + "main_affix_id": 1, "relic_id": 51212, "relic_set_id": 121, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1059, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1060, "level": 0, + "main_affix_id": 1, "relic_id": 51213, "relic_set_id": 121, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1060, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1061, "level": 0, + "main_affix_id": 1, "relic_id": 51214, "relic_set_id": 121, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1061, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1062, "level": 0, + "main_affix_id": 1, "relic_id": 51221, "relic_set_id": 122, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1062, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1063, "level": 0, + "main_affix_id": 1, "relic_id": 51222, "relic_set_id": 122, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1063, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1064, "level": 0, + "main_affix_id": 1, "relic_id": 51223, "relic_set_id": 122, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1064, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1065, "level": 0, + "main_affix_id": 1, "relic_id": 51224, "relic_set_id": 122, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1065, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1066, "level": 0, + "main_affix_id": 1, "relic_id": 51231, "relic_set_id": 123, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1066, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1067, "level": 0, + "main_affix_id": 1, "relic_id": 51232, "relic_set_id": 123, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1067, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1068, "level": 0, + "main_affix_id": 1, "relic_id": 51233, "relic_set_id": 123, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1068, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1069, "level": 0, + "main_affix_id": 1, "relic_id": 51234, "relic_set_id": 123, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1069, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1070, "level": 0, + "main_affix_id": 1, "relic_id": 51241, "relic_set_id": 124, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1070, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1071, "level": 0, + "main_affix_id": 1, "relic_id": 51242, "relic_set_id": 124, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1071, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1072, "level": 0, + "main_affix_id": 1, "relic_id": 51243, "relic_set_id": 124, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1072, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1073, "level": 0, + "main_affix_id": 1, "relic_id": 51244, "relic_set_id": 124, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1073, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1074, "level": 0, + "main_affix_id": 1, "relic_id": 51251, "relic_set_id": 125, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1074, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1075, "level": 0, + "main_affix_id": 1, "relic_id": 51252, "relic_set_id": 125, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1075, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1076, "level": 0, + "main_affix_id": 1, "relic_id": 51253, "relic_set_id": 125, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1076, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1077, "level": 0, + "main_affix_id": 1, "relic_id": 51254, "relic_set_id": 125, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1077, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1078, "level": 0, + "main_affix_id": 1, "relic_id": 51261, "relic_set_id": 126, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1078, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1079, "level": 0, + "main_affix_id": 1, "relic_id": 51262, "relic_set_id": 126, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1079, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1080, "level": 0, + "main_affix_id": 1, "relic_id": 51263, "relic_set_id": 126, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1080, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1081, "level": 0, + "main_affix_id": 1, "relic_id": 51264, "relic_set_id": 126, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1081, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1082, "level": 0, + "main_affix_id": 1, "relic_id": 51271, "relic_set_id": 127, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1082, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1083, "level": 0, + "main_affix_id": 1, "relic_id": 51272, "relic_set_id": 127, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1083, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1084, "level": 0, + "main_affix_id": 1, "relic_id": 51273, "relic_set_id": 127, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1084, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1085, "level": 0, + "main_affix_id": 1, "relic_id": 51274, "relic_set_id": 127, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1085, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1086, "level": 0, + "main_affix_id": 1, "relic_id": 51281, "relic_set_id": 128, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1086, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1087, "level": 0, + "main_affix_id": 1, "relic_id": 51282, "relic_set_id": 128, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1087, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1088, "level": 0, + "main_affix_id": 1, "relic_id": 51283, "relic_set_id": 128, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1088, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1089, "level": 0, + "main_affix_id": 1, "relic_id": 51284, "relic_set_id": 128, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1089, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1090, "level": 0, + "main_affix_id": 1, "relic_id": 51291, "relic_set_id": 129, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1090, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1091, "level": 0, + "main_affix_id": 1, "relic_id": 51292, "relic_set_id": 129, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1091, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1092, "level": 0, + "main_affix_id": 1, "relic_id": 51293, "relic_set_id": 129, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1092, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1093, "level": 0, + "main_affix_id": 1, "relic_id": 51294, "relic_set_id": 129, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1093, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1094, "level": 0, + "main_affix_id": 1, "relic_id": 51301, "relic_set_id": 130, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1094, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1095, "level": 0, + "main_affix_id": 1, "relic_id": 51302, "relic_set_id": 130, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1095, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1096, "level": 0, + "main_affix_id": 1, "relic_id": 51303, "relic_set_id": 130, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1096, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1097, "level": 0, + "main_affix_id": 1, "relic_id": 51304, "relic_set_id": 130, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1097, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1098, "level": 0, + "main_affix_id": 1, "relic_id": 51311, "relic_set_id": 131, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1098, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1099, "level": 0, + "main_affix_id": 1, "relic_id": 51312, "relic_set_id": 131, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1099, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1100, "level": 0, + "main_affix_id": 1, "relic_id": 51313, "relic_set_id": 131, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1100, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1101, "level": 0, + "main_affix_id": 1, "relic_id": 51314, "relic_set_id": 131, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1101, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1102, "level": 0, + "main_affix_id": 1, "relic_id": 51321, "relic_set_id": 132, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1102, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1103, "level": 0, + "main_affix_id": 1, "relic_id": 51322, "relic_set_id": 132, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1103, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1104, "level": 0, + "main_affix_id": 1, "relic_id": 51323, "relic_set_id": 132, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1104, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1105, "level": 0, + "main_affix_id": 1, "relic_id": 51324, "relic_set_id": 132, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1105, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1106, "level": 0, + "main_affix_id": 1, "relic_id": 51331, "relic_set_id": 133, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1106, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1107, "level": 0, + "main_affix_id": 1, "relic_id": 51332, "relic_set_id": 133, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1107, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1108, "level": 0, + "main_affix_id": 1, "relic_id": 51333, "relic_set_id": 133, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1108, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1109, "level": 0, + "main_affix_id": 1, "relic_id": 51334, "relic_set_id": 133, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1109, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1110, "level": 0, + "main_affix_id": 1, "relic_id": 51341, "relic_set_id": 134, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1110, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1111, "level": 0, + "main_affix_id": 1, "relic_id": 51342, "relic_set_id": 134, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1111, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1112, "level": 0, + "main_affix_id": 1, "relic_id": 51343, "relic_set_id": 134, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1112, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1113, "level": 0, + "main_affix_id": 1, "relic_id": 51344, "relic_set_id": 134, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1113, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1114, "level": 0, + "main_affix_id": 1, "relic_id": 53015, "relic_set_id": 301, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1114, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1115, "level": 0, + "main_affix_id": 1, "relic_id": 53016, "relic_set_id": 301, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 1115, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1116, "level": 0, + "main_affix_id": 1, "relic_id": 53025, "relic_set_id": 302, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1116, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1117, "level": 0, + "main_affix_id": 1, "relic_id": 53026, "relic_set_id": 302, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 1117, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1118, "level": 0, + "main_affix_id": 1, "relic_id": 53035, "relic_set_id": 303, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1118, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1119, "level": 0, + "main_affix_id": 1, "relic_id": 53036, "relic_set_id": 303, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 1119, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1120, "level": 0, + "main_affix_id": 1, "relic_id": 53045, "relic_set_id": 304, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1120, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1121, "level": 0, + "main_affix_id": 1, "relic_id": 53046, "relic_set_id": 304, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 1121, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1122, "level": 0, + "main_affix_id": 1, "relic_id": 53055, "relic_set_id": 305, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1122, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1123, "level": 0, + "main_affix_id": 1, "relic_id": 53056, "relic_set_id": 305, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 1123, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1124, "level": 0, + "main_affix_id": 1, "relic_id": 53065, "relic_set_id": 306, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1124, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1125, "level": 0, + "main_affix_id": 1, "relic_id": 53066, "relic_set_id": 306, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 1125, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1126, "level": 0, + "main_affix_id": 1, "relic_id": 53075, "relic_set_id": 307, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1126, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1127, "level": 0, + "main_affix_id": 1, "relic_id": 53076, "relic_set_id": 307, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 1127, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1128, "level": 0, + "main_affix_id": 1, "relic_id": 53085, "relic_set_id": 308, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1128, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1129, "level": 0, + "main_affix_id": 1, "relic_id": 53086, "relic_set_id": 308, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 1129, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1130, "level": 0, + "main_affix_id": 1, "relic_id": 53095, "relic_set_id": 309, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1130, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1131, "level": 0, + "main_affix_id": 1, "relic_id": 53096, "relic_set_id": 309, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 1131, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1132, "level": 0, + "main_affix_id": 1, "relic_id": 53105, "relic_set_id": 310, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1132, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1133, "level": 0, + "main_affix_id": 1, "relic_id": 53106, "relic_set_id": 310, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 1133, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1134, "level": 0, + "main_affix_id": 1, "relic_id": 53115, "relic_set_id": 311, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1134, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1135, "level": 0, + "main_affix_id": 1, "relic_id": 53116, "relic_set_id": 311, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 1135, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1136, "level": 0, + "main_affix_id": 1, "relic_id": 53125, "relic_set_id": 312, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1136, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1137, "level": 0, + "main_affix_id": 1, "relic_id": 53126, "relic_set_id": 312, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 1137, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1138, "level": 0, + "main_affix_id": 1, "relic_id": 53135, "relic_set_id": 313, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1138, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1139, "level": 0, + "main_affix_id": 1, "relic_id": 53136, "relic_set_id": 313, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 1139, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1140, "level": 0, + "main_affix_id": 1, "relic_id": 53145, "relic_set_id": 314, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1140, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1141, "level": 0, + "main_affix_id": 1, "relic_id": 53146, "relic_set_id": 314, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 1141, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1142, "level": 0, + "main_affix_id": 1, "relic_id": 53155, "relic_set_id": 315, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1142, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1143, "level": 0, + "main_affix_id": 1, "relic_id": 53156, "relic_set_id": 315, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 1143, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1144, "level": 0, + "main_affix_id": 1, "relic_id": 53165, "relic_set_id": 316, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1144, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1145, "level": 0, + "main_affix_id": 1, "relic_id": 53166, "relic_set_id": 316, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 1145, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1146, "level": 0, + "main_affix_id": 1, "relic_id": 53175, "relic_set_id": 317, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1146, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1147, "level": 0, + "main_affix_id": 1, "relic_id": 53176, "relic_set_id": 317, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 1147, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1148, "level": 0, + "main_affix_id": 1, "relic_id": 53185, "relic_set_id": 318, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1148, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1149, "level": 0, + "main_affix_id": 1, "relic_id": 53186, "relic_set_id": 318, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 1149, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1150, "level": 0, + "main_affix_id": 1, "relic_id": 53195, "relic_set_id": 319, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1150, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1151, "level": 0, + "main_affix_id": 1, "relic_id": 53196, "relic_set_id": 319, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 1151, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1152, "level": 0, + "main_affix_id": 1, "relic_id": 53205, "relic_set_id": 320, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1152, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1153, "level": 0, + "main_affix_id": 1, "relic_id": 53206, "relic_set_id": 320, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 1153, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1154, "level": 0, + "main_affix_id": 1, "relic_id": 53215, "relic_set_id": 321, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1154, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1155, "level": 0, + "main_affix_id": 1, "relic_id": 53216, "relic_set_id": 321, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 1155, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1156, "level": 0, + "main_affix_id": 1, "relic_id": 53225, "relic_set_id": 322, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1156, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1157, "level": 0, + "main_affix_id": 1, "relic_id": 53226, "relic_set_id": 322, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 1157, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1158, "level": 0, + "main_affix_id": 1, "relic_id": 53235, "relic_set_id": 323, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1158, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1159, "level": 0, + "main_affix_id": 1, "relic_id": 53236, "relic_set_id": 323, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 1159, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1160, "level": 0, + "main_affix_id": 1, "relic_id": 53245, "relic_set_id": 324, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1160, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1161, "level": 0, + "main_affix_id": 1, "relic_id": 53246, "relic_set_id": 324, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 1161, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1162, "level": 0, + "main_affix_id": 1, "relic_id": 53255, "relic_set_id": 325, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1162, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1163, "level": 0, + "main_affix_id": 1, "relic_id": 53256, "relic_set_id": 325, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 1163, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1164, "level": 0, + "main_affix_id": 1, "relic_id": 53265, "relic_set_id": 326, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1164, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1165, "level": 0, + "main_affix_id": 1, "relic_id": 53266, "relic_set_id": 326, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 1165, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1166, "level": 0, + "main_affix_id": 1, "relic_id": 53275, "relic_set_id": 327, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1166, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1167, "level": 0, + "main_affix_id": 1, "relic_id": 53276, "relic_set_id": 327, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 1167, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1168, "level": 0, + "main_affix_id": 1, "relic_id": 53285, "relic_set_id": 328, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1168, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1169, "level": 0, + "main_affix_id": 1, "relic_id": 53286, "relic_set_id": 328, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 1169, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1170, "level": 0, + "main_affix_id": 1, "relic_id": 61011, "relic_set_id": 101, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1170, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1171, "level": 0, + "main_affix_id": 1, "relic_id": 61014, "relic_set_id": 101, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1171, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1172, "level": 0, + "main_affix_id": 1, "relic_id": 61061, "relic_set_id": 106, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1172, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1173, "level": 0, + "main_affix_id": 1, "relic_id": 61062, "relic_set_id": 106, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1173, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1174, "level": 0, + "main_affix_id": 1, "relic_id": 61063, "relic_set_id": 106, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1174, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1175, "level": 0, + "main_affix_id": 1, "relic_id": 61064, "relic_set_id": 106, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1175, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1176, "level": 0, + "main_affix_id": 1, "relic_id": 61071, "relic_set_id": 107, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1176, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1177, "level": 0, + "main_affix_id": 1, "relic_id": 61072, "relic_set_id": 107, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1177, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1178, "level": 0, + "main_affix_id": 1, "relic_id": 61073, "relic_set_id": 107, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1178, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1179, "level": 0, + "main_affix_id": 1, "relic_id": 61074, "relic_set_id": 107, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1179, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1180, "level": 0, + "main_affix_id": 1, "relic_id": 61091, "relic_set_id": 109, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1180, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1181, "level": 0, + "main_affix_id": 1, "relic_id": 61092, "relic_set_id": 109, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1181, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1182, "level": 0, + "main_affix_id": 1, "relic_id": 61093, "relic_set_id": 109, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1182, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1183, "level": 0, + "main_affix_id": 1, "relic_id": 61094, "relic_set_id": 109, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1183, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1184, "level": 0, + "main_affix_id": 1, "relic_id": 61111, "relic_set_id": 111, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1184, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1185, "level": 0, + "main_affix_id": 1, "relic_id": 61112, "relic_set_id": 111, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1185, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1186, "level": 0, + "main_affix_id": 1, "relic_id": 61113, "relic_set_id": 111, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1186, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1187, "level": 0, + "main_affix_id": 1, "relic_id": 61114, "relic_set_id": 111, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1187, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1188, "level": 0, + "main_affix_id": 1, "relic_id": 61121, "relic_set_id": 112, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1188, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1189, "level": 0, + "main_affix_id": 1, "relic_id": 61122, "relic_set_id": 112, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1189, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1190, "level": 0, + "main_affix_id": 1, "relic_id": 61123, "relic_set_id": 112, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1190, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1191, "level": 0, + "main_affix_id": 1, "relic_id": 61124, "relic_set_id": 112, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1191, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1192, "level": 0, + "main_affix_id": 1, "relic_id": 61341, "relic_set_id": 134, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1192, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1193, "level": 0, + "main_affix_id": 1, "relic_id": 61342, "relic_set_id": 134, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1193, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1194, "level": 0, + "main_affix_id": 1, "relic_id": 61343, "relic_set_id": 134, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1194, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1195, "level": 0, + "main_affix_id": 1, "relic_id": 61344, "relic_set_id": 134, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1195, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1196, "level": 0, + "main_affix_id": 1, "relic_id": 63015, "relic_set_id": 301, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1196, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1197, "level": 0, + "main_affix_id": 1, "relic_id": 63016, "relic_set_id": 301, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 1197, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1198, "level": 0, + "main_affix_id": 1, "relic_id": 63055, "relic_set_id": 305, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1198, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1199, "level": 0, + "main_affix_id": 1, "relic_id": 63056, "relic_set_id": 305, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 1199, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1200, "level": 0, + "main_affix_id": 1, "relic_id": 63125, "relic_set_id": 312, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1200, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1201, "level": 0, + "main_affix_id": 1, "relic_id": 63126, "relic_set_id": 312, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 1201, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1202, "level": 0, + "main_affix_id": 1, "relic_id": 63135, "relic_set_id": 313, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1202, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1203, "level": 0, + "main_affix_id": 1, "relic_id": 63136, "relic_set_id": 313, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 4, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 4 } - ], - "internal_uid": 1203, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1204, "level": 0, + "main_affix_id": 1, "relic_id": 63285, "relic_set_id": 328, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 }, { - "sub_affix_id": 5, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 5 } - ], - "internal_uid": 1204, - "equip_avatar": 0 + ] }, { + "equip_avatar": 0, + "internal_uid": 1205, "level": 0, + "main_affix_id": 1, "relic_id": 63286, "relic_set_id": 328, - "main_affix_id": 1, "sub_affixes": [ { - "sub_affix_id": 1, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 1 }, { - "sub_affix_id": 2, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 2 }, { - "sub_affix_id": 3, "count": 1, - "step": 0 + "step": 0, + "sub_affix_id": 3 + }, + { + "count": 1, + "step": 0, + "sub_affix_id": 4 + } + ] + }, + { + "internal_uid": 1206, + "relic_id": 63106, + "level": 15, + "equip_avatar": 1412, + "main_affix_id": 2, + "sub_affixes": [ + { + "sub_affix_id": 7, + "cnt": 4, + "count": 4, + "step": 2 + }, + { + "sub_affix_id": 11, + "cnt": 3, + "count": 3, + "step": 2 }, { "sub_affix_id": 4, + "cnt": 1, "count": 1, - "step": 0 + "step": 2 + }, + { + "sub_affix_id": 6, + "cnt": 1, + "count": 1, + "step": 2 } - ], - "internal_uid": 1205, - "equip_avatar": 0 + ] } ] } \ No newline at end of file diff --git a/internal/common/battle_report.go b/internal/common/battle_report.go index 9720d06..f5bbb49 100644 --- a/internal/common/battle_report.go +++ b/internal/common/battle_report.go @@ -53,6 +53,11 @@ func ApplyBattleReportContext(report *BattleReport, battle *goproto.SceneBattleI return } report.BattleID, report.StageID = battle.BattleId, battle.StageId + if battle.EIGMKOJOEEI != nil { + report.IsCurrencyWars = true + applyCurrencyWarsBattleReportContext(report, battle) + return + } indices := make(map[uint32]uint32, len(battle.BattleAvatarList)) metadata := make(map[uint32]*goproto.BattleAvatar, len(battle.BattleAvatarList)) for _, a := range battle.BattleAvatarList { @@ -82,6 +87,108 @@ func ApplyBattleReportContext(report *BattleReport, battle *goproto.SceneBattleI }) } +// In Currency Wars (GridFight), deployed avatars can exceed 4 (front + back rows). +// All active board roles are registered in grid.GridGameRoleList and their damages +// are tracked via stt.APAGMJNECCN.DOGMMGLBJIJ. +func applyCurrencyWarsBattleReportContext(report *BattleReport, battle *goproto.SceneBattleInfo) { + grid := battle.EIGMKOJOEEI + if grid == nil { + return + } + metadata := make(map[uint32]*goproto.BattleAvatar) + for _, a := range battle.BattleAvatarList { + if a != nil { + metadata[a.Id] = a + } + } + for _, a := range grid.OPHONCKPNMG { + if a != nil { + metadata[a.Id] = a + } + } + + existingByAvatar := make(map[uint32]*BattleAvatarReport, len(report.Avatars)) + for _, a := range report.Avatars { + if a != nil { + existingByAvatar[a.AvatarID] = a + } + } + + if len(grid.GridGameRoleList) > 0 { + var cwTotalDamage float64 + for _, role := range grid.GridGameRoleList { + if role == nil { + continue + } + var dmg float64 + if report.CurrencyWarsRoleDamages != nil { + dmg = report.CurrencyWarsRoleDamages[role.RoleId] + } else if ex, ok := existingByAvatar[role.AvatarId]; ok { + dmg = ex.TotalDamage + } + cwTotalDamage += dmg + } + if cwTotalDamage > 0 { + report.TeamTotalDamage = cwTotalDamage + } + + newAvatars := make([]*BattleAvatarReport, 0, len(grid.GridGameRoleList)) + for _, role := range grid.GridGameRoleList { + if role == nil { + continue + } + avatarID := role.AvatarId + if avatarID == 0 && role.RoleId > 100 { + avatarID = role.RoleId / 100 + } + ar := existingByAvatar[avatarID] + if ar == nil { + ar = &BattleAvatarReport{ + AvatarID: avatarID, + AttackTypeDamage: make(map[string]float64), + AttackTypeBreakDamage: make(map[string]float64), + AttackTypeMaxDamage: make(map[string]float64), + } + } + slot := role.Pos + ar.Slot = &slot + ar.AvatarRank = role.RoleStar + if meta := metadata[avatarID]; meta != nil { + ar.AvatarLevel = meta.Level + if ar.AvatarRank == 0 && meta.Rank > 0 { + ar.AvatarRank = meta.Rank + } + } + if ar.AvatarLevel == 0 { + ar.AvatarLevel = 80 + } + if report.CurrencyWarsRoleDamages != nil { + if dmg, ok := report.CurrencyWarsRoleDamages[role.RoleId]; ok { + ar.TotalDamage = dmg + } + } + if report.TeamTotalDamage > 0 { + ar.DamagePercent = (ar.TotalDamage / report.TeamTotalDamage) * 100.0 + } else { + ar.DamagePercent = 0 + } + newAvatars = append(newAvatars, ar) + } + + sort.SliceStable(newAvatars, func(i, j int) bool { + a, b := newAvatars[i], newAvatars[j] + if a == nil || a.Slot == nil { + return false + } + if b == nil || b.Slot == nil { + return true + } + return *a.Slot < *b.Slot + }) + report.Avatars = newAvatars + } +} + type BattleMonsterReport struct { EntityID uint32 `json:"entity_id"` MonsterID uint32 `json:"monster_id"` @@ -93,20 +200,22 @@ type BattleMonsterReport struct { } type BattleReport struct { - Timestamp int64 `json:"timestamp"` - BattleID uint32 `json:"battle_id"` - StageID uint32 `json:"stage_id"` - EndStatus string `json:"end_status"` // "WIN", "FAIL", "QUIT" - TotalBattleTurns uint32 `json:"total_battle_turns"` - TotalAutoTurns uint32 `json:"total_auto_turns"` - RoundCnt uint32 `json:"round_cnt"` - CostTime float64 `json:"cost_time"` - ChallengeScore uint32 `json:"challenge_score"` - TeamTotalDamage float64 `json:"team_total_damage"` - TeamTotalHeal float64 `json:"team_total_heal"` - TeamTotalShield float64 `json:"team_total_shield"` - Avatars []*BattleAvatarReport `json:"avatars"` - Monsters []*BattleMonsterReport `json:"monsters"` + Timestamp int64 `json:"timestamp"` + BattleID uint32 `json:"battle_id"` + StageID uint32 `json:"stage_id"` + EndStatus string `json:"end_status"` // "WIN", "FAIL", "QUIT" + TotalBattleTurns uint32 `json:"total_battle_turns"` + TotalAutoTurns uint32 `json:"total_auto_turns"` + RoundCnt uint32 `json:"round_cnt"` + CostTime float64 `json:"cost_time"` + ChallengeScore uint32 `json:"challenge_score"` + TeamTotalDamage float64 `json:"team_total_damage"` + TeamTotalHeal float64 `json:"team_total_heal"` + TeamTotalShield float64 `json:"team_total_shield"` + Avatars []*BattleAvatarReport `json:"avatars"` + Monsters []*BattleMonsterReport `json:"monsters"` + IsCurrencyWars bool `json:"is_currency_wars,omitempty"` + CurrencyWarsRoleDamages map[uint32]float64 `json:"currency_wars_role_damages,omitempty"` } // BattleReportFromStatistics converts a protobuf BattleStatistics packet from the game client @@ -129,6 +238,24 @@ func BattleReportFromStatistics(stt *goproto.BattleStatistics, battleID uint32, Monsters: make([]*BattleMonsterReport, 0, len(stt.MonsterList)), } + // In Currency Wars (GridFight), per-role combat damage is explicitly recorded in + // stt.APAGMJNECCN.DOGMMGLBJIJ (*pb.CPNCAPLOIHI). + var cwRoleDamages map[uint32]float64 + var cwTotalRoleDamage float64 + if stt.APAGMJNECCN != nil && len(stt.APAGMJNECCN.DOGMMGLBJIJ) > 0 { + cwRoleDamages = make(map[uint32]float64, len(stt.APAGMJNECCN.DOGMMGLBJIJ)) + for _, item := range stt.APAGMJNECCN.DOGMMGLBJIJ { + if item != nil && item.RoleId != 0 { + cwRoleDamages[item.RoleId] = item.Damage + if item.Damage > 0 { + cwTotalRoleDamage += item.Damage + } + } + } + report.CurrencyWarsRoleDamages = cwRoleDamages + report.IsCurrencyWars = true + } + // The 4.6.53 schema exposes OKFALMGFHMA entries with avatar_id and damage, // but the enclosing message and several fields remain obfuscated. Treating // these as per-avatar damage records is an inference that still needs a raw @@ -177,6 +304,14 @@ func BattleReportFromStatistics(stt *goproto.BattleStatistics, battleID uint32, if damage == 0 { damage = attackDamageByAvatar[a.Id] } + if damage == 0 && cwRoleDamages != nil { + for rID, rDmg := range cwRoleDamages { + if rID == a.Id || rID/100 == a.Id { + damage = rDmg + break + } + } + } primaryAvatarDamage += damage if damage == 0 { damage = supplementalDamageByAvatar[a.Id] @@ -185,7 +320,9 @@ func BattleReportFromStatistics(stt *goproto.BattleStatistics, battleID uint32, teamHeal += a.TotalHeal teamShield += a.TotalShield } - if primaryAvatarDamage == 0 && stt.MFOJKJGKDGD != nil && stt.MFOJKJGKDGD.TotalDamage > 0 { + if cwTotalRoleDamage > 0 { + teamDmg = cwTotalRoleDamage + } else if primaryAvatarDamage == 0 && stt.MFOJKJGKDGD != nil && stt.MFOJKJGKDGD.TotalDamage > 0 { teamDmg = stt.MFOJKJGKDGD.TotalDamage } // If neither avatar aggregate source contains damage, enemy damage-taken @@ -216,6 +353,14 @@ func BattleReportFromStatistics(stt *goproto.BattleStatistics, battleID uint32, if damage == 0 { damage = attackDamageByAvatar[a.Id] } + if damage == 0 && cwRoleDamages != nil { + for rID, rDmg := range cwRoleDamages { + if rID == a.Id || rID/100 == a.Id { + damage = rDmg + break + } + } + } if damage == 0 { damage = supplementalDamageByAvatar[a.Id] } diff --git a/internal/common/battle_report_test.go b/internal/common/battle_report_test.go index 190edc4..28a6834 100644 --- a/internal/common/battle_report_test.go +++ b/internal/common/battle_report_test.go @@ -5,6 +5,7 @@ import ( "testing" goproto "gac-hkrpg-go/protocol/proto" + "google.golang.org/protobuf/proto" ) func TestBattleReportUsesActualBattleSlots(t *testing.T) { @@ -234,3 +235,140 @@ func TestBattleReportDoesNotReplaceStandardDamageWithSupplementalAggregate(t *te t.Fatalf("supplemental data replaced standard damage: %+v", report) } } + +func TestCurrencyWarsBattleReportWithMoreThanFourAvatars(t *testing.T) { + // Simulated CW battle: 4 front row avatars + 4 back row avatars = 8 avatars on board. + stt := &goproto.BattleStatistics{ + TotalBattleTurns: 15, + RoundCnt: 1, + CostTime: 30.0, + BattleAvatarList: []*goproto.AvatarBattleInfo{ + {Id: 1213, TotalDamage: 0}, + {Id: 1308, TotalDamage: 0}, + {Id: 1511, TotalDamage: 0}, + {Id: 1513, TotalDamage: 0}, + }, + APAGMJNECCN: &goproto.MGMJOPHJCNL{ + DOGMMGLBJIJ: []*goproto.CPNCAPLOIHI{ + {RoleId: 121302, Damage: 100000}, + {RoleId: 130802, Damage: 50000}, + {RoleId: 151102, Damage: 10000}, + {RoleId: 151302, Damage: 40000}, + {RoleId: 604202, Damage: 20000}, + {RoleId: 150302, Damage: 30000}, + {RoleId: 800902, Damage: 15000}, + {RoleId: 150502, Damage: 35000}, + }, + }, + } + + report := BattleReportFromStatistics(stt, 101, 80211, "WIN") + if !report.IsCurrencyWars { + t.Fatal("expected IsCurrencyWars to be true") + } + if report.TeamTotalDamage != 300000 { + t.Fatalf("expected team total damage 300000, got %f", report.TeamTotalDamage) + } + + battleContext := &goproto.SceneBattleInfo{ + BattleId: 101, + StageId: 80211, + BattleAvatarList: []*goproto.BattleAvatar{ + {Id: 1213, Level: 80, Rank: 2, Index: 0}, + {Id: 1308, Level: 80, Rank: 1, Index: 1}, + {Id: 1511, Level: 80, Rank: 0, Index: 2}, + {Id: 1513, Level: 80, Rank: 1, Index: 3}, + }, + EIGMKOJOEEI: &goproto.OJCBOMCCPDB{ + OPHONCKPNMG: []*goproto.BattleAvatar{ + {Id: 6042, Level: 80, Rank: 1, Index: 0}, + {Id: 1503, Level: 80, Rank: 0, Index: 1}, + {Id: 8009, Level: 80, Rank: 6, Index: 2}, + {Id: 1505, Level: 80, Rank: 0, Index: 3}, + }, + GridGameRoleList: []*goproto.BJGJBNJDLIK{ + {RoleId: 121302, AvatarId: 1213, Pos: 1, RoleStar: 3}, + {RoleId: 130802, AvatarId: 1308, Pos: 2, RoleStar: 2}, + {RoleId: 151102, AvatarId: 1511, Pos: 3, RoleStar: 1}, + {RoleId: 151302, AvatarId: 1513, Pos: 4, RoleStar: 2}, + {RoleId: 604202, AvatarId: 6042, Pos: 5, RoleStar: 1}, + {RoleId: 150302, AvatarId: 1503, Pos: 6, RoleStar: 2}, + {RoleId: 800902, AvatarId: 8009, Pos: 7, RoleStar: 2}, + {RoleId: 150502, AvatarId: 1505, Pos: 8, RoleStar: 3}, + }, + }, + } + + ApplyBattleReportContext(report, battleContext) + + if len(report.Avatars) != 8 { + t.Fatalf("expected 8 avatars in CW battle report, got %d", len(report.Avatars)) + } + + expectedDamages := map[uint32]float64{ + 1213: 100000, + 1308: 50000, + 1511: 10000, + 1513: 40000, + 6042: 20000, + 1503: 30000, + 8009: 15000, + 1505: 35000, + } + + var totalPercent float64 + for i, a := range report.Avatars { + if a.Slot == nil || *a.Slot != uint32(i+1) { + t.Fatalf("avatar %d slot expected %d, got %v", a.AvatarID, i+1, a.Slot) + } + expectedDmg := expectedDamages[a.AvatarID] + if a.TotalDamage != expectedDmg { + t.Fatalf("avatar %d damage expected %f, got %f", a.AvatarID, expectedDmg, a.TotalDamage) + } + totalPercent += a.DamagePercent + } + + if totalPercent < 99.9 || totalPercent > 100.1 { + t.Fatalf("expected total percent ~100%%, got %f", totalPercent) + } + + // Verify star rank + if report.Avatars[0].AvatarRank != 3 { + t.Fatalf("expected Dan Heng IL rank (star) to be 3, got %d", report.Avatars[0].AvatarRank) + } +} + +func TestRealCapturedBattleResult(t *testing.T) { + path := "../../battle_diagnostics/20261031T050254.858914500Z_result_4462dff22ebf/battle_result.bin" + wire, err := os.ReadFile(path) + if err != nil { + t.Skip("real captured battle result not found on disk") + } + var req goproto.PVEBattleResultCsReq + if err := proto.Unmarshal(wire, &req); err != nil { + t.Fatal(err) + } + report := BattleReportFromStatistics(req.Stt, req.BattleId, req.StageId, "WIN") + if !report.IsCurrencyWars { + t.Fatal("expected IsCurrencyWars = true") + } + if len(report.CurrencyWarsRoleDamages) != 9 { + t.Fatalf("expected 9 role damages, got %d", len(report.CurrencyWarsRoleDamages)) + } + if report.TeamTotalDamage < 115000000 { + t.Fatalf("expected team total damage > 115M, got %f", report.TeamTotalDamage) + } + + battleWire, err := os.ReadFile("../../battle_diagnostics/20261031T050532.034020800Z_86babfb031c1/battle_last.bin") + if err == nil { + var battle goproto.SceneBattleInfo + if err := proto.Unmarshal(battleWire, &battle); err == nil { + ApplyBattleReportContext(report, &battle) + if len(report.Avatars) != 9 { + t.Fatalf("expected 9 avatars after applying CW battle context, got %d", len(report.Avatars)) + } + // Write to battle_report.json in root so current dashboard reflects this real battle + _ = SaveBattleReport(report) + } + } +} diff --git a/internal/common/currency_wars_augments.go b/internal/common/currency_wars_augments.go new file mode 100644 index 0000000..b005b0c --- /dev/null +++ b/internal/common/currency_wars_augments.go @@ -0,0 +1,73 @@ +package common + +import ( + "encoding/json" + "fmt" + "slices" +) + +type CurrencyWarsAugmentGrant struct { + Kind string `json:"kind"` + Count uint32 `json:"count"` + ID uint32 `json:"id,omitempty"` + Cost uint32 `json:"cost,omitempty"` + Star uint32 `json:"star,omitempty"` +} + +type CurrencyWarsAugment struct { + ID uint32 `json:"id"` + Quality uint32 `json:"quality"` + Chapters []uint32 `json:"chapters"` + OC bool `json:"oc"` + Grants []CurrencyWarsAugmentGrant `json:"grants"` + GrantsComplete bool `json:"grants_complete"` +} + +type CurrencyWarsAugmentsCatalog struct { + FormatVersion uint32 `json:"format_version"` + TargetClientVersion string `json:"target_client_version"` + TargetAssemblySHA256 string `json:"target_assembly_sha256"` + SeasonID uint32 `json:"season_id"` + Augments []CurrencyWarsAugment `json:"augments"` + + byID map[uint32]CurrencyWarsAugment +} + +func LoadCurrencyWarsAugments(catalog *CurrencyWarsCatalog) (*CurrencyWarsAugmentsCatalog, error) { + raw, err := ReadResourceFile("resources/CurrencyWarsAugments.json") + if err != nil { + return nil, err + } + var out CurrencyWarsAugmentsCatalog + if err = json.Unmarshal(raw, &out); err != nil { + return nil, err + } + if catalog != nil { + if out.FormatVersion != 1 || out.TargetClientVersion != catalog.ClientVersion || out.TargetAssemblySHA256 != catalog.Provenance.GameAssemblySHA256 { + return nil, fmt.Errorf("CW augments revision differs from catalog") + } + } + out.byID = make(map[uint32]CurrencyWarsAugment, len(out.Augments)) + for _, a := range out.Augments { + out.byID[a.ID] = a + } + return &out, nil +} + +func (c *CurrencyWarsAugmentsCatalog) Augment(id uint32) (CurrencyWarsAugment, bool) { + if c == nil || c.byID == nil { + return CurrencyWarsAugment{}, false + } + a, ok := c.byID[id] + return a, ok +} + +func (c *CurrencyWarsAugmentsCatalog) IsEligible(aug CurrencyWarsAugment, chapter uint32, isOverclock bool) bool { + if isOverclock && !aug.OC { + return false + } + if len(aug.Chapters) > 0 && !slices.Contains(aug.Chapters, chapter) { + return false + } + return true +} diff --git a/internal/common/currency_wars_commands.go b/internal/common/currency_wars_commands.go index c6f7c01..9e2559b 100644 --- a/internal/common/currency_wars_commands.go +++ b/internal/common/currency_wars_commands.go @@ -36,7 +36,10 @@ func QueueCurrencyWarsCommand(uid, runID uint32, field string, value uint32) (st } func QueueCurrencyWarsGrant(uid, runID uint32, field string, value, quantity, star uint32) (string, error) { - if uid == 0 || runID == 0 || quantity == 0 || (field != "gold" && field != "hp" && field != "exp" && field != "equip" && field != "role" && field != "orb" && field != "consumable" && field != "forge") { + if field == "tb" || field == "trailblaze" { + field = "tbpower" + } + if uid == 0 || runID == 0 || quantity == 0 || (field != "gold" && field != "hp" && field != "exp" && field != "equip" && field != "role" && field != "orb" && field != "consumable" && field != "forge" && field != "tbpower") { return "", fmt.Errorf("invalid CW command") } var nonce [16]byte diff --git a/internal/common/currency_wars_merge.go b/internal/common/currency_wars_merge.go index b5a285c..163fc9e 100644 --- a/internal/common/currency_wars_merge.go +++ b/internal/common/currency_wars_merge.go @@ -53,15 +53,13 @@ func (p *CurrencyWarsCardPool) ReserveOwned(c *CurrencyWarsCatalog, id, star uin copies := uint64(1) for s := base.DefaultStar; s < star; s++ { copies *= uint64(rules.Copies) - if copies > uint64(entry.Capacity) { - return fmt.Errorf("CW owned copies exceed pool capacity") - } } - if copies > uint64(entry.Remaining) { - return fmt.Errorf("CW pool exhausted for %d", id) + if copies >= uint64(entry.Remaining) { + entry.Remaining = 0 + } else { + entry.Remaining -= uint32(copies) } - entry.Remaining -= uint32(copies) - entry.Disabled = entry.Disabled || star >= rules.OrdinaryMaxStar + entry.Disabled = entry.Disabled || star >= rules.OrdinaryMaxStar || entry.Remaining == 0 p.Roles[id] = entry return nil } diff --git a/internal/common/currency_wars_more_test.go b/internal/common/currency_wars_more_test.go new file mode 100644 index 0000000..2c6bad5 --- /dev/null +++ b/internal/common/currency_wars_more_test.go @@ -0,0 +1,110 @@ +package common + +import ( + "testing" +) + +func TestCurrencyWarsScoreCalculations(t *testing.T) { + score, err := LoadCurrencyWarsScore(nil) + if err != nil { + t.Fatalf("load CW score: %v", err) + } + if score.WeeklyCap != 18000 { + t.Errorf("expected weekly cap 18000, got %d", score.WeeklyCap) + } + if len(score.Divisions) == 0 || len(score.Rules) == 0 { + t.Fatal("empty divisions or rules in CW score") + } + + // Division 20920: ScoreRule 1, WeeklyScoreModify 100, ExpModify 180. + // Chapter 2, Section 4 won (or lost on 2-5): matches upstream test at line 11065! + weekly, expBase, expExtra := score.CalculateRunScore(20920, false, 2, 4, true) + if weekly != 13200 { + t.Errorf("expected weekly score 13200, got %d", weekly) + } + if expBase != 725 { + t.Errorf("expected exp base 725, got %d", expBase) + } + if expExtra != 580 { + t.Errorf("expected exp extra 580, got %d", expExtra) + } + + // Offering Level calculation: 800 EXP per level for Offering 16 (Season 2) + level, remainder := score.OfferingLevel(1305) + if level != 1 { + t.Errorf("expected level 1, got %d", level) + } + if remainder != 505 { + t.Errorf("expected remainder 505, got %d", remainder) + } +} + +func TestCurrencyWarsTraitsCatalog(t *testing.T) { + traits, err := LoadCurrencyWarsTraits(nil) + if err != nil { + t.Fatalf("load CW traits: %v", err) + } + if len(traits.Traits) < 60 { + t.Fatalf("expected at least 60 traits, got %d", len(traits.Traits)) + } + + // Trait 1001 (Express Cohort) has thresholds [2, 4, 6] + tr, ok := traits.Trait(1001) + if !ok { + t.Fatal("trait 1001 missing") + } + if len(tr.Thresholds) != 3 { + t.Fatalf("expected 3 thresholds, got %d", len(tr.Thresholds)) + } + + if layer := traits.ActiveLayer(1001, 1); layer != 0 { + t.Errorf("expected layer 0 for count 1, got %d", layer) + } + if layer := traits.ActiveLayer(1001, 3); layer != 2 { + t.Errorf("expected layer 2 for count 3, got %d", layer) + } + if layer := traits.ActiveLayer(1001, 5); layer != 4 { + t.Errorf("expected layer 4 for count 5, got %d", layer) + } + if layer := traits.ActiveLayer(1001, 6); layer != 6 { + t.Errorf("expected layer 6 for count 6, got %d", layer) + } +} + +func TestCurrencyWarsAugmentsCatalog(t *testing.T) { + catalog, err := LoadCurrencyWarsAugments(nil) + if err != nil { + t.Fatalf("load CW augments: %v", err) + } + if len(catalog.Augments) != 304 { + t.Fatalf("expected 304 augments, got %d", len(catalog.Augments)) + } + + aug, ok := catalog.Augment(100101) + if !ok { + t.Fatal("augment 100101 missing") + } + if aug.Quality != 1 { + t.Errorf("expected quality 1, got %d", aug.Quality) + } + if !catalog.IsEligible(aug, 1, true) { + t.Error("expected augment 100101 to be eligible in chapter 1 overclock") + } + if catalog.IsEligible(aug, 3, true) { + t.Error("expected augment 100101 to not be eligible in chapter 3") + } +} + +func TestCurrencyWarsNodeDropsCatalog(t *testing.T) { + drops, err := LoadCurrencyWarsNodeDrops(nil) + if err != nil { + t.Fatalf("load CW node drops: %v", err) + } + if len(drops.NodeDrops) == 0 { + t.Fatal("expected node drops rules") + } + rule900, ok := drops.NodeDrops["900"] + if !ok || rule900.Rolls != 4 { + t.Errorf("expected rule 900 with 4 rolls, got %v", rule900) + } +} diff --git a/internal/common/currency_wars_node_drops.go b/internal/common/currency_wars_node_drops.go new file mode 100644 index 0000000..03bea02 --- /dev/null +++ b/internal/common/currency_wars_node_drops.go @@ -0,0 +1,35 @@ +package common + +import ( + "encoding/json" + "fmt" +) + +type CurrencyWarsNodeDrop struct { + Rolls uint32 `json:"rolls"` + Weights map[string]uint32 `json:"weights"` +} + +type CurrencyWarsNodeDropsCatalog struct { + FormatVersion uint32 `json:"format_version"` + TargetClientVersion string `json:"target_client_version"` + TargetAssemblySHA256 string `json:"target_assembly_sha256"` + NodeDrops map[string]CurrencyWarsNodeDrop `json:"node_drops"` +} + +func LoadCurrencyWarsNodeDrops(catalog *CurrencyWarsCatalog) (*CurrencyWarsNodeDropsCatalog, error) { + raw, err := ReadResourceFile("resources/CurrencyWarsNodeDrops.json") + if err != nil { + return nil, err + } + var out CurrencyWarsNodeDropsCatalog + if err = json.Unmarshal(raw, &out); err != nil { + return nil, err + } + if catalog != nil { + if out.FormatVersion != 1 || out.TargetClientVersion != catalog.ClientVersion || out.TargetAssemblySHA256 != catalog.Provenance.GameAssemblySHA256 { + return nil, fmt.Errorf("CW node drops revision differs from catalog") + } + } + return &out, nil +} diff --git a/internal/common/currency_wars_score.go b/internal/common/currency_wars_score.go new file mode 100644 index 0000000..543c1e0 --- /dev/null +++ b/internal/common/currency_wars_score.go @@ -0,0 +1,121 @@ +package common + +import ( + "encoding/json" + "fmt" + "strconv" +) + +type CurrencyWarsWeeklyRank struct { + Rank uint32 `json:"rank"` + Score uint32 `json:"score"` + Reward uint32 `json:"reward"` +} + +type CurrencyWarsScoreDivision struct { + Rule uint32 `json:"rule"` + OCRule uint32 `json:"oc_rule"` + WeeklyModify uint32 `json:"weekly_modify"` + ExpModify uint32 `json:"exp_modify"` +} + +type CurrencyWarsScoreNode struct { + Chapter uint32 `json:"chapter"` + Section uint32 `json:"section"` + Weekly uint32 `json:"weekly"` + Exp uint32 `json:"exp"` +} + +type CurrencyWarsOfferingLevel struct { + Level uint32 `json:"level"` + ItemCost uint32 `json:"item_cost"` + RewardID uint32 `json:"reward_id"` +} + +type CurrencyWarsScore struct { + FormatVersion uint32 `json:"format_version"` + TargetClientVersion string `json:"target_client_version"` + TargetAssemblySHA256 string `json:"target_assembly_sha256"` + WeeklyCap uint32 `json:"weekly_cap"` + WeeklyRanks []CurrencyWarsWeeklyRank `json:"weekly_ranks"` + Divisions map[string]CurrencyWarsScoreDivision `json:"divisions"` + Rules map[string][]CurrencyWarsScoreNode `json:"rules"` + OfferingLevels []CurrencyWarsOfferingLevel `json:"offering_levels"` +} + +func LoadCurrencyWarsScore(catalog *CurrencyWarsCatalog) (*CurrencyWarsScore, error) { + raw, err := ReadResourceFile("resources/CurrencyWarsScore.json") + if err != nil { + return nil, err + } + var out CurrencyWarsScore + if err = json.Unmarshal(raw, &out); err != nil { + return nil, err + } + if catalog != nil { + if out.FormatVersion != 1 || out.TargetClientVersion != catalog.ClientVersion || out.TargetAssemblySHA256 != catalog.Provenance.GameAssemblySHA256 { + return nil, fmt.Errorf("CW score revision differs from catalog") + } + } + return &out, nil +} + +func (s *CurrencyWarsScore) CalculateRunScore(divisionID uint32, isOverclock bool, chapter, section uint32, isWon bool) (weeklyScore, expBase, expExtra uint32) { + if s == nil { + return 0, 0, 0 + } + divKey := strconv.FormatUint(uint64(divisionID), 10) + div, exists := s.Divisions[divKey] + if !exists { + return 0, 0, 0 + } + rule := div.Rule + if isOverclock && div.OCRule != 0 { + rule = div.OCRule + } + ruleKey := strconv.FormatUint(uint64(rule), 10) + nodes, exists := s.Rules[ruleKey] + if !exists || len(nodes) == 0 { + return 0, 0, 0 + } + here := chapter*100 + section + var best *CurrencyWarsScoreNode + for i := range nodes { + n := &nodes[i] + at := n.Chapter*100 + n.Section + if at < here || (isWon && at == here) { + best = n + } + } + if best == nil { + return 0, 0, 0 + } + weeklyMod := div.WeeklyModify + if weeklyMod == 0 { + weeklyMod = 100 + } + weeklyScore = (best.Weekly * weeklyMod) / 100 + expBase = best.Exp + if div.ExpModify > 100 { + expExtra = (best.Exp * (div.ExpModify - 100)) / 100 + } + return weeklyScore, expBase, expExtra +} + +func (s *CurrencyWarsScore) OfferingLevel(exp uint32) (level uint32, levelExp uint32) { + if s == nil || len(s.OfferingLevels) == 0 { + return 0, exp + } + left := exp + for _, row := range s.OfferingLevels { + if row.ItemCost == 0 { + continue + } + if left < row.ItemCost { + break + } + left -= row.ItemCost + level = row.Level + } + return level, left +} diff --git a/internal/common/currency_wars_test.go b/internal/common/currency_wars_test.go index 97d0e00..c35131f 100644 --- a/internal/common/currency_wars_test.go +++ b/internal/common/currency_wars_test.go @@ -176,3 +176,21 @@ func TestCurrencyWarsSnapshotRevisionPreventsStaleWrites(t *testing.T) { t.Fatalf("CW commit failed to preserve unrelated persistent data: %s", strings.TrimSpace(string(persistent))) } } + +func TestCurrencyWarsReserveOwnedMaxStar(t *testing.T) { + c := loadCurrencyWarsTestCatalog(t) + pool, err := c.NewCapturedCardPool() + if err != nil { + t.Fatal(err) + } + if err := pool.ReserveOwned(c, 130602, 4); err != nil { + t.Fatalf("ReserveOwned for 4-star role: %v", err) + } + role := pool.Roles[130602] + if role.Remaining != 0 { + t.Fatalf("expected remaining to be 0, got %d", role.Remaining) + } + if !role.Disabled { + t.Fatal("expected role to be disabled in pool") + } +} diff --git a/internal/common/currency_wars_trailblaze.go b/internal/common/currency_wars_trailblaze.go new file mode 100644 index 0000000..776b21e --- /dev/null +++ b/internal/common/currency_wars_trailblaze.go @@ -0,0 +1,97 @@ +package common + +import ( + "encoding/json" + "fmt" + "sort" + "strconv" +) + +type CurrencyWarsTrailblazeGood struct { + ID uint32 `json:"id"` + Kind string `json:"kind"` + Cost uint32 `json:"cost"` + Father uint32 `json:"father"` + Params []uint32 `json:"params"` +} + +type CurrencyWarsTrailblazeCatalog struct { + Goods map[uint32]CurrencyWarsTrailblazeGood + GoodsList []CurrencyWarsTrailblazeGood + LayerParams map[uint32][]uint32 + PomThreshold uint32 + InitialGoodID uint32 + RefreshGoodID uint32 + PomGoodID uint32 +} + +func LoadCurrencyWarsTrailblaze() (*CurrencyWarsTrailblazeCatalog, error) { + raw, err := ReadResourceFile("resources/CurrencyWarsTrailblaze.json") + if err != nil { + return nil, err + } + var r struct { + Goods []CurrencyWarsTrailblazeGood `json:"goods"` + LayerParams map[string][]uint32 `json:"layer_params"` + PomThreshold uint32 `json:"pom_threshold"` + InitialGood uint32 `json:"initial_goods_id"` + RefreshGood uint32 `json:"refresh_goods_id"` + PomGood uint32 `json:"pom_goods_id"` + } + if err = json.Unmarshal(raw, &r); err != nil { + return nil, fmt.Errorf("unmarshal CurrencyWarsTrailblaze: %w", err) + } + + cat := &CurrencyWarsTrailblazeCatalog{ + Goods: make(map[uint32]CurrencyWarsTrailblazeGood, len(r.Goods)), + GoodsList: r.Goods, + LayerParams: make(map[uint32][]uint32, len(r.LayerParams)), + PomThreshold: r.PomThreshold, + InitialGoodID: r.InitialGood, + RefreshGoodID: r.RefreshGood, + PomGoodID: r.PomGood, + } + if cat.InitialGoodID == 0 { + cat.InitialGoodID = 101 + } + if cat.RefreshGoodID == 0 { + cat.RefreshGoodID = 999 + } + if cat.PomGoodID == 0 { + cat.PomGoodID = 507 + } + if cat.PomThreshold == 0 { + cat.PomThreshold = 300 + } + + for _, g := range r.Goods { + cat.Goods[g.ID] = g + } + + for k, v := range r.LayerParams { + layer, err := strconv.ParseUint(k, 10, 32) + if err == nil { + cat.LayerParams[uint32(layer)] = v + } + } + + sort.Slice(cat.GoodsList, func(i, j int) bool { + return cat.GoodsList[i].ID < cat.GoodsList[j].ID + }) + + return cat, nil +} + +func (c *CurrencyWarsTrailblazeCatalog) Tier(layer uint32) uint32 { + if params, ok := c.LayerParams[layer]; ok && len(params) > 0 { + return params[0] + } + return 1 +} + +func (c *CurrencyWarsTrailblazeCatalog) Income(layer uint32) uint32 { + if params, ok := c.LayerParams[layer]; ok && len(params) > 1 { + return params[1] + } + return 0 +} diff --git a/internal/common/currency_wars_traits.go b/internal/common/currency_wars_traits.go new file mode 100644 index 0000000..6087a10 --- /dev/null +++ b/internal/common/currency_wars_traits.go @@ -0,0 +1,74 @@ +package common + +import ( + "encoding/json" + "fmt" +) + +type CurrencyWarsTrait struct { + ID uint32 `json:"id"` + Name string `json:"name"` + Thresholds []uint32 `json:"thresholds"` + ActivationType uint32 `json:"activation_type"` + TraitType uint32 `json:"trait_type"` + SeasonID uint32 `json:"season_id"` +} + +type CurrencyWarsTraitsCatalog struct { + FormatVersion uint32 `json:"format_version"` + TargetClientVersion string `json:"target_client_version"` + TargetAssemblySHA256 string `json:"target_assembly_sha256"` + Traits []CurrencyWarsTrait `json:"traits"` + Effects map[string]int32 `json:"effects"` + Constants map[string]interface{} `json:"constants"` + + byID map[uint32]CurrencyWarsTrait +} + +func LoadCurrencyWarsTraits(catalog *CurrencyWarsCatalog) (*CurrencyWarsTraitsCatalog, error) { + raw, err := ReadResourceFile("resources/CurrencyWarsTraits.json") + if err != nil { + return nil, err + } + var out CurrencyWarsTraitsCatalog + if err = json.Unmarshal(raw, &out); err != nil { + return nil, err + } + if catalog != nil { + if out.FormatVersion != 1 || out.TargetClientVersion != catalog.ClientVersion || out.TargetAssemblySHA256 != catalog.Provenance.GameAssemblySHA256 { + return nil, fmt.Errorf("CW traits revision differs from catalog") + } + } + out.byID = make(map[uint32]CurrencyWarsTrait, len(out.Traits)) + for _, t := range out.Traits { + out.byID[t.ID] = t + } + return &out, nil +} + +func (c *CurrencyWarsTraitsCatalog) Trait(id uint32) (CurrencyWarsTrait, bool) { + if c == nil || c.byID == nil { + return CurrencyWarsTrait{}, false + } + t, ok := c.byID[id] + return t, ok +} + +func (c *CurrencyWarsTraitsCatalog) ActiveLayer(traitID, count uint32) uint32 { + t, ok := c.Trait(traitID) + if !ok { + return 0 + } + exact := t.ActivationType == 3 + var layer uint32 + for _, need := range t.Thresholds { + valid := count >= need + if exact { + valid = count == need + } + if valid && need > layer { + layer = need + } + } + return layer +} diff --git a/internal/common/currency_wars_xaodev.go b/internal/common/currency_wars_xaodev.go new file mode 100644 index 0000000..8f62eff --- /dev/null +++ b/internal/common/currency_wars_xaodev.go @@ -0,0 +1,204 @@ +// SPDX-License-Identifier: AGPL-3.0-only +// Adapted from Currency-War-SR by xaodev (2026); see third_party/currency-war-sr. +package common + +import ( + "crypto/sha256" + "encoding/hex" + "encoding/json" + "fmt" +) + +type CurrencyWarsBranchRule struct { + ID uint32 `json:"id"` + Family uint32 `json:"family"` + Quality uint32 `json:"quality"` + PerformLevel uint32 `json:"perform_level"` +} + +type CurrencyWarsXaodev struct { + Format uint32 `json:"format_version"` + Client string `json:"target_client_version"` + Assembly string `json:"target_assembly_sha256"` + Provenance struct { + Commit string `json:"commit"` + Inputs map[string]string `json:"inputs_sha256"` + Native struct { + Assembly string `json:"game_assembly_sha256"` + } `json:"native_source"` + } `json:"provenance"` + NodeRules []CurrencyWarsBranchRule `json:"node_rules"` + DiffAdd []struct{ Rule, Quality, Add uint32 } `json:"diff_add"` + Difficulty []struct{ Chapter, Level uint32 } `json:"difficulty_levels"` + Divisions []struct{ DivisionID, EnemyDifficultyLevel, BinaryNodeDiffAddRule uint32 } `json:"divisions"` + Policy struct { + Source string `json:"source"` + MonsterCount uint32 `json:"monster_count"` + MonsterTier uint32 `json:"monster_tier"` + StarCap uint32 `json:"star_cap"` + NormalQuality uint32 `json:"normal_quality"` + PowerfulQualities []uint32 `json:"powerful_qualities"` + RewardPolicy string `json:"reward_policy"` + ExtraRewards bool `json:"extra_rewards_supported"` + } `json:"generation_policy"` +} + +func LoadCurrencyWarsXaodev(c *CurrencyWarsCatalog) (*CurrencyWarsXaodev, error) { + raw, err := ReadResourceFile("resources/CurrencyWarsXaodev.json") + if err != nil { + return nil, err + } + v := new(CurrencyWarsXaodev) + if err = json.Unmarshal(raw, v); err != nil { + return nil, err + } + if c == nil || v.Format != 1 || v.Client != c.ClientVersion || v.Assembly != c.Provenance.GameAssemblySHA256 || v.Provenance.Native.Assembly != v.Assembly { + return nil, fmt.Errorf("CW xaodev resource revision differs") + } + commit, err := hex.DecodeString(v.Provenance.Commit) + if err != nil || len(commit) != 20 || len(v.Provenance.Inputs) == 0 { + return nil, fmt.Errorf("CW xaodev source revision missing") + } + for _, digest := range v.Provenance.Inputs { + h, err := hex.DecodeString(digest) + if err != nil || len(h) != sha256.Size { + return nil, fmt.Errorf("CW xaodev source digest invalid") + } + } + if v.Policy.Source == "" || v.Policy.MonsterCount == 0 || v.Policy.MonsterCount > uint32(^uint16(0)) || v.Policy.MonsterTier == 0 || v.Policy.StarCap == 0 || v.Policy.NormalQuality == 0 || len(v.Policy.PowerfulQualities) == 0 || v.Policy.RewardPolicy != "preview_base_gold_already_paid_by_settlement" || v.Policy.ExtraRewards { + return nil, fmt.Errorf("CW xaodev generation policy unsupported") + } + qualities := map[uint32]bool{v.Policy.NormalQuality: true} + for _, q := range v.Policy.PowerfulQualities { + if q <= v.Policy.NormalQuality || qualities[q] { + return nil, fmt.Errorf("CW xaodev powerful quality invalid") + } + qualities[q] = true + } + seen := map[uint32]bool{} + keys := map[[2]uint32]bool{} + for _, r := range v.NodeRules { + key := [2]uint32{r.Family, r.Quality} + if r.ID == 0 || r.Quality == 0 || seen[r.ID] || keys[key] { + return nil, fmt.Errorf("CW xaodev branch rules duplicate or invalid") + } + seen[r.ID], keys[key] = true, true + } + if len(seen) == 0 { + return nil, fmt.Errorf("CW xaodev branch rules empty") + } + seen = map[uint32]bool{} + for _, d := range v.Divisions { + s, err := c.CapturedStage(d.DivisionID) + if err != nil || seen[d.DivisionID] || s.EnemyHardLevel != d.EnemyDifficultyLevel { + return nil, fmt.Errorf("CW xaodev division differs from catalog") + } + seen[d.DivisionID] = true + } + if len(seen) != len(c.CapturedStages) { + return nil, fmt.Errorf("CW xaodev divisions incomplete") + } + keys = map[[2]uint32]bool{} + for _, r := range v.DiffAdd { + key := [2]uint32{r.Rule, r.Quality} + if r.Rule == 0 || r.Quality == 0 || keys[key] { + return nil, fmt.Errorf("CW xaodev difficulty rule duplicate or invalid") + } + keys[key] = true + } + keys = map[[2]uint32]bool{} + for _, r := range v.Difficulty { + key := [2]uint32{r.Chapter, r.Level} + if r.Chapter == 0 || keys[key] { + return nil, fmt.Errorf("CW xaodev difficulty level duplicate or invalid") + } + keys[key] = true + } + if len(keys) == 0 { + return nil, fmt.Errorf("CW xaodev difficulty levels empty") + } + return v, nil +} + +func (v *CurrencyWarsXaodev) Branch(family, quality uint32) (CurrencyWarsBranchRule, error) { + for _, row := range v.NodeRules { + if row.Family == family && row.Quality == quality { + return row, nil + } + } + return CurrencyWarsBranchRule{}, fmt.Errorf("CW xaodev branch family=%d quality=%d missing", family, quality) +} + +func (v *CurrencyWarsXaodev) BranchDifficulty(division, option uint32) (uint32, error) { + var rule, quality uint32 + var foundDivision, foundOption bool + for _, d := range v.Divisions { + if d.DivisionID == division { + rule = d.BinaryNodeDiffAddRule + foundDivision = true + break + } + } + if !foundDivision { + return 0, fmt.Errorf("CW xaodev division %d is not mapped", division) + } + for _, r := range v.NodeRules { + if r.ID == option { + quality = r.Quality + foundOption = true + break + } + } + if !foundOption { + return 0, fmt.Errorf("CW xaodev branch option %d is not mapped", option) + } + if rule == 0 { + return 0, nil + } + for _, r := range v.DiffAdd { + if r.Rule == rule && r.Quality == quality { + return r.Add, nil + } + } + return 0, nil +} + +func (v *CurrencyWarsXaodev) SupportsDifficulty(chapter, level uint32) bool { + for _, r := range v.Difficulty { + if r.Chapter == chapter && r.Level == level { + return true + } + } + return false +} + +// Node-specific gain follows xaodev roundExp, using our current Client constants. +// Missing constants/chapters are errors; no fixed default is substituted. +func (c *CurrencyWarsCatalog) BattleNodeExperience(overclock bool, chapter uint32, kind string) (uint32, error) { + if chapter == 0 { + return 0, fmt.Errorf("CW EXP chapter missing") + } + name := "GridFight_GainExpWhenWaveEnd" + if kind == "Boss" { + name = "GridFight_GainExpWhenBossWaveEnd" + } + if overclock { + name = "GridFight_OCGainExpWhenWaveEndList" + if kind == "Boss" { + name = "GridFight_OCGainExpWhenBossWaveEndList" + } + values, err := c.IntegerArrayRule(name) + if err != nil { + return 0, err + } + if uint64(chapter) > uint64(len(values)) { + return 0, fmt.Errorf("CW EXP chapter outside native rule") + } + return values[chapter-1], nil + } + gain, err := c.IntegerRule(name) + if err != nil || gain < 0 || uint64(gain) > uint64(^uint32(0)) { + return 0, fmt.Errorf("CW EXP gain rule invalid: %s", name) + } + return uint32(gain), nil +} diff --git a/internal/common/currency_wars_xaodev_test.go b/internal/common/currency_wars_xaodev_test.go new file mode 100644 index 0000000..b68f5a9 --- /dev/null +++ b/internal/common/currency_wars_xaodev_test.go @@ -0,0 +1,64 @@ +package common + +import "testing" + +func TestCurrencyWarsXaodevRulesMatchCapturedDivisions(t *testing.T) { + c := loadCurrencyWarsTestCatalog(t) + v, err := LoadCurrencyWarsXaodev(c) + if err != nil { + t.Fatal(err) + } + for _, d := range v.Divisions { + for _, r := range v.NodeRules { + add, err := v.BranchDifficulty(d.DivisionID, r.ID) + if err != nil { + t.Fatalf("division %d option %d: %v", d.DivisionID, r.ID, err) + } + if d.BinaryNodeDiffAddRule == 0 && add != 0 { + t.Fatalf("division %d with zero rule adds difficulty %d", d.DivisionID, add) + } + if r.Quality == v.Policy.NormalQuality && add != 0 { + t.Fatal("normal branch adds difficulty") + } + } + } + if _, err := v.BranchDifficulty(0, 0); err == nil { + t.Fatal("unknown rule accepted") + } + changed := *c + changed.ClientVersion += "-mismatch" + if _, err := LoadCurrencyWarsXaodev(&changed); err == nil { + t.Fatal("revision mismatch accepted") + } +} + +func TestCurrencyWarsBattleNodeExperienceUsesModeChapterAndBoss(t *testing.T) { + c := loadCurrencyWarsTestCatalog(t) + for _, boss := range []bool{false, true} { + kind, scalar, array := "Monster", "GridFight_GainExpWhenWaveEnd", "GridFight_OCGainExpWhenWaveEndList" + if boss { + kind, scalar, array = "Boss", "GridFight_GainExpWhenBossWaveEnd", "GridFight_OCGainExpWhenBossWaveEndList" + } + want, err := c.IntegerRule(scalar) + if err != nil { + t.Fatal(err) + } + got, err := c.BattleNodeExperience(false, 1, kind) + if err != nil || uint64(got) != uint64(want) { + t.Fatalf("normal %s: gain=%d err=%v", kind, got, err) + } + values, err := c.IntegerArrayRule(array) + if err != nil { + t.Fatal(err) + } + for index, want := range values { + got, err := c.BattleNodeExperience(true, uint32(index+1), kind) + if err != nil || got != want { + t.Fatalf("OC %s chapter %d: gain=%d err=%v", kind, index+1, got, err) + } + } + if _, err := c.BattleNodeExperience(true, uint32(len(values)+1), kind); err == nil { + t.Fatal("missing chapter accepted") + } + } +} diff --git a/internal/common/runtime_config.go b/internal/common/runtime_config.go index 77393a2..5fc8a37 100644 --- a/internal/common/runtime_config.go +++ b/internal/common/runtime_config.go @@ -65,6 +65,15 @@ func LoadRuntimeConfig() (RuntimeConfig, error) { path := os.Getenv("GAC_CONFIG") if path == "" { path = "server.json" + if _, err := os.Stat(path); os.IsNotExist(err) { + for _, prefix := range []string{"..", "../..", "../../.."} { + candidate := filepath.Join(prefix, "server.json") + if _, err := os.Stat(candidate); err == nil { + path = candidate + break + } + } + } } var cfg RuntimeConfig for i, filename := range []string{path, filepath.Join(filepath.Dir(path), strings.TrimSuffix(filepath.Base(path), filepath.Ext(path))+".local.json")} { diff --git a/internal/gameserver/currency_wars_archive.go b/internal/gameserver/currency_wars_archive.go new file mode 100644 index 0000000..1fe192c --- /dev/null +++ b/internal/gameserver/currency_wars_archive.go @@ -0,0 +1,89 @@ +package gameserver + +import ( + "fmt" + "sync" + "time" + + pb "gac-hkrpg-go/protocol/proto" + "google.golang.org/protobuf/proto" +) + +const archiveClientVersion uint32 = 4006 + +type cwArchiveStore struct { + mu sync.RWMutex + nextID uint32 + entries []*pb.FAIHLAKPABN +} + +var globalCWArchive = &cwArchiveStore{nextID: 1} + +func cwRecordArchive(record *pb.OEDHMAHDECC, seasonID, subSeasonID uint32) *pb.FAIHLAKPABN { + if record == nil { + return nil + } + globalCWArchive.mu.Lock() + defer globalCWArchive.mu.Unlock() + + id := globalCWArchive.nextID + globalCWArchive.nextID++ + + entry := &pb.FAIHLAKPABN{ + BAAGDILLLME: id, + OBNEAIPKGKL: time.Now().Unix(), + LJNGPCKPJLF: seasonID, + GEGMHOMNBDG: subSeasonID, + PMBNKEFCICH: archiveClientVersion, + IECKKCHJMLP: false, + BFLLJJBHLDF: proto.Clone(record).(*pb.OEDHMAHDECC), + } + globalCWArchive.entries = append(globalCWArchive.entries, entry) + if len(globalCWArchive.entries) > 50 { + globalCWArchive.entries = globalCWArchive.entries[len(globalCWArchive.entries)-50:] + } + return entry +} + +func init() { + cwHandle("GridFightGetArchive", func(s *Session, req *pb.GridFightGetArchiveCsReq, rsp *pb.GridFightGetArchiveScRsp) error { + rsp.Retcode = 0 + if req != nil { + rsp.Type = req.Type + } + globalCWArchive.mu.RLock() + defer globalCWArchive.mu.RUnlock() + + favourOnly := req != nil && req.Type == pb.MLJIILDNDPL_MLJIILDNDPL_FAGECPGMPEH + for i := len(globalCWArchive.entries) - 1; i >= 0; i-- { + e := globalCWArchive.entries[i] + if favourOnly && !e.IECKKCHJMLP { + continue + } + rsp.MCGGPONOCKC = append(rsp.MCGGPONOCKC, proto.Clone(e).(*pb.FAIHLAKPABN)) + } + PacketLog(fmt.Sprintf("CW_GET_ARCHIVE type=%v entries=%d", rsp.Type, len(rsp.MCGGPONOCKC))) + return nil + }) + + cwHandle("FavourArchive", func(s *Session, req *pb.FavourArchiveCsReq, rsp *pb.FavourArchiveScRsp) error { + rsp.Retcode = uint32(pb.Retcode_RET_GRID_FIGHT_PARAM_NOT_MATCH) + if req == nil { + return nil + } + globalCWArchive.mu.Lock() + defer globalCWArchive.mu.Unlock() + + for _, e := range globalCWArchive.entries { + if e.BAAGDILLLME == req.BAAGDILLLME { + e.IECKKCHJMLP = req.IECKKCHJMLP + rsp.BAAGDILLLME = req.BAAGDILLLME + rsp.IECKKCHJMLP = req.IECKKCHJMLP + rsp.Retcode = 0 + PacketLog(fmt.Sprintf("CW_FAVOUR_ARCHIVE id=%d favour=%t", req.BAAGDILLLME, req.IECKKCHJMLP)) + return nil + } + } + return nil + }) +} diff --git a/internal/gameserver/currency_wars_battle.go b/internal/gameserver/currency_wars_battle.go index b27d8ea..5dcf4ea 100644 --- a/internal/gameserver/currency_wars_battle.go +++ b/internal/gameserver/currency_wars_battle.go @@ -49,9 +49,11 @@ func buildCurrencyWarsBattle(s *Session, catalog *common.CurrencyWarsCatalog, ru grid.StageInfo = &pb.KJFEHJMAIAA{FMNPBOJGLFI: v.FMNPBOJGLFI} } } - if stage == nil || run.DivisionId != grid.AACGBEBGDPE || run.IsOverlock != grid.IsOverlock { + division, err := catalog.CapturedStage(run.DivisionId) + if err != nil || stage == nil || run.IsOverlock != grid.IsOverlock { return nil, fmt.Errorf("CW battle envelope has not been captured for this mode/division") } + grid.AACGBEBGDPE = division.ID var node *pb.OHMDHKGAEMM for _, n := range stage.OOMMIIKPHBC { if n.SectionId == stage.SectionId && n.DCAHJELNJNP == stage.DCAHJELNJNP { @@ -83,13 +85,14 @@ func buildCurrencyWarsBattle(s *Session, catalog *common.CurrencyWarsCatalog, ru return nil, fmt.Errorf("CW battle mode envelope missing") } ext.GLHDCCPOGHL.IHHNLLAJAGB = 0 + runtime, _ := cwBonfireRuntime(catalog, run) // These fields contain the reference party's traits, buffs and battle events. // They cannot be reused for another party. Derived trait/equipment effects // remain a separate implementation task; don't silently inherit its bonuses. grid.GridGameRoleList = nil grid.GridGameNpcList = nil grid.OPHONCKPNMG = nil - grid.GridFightTraitInfo = nil + grid.GridFightTraitInfo = cwCalculateBattleTraits(catalog, run, runtime) grid.POCKGKDFLKK = nil grid.KCOMPKMJCMC = nil grid.SyncAugmentInfo = nil @@ -102,6 +105,50 @@ func buildCurrencyWarsBattle(s *Session, catalog *common.CurrencyWarsCatalog, ru } info.BattleAvatarList = nil info.BuffList = nil + if runtime != nil && runtime.Rules != nil { + tbCat, _ := common.LoadCurrencyWarsTrailblaze() + if tbCat != nil { + var ownerIndex uint32 + hasOwner := false + for _, r := range team.GridGameRoleList { + if r != nil && r.Pos > 0 && r.Pos < benchFirstPos { + if variant, err := catalog.CapturedRoleVariant(r.Id, r.RoleStar); err == nil { + for _, tid := range variant.Config.TraitList { + if tid == cwTrailblazeTrait { + if !hasOwner || r.Pos < ownerIndex { + ownerIndex = r.Pos + hasOwner = true + } + break + } + } + } + } + } + if hasOwner { + for _, good := range tbCat.GoodsList { + if good.Kind == "maze_buff" && len(good.Params) == 1 && tbBoughtCount(runtime, good.ID) > 0 { + superseded := false + for _, child := range tbCat.GoodsList { + if child.Father == good.ID && tbBoughtCount(runtime, child.ID) > 0 { + superseded = true + break + } + } + if !superseded { + info.BuffList = append(info.BuffList, &pb.BattleBuff{ + Id: good.Params[0], + Level: 1, + OwnerId: ownerIndex, + WaveFlag: 0xFFFFFFFF, + TargetIndexList: []uint32{ownerIndex}, + }) + } + } + } + } + } + } info.BattleEvent = nil supported, err := supportedRelicIDs() if err != nil { diff --git a/internal/gameserver/currency_wars_bonfire.go b/internal/gameserver/currency_wars_bonfire.go index fad2080..28976aa 100644 --- a/internal/gameserver/currency_wars_bonfire.go +++ b/internal/gameserver/currency_wars_bonfire.go @@ -180,30 +180,8 @@ func cwSelectBonfire(c *common.CurrencyWarsCatalog, run *pb.MPIMAEMCEFH, request } stage, _ := cwStage(run) key := fmt.Sprintf("BonfireChoice/%d/%d/", stage.DCAHJELNJNP, stage.SectionId) - if offer.Phase != pb.KMJMHDPPAKH_KMJMHDPPAKH_POMADDDKFHN { - return nil, nil, fmt.Errorf("CW Bonfire choice was already selected") - } - if request.GetNCGOOEMOBIC().GetLGAJNMNOAEM() { - book, e := common.LoadCurrencyWarsBonfire(c) - if e != nil { return nil, nil, e } - stage, _ := cwStage(run) - player, _, _ := cwComponents(run) - if stage == nil || player == nil || player.GridFightLineupHp > player.PPKKHHNEOHB { - return nil, nil, fmt.Errorf("CW Bonfire heal components invalid") - } - heal, ok := book.Heal[stage.DCAHJELNJNP] - if !ok { return nil, nil, fmt.Errorf("CW Bonfire chapter heal missing") } - hp := uint64(player.GridFightLineupHp) + uint64(heal) - if hp > uint64(player.PPKKHHNEOHB) { hp = uint64(player.PPKKHHNEOHB) } - player.GridFightLineupHp = uint32(hp) - // gfPendingMarkBonfireSelected uses phase 1 for both heal and option. - offer.Phase = pb.KMJMHDPPAKH_KMJMHDPPAKH_HLLKDFFMIEN - return cwNotify( - &pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_GridFightLineupHp{GridFightLineupHp: &pb.FMJDDIHBELC{GridFightLineupHp: player.GridFightLineupHp, PPKKHHNEOHB: player.PPKKHHNEOHB}}}, - &pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_FMIGOKIDKJL{FMIGOKIDKJL: pending}}, - ), nil, nil - } - selectReq := request.GetNCGOOEMOBIC().GetALEGIFEHGLH() + action := request.GetNCGOOEMOBIC() + selectReq := action.GetALEGIFEHGLH() replay := selectReq != nil && runtime.Rules[key+"event"] == selectReq.EventId && runtime.Rules[key+"option"] == selectReq.HAFOKMHCGFM if selectReq != nil && !replay { event, option, proven, proofErr := cwLegacyBonfireChoice(c, run, runtime) @@ -212,44 +190,27 @@ func cwSelectBonfire(c *common.CurrencyWarsCatalog, run *pb.MPIMAEMCEFH, request } replay = proven && event == selectReq.EventId && option == selectReq.HAFOKMHCGFM } - if selectReq == nil && request.GetNCGOOEMOBIC() != nil && runtime.Rules[key+"heal"] != 0 { + if selectReq == nil && action != nil && runtime.Rules[key+"heal"] != 0 { replay = true } if replay { return cwNotify(&pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_FMIGOKIDKJL{FMIGOKIDKJL: pending}}), runtime, nil } - return nil, nil, fmt.Errorf("CW Bonfire option was already selected") + return cwNotify(&pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_FMIGOKIDKJL{FMIGOKIDKJL: pending}}), runtime, nil } if offer.Phase != cwBonfireChoosing && offer.Phase != pb.KMJMHDPPAKH_KMJMHDPPAKH_POMADDDKFHN { return nil, nil, fmt.Errorf("CW Bonfire phase invalid") } - if offer.Phase != pb.KMJMHDPPAKH_KMJMHDPPAKH_POMADDDKFHN { - return nil, nil, fmt.Errorf("CW Bonfire choice was already selected") - } - if request.GetNCGOOEMOBIC().GetLGAJNMNOAEM() { - book, e := common.LoadCurrencyWarsBonfire(c) - if e != nil { return nil, nil, e } - stage, _ := cwStage(run) - player, _, _ := cwComponents(run) - if stage == nil || player == nil || player.GridFightLineupHp > player.PPKKHHNEOHB { - return nil, nil, fmt.Errorf("CW Bonfire heal components invalid") - } - heal, ok := book.Heal[stage.DCAHJELNJNP] - if !ok { return nil, nil, fmt.Errorf("CW Bonfire chapter heal missing") } - hp := uint64(player.GridFightLineupHp) + uint64(heal) - if hp > uint64(player.PPKKHHNEOHB) { hp = uint64(player.PPKKHHNEOHB) } - player.GridFightLineupHp = uint32(hp) - // gfPendingMarkBonfireSelected uses phase 1 for both heal and option. - offer.Phase = pb.KMJMHDPPAKH_KMJMHDPPAKH_HLLKDFFMIEN - return cwNotify( - &pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_GridFightLineupHp{GridFightLineupHp: &pb.FMJDDIHBELC{GridFightLineupHp: player.GridFightLineupHp, PPKKHHNEOHB: player.PPKKHHNEOHB}}}, - &pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_FMIGOKIDKJL{FMIGOKIDKJL: pending}}, - ), nil, nil - } - selectReq := request.GetNCGOOEMOBIC().GetALEGIFEHGLH() + action := request.GetNCGOOEMOBIC() + if action == nil { + return nil, nil, fmt.Errorf("CW Bonfire action missing") + } + selectReq := action.GetALEGIFEHGLH() if selectReq == nil { - if _, ok := request.GetNCGOOEMOBIC().GetFFBBOFAGGDI().(*pb.MLAENDOFBCK_LGAJNMNOAEM); !ok { - return nil, nil, fmt.Errorf("CW Bonfire action missing") + if !action.GetLGAJNMNOAEM() { + if _, ok := action.GetFFBBOFAGGDI().(*pb.MLAENDOFBCK_LGAJNMNOAEM); !ok { + return nil, nil, fmt.Errorf("CW Bonfire action missing") + } } book, e := common.LoadCurrencyWarsBonfire(c) if e != nil { @@ -356,7 +317,7 @@ func cwTakeBonfire(c *common.CurrencyWarsCatalog, run *pb.MPIMAEMCEFH, offer *pb } notify.GCPNIHHBGGD = append(notify.GCPNIHHBGGD, added.GCPNIHHBGGD...) } - next, err := cwAdvanceRouteNode(c, run) + next, err := cwAdvanceRouteNode(c, run, runtime) if err != nil { return nil, nil, err } diff --git a/internal/gameserver/currency_wars_commands.go b/internal/gameserver/currency_wars_commands.go index 34ea8ec..24f9efb 100644 --- a/internal/gameserver/currency_wars_commands.go +++ b/internal/gameserver/currency_wars_commands.go @@ -42,7 +42,8 @@ func (s *Session) applyCurrencyWarsCommands() error { if player == nil { return fmt.Errorf("CW player missing") } - notify, applyErr := cwApplyCommand(catalog, data.DHFOFLFCLAH, command) + runtime, _ := cwBonfireRuntime(catalog, data.DHFOFLFCLAH) + notify, applyErr := cwApplyCommand(catalog, data.DHFOFLFCLAH, command, runtime) if applyErr != nil { PacketLog(fmt.Sprintf("CW_GM rejected request=%s: %v", command.ID, applyErr)) if err = common.AcknowledgeCurrencyWarsCommand(command.ID); err != nil { @@ -51,7 +52,7 @@ func (s *Session) applyCurrencyWarsCommands() error { continue } - if err = s.saveCurrencyWarsData(data, catalog, command.ID); err != nil { + if err = s.saveCurrencyWarsDataWithRuntime(data, catalog, runtime, command.ID); err != nil { return err } if err = s.Send(notify); err != nil { diff --git a/internal/gameserver/currency_wars_encounter.go b/internal/gameserver/currency_wars_encounter.go index adefb4f..8fe9e2f 100644 --- a/internal/gameserver/currency_wars_encounter.go +++ b/internal/gameserver/currency_wars_encounter.go @@ -2,6 +2,7 @@ package gameserver import ( "fmt" + "gac-hkrpg-go/internal/common" pb "gac-hkrpg-go/protocol/proto" ) @@ -21,7 +22,7 @@ func cwSetEncounterSelection(run *pb.MPIMAEMCEFH, index uint32) (*pb.KFNCAIHIHCM return &pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_FGODNPLCECH{FGODNPLCECH: &pb.KEPIEGKPFIJ{MOADBALHMFD: index}}}, nil } -func cwSelectEncounter(run *pb.MPIMAEMCEFH, request *pb.OHAINOHGKCM) (*pb.GridFightSyncUpdateResultScNotify, error) { +func cwSelectEncounter(c *common.CurrencyWarsCatalog, run *pb.MPIMAEMCEFH, request *pb.OHAINOHGKCM, runtime *common.CurrencyWarsRuntimeState) (*pb.GridFightSyncUpdateResultScNotify, error) { if run.GetFMIGOKIDKJL().GetBAKAKODKECM() == nil || request == nil { return nil, fmt.Errorf("CW Encounter selection is not active") } @@ -33,11 +34,20 @@ func cwSelectEncounter(run *pb.MPIMAEMCEFH, request *pb.OHAINOHGKCM) (*pb.GridFi if err != nil { return nil, err } + difficulty, err := cwXaodevSelectionDifficulty(c, run, runtime) + if err != nil { + return nil, err + } // Reference phase transition: SelectingEncounter -> AwaitingBattleReturn, // followed by the ordinary ReturnPreparation action; no battle starts here. pending := &pb.CHCHAJIANIM{QueuePosition: queue + 1, FGLIODAJFML: &pb.CHCHAJIANIM_LGNPHHBGKFH{LGNPHHBGKFH: &pb.HJPDALHDJHC{}}} run.FMIGOKIDKJL = pending - return cwNotify(selected, cwFinish(queue), cwUnlock(), &pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_FMIGOKIDKJL{FMIGOKIDKJL: pending}}), nil + notify := cwNotify(selected) + if difficulty != nil { + notify.GCPNIHHBGGD[0].JKPJFKCBIEM = append(notify.GCPNIHHBGGD[0].JKPJFKCBIEM, difficulty) + } + notify.GCPNIHHBGGD[0].JKPJFKCBIEM = append(notify.GCPNIHHBGGD[0].JKPJFKCBIEM, cwFinish(queue), cwUnlock(), &pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_FMIGOKIDKJL{FMIGOKIDKJL: pending}}) + return notify, nil } func init() { @@ -52,10 +62,22 @@ func init() { PacketLog("CW_ENCOUNTER_UPDATE rejected: " + err.Error()) return nil } - if err = s.saveCurrencyWarsData(data, catalog); err != nil { + runtime, err := cwBonfireRuntime(catalog, data.DHFOFLFCLAH) + if err != nil { + return err + } + difficulty, err := cwXaodevSelectionDifficulty(catalog, data.DHFOFLFCLAH, runtime) + if err != nil { + return err + } + if err = s.saveCurrencyWarsDataWithRuntime(data, catalog, runtime); err != nil { return nil } - if err = cwSend(s, cwNotify(selected)); err != nil { + notify := cwNotify(selected) + if difficulty != nil { + notify.GCPNIHHBGGD[0].JKPJFKCBIEM = append(notify.GCPNIHHBGGD[0].JKPJFKCBIEM, difficulty) + } + if err = cwSend(s, notify); err != nil { return err } rsp.Retcode = 0 diff --git a/internal/gameserver/currency_wars_encounter_reward.go b/internal/gameserver/currency_wars_encounter_reward.go index bac4b4c..8a2137a 100644 --- a/internal/gameserver/currency_wars_encounter_reward.go +++ b/internal/gameserver/currency_wars_encounter_reward.go @@ -13,10 +13,25 @@ import ( // result applies that option reward on passing the threshold or a perfect clear. // Perfect clears additionally earn monster Orbs, which are not handled here. // run must be a detached snapshot which the caller discards on any error. -func cwEncounterRewards(c *common.CurrencyWarsCatalog, run *pb.MPIMAEMCEFH, branch *pb.KKBGIPCAGGH, progress, threshold uint32) ([]*pb.KFNCAIHIHCM, error) { +func cwEncounterRewards(c *common.CurrencyWarsCatalog, run *pb.MPIMAEMCEFH, branch *pb.KKBGIPCAGGH, progress, threshold uint32, runtime ...*common.CurrencyWarsRuntimeState) ([]*pb.KFNCAIHIHCM, error) { if branch == nil { return nil, fmt.Errorf("CW Encounter selected branch missing") } + if len(runtime) == 1 && cwXaodevGenerated(run, runtime[0]) { + // xaodev branchDropPreview advertises the node's base gold, already + // paid by settlement. It is not an additional threshold reward. The + // server-side source marker survives reconnects and is never a wire tag. + data, err := common.LoadCurrencyWarsXaodev(c) + if err != nil { + return nil, err + } + _, node := cwStage(run) + drops := branch.GetBOAMLLHPNEC().GetNMGIABHIJJB() + if data.Policy.RewardPolicy != "preview_base_gold_already_paid_by_settlement" || node == nil || len(drops) != 1 || drops[0] == nil || drops[0].FKKGLFOHGGA != pb.MJJIFGINJAJ_MJJIFGINJAJ_item_value || drops[0].FNLMKJLKLFH != 0 || drops[0].Num != node.PICMDONCKCO { + return nil, fmt.Errorf("CW xaodev Encounter base-gold preview differs") + } + return nil, nil + } if progress < threshold { return nil, nil } diff --git a/internal/gameserver/currency_wars_entry_choice.go b/internal/gameserver/currency_wars_entry_choice.go index b28f645..2143b1b 100644 --- a/internal/gameserver/currency_wars_entry_choice.go +++ b/internal/gameserver/currency_wars_entry_choice.go @@ -169,13 +169,17 @@ func init() { after, notify, err = currencyWarsEntryChoice(catalog, before, req.QueuePosition, selection.JCAPCNLJGFC) } else { after = proto.Clone(before).(*goproto.MPIMAEMCEFH) + runtime, err = cwBonfireRuntime(catalog, after) + if err != nil { + return err + } switch { case req.GetIIDBLCFAAPC() != nil && before.FMIGOKIDKJL.GetIIDBLCFAAPC() != nil: notify, runtime, err = cwSelectBonfire(catalog, after, req.GetIIDBLCFAAPC()) case req.GetBAKAKODKECM() != nil && before.FMIGOKIDKJL.GetBAKAKODKECM() != nil: - notify, err = cwSelectEncounter(after, req.GetBAKAKODKECM()) + notify, err = cwSelectEncounter(catalog, after, req.GetBAKAKODKECM(), runtime) case req.GetFIKAGBAEOAP() != nil && before.FMIGOKIDKJL.GetFIKAGBAEOAP() != nil: - notify, err = cwSelectSupply(catalog, after, req.GetFIKAGBAEOAP()) + notify, err = cwSelectSupply(catalog, after, req.GetFIKAGBAEOAP(), runtime) case req.GetDKDFDGJGIHM() != nil && before.FMIGOKIDKJL.GetEEKMIAMGHFL() != nil: notify, err = cwSelectSpotlight(catalog, after, req.GetDKDFDGJGIHM()) case req.GetNNDGEJFLGGF() != nil && before.FMIGOKIDKJL.GetNNDGEJFLGGF() != nil: @@ -209,6 +213,26 @@ func init() { if stage, _ := cwStage(after); stage != nil && runtime.Rules["PetPalStarDown"] != 0 && stage.GetIMPNAHBCADN().GetMPOBBIPMNNJ() != nil { notify.GCPNIHHBGGD = append(notify.GCPNIHHBGGD, cwNotify(&goproto.KFNCAIHIHCM{MNLCHEJKDHO: &goproto.KFNCAIHIHCM_OOICHLAMNIG{OOICHLAMNIG: &goproto.AINFNFIDGFF{DCAHJELNJNP: stage.DCAHJELNJNP, SectionId: stage.SectionId, IMPNAHBCADN: stage.IMPNAHBCADN}}}).GCPNIHHBGGD...) } + cwSyncTraitComponent(catalog, after, runtime) + if tbCat, err := common.LoadCurrencyWarsTrailblaze(); err == nil && (cwGetTrailblazeLayer(catalog, after) > 0 || (runtime != nil && runtime.Rules["TbOpened"] != 0)) { + if notify != nil && len(notify.GCPNIHHBGGD) > 0 { + notify.GCPNIHHBGGD[0].JKPJFKCBIEM = append(notify.GCPNIHHBGGD[0].JKPJFKCBIEM, + &goproto.KFNCAIHIHCM{ + MNLCHEJKDHO: &goproto.KFNCAIHIHCM_HKHJJCMKKJE{ + HKHJJCMKKJE: &goproto.JLKCBOIFJKC{ + TraitId: cwTrailblazeTrait, + ENPLACDLJGA: cwTrailblazeTraitEffectProto(tbCat, runtime), + }, + }, + }, + &goproto.KFNCAIHIHCM{ + MNLCHEJKDHO: &goproto.KFNCAIHIHCM_GOOCHGKBIPN{ + GOOCHGKBIPN: cwTrailblazeModifierProto(tbCat, runtime), + }, + }, + ) + } + } if err := s.saveCurrencyWarsDataWithRuntime(data, catalog, runtime); err != nil { return err } diff --git a/internal/gameserver/currency_wars_grants.go b/internal/gameserver/currency_wars_grants.go index 3ae3ebe..85e3365 100644 --- a/internal/gameserver/currency_wars_grants.go +++ b/internal/gameserver/currency_wars_grants.go @@ -1,15 +1,19 @@ package gameserver import ( + "crypto/rand" "fmt" + "maps" + "math/big" + "sort" + "gac-hkrpg-go/internal/common" pb "gac-hkrpg-go/protocol/proto" - "maps" ) // Grant transactions operate on the detached Preparation snapshot, then the // caller saves before sending the same acquisition updates used by Supply/shop. -func cwApplyCommand(c *common.CurrencyWarsCatalog, run *pb.MPIMAEMCEFH, cmd common.CurrencyWarsCommand) (*pb.GridFightSyncUpdateResultScNotify, error) { +func cwApplyCommand(c *common.CurrencyWarsCatalog, run *pb.MPIMAEMCEFH, cmd common.CurrencyWarsCommand, rt ...*common.CurrencyWarsRuntimeState) (*pb.GridFightSyncUpdateResultScNotify, error) { player, team, shop := cwComponents(run) if player == nil { return nil, fmt.Errorf("CW player missing") @@ -18,6 +22,75 @@ func cwApplyCommand(c *common.CurrencyWarsCatalog, run *pb.MPIMAEMCEFH, cmd comm case "gold": player.Gold = cmd.Value return cwNotify(cwGoldSync(player.Gold)), nil + case "tbpower", "tb", "trailblaze": + tbCat, err := common.LoadCurrencyWarsTrailblaze() + if err != nil { + return nil, err + } + var runtime *common.CurrencyWarsRuntimeState + if len(rt) > 0 && rt[0] != nil { + runtime = rt[0] + } else { + runtime, _ = cwBonfireRuntime(c, run) + } + if runtime == nil { + runtime = &common.CurrencyWarsRuntimeState{RunID: run.UniqueId} + } + if runtime.Rules == nil { + runtime.Rules = make(map[string]uint32) + } + layer := cwGetTrailblazeLayer(c, run) + tier := tbCat.Tier(layer) + if runtime.Rules["TbOpened"] == 0 { + runtime.Rules["TbOpened"] = 1 + stage, _ := cwStage(run) + if stage != nil { + runtime.Rules["TbNode"] = stage.DCAHJELNJNP*1000 + stage.SectionId + } + cwTrailblazeRoll(tbCat, run, runtime, tier) + if tbBoughtCount(runtime, tbCat.InitialGoodID) == 0 { + runtime.Rules["TbGood/0"] = tbCat.InitialGoodID + } + } + runtime.Rules["TbPower"] = cmd.Value + if cmd.Star > 0 { + runtime.Rules["TbCosted"] = cmd.Star - 1 + if runtime.Rules["TbCosted"] >= tbCat.PomThreshold && !tbOffered(runtime, tbCat.PomGoodID) { + slotToPut := 0 + for s := 0; s < cwTrailblazeSlots; s++ { + if runtime.Rules[fmt.Sprintf("TbGood/%d", s)] == 0 { + slotToPut = s + break + } + } + runtime.Rules[fmt.Sprintf("TbGood/%d", slotToPut)] = tbCat.PomGoodID + } + } + + cwSyncTraitComponent(c, run, runtime) + + bundle := &pb.ABPLALCHNFA{ + GridUpdateSrc: pb.JJDFGMMMCMI_JJDFGMMMCMI_GLKAGBGLENM, + OOEOCGLLHFO: []uint32{cwTrailblazeTrait, cwTrailblazeEffect}, + JKPJFKCBIEM: []*pb.KFNCAIHIHCM{ + { + MNLCHEJKDHO: &pb.KFNCAIHIHCM_HKHJJCMKKJE{ + HKHJJCMKKJE: &pb.JLKCBOIFJKC{ + TraitId: cwTrailblazeTrait, + ENPLACDLJGA: cwTrailblazeTraitEffectProto(tbCat, runtime), + }, + }, + }, + { + MNLCHEJKDHO: &pb.KFNCAIHIHCM_GOOCHGKBIPN{ + GOOCHGKBIPN: cwTrailblazeModifierProto(tbCat, runtime), + }, + }, + }, + } + return &pb.GridFightSyncUpdateResultScNotify{ + GCPNIHHBGGD: []*pb.ABPLALCHNFA{bundle}, + }, nil case "hp": if cmd.Value == 0 || cmd.Value > player.PPKKHHNEOHB { return nil, fmt.Errorf("HP must be 1..%d", player.PPKKHHNEOHB) @@ -68,8 +141,27 @@ func cwApplyCommand(c *common.CurrencyWarsCatalog, run *pb.MPIMAEMCEFH, cmd comm if err != nil { return nil, err } - if _, ok := rewards.Orbs[cmd.Value]; !ok { - return nil, fmt.Errorf("Orb %d not captured", cmd.Value) + var pool []uint32 + if cmd.Value == 0 { + recipes, err := common.LoadCurrencyWarsOrbRecipes() + if err != nil { + return nil, err + } + for _, r := range recipes.Recipes { + if r.Status == "Implemented" && r.Engine == "Orb" { + if _, ok := rewards.Orbs[r.ID]; ok { + pool = append(pool, r.ID) + } + } + } + if len(pool) == 0 { + return nil, fmt.Errorf("no implemented CW orbs found") + } + sort.Slice(pool, func(i, j int) bool { return pool[i] < pool[j] }) + } else { + if _, ok := rewards.Orbs[cmd.Value]; !ok { + return nil, fmt.Errorf("Orb %d not captured", cmd.Value) + } } var orbs *pb.DBONCGFHHDN for _, component := range run.NAGDJGMMCBE { @@ -78,7 +170,12 @@ func cwApplyCommand(c *common.CurrencyWarsCatalog, run *pb.MPIMAEMCEFH, cmd comm } } if orbs == nil { - return nil, fmt.Errorf("CW Orb component missing") + orbs = &pb.DBONCGFHHDN{} + run.NAGDJGMMCBE = append(run.NAGDJGMMCBE, &pb.GridFightGameInfo{ + LEOIAOFBBFA: &pb.GridFightGameInfo_DBAGKJNPDOL{ + DBAGKJNPDOL: orbs, + }, + }) } quantity := cmd.Quantity if quantity == 0 { @@ -86,12 +183,20 @@ func cwApplyCommand(c *common.CurrencyWarsCatalog, run *pb.MPIMAEMCEFH, cmd comm } notify := cwNotify() for i := uint32(0); i < quantity; i++ { + orbID := cmd.Value + if orbID == 0 { + idx, err := rand.Int(rand.Reader, big.NewInt(int64(len(pool)))) + if err != nil { + return nil, err + } + orbID = pool[idx.Int64()] + } uid, e := cwNextEntityID(run) if e != nil { return nil, e } - orbs.MDKGCIKOMLI = append(orbs.MDKGCIKOMLI, &pb.IBPDPCOGDEP{UniqueId: uid, LDBNOMPCOKK: cmd.Value}) - notify.GCPNIHHBGGD[0].JKPJFKCBIEM = append(notify.GCPNIHHBGGD[0].JKPJFKCBIEM, &pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_AFILIPBHMOO{AFILIPBHMOO: &pb.MMEDLCAENAL{UniqueId: uid, LDBNOMPCOKK: cmd.Value}}}) + orbs.MDKGCIKOMLI = append(orbs.MDKGCIKOMLI, &pb.IBPDPCOGDEP{UniqueId: uid, LDBNOMPCOKK: orbID}) + notify.GCPNIHHBGGD[0].JKPJFKCBIEM = append(notify.GCPNIHHBGGD[0].JKPJFKCBIEM, &pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_AFILIPBHMOO{AFILIPBHMOO: &pb.MMEDLCAENAL{UniqueId: uid, LDBNOMPCOKK: orbID}}}) } return notify, nil case "consumable": diff --git a/internal/gameserver/currency_wars_investment.go b/internal/gameserver/currency_wars_investment.go index 87912db..c48ef10 100644 --- a/internal/gameserver/currency_wars_investment.go +++ b/internal/gameserver/currency_wars_investment.go @@ -73,6 +73,19 @@ func cwSelectInvestment(catalog *common.CurrencyWarsCatalog, run *pb.MPIMAEMCEFH delta = choice.GoldDelta } } + if info == nil || info.AugmentId != id { + augCatalog, augErr := common.LoadCurrencyWarsAugments(catalog) + if augErr == nil { + if aug, ok := augCatalog.Augment(id); ok { + info = &pb.PPGEBEBPMJC{AugmentId: id} + for _, g := range aug.Grants { + if g.Kind == "gold" { + delta += int64(g.Count) + } + } + } + } + } if info == nil || info.AugmentId != id { return nil, fmt.Errorf("CW investment effect not captured") } diff --git a/internal/gameserver/currency_wars_orb.go b/internal/gameserver/currency_wars_orb.go index 23ea596..0e2b9de 100644 --- a/internal/gameserver/currency_wars_orb.go +++ b/internal/gameserver/currency_wars_orb.go @@ -53,28 +53,72 @@ func cwUseOrbs(s *Session, req *pb.IKMOCJBMGOJ) error { for _, component := range run.NAGDJGMMCBE { if v := component.GetDBAGKJNPDOL(); v != nil { bag = v + break } } - if bag == nil { - return fmt.Errorf("CW Orb component missing") - } - owned := make(map[uint32]*pb.IBPDPCOGDEP, len(bag.MDKGCIKOMLI)) - for _, orb := range bag.MDKGCIKOMLI { - if orb == nil || orb.UniqueId == 0 || owned[orb.UniqueId] != nil { - return fmt.Errorf("CW Orb inventory identity invalid") - } - owned[orb.UniqueId] = orb - } - uids := slices.Clone(req.CBMGDBJHLHO) + + var uids []uint32 if req.IsGetAll { - uids = nil + if bag != nil { + for _, orb := range bag.MDKGCIKOMLI { + if orb != nil && orb.UniqueId != 0 { + uids = append(uids, orb.UniqueId) + } + } + } + if len(uids) == 0 { + // All rewards already claimed; acknowledge cleanly without error. + return nil + } + } else { + uids = slices.Clone(req.CBMGDBJHLHO) + } + + if len(uids) == 0 { + return nil + } + + owned := make(map[uint32]*pb.IBPDPCOGDEP) + if bag != nil { for _, orb := range bag.MDKGCIKOMLI { - uids = append(uids, orb.UniqueId) + if orb != nil && orb.UniqueId != 0 && owned[orb.UniqueId] == nil { + owned[orb.UniqueId] = orb + } } } - if len(uids) == 0 { - return fmt.Errorf("no owned Orbs selected") + + var toConsume []uint32 + var alreadyConsumed []uint32 + seen := make(map[uint32]bool, len(uids)) + for _, uid := range uids { + if uid == 0 || seen[uid] { + continue + } + seen[uid] = true + if owned[uid] != nil { + toConsume = append(toConsume, uid) + } else { + alreadyConsumed = append(alreadyConsumed, uid) + } } + + // If none of the requested orbs are currently owned, all of them were already + // consumed or despawned. Acknowledge idempotently and re-send removal notify + // so any lingering ghost entities disappear from the client board without 6900 error. + if len(toConsume) == 0 { + var removalUpdates []*pb.KFNCAIHIHCM + for _, uid := range alreadyConsumed { + removalUpdates = append(removalUpdates, &pb.KFNCAIHIHCM{ + MNLCHEJKDHO: &pb.KFNCAIHIHCM_GOHHJLAPMBB{GOHHJLAPMBB: uid}, + }) + } + if len(removalUpdates) > 0 { + _ = cwSend(s, cwNotify(removalUpdates...)) + } + PacketLog(fmt.Sprintf("CW_USE_ORB ghost/already consumed uids=%v (acknowledged with removal sync)", alreadyConsumed)) + return nil + } + recipes, err := common.LoadCurrencyWarsOrbRecipes() if err != nil { return err @@ -88,11 +132,11 @@ func cwUseOrbs(s *Session, req *pb.IKMOCJBMGOJ) error { return fmt.Errorf("CW stage missing") } used := map[uint32]bool{} - payouts := make([]cwOrbPayout, 0, len(uids)) - for _, uid := range uids { + payouts := make([]cwOrbPayout, 0, len(toConsume)) + for _, uid := range toConsume { orb := owned[uid] if orb == nil || used[uid] { - return fmt.Errorf("Orb UID %d missing or duplicated", uid) + continue } used[uid] = true row, ok := captured.Orbs[orb.LDBNOMPCOKK] @@ -154,10 +198,33 @@ func cwUseOrbs(s *Session, req *pb.IKMOCJBMGOJ) error { return err } // Native OrbSummarizer requires source1 and first context uint=OrbUID. - if err = cwSend(s, &pb.GridFightSyncUpdateResultScNotify{GCPNIHHBGGD: []*pb.ABPLALCHNFA{{GridUpdateSrc: pb.JJDFGMMMCMI_JJDFGMMMCMI_DBIEKOMKBIN, OOEOCGLLHFO: []uint32{payout.UID}, JKPJFKCBIEM: payout.Updates}}}); err != nil { + // Include both source1 group and general removal group for complete entity cleanup. + if err = cwSend(s, &pb.GridFightSyncUpdateResultScNotify{ + GCPNIHHBGGD: []*pb.ABPLALCHNFA{ + { + GridUpdateSrc: pb.JJDFGMMMCMI_JJDFGMMMCMI_DBIEKOMKBIN, + OOEOCGLLHFO: []uint32{payout.UID}, + JKPJFKCBIEM: payout.Updates, + }, + { + JKPJFKCBIEM: []*pb.KFNCAIHIHCM{ + {MNLCHEJKDHO: &pb.KFNCAIHIHCM_GOHHJLAPMBB{GOHHJLAPMBB: payout.UID}}, + }, + }, + }, + }); err != nil { return err } } + if len(alreadyConsumed) > 0 { + var removalUpdates []*pb.KFNCAIHIHCM + for _, uid := range alreadyConsumed { + removalUpdates = append(removalUpdates, &pb.KFNCAIHIHCM{ + MNLCHEJKDHO: &pb.KFNCAIHIHCM_GOHHJLAPMBB{GOHHJLAPMBB: uid}, + }) + } + _ = cwSend(s, cwNotify(removalUpdates...)) + } PacketLog(fmt.Sprintf("CW_USE_ORB saved count=%d revision=%d", len(payouts), s.currencyWarsRevision)) return nil } diff --git a/internal/gameserver/currency_wars_preparation.go b/internal/gameserver/currency_wars_preparation.go index 7958f97..675c913 100644 --- a/internal/gameserver/currency_wars_preparation.go +++ b/internal/gameserver/currency_wars_preparation.go @@ -160,10 +160,9 @@ func cwRestockForNode(catalog *common.CurrencyWarsCatalog, run *pb.MPIMAEMCEFH) if !ok { return nil } - if r.Remaining == 0 { - return fmt.Errorf("CW card pool exhausted") + if r.Remaining > 0 { + r.Remaining-- } - r.Remaining-- pool.Roles[id] = r return nil } @@ -416,7 +415,26 @@ func init() { updates = append(updates, &pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_FEKCPKAPHHO{FEKCPKAPHHO: forge}}) } } - if err = s.saveCurrencyWarsData(data, catalog); err != nil { + runtime, _ := cwBonfireRuntime(catalog, run) + cwSyncTraitComponent(catalog, run, runtime) + if tbCat, err := common.LoadCurrencyWarsTrailblaze(); err == nil && (cwGetTrailblazeLayer(catalog, run) > 0 || (runtime != nil && runtime.Rules["TbOpened"] != 0)) { + updates = append(updates, + &pb.KFNCAIHIHCM{ + MNLCHEJKDHO: &pb.KFNCAIHIHCM_HKHJJCMKKJE{ + HKHJJCMKKJE: &pb.JLKCBOIFJKC{ + TraitId: cwTrailblazeTrait, + ENPLACDLJGA: cwTrailblazeTraitEffectProto(tbCat, runtime), + }, + }, + }, + &pb.KFNCAIHIHCM{ + MNLCHEJKDHO: &pb.KFNCAIHIHCM_GOOCHGKBIPN{ + GOOCHGKBIPN: cwTrailblazeModifierProto(tbCat, runtime), + }, + }, + ) + } + if err = s.saveCurrencyWarsDataWithRuntime(data, catalog, runtime); err != nil { return err } if err = cwSend(s, cwNotify(updates...)); err != nil { @@ -427,4 +445,47 @@ func init() { PacketLog(fmt.Sprintf("CW_UPDATE_POS saved count=%d active=%d revision=%d", len(changes), active, s.currencyWarsRevision)) return nil }) + + // CmdID 8543 (GEBBMDCOLCH) -> ScRsp 8630 (ALNFEBKHCFC) + // Protocol revision: 4.6.53 (upstream_cmdids.go). + // Assumption: Native Client sends GEBBMDCOLCH during preparation / round transition to sync + // the batch formation role positions (CONJCEMAHGN: MDNAGANGNKK = role UID, JFCJNHNBMCB = pos). + // Acknowledging with retcode 0 unblocks the Client UI input controller. + cwHandleNamed("GEBBMDCOLCH", "ALNFEBKHCFC", func(s *Session, req *pb.GEBBMDCOLCH, rsp *pb.ALNFEBKHCFC) error { + catalog, err := common.LoadCurrencyWarsCatalog() + if err != nil { + rsp.Retcode = 0 + return nil + } + data, _, err := readCurrencyWarsData(catalog) + if err != nil || data == nil || data.DHFOFLFCLAH == nil { + rsp.Retcode = 0 + return nil + } + run := data.DHFOFLFCLAH + _, team, _ := cwComponents(run) + if team != nil && len(req.CONJCEMAHGN) > 0 { + posMap := make(map[uint32]uint32, len(req.CONJCEMAHGN)) + for _, entry := range req.CONJCEMAHGN { + if entry != nil && entry.MDNAGANGNKK != 0 { + posMap[entry.MDNAGANGNKK] = entry.JFCJNHNBMCB + } + } + changed := false + for _, role := range team.GridGameRoleList { + if newPos, ok := posMap[role.UniqueId]; ok && role.Pos != newPos { + role.Pos = newPos + changed = true + } + } + if changed { + runtime, _ := cwBonfireRuntime(catalog, run) + cwSyncTraitComponent(catalog, run, runtime) + _ = s.saveCurrencyWarsDataWithRuntime(data, catalog, runtime) + } + } + rsp.Retcode = 0 + PacketLog(fmt.Sprintf("CW_BATCH_UPDATE_POS count=%d revision=%d", len(req.CONJCEMAHGN), s.currencyWarsRevision)) + return nil + }) } diff --git a/internal/gameserver/currency_wars_quit.go b/internal/gameserver/currency_wars_quit.go index 9a6f53d..f402fdc 100644 --- a/internal/gameserver/currency_wars_quit.go +++ b/internal/gameserver/currency_wars_quit.go @@ -19,16 +19,19 @@ func currencyWarsAbandonRun(data *pb.GridFightGetDataScRsp) (uint32, bool) { return runID, true } -// currencyWarsQuitSettlement builds the ordered server notifications expected -// before GridFightQuitSettleScRsp. All packet IDs and wire tags come from the -// current generated protocol; values are copied from the saved run/catalog. -func currencyWarsQuitSettlement(catalog *common.CurrencyWarsCatalog, run *pb.MPIMAEMCEFH) (*pb.GridFightSyncKeepWinCntNotify, *pb.GridFightSettleNotify, error) { +func currencyWarsQuitSettlement(catalog *common.CurrencyWarsCatalog, data *pb.GridFightGetDataScRsp, run *pb.MPIMAEMCEFH) (*pb.GridFightSyncKeepWinCntNotify, *pb.GridFightSettleNotify, error) { + return currencyWarsTerminalSettlement(catalog, data, run, pb.PEIJNEFOCHI_PEIJNEFOCHI_NOCIJDINKPJ, false, nil) +} + +// currencyWarsTerminalSettlement builds the ordered server notifications expected +// when a run ends (quit, defeat, or victory). +func currencyWarsTerminalSettlement(catalog *common.CurrencyWarsCatalog, data *pb.GridFightGetDataScRsp, run *pb.MPIMAEMCEFH, reason pb.PEIJNEFOCHI, isWon bool, damageList []*pb.PLFKMENCDMM) (*pb.GridFightSyncKeepWinCntNotify, *pb.GridFightSettleNotify, error) { if catalog == nil || run == nil || run.UniqueId == 0 { - return nil, nil, fmt.Errorf("CW quit settlement is missing catalog or active run") + return nil, nil, fmt.Errorf("CW settlement is missing catalog or active run") } player, team, _ := cwComponents(run) if player == nil || team == nil { - return nil, nil, fmt.Errorf("CW quit settlement is missing player/team components") + return nil, nil, fmt.Errorf("CW settlement is missing player/team components") } var stage *pb.BEEFLEJAOCN for _, component := range run.NAGDJGMMCBE { @@ -37,13 +40,13 @@ func currencyWarsQuitSettlement(catalog *common.CurrencyWarsCatalog, run *pb.MPI } if current := component.GetKLCPAAHDPJF(); current != nil { if stage != nil { - return nil, nil, fmt.Errorf("CW quit settlement has duplicate stage components") + return nil, nil, fmt.Errorf("CW settlement has duplicate stage components") } stage = current } } if stage == nil { - return nil, nil, fmt.Errorf("CW quit settlement is missing the current stage component") + return nil, nil, fmt.Errorf("CW settlement is missing the current stage component") } avatarIDs := make([]uint32, 0, len(team.GridGameRoleList)) @@ -72,22 +75,88 @@ func currencyWarsQuitSettlement(catalog *common.CurrencyWarsCatalog, run *pb.MPI return roles[i].UniqueId < roles[j].UniqueId }) - var highestDivision common.CurrencyWarsDivision - for _, division := range catalog.SeasonDivisions(catalog.ActiveSeasonID) { - if division.DivisionLevel > highestDivision.DivisionLevel || - (division.DivisionLevel == highestDivision.DivisionLevel && division.Progress > highestDivision.Progress) { - highestDivision = division + // Rank calculation based on remaining HP percentage (GridFight_SettleRankHpConfig: [1, 40, 70]) + // Rank C = 0, Rank B = 1, Rank A = 2, Rank S = 3, Rank SS = 4 + var rankType uint32 + var divisionUp uint32 + if isWon && player.PPKKHHNEOHB > 0 && player.GridFightLineupHp > 0 { + hpPercent := float64(player.GridFightLineupHp) / float64(player.PPKKHHNEOHB) * 100.0 + if hpPercent >= 100.0 || player.GridFightLineupHp >= player.PPKKHHNEOHB { + rankType = 4 // Rank SS (flawless / 100% HP) + divisionUp = 3 + } else if hpPercent >= 70.0 { + rankType = 3 // Rank S + divisionUp = 3 + } else if hpPercent >= 40.0 { + rankType = 2 // Rank A + divisionUp = 2 + } else { + rankType = 1 // Rank B + divisionUp = 1 + } + } else { + rankType = 0 + divisionUp = 0 + } + + seasonDivisions := catalog.SeasonDivisions(catalog.ActiveSeasonID) + curDivisionID := run.DivisionId + newDivisionID := curDivisionID + isPromotion := false + if isWon && divisionUp > 0 && len(seasonDivisions) > 0 { + curIdx := -1 + for i, d := range seasonDivisions { + if d.ID == curDivisionID { + curIdx = i + break + } + } + if curIdx >= 0 { + targetIdx := curIdx + int(divisionUp) + if targetIdx >= len(seasonDivisions) { + targetIdx = len(seasonDivisions) - 1 + } + newDivisionID = seasonDivisions[targetIdx].ID + if seasonDivisions[targetIdx].DivisionLevel > seasonDivisions[curIdx].DivisionLevel { + isPromotion = true + } } } - if highestDivision.ID == 0 { - return nil, nil, fmt.Errorf("CW quit settlement has no active-season division data") + + var equipmentList []*pb.IJDLENJJBHJ + if bag := cwEquipmentBag(run); bag != nil { + equipmentList = bag.GridFightEquipmentList + } + + var augments []*pb.PPGEBEBPMJC + for _, component := range run.NAGDJGMMCBE { + if component == nil { + continue + } + if owned := component.GetLLDBOIANGDN(); owned != nil { + augments = owned.SyncAugmentInfo + break + } + } + + var teamValue uint32 + for _, r := range team.GridGameRoleList { + if r != nil { + teamValue += uint32(r.RoleStar) * 2 + } } finish := &pb.OEDHMAHDECC{ - // GridFightSettleReason value 1 is Quit in the Client enum. - Reason: pb.PEIJNEFOCHI_PEIJNEFOCHI_NOCIJDINKPJ, - IsOverlock: run.IsOverlock, - JLDDDLDNFEF: run.JLDDDLDNFEF, + Reason: reason, + IsOverlock: run.IsOverlock, + JLDDDLDNFEF: run.JLDDDLDNFEF, + LLJJCOOKANH: rankType, + MBMJNEDIFFC: divisionUp, + DBODGPBOIHC: isPromotion, + EEGPILJDLBG: damageList, + GridFightEquipmentList: equipmentList, + SyncAugmentInfo: augments, + AKEGLJLCFPH: teamValue, NDLIKNEKDLL: &pb.GOCIGPCDKDJ{ LKCJAPIGEND: player.PPKKHHNEOHB, PAGPKOKMDBK: player.GridFightLineupHp, @@ -95,21 +164,72 @@ func currencyWarsQuitSettlement(catalog *common.CurrencyWarsCatalog, run *pb.MPI EMOHLJHPOPC: stage.EMOHLJHPOPC, AACGBEBGDPE: run.DivisionId, SectionId: stage.SectionId, + FDGKBOIEHBD: player.NDAINFLOLPJ, + FMEOHKHGMJM: []uint32{catalog.ActiveSeasonModule().ActivityModuleID}, }, - PPJFPNMIFHL: &pb.GLPCEKMKLGG{GridFightAvatarList: avatarIDs}, - GridGameRoleList: roles, - GridGameNpcList: team.GridGameNpcList, - BossInfo: stage.BossInfo, - CFFCNEDGCJJ: stage.CFFCNEDGCJJ, - OAIGAGABCMK: stage.GridFightPortalBuffList, + PPJFPNMIFHL: &pb.GLPCEKMKLGG{GridFightAvatarList: avatarIDs}, + GridGameRoleList: roles, + GridGameNpcList: team.GridGameNpcList, + BossInfo: stage.BossInfo, + CFFCNEDGCJJ: stage.CFFCNEDGCJJ, + OAIGAGABCMK: stage.GridFightPortalBuffList, + GridFightTraitInfo: cwCalculateBoardTraits(catalog, run), } - return &pb.GridFightSyncKeepWinCntNotify{EHBLJGIENPI: player.GDAKCOICIDA}, &pb.GridFightSettleNotify{ + var weeklyScore, expBase, expExtra, totalExp uint32 + if scoreCatalog, err := common.LoadCurrencyWarsScore(catalog); err == nil { + weeklyScore, expBase, expExtra = scoreCatalog.CalculateRunScore(run.DivisionId, run.IsOverlock, stage.DCAHJELNJNP, stage.SectionId, isWon) + totalExp = expBase + expExtra + } + + var curSeasonExp, curTalentCoin uint32 + if data != nil && data.LFLCDNGALME != nil && data.LFLCDNGALME.HFPEJIDDDJG != nil { + if data.LFLCDNGALME.HFPEJIDDDJG.Exp != nil { + curSeasonExp = data.LFLCDNGALME.HFPEJIDDDJG.Exp.KEJGCDOGEDD + } + curTalentCoin = data.LFLCDNGALME.HFPEJIDDDJG.GEBGGMAGDJB + } + newSeasonExp := curSeasonExp + totalExp + seasonLevel := (newSeasonExp / 800) + 1 + maxExp := catalog.ActiveSeasonModule().MaxRewardExp + maxLevel := maxExp / 800 + if maxLevel == 0 { + maxLevel = 60 + } + if seasonLevel > maxLevel { + seasonLevel = maxLevel + } + + cwRecordArchive(finish, catalog.ActiveSeasonID, catalog.ActiveSubSeasonID) + + if data != nil && data.LFLCDNGALME != nil && data.LFLCDNGALME.HFPEJIDDDJG != nil { + if data.LFLCDNGALME.HFPEJIDDDJG.Exp == nil { + data.LFLCDNGALME.HFPEJIDDDJG.Exp = &pb.GLIFOLCEMIN{} + } + data.LFLCDNGALME.HFPEJIDDDJG.Exp.KEJGCDOGEDD = newSeasonExp + if newDivisionID > data.LFLCDNGALME.HFPEJIDDDJG.DivisionId { + data.LFLCDNGALME.HFPEJIDDDJG.DivisionId = newDivisionID + } + data.LFLCDNGALME.HFPEJIDDDJG.GDAKCOICIDA = player.GDAKCOICIDA + } + + settleNotify := &pb.GridFightSettleNotify{ FLHOPMMEGAI: run.DivisionId, - CDANKDNACJP: highestDivision.ID, - NMCKJPEMACM: catalog.ActiveSeasonModule().MaxRewardExp, + CDANKDNACJP: newDivisionID, + NMCKJPEMACM: seasonLevel, + EDHKHJAPMKA: weeklyScore, + CGKNDIJMOCJ: expBase, + MNNKKEPCPPL: expExtra, + KEJGCDOGEDD: newSeasonExp, + GEBGGMAGDJB: curTalentCoin, BFLLJJBHLDF: finish, - }, nil + } + if data == nil { + settleNotify.NMCKJPEMACM = catalog.ActiveSeasonModule().MaxRewardExp + settleNotify.KEJGCDOGEDD = totalExp + } + + return &pb.GridFightSyncKeepWinCntNotify{EHBLJGIENPI: player.GDAKCOICIDA}, settleNotify, nil } func init() { @@ -143,7 +263,7 @@ func init() { return nil } runID := run.UniqueId - keepWinCount, settlement, err := currencyWarsQuitSettlement(catalog, run) + keepWinCount, settlement, err := currencyWarsQuitSettlement(catalog, data, run) if err != nil { PacketLog(fmt.Sprintf("CW_QUIT_SETTLE rejected run=%d reason=settlement_build_error error=%v", runID, err)) return nil diff --git a/internal/gameserver/currency_wars_quit_test.go b/internal/gameserver/currency_wars_quit_test.go index 8e4acd7..5e145b8 100644 --- a/internal/gameserver/currency_wars_quit_test.go +++ b/internal/gameserver/currency_wars_quit_test.go @@ -64,7 +64,7 @@ func TestCurrencyWarsQuitSettlementUsesActiveRunData(t *testing.T) { }, } - keepWin, settlement, err := currencyWarsQuitSettlement(catalog, run) + keepWin, settlement, err := currencyWarsQuitSettlement(catalog, nil, run) if err != nil { t.Fatal(err) } diff --git a/internal/gameserver/currency_wars_reference_enemy.go b/internal/gameserver/currency_wars_reference_enemy.go index f041a12..ca9f955 100644 --- a/internal/gameserver/currency_wars_reference_enemy.go +++ b/internal/gameserver/currency_wars_reference_enemy.go @@ -442,6 +442,18 @@ func cwBuildReferenceBossEnemy(c *common.CurrencyWarsCatalog, run *pb.MPIMAEMCEF grid.IOCKDJCDMAH = &pb.MIPDJDJLGPL{EMOHLJHPOPC: s.EMOHLJHPOPC, DCAHJELNJNP: s.DCAHJELNJNP, SectionId: s.SectionId, CJCCMAACJDB: node.CJCCMAACJDB} grid.IFIIOIIKJHO = branch.IFIIOIIKJHO grid.LODNFLMBIEH = division.EnemyHardLevel + if t.NodeType == "EliteBranch" { + runtime, err := cwBonfireRuntime(c, run) + if err != nil { + return err + } + if cwXaodevGenerated(run, runtime) { + if _, err := cwXaodevSelectionDifficulty(c, run, runtime); err != nil { + return err + } + grid.LODNFLMBIEH = s.ANJBLBEOJND.PIJBPDPEODG + } + } grid.BattleWaveId = waves[0].WaveId return nil } diff --git a/internal/gameserver/currency_wars_route_factory.go b/internal/gameserver/currency_wars_route_factory.go index 6b1df19..009c239 100644 --- a/internal/gameserver/currency_wars_route_factory.go +++ b/internal/gameserver/currency_wars_route_factory.go @@ -12,7 +12,7 @@ import ( // reference workflow. The caller owns the detached snapshot and its single CAS // commit; this helper grants no rewards and never reads/writes a player save. // Saved route displays retain run-specific Tea Break/spotlight substitutions. -func cwAdvanceRouteNode(catalog *common.CurrencyWarsCatalog, run *pb.MPIMAEMCEFH) (*pb.GridFightSyncUpdateResultScNotify, error) { +func cwAdvanceRouteNode(catalog *common.CurrencyWarsCatalog, run *pb.MPIMAEMCEFH, runtime *common.CurrencyWarsRuntimeState) (*pb.GridFightSyncUpdateResultScNotify, error) { if run == nil || run.FMIGOKIDKJL == nil { return nil, fmt.Errorf("CW advance requires current pending queue") } @@ -71,8 +71,14 @@ func cwAdvanceRouteNode(catalog *common.CurrencyWarsCatalog, run *pb.MPIMAEMCEFH shadowStage.SectionId = next.SectionID shadowStage.IMPNAHBCADN = nil var layer *pb.NCKOKDHNJDC + var difficulty *pb.KFNCAIHIHCM if template.NodeType == "Supply" { layer, err = cwCapturedSupplyLayer(catalog) + } else if template.NodeType == "EliteBranch" { + if runtime == nil || runtime.RunID != run.UniqueId { + return nil, fmt.Errorf("CW generated Encounter requires matching runtime transaction") + } + layer, err = cwGenerateXaodevEncounter(catalog, shadow, display) } else { layer, err = cwGenerateReferenceEnemyLayer(catalog, shadow, display) } @@ -82,12 +88,50 @@ func cwAdvanceRouteNode(catalog *common.CurrencyWarsCatalog, run *pb.MPIMAEMCEFH if layer == nil { return nil, fmt.Errorf("CW generated next node layer missing") } + hasBranchDiff := stage.ANJBLBEOJND != nil && stage.ANJBLBEOJND.PIFNOKAMBLL != nil && stage.ANJBLBEOJND.PIFNOKAMBLL[cwDifficultySourceBranch] != 0 + if template.NodeType == "EliteBranch" || cwXaodevGenerated(run, runtime) || hasBranchDiff { + difficulty, err = cwXaodevDifficulty(catalog, shadow, 0) + if err != nil { + return nil, err + } + } pending := &pb.CHCHAJIANIM{QueuePosition: queue + 1, FGLIODAJFML: &pb.CHCHAJIANIM_NNDGEJFLGGF{NNDGEJFLGGF: &pb.DPHPAKFLNOD{}}} stage.DCAHJELNJNP = next.ChapterID stage.SectionId = next.SectionID stage.IMPNAHBCADN = layer + stage.ANJBLBEOJND = shadowStage.ANJBLBEOJND run.FMIGOKIDKJL = pending - return cwNotify(cwFinish(queue), &pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_OOICHLAMNIG{OOICHLAMNIG: &pb.AINFNFIDGFF{DCAHJELNJNP: stage.DCAHJELNJNP, SectionId: stage.SectionId, IMPNAHBCADN: layer}}}, &pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_FMIGOKIDKJL{FMIGOKIDKJL: pending}}), nil + if template.NodeType == "EliteBranch" { + if runtime.Rules == nil { + runtime.Rules = map[string]uint32{} + } + runtime.Rules[cwXaodevNodeKey(stage)] = 1 + } + cwTrailblazeNodeEntered(catalog, run, runtime) + cwSyncTraitComponent(catalog, run, runtime) + notify := cwNotify(cwFinish(queue), &pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_OOICHLAMNIG{OOICHLAMNIG: &pb.AINFNFIDGFF{DCAHJELNJNP: stage.DCAHJELNJNP, SectionId: stage.SectionId, IMPNAHBCADN: layer}}}) + if difficulty != nil { + notify.GCPNIHHBGGD[0].JKPJFKCBIEM = append(notify.GCPNIHHBGGD[0].JKPJFKCBIEM, difficulty) + } + if tbCat, err := common.LoadCurrencyWarsTrailblaze(); err == nil && (cwGetTrailblazeLayer(catalog, run) > 0 || (runtime != nil && runtime.Rules["TbOpened"] != 0)) { + notify.GCPNIHHBGGD[0].JKPJFKCBIEM = append(notify.GCPNIHHBGGD[0].JKPJFKCBIEM, + &pb.KFNCAIHIHCM{ + MNLCHEJKDHO: &pb.KFNCAIHIHCM_HKHJJCMKKJE{ + HKHJJCMKKJE: &pb.JLKCBOIFJKC{ + TraitId: cwTrailblazeTrait, + ENPLACDLJGA: cwTrailblazeTraitEffectProto(tbCat, runtime), + }, + }, + }, + &pb.KFNCAIHIHCM{ + MNLCHEJKDHO: &pb.KFNCAIHIHCM_GOOCHGKBIPN{ + GOOCHGKBIPN: cwTrailblazeModifierProto(tbCat, runtime), + }, + }, + ) + } + notify.GCPNIHHBGGD[0].JKPJFKCBIEM = append(notify.GCPNIHHBGGD[0].JKPJFKCBIEM, &pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_FMIGOKIDKJL{FMIGOKIDKJL: pending}}) + return notify, nil } // cwGeneratedRoundPreparation handles the same RoundBegin -> shop refresh -> diff --git a/internal/gameserver/currency_wars_save.go b/internal/gameserver/currency_wars_save.go index 54928d7..1ec1482 100644 --- a/internal/gameserver/currency_wars_save.go +++ b/internal/gameserver/currency_wars_save.go @@ -102,7 +102,12 @@ func (s *Session) saveCurrencyWarsDataWithRuntime(data *goproto.GridFightGetData return fmt.Errorf("CW runtime rules belong to a different run") } if runtime == nil { - runtime = &common.CurrencyWarsRuntimeState{RunID: run.UniqueId} + existing, err := cwBonfireRuntime(catalog, run) + if err == nil && existing != nil && existing.RunID == run.UniqueId { + runtime = existing + } else { + runtime = &common.CurrencyWarsRuntimeState{RunID: run.UniqueId} + } } next.Runtime = runtime } diff --git a/internal/gameserver/currency_wars_settlement.go b/internal/gameserver/currency_wars_settlement.go index 8c389f4..adf78ec 100644 --- a/internal/gameserver/currency_wars_settlement.go +++ b/internal/gameserver/currency_wars_settlement.go @@ -1,9 +1,14 @@ package gameserver import ( + "crypto/rand" "encoding/json" "fmt" "math" + "math/big" + "sort" + "strconv" + "strings" "gac-hkrpg-go/internal/common" pb "gac-hkrpg-go/protocol/proto" @@ -45,10 +50,16 @@ func cwNodeTransitions(c *common.CurrencyWarsCatalog) ([]cwNodeTransition, error return r.Entries, nil } func cwStage(run *pb.MPIMAEMCEFH) (*pb.BEEFLEJAOCN, *pb.OHMDHKGAEMM) { + if run == nil { + return nil, nil + } for _, c := range run.NAGDJGMMCBE { + if c == nil { + continue + } if s := c.GetKLCPAAHDPJF(); s != nil { for _, n := range s.OOMMIIKPHBC { - if n.SectionId == s.SectionId && n.DCAHJELNJNP == s.DCAHJELNJNP { + if n != nil && n.SectionId == s.SectionId && n.DCAHJELNJNP == s.DCAHJELNJNP { return s, n.DisplayValue } } @@ -58,6 +69,102 @@ func cwStage(run *pb.MPIMAEMCEFH) (*pb.BEEFLEJAOCN, *pb.OHMDHKGAEMM) { return nil, nil } +func cwRollNodeOrbs(catalog *common.CurrencyWarsCatalog, run *pb.MPIMAEMCEFH, routeID, chapter, section, progress uint32) ([]uint32, error) { + route, err := catalog.RouteNodes(routeID) + if err != nil { + return nil, err + } + refNodes, err := common.LoadCurrencyWarsReferenceNodes(catalog) + if err != nil { + return nil, err + } + var template common.CurrencyWarsReferenceNodeTemplate + found := false + for _, r := range route { + if r.ChapterID == chapter && r.SectionID == section { + t, err := refNodes.Template(r.NodeTemplateID) + if err == nil { + template = t + found = true + } + break + } + } + if !found || len(template.ParamList) == 0 { + return nil, nil + } + + nodeDropsCat, err := common.LoadCurrencyWarsNodeDrops(catalog) + if err != nil { + return nil, err + } + + dropKey := strconv.FormatUint(uint64(template.ParamList[0]), 10) + drop, ok := nodeDropsCat.NodeDrops[dropKey] + if !ok || len(drop.Weights) == 0 { + return nil, nil + } + + recipes, err := common.LoadCurrencyWarsOrbRecipes() + if err != nil { + return nil, err + } + supported := map[uint32]bool{} + for _, recipe := range recipes.Recipes { + if recipe.Status == "Implemented" && recipe.Engine == "Orb" { + supported[recipe.ID] = true + } + } + + type weightEntry struct { + id uint32 + weight uint32 + } + var entries []weightEntry + var totalWeight uint32 + for k, w := range drop.Weights { + id64, err := strconv.ParseUint(k, 10, 32) + if err != nil { + continue + } + id := uint32(id64) + if supported[id] && w > 0 { + entries = append(entries, weightEntry{id: id, weight: w}) + totalWeight += w + } + } + if totalWeight == 0 { + return nil, nil + } + + sort.Slice(entries, func(i, j int) bool { + return entries[i].id < entries[j].id + }) + + percent := progress + if percent > 100 { + percent = 100 + } + count := (drop.Rolls*percent + 99) / 100 + + var rolled []uint32 + for i := uint32(0); i < count; i++ { + rBig, err := rand.Int(rand.Reader, big.NewInt(int64(totalWeight))) + if err != nil { + return nil, err + } + roll := uint32(rBig.Int64()) + for _, e := range entries { + if roll < e.weight { + rolled = append(rolled, e.id) + break + } + roll -= e.weight + } + } + return rolled, nil +} + func finishCurrencyWarsBattle(s *Session, req *pb.PVEBattleResultCsReq, current *pb.SceneBattleInfo) error { if current == nil || req.BattleId != current.BattleId || req.StageId != current.StageId { return fmt.Errorf("CW battle identity differs") @@ -216,55 +323,67 @@ func finishCurrencyWarsBattle(s *Session, req *pb.PVEBattleResultCsReq, current loseHP(basicLoss, pb.DFIJIBKBEJL_DFIJIBKBEJL_FDKCPHMMFCD) loseHP(progressLoss, pb.DFIJIBKBEJL_DFIJIBKBEJL_IEGEHAKDEHA) loseHP(thresholdLoss, pb.DFIJIBKBEJL_DFIJIBKBEJL_KEOPICGMGPI) - if player.GridFightLineupHp == 0 { - return fmt.Errorf("CW defeat settlement requires terminal-run handler") - } + isDefeat := (player.GridFightLineupHp == 0) kind, err := catalog.NodeTypeName(node.CJCCMAACJDB) if err != nil { return err } - for _, name := range rules.StreakNodes { - if name == kind { - if perfect { - if player.GDAKCOICIDA == math.MaxUint32 { - return fmt.Errorf("CW streak overflow") + if isDefeat { + player.GDAKCOICIDA = 0 + } else { + for _, name := range rules.StreakNodes { + if name == kind { + if perfect { + if player.GDAKCOICIDA == math.MaxUint32 { + return fmt.Errorf("CW streak overflow") + } + player.GDAKCOICIDA++ + } else { + player.GDAKCOICIDA = 0 } - player.GDAKCOICIDA++ - } else { - player.GDAKCOICIDA = 0 + break } - break } } - bonusFound := false var streakGold uint32 - for _, v := range rules.Victories { - if v.Low <= player.GDAKCOICIDA && player.GDAKCOICIDA <= v.Up { - if bonusFound { - return fmt.Errorf("CW victory ranges overlap") + if !isDefeat { + bonusFound := false + for _, v := range rules.Victories { + if v.Low <= player.GDAKCOICIDA && player.GDAKCOICIDA <= v.Up { + if bonusFound { + return fmt.Errorf("CW victory ranges overlap") + } + bonusFound = true + streakGold = v.Bonus } - bonusFound = true - streakGold = v.Bonus } - } - if !bonusFound { - return fmt.Errorf("CW victory range missing") + if !bonusFound { + return fmt.Errorf("CW victory range missing") + } } interest, err := catalog.BaseInterest(player.Gold, player.KNFOCCPOJEA) if err != nil { return err } basicGold := node.PICMDONCKCO + if isDefeat { + basicGold = 0 + streakGold = 0 + } sum := uint64(player.Gold) + uint64(basicGold) + uint64(interest) + uint64(streakGold) if sum > math.MaxUint32 { sum = math.MaxUint32 } player.Gold = uint32(sum) - gain, err := catalog.IntegerRule("GridFight_GainExpWhenWaveEnd") - if err != nil || gain < 0 || uint64(gain) > math.MaxUint32 { - return fmt.Errorf("CW EXP gain rule invalid") + var gain uint32 + if !isDefeat { + g, err := catalog.BattleNodeExperience(run.IsOverlock, stage.DCAHJELNJNP, kind) + if err != nil { + return err + } + gain = uint32(g) } - plan, err := catalog.PlanBaseExperience(player.EHNJDIDABHD, player.GridFightCurLevelExp, uint32(gain), player.NDAINFLOLPJ) + plan, err := catalog.PlanBaseExperience(player.EHNJDIDABHD, player.GridFightCurLevelExp, gain, player.NDAINFLOLPJ) if err != nil { return err } @@ -276,12 +395,18 @@ func finishCurrencyWarsBattle(s *Session, req *pb.PVEBattleResultCsReq, current if err != nil { return err } - if err = cwApplyBaseExperience(catalog, player, shop, uint32(gain)); err != nil { - return err + if gain > 0 { + if err = cwApplyBaseExperience(catalog, player, shop, gain); err != nil { + return err + } } var encounterUpdates []*pb.KFNCAIHIHCM - if kind == "EliteBranch" { - encounterUpdates, err = cwEncounterRewards(catalog, run, selectedBranch, progress, penalty.ThresholdPosition) + runtime, err := cwBonfireRuntime(catalog, run) + if err != nil { + return err + } + if kind == "EliteBranch" && !isDefeat { + encounterUpdates, err = cwEncounterRewards(catalog, run, selectedBranch, progress, penalty.ThresholdPosition, runtime) if err != nil { return err } @@ -293,21 +418,39 @@ func finishCurrencyWarsBattle(s *Session, req *pb.PVEBattleResultCsReq, current } for _, role := range current.EIGMKOJOEEI.GridGameRoleList { total := float64(0) - for _, stat := range req.Stt.APAGMJNECCN.DOGMMGLBJIJ { - if stat.RoleId == role.RoleId && !math.IsNaN(stat.Damage) && !math.IsInf(stat.Damage, 0) && stat.Damage > 0 { - total += stat.Damage + if req.Stt != nil && req.Stt.APAGMJNECCN != nil { + for _, stat := range req.Stt.APAGMJNECCN.DOGMMGLBJIJ { + if (stat.RoleId == role.RoleId || stat.RoleId == role.AvatarId || stat.RoleId == role.UniqueId) && !math.IsNaN(stat.Damage) && !math.IsInf(stat.Damage, 0) && stat.Damage > 0 { + total += stat.Damage + } + } + } + if total == 0 && req.Stt != nil { + for _, ba := range req.Stt.BattleAvatarList { + if (ba.Id == role.AvatarId || ba.Id == role.RoleId) && !math.IsNaN(ba.TotalDamage) && !math.IsInf(ba.TotalDamage, 0) && ba.TotalDamage > 0 { + total += ba.TotalDamage + } } } damage.EEGPILJDLBG = append(damage.EEGPILJDLBG, &pb.PLFKMENCDMM{RoleId: role.RoleId, RoleStar: role.RoleStar, TotalDamage: total, DIGNECHKINI: uint64(role.Pos) > uint64(frontMax)}) } oldChapter, oldSection := stage.DCAHJELNJNP, stage.SectionId var nextNotify *pb.GridFightSyncUpdateResultScNotify - if transition != nil { + isFinalNode := false + if isDefeat { + // Terminal defeat: stage does not advance + } else if transition != nil { stage.DCAHJELNJNP = nextLevel.DCAHJELNJNP stage.SectionId = nextLevel.SectionId stage.IMPNAHBCADN = proto.Clone(nextLevel.IMPNAHBCADN).(*pb.NCKOKDHNJDC) run.FMIGOKIDKJL = nextPending nextNotify = cwNotify(&pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_OOICHLAMNIG{OOICHLAMNIG: nextLevel}}, &pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_FMIGOKIDKJL{FMIGOKIDKJL: nextPending}}) + if stage.ANJBLBEOJND != nil && stage.ANJBLBEOJND.PIFNOKAMBLL != nil && stage.ANJBLBEOJND.PIFNOKAMBLL[cwDifficultySourceBranch] != 0 { + diffUpdate, err := cwXaodevDifficulty(catalog, run, 0) + if err == nil && diffUpdate != nil { + nextNotify.GCPNIHHBGGD[0].JKPJFKCBIEM = append(nextNotify.GCPNIHHBGGD[0].JKPJFKCBIEM, diffUpdate) + } + } } else { // A battle has no pending action to allocate from. The persisted CAS // revision is monotonic and also backs Bonfire entry queue allocation. @@ -315,39 +458,167 @@ func finishCurrencyWarsBattle(s *Session, req *pb.PVEBattleResultCsReq, current return fmt.Errorf("CW settlement queue exhausted") } run.FMIGOKIDKJL = &pb.CHCHAJIANIM{QueuePosition: uint32(revision)} - nextNotify, err = cwAdvanceRouteNode(catalog, run) + nextNotify, err = cwAdvanceRouteNode(catalog, run, runtime) + if err != nil { + if strings.Contains(err.Error(), "no next node") { + isFinalNode = true + } else { + return err + } + } else { + // The allocation seed is not a Client pending action, so it must not + // produce a synthetic FinishPendingAction notification. + for _, group := range nextNotify.GCPNIHHBGGD { + kept := group.JKPJFKCBIEM[:0] + for _, update := range group.JKPJFKCBIEM { + if update.GetPMFBIGLPJJA() != uint32(revision) { + kept = append(kept, update) + } + } + group.JKPJFKCBIEM = kept + } + } + } + if !isDefeat && !isFinalNode { + runtime, err = cwApplyBonfireEnemyRules(catalog, run, runtime) if err != nil { return err } - // The allocation seed is not a Client pending action, so it must not - // produce a synthetic FinishPendingAction notification. - for _, group := range nextNotify.GCPNIHHBGGD { - kept := group.JKPJFKCBIEM[:0] - for _, update := range group.JKPJFKCBIEM { - if update.GetPMFBIGLPJJA() != uint32(revision) { - kept = append(kept, update) - } - } - group.JKPJFKCBIEM = kept + if runtime.Rules["PetPalStarDown"] != 0 && stage.GetIMPNAHBCADN().GetMPOBBIPMNNJ() != nil { + nextNotify.GCPNIHHBGGD = append(nextNotify.GCPNIHHBGGD, cwNotify(&pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_OOICHLAMNIG{OOICHLAMNIG: &pb.AINFNFIDGFF{DCAHJELNJNP: stage.DCAHJELNJNP, SectionId: stage.SectionId, IMPNAHBCADN: stage.IMPNAHBCADN}}}).GCPNIHHBGGD...) } } - runtime, err := cwApplyBonfireEnemyRules(catalog, run, nil) - if err != nil { - return err + tbLayer := cwGetTrailblazeLayer(catalog, run) + if tbLayer > 0 { + if tbCat, err := common.LoadCurrencyWarsTrailblaze(); err == nil { + income := tbCat.Income(tbLayer) + runtime.Rules["TbPower"] += income + PacketLog(fmt.Sprintf("CW_TRAILBLAZE_INCOME layer=%d income=%d total_power=%d", tbLayer, income, runtime.Rules["TbPower"])) + } } - if runtime.Rules["PetPalStarDown"] != 0 && stage.GetIMPNAHBCADN().GetMPOBBIPMNNJ() != nil { - nextNotify.GCPNIHHBGGD = append(nextNotify.GCPNIHHBGGD, cwNotify(&pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_OOICHLAMNIG{OOICHLAMNIG: &pb.AINFNFIDGFF{DCAHJELNJNP: stage.DCAHJELNJNP, SectionId: stage.SectionId, IMPNAHBCADN: stage.IMPNAHBCADN}}}).GCPNIHHBGGD...) + cwSyncTraitComponent(catalog, run, runtime) + + var droppedOrbs []*pb.IBPDPCOGDEP + var orbUpdates []*pb.KFNCAIHIHCM + if !isDefeat { + orbIDs, dropErr := cwRollNodeOrbs(catalog, run, stage.EMOHLJHPOPC, oldChapter, oldSection, progress) + if dropErr != nil { + PacketLog(fmt.Sprintf("CW_DROP_ORBS error: %v", dropErr)) + } else if len(orbIDs) > 0 { + var orbBag *pb.DBONCGFHHDN + for _, c := range run.NAGDJGMMCBE { + if b := c.GetDBAGKJNPDOL(); b != nil { + orbBag = b + break + } + } + if orbBag == nil { + orbBag = &pb.DBONCGFHHDN{} + run.NAGDJGMMCBE = append(run.NAGDJGMMCBE, &pb.GridFightGameInfo{ + LEOIAOFBBFA: &pb.GridFightGameInfo_DBAGKJNPDOL{ + DBAGKJNPDOL: orbBag, + }, + }) + } + for _, orbID := range orbIDs { + uid, idErr := cwNextEntityID(run) + if idErr != nil { + break + } + orb := &pb.IBPDPCOGDEP{ + UniqueId: uid, + LDBNOMPCOKK: orbID, + } + orbBag.MDKGCIKOMLI = append(orbBag.MDKGCIKOMLI, orb) + droppedOrbs = append(droppedOrbs, orb) + orbUpdates = append(orbUpdates, &pb.KFNCAIHIHCM{ + MNLCHEJKDHO: &pb.KFNCAIHIHCM_AFILIPBHMOO{ + AFILIPBHMOO: &pb.MMEDLCAENAL{ + UniqueId: uid, + LDBNOMPCOKK: orbID, + }, + }, + }) + } + PacketLog(fmt.Sprintf("CW_DROP_ORBS node=%d-%d dropped=%d", oldChapter, oldSection, len(droppedOrbs))) + } } + player.GAJGBBFELEL = &pb.GMBMNFLMKPI{IBCPAKJAPFF: pb.KMJNPEMPNMC_KMJNPEMPNMC_AEINKINPAFL, LMICDNNABNM: pb.GGNKDLAHAGH_GGNKDLAHAGH_FHOHPFBDNOH} end := &pb.GridFightEndBattleStageNotify{DCAHJELNJNP: oldChapter, SectionId: oldSection, EMOHLJHPOPC: stage.EMOHLJHPOPC, HFLPDCJDIAC: progress, OAHFCPPIMLF: penalty.ThresholdPosition, CBHJKFNCDAD: perfect, JPKHBELGHCG: previousStreak, OCCOHKIMGCN: player.GDAKCOICIDA, CLFNPMGHMFC: basicGold, DMNILJEGICO: interest, OGLOCDMNNIM: streakGold, ODJEBDNBLLC: previousHP, PAGPKOKMDBK: player.GridFightLineupHp, LKCJAPIGEND: player.PPKKHHNEOHB, NKBIDLBLONC: damage, HMKOKACNHJH: hpChanges, AEOCOGHMEBG: &pb.GEEMFDBDMDH{PBMAHOJBAGG: uint32(gain), EDCLIPCEHAO: &pb.NFPLDMFCLHA{Level: previousLevel, Exp: previousExp, KDBMPKJKAKI: previousRow.LevelUpExp}, LHIAODNJIIC: &pb.NFPLDMFCLHA{Level: plan.Level, Exp: plan.Exp, KDBMPKJKAKI: row.LevelUpExp}}} + if len(droppedOrbs) > 0 { + var dropEntries []*pb.BCCONKJHDJL + for _, orb := range droppedOrbs { + dropEntries = append(dropEntries, &pb.BCCONKJHDJL{ + FKKGLFOHGGA: pb.MJJIFGINJAJ_MJJIFGINJAJ_ANMDMBGNOHL, + FNLMKJLKLFH: orb.LDBNOMPCOKK, + Num: 1, + }) + } + end.AFFFCONCDMG = map[uint32]*pb.GridFightDropInfo{ + 0: {NMGIABHIJJB: dropEntries}, + } + } + + update := cwNotify(cwUnlock(), cwGoldSync(player.Gold), cwCapacitySync(player), &pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_GridFightLineupHp{GridFightLineupHp: &pb.FMJDDIHBELC{PPKKHHNEOHB: player.PPKKHHNEOHB, GridFightLineupHp: player.GridFightLineupHp}}}, &pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_PLPHEIBLPDL{PLPHEIBLPDL: &pb.PIHKLPJGEPM{Level: plan.Level, Exp: plan.Exp, HFMKLAPEKFA: player.NDAINFLOLPJ}}}) + update.GCPNIHHBGGD[0].JKPJFKCBIEM = append(update.GCPNIHHBGGD[0].JKPJFKCBIEM, encounterUpdates...) + update.GCPNIHHBGGD[0].JKPJFKCBIEM = append(update.GCPNIHHBGGD[0].JKPJFKCBIEM, orbUpdates...) + if tbCat, err := common.LoadCurrencyWarsTrailblaze(); err == nil && (tbLayer > 0 || runtime.Rules["TbOpened"] != 0) { + update.GCPNIHHBGGD[0].JKPJFKCBIEM = append(update.GCPNIHHBGGD[0].JKPJFKCBIEM, + &pb.KFNCAIHIHCM{ + MNLCHEJKDHO: &pb.KFNCAIHIHCM_HKHJJCMKKJE{ + HKHJJCMKKJE: &pb.JLKCBOIFJKC{ + TraitId: cwTrailblazeTrait, + ENPLACDLJGA: cwTrailblazeTraitEffectProto(tbCat, runtime), + }, + }, + }, + &pb.KFNCAIHIHCM{ + MNLCHEJKDHO: &pb.KFNCAIHIHCM_GOOCHGKBIPN{ + GOOCHGKBIPN: cwTrailblazeModifierProto(tbCat, runtime), + }, + }, + ) + } + + if isDefeat || isFinalNode { + reason := pb.PEIJNEFOCHI_PEIJNEFOCHI_JOJPGBCECLN + if isDefeat { + reason = pb.PEIJNEFOCHI_PEIJNEFOCHI_HIGMFLKHGNJ + } + keepWin, settle, termErr := currencyWarsTerminalSettlement(catalog, data, run, reason, !isDefeat, damage.EEGPILJDLBG) + if termErr != nil { + return termErr + } + data.DHFOFLFCLAH = nil + if termErr = s.saveCurrencyWarsData(data, catalog); termErr != nil { + return termErr + } + CURRENT_BATTLE.Delete(s.Token) + s.currencyWarsOpen = false + + if err = s.Send(update); err != nil { + return err + } + if err = s.Send(end); err != nil { + return err + } + if err = cwSend(s, keepWin); err != nil { + return err + } + if err = cwSend(s, settle); err != nil { + return err + } + PacketLog(fmt.Sprintf("CW_SETTLEMENT terminal victory=%v battle=%d progress=%d gold=%d node=%d-%d", !isDefeat, req.BattleId, progress, player.Gold, oldChapter, oldSection)) + return nil + } + if err = s.saveCurrencyWarsDataWithRuntime(data, catalog, runtime); err != nil { return err } CURRENT_BATTLE.Delete(s.Token) // Duplicate uploads cannot award again. - update := cwNotify(cwUnlock(), cwGoldSync(player.Gold), cwCapacitySync(player), &pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_GridFightLineupHp{GridFightLineupHp: &pb.FMJDDIHBELC{PPKKHHNEOHB: player.PPKKHHNEOHB, GridFightLineupHp: player.GridFightLineupHp}}}, &pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_PLPHEIBLPDL{PLPHEIBLPDL: &pb.PIHKLPJGEPM{Level: plan.Level, Exp: plan.Exp, HFMKLAPEKFA: player.NDAINFLOLPJ}}}) - update.GCPNIHHBGGD[0].JKPJFKCBIEM = append(update.GCPNIHHBGGD[0].JKPJFKCBIEM, encounterUpdates...) if err = s.Send(update); err != nil { return err } diff --git a/internal/gameserver/currency_wars_supply.go b/internal/gameserver/currency_wars_supply.go index 3464b17..508a2ee 100644 --- a/internal/gameserver/currency_wars_supply.go +++ b/internal/gameserver/currency_wars_supply.go @@ -15,7 +15,7 @@ const cwEquipmentSourceSupply uint32 = 9 // Applies only to the detached pending-action snapshot; the caller saves the // complete transaction before publishing any grant or queue acknowledgement. -func cwSelectSupply(catalog *common.CurrencyWarsCatalog, run *pb.MPIMAEMCEFH, request *pb.HMNMJKCHABE) (*pb.GridFightSyncUpdateResultScNotify, error) { +func cwSelectSupply(catalog *common.CurrencyWarsCatalog, run *pb.MPIMAEMCEFH, request *pb.HMNMJKCHABE, runtime *common.CurrencyWarsRuntimeState) (*pb.GridFightSyncUpdateResultScNotify, error) { pending := run.GetFMIGOKIDKJL() offer := pending.GetFIKAGBAEOAP() if offer == nil || request == nil || offer.DAEMEBCKHIK == 0 || uint64(len(request.LOAFKKAFBOK)) != uint64(offer.DAEMEBCKHIK) { @@ -162,7 +162,7 @@ func cwSelectSupply(catalog *common.CurrencyWarsCatalog, run *pb.MPIMAEMCEFH, re // Keep the selected rewards on this detached run and let the common // route factory validate and construct the actual next node. If that // node family is not supported yet, the caller discards the snapshot. - advance, err := cwAdvanceRouteNode(catalog, run) + advance, err := cwAdvanceRouteNode(catalog, run, runtime) if err != nil { return nil, fmt.Errorf("CW Supply next node: %w", err) } diff --git a/internal/gameserver/currency_wars_supply_offer.go b/internal/gameserver/currency_wars_supply_offer.go index b738a2d..c9018b1 100644 --- a/internal/gameserver/currency_wars_supply_offer.go +++ b/internal/gameserver/currency_wars_supply_offer.go @@ -64,10 +64,9 @@ func cwGenerateSupplyOfferWithRules(c *common.CurrencyWarsCatalog, run *pb.MPIMA var equipment []uint32 for _, item := range recipes.Equipment { if item.Category == category { - if _, ok := gear[item.ID]; !ok { - return nil, fmt.Errorf("CW Supply equipment %d is not captured", item.ID) + if _, ok := gear[item.ID]; ok { + equipment = append(equipment, item.ID) } - equipment = append(equipment, item.ID) } } if len(equipment) == 0 { diff --git a/internal/gameserver/currency_wars_supply_offer_test.go b/internal/gameserver/currency_wars_supply_offer_test.go index b4e3281..32f658e 100644 --- a/internal/gameserver/currency_wars_supply_offer_test.go +++ b/internal/gameserver/currency_wars_supply_offer_test.go @@ -17,32 +17,35 @@ func TestCurrencyWarsSupplyOffersUseCapturedRolesAndEquipment(t *testing.T) { if err != nil { t.Fatal(err) } - for n := 0; n < 10; n++ { - offer, err := cwGenerateSupplyOfferWithRules(c, run, nil) - if err != nil { - t.Fatal(err) - } - if offer.DAEMEBCKHIK == 0 || len(offer.MFBAKLCBPCJ) < int(offer.DAEMEBCKHIK) { - t.Fatal("Supply has too few options") - } - used := map[uint32]bool{} - for _, item := range offer.MFBAKLCBPCJ { - if item == nil || used[item.RoleId] { - t.Fatal("Supply role is missing or duplicated") + for _, chapter := range []uint32{2, 3} { + chRun := &pb.MPIMAEMCEFH{NAGDJGMMCBE: []*pb.GridFightGameInfo{{LEOIAOFBBFA: &pb.GridFightGameInfo_KLCPAAHDPJF{KLCPAAHDPJF: &pb.BEEFLEJAOCN{DCAHJELNJNP: chapter}}}}} + for n := 0; n < 10; n++ { + offer, err := cwGenerateSupplyOfferWithRules(c, chRun, nil) + if err != nil { + t.Fatalf("chapter %d supply offer: %v", chapter, err) } - used[item.RoleId] = true - drop := item.GetOPPFPECOBEF() - if drop == nil || drop.FNLMKJLKLFH != item.RoleId { - t.Fatal("Supply role display differs") + if offer.DAEMEBCKHIK == 0 || len(offer.MFBAKLCBPCJ) < int(offer.DAEMEBCKHIK) { + t.Fatalf("chapter %d supply has too few options", chapter) } - if _, err := c.CapturedRoleVariant(item.RoleId, drop.GetDisplayValue().GetRoleStar()); err != nil { - t.Fatal(err) - } - if len(item.GridFightItemList) != 1 || len(item.DHLBEFLOOPM) != 1 || item.GridFightItemList[0] != item.DHLBEFLOOPM[0].FNLMKJLKLFH { - t.Fatal("Supply gear display differs") - } - if _, ok := gear[item.GridFightItemList[0]]; !ok { - t.Fatal("Supply gear is not captured") + used := map[uint32]bool{} + for _, item := range offer.MFBAKLCBPCJ { + if item == nil || used[item.RoleId] { + t.Fatal("Supply role is missing or duplicated") + } + used[item.RoleId] = true + drop := item.GetOPPFPECOBEF() + if drop == nil || drop.FNLMKJLKLFH != item.RoleId { + t.Fatal("Supply role display differs") + } + if _, err := c.CapturedRoleVariant(item.RoleId, drop.GetDisplayValue().GetRoleStar()); err != nil { + t.Fatal(err) + } + if len(item.GridFightItemList) != 1 || len(item.DHLBEFLOOPM) != 1 || item.GridFightItemList[0] != item.DHLBEFLOOPM[0].FNLMKJLKLFH { + t.Fatal("Supply gear display differs") + } + if _, ok := gear[item.GridFightItemList[0]]; !ok { + t.Fatalf("Supply gear %d is not captured", item.GridFightItemList[0]) + } } } } diff --git a/internal/gameserver/currency_wars_systems_test.go b/internal/gameserver/currency_wars_systems_test.go new file mode 100644 index 0000000..c2fac1e --- /dev/null +++ b/internal/gameserver/currency_wars_systems_test.go @@ -0,0 +1,617 @@ +package gameserver + +import ( + "os" + "testing" + + "gac-hkrpg-go/internal/common" + pb "gac-hkrpg-go/protocol/proto" + "google.golang.org/protobuf/proto" +) + +func TestCurrencyWarsTalentsHandlers(t *testing.T) { + origPersistent, _ := os.ReadFile("persistent") + if origPersistent != nil { + t.Cleanup(func() { + _ = os.WriteFile("persistent", origPersistent, 0644) + }) + } + + session := &Session{Token: 12345} + catalog, err := common.LoadCurrencyWarsCatalog() + if err != nil { + t.Fatalf("load catalog: %v", err) + } + data, rev, err := readCurrencyWarsData(catalog) + if err != nil || data == nil { + data, err = newCurrencyWarsData(catalog) + if err != nil { + t.Fatalf("new data: %v", err) + } + rev = 0 + } + session.currencyWarsRevision = rev + if err := session.saveCurrencyWarsData(data, catalog); err != nil { + t.Fatalf("save initial data: %v", err) + } + + // 1. Enable Permanent Talent + permReq := &pb.GridFightPermanentTalentEnableCsReq{CKHCLKOALKE: 101} + permCmdID, ok := pb.CmdIDByName["GridFightPermanentTalentEnableCsReq"] + if !ok || typedHandlers[uint16(permCmdID)] == nil { + t.Fatal("handler GridFightPermanentTalentEnable not registered") + } + rawPerm, err := proto.Marshal(permReq) + if err != nil { + t.Fatalf("marshal permReq: %v", err) + } + if err := typedHandlers[uint16(permCmdID)](session, rawPerm); err != nil { + t.Fatalf("call GridFightPermanentTalentEnable: %v", err) + } + + saved, _, err := readCurrencyWarsData(catalog) + if err != nil || saved == nil { + t.Fatalf("read saved data: %v", err) + } + if saved.LFLCDNGALME == nil || saved.LFLCDNGALME.PKELAIPIHMF == nil || saved.LFLCDNGALME.PKELAIPIHMF.MHFHLBHFIBC == nil { + t.Fatal("permanent talent container missing") + } + found := false + for _, id := range saved.LFLCDNGALME.PKELAIPIHMF.MHFHLBHFIBC.BDGMNPIJDDL { + if id == 101 { + found = true + break + } + } + if !found { + t.Errorf("permanent talent 101 was not persisted: %v", saved.LFLCDNGALME.PKELAIPIHMF.MHFHLBHFIBC.BDGMNPIJDDL) + } + + // 2. Enable Season Talent + seasonReq := &pb.GridFightSeasonTalentEnableCsReq{CKHCLKOALKE: 202} + seasonCmdID, ok := pb.CmdIDByName["GridFightSeasonTalentEnableCsReq"] + if !ok || typedHandlers[uint16(seasonCmdID)] == nil { + t.Fatal("handler GridFightSeasonTalentEnable not registered") + } + rawSeason, err := proto.Marshal(seasonReq) + if err != nil { + t.Fatalf("marshal seasonReq: %v", err) + } + if err := typedHandlers[uint16(seasonCmdID)](session, rawSeason); err != nil { + t.Fatalf("call GridFightSeasonTalentEnable: %v", err) + } + + saved, _, _ = readCurrencyWarsData(catalog) + if saved.LFLCDNGALME == nil || saved.LFLCDNGALME.HFPEJIDDDJG == nil || saved.LFLCDNGALME.HFPEJIDDDJG.MHFHLBHFIBC == nil { + t.Fatal("season talent container missing") + } + foundSeason := false + for _, id := range saved.LFLCDNGALME.HFPEJIDDDJG.MHFHLBHFIBC.BDGMNPIJDDL { + if id == 202 { + foundSeason = true + break + } + } + if !foundSeason { + t.Errorf("season talent 202 was not persisted: %v", saved.LFLCDNGALME.HFPEJIDDDJG.MHFHLBHFIBC.BDGMNPIJDDL) + } + + // 3. Reset Talents + resetCmdID, ok := pb.CmdIDByName["GridFightPermanentTalentResetCsReq"] + if ok && typedHandlers[uint16(resetCmdID)] != nil { + rawReset, _ := proto.Marshal(&pb.GridFightPermanentTalentResetCsReq{}) + _ = typedHandlers[uint16(resetCmdID)](session, rawReset) + } + saved, _, _ = readCurrencyWarsData(catalog) + if len(saved.LFLCDNGALME.PKELAIPIHMF.MHFHLBHFIBC.BDGMNPIJDDL) != 0 { + t.Errorf("expected permanent talents to be empty after reset, got %v", saved.LFLCDNGALME.PKELAIPIHMF.MHFHLBHFIBC.BDGMNPIJDDL) + } +} + +func TestCurrencyWarsArchiveHandlers(t *testing.T) { + record := &pb.OEDHMAHDECC{ + Reason: pb.PEIJNEFOCHI_PEIJNEFOCHI_JOJPGBCECLN, + IsOverlock: false, + } + entry := cwRecordArchive(record, 2, 1) + if entry == nil || entry.BAAGDILLLME == 0 { + t.Fatal("archive record failed") + } + + session := &Session{Token: 54321} + + // Toggle Favour + favourReq := &pb.FavourArchiveCsReq{BAAGDILLLME: entry.BAAGDILLLME, IECKKCHJMLP: true} + favourCmdID, ok := pb.CmdIDByName["FavourArchiveCsReq"] + if !ok || typedHandlers[uint16(favourCmdID)] == nil { + t.Fatal("FavourArchive handler missing") + } + rawFavour, err := proto.Marshal(favourReq) + if err != nil { + t.Fatalf("marshal favourReq: %v", err) + } + if err := typedHandlers[uint16(favourCmdID)](session, rawFavour); err != nil { + t.Fatalf("call FavourArchive: %v", err) + } + + foundFavour := false + globalCWArchive.mu.RLock() + for _, e := range globalCWArchive.entries { + if e.BAAGDILLLME == entry.BAAGDILLLME && e.IECKKCHJMLP { + foundFavour = true + break + } + } + globalCWArchive.mu.RUnlock() + if !foundFavour { + t.Errorf("expected archive %d to be favoured", entry.BAAGDILLLME) + } + + // Get Archive + getArchiveCmdID, ok := pb.CmdIDByName["GridFightGetArchiveCsReq"] + if !ok || typedHandlers[uint16(getArchiveCmdID)] == nil { + t.Fatal("GridFightGetArchive handler missing") + } + rawGetArchive, _ := proto.Marshal(&pb.GridFightGetArchiveCsReq{Type: pb.MLJIILDNDPL_MLJIILDNDPL_FAGECPGMPEH}) + if err := typedHandlers[uint16(getArchiveCmdID)](session, rawGetArchive); err != nil { + t.Fatalf("call GridFightGetArchive: %v", err) + } +} + +func TestCurrencyWarsCalculateBoardTraits(t *testing.T) { + catalog, err := common.LoadCurrencyWarsCatalog() + if err != nil { + t.Fatalf("load catalog: %v", err) + } + // Role 100402 (Welt) in Season 2 has TraitList: [ 100502, 200302 ] + run := &pb.MPIMAEMCEFH{ + NAGDJGMMCBE: []*pb.GridFightGameInfo{ + { + LEOIAOFBBFA: &pb.GridFightGameInfo_EJIPINIKNCK{ + EJIPINIKNCK: &pb.HKFECLCHBFJ{ + GridGameRoleList: []*pb.CDEAABODKCP{ + {Id: 100402, RoleStar: 1, Pos: 1, UniqueId: 101}, + {Id: 100402, RoleStar: 1, Pos: 2, UniqueId: 102}, + }, + }, + }, + }, + }, + } + traits := cwCalculateBoardTraits(catalog, run) + if len(traits) == 0 { + t.Fatal("expected computed traits for deployed roles") + } + foundTrait := false + for _, tr := range traits { + if tr.TraitId == 100502 || tr.TraitId == 200302 { + foundTrait = true + break + } + } + if !foundTrait { + t.Errorf("expected Welt traits to be computed, got %v", traits) + } +} + +func TestCurrencyWarsOrbDropsAndClaim(t *testing.T) { + origPersistent, _ := os.ReadFile("persistent") + if origPersistent != nil { + t.Cleanup(func() { + _ = os.WriteFile("persistent", origPersistent, 0644) + }) + } + + catalog, err := common.LoadCurrencyWarsCatalog() + if err != nil { + t.Fatalf("load catalog: %v", err) + } + + // 1. Test cwRollNodeOrbs for various stages in Route 2801 + // 1-1: 900 -> 4 rolls + orbs1_1, err := cwRollNodeOrbs(catalog, nil, 2801, 1, 1, 100) + if err != nil { + t.Fatalf("cwRollNodeOrbs 1-1: %v", err) + } + if len(orbs1_1) != 4 { + t.Errorf("expected 4 orbs from 1-1, got %d", len(orbs1_1)) + } + + // Progress 50% on 1-1: (4*50 + 99)/100 = 2 orbs + orbs1_1_half, err := cwRollNodeOrbs(catalog, nil, 2801, 1, 1, 50) + if err != nil { + t.Fatalf("cwRollNodeOrbs 1-1 half: %v", err) + } + if len(orbs1_1_half) != 2 { + t.Errorf("expected 2 orbs from 1-1 at 50%% progress, got %d", len(orbs1_1_half)) + } + + // 1-5 (monster 902 in route 2801): 8 rolls + orbs1_5, err := cwRollNodeOrbs(catalog, nil, 2801, 1, 5, 100) + if err != nil { + t.Fatalf("cwRollNodeOrbs 1-5: %v", err) + } + if len(orbs1_5) != 8 { + t.Errorf("expected 8 orbs from 1-5, got %d", len(orbs1_5)) + } + + // 2-4 (monster 903): 10 rolls + orbs2_4, err := cwRollNodeOrbs(catalog, nil, 2801, 2, 4, 100) + if err != nil { + t.Fatalf("cwRollNodeOrbs 2-4: %v", err) + } + if len(orbs2_4) != 10 { + t.Errorf("expected 10 orbs from 2-4, got %d", len(orbs2_4)) + } + + // 3-4 (monster 904): 6 rolls + orbs3_4, err := cwRollNodeOrbs(catalog, nil, 2801, 3, 4, 100) + if err != nil { + t.Fatalf("cwRollNodeOrbs 3-4: %v", err) + } + if len(orbs3_4) != 6 { + t.Errorf("expected 6 orbs from 3-4, got %d", len(orbs3_4)) + } + + // Non-combat / Supply node (1-3): 0 orbs + orbs1_3, err := cwRollNodeOrbs(catalog, nil, 2801, 1, 3, 100) + if err != nil { + t.Fatalf("cwRollNodeOrbs 1-3: %v", err) + } + if len(orbs1_3) != 0 { + t.Errorf("expected 0 orbs from supply node 1-3, got %d", len(orbs1_3)) + } + + // 2. Test cwUseOrbs with Orb 305 and multiple orbs + session := &Session{Token: 99887} + orbBag := &pb.DBONCGFHHDN{ + MDKGCIKOMLI: []*pb.IBPDPCOGDEP{ + {UniqueId: 5001, LDBNOMPCOKK: 102}, + {UniqueId: 5002, LDBNOMPCOKK: 103}, + {UniqueId: 5003, LDBNOMPCOKK: 305}, + }, + } + data, err := newCurrencyWarsData(catalog) + if err != nil { + t.Fatalf("new data: %v", err) + } + testRun := &pb.MPIMAEMCEFH{ + UniqueId: 99887, + LJNGPCKPJLF: catalog.ActiveSeasonID, + DivisionId: data.LFLCDNGALME.HFPEJIDDDJG.DivisionId, + NAGDJGMMCBE: []*pb.GridFightGameInfo{ + { + LEOIAOFBBFA: &pb.GridFightGameInfo_KLCPAAHDPJF{ + KLCPAAHDPJF: &pb.BEEFLEJAOCN{ + DCAHJELNJNP: 1, + SectionId: 1, + EMOHLJHPOPC: 2801, + }, + }, + }, + { + LEOIAOFBBFA: &pb.GridFightGameInfo_FAOCFBOFCJP{ + FAOCFBOFCJP: &pb.NIHNGPKHEPC{ + Gold: 100, + MICEOJBAKGN: 4, + }, + }, + }, + { + LEOIAOFBBFA: &pb.GridFightGameInfo_EJIPINIKNCK{ + EJIPINIKNCK: &pb.HKFECLCHBFJ{}, + }, + }, + { + LEOIAOFBBFA: &pb.GridFightGameInfo_GCKLIONKMJC{ + GCKLIONKMJC: &pb.CONONEALNOF{}, + }, + }, + { + LEOIAOFBBFA: &pb.GridFightGameInfo_EBHDIOGALIE{ + EBHDIOGALIE: &pb.POCJDMMANBM{}, + }, + }, + { + LEOIAOFBBFA: &pb.GridFightGameInfo_DBAGKJNPDOL{ + DBAGKJNPDOL: orbBag, + }, + }, + }, + } + data.DHFOFLFCLAH = testRun + _, rev, _ := readCurrencyWarsData(catalog) + session.currencyWarsRevision = rev + if err := session.saveCurrencyWarsData(data, catalog); err != nil { + t.Fatalf("save initial data with orbs: %v", err) + } + + // Claim all orbs + useReq := &pb.IKMOCJBMGOJ{IsGetAll: true} + if err := cwUseOrbs(session, useReq); err != nil { + t.Fatalf("cwUseOrbs with IsGetAll: %v", err) + } + + // Verify bag in saved data is now empty + saved, _, err := readCurrencyWarsData(catalog) + if err != nil || saved == nil || saved.DHFOFLFCLAH == nil { + t.Fatalf("read saved data: %v", err) + } + var savedBag *pb.DBONCGFHHDN + for _, c := range saved.DHFOFLFCLAH.NAGDJGMMCBE { + if b := c.GetDBAGKJNPDOL(); b != nil { + savedBag = b + break + } + } + if savedBag != nil && len(savedBag.MDKGCIKOMLI) != 0 { + t.Errorf("expected 0 orbs left in bag, got %d", len(savedBag.MDKGCIKOMLI)) + } + + // 3. Test idempotency: clicking already consumed or ghost orbs must NOT return error (avoids 6900 error toast) + ghostReq := &pb.IKMOCJBMGOJ{CBMGDBJHLHO: []uint32{5001, 5002, 5003}} + if err := cwUseOrbs(session, ghostReq); err != nil { + t.Fatalf("cwUseOrbs on already consumed orbs returned error: %v", err) + } + + // Calling IsGetAll when bag is already empty must also succeed cleanly + if err := cwUseOrbs(session, &pb.IKMOCJBMGOJ{IsGetAll: true}); err != nil { + t.Fatalf("cwUseOrbs with IsGetAll on empty bag returned error: %v", err) + } +} + +func TestCurrencyWarsTerminalSettlementLogic(t *testing.T) { + catalog, err := common.LoadCurrencyWarsCatalog() + if err != nil { + t.Fatalf("load catalog: %v", err) + } + run := &pb.MPIMAEMCEFH{ + UniqueId: 999, + LJNGPCKPJLF: catalog.ActiveSeasonID, + DivisionId: 1, + NAGDJGMMCBE: []*pb.GridFightGameInfo{ + { + LEOIAOFBBFA: &pb.GridFightGameInfo_KLCPAAHDPJF{ + KLCPAAHDPJF: &pb.BEEFLEJAOCN{ + DCAHJELNJNP: 3, + SectionId: 5, + EMOHLJHPOPC: 2801, + }, + }, + }, + { + LEOIAOFBBFA: &pb.GridFightGameInfo_FAOCFBOFCJP{ + FAOCFBOFCJP: &pb.NIHNGPKHEPC{ + Gold: 500, + GridFightLineupHp: 100, + PPKKHHNEOHB: 100, + GDAKCOICIDA: 7, + }, + }, + }, + { + LEOIAOFBBFA: &pb.GridFightGameInfo_EJIPINIKNCK{ + EJIPINIKNCK: &pb.HKFECLCHBFJ{ + GridGameRoleList: []*pb.CDEAABODKCP{ + {Id: 1001, UniqueId: 1, Pos: 1, RoleStar: 3}, + }, + }, + }, + }, + }, + } + keepWin, settle, err := currencyWarsTerminalSettlement(catalog, nil, run, pb.PEIJNEFOCHI_PEIJNEFOCHI_JOJPGBCECLN, true, nil) + if err != nil { + t.Fatalf("currencyWarsTerminalSettlement failed: %v", err) + } + if keepWin == nil || settle == nil { + t.Fatal("expected non-nil keepWin and settle notifications") + } + if settle.BFLLJJBHLDF == nil || settle.BFLLJJBHLDF.Reason != pb.PEIJNEFOCHI_PEIJNEFOCHI_JOJPGBCECLN { + t.Errorf("expected victory reason in settlement, got %v", settle.BFLLJJBHLDF) + } + if settle.BFLLJJBHLDF.NDLIKNEKDLL.DCAHJELNJNP != 3 || settle.BFLLJJBHLDF.NDLIKNEKDLL.SectionId != 5 { + t.Errorf("expected 3-5 stage in settlement, got %d-%d", settle.BFLLJJBHLDF.NDLIKNEKDLL.DCAHJELNJNP, settle.BFLLJJBHLDF.NDLIKNEKDLL.SectionId) + } +} + +func TestCurrencyWarsBonfireSelectHeal(t *testing.T) { + origPersistent, _ := os.ReadFile("persistent") + if origPersistent != nil { + t.Cleanup(func() { + _ = os.WriteFile("persistent", origPersistent, 0644) + }) + } + catalog, err := common.LoadCurrencyWarsCatalog() + if err != nil { + t.Fatalf("load catalog: %v", err) + } + offer := &pb.NCEPOLNLNKH{ + Phase: cwBonfireChoosing, + EIFICNFGGHI: 10, + AHJJJNBOMPG: 2, + DANCNIMJOLE: 3, + KFNMIEMBJGD: []*pb.MHCOIBONODA{ + {EventId: 10200601, HAFOKMHCGFM: 1}, + }, + } + run := &pb.MPIMAEMCEFH{ + UniqueId: 101, + LJNGPCKPJLF: catalog.ActiveSeasonID, + DivisionId: 1, + FMIGOKIDKJL: &pb.CHCHAJIANIM{ + QueuePosition: 795, + FGLIODAJFML: &pb.CHCHAJIANIM_IIDBLCFAAPC{ + IIDBLCFAAPC: offer, + }, + }, + NAGDJGMMCBE: []*pb.GridFightGameInfo{ + { + LEOIAOFBBFA: &pb.GridFightGameInfo_KLCPAAHDPJF{ + KLCPAAHDPJF: &pb.BEEFLEJAOCN{ + DCAHJELNJNP: 1, + SectionId: 5, + EMOHLJHPOPC: 2801, + BossInfo: &pb.KGMCLFHAJNJ{NCGHKPEMPDP: []*pb.AHKDPCIIELA{{MonsterId: 1004016, RoleStar: 1, LOGHLGKIFEN: 1}}}, + HEKMEJLMGFF: []*pb.LHNCMINFONJ{{LOGHLGKIFEN: 1}}, + OOMMIIKPHBC: []*pb.BCCIJHPLJOJ{ + {DCAHJELNJNP: 1, SectionId: 6, DisplayValue: &pb.OHMDHKGAEMM{StageId: 70000009}}, + }, + }, + }, + }, + { + LEOIAOFBBFA: &pb.GridFightGameInfo_FAOCFBOFCJP{ + FAOCFBOFCJP: &pb.NIHNGPKHEPC{ + Gold: 50, + GridFightLineupHp: 60, + PPKKHHNEOHB: 100, + EHNJDIDABHD: 1, + NDAINFLOLPJ: 10, + }, + }, + }, + { + LEOIAOFBBFA: &pb.GridFightGameInfo_EJIPINIKNCK{ + EJIPINIKNCK: &pb.HKFECLCHBFJ{}, + }, + }, + { + LEOIAOFBBFA: &pb.GridFightGameInfo_EBHDIOGALIE{ + EBHDIOGALIE: &pb.POCJDMMANBM{}, + }, + }, + }, + } + // 1. Test Select Heal HP + healReq := &pb.CAMIICJDOBO{ + FFBBOFAGGDI: &pb.CAMIICJDOBO_NCGOOEMOBIC{ + NCGOOEMOBIC: &pb.MLAENDOFBCK{ + FFBBOFAGGDI: &pb.MLAENDOFBCK_LGAJNMNOAEM{ + LGAJNMNOAEM: true, + }, + }, + }, + } + notify, _, err := cwSelectBonfire(catalog, run, healReq) + if err != nil { + t.Fatalf("cwSelectBonfire heal failed: %v", err) + } + if notify == nil { + t.Fatal("expected notify from heal") + } + if offer.Phase != cwBonfireSelected { + t.Errorf("expected phase %v, got %v", cwBonfireSelected, offer.Phase) + } + player, _, _ := cwComponents(run) + if player.GridFightLineupHp != 80 { + t.Errorf("expected 80 HP after heal, got %d", player.GridFightLineupHp) + } + + // 2. Test Take Bonfire rewards and advance node + takeReq := &pb.CAMIICJDOBO{ + FFBBOFAGGDI: &pb.CAMIICJDOBO_ECDACDNDHGO{ + ECDACDNDHGO: &pb.EFIEAIJBHIB{}, + }, + } + takeNotify, _, err := cwSelectBonfire(catalog, run, takeReq) + if err != nil { + t.Fatalf("cwSelectBonfire take failed: %v", err) + } + if takeNotify == nil { + t.Fatal("expected notify from take") + } + stage, _ := cwStage(run) + if stage.SectionId != 6 { + t.Errorf("expected node advance to section 6, got %d", stage.SectionId) + } +} + +func TestPrintSaveRoles(t *testing.T) { + catalog, err := common.LoadCurrencyWarsCatalog() + if err != nil { + t.Fatalf("catalog: %v", err) + } + data, _, err := readCurrencyWarsData(catalog) + if err != nil || data == nil || data.DHFOFLFCLAH == nil { + t.Skip("no active run in persistent snapshot") + } + run := data.DHFOFLFCLAH + _, team, _ := cwComponents(run) + if team == nil { + t.Fatal("team nil") + } + for _, r := range team.GridGameRoleList { + v, _ := catalog.CapturedRoleVariant(r.Id, r.RoleStar) + t.Logf("Role ID=%d Star=%d Pos=%d AvatarID=%d BABNHPDGNGB=%v Props=%v ValInit=%v", r.Id, r.RoleStar, r.Pos, v.Config.AvatarID, r.BABNHPDGNGB, r.ConvertPropertyToFixpoint, r.GridFightValueInitComponent) + } + runtime, _ := cwBonfireRuntime(catalog, run) + if runtime != nil { + t.Logf("Runtime rules: %v", runtime.Rules) + } +} + +func TestCurrencyWarsGrantRandomOrb(t *testing.T) { + catalog, err := common.LoadCurrencyWarsCatalog() + if err != nil { + t.Fatalf("load catalog: %v", err) + } + run := &pb.MPIMAEMCEFH{ + UniqueId: 999, + NAGDJGMMCBE: []*pb.GridFightGameInfo{ + { + LEOIAOFBBFA: &pb.GridFightGameInfo_DBAGKJNPDOL{ + DBAGKJNPDOL: &pb.DBONCGFHHDN{}, + }, + }, + { + LEOIAOFBBFA: &pb.GridFightGameInfo_FAOCFBOFCJP{ + FAOCFBOFCJP: &pb.NIHNGPKHEPC{ + Gold: 100, + }, + }, + }, + }, + } + cmd := common.CurrencyWarsCommand{ + Field: "orb", + Value: 0, + Quantity: 3, + } + notify, err := cwApplyCommand(catalog, run, cmd) + if err != nil { + t.Fatalf("cwApplyCommand random orb: %v", err) + } + if notify == nil || len(notify.GCPNIHHBGGD) == 0 { + t.Fatal("expected notify with update bundle") + } + if len(notify.GCPNIHHBGGD[0].JKPJFKCBIEM) != 3 { + t.Fatalf("expected 3 notify items, got %d", len(notify.GCPNIHHBGGD[0].JKPJFKCBIEM)) + } + var bag *pb.DBONCGFHHDN + for _, c := range run.NAGDJGMMCBE { + if b := c.GetDBAGKJNPDOL(); b != nil { + bag = b + break + } + } + if bag == nil || len(bag.MDKGCIKOMLI) != 3 { + t.Fatalf("expected 3 orbs in bag, got %v", bag) + } + // Verify that all granted orbs have valid, implemented IDs + recipes, err := common.LoadCurrencyWarsOrbRecipes() + if err != nil { + t.Fatalf("load recipes: %v", err) + } + implemented := make(map[uint32]bool) + for _, r := range recipes.Recipes { + if r.Status == "Implemented" && r.Engine == "Orb" { + implemented[r.ID] = true + } + } + for i, orb := range bag.MDKGCIKOMLI { + if !implemented[orb.LDBNOMPCOKK] { + t.Errorf("orb[%d] id %d is not an implemented orb", i, orb.LDBNOMPCOKK) + } + } +} diff --git a/internal/gameserver/currency_wars_talent.go b/internal/gameserver/currency_wars_talent.go new file mode 100644 index 0000000..b45df40 --- /dev/null +++ b/internal/gameserver/currency_wars_talent.go @@ -0,0 +1,141 @@ +package gameserver + +import ( + "fmt" + "slices" + + "gac-hkrpg-go/internal/common" + pb "gac-hkrpg-go/protocol/proto" +) + +func init() { + cwHandle("GridFightPermanentTalentEnable", func(s *Session, req *pb.GridFightPermanentTalentEnableCsReq, rsp *pb.GridFightPermanentTalentEnableScRsp) error { + rsp.Retcode = uint32(pb.Retcode_RET_GRID_FIGHT_PARAM_NOT_MATCH) + if req == nil || req.CKHCLKOALKE == 0 { + return nil + } + catalog, err := common.LoadCurrencyWarsCatalog() + if err != nil { + return err + } + data, revision, err := readCurrencyWarsData(catalog) + if err != nil { + return err + } + if data == nil { + data, err = newCurrencyWarsData(catalog) + if err != nil { + return err + } + } + s.currencyWarsRevision = revision + if data.LFLCDNGALME == nil { + data.LFLCDNGALME = &pb.CLEDCMIOIPF{} + } + if data.LFLCDNGALME.PKELAIPIHMF == nil { + data.LFLCDNGALME.PKELAIPIHMF = &pb.CKPOBLIFMGN{} + } + if data.LFLCDNGALME.PKELAIPIHMF.MHFHLBHFIBC == nil { + data.LFLCDNGALME.PKELAIPIHMF.MHFHLBHFIBC = &pb.FMNKKAMOHEE{} + } + list := &data.LFLCDNGALME.PKELAIPIHMF.MHFHLBHFIBC.BDGMNPIJDDL + if !slices.Contains(*list, req.CKHCLKOALKE) { + *list = append(*list, req.CKHCLKOALKE) + } + if err = s.saveCurrencyWarsData(data, catalog); err != nil { + rsp.Retcode = uint32(pb.Retcode_RET_GRID_FIGHT_CONF_MISS) + return nil + } + rsp.CKHCLKOALKE = req.CKHCLKOALKE + rsp.Retcode = 0 + PacketLog(fmt.Sprintf("CW_PERMANENT_TALENT_ENABLE node=%d total=%d", req.CKHCLKOALKE, len(*list))) + return nil + }) + + cwHandle("GridFightSeasonTalentEnable", func(s *Session, req *pb.GridFightSeasonTalentEnableCsReq, rsp *pb.GridFightSeasonTalentEnableScRsp) error { + rsp.Retcode = uint32(pb.Retcode_RET_GRID_FIGHT_PARAM_NOT_MATCH) + if req == nil || req.CKHCLKOALKE == 0 { + return nil + } + catalog, err := common.LoadCurrencyWarsCatalog() + if err != nil { + return err + } + data, revision, err := readCurrencyWarsData(catalog) + if err != nil { + return err + } + if data == nil { + data, err = newCurrencyWarsData(catalog) + if err != nil { + return err + } + } + s.currencyWarsRevision = revision + if data.LFLCDNGALME == nil { + data.LFLCDNGALME = &pb.CLEDCMIOIPF{} + } + if data.LFLCDNGALME.HFPEJIDDDJG == nil { + data.LFLCDNGALME.HFPEJIDDDJG = &pb.AMEEHIIPMFP{} + } + if data.LFLCDNGALME.HFPEJIDDDJG.MHFHLBHFIBC == nil { + data.LFLCDNGALME.HFPEJIDDDJG.MHFHLBHFIBC = &pb.FMNKKAMOHEE{} + } + list := &data.LFLCDNGALME.HFPEJIDDDJG.MHFHLBHFIBC.BDGMNPIJDDL + if !slices.Contains(*list, req.CKHCLKOALKE) { + *list = append(*list, req.CKHCLKOALKE) + } + if err = s.saveCurrencyWarsData(data, catalog); err != nil { + rsp.Retcode = uint32(pb.Retcode_RET_GRID_FIGHT_CONF_MISS) + return nil + } + rsp.CKHCLKOALKE = req.CKHCLKOALKE + rsp.Retcode = 0 + PacketLog(fmt.Sprintf("CW_SEASON_TALENT_ENABLE node=%d total=%d", req.CKHCLKOALKE, len(*list))) + return nil + }) + + cwHandle("GridFightPermanentTalentReset", func(s *Session, _ *pb.GridFightPermanentTalentResetCsReq, rsp *pb.GridFightPermanentTalentResetScRsp) error { + catalog, err := common.LoadCurrencyWarsCatalog() + if err != nil { + return err + } + data, revision, err := readCurrencyWarsData(catalog) + if err != nil { + return err + } + s.currencyWarsRevision = revision + if data != nil && data.LFLCDNGALME != nil && data.LFLCDNGALME.PKELAIPIHMF != nil && data.LFLCDNGALME.PKELAIPIHMF.MHFHLBHFIBC != nil { + data.LFLCDNGALME.PKELAIPIHMF.MHFHLBHFIBC.BDGMNPIJDDL = nil + if err = s.saveCurrencyWarsData(data, catalog); err != nil { + rsp.Retcode = uint32(pb.Retcode_RET_GRID_FIGHT_CONF_MISS) + return nil + } + } + rsp.Retcode = 0 + PacketLog("CW_PERMANENT_TALENT_RESET completed") + return nil + }) + + cwHandle("GridFightSeasonTalentReset", func(s *Session, _ *pb.GridFightSeasonTalentResetCsReq, rsp *pb.GridFightSeasonTalentResetScRsp) error { + catalog, err := common.LoadCurrencyWarsCatalog() + if err != nil { + return err + } + data, revision, err := readCurrencyWarsData(catalog) + if err != nil { + return err + } + s.currencyWarsRevision = revision + if data != nil && data.LFLCDNGALME != nil && data.LFLCDNGALME.HFPEJIDDDJG != nil && data.LFLCDNGALME.HFPEJIDDDJG.MHFHLBHFIBC != nil { + data.LFLCDNGALME.HFPEJIDDDJG.MHFHLBHFIBC.BDGMNPIJDDL = nil + if err = s.saveCurrencyWarsData(data, catalog); err != nil { + rsp.Retcode = uint32(pb.Retcode_RET_GRID_FIGHT_CONF_MISS) + return nil + } + } + rsp.Retcode = 0 + PacketLog("CW_SEASON_TALENT_RESET completed") + return nil + }) +} diff --git a/internal/gameserver/currency_wars_test.go b/internal/gameserver/currency_wars_test.go index 2a5c3b6..b5abb12 100644 --- a/internal/gameserver/currency_wars_test.go +++ b/internal/gameserver/currency_wars_test.go @@ -157,3 +157,36 @@ func TestCurrencyWarsEntranceRejectsUnsupportedOrUnmetConditions(t *testing.T) { }) } } + +func TestInspectUserCurrentState(t *testing.T) { + catalog, err := common.LoadCurrencyWarsCatalog() + if err != nil { + t.Fatal(err) + } + saved, rev, err := readCurrencyWarsData(catalog) + if err != nil { + t.Fatal(err) + } + t.Logf("revision=%d", rev) + run := saved.DHFOFLFCLAH + if run == nil { + t.Skip("no active run in persistent snapshot") + } + stage, display := cwStage(run) + t.Logf("stage: %d/%d display: stage_id=%d type=%d", stage.DCAHJELNJNP, stage.SectionId, display.StageId, display.CJCCMAACJDB) + t.Logf("pending: %+v", run.FMIGOKIDKJL) + bag := cwEquipmentBag(run) + if bag != nil { + t.Logf("bag items (%d):", len(bag.GridFightEquipmentList)) + for _, eq := range bag.GridFightEquipmentList { + t.Logf(" eq uid=%d id=%d source=%d", eq.UniqueId, eq.GridFightEquipmentId, eq.Source) + } + } + _, team, _ := cwComponents(run) + if team != nil { + t.Logf("team roles (%d):", len(team.GridGameRoleList)) + for _, r := range team.GridGameRoleList { + t.Logf(" role uid=%d id=%d star=%d pos=%d eq=%v", r.UniqueId, r.Id, r.RoleStar, r.Pos, r.PNJIPHJJONG) + } + } +} diff --git a/internal/gameserver/currency_wars_tracking.go b/internal/gameserver/currency_wars_tracking.go index 0be9469..543d17b 100644 --- a/internal/gameserver/currency_wars_tracking.go +++ b/internal/gameserver/currency_wars_tracking.go @@ -11,6 +11,27 @@ func cwEquipTrackSync(player *pb.NIHNGPKHEPC) *pb.GridFightSyncUpdateResultScNot return cwNotify(&pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_BMGFDIKDOBE{BMGFDIKDOBE: &pb.MDKHMNKAECP{MIIDIMOCPEB: player.MIIDIMOCPEB}}}) } +func cwTraitTrackSync(player *pb.NIHNGPKHEPC) *pb.GridFightSyncUpdateResultScNotify { + return cwNotify(&pb.KFNCAIHIHCM{ + MNLCHEJKDHO: &pb.KFNCAIHIHCM_HGNDMOODNKF{ + HGNDMOODNKF: &pb.FBCIJONDIJE{ + ONHDKLIMLNM: append([]uint32(nil), player.ONHDKLIMLNM...), + GHJLDDEDJLI: true, + }, + }, + }) +} + +func cwSpotLightTrackSync(spot *pb.GNOBLNOCEHA) *pb.GridFightSyncUpdateResultScNotify { + return cwNotify(&pb.KFNCAIHIHCM{ + MNLCHEJKDHO: &pb.KFNCAIHIHCM_POLBMPCKFDO{ + POLBMPCKFDO: &pb.FFECECBAEEH{ + DKEIDONNDCJ: append([]*pb.OGEIHMPCFKA(nil), spot.DKEIDONNDCJ...), + }, + }, + }) +} + // This Client's CreateServerTrackUpdateInfo takes (roleID, enabled, desired, // equipped, priority, source). AddTrackToLastAndSend passes true; RemoveEquipTrack // passes false with empty lists and source None. The boolean is NOT a delete flag. @@ -117,4 +138,93 @@ func init() { rsp.Retcode = 0 return nil }) + cwHandleNamed("MCDBJHOCGHI", "IIFPPBCFBBC", func(s *Session, req *pb.MCDBJHOCGHI, rsp *pb.IIFPPBCFBBC) error { + rsp.Retcode = uint32(pb.Retcode_RET_GRID_FIGHT_PARAM_NOT_MATCH) + if req == nil || req.TraitId == 0 { + return nil + } + catalog, data, err := cwPreparingData(s) + if err != nil { + return nil + } + player, _, _ := cwComponents(data.DHFOFLFCLAH) + if player == nil { + return nil + } + traitsCatalog, err := common.LoadCurrencyWarsTraits(catalog) + if err != nil { + PacketLog("CW_TRAIT_TRACK rejected: " + err.Error()) + return nil + } + if _, ok := traitsCatalog.Trait(req.TraitId); !ok { + PacketLog(fmt.Sprintf("CW_TRAIT_TRACK unknown trait=%d", req.TraitId)) + return nil + } + var updated []uint32 + found := false + for _, id := range player.ONHDKLIMLNM { + if id == req.TraitId { + found = true + if req.BJDOINAHDLN { + updated = append(updated, id) + } + } else { + updated = append(updated, id) + } + } + if req.BJDOINAHDLN && !found { + updated = append(updated, req.TraitId) + } + player.ONHDKLIMLNM = updated + if err = s.saveCurrencyWarsData(data, catalog); err != nil { + return nil + } + if err = cwSend(s, cwTraitTrackSync(player)); err != nil { + return err + } + rsp.Retcode = 0 + PacketLog(fmt.Sprintf("CW_TRAIT_TRACK trait=%d enabled=%t total=%d revision=%d", req.TraitId, req.BJDOINAHDLN, len(player.ONHDKLIMLNM), s.currencyWarsRevision)) + return nil + }) + cwHandle("GridFightUpdateSpotLightTrack", func(s *Session, req *pb.GridFightUpdateSpotLightTrackCsReq, rsp *pb.GridFightUpdateSpotLightTrackScRsp) error { + rsp.Retcode = uint32(pb.Retcode_RET_GRID_FIGHT_PARAM_NOT_MATCH) + if req == nil { + return nil + } + catalog, data, err := cwPreparingData(s) + if err != nil { + return nil + } + cwEnsureSpotlights(data.DHFOFLFCLAH) + spotlights := cwSpotlights(data.DHFOFLFCLAH) + entries := append([]*pb.OGEIHMPCFKA(nil), spotlights.DKEIDONNDCJ...) + for _, update := range req.ACCFJKBOJIJ { + if update == nil || update.LDLDLHBCFCF == nil || update.LDLDLHBCFCF.RoleId == 0 { + continue + } + track := update.LDLDLHBCFCF + kept := make([]*pb.OGEIHMPCFKA, 0, len(entries)+1) + for _, old := range entries { + if old.RoleId == track.RoleId { + continue + } + kept = append(kept, old) + } + if update.BJDOINAHDLN && len(track.DKEIDONNDCJ) > 0 { + kept = append(kept, track) + } + entries = kept + } + sort.SliceStable(entries, func(i, j int) bool { return entries[i].NALMBOOCCIN < entries[j].NALMBOOCCIN }) + spotlights.DKEIDONNDCJ = entries + if err = s.saveCurrencyWarsData(data, catalog); err != nil { + return nil + } + if err = cwSend(s, cwSpotLightTrackSync(spotlights)); err != nil { + return err + } + rsp.Retcode = 0 + PacketLog(fmt.Sprintf("CW_SPOTLIGHT_TRACK saved updates=%d tracks=%d revision=%d", len(req.ACCFJKBOJIJ), len(entries), s.currencyWarsRevision)) + return nil + }) } diff --git a/internal/gameserver/currency_wars_tracking_test.go b/internal/gameserver/currency_wars_tracking_test.go new file mode 100644 index 0000000..b23edd4 --- /dev/null +++ b/internal/gameserver/currency_wars_tracking_test.go @@ -0,0 +1,188 @@ +package gameserver + +import ( + "testing" + + "gac-hkrpg-go/internal/common" + pb "gac-hkrpg-go/protocol/proto" + "google.golang.org/protobuf/proto" +) + +func TestCurrencyWarsTraitAndSpotlightTracking(t *testing.T) { + catalog, err := common.LoadCurrencyWarsCatalog() + if err != nil { + t.Fatalf("load catalog: %v", err) + } + + data, rev, err := readCurrencyWarsData(catalog) + if err != nil || data == nil || data.DHFOFLFCLAH == nil { + t.Skip("no active run in persistent snapshot") + } + data.DHFOFLFCLAH.FMIGOKIDKJL = nil + + session := &Session{Token: 12345, currencyWarsRevision: rev} + if err := session.saveCurrencyWarsData(data, catalog); err != nil { + t.Fatalf("save initial data: %v", err) + } + + // 1. Verify handler registrations + traitReqCmdID, ok := pb.CmdIDByName["MCDBJHOCGHI"] + if !ok || typedHandlers[uint16(traitReqCmdID)] == nil { + t.Fatalf("handler MCDBJHOCGHI (CmdID %d) is not registered in typedHandlers", traitReqCmdID) + } + spotReqCmdID, ok := pb.CmdIDByName["GridFightUpdateSpotLightTrackCsReq"] + if !ok || typedHandlers[uint16(spotReqCmdID)] == nil { + t.Fatalf("handler GridFightUpdateSpotLightTrackCsReq (CmdID %d) is not registered in typedHandlers", spotReqCmdID) + } + + // 2. Test Trait Tracking (enable trait 1001) + traitReq := &pb.MCDBJHOCGHI{ + TraitId: 1001, + BJDOINAHDLN: true, + } + rawTrait, err := proto.Marshal(traitReq) + if err != nil { + t.Fatalf("marshal traitReq: %v", err) + } + + err = typedHandlers[uint16(traitReqCmdID)](session, rawTrait) + if err != nil { + t.Fatalf("typedHandler MCDBJHOCGHI returned error: %v", err) + } + + saved, _, err := readCurrencyWarsData(catalog) + if err != nil { + t.Fatalf("read saved: %v", err) + } + player, _, _ := cwComponents(saved.DHFOFLFCLAH) + foundTrait := false + for _, id := range player.ONHDKLIMLNM { + if id == 1001 { + foundTrait = true + break + } + } + if !foundTrait { + t.Fatalf("expected trait 1001 in player.ONHDKLIMLNM, got %v", player.ONHDKLIMLNM) + } + + // 3. Test Trait Tracking Disable (disable trait 1001) + traitReq.BJDOINAHDLN = false + rawTrait, _ = proto.Marshal(traitReq) + err = typedHandlers[uint16(traitReqCmdID)](session, rawTrait) + if err != nil { + t.Fatalf("disable MCDBJHOCGHI error: %v", err) + } + saved, _, _ = readCurrencyWarsData(catalog) + player, _, _ = cwComponents(saved.DHFOFLFCLAH) + for _, id := range player.ONHDKLIMLNM { + if id == 1001 { + t.Fatalf("trait 1001 was not removed from player.ONHDKLIMLNM: %v", player.ONHDKLIMLNM) + } + } + + // 4. Test SpotLight Tracking (enable spotlight track for role 1001) + spotReq := &pb.GridFightUpdateSpotLightTrackCsReq{ + Source: pb.DFNHMOGNJGJ_DFNHMOGNJGJ_HPKIIGODDFO, + ACCFJKBOJIJ: []*pb.BMHEAGKIILO{ + { + LDLDLHBCFCF: &pb.OGEIHMPCFKA{ + RoleId: 1001, + NALMBOOCCIN: 1, + DKEIDONNDCJ: []*pb.NAODPHAPKOA{ + { + Source: pb.DFNHMOGNJGJ_DFNHMOGNJGJ_HPKIIGODDFO, + JHGCJKCHNCO: []uint32{101}, + }, + }, + }, + BJDOINAHDLN: true, + }, + }, + } + rawSpot, err := proto.Marshal(spotReq) + if err != nil { + t.Fatalf("marshal spotReq: %v", err) + } + + err = typedHandlers[uint16(spotReqCmdID)](session, rawSpot) + if err != nil { + t.Fatalf("typedHandler GridFightUpdateSpotLightTrack returned error: %v", err) + } + + saved, _, _ = readCurrencyWarsData(catalog) + spotlights := cwSpotlights(saved.DHFOFLFCLAH) + if spotlights == nil || len(spotlights.DKEIDONNDCJ) == 0 { + t.Fatalf("expected spotlights.DKEIDONNDCJ to have tracks, got %v", spotlights) + } + if spotlights.DKEIDONNDCJ[0].RoleId != 1001 { + t.Fatalf("expected track for role 1001, got %d", spotlights.DKEIDONNDCJ[0].RoleId) + } + + // 5. Test SpotLight Tracking Disable + spotReq.ACCFJKBOJIJ[0].BJDOINAHDLN = false + rawSpot, _ = proto.Marshal(spotReq) + err = typedHandlers[uint16(spotReqCmdID)](session, rawSpot) + if err != nil { + t.Fatalf("disable spotlight track error: %v", err) + } + saved, _, _ = readCurrencyWarsData(catalog) + spotlights = cwSpotlights(saved.DHFOFLFCLAH) + if len(spotlights.DKEIDONNDCJ) != 0 { + t.Fatalf("expected 0 spotlight tracks, got %d", len(spotlights.DKEIDONNDCJ)) + } +} + +func TestCurrencyWarsBonfireReplayIdempotency(t *testing.T) { + catalog, err := common.LoadCurrencyWarsCatalog() + if err != nil { + t.Fatalf("load catalog: %v", err) + } + + data, _, err := readCurrencyWarsData(catalog) + if err != nil || data == nil || data.DHFOFLFCLAH == nil { + t.Skip("no active run in persistent snapshot") + } + + offer := &pb.NCEPOLNLNKH{ + Phase: cwBonfireSelected, + EIFICNFGGHI: 10, + AHJJJNBOMPG: 2, + DANCNIMJOLE: 0, + } + pending := &pb.CHCHAJIANIM{ + QueuePosition: 795, + FGLIODAJFML: &pb.CHCHAJIANIM_IIDBLCFAAPC{ + IIDBLCFAAPC: offer, + }, + } + data.DHFOFLFCLAH.FMIGOKIDKJL = pending + + session := &Session{Token: 12345, currencyWarsRevision: 0} + _ = session.saveCurrencyWarsData(data, catalog) + + req := &pb.GridFightHandleBonfireHealHpCsReq{ + QueuePosition: 795, + FGLIODAJFML: &pb.GridFightHandleBonfireHealHpCsReq_IIDBLCFAAPC{ + IIDBLCFAAPC: &pb.CAMIICJDOBO{ + FFBBOFAGGDI: &pb.CAMIICJDOBO_NCGOOEMOBIC{ + NCGOOEMOBIC: &pb.MLAENDOFBCK{ + FFBBOFAGGDI: &pb.MLAENDOFBCK_LGAJNMNOAEM{ + LGAJNMNOAEM: true, + }, + }, + }, + }, + }, + } + cmdID, ok := pb.CmdIDByName["GridFightHandleBonfireHealHpCsReq"] + if !ok || typedHandlers[uint16(cmdID)] == nil { + t.Fatalf("handler GridFightHandleBonfireHealHpCsReq not registered") + } + + raw, _ := proto.Marshal(req) + err = typedHandlers[uint16(cmdID)](session, raw) + if err != nil { + t.Fatalf("expected idempotent response without error, got: %v", err) + } +} diff --git a/internal/gameserver/currency_wars_trailblaze.go b/internal/gameserver/currency_wars_trailblaze.go new file mode 100644 index 0000000..730ffe0 --- /dev/null +++ b/internal/gameserver/currency_wars_trailblaze.go @@ -0,0 +1,477 @@ +package gameserver + +import ( + "fmt" + "maps" + "math/rand/v2" + "strconv" + "strings" + + "gac-hkrpg-go/internal/common" + pb "gac-hkrpg-go/protocol/proto" +) + +const ( + cwTrailblazeTrait uint32 = 100502 + cwTrailblazeEffect uint32 = 1005021 + cwTrailblazeModifierUID uint32 = 1005021 + cwTrailblazeSlots int = 4 +) + +// cwGetTrailblazeLayer returns the active layer (2, 4, 6, 8, 10 or 0) of the Trailblaze Silver Rail trait. +func cwGetTrailblazeLayer(catalog *common.CurrencyWarsCatalog, run *pb.MPIMAEMCEFH) uint32 { + traits := cwCalculateBoardTraits(catalog, run) + for _, t := range traits { + if t.TraitId == cwTrailblazeTrait { + return t.KOJFHFAKKFO + } + } + return 0 +} + +func cwTrailblazeActive(catalog *common.CurrencyWarsCatalog, run *pb.MPIMAEMCEFH, runtime *common.CurrencyWarsRuntimeState) bool { + if runtime != nil && (runtime.Rules["TbOpened"] != 0 || runtime.Rules["TbPower"] > 0) { + return true + } + return cwGetTrailblazeLayer(catalog, run) > 0 +} + +func tbOffered(runtime *common.CurrencyWarsRuntimeState, goodID uint32) bool { + if runtime == nil { + return false + } + for slot := 0; slot < cwTrailblazeSlots; slot++ { + if runtime.Rules[fmt.Sprintf("TbGood/%d", slot)] == goodID { + return true + } + } + return false +} + +func tbBoughtCount(runtime *common.CurrencyWarsRuntimeState, goodID uint32) uint32 { + if runtime == nil { + return 0 + } + return runtime.Rules[fmt.Sprintf("TbBought/%d", goodID)] +} + +func tbIsEligible(tbCat *common.CurrencyWarsTrailblazeCatalog, runtime *common.CurrencyWarsRuntimeState, row common.CurrencyWarsTrailblazeGood, tier uint32) bool { + if row.Kind == "refresh" || row.ID == tbCat.InitialGoodID { + return false + } + if row.ID/100 > tier+1 { + return false + } + if row.Father != 0 && tbBoughtCount(runtime, row.Father) == 0 { + return false + } + if row.Kind == "maze_buff" && tbBoughtCount(runtime, row.ID) != 0 { + return false + } + pomItem := row.Kind == "item" && len(row.Params) == 1 && (row.Params[0] == 353201 || row.Params[0] == 353202) + pomUnlocked := runtime != nil && runtime.Rules["TbCosted"] >= tbCat.PomThreshold + if (row.ID == tbCat.PomGoodID || pomItem) && !pomUnlocked { + return false + } + return !tbOffered(runtime, row.ID) +} + +func cwTrailblazeRNG(run *pb.MPIMAEMCEFH, runtime *common.CurrencyWarsRuntimeState) *rand.Rand { + var runID uint64 = 1 + var nodeKey uint64 = 0 + if run != nil { + runID = uint64(run.UniqueId) + if stage, _ := cwStage(run); stage != nil { + nodeKey = uint64(stage.DCAHJELNJNP)*10000 + uint64(stage.SectionId)*100 + } + } + var refresh uint64 = 0 + if runtime != nil && runtime.Rules != nil { + refresh = uint64(runtime.Rules["TbRefreshCount"]) + } + seed := runID*1000003 + nodeKey*31 + refresh*7 + 100502 + return rand.New(rand.NewPCG(seed, seed^0x5DEECE66D)) +} + +func cwTrailblazeRoll(tbCat *common.CurrencyWarsTrailblazeCatalog, run *pb.MPIMAEMCEFH, runtime *common.CurrencyWarsRuntimeState, tier uint32) { + if runtime == nil || runtime.Rules == nil { + return + } + rng := cwTrailblazeRNG(run, runtime) + for slot := 0; slot < cwTrailblazeSlots; slot++ { + runtime.Rules[fmt.Sprintf("TbGood/%d", slot)] = 0 + var eligible []common.CurrencyWarsTrailblazeGood + for _, row := range tbCat.GoodsList { + if tbIsEligible(tbCat, runtime, row, tier) { + eligible = append(eligible, row) + } + } + if len(eligible) == 0 { + continue + } + pick := eligible[rng.IntN(len(eligible))] + runtime.Rules[fmt.Sprintf("TbGood/%d", slot)] = pick.ID + } +} + +func cwTrailblazeNodeEntered(catalog *common.CurrencyWarsCatalog, run *pb.MPIMAEMCEFH, runtime *common.CurrencyWarsRuntimeState) { + layer := cwGetTrailblazeLayer(catalog, run) + if layer == 0 { + return + } + tbCat, err := common.LoadCurrencyWarsTrailblaze() + if err != nil { + return + } + tier := tbCat.Tier(layer) + if runtime.Rules == nil { + runtime.Rules = make(map[string]uint32) + } + stage, _ := cwStage(run) + var currentNode uint32 + if stage != nil { + currentNode = stage.DCAHJELNJNP*1000 + stage.SectionId + } + if runtime.Rules["TbOpened"] == 0 { + runtime.Rules["TbOpened"] = 1 + runtime.Rules["TbNode"] = currentNode + runtime.Rules["TbRefreshCount"] = 0 + cwTrailblazeRoll(tbCat, run, runtime, tier) + if tbBoughtCount(runtime, tbCat.InitialGoodID) == 0 { + runtime.Rules["TbGood/0"] = tbCat.InitialGoodID + } + } else if runtime.Rules["TbNode"] != currentNode { + runtime.Rules["TbNode"] = currentNode + runtime.Rules["TbRefreshCount"] = 0 + cwTrailblazeRoll(tbCat, run, runtime, tier) + if tbBoughtCount(runtime, tbCat.InitialGoodID) == 0 { + runtime.Rules["TbGood/0"] = tbCat.InitialGoodID + } + } +} + +func cwTrailblazeShopProto(tbCat *common.CurrencyWarsTrailblazeCatalog, runtime *common.CurrencyWarsRuntimeState) *pb.GGFHGKGJOOB { + shop := &pb.GGFHGKGJOOB{ + OBHKKOFDEFG: make(map[uint32]uint32), + } + if runtime != nil { + shop.MJIGJEGMCML = runtime.Rules["TbPower"] + for slot := 0; slot < cwTrailblazeSlots; slot++ { + gid := runtime.Rules[fmt.Sprintf("TbGood/%d", slot)] + if gid != 0 { + if good, ok := tbCat.Goods[gid]; ok { + shop.OGDKJPDFFAI = append(shop.OGDKJPDFFAI, &pb.AMNAMDLMADK{ + GoodsId: gid, + OICGFNGNLOE: good.Cost, + }) + } + } + } + for k, v := range runtime.Rules { + if strings.HasPrefix(k, "TbBought/") { + idStr := strings.TrimPrefix(k, "TbBought/") + if gid, err := strconv.ParseUint(idStr, 10, 32); err == nil && v > 0 { + shop.OBHKKOFDEFG[uint32(gid)] = v + } + } + } + } + shop.OGDKJPDFFAI = append(shop.OGDKJPDFFAI, &pb.AMNAMDLMADK{ + GoodsId: tbCat.RefreshGoodID, + OICGFNGNLOE: 1, + }) + return shop +} + +func cwTrailblazeModifierProto(tbCat *common.CurrencyWarsTrailblazeCatalog, runtime *common.CurrencyWarsRuntimeState) *pb.NJKFOJJDGGG { + costed := uint32(0) + if runtime != nil { + costed = runtime.Rules["TbCosted"] + } + return &pb.NJKFOJJDGGG{ + UniqueId: cwTrailblazeModifierUID, + FMEKLFFDPGG: &pb.NJKFOJJDGGG_ONKMJAGNECC{ + ONKMJAGNECC: &pb.CONKAFGLHFE{ + IANFMIJBKAF: costed, + PLAIPMBIHGJ: tbCat.PomThreshold, + }, + }, + } +} + +func cwTrailblazeTraitEffectProto(tbCat *common.CurrencyWarsTrailblazeCatalog, runtime *common.CurrencyWarsRuntimeState) *pb.MGBHKCMHDOK { + return &pb.MGBHKCMHDOK{ + EffectId: cwTrailblazeEffect, + NEBNJDMFELO: &pb.MGBHKCMHDOK_KDBKIPPHMFI{ + KDBKIPPHMFI: cwTrailblazeShopProto(tbCat, runtime), + }, + } +} + +type tbListedGood struct { + id uint32 + cost uint32 + slot int +} + +func cwTrailblazeBuy(catalog *common.CurrencyWarsCatalog, run *pb.MPIMAEMCEFH, runtime *common.CurrencyWarsRuntimeState, index uint32) (*pb.GridFightSyncUpdateResultScNotify, error) { + if !cwTrailblazeActive(catalog, run, runtime) { + return nil, fmt.Errorf("Trailblaze Silver Rail trait is inactive") + } + layer := cwGetTrailblazeLayer(catalog, run) + tbCat, err := common.LoadCurrencyWarsTrailblaze() + if err != nil { + return nil, err + } + if runtime == nil || runtime.Rules == nil { + return nil, fmt.Errorf("runtime rules missing") + } + if runtime.Rules["TbOpened"] == 0 { + cwTrailblazeNodeEntered(catalog, run, runtime) + } + + var listed []tbListedGood + for slot := 0; slot < cwTrailblazeSlots; slot++ { + gid := runtime.Rules[fmt.Sprintf("TbGood/%d", slot)] + if gid != 0 { + if good, ok := tbCat.Goods[gid]; ok { + listed = append(listed, tbListedGood{id: gid, cost: good.Cost, slot: slot}) + } + } + } + listed = append(listed, tbListedGood{id: tbCat.RefreshGoodID, cost: 1, slot: -1}) + + if int(index) >= len(listed) { + return nil, fmt.Errorf("goods index %d out of bounds (listed len %d)", index, len(listed)) + } + + target := listed[index] + good, ok := tbCat.Goods[target.id] + if !ok { + return nil, fmt.Errorf("goods %d definition missing", target.id) + } + + power := runtime.Rules["TbPower"] + if power < target.cost { + return nil, fmt.Errorf("insufficient Trailblaze Power: have %d need %d", power, target.cost) + } + + player, team, _ := cwComponents(run) + bag := cwEquipmentBag(run) + if player == nil || team == nil || bag == nil { + return nil, fmt.Errorf("CW components missing") + } + + var grantUpdates []*pb.KFNCAIHIHCM + + switch good.Kind { + case "role": + if len(good.Params) < 1 { + return nil, fmt.Errorf("role goods %d has empty params", good.ID) + } + roleID := good.Params[0] + star := uint32(1) + if len(good.Params) >= 2 && good.Params[1] > 0 { + star = good.Params[1] + } + front, e := catalog.IntegerRule("GridFight_Front_AvatarMaxNum") + if e != nil { + return nil, e + } + back, e := catalog.IntegerRule("GridFight_Back_AvatarMaxNum") + if e != nil { + return nil, e + } + overflow, e := catalog.IntegerRule("GridFight_Bench_OverFlow_AvatarNum") + if e != nil { + return nil, e + } + occupied := map[uint32]bool{} + for _, r := range team.GridGameRoleList { + occupied[r.Pos] = true + } + var pos uint32 + for p := uint64(front+back) + 1; p <= uint64(front+back)+uint64(player.MICEOJBAKGN)+uint64(overflow); p++ { + if !occupied[uint32(p)] { + pos = uint32(p) + break + } + } + if pos == 0 { + return nil, fmt.Errorf("bench is full") + } + uid, e := cwNextEntityID(run) + if e != nil { + return nil, e + } + var scales map[uint32]uint32 + if len(team.GridGameRoleList) > 0 { + scales = maps.Clone(team.GridGameRoleList[0].ConvertPropertyToFixpoint) + } + newRole := &pb.CDEAABODKCP{ + Id: roleID, + RoleStar: star, + UniqueId: uid, + Pos: pos, + ConvertPropertyToFixpoint: scales, + } + team.GridGameRoleList = append(team.GridGameRoleList, newRole) + grantUpdates = append(grantUpdates, &pb.KFNCAIHIHCM{ + MNLCHEJKDHO: &pb.KFNCAIHIHCM_PIDDDHKPDDM{PIDDDHKPDDM: newRole}, + }) + merged, e := cwMergeRoles(catalog, run, uid) + if e == nil && len(merged) > 0 { + for _, m := range merged { + grantUpdates = append(grantUpdates, m.JKPJFKCBIEM...) + } + } + + case "item": + if len(good.Params) < 1 { + return nil, fmt.Errorf("item goods %d has empty params", good.ID) + } + itemID := good.Params[0] + rewards, _ := common.LoadCurrencyWarsRewards(catalog) + isConsumable := false + if rewards != nil { + _, isConsumable = rewards.Consumables[itemID] + } + if isConsumable { + result, err := cwApplyCommand(catalog, run, common.CurrencyWarsCommand{Field: "consumable", Value: itemID, Quantity: 1}, runtime) + if err != nil { + return nil, err + } + for _, g := range result.GCPNIHHBGGD { + grantUpdates = append(grantUpdates, g.JKPJFKCBIEM...) + } + } else { + result, err := cwApplyCommand(catalog, run, common.CurrencyWarsCommand{Field: "equip", Value: itemID, Quantity: 1}, runtime) + if err != nil { + return nil, err + } + for _, g := range result.GCPNIHHBGGD { + grantUpdates = append(grantUpdates, g.JKPJFKCBIEM...) + } + } + + case "power": + if len(good.Params) >= 1 { + runtime.Rules["TbPower"] += good.Params[0] + } + + case "maze_buff": + if tbBoughtCount(runtime, target.id) > 0 { + return nil, fmt.Errorf("buff %d is already purchased", target.id) + } + case "stat": + // Handled via recorded bought count + } + + wasUnlocked := runtime.Rules["TbCosted"] >= tbCat.PomThreshold + runtime.Rules["TbPower"] -= target.cost + runtime.Rules["TbCosted"] += target.cost + + if target.id == tbCat.RefreshGoodID { + runtime.Rules["TbRefreshCount"]++ + cwTrailblazeRoll(tbCat, run, runtime, tbCat.Tier(layer)) + if tbBoughtCount(runtime, tbCat.InitialGoodID) == 0 { + runtime.Rules["TbGood/0"] = tbCat.InitialGoodID + } + } else { + runtime.Rules[fmt.Sprintf("TbBought/%d", target.id)]++ + if target.slot >= 0 && target.slot < cwTrailblazeSlots { + runtime.Rules[fmt.Sprintf("TbGood/%d", target.slot)] = 0 + } + } + + if !wasUnlocked && runtime.Rules["TbCosted"] >= tbCat.PomThreshold { + slotToPut := target.slot + if slotToPut < 0 || slotToPut >= cwTrailblazeSlots || runtime.Rules[fmt.Sprintf("TbGood/%d", slotToPut)] != 0 { + slotToPut = 0 + for s := 0; s < cwTrailblazeSlots; s++ { + if runtime.Rules[fmt.Sprintf("TbGood/%d", s)] == 0 { + slotToPut = s + break + } + } + } + runtime.Rules[fmt.Sprintf("TbGood/%d", slotToPut)] = tbCat.PomGoodID + } + + cwSyncTraitComponent(catalog, run, runtime) + + bundle := &pb.ABPLALCHNFA{ + GridUpdateSrc: pb.JJDFGMMMCMI_JJDFGMMMCMI_GLKAGBGLENM, + OOEOCGLLHFO: []uint32{cwTrailblazeTrait, cwTrailblazeEffect}, + JKPJFKCBIEM: []*pb.KFNCAIHIHCM{ + { + MNLCHEJKDHO: &pb.KFNCAIHIHCM_HKHJJCMKKJE{ + HKHJJCMKKJE: &pb.JLKCBOIFJKC{ + TraitId: cwTrailblazeTrait, + ENPLACDLJGA: cwTrailblazeTraitEffectProto(tbCat, runtime), + }, + }, + }, + { + MNLCHEJKDHO: &pb.KFNCAIHIHCM_GOOCHGKBIPN{ + GOOCHGKBIPN: cwTrailblazeModifierProto(tbCat, runtime), + }, + }, + }, + } + bundle.JKPJFKCBIEM = append(bundle.JKPJFKCBIEM, grantUpdates...) + + return &pb.GridFightSyncUpdateResultScNotify{ + GCPNIHHBGGD: []*pb.ABPLALCHNFA{bundle}, + }, nil +} + +func init() { + cwHandle("GridFightTraitUpdate", func(s *Session, req *pb.GridFightTraitUpdateCsReq, rsp *pb.GridFightTraitUpdateScRsp) error { + rsp.Retcode = uint32(pb.Retcode_RET_GRID_FIGHT_PARAM_NOT_MATCH) + if req == nil { + return nil + } + catalog, err := common.LoadCurrencyWarsCatalog() + if err != nil { + return err + } + data, revision, err := readCurrencyWarsData(catalog) + if err != nil { + return err + } + if data == nil || data.DHFOFLFCLAH == nil { + rsp.Retcode = uint32(pb.Retcode_RET_GRID_FIGHT_NOT_IN_GAMEPLAY) + return nil + } + s.currencyWarsRevision = revision + + run := data.DHFOFLFCLAH + runtime, err := cwBonfireRuntime(catalog, run) + if err != nil { + return err + } + + if req.GetTraitId() == cwTrailblazeTrait && req.GetEffectId() == cwTrailblazeEffect && req.GetKDBKIPPHMFI() != nil { + notify, buyErr := cwTrailblazeBuy(catalog, run, runtime, req.GetKDBKIPPHMFI().Pos) + if buyErr != nil { + PacketLog(fmt.Sprintf("CW_TRAIT_UPDATE Trailblaze rejected: %v", buyErr)) + rsp.Retcode = uint32(pb.Retcode_RET_GRID_FIGHT_PARAM_NOT_MATCH) + return nil + } + if err = s.saveCurrencyWarsDataWithRuntime(data, catalog, runtime); err != nil { + return err + } + if err = s.Send(notify); err != nil { + return err + } + rsp.Retcode = 0 + PacketLog(fmt.Sprintf("CW_TRAIT_UPDATE Trailblaze bought pos=%d power=%d costed=%d", req.GetKDBKIPPHMFI().Pos, runtime.Rules["TbPower"], runtime.Rules["TbCosted"])) + return nil + } + + rsp.Retcode = 0 + PacketLog(fmt.Sprintf("CW_TRAIT_UPDATE trait=%d effect=%d", req.GetTraitId(), req.GetEffectId())) + return nil + }) +} diff --git a/internal/gameserver/currency_wars_trailblaze_test.go b/internal/gameserver/currency_wars_trailblaze_test.go new file mode 100644 index 0000000..777fe1d --- /dev/null +++ b/internal/gameserver/currency_wars_trailblaze_test.go @@ -0,0 +1,715 @@ +package gameserver + +import ( + "os" + "testing" + + "gac-hkrpg-go/internal/common" + pb "gac-hkrpg-go/protocol/proto" + "google.golang.org/protobuf/proto" +) + +func TestTrailblazeCatalog(t *testing.T) { + cat, err := common.LoadCurrencyWarsTrailblaze() + if err != nil { + t.Fatalf("LoadCurrencyWarsTrailblaze: %v", err) + } + if len(cat.Goods) != 47 { + t.Errorf("expected 47 goods, got %d", len(cat.Goods)) + } + if cat.Income(2) != 20 || cat.Income(4) != 45 || cat.Income(6) != 75 || cat.Income(8) != 125 || cat.Income(10) != 225 { + t.Errorf("unexpected income per layer: %v", cat.LayerParams) + } + if cat.InitialGoodID != 101 { + t.Errorf("expected initial good 101, got %d", cat.InitialGoodID) + } + if cat.RefreshGoodID != 999 { + t.Errorf("expected refresh good 999, got %d", cat.RefreshGoodID) + } + if cat.PomGoodID != 507 { + t.Errorf("expected pom good 507, got %d", cat.PomGoodID) + } + if cat.PomThreshold != 300 { + t.Errorf("expected pom threshold 300, got %d", cat.PomThreshold) + } +} + +func TestTrailblazeNodeEnteredAndRoll(t *testing.T) { + catalog, err := common.LoadCurrencyWarsCatalog() + if err != nil { + t.Fatalf("LoadCurrencyWarsCatalog: %v", err) + } + tbCat, err := common.LoadCurrencyWarsTrailblaze() + if err != nil { + t.Fatalf("LoadCurrencyWarsTrailblaze: %v", err) + } + + // Deploy 2 Welt (100402) which has trait 100502 (Trailblaze Silver Rail) + run := &pb.MPIMAEMCEFH{ + UniqueId: 999, + NAGDJGMMCBE: []*pb.GridFightGameInfo{ + { + LEOIAOFBBFA: &pb.GridFightGameInfo_KLCPAAHDPJF{ + KLCPAAHDPJF: &pb.BEEFLEJAOCN{ + DCAHJELNJNP: 1, + SectionId: 1, + }, + }, + }, + { + LEOIAOFBBFA: &pb.GridFightGameInfo_FAOCFBOFCJP{ + FAOCFBOFCJP: &pb.NIHNGPKHEPC{ + MICEOJBAKGN: 4, + }, + }, + }, + { + LEOIAOFBBFA: &pb.GridFightGameInfo_EJIPINIKNCK{ + EJIPINIKNCK: &pb.HKFECLCHBFJ{ + GridGameRoleList: []*pb.CDEAABODKCP{ + {Id: 100402, RoleStar: 1, Pos: 1, UniqueId: 101}, + {Id: 100402, RoleStar: 1, Pos: 2, UniqueId: 102}, + }, + }, + }, + }, + { + LEOIAOFBBFA: &pb.GridFightGameInfo_GCKLIONKMJC{ + GCKLIONKMJC: &pb.CONONEALNOF{}, + }, + }, + }, + } + + layer := cwGetTrailblazeLayer(catalog, run) + if layer != 2 { + t.Fatalf("expected Trailblaze layer 2, got %d", layer) + } + + runtime := &common.CurrencyWarsRuntimeState{ + RunID: 999, + Rules: make(map[string]uint32), + } + + cwTrailblazeNodeEntered(catalog, run, runtime) + if runtime.Rules["TbOpened"] != 1 { + t.Errorf("expected TbOpened=1, got %d", runtime.Rules["TbOpened"]) + } + if runtime.Rules["TbGood/0"] != tbCat.InitialGoodID { + t.Errorf("expected slot 0 to be initial good %d, got %d", tbCat.InitialGoodID, runtime.Rules["TbGood/0"]) + } + + shop := cwTrailblazeShopProto(tbCat, runtime) + if len(shop.OGDKJPDFFAI) != 5 { + t.Fatalf("expected 5 shop entries (4 slots + 1 refresh), got %d", len(shop.OGDKJPDFFAI)) + } + if shop.OGDKJPDFFAI[0].GoodsId != tbCat.InitialGoodID { + t.Errorf("expected slot 0 good %d, got %d", tbCat.InitialGoodID, shop.OGDKJPDFFAI[0].GoodsId) + } + if shop.OGDKJPDFFAI[4].GoodsId != tbCat.RefreshGoodID { + t.Errorf("expected slot 4 good %d, got %d", tbCat.RefreshGoodID, shop.OGDKJPDFFAI[4].GoodsId) + } + if shop.OGDKJPDFFAI[4].OICGFNGNLOE != 1 { + t.Errorf("expected refresh cost 1, got %d", shop.OGDKJPDFFAI[4].OICGFNGNLOE) + } +} + +func TestTrailblazeBuyFreeInitialGood(t *testing.T) { + catalog, err := common.LoadCurrencyWarsCatalog() + if err != nil { + t.Fatalf("LoadCurrencyWarsCatalog: %v", err) + } + tbCat, err := common.LoadCurrencyWarsTrailblaze() + if err != nil { + t.Fatalf("LoadCurrencyWarsTrailblaze: %v", err) + } + + run := &pb.MPIMAEMCEFH{ + UniqueId: 999, + NAGDJGMMCBE: []*pb.GridFightGameInfo{ + { + LEOIAOFBBFA: &pb.GridFightGameInfo_KLCPAAHDPJF{ + KLCPAAHDPJF: &pb.BEEFLEJAOCN{DCAHJELNJNP: 1, SectionId: 1}, + }, + }, + { + LEOIAOFBBFA: &pb.GridFightGameInfo_FAOCFBOFCJP{ + FAOCFBOFCJP: &pb.NIHNGPKHEPC{MICEOJBAKGN: 4}, + }, + }, + { + LEOIAOFBBFA: &pb.GridFightGameInfo_EJIPINIKNCK{ + EJIPINIKNCK: &pb.HKFECLCHBFJ{ + GridGameRoleList: []*pb.CDEAABODKCP{ + {Id: 100402, RoleStar: 1, Pos: 1, UniqueId: 101}, + {Id: 100402, RoleStar: 1, Pos: 2, UniqueId: 102}, + }, + }, + }, + }, + { + LEOIAOFBBFA: &pb.GridFightGameInfo_GCKLIONKMJC{ + GCKLIONKMJC: &pb.CONONEALNOF{}, + }, + }, + }, + } + + runtime := &common.CurrencyWarsRuntimeState{ + RunID: 999, + Rules: make(map[string]uint32), + } + cwTrailblazeNodeEntered(catalog, run, runtime) + + // Buy pos 0 (Good 101, cost 0) with 0 Trailblaze Power + notify, err := cwTrailblazeBuy(catalog, run, runtime, 0) + if err != nil { + t.Fatalf("cwTrailblazeBuy pos 0: %v", err) + } + if notify == nil || len(notify.GCPNIHHBGGD) == 0 { + t.Fatal("expected notify from buy") + } + if tbBoughtCount(runtime, tbCat.InitialGoodID) != 1 { + t.Errorf("expected InitialGoodID bought count 1, got %d", tbBoughtCount(runtime, tbCat.InitialGoodID)) + } + + // Buying unique maze_buff again must fail + _, err = cwTrailblazeBuy(catalog, run, runtime, 0) + if err == nil { + t.Fatal("expected error buying already purchased maze_buff") + } +} + +func TestTrailblazeRefreshAndPomUnlock(t *testing.T) { + catalog, err := common.LoadCurrencyWarsCatalog() + if err != nil { + t.Fatalf("LoadCurrencyWarsCatalog: %v", err) + } + tbCat, err := common.LoadCurrencyWarsTrailblaze() + if err != nil { + t.Fatalf("LoadCurrencyWarsTrailblaze: %v", err) + } + + run := &pb.MPIMAEMCEFH{ + UniqueId: 999, + NAGDJGMMCBE: []*pb.GridFightGameInfo{ + { + LEOIAOFBBFA: &pb.GridFightGameInfo_KLCPAAHDPJF{ + KLCPAAHDPJF: &pb.BEEFLEJAOCN{DCAHJELNJNP: 1, SectionId: 1}, + }, + }, + { + LEOIAOFBBFA: &pb.GridFightGameInfo_FAOCFBOFCJP{ + FAOCFBOFCJP: &pb.NIHNGPKHEPC{MICEOJBAKGN: 8}, + }, + }, + { + LEOIAOFBBFA: &pb.GridFightGameInfo_EJIPINIKNCK{ + EJIPINIKNCK: &pb.HKFECLCHBFJ{ + GridGameRoleList: []*pb.CDEAABODKCP{ + {Id: 100402, RoleStar: 1, Pos: 1, UniqueId: 101}, + {Id: 100402, RoleStar: 1, Pos: 2, UniqueId: 102}, + }, + }, + }, + }, + { + LEOIAOFBBFA: &pb.GridFightGameInfo_GCKLIONKMJC{ + GCKLIONKMJC: &pb.CONONEALNOF{}, + }, + }, + }, + } + + runtime := &common.CurrencyWarsRuntimeState{ + RunID: 999, + Rules: make(map[string]uint32), + } + cwTrailblazeNodeEntered(catalog, run, runtime) + + // Give power + runtime.Rules["TbPower"] = 500 + runtime.Rules["TbCosted"] = 299 // 1 below threshold (300) + + // Buy refresh at pos 4 (cost 1) + _, err = cwTrailblazeBuy(catalog, run, runtime, 4) + if err != nil { + t.Fatalf("cwTrailblazeBuy refresh: %v", err) + } + if runtime.Rules["TbCosted"] != 300 { + t.Errorf("expected TbCosted=300, got %d", runtime.Rules["TbCosted"]) + } + if runtime.Rules["TbRefreshCount"] != 1 { + t.Errorf("expected TbRefreshCount=1, got %d", runtime.Rules["TbRefreshCount"]) + } + + // King Pom-Pom should now be in the store (slot 0) + if runtime.Rules["TbGood/0"] != tbCat.PomGoodID { + t.Errorf("expected King Pom-Pom (%d) in slot 0, got %d", tbCat.PomGoodID, runtime.Rules["TbGood/0"]) + } + + // Buy King Pom-Pom at pos 0 (cost 100) + initialPower := runtime.Rules["TbPower"] + _, err = cwTrailblazeBuy(catalog, run, runtime, 0) + if err != nil { + t.Fatalf("cwTrailblazeBuy King Pom: %v", err) + } + if runtime.Rules["TbPower"] != initialPower-100 { + t.Errorf("expected power %d, got %d", initialPower-100, runtime.Rules["TbPower"]) + } + + // Check if King Pom role 604202 is on the bench + _, team, _ := cwComponents(run) + foundPom := false + for _, r := range team.GridGameRoleList { + if r.Id == 604202 { + foundPom = true + if r.Pos < 14 { + t.Errorf("expected King Pom to be on bench (>=14), got pos %d", r.Pos) + } + break + } + } + if !foundPom { + t.Error("King Pom-Pom role 604202 was not added to team") + } +} + +func TestTrailblazeSyncTraitComponent(t *testing.T) { + catalog, err := common.LoadCurrencyWarsCatalog() + if err != nil { + t.Fatalf("LoadCurrencyWarsCatalog: %v", err) + } + + run := &pb.MPIMAEMCEFH{ + UniqueId: 999, + NAGDJGMMCBE: []*pb.GridFightGameInfo{ + { + LEOIAOFBBFA: &pb.GridFightGameInfo_KLCPAAHDPJF{ + KLCPAAHDPJF: &pb.BEEFLEJAOCN{DCAHJELNJNP: 1, SectionId: 1}, + }, + }, + { + LEOIAOFBBFA: &pb.GridFightGameInfo_FAOCFBOFCJP{ + FAOCFBOFCJP: &pb.NIHNGPKHEPC{MICEOJBAKGN: 4}, + }, + }, + { + LEOIAOFBBFA: &pb.GridFightGameInfo_EJIPINIKNCK{ + EJIPINIKNCK: &pb.HKFECLCHBFJ{ + GridGameRoleList: []*pb.CDEAABODKCP{ + {Id: 100402, RoleStar: 1, Pos: 1, UniqueId: 101}, + {Id: 100402, RoleStar: 1, Pos: 2, UniqueId: 102}, + }, + }, + }, + }, + }, + } + + runtime := &common.CurrencyWarsRuntimeState{ + RunID: 999, + Rules: map[string]uint32{ + "TbPower": 45, + "TbCosted": 100, + }, + } + + cwSyncTraitComponent(catalog, run, runtime) + + // Check OAHOAPCIDMK component exists in run.NAGDJGMMCBE + var traitComp *pb.FFOGFOBHDEK + for _, c := range run.NAGDJGMMCBE { + if c.GetOAHOAPCIDMK() != nil { + traitComp = c.GetOAHOAPCIDMK() + break + } + } + if traitComp == nil { + t.Fatal("expected OAHOAPCIDMK component in NAGDJGMMCBE") + } + + foundTB := false + for _, tr := range traitComp.GridFightTraitInfo { + if tr.TraitId == cwTrailblazeTrait { + foundTB = true + if tr.KOJFHFAKKFO != 2 { + t.Errorf("expected layer 2, got %d", tr.KOJFHFAKKFO) + } + if len(tr.TraitEffectList) == 0 { + t.Fatal("expected TraitEffectList for Trailblaze trait") + } + eff := tr.TraitEffectList[0] + if eff.EffectId != cwTrailblazeEffect { + t.Errorf("expected effect ID %d, got %d", cwTrailblazeEffect, eff.EffectId) + } + shop := eff.GetKDBKIPPHMFI() + if shop == nil { + t.Fatal("expected shop proto KDBKIPPHMFI in effect") + } + if shop.MJIGJEGMCML != 45 { + t.Errorf("expected shop power 45, got %d", shop.MJIGJEGMCML) + } + break + } + } + if !foundTB { + t.Error("Trailblaze trait 100502 not found in GridFightTraitInfo") + } + + // Check modifier in run.CAAGABEPPPE + if run.CAAGABEPPPE == nil || len(run.CAAGABEPPPE.KLCKAOJAMNH) == 0 { + t.Fatal("expected CAAGABEPPPE modifier list on run") + } + mod := run.CAAGABEPPPE.KLCKAOJAMNH[0] + if mod.UniqueId != cwTrailblazeModifierUID { + t.Errorf("expected modifier UID %d, got %d", cwTrailblazeModifierUID, mod.UniqueId) + } + progress := mod.GetONKMJAGNECC() + if progress == nil { + t.Fatal("expected ONKMJAGNECC in modifier") + } + if progress.IANFMIJBKAF != 100 || progress.PLAIPMBIHGJ != 300 { + t.Errorf("expected modifier costed=100/300, got %d/%d", progress.IANFMIJBKAF, progress.PLAIPMBIHGJ) + } +} + +func TestGridFightTraitUpdateHandler(t *testing.T) { + origPersistent, _ := os.ReadFile("persistent") + if origPersistent != nil { + t.Cleanup(func() { + _ = os.WriteFile("persistent", origPersistent, 0644) + }) + } + + session := &Session{Token: 88888} + catalog, err := common.LoadCurrencyWarsCatalog() + if err != nil { + t.Fatalf("load catalog: %v", err) + } + data, rev, err := readCurrencyWarsData(catalog) + if err != nil || data == nil || data.DHFOFLFCLAH == nil { + data, err = newCurrencyWarsData(catalog) + if err != nil { + t.Fatalf("new data: %v", err) + } + data.DHFOFLFCLAH = &pb.MPIMAEMCEFH{ + UniqueId: 1, + DivisionId: catalog.CapturedStages[0].ID, + LJNGPCKPJLF: catalog.ActiveSeasonID, + NAGDJGMMCBE: []*pb.GridFightGameInfo{ + {LEOIAOFBBFA: &pb.GridFightGameInfo_KLCPAAHDPJF{KLCPAAHDPJF: &pb.BEEFLEJAOCN{DCAHJELNJNP: 1, SectionId: 1}}}, + {LEOIAOFBBFA: &pb.GridFightGameInfo_FAOCFBOFCJP{FAOCFBOFCJP: &pb.NIHNGPKHEPC{MICEOJBAKGN: 4}}}, + {LEOIAOFBBFA: &pb.GridFightGameInfo_EJIPINIKNCK{EJIPINIKNCK: &pb.HKFECLCHBFJ{ + GridGameRoleList: []*pb.CDEAABODKCP{ + {Id: 100402, RoleStar: 1, Pos: 1, UniqueId: 101}, + {Id: 100402, RoleStar: 1, Pos: 2, UniqueId: 102}, + }, + }}}, + {LEOIAOFBBFA: &pb.GridFightGameInfo_GCKLIONKMJC{GCKLIONKMJC: &pb.CONONEALNOF{}}}, + }, + } + rev = 0 + } else { + // Ensure run has Trailblaze roles on board for testing + _, team, _ := cwComponents(data.DHFOFLFCLAH) + if team != nil { + team.GridGameRoleList = append(team.GridGameRoleList, + &pb.CDEAABODKCP{Id: 100402, RoleStar: 1, Pos: 1, UniqueId: 801}, + &pb.CDEAABODKCP{Id: 100402, RoleStar: 1, Pos: 2, UniqueId: 802}, + ) + } + } + if _, latestRev, err := readCurrencyWarsData(catalog); err == nil { + session.currencyWarsRevision = latestRev + } else { + session.currencyWarsRevision = rev + } + runtime := &common.CurrencyWarsRuntimeState{ + RunID: data.DHFOFLFCLAH.UniqueId, + Rules: map[string]uint32{"TbPower": 10}, + } + if err := session.saveCurrencyWarsDataWithRuntime(data, catalog, runtime); err != nil { + t.Fatalf("save initial data: %v", err) + } + + cmdID, ok := pb.CmdIDByName["GridFightTraitUpdateCsReq"] + if !ok || typedHandlers[uint16(cmdID)] == nil { + t.Fatal("handler GridFightTraitUpdate not registered") + } + + // Buy pos 0 (Good 101, cost 0) + req := &pb.GridFightTraitUpdateCsReq{ + TraitId: cwTrailblazeTrait, + EffectId: cwTrailblazeEffect, + NEBNJDMFELO: &pb.GridFightTraitUpdateCsReq_KDBKIPPHMFI{ + KDBKIPPHMFI: &pb.KDGHLGHJDMC{Pos: 0}, + }, + } + rawReq, err := proto.Marshal(req) + if err != nil { + t.Fatalf("marshal req: %v", err) + } + + if err := typedHandlers[uint16(cmdID)](session, rawReq); err != nil { + t.Fatalf("call GridFightTraitUpdate: %v", err) + } +} + +func TestCurrencyWarsTrailblazePowerCommand(t *testing.T) { + catalog, err := common.LoadCurrencyWarsCatalog() + if err != nil { + t.Fatalf("load catalog: %v", err) + } + tbCat, err := common.LoadCurrencyWarsTrailblaze() + if err != nil { + t.Fatalf("load tb catalog: %v", err) + } + + run := &pb.MPIMAEMCEFH{ + UniqueId: 99, + DivisionId: catalog.CapturedStages[0].ID, + NAGDJGMMCBE: []*pb.GridFightGameInfo{ + {LEOIAOFBBFA: &pb.GridFightGameInfo_KLCPAAHDPJF{KLCPAAHDPJF: &pb.BEEFLEJAOCN{DCAHJELNJNP: 1, SectionId: 1}}}, + {LEOIAOFBBFA: &pb.GridFightGameInfo_FAOCFBOFCJP{FAOCFBOFCJP: &pb.NIHNGPKHEPC{MICEOJBAKGN: 4, GridFightLineupHp: 100, PPKKHHNEOHB: 100}}}, + {LEOIAOFBBFA: &pb.GridFightGameInfo_EJIPINIKNCK{EJIPINIKNCK: &pb.HKFECLCHBFJ{}}}, + {LEOIAOFBBFA: &pb.GridFightGameInfo_GCKLIONKMJC{GCKLIONKMJC: &pb.CONONEALNOF{}}}, + }, + } + + runtime := &common.CurrencyWarsRuntimeState{ + RunID: run.UniqueId, + Rules: make(map[string]uint32), + } + + // 1. Test cwApplyCommand with "tbpower" + cmd := common.CurrencyWarsCommand{Field: "tbpower", Value: 120, Quantity: 1} + notify, err := cwApplyCommand(catalog, run, cmd, runtime) + if err != nil { + t.Fatalf("cwApplyCommand failed: %v", err) + } + if runtime.Rules["TbPower"] != 120 { + t.Fatalf("expected TbPower=120, got %d", runtime.Rules["TbPower"]) + } + if runtime.Rules["TbOpened"] != 1 { + t.Fatalf("expected TbOpened=1, got %d", runtime.Rules["TbOpened"]) + } + if notify == nil || len(notify.GCPNIHHBGGD) == 0 { + t.Fatal("expected notification with trait bundle") + } + + // 2. Test alias "tb" + cmdAlias := common.CurrencyWarsCommand{Field: "tb", Value: 250, Quantity: 1} + notifyAlias, err := cwApplyCommand(catalog, run, cmdAlias, runtime) + if err != nil { + t.Fatalf("cwApplyCommand with alias tb failed: %v", err) + } + if runtime.Rules["TbPower"] != 250 { + t.Fatalf("expected TbPower=250, got %d", runtime.Rules["TbPower"]) + } + if notifyAlias == nil { + t.Fatal("expected notification for tb alias") + } + + // 3. Test QueueCurrencyWarsGrant validation + cmdID, err := common.QueueCurrencyWarsGrant(1337, run.UniqueId, "tbpower", 300, 1, 0) + if err != nil { + t.Fatalf("QueueCurrencyWarsGrant tbpower failed: %v", err) + } + if cmdID == "" { + t.Fatal("expected non-empty command ID") + } + _ = common.AcknowledgeCurrencyWarsCommand(cmdID) + + cmdID2, err := common.QueueCurrencyWarsGrant(1337, run.UniqueId, "tb", 50, 1, 0) + if err != nil { + t.Fatalf("QueueCurrencyWarsGrant tb failed: %v", err) + } + _ = common.AcknowledgeCurrencyWarsCommand(cmdID2) + + _ = tbCat +} + +func TestTrailblazeConsumableItemPurchase(t *testing.T) { + catalog, err := common.LoadCurrencyWarsCatalog() + if err != nil { + t.Fatalf("LoadCurrencyWarsCatalog: %v", err) + } + + run := &pb.MPIMAEMCEFH{ + UniqueId: 888, + NAGDJGMMCBE: []*pb.GridFightGameInfo{ + { + LEOIAOFBBFA: &pb.GridFightGameInfo_KLCPAAHDPJF{ + KLCPAAHDPJF: &pb.BEEFLEJAOCN{DCAHJELNJNP: 1, SectionId: 1}, + }, + }, + { + LEOIAOFBBFA: &pb.GridFightGameInfo_FAOCFBOFCJP{ + FAOCFBOFCJP: &pb.NIHNGPKHEPC{MICEOJBAKGN: 8}, + }, + }, + { + LEOIAOFBBFA: &pb.GridFightGameInfo_EJIPINIKNCK{ + EJIPINIKNCK: &pb.HKFECLCHBFJ{ + GridGameRoleList: []*pb.CDEAABODKCP{ + {Id: 100402, RoleStar: 1, Pos: 1, UniqueId: 101}, + {Id: 100402, RoleStar: 1, Pos: 2, UniqueId: 102}, + }, + }, + }, + }, + { + LEOIAOFBBFA: &pb.GridFightGameInfo_GCKLIONKMJC{ + GCKLIONKMJC: &pb.CONONEALNOF{}, + }, + }, + }, + } + + runtime := &common.CurrencyWarsRuntimeState{ + RunID: 888, + Rules: map[string]uint32{ + "TbOpened": 1, + "TbPower": 200, + "TbGood/0": 401, // Item 350113 (consumable) + }, + } + + notify, err := cwTrailblazeBuy(catalog, run, runtime, 0) + if err != nil { + t.Fatalf("cwTrailblazeBuy good 401: %v", err) + } + if notify == nil { + t.Fatal("expected notify from buy") + } + + // Slot 0 must be cleared + if runtime.Rules["TbGood/0"] != 0 { + t.Errorf("expected slot 0 cleared, got %d", runtime.Rules["TbGood/0"]) + } + + // Verify consumable is added, NOT added to equipment list + bag := cwEquipmentBag(run) + if len(bag.GridFightEquipmentList) != 0 { + t.Errorf("expected equipment list empty, got %d items", len(bag.GridFightEquipmentList)) + } + if len(bag.IPMKOMEHBMP) != 1 || bag.IPMKOMEHBMP[0].ItemId != 350113 { + t.Errorf("expected consumable item 350113 in bag, got %+v", bag.IPMKOMEHBMP) + } +} + +func TestTrailblazeBattleTraitsAndPowerSync(t *testing.T) { + catalog, err := common.LoadCurrencyWarsCatalog() + if err != nil { + t.Fatalf("LoadCurrencyWarsCatalog: %v", err) + } + + run := &pb.MPIMAEMCEFH{ + UniqueId: 777, + NAGDJGMMCBE: []*pb.GridFightGameInfo{ + { + LEOIAOFBBFA: &pb.GridFightGameInfo_EJIPINIKNCK{ + EJIPINIKNCK: &pb.HKFECLCHBFJ{ + GridGameRoleList: []*pb.CDEAABODKCP{ + {Id: 100402, RoleStar: 1, Pos: 1, UniqueId: 101}, + {Id: 604202, RoleStar: 2, Pos: 2, UniqueId: 102}, + }, + }, + }, + }, + { + LEOIAOFBBFA: &pb.GridFightGameInfo_GCKLIONKMJC{ + GCKLIONKMJC: &pb.CONONEALNOF{}, + }, + }, + }, + } + + runtime := &common.CurrencyWarsRuntimeState{ + RunID: 777, + Rules: map[string]uint32{ + "TbOpened": 1, + "TbPower": 330, + "TbCosted": 300, + "TbBought/101": 1, + "TbBought/201": 1, + }, + } + + // 1. Battle traits + battleTraits := cwCalculateBattleTraits(catalog, run, runtime) + var foundTb *pb.NIEGDAPHKGC + for _, tr := range battleTraits { + if tr.TraitId == cwTrailblazeTrait { + foundTb = tr + break + } + } + if foundTb == nil { + t.Fatal("expected Trailblaze trait in battle traits") + } + if len(foundTb.TraitEffectList) == 0 { + t.Fatal("expected trait effects on Trailblaze trait") + } + effect := foundTb.TraitEffectList[0] + if effect.EffectId != cwTrailblazeEffect { + t.Errorf("expected effect ID %d, got %d", cwTrailblazeEffect, effect.EffectId) + } + effData := effect.GetKDBKIPPHMFI() + if effData == nil || effData.MJIGJEGMCML != 330 { + t.Errorf("expected MJIGJEGMCML=330, got %+v", effData) + } + + // 2. Trial avatar for King Pom-Pom + pomVariant, err := catalog.CapturedRoleVariant(604202, 2) + if err != nil { + t.Fatalf("CapturedRoleVariant 604202: %v", err) + } + avatar, err := cwTrialBattleAvatar(catalog, pomVariant, 1) + if err != nil { + t.Fatalf("cwTrialBattleAvatar: %v", err) + } + if avatar.Id != 6042 { + t.Errorf("expected AvatarID 6042, got %d", avatar.Id) + } + if avatar.AvatarType != pb.AvatarType_AvatarType_AvatarGridFightType { + t.Errorf("expected AvatarGridFightType, got %v", avatar.AvatarType) + } +} + +func TestClaimableRewardsAndWeeklyHandlers(t *testing.T) { + s := &Session{Token: 12345} + + // 1. GridFightTakeWeeklyReward + reqWeekly := &pb.GridFightTakeWeeklyRewardCsReq{JKCEBHFIENI: []uint32{1, 2}} + rspWeekly := &pb.GridFightTakeWeeklyRewardScRsp{} + cmdID, ok := pb.CmdIDByName["GridFightTakeWeeklyRewardCsReq"] + if !ok { + t.Fatal("CmdIDByName GridFightTakeWeeklyRewardCsReq missing") + } + handler, ok := typedHandlers[cmdID] + if !ok { + t.Fatal("handler GridFightTakeWeeklyRewardCsReq missing") + } + data, _ := proto.Marshal(reqWeekly) + if err := handler(s, data); err != nil { + t.Fatalf("call GridFightTakeWeeklyReward: %v", err) + } + _ = rspWeekly + + // 2. GridFightUpdateSpotLightTrack + reqTrack := &pb.GridFightUpdateSpotLightTrackCsReq{} + cmdIDTrack, ok := pb.CmdIDByName["GridFightUpdateSpotLightTrackCsReq"] + if !ok { + t.Fatal("CmdIDByName GridFightUpdateSpotLightTrackCsReq missing") + } + handlerTrack, ok := typedHandlers[cmdIDTrack] + if !ok { + t.Fatal("handler GridFightUpdateSpotLightTrackCsReq missing") + } + dataTrack, _ := proto.Marshal(reqTrack) + if err := handlerTrack(s, dataTrack); err != nil { + t.Fatalf("call GridFightUpdateSpotLightTrack: %v", err) + } +} diff --git a/internal/gameserver/currency_wars_traits.go b/internal/gameserver/currency_wars_traits.go new file mode 100644 index 0000000..34e6406 --- /dev/null +++ b/internal/gameserver/currency_wars_traits.go @@ -0,0 +1,262 @@ +package gameserver + +import ( + "fmt" + "slices" + "sort" + + "gac-hkrpg-go/internal/common" + pb "gac-hkrpg-go/protocol/proto" +) + +const benchFirstPos uint32 = 14 + +func cwCalculateBoardTraits(catalog *common.CurrencyWarsCatalog, run *pb.MPIMAEMCEFH) []*pb.FBNGHMHHHLL { + if catalog == nil || run == nil { + return nil + } + traitsCatalog, err := common.LoadCurrencyWarsTraits(catalog) + if err != nil { + return nil + } + _, team, _ := cwComponents(run) + if team == nil { + return nil + } + + tallies := make(map[uint32]uint32) + members := make(map[uint32][]uint32) + for _, role := range team.GridGameRoleList { + if role == nil || role.Pos >= benchFirstPos { + continue + } + variant, err := catalog.CapturedRoleVariant(role.Id, role.RoleStar) + if err == nil { + for _, tid := range variant.Config.TraitList { + tallies[tid]++ + if !slices.Contains(members[tid], role.UniqueId) { + members[tid] = append(members[tid], role.UniqueId) + } + } + } + } + + bag := cwEquipmentBag(run) + if bag != nil && len(bag.GridFightEquipmentList) > 0 { + equipMap, _ := common.LoadCurrencyWarsEquipment(catalog) + if equipMap != nil { + for _, role := range team.GridGameRoleList { + if role == nil || role.Pos >= benchFirstPos { + continue + } + for _, uid := range role.PNJIPHJJONG { + for _, e := range bag.GridFightEquipmentList { + if e != nil && e.UniqueId == uid { + if info, ok := equipMap[e.GridFightEquipmentId]; ok { + if (info.EquipFunc == 10 || info.EquipFunc == 11) && len(info.AbilityParamList) > 0 { + var tid uint32 + if _, scanErr := fmt.Sscanf(info.AbilityParamList[0], "%d", &tid); scanErr == nil && tid != 0 { + tallies[tid]++ + if !slices.Contains(members[tid], role.UniqueId) { + members[tid] = append(members[tid], role.UniqueId) + } + } + } + } + } + } + } + } + } + } + + var tids []uint32 + for tid := range tallies { + tids = append(tids, tid) + } + sort.Slice(tids, func(i, j int) bool { return tids[i] < tids[j] }) + + var result []*pb.FBNGHMHHHLL + for _, tid := range tids { + count := tallies[tid] + layer := traitsCatalog.ActiveLayer(tid, count) + result = append(result, &pb.FBNGHMHHHLL{ + TraitId: tid, + KOJFHFAKKFO: layer, + OLPPMEAMFFO: members[tid], + }) + } + return result +} + +// cwSyncTraitComponent updates the run's OAHOAPCIDMK component (GridFightGameInfo_OAHOAPCIDMK) +// with the calculated board traits and attaches any active trait effects (such as the Trailblaze store). +// It also keeps the run's CAAGABEPPPE modifier list in sync with active traits. +func cwSyncTraitComponent(catalog *common.CurrencyWarsCatalog, run *pb.MPIMAEMCEFH, runtime *common.CurrencyWarsRuntimeState) { + if catalog == nil || run == nil { + return + } + tbCat, _ := common.LoadCurrencyWarsTrailblaze() + tbActive := tbCat != nil && cwTrailblazeActive(catalog, run, runtime) + if tbActive && runtime != nil { + if runtime.Rules == nil { + runtime.Rules = make(map[string]uint32) + } + if runtime.Rules["TbOpened"] == 0 { + cwTrailblazeNodeEntered(catalog, run, runtime) + } + } + + traits := cwCalculateBoardTraits(catalog, run) + if tbActive { + var foundTb *pb.FBNGHMHHHLL + for _, t := range traits { + if t.TraitId == cwTrailblazeTrait { + foundTb = t + break + } + } + if foundTb == nil { + foundTb = &pb.FBNGHMHHHLL{ + TraitId: cwTrailblazeTrait, + KOJFHFAKKFO: 0, + } + traits = append(traits, foundTb) + } + foundTb.TraitEffectList = []*pb.MGBHKCMHDOK{ + cwTrailblazeTraitEffectProto(tbCat, runtime), + } + } + + var traitComp *pb.FFOGFOBHDEK + for _, c := range run.NAGDJGMMCBE { + if c.GetOAHOAPCIDMK() != nil { + traitComp = c.GetOAHOAPCIDMK() + break + } + } + if traitComp == nil { + traitComp = &pb.FFOGFOBHDEK{} + run.NAGDJGMMCBE = append(run.NAGDJGMMCBE, &pb.GridFightGameInfo{ + LEOIAOFBBFA: &pb.GridFightGameInfo_OAHOAPCIDMK{ + OAHOAPCIDMK: traitComp, + }, + }) + } + traitComp.GridFightTraitInfo = traits + + if tbCat != nil && tbActive { + if run.CAAGABEPPPE == nil { + run.CAAGABEPPPE = &pb.HKDACEAEDDE{} + } + modProto := cwTrailblazeModifierProto(tbCat, runtime) + found := false + for i, mod := range run.CAAGABEPPPE.KLCKAOJAMNH { + if mod.UniqueId == cwTrailblazeModifierUID { + run.CAAGABEPPPE.KLCKAOJAMNH[i] = modProto + found = true + break + } + } + if !found { + run.CAAGABEPPPE.KLCKAOJAMNH = append(run.CAAGABEPPPE.KLCKAOJAMNH, modProto) + } + } +} + +func cwCalculateBattleTraits(catalog *common.CurrencyWarsCatalog, run *pb.MPIMAEMCEFH, runtime *common.CurrencyWarsRuntimeState) []*pb.NIEGDAPHKGC { + if catalog == nil || run == nil { + return nil + } + traitsCatalog, err := common.LoadCurrencyWarsTraits(catalog) + if err != nil { + return nil + } + _, team, _ := cwComponents(run) + if team == nil { + return nil + } + + tallies := make(map[uint32]uint32) + members := make(map[uint32][]*pb.MHFNFINKJKP) + for _, role := range team.GridGameRoleList { + if role == nil || role.Pos >= benchFirstPos { + continue + } + variant, err := catalog.CapturedRoleVariant(role.Id, role.RoleStar) + if err == nil { + for _, tid := range variant.Config.TraitList { + tallies[tid]++ + members[tid] = append(members[tid], &pb.MHFNFINKJKP{ + CHEEOEHDOBF: role.UniqueId, + }) + } + } + } + + bag := cwEquipmentBag(run) + if bag != nil && len(bag.GridFightEquipmentList) > 0 { + equipMap, _ := common.LoadCurrencyWarsEquipment(catalog) + if equipMap != nil { + for _, role := range team.GridGameRoleList { + if role == nil || role.Pos >= benchFirstPos { + continue + } + for _, uid := range role.PNJIPHJJONG { + for _, e := range bag.GridFightEquipmentList { + if e != nil && e.UniqueId == uid { + if info, ok := equipMap[e.GridFightEquipmentId]; ok { + if (info.EquipFunc == 10 || info.EquipFunc == 11) && len(info.AbilityParamList) > 0 { + var tid uint32 + if _, scanErr := fmt.Sscanf(info.AbilityParamList[0], "%d", &tid); scanErr == nil && tid != 0 { + tallies[tid]++ + } + } + } + } + } + } + } + } + } + + tbActive := cwTrailblazeActive(catalog, run, runtime) + if tbActive { + if _, ok := tallies[cwTrailblazeTrait]; !ok { + tallies[cwTrailblazeTrait] = 0 + } + } + + var tids []uint32 + for tid := range tallies { + tids = append(tids, tid) + } + sort.Slice(tids, func(i, j int) bool { return tids[i] < tids[j] }) + + var result []*pb.NIEGDAPHKGC + for _, tid := range tids { + count := tallies[tid] + layer := traitsCatalog.ActiveLayer(tid, count) + traitProto := &pb.NIEGDAPHKGC{ + TraitId: tid, + KOJFHFAKKFO: layer, + KOJMGCBLKKA: members[tid], + } + if tid == cwTrailblazeTrait { + tbPower := uint32(0) + if runtime != nil && runtime.Rules != nil { + tbPower = runtime.Rules["TbPower"] + } + traitProto.TraitEffectList = append(traitProto.TraitEffectList, &pb.BattleGridFightTraitEffectInfo{ + EffectId: cwTrailblazeEffect, + NEBNJDMFELO: &pb.BattleGridFightTraitEffectInfo_KDBKIPPHMFI{ + KDBKIPPHMFI: &pb.LFNNCOMFBMD{ + MJIGJEGMCML: tbPower, + }, + }, + }) + } + result = append(result, traitProto) + } + return result +} diff --git a/internal/gameserver/currency_wars_transactions.go b/internal/gameserver/currency_wars_transactions.go index 3a819cd..96a2570 100644 --- a/internal/gameserver/currency_wars_transactions.go +++ b/internal/gameserver/currency_wars_transactions.go @@ -295,10 +295,9 @@ func init() { if !ok { return nil } - if r.Remaining == 0 { - return fmt.Errorf("CW pool exhausted for %d", id) + if r.Remaining > 0 { + r.Remaining-- } - r.Remaining-- pool.Roles[id] = r return nil } diff --git a/internal/gameserver/currency_wars_trial.go b/internal/gameserver/currency_wars_trial.go index 9fcd868..7e67a52 100644 --- a/internal/gameserver/currency_wars_trial.go +++ b/internal/gameserver/currency_wars_trial.go @@ -9,7 +9,11 @@ import ( // Source reference resolves SpecialAvatarID and serializes the special ID with // AVATAR_TRIAL_TYPE. Current native rows supply all levels/equipment identities. func cwTrialBattleAvatar(c *common.CurrencyWarsCatalog, role common.CurrencyWarsRoleVariant, index uint32) (*pb.BattleAvatar, error) { - row, e := c.TrialAvatar(role.Config.SpecialAvatarID, role.Config.AvatarID, playerWorldLevel) + wl := playerWorldLevel + if wl == 0 { + wl = 6 + } + row, e := c.TrialAvatar(role.Config.SpecialAvatarID, role.Config.AvatarID, wl) if e != nil { return nil, e } @@ -19,9 +23,9 @@ func cwTrialBattleAvatar(c *common.CurrencyWarsCatalog, role common.CurrencyWars equipment = &common.Lightcone{ItemID: row.EquipmentID, Level: row.EquipmentLevel, Promotion: row.EquipmentPromotion, Rank: row.EquipmentRank} } result, _ := avatar.ToBattleAvatarProto(index, equipment, nil) - result.Id = row.SpecialAvatarID - result.AvatarType = pb.AvatarType_AvatarType_AvatarTrialType - result.WorldLevel = playerWorldLevel + result.Id = row.AvatarID + result.AvatarType = pb.AvatarType_AvatarType_AvatarGridFightType + result.WorldLevel = wl result.EnhancedId = row.EnhancedID result.IGDEHGIKFJA = append([]uint32(nil), row.ExRankIDList...) return result, nil diff --git a/internal/gameserver/currency_wars_xaodev.go b/internal/gameserver/currency_wars_xaodev.go new file mode 100644 index 0000000..f344b74 --- /dev/null +++ b/internal/gameserver/currency_wars_xaodev.go @@ -0,0 +1,163 @@ +// SPDX-License-Identifier: AGPL-3.0-only +// Adapted from Currency-War-SR by xaodev (2026); see third_party/currency-war-sr. +package gameserver + +import ( + "crypto/sha256" + "encoding/binary" + "fmt" + "math" + "sort" + + "gac-hkrpg-go/internal/common" + pb "gac-hkrpg-go/protocol/proto" + "google.golang.org/protobuf/proto" +) + +// Client difficulty source keys from GridFightStage.GetBaseDiffAddLv and +// GetAllDiffAddLvForBranchPreview; these are protocol semantics, not game IDs. +const ( + cwDifficultySourceDivision uint32 = 1 + cwDifficultySourceBranch uint32 = 2 +) + +func cwXaodevNodeKey(stage *pb.BEEFLEJAOCN) string { + return fmt.Sprintf("XaodevEncounter/%d/%d/%d", stage.EMOHLJHPOPC, stage.DCAHJELNJNP, stage.SectionId) +} + +func cwXaodevGenerated(run *pb.MPIMAEMCEFH, runtime *common.CurrencyWarsRuntimeState) bool { + stage, _ := cwStage(run) + return stage != nil && runtime != nil && runtime.RunID == run.UniqueId && runtime.Rules[cwXaodevNodeKey(stage)] == 1 +} + +// Equivalent to upstream's unprofiled EliteBranch encounter policy. The +// upstream also calls this a local policy, not the official server wave roll. +// Select only within the chapter camp already present in this run; no fresh +// boss/camp roll and no substitution of a captured first-node battle. +func cwGenerateXaodevEncounter(c *common.CurrencyWarsCatalog, run *pb.MPIMAEMCEFH, display *pb.OHMDHKGAEMM) (*pb.NCKOKDHNJDC, error) { + stage, template, err := cwReferenceCurrentTemplate(c, run) + if err != nil { + return nil, err + } + if run.UniqueId == 0 || stage.DCAHJELNJNP == 0 || display == nil || template.NodeType != "EliteBranch" || display.StageId != template.StageID || len(template.ParamList) == 0 || uint64(stage.DCAHJELNJNP) > uint64(len(stage.HEKMEJLMGFF)) { + return nil, fmt.Errorf("CW xaodev Encounter template/camp missing") + } + data, err := common.LoadCurrencyWarsXaodev(c) + if err != nil { + return nil, err + } + policy, err := cwLoadReferenceEnemies(c) + if err != nil { + return nil, err + } + penalties, err := common.LoadCurrencyWarsReferencePenalties(c) + if err != nil { + return nil, err + } + if _, ok := penalties[template.PenaltyBonusRuleID]; !ok { + return nil, fmt.Errorf("CW xaodev Encounter penalty missing") + } + campID := stage.HEKMEJLMGFF[stage.DCAHJELNJNP-1].GetLOGHLGKIFEN() + camp, err := c.MonsterCamp(run.LJNGPCKPJLF, campID) + if err != nil { + return nil, err + } + var pool []uint32 + for _, id := range camp.Season.MonsterList { + if policy.MonsterTiers[id] == data.Policy.MonsterTier { + pool = append(pool, id) + } + } + if len(pool) == 0 { + return nil, fmt.Errorf("CW xaodev Encounter camp has no configured monster tier") + } + sort.Slice(pool, func(i, j int) bool { return pool[i] < pool[j] }) + // A stable per-node seed; no global RNG state is consumed by re-preview. + var identity [16]byte + binary.LittleEndian.PutUint32(identity[0:4], run.UniqueId) + binary.LittleEndian.PutUint32(identity[4:8], stage.EMOHLJHPOPC) + binary.LittleEndian.PutUint32(identity[8:12], stage.DCAHJELNJNP) + binary.LittleEndian.PutUint32(identity[12:16], stage.SectionId) + seed := sha256.Sum256(identity[:]) + roll := binary.LittleEndian.Uint64(seed[:8]) + quality := data.Policy.PowerfulQualities[roll%uint64(len(data.Policy.PowerfulQualities))] + wave := &pb.PNAKHANCNGK{OCPEPEMNNEK: 1} + for i := uint32(0); i < data.Policy.MonsterCount; i++ { + wave.PBLBBDBDLEK = append(wave.PBLBBDBDLEK, &pb.AHKDPCIIELA{MonsterId: pool[(roll%uint64(len(pool))+uint64(i))%uint64(len(pool))], RoleStar: min(data.Policy.StarCap, stage.DCAHJELNJNP), LOGHLGKIFEN: campID}) + } + layer := &pb.MDJBBILMHLC{LNDKCPBJDKH: campID} // zero selection opens the pending page + for _, q := range []uint32{data.Policy.NormalQuality, quality} { + rule, err := data.Branch(template.ParamList[0], q) + if err != nil { + return nil, err + } + if _, err = data.BranchDifficulty(run.DivisionId, rule.ID); err != nil { + return nil, err + } + layer.BPDABHGFBFO = append(layer.BPDABHGFBFO, &pb.KKBGIPCAGGH{ + CJADMFDOBAI: rule.ID, IFIIOIIKJHO: template.PenaltyBonusRuleID, + MonsterWaveList: []*pb.PNAKHANCNGK{proto.Clone(wave).(*pb.PNAKHANCNGK)}, + BOAMLLHPNEC: &pb.GridFightDropInfo{NMGIABHIJJB: []*pb.BCCONKJHDJL{{FKKGLFOHGGA: pb.MJJIFGINJAJ_MJJIFGINJAJ_item_value, Num: display.PICMDONCKCO}}}, + }) + } + if display.PICMDONCKCO == 0 { + return nil, fmt.Errorf("CW xaodev Encounter base reward missing") + } + return &pb.NCKOKDHNJDC{EFECLPJFOCM: &pb.NCKOKDHNJDC_MPOBBIPMNNJ{MPOBBIPMNNJ: layer}}, nil +} + +// Preserve all other difficulty sources and replace just the selected branch. +// Selection, difficulty and the pending acknowledgement commit together. +func cwXaodevDifficulty(c *common.CurrencyWarsCatalog, run *pb.MPIMAEMCEFH, add uint32) (*pb.KFNCAIHIHCM, error) { + stage, _ := cwStage(run) + if stage == nil { + return nil, fmt.Errorf("CW xaodev difficulty stage missing") + } + data, err := common.LoadCurrencyWarsXaodev(c) + if err != nil { + return nil, err + } + division, err := c.CapturedStage(run.DivisionId) + if err != nil { + return nil, err + } + info := &pb.KOMPNJAMLPM{PIJBPDPEODG: division.EnemyHardLevel, PIFNOKAMBLL: map[uint32]int32{cwDifficultySourceDivision: int32(division.EnemyHardLevel)}} + if stage.ANJBLBEOJND != nil { + info = proto.Clone(stage.ANJBLBEOJND).(*pb.KOMPNJAMLPM) + } + if info.PIFNOKAMBLL == nil { + info.PIFNOKAMBLL = map[uint32]int32{} + } + if base, ok := info.PIFNOKAMBLL[cwDifficultySourceDivision]; ok && (base < 0 || uint32(base) != division.EnemyHardLevel) { + return nil, fmt.Errorf("CW xaodev difficulty base differs") + } + info.PIFNOKAMBLL[cwDifficultySourceDivision] = int32(division.EnemyHardLevel) + total := int64(info.PIJBPDPEODG) - int64(info.PIFNOKAMBLL[cwDifficultySourceBranch]) + int64(add) + if total < 0 || total > math.MaxInt32 || add > math.MaxInt32 || !data.SupportsDifficulty(stage.DCAHJELNJNP, uint32(total)) { + return nil, fmt.Errorf("CW xaodev difficulty outside Client table") + } + info.PIJBPDPEODG = uint32(total) + info.PIFNOKAMBLL[cwDifficultySourceBranch] = int32(add) + stage.ANJBLBEOJND = info + return &pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_CEDMLOEDPEH{CEDMLOEDPEH: info}}, nil +} + +func cwXaodevSelectionDifficulty(c *common.CurrencyWarsCatalog, run *pb.MPIMAEMCEFH, runtime *common.CurrencyWarsRuntimeState) (*pb.KFNCAIHIHCM, error) { + if !cwXaodevGenerated(run, runtime) { + return nil, nil + } + stage, _ := cwStage(run) + branch, err := cwSelectedEnemyBranch(stage.GetIMPNAHBCADN().GetMPOBBIPMNNJ()) + if err != nil { + return nil, err + } + data, err := common.LoadCurrencyWarsXaodev(c) + if err != nil { + return nil, err + } + add, err := data.BranchDifficulty(run.DivisionId, branch.CJADMFDOBAI) + if err != nil { + return nil, err + } + return cwXaodevDifficulty(c, run, add) +} diff --git a/internal/gameserver/currency_wars_xaodev_test.go b/internal/gameserver/currency_wars_xaodev_test.go new file mode 100644 index 0000000..d6e6a31 --- /dev/null +++ b/internal/gameserver/currency_wars_xaodev_test.go @@ -0,0 +1,501 @@ +package gameserver + +import ( + "os" + "path/filepath" + "testing" + + "gac-hkrpg-go/internal/common" + kcplib "gac-hkrpg-go/pkg/kcp" + pb "gac-hkrpg-go/protocol/proto" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/reflect/protoreflect" +) + +func TestCurrencyWarsXaodevEncounterAdvancesSelectsAndDoesNotDuplicateGold(t *testing.T) { + c, err := common.LoadCurrencyWarsCatalog() + if err != nil { + t.Fatal(err) + } + refs, err := common.LoadCurrencyWarsReferenceNodes(c) + if err != nil { + t.Fatal(err) + } + var route []common.CurrencyWarsRouteNode + var index int + for _, r := range c.StageRoutes { + rows, err := c.RouteNodes(r.RouteID) + if err != nil { + t.Fatal(err) + } + for i, n := range rows { + tmpl, err := refs.Template(n.NodeTemplateID) + if err == nil && tmpl.NodeType == "EliteBranch" && i > 0 { + route, index = rows, i + break + } + } + if route != nil { + break + } + } + if route == nil { + t.Fatal("no Encounter route") + } + // Build only typed components needed for a detached route transaction. + target, previous := route[index], route[index-1] + stage := &pb.BEEFLEJAOCN{EMOHLJHPOPC: target.RouteID, DCAHJELNJNP: previous.ChapterID, SectionId: previous.SectionID} + policy, err := cwLoadReferenceEnemies(c) + if err != nil { + t.Fatal(err) + } + var campID uint32 + x, err := common.LoadCurrencyWarsXaodev(c) + if err != nil { + t.Fatal(err) + } + for _, camp := range c.MonsterCamps { + if camp.SeasonID != c.ActiveSeasonID { + continue + } + for _, id := range camp.Season.MonsterList { + if policy.MonsterTiers[id] == x.Policy.MonsterTier { + campID = camp.ID + break + } + } + if campID != 0 { + break + } + } + if campID == 0 { + t.Fatal("no eligible camp") + } + // Obtain camp-list element type from its descriptor instead of copying + // an obfuscated generated type name into the fixture. + field := stage.ProtoReflect().Descriptor().Fields().ByName("HEKMEJLMGFF") + list := stage.ProtoReflect().Mutable(field).List() + for i := uint32(0); i < target.ChapterID; i++ { + entry := list.NewElement() + f := entry.Message().Descriptor().Fields().ByName("LOGHLGKIFEN") + entry.Message().Set(f, protoreflect.ValueOfUint32(campID)) + list.Append(entry) + } + for _, n := range route { + tmpl, err := refs.Template(n.NodeTemplateID) + if err != nil { + t.Fatal(err) + } + var kind uint32 + for _, k := range c.NodeTypes { + if k.Name == tmpl.NodeType { + kind = k.Value + } + } + stage.OOMMIIKPHBC = append(stage.OOMMIIKPHBC, &pb.BCCIJHPLJOJ{DCAHJELNJNP: n.ChapterID, SectionId: n.SectionID, DisplayValue: &pb.OHMDHKGAEMM{StageId: tmpl.StageID, CJCCMAACJDB: kind, PICMDONCKCO: tmpl.BasicGoldRewardNum}}) + } + player := &pb.NIHNGPKHEPC{Gold: 10} + var divisionID uint32 + for _, d := range x.Divisions { + if d.BinaryNodeDiffAddRule != 0 { + divisionID = d.DivisionID + break + } + } + run := &pb.MPIMAEMCEFH{UniqueId: 1, DivisionId: c.CapturedStages[0].ID, LJNGPCKPJLF: c.ActiveSeasonID, FMIGOKIDKJL: &pb.CHCHAJIANIM{QueuePosition: 10}, NAGDJGMMCBE: []*pb.GridFightGameInfo{ + {LEOIAOFBBFA: &pb.GridFightGameInfo_KLCPAAHDPJF{KLCPAAHDPJF: stage}}, + {LEOIAOFBBFA: &pb.GridFightGameInfo_FAOCFBOFCJP{FAOCFBOFCJP: player}}, + }} + runtime := &common.CurrencyWarsRuntimeState{RunID: run.UniqueId, Rules: map[string]uint32{}} + run.DivisionId = divisionID + if _, err := cwAdvanceRouteNode(c, run, runtime); err != nil { + t.Fatal(err) + } + layer := stage.GetIMPNAHBCADN().GetMPOBBIPMNNJ() + if len(layer.BPDABHGFBFO) != 2 || !cwXaodevGenerated(run, runtime) { + t.Fatal("generated branch/source missing") + } + _, display := cwStage(run) + again, err := cwGenerateXaodevEncounter(c, run, display) + if err != nil || !proto.Equal(stage.IMPNAHBCADN, again) { + t.Fatalf("Encounter generation unstable: %v", err) + } + pending, err := cwGeneratedRoundPreparation(c, run) + if err != nil || pending.GetBAKAKODKECM() == nil { + t.Fatalf("branch pending missing: %v", err) + } + run.FMIGOKIDKJL = pending + if _, err := cwSelectEncounter(c, run, &pb.OHAINOHGKCM{MOADBALHMFD: 3}, runtime); err == nil { + t.Fatal("unadvertised branch accepted") + } + notify, err := cwSelectEncounter(c, run, &pb.OHAINOHGKCM{MOADBALHMFD: 2}, runtime) + if err != nil { + t.Fatal(err) + } + if notify.GCPNIHHBGGD[0].JKPJFKCBIEM[1].GetCEDMLOEDPEH() == nil { + t.Fatal("difficulty not synchronized with branch") + } + add, err := x.BranchDifficulty(run.DivisionId, layer.BPDABHGFBFO[1].CJADMFDOBAI) + if err != nil { + t.Fatal(err) + } + base, _ := c.CapturedStage(run.DivisionId) + if stage.ANJBLBEOJND.PIJBPDPEODG != base.EnemyHardLevel+add { + t.Fatal("branch difficulty differs") + } + if _, err := cwEncounterRewards(c, run, layer.BPDABHGFBFO[1], 100, 0, runtime); err != nil { + t.Fatal(err) + } + if player.Gold != 10 { + t.Fatal("preview gold awarded twice") + } + before := proto.Clone(run) + if _, err := cwXaodevDifficulty(c, run, ^uint32(0)); err == nil || !proto.Equal(before, run) { + t.Fatal("invalid difficulty changed run") + } +} + +func findRepoRoot() string { + dir, err := os.Getwd() + if err != nil { + return "." + } + for i := 0; i < 6; i++ { + if _, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil { + return dir + } + parent := filepath.Dir(dir) + if parent == dir { + break + } + dir = parent + } + return "." +} + +func TestCurrencyWarsSupplyToEncounterBattleSettlementPipeline(t *testing.T) { + repoRoot := findRepoRoot() + tempDir := filepath.Join(repoRoot, "test_tmp_cw_integration") + if err := os.MkdirAll(tempDir, 0755); err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + _ = os.RemoveAll(tempDir) + }) + t.Chdir(tempDir) + + baseCfg, err := os.ReadFile(filepath.Join(repoRoot, "server.json")) + if err != nil { + t.Fatal(err) + } + if err := os.WriteFile("server.json", baseCfg, 0600); err != nil { + t.Fatal(err) + } + if err := os.WriteFile("persistent", []byte(`{}`), 0600); err != nil { + t.Fatal(err) + } + t.Setenv("GAC_CONFIG", "server.json") + + c, err := common.LoadCurrencyWarsCatalog() + if err != nil { + t.Fatal(err) + } + wd, _ := os.Getwd() + t.Logf("pipeline test wd=%s, repoRoot=%s", wd, repoRoot) + refs, err := common.LoadCurrencyWarsReferenceNodes(c) + if err != nil { + t.Fatalf("LoadCurrencyWarsReferenceNodes failed at wd=%s: %v", wd, err) + } + x, err := common.LoadCurrencyWarsXaodev(c) + if err != nil { + t.Fatal(err) + } + + routeNodes, err := c.RouteNodes(2801) + if err != nil { + t.Fatal(err) + } + policy, err := cwLoadReferenceEnemies(c) + if err != nil { + t.Fatal(err) + } + var campID uint32 + for _, camp := range c.MonsterCamps { + if camp.SeasonID != c.ActiveSeasonID { + continue + } + for _, id := range camp.Season.MonsterList { + if policy.MonsterTiers[id] == x.Policy.MonsterTier { + campID = camp.ID + break + } + } + if campID != 0 { + break + } + } + if campID == 0 { + t.Fatal("no eligible camp") + } + + var divisionID uint32 + for _, d := range x.Divisions { + if d.BinaryNodeDiffAddRule != 0 { + divisionID = d.DivisionID + break + } + } + if divisionID == 0 { + t.Fatal("no division with difficulty add rules") + } + + stage := &pb.BEEFLEJAOCN{EMOHLJHPOPC: 2801, DCAHJELNJNP: 2, SectionId: 2} + field := stage.ProtoReflect().Descriptor().Fields().ByName("HEKMEJLMGFF") + list := stage.ProtoReflect().Mutable(field).List() + for i := uint32(0); i < 4; i++ { + entry := list.NewElement() + f := entry.Message().Descriptor().Fields().ByName("LOGHLGKIFEN") + entry.Message().Set(f, protoreflect.ValueOfUint32(campID)) + list.Append(entry) + } + for _, n := range routeNodes { + tmpl, err := refs.Template(n.NodeTemplateID) + if err != nil { + t.Fatal(err) + } + var kind uint32 + for _, k := range c.NodeTypes { + if k.Name == tmpl.NodeType { + kind = k.Value + } + } + stage.OOMMIIKPHBC = append(stage.OOMMIIKPHBC, &pb.BCCIJHPLJOJ{ + DCAHJELNJNP: n.ChapterID, + SectionId: n.SectionID, + DisplayValue: &pb.OHMDHKGAEMM{ + StageId: tmpl.StageID, + CJCCMAACJDB: kind, + PICMDONCKCO: tmpl.BasicGoldRewardNum, + }, + }) + } + + firstLevel := c.PlayerLevels[0] + maxLevel := c.PlayerLevels[len(c.PlayerLevels)-1].Level + player := &pb.NIHNGPKHEPC{ + Gold: 50, + GridFightLineupHp: 100, + PPKKHHNEOHB: 100, + AFFCFLFIFPH: firstLevel.AvatarMaxNumber, + MICEOJBAKGN: 8, + EHNJDIDABHD: firstLevel.Level, + NDAINFLOLPJ: maxLevel, + } + var sampleVariant common.CurrencyWarsRoleVariant + for _, v := range c.CapturedVariants { + if v.Star == 1 { + sampleVariant = v + break + } + } + if sampleVariant.Config.ID == 0 { + t.Fatal("no star 1 role variant found") + } + + team := &pb.HKFECLCHBFJ{ + GridGameRoleList: []*pb.CDEAABODKCP{ + { + Id: sampleVariant.Config.ID, RoleStar: sampleVariant.Star, Pos: 1, UniqueId: 1, + ConvertPropertyToFixpoint: map[uint32]uint32{1: 1000}, + }, + }, + } + shop := &pb.POCJDMMANBM{} + bag := &pb.CONONEALNOF{} + run := &pb.MPIMAEMCEFH{ + UniqueId: 101, + DivisionId: divisionID, + LJNGPCKPJLF: c.ActiveSeasonID, + NAGDJGMMCBE: []*pb.GridFightGameInfo{ + {LEOIAOFBBFA: &pb.GridFightGameInfo_KLCPAAHDPJF{KLCPAAHDPJF: stage}}, + {LEOIAOFBBFA: &pb.GridFightGameInfo_FAOCFBOFCJP{FAOCFBOFCJP: player}}, + {LEOIAOFBBFA: &pb.GridFightGameInfo_EJIPINIKNCK{EJIPINIKNCK: team}}, + {LEOIAOFBBFA: &pb.GridFightGameInfo_EBHDIOGALIE{EBHDIOGALIE: shop}}, + {LEOIAOFBBFA: &pb.GridFightGameInfo_GCKLIONKMJC{GCKLIONKMJC: bag}}, + }, + } + + s := &Session{kcp: kcplib.New(1, 1, false, func([]byte) error { return nil })} + _ = s.kcp.Update(s.kcp.Now()) + s.Token = 1 + s.json = &common.FreesrData{ + Avatars: map[uint32]*common.AvatarJson{ + sampleVariant.Config.AvatarID: {AvatarID: sampleVariant.Config.AvatarID, Level: 80, Data: common.AvatarData{Rank: 6}}, + }, + } + + data, err := newCurrencyWarsData(c) + if err != nil { + t.Fatalf("newCurrencyWarsData: %v", err) + } + data.DHFOFLFCLAH = run + data.LFLCDNGALME.HFPEJIDDDJG.DivisionId = divisionID + + supplyOffer, err := cwGenerateSupplyOffer(c, run) + if err != nil { + t.Fatalf("generate supply offer: %v", err) + } + run.FMIGOKIDKJL = &pb.CHCHAJIANIM{ + QueuePosition: 1, + FGLIODAJFML: &pb.CHCHAJIANIM_FIKAGBAEOAP{FIKAGBAEOAP: supplyOffer}, + } + + runtime := &common.CurrencyWarsRuntimeState{RunID: run.UniqueId, Rules: map[string]uint32{}} + if err := s.saveCurrencyWarsDataWithRuntime(data, c, runtime); err != nil { + t.Fatalf("initial save: %v", err) + } + + // 1. Supply Selection -> Advances to Chapter 2 Section 3 (EliteBranch) + supplyReq := &pb.HMNMJKCHABE{LOAFKKAFBOK: []uint32{1}} + if _, err := cwSelectSupply(c, run, supplyReq, runtime); err != nil { + t.Fatalf("select supply: %v", err) + } + if stage.DCAHJELNJNP != 2 || stage.SectionId != 3 { + t.Fatalf("expected advance to Ch2 Sec3, got Ch%d Sec%d", stage.DCAHJELNJNP, stage.SectionId) + } + if !cwXaodevGenerated(run, runtime) { + t.Fatal("expected xaodev generated marker on Encounter node") + } + if err := s.saveCurrencyWarsDataWithRuntime(data, c, runtime); err != nil { + t.Fatalf("save after supply: %v", err) + } + + // 2. Round preparation sets pending to SelectingEncounter + prepPending, err := cwGeneratedRoundPreparation(c, run) + if err != nil || prepPending.GetBAKAKODKECM() == nil { + t.Fatalf("prepare encounter pending: %v", err) + } + run.FMIGOKIDKJL = prepPending + if err := s.saveCurrencyWarsDataWithRuntime(data, c, runtime); err != nil { + t.Fatalf("save prep pending: %v", err) + } + + // 3. Player selects Encounter Branch 2 (powerful branch) + encounterLayer := stage.GetIMPNAHBCADN().GetMPOBBIPMNNJ() + if len(encounterLayer.BPDABHGFBFO) != 2 { + t.Fatalf("expected 2 encounter branches, got %d", len(encounterLayer.BPDABHGFBFO)) + } + selReq := &pb.OHAINOHGKCM{MOADBALHMFD: 2} + selNotify, err := cwSelectEncounter(c, run, selReq, runtime) + if err != nil { + t.Fatalf("select encounter branch: %v", err) + } + if selNotify == nil || len(selNotify.GCPNIHHBGGD) == 0 { + t.Fatal("expected selection notify") + } + branchAdd, err := x.BranchDifficulty(run.DivisionId, encounterLayer.BPDABHGFBFO[1].CJADMFDOBAI) + if err != nil { + t.Fatalf("branch difficulty: %v", err) + } + baseStage, _ := c.CapturedStage(run.DivisionId) + expectedHardLevel := baseStage.EnemyHardLevel + branchAdd + if stage.ANJBLBEOJND.PIJBPDPEODG != expectedHardLevel { + t.Fatalf("difficulty got %d, want %d", stage.ANJBLBEOJND.PIJBPDPEODG, expectedHardLevel) + } + if run.FMIGOKIDKJL.GetLGNPHHBGKFH() == nil { + t.Fatal("expected pending ReturnPreparation after branch selection") + } + if err := s.saveCurrencyWarsDataWithRuntime(data, c, runtime); err != nil { + t.Fatalf("save after encounter select: %v", err) + } + + // 4. ReturnPreparation clears pending action + if _, err := cwReturnPreparation(run); err != nil { + t.Fatalf("return preparation: %v", err) + } + if run.FMIGOKIDKJL != nil { + t.Fatal("expected pending cleared after ReturnPreparation") + } + if err := s.saveCurrencyWarsDataWithRuntime(data, c, runtime); err != nil { + t.Fatalf("save after ReturnPreparation: %v", err) + } + + // 5. Build Battle with buildCurrencyWarsBattle + battleInfo, err := buildCurrencyWarsBattle(s, c, run) + if err != nil { + t.Fatalf("buildCurrencyWarsBattle: %v", err) + } + if battleInfo == nil || battleInfo.EIGMKOJOEEI == nil { + t.Fatal("battle info or grid missing") + } + if battleInfo.EIGMKOJOEEI.LODNFLMBIEH != expectedHardLevel { + t.Fatalf("battle grid hard level = %d, want %d", battleInfo.EIGMKOJOEEI.LODNFLMBIEH, expectedHardLevel) + } + if len(battleInfo.BattleAvatarList) == 0 { + t.Fatal("battle avatar list empty") + } + + // 6. Settlement with finishCurrencyWarsBattle + CURRENT_BATTLE.Store(s.Token, battleInfo) + settleReq := &pb.PVEBattleResultCsReq{ + BattleId: battleInfo.BattleId, + StageId: battleInfo.StageId, + EndStatus: pb.BattleEndStatus_BATTLE_END_WIN, + Stt: &pb.BattleStatistics{ + APAGMJNECCN: &pb.MGMJOPHJCNL{ + HFLPDCJDIAC: cwPercentScale, + DOGMMGLBJIJ: []*pb.CPNCAPLOIHI{ + {RoleId: sampleVariant.Config.ID, Damage: 50000}, + }, + }, + }, + } + goldBefore := player.Gold + if err := finishCurrencyWarsBattle(s, settleReq, battleInfo); err != nil { + t.Fatalf("finishCurrencyWarsBattle: %v", err) + } + + // 7. Verify state after settlement & next node transition + data, _, err = readCurrencyWarsData(c) + if err != nil { + t.Fatalf("load reloaded data: %v", err) + } + run = data.DHFOFLFCLAH + stage, _ = cwStage(run) + player, _, _ = cwComponents(run) + + if stage.DCAHJELNJNP != 2 || stage.SectionId != 4 { + t.Fatalf("expected advance to Ch2 Sec4, got Ch%d Sec%d", stage.DCAHJELNJNP, stage.SectionId) + } + if stage.ANJBLBEOJND.PIJBPDPEODG != baseStage.EnemyHardLevel { + t.Fatalf("expected hard level reset to base %d, got %d", baseStage.EnemyHardLevel, stage.ANJBLBEOJND.PIJBPDPEODG) + } + if stage.ANJBLBEOJND.PIFNOKAMBLL[cwDifficultySourceBranch] != 0 { + t.Fatalf("expected branch diff add 0, got %d", stage.ANJBLBEOJND.PIFNOKAMBLL[cwDifficultySourceBranch]) + } + if player.Gold <= goldBefore { + t.Fatalf("gold should have increased from %d, got %d", goldBefore, player.Gold) + } + if player.GridFightCurLevelExp == 0 && player.EHNJDIDABHD <= 1 { + t.Fatal("player exp should have increased from settlement") + } + + // 8. Full CAS roundtrip verification from persistent storage + reloadedData, _, err := readCurrencyWarsData(c) + if err != nil { + t.Fatalf("load reloaded data: %v", err) + } + reloadedStage, _ := cwStage(reloadedData.DHFOFLFCLAH) + if reloadedStage.DCAHJELNJNP != 2 || reloadedStage.SectionId != 4 { + t.Fatalf("reloaded stage = Ch%d Sec%d, want Ch2 Sec4", reloadedStage.DCAHJELNJNP, reloadedStage.SectionId) + } + if reloadedStage.ANJBLBEOJND.PIJBPDPEODG != baseStage.EnemyHardLevel { + t.Fatalf("reloaded hard level = %d, want %d", reloadedStage.ANJBLBEOJND.PIJBPDPEODG, baseStage.EnemyHardLevel) + } + if reloadedStage.ANJBLBEOJND.PIFNOKAMBLL[cwDifficultySourceBranch] != 0 { + t.Fatalf("reloaded branch diff = %d, want 0", reloadedStage.ANJBLBEOJND.PIFNOKAMBLL[cwDifficultySourceBranch]) + } + if s.currencyWarsRevision < 5 { + t.Fatalf("expected monotonic CAS revisions >= 5, got %d", s.currencyWarsRevision) + } +} diff --git a/internal/gameserver/dispatch.go b/internal/gameserver/dispatch.go index 6088820..a73578f 100644 --- a/internal/gameserver/dispatch.go +++ b/internal/gameserver/dispatch.go @@ -64,8 +64,11 @@ func handle[ReqT any, RspT any](name string, fn func(s *Session, req *ReqT, rsp func handleNamed[ReqT any, RspT any](name, rspName string, fn func(s *Session, req *ReqT, rsp *RspT) error) { reqID, ok := goproto.CmdIDByName[name+"CsReq"] if !ok { - Log("[DISPATCH] loaded protocol has no " + name + "CsReq; handler skipped") - return + reqID, ok = goproto.CmdIDByName[name] + if !ok { + Log("[DISPATCH] loaded protocol has no " + name + "CsReq or " + name + "; handler skipped") + return + } } rspID, ok := goproto.CmdIDByName[rspName] if !ok { diff --git a/internal/gameserver/handlers_currency_wars.go b/internal/gameserver/handlers_currency_wars.go index 3fc64f6..d580661 100644 --- a/internal/gameserver/handlers_currency_wars.go +++ b/internal/gameserver/handlers_currency_wars.go @@ -116,7 +116,9 @@ func init() { rsp.Retcode = uint32(goproto.Retcode_RET_GRID_FIGHT_PARAM_NOT_MATCH) return nil } - if revision != s.currencyWarsRevision { + if s.currencyWarsRevision == 0 { + s.currencyWarsRevision = revision + } else if revision != s.currencyWarsRevision { rsp.Retcode = uint32(goproto.Retcode_RET_GRID_FIGHT_PARAM_NOT_MATCH) return nil } @@ -144,13 +146,16 @@ func init() { return nil } addedSpotlights := cwEnsureSpotlights(data.DHFOFLFCLAH) - if len(groups) > 0 || addedSpotlights { - if e = s.saveCurrencyWarsData(data, catalog); e != nil { + runtime, _ := cwBonfireRuntime(catalog, data.DHFOFLFCLAH) + cwSyncTraitComponent(catalog, data.DHFOFLFCLAH, runtime) + recovered, _ := s.cwRecoverMissingPendingAction(data, catalog) + if len(groups) > 0 || addedSpotlights || recovered { + if e = s.saveCurrencyWarsDataWithRuntime(data, catalog, runtime); e != nil { return nil } // Resume is followed by GetData. Return the reconciled roster in // that snapshot, after the Client constructs its game session. - PacketLog(fmt.Sprintf("CW_RESUME merged=%d revision=%d", len(groups), s.currencyWarsRevision)) + PacketLog(fmt.Sprintf("CW_RESUME merged=%d recovered=%t revision=%d", len(groups), recovered, s.currencyWarsRevision)) } } } @@ -172,10 +177,74 @@ func init() { if err != nil { return err } - rsp.NFEDANFBIBE = &goproto.GAAMBBFPCHB{EndTime: *cfg.ActivitySchedule.EndTime} - PacketLog(fmt.Sprintf("CYCLE_SCORE_REWARD_DATA end_time=%d score=0 bootstrap=true policy=activity_window", *cfg.ActivitySchedule.EndTime)) + rsp.NFEDANFBIBE = &goproto.GAAMBBFPCHB{ + EndTime: *cfg.ActivitySchedule.EndTime, + } + if cfg.ActivitySchedule.BeginTime != nil { + rsp.NFEDANFBIBE.IFEPJHGNCOI = *cfg.ActivitySchedule.BeginTime + } + if scoreCatalog, err := common.LoadCurrencyWarsScore(nil); err == nil { + rsp.NFEDANFBIBE.PMJGAJGIFOA = scoreCatalog.WeeklyCap + for _, r := range scoreCatalog.WeeklyRanks { + rsp.NFEDANFBIBE.OPLNKCOLCKJ = append(rsp.NFEDANFBIBE.OPLNKCOLCKJ, r.Rank) + } + } + PacketLog(fmt.Sprintf("CYCLE_SCORE_REWARD_DATA end_time=%d weekly_cap=%d ranks=%d policy=score_catalog", *cfg.ActivitySchedule.EndTime, rsp.NFEDANFBIBE.PMJGAJGIFOA, len(rsp.NFEDANFBIBE.OPLNKCOLCKJ))) return nil }) + + handle("CycleScoreRewardTakeReward", func(s *Session, req *goproto.CycleScoreRewardTakeRewardCsReq, rsp *goproto.CycleScoreRewardTakeRewardScRsp) error { + cfg, err := common.LoadRuntimeConfig() + if err != nil { + return err + } + rsp.Reward = &goproto.ItemList{} + if req != nil { + rsp.NGKOFMBBGNL = append(rsp.NGKOFMBBGNL, req.JKCEBHFIENI...) + } + rsp.NFEDANFBIBE = &goproto.GAAMBBFPCHB{ + EndTime: *cfg.ActivitySchedule.EndTime, + } + if cfg.ActivitySchedule.BeginTime != nil { + rsp.NFEDANFBIBE.IFEPJHGNCOI = *cfg.ActivitySchedule.BeginTime + } + if scoreCatalog, err := common.LoadCurrencyWarsScore(nil); err == nil { + rsp.NFEDANFBIBE.PMJGAJGIFOA = scoreCatalog.WeeklyCap + for _, r := range scoreCatalog.WeeklyRanks { + rsp.NFEDANFBIBE.OPLNKCOLCKJ = append(rsp.NFEDANFBIBE.OPLNKCOLCKJ, r.Rank) + } + } + rsp.Retcode = 0 + PacketLog(fmt.Sprintf("CYCLE_SCORE_REWARD_TAKE claims=%v", rsp.NGKOFMBBGNL)) + return nil + }) + + cwHandle("GridFightTakeWeeklyReward", func(s *Session, req *goproto.GridFightTakeWeeklyRewardCsReq, rsp *goproto.GridFightTakeWeeklyRewardScRsp) error { + cfg, err := common.LoadRuntimeConfig() + if err != nil { + return err + } + rsp.Reward = &goproto.ItemList{} + if req != nil { + rsp.NGKOFMBBGNL = append(rsp.NGKOFMBBGNL, req.JKCEBHFIENI...) + } + rsp.MGBNEAHCDMA = &goproto.NDLNIKCCALK{ + EndTime: *cfg.ActivitySchedule.EndTime, + } + if cfg.ActivitySchedule.BeginTime != nil { + rsp.MGBNEAHCDMA.IFEPJHGNCOI = *cfg.ActivitySchedule.BeginTime + } + if scoreCatalog, err := common.LoadCurrencyWarsScore(nil); err == nil { + rsp.MGBNEAHCDMA.EDHKHJAPMKA = scoreCatalog.WeeklyCap + for _, r := range scoreCatalog.WeeklyRanks { + rsp.MGBNEAHCDMA.OPLNKCOLCKJ = append(rsp.MGBNEAHCDMA.OPLNKCOLCKJ, r.Rank) + } + } + rsp.Retcode = 0 + PacketLog(fmt.Sprintf("CW_TAKE_WEEKLY_REWARD claims=%v", rsp.NGKOFMBBGNL)) + return nil + }) + // Bootstrap only. Keep the danger-family guard for every unsupported CW // operation; it must never receive a successful empty gameplay response. cwHandle("GridFightGetData", func(s *Session, req *goproto.GridFightGetDataCsReq, rsp *goproto.GridFightGetDataScRsp) error { @@ -188,10 +257,21 @@ func init() { return err } if saved != nil { - proto.Merge(rsp, saved) s.currencyWarsRevision = revision s.currencyWarsOpen = saved.DHFOFLFCLAH != nil - PacketLog(fmt.Sprintf("CW_GET_DATA restored=true revision=%d active_run=%t", revision, saved.DHFOFLFCLAH != nil)) + if saved.DHFOFLFCLAH != nil { + runtime, _ := cwBonfireRuntime(catalog, saved.DHFOFLFCLAH) + cwSyncTraitComponent(catalog, saved.DHFOFLFCLAH, runtime) + if recovered, recErr := s.cwRecoverMissingPendingAction(saved, catalog); recErr != nil { + PacketLog("CW_GET_DATA recovery failed: " + recErr.Error()) + } else if recovered { + if err := s.saveCurrencyWarsDataWithRuntime(saved, catalog, runtime); err != nil { + PacketLog("CW_GET_DATA save recovery failed: " + err.Error()) + } + } + } + proto.Merge(rsp, saved) + PacketLog(fmt.Sprintf("CW_GET_DATA restored=true revision=%d active_run=%t", s.currencyWarsRevision, saved.DHFOFLFCLAH != nil)) return nil } s.currencyWarsRevision = 0 @@ -204,3 +284,46 @@ func init() { return nil }) } + +// cwRecoverMissingPendingAction detects if an active run is in an interactive non-battle stage +// (such as Supply) where a pending action was dropped or interrupted, and regenerates the offer. +func (s *Session) cwRecoverMissingPendingAction(data *goproto.GridFightGetDataScRsp, catalog *common.CurrencyWarsCatalog) (bool, error) { + if data == nil || data.DHFOFLFCLAH == nil { + return false, nil + } + run := data.DHFOFLFCLAH + stage, display := cwStage(run) + if stage == nil || display == nil { + return false, nil + } + name, err := catalog.NodeTypeName(display.CJCCMAACJDB) + if err != nil { + return false, err + } + if name == "Supply" && (run.FMIGOKIDKJL == nil || run.FMIGOKIDKJL.GetFIKAGBAEOAP() == nil) { + offer, err := cwGenerateSupplyOffer(catalog, run) + if err != nil { + return false, err + } + queue := run.GetFMIGOKIDKJL().GetQueuePosition() + if queue == 0 { + queue = uint32(s.currencyWarsRevision + 1) + } + run.FMIGOKIDKJL = &goproto.CHCHAJIANIM{ + QueuePosition: queue, + FGLIODAJFML: &goproto.CHCHAJIANIM_FIKAGBAEOAP{ + FIKAGBAEOAP: offer, + }, + } + player, _, _ := cwComponents(run) + if player != nil { + player.GAJGBBFELEL = &goproto.GMBMNFLMKPI{ + IBCPAKJAPFF: goproto.KMJNPEMPNMC_KMJNPEMPNMC_AEINKINPAFL, + LMICDNNABNM: goproto.GGNKDLAHAGH_GGNKDLAHAGH_FHOHPFBDNOH, + } + } + PacketLog(fmt.Sprintf("CW_RECOVER_SUPPLY restored pending offer queue=%d choices=%d", queue, len(offer.MFBAKLCBPCJ))) + return true, nil + } + return false, nil +} diff --git a/internal/gameserver/handlers_equipment_test.go b/internal/gameserver/handlers_equipment_test.go index 57db6f3..3832471 100644 --- a/internal/gameserver/handlers_equipment_test.go +++ b/internal/gameserver/handlers_equipment_test.go @@ -49,8 +49,11 @@ func TestLightconeSwapAndUnequipPersistAssignments(t *testing.T) { } func TestRelicSwapUnequipAndSlotValidationPersistAssignments(t *testing.T) { + if _, err := supportedRelicIDs(); err != nil { + t.Fatalf("preload inventory items: %v", err) + } t.Chdir(t.TempDir()) - writeEquipmentFixture(t, `{"lightcones":[],"relics":[{"relic_id":31012,"internal_uid":11,"equip_avatar":1201},{"relic_id":32012,"internal_uid":12,"equip_avatar":1202}]}`) + writeEquipmentFixture(t, `{"lightcones":[],"relics":[{"relic_id":31012,"internal_uid":11,"equip_avatar":1201},{"relic_id":31022,"internal_uid":12,"equip_avatar":1202}]}`) player := &common.FreesrData{ Avatars: map[uint32]*common.AvatarJson{ @@ -59,7 +62,7 @@ func TestRelicSwapUnequipAndSlotValidationPersistAssignments(t *testing.T) { }, Relics: []common.Relic{ {RelicID: 31012, InternalUID: 11, EquipAvatar: 1201}, - {RelicID: 32012, InternalUID: 12, EquipAvatar: 1202}, + {RelicID: 31022, InternalUID: 12, EquipAvatar: 1202}, }, } session := &Session{json: player} diff --git a/internal/gameserver/session.go b/internal/gameserver/session.go index 1a6a01d..9b11195 100644 --- a/internal/gameserver/session.go +++ b/internal/gameserver/session.go @@ -207,6 +207,9 @@ func (s *Session) SendRaw(pkt *NetPacket) error { s.kcpMu.Lock() defer s.kcpMu.Unlock() + if s.kcp == nil { + return nil + } if _, err := s.kcp.Send(payload); err != nil { return err } diff --git a/internal/gameserver/smoke_test.go b/internal/gameserver/smoke_test.go index 298dae1..c08c077 100644 --- a/internal/gameserver/smoke_test.go +++ b/internal/gameserver/smoke_test.go @@ -105,7 +105,7 @@ func TestGatewayHandshakeAndLogin(t *testing.T) { t.Log("SceneCastSkill fix verified: no battle triggered for non-monster targets") // 5. attacking a real scene monster DOES start a battle - serverSession.MonsterEntities[30001] = 30124011 + serverSession.MonsterEntities[30001] = 101201 skillReq.HitTargetEntityIdList = []uint32{30001} reqBody, _ = proto.Marshal(skillReq) if err := client.sendCmd(skillReqID, reqBody); err != nil { diff --git a/internal/sdkserver/api_currency_wars.go b/internal/sdkserver/api_currency_wars.go index 5c9b7dc..ba30a54 100644 --- a/internal/sdkserver/api_currency_wars.go +++ b/internal/sdkserver/api_currency_wars.go @@ -14,7 +14,7 @@ import ( func apiCurrencyWarsCommand(w http.ResponseWriter, command string) { fail := func(err error) { writeJSON(w, map[string]any{"success": false, "output": err.Error()}) } parts := strings.Fields(command) - usage := "Usage: /cw status | /cw list role|equip|orb|consumable|forge | /cw gold | /cw hp | /cw exp | /cw equip|orb|consumable|forge [count] | /cw role [count]. Changes apply in CW Preparation." + usage := "Usage: /cw status | /cw list role|equip|orb|consumable|forge | /cw gold | /cw hp | /cw exp | /cw tbpower [costed] | /cw equip|orb|consumable|forge [count] (for orb: or 'random') | /cw role [count]. Changes apply in CW Preparation." if len(parts) < 2 { fail(fmt.Errorf("%s", usage)) return @@ -132,18 +132,38 @@ func apiCurrencyWarsCommand(w http.ResponseWriter, command string) { return } field := strings.ToLower(parts[1]) + if field == "tb" || field == "trailblaze" { + field = "tbpower" + } if field == "status" && len(parts) == 2 { - writeJSON(w, map[string]any{"success": true, "output": fmt.Sprintf("CW run=%d revision=%d gold=%d hp=%d/%d level=%d", run.UniqueId, snapshot.Revision, player.Gold, player.GridFightLineupHp, player.PPKKHHNEOHB, player.EHNJDIDABHD)}) + tbPower := uint32(0) + if snapshot.Runtime != nil && snapshot.Runtime.Rules != nil { + tbPower = snapshot.Runtime.Rules["TbPower"] + } + writeJSON(w, map[string]any{"success": true, "output": fmt.Sprintf("CW run=%d revision=%d gold=%d tbpower=%d hp=%d/%d level=%d", run.UniqueId, snapshot.Revision, player.Gold, tbPower, player.GridFightLineupHp, player.PPKKHHNEOHB, player.EHNJDIDABHD)}) return } - if len(parts) < 3 || len(parts) > 5 || (field != "gold" && field != "hp" && field != "exp" && field != "equip" && field != "role" && field != "orb" && field != "consumable" && field != "forge") { + isRandomOrb := false + if field == "orb" { + if len(parts) == 2 { + isRandomOrb = true + } else if len(parts) >= 3 && (strings.EqualFold(parts[2], "random") || strings.EqualFold(parts[2], "rand")) { + isRandomOrb = true + } + } + if (!isRandomOrb && (len(parts) < 3 || len(parts) > 5)) || (isRandomOrb && len(parts) > 4) || (field != "gold" && field != "hp" && field != "exp" && field != "equip" && field != "role" && field != "orb" && field != "consumable" && field != "forge" && field != "tbpower") { fail(fmt.Errorf("%s", usage)) return } - value, err := strconv.ParseUint(parts[2], 10, 32) - if err != nil { - fail(fmt.Errorf("value must be a nonnegative uint32")) - return + var value uint64 + if isRandomOrb { + value = 0 + } else { + value, err = strconv.ParseUint(parts[2], 10, 32) + if err != nil { + fail(fmt.Errorf("value must be a nonnegative uint32")) + return + } } if field == "hp" && (value == 0 || value > uint64(player.PPKKHHNEOHB)) { fail(fmt.Errorf("HP must be 1..%d", player.PPKKHHNEOHB)) @@ -171,31 +191,51 @@ func apiCurrencyWarsCommand(w http.ResponseWriter, command string) { _, err = catalog.CapturedRoleVariant(uint32(value), star) } case "orb", "consumable", "forge": + if isRandomOrb { + if len(parts) == 4 { + quantity, err = parse(parts[3]) + } + } else { + if len(parts) > 4 { + fail(fmt.Errorf("%s", usage)) + return + } + if len(parts) == 4 { + quantity, err = parse(parts[3]) + } + if err == nil { + var rows *common.CurrencyWarsRewards + rows, err = common.LoadCurrencyWarsRewards(catalog) + if err == nil { + if field == "forge" { + if _, ok := rows.Forges[uint32(value)]; !ok { + err = fmt.Errorf("unknown forge %d", value) + } + } else if field == "orb" { + if _, ok := rows.Orbs[uint32(value)]; !ok { + err = fmt.Errorf("unknown Orb %d", value) + } + } else { + if _, ok := rows.Consumables[uint32(value)]; !ok { + err = fmt.Errorf("unknown consumable %d", value) + } + } + } + } + } + case "tbpower": if len(parts) > 4 { fail(fmt.Errorf("%s", usage)) return } if len(parts) == 4 { - quantity, err = parse(parts[3]) - } - if err == nil { - var rows *common.CurrencyWarsRewards - rows, err = common.LoadCurrencyWarsRewards(catalog) - if err == nil { - if field == "forge" { - if _, ok := rows.Forges[uint32(value)]; !ok { - err = fmt.Errorf("unknown forge %d", value) - } - } else if field == "orb" { - if _, ok := rows.Orbs[uint32(value)]; !ok { - err = fmt.Errorf("unknown Orb %d", value) - } - } else { - if _, ok := rows.Consumables[uint32(value)]; !ok { - err = fmt.Errorf("unknown consumable %d", value) - } - } + var costed uint64 + costed, err = strconv.ParseUint(parts[3], 10, 32) + if err != nil { + fail(fmt.Errorf("costed must be a nonnegative uint32")) + return } + star = uint32(costed) + 1 } case "equip": if len(parts) > 4 { @@ -229,5 +269,9 @@ func apiCurrencyWarsCommand(w http.ResponseWriter, command string) { fail(err) return } - writeJSON(w, map[string]any{"success": true, "state": "queued", "request_id": id, "output": fmt.Sprintf("Queued CW %s=%d star=%d count=%d for run %d. Applies at the next heartbeat in Preparation; check CW_GM log for the applied result.", field, value, star, quantity, run.UniqueId)}) + valDesc := fmt.Sprintf("%d", value) + if isRandomOrb { + valDesc = "random" + } + writeJSON(w, map[string]any{"success": true, "state": "queued", "request_id": id, "output": fmt.Sprintf("Queued CW %s=%s star=%d count=%d for run %d. Applies at the next heartbeat in Preparation; check CW_GM log for the applied result.", field, valDesc, star, quantity, run.UniqueId)}) } diff --git a/persistent b/persistent index 889def5..9c41df1 100644 --- a/persistent +++ b/persistent @@ -1,29 +1,35 @@ { - "aa_round_selection": { - "client_version": "4.6.53", - "peak_group_id": 2 - }, "active_team_preset": 3, + "aha_help_level": 3, "auto_technique_food": true, + "camera_settings": { + "distance": 2, + "mode": "facecam" + }, "currency_wars": { "52022": { - "assembly_sha256": "c5e02e6e984f7e5577c78b995a8efeee2d06ee22e67d484ad5e859c8bf21e144", - "client_version": "4.6.53", "format_version": 1, - "revision": 547, - "runtime": { - "rules": { - "PetPalStarDown": 40, - "PetPalStarDownApplied/1/5": 1, - "PetPalStarDownApplied/1/6": 1, - "PetPalStarDownApplied/2/1": 1 - }, - "run_id": 1 - }, "uid": 52022, - "wire_data": "GkBCHAoAMgoIARIGCCE4BmgBMgoIAhIGCEg4BmgCWAJiIBCFnQEgAkoQGgAiACoAMgA6AFoAcgB6AFIAWAFyAHgGOpMUGAIqHDCjBMpxFiABKgRoiasHKgRoiqsHKgRotJAGcAEyCSoH+q8VzozaEEgBUp8BGg0IBCoGSgL1TnBp4hQAGk8ICCoGSgLpUnBpqi1CegQIBxAwegQICRBIegQIARACegQIChAAegQIBRAQegQIAxAEegQIBhAkegQIAhACegQICBA8egQICxAAegQIBBAGGgwICSoFSgEAcAX6NQAaDQgTKgZKAsk+cG/6XwAaDwg4KgZKAsMXcGu6ZQIgAhoPCF4qBkoC3R9wa7plAiABYi5iLAhkEAUYCiAHKAMwCTgKQOjY/w9ICFBkWApgBLIYBGADeAbSNATV+7EDglEAYu8JCuwJEgsIBxgGMgMDBAVYCyplGAEonpYJMGU4BkIFCDQQ8AFCBQg1EOADQgUILBCwAkIFCPYHEGRCBQggELQBQgUIgQgQZEIFCDsQwgNCBQj1BxBkQgUIMRC0AUIFCPsHEGRCBQg5EPABQgUIIRDgA0IFCCIQuAMqZRgCKOaXCTBsOAlCBQiBCBBkQgUIMRC0AUIFCPUHEGRCBQg5EPABQgUIOxDCA0IFCCEQ4ANCBQj2BxBkQgUI+wcQZEIFCDUQ4ANCBQg0EPABQgUIIBC0AUIFCCIQuANCBQgsELACKmUYAiiq8CQwbTgFQgUIgQgQZEIFCDkQ8AFCBQj7BxBkQgUIMRC0AUIFCPUHEGRCBQgsELACQgUIOxDCA0IFCCAQtAFCBQj2BxBkQgUIIhC4A0IFCCEQ4ANCBQg0EPABQgUINRDgAyppGAEiAoUBKIaeCTB5OARCBQghEOADQgUI9QcQZEIFCIEIEGRCBQgxELQBQgUIIhC4A0IFCDQQ8AFCBQggELQBQgUI9gcQZEIFCDkQ8AFCBQgsELACQgUI+wcQZEIFCDsQwgNCBQg1EOADKmUYAiiG8TAwfTgHQgUINRDgA0IFCDsQwgNCBQj2BxBkQgUILBCwAkIFCIEIEGRCBQggELQBQgUIIhC4A0IFCPUHEGRCBQgxELQBQgUIORDwAUIFCCEQ4ANCBQj7BxBkQgUINBDwASpmGAIo8v0HMIEBOANCBQj1BxBkQgUIMRC0AUIFCDsQwgNCBQj2BxBkQgUINRDgA0IFCCIQuANCBQgsELACQgUIORDwAUIFCCEQ4ANCBQggELQBQgUI+wcQZEIFCIEIEGRCBQg0EPABKmYYASjSrAcwggE4CEIFCIEIEGRCBQgiELgDQgUI+wcQZEIFCCEQ4ANCBQj2BxBkQgUIOxDCA0IFCDUQ4ANCBQg5EPABQgUI9QcQZEIFCDQQ8AFCBQgxELQBQgUIIBC0AUIFCCwQsAIqZhgCKMicCTCLATgCQgUIMRC0AUIFCCEQ4ANCBQggELQBQgUINRDgA0IFCDQQ8AFCBQj7BxBkQgUIOxDCA0IFCCwQsAJCBQj1BxBkQgUIgQgQZEIFCDkQ8AFCBQj2BxBkQgUIIhC4AypmGAIoyJwJMI8BOA5CBQgiELgDQgUIIBC0AUIFCPsHEGRCBQiBCBBkQgUINRDgA0IFCDsQwgNCBQghEOADQgUI9QcQZEIFCDQQ8AFCBQg5EPABQgUI9gcQZEIFCCwQsAJCBQgxELQBKmYYASjInAkwkgE4EUIFCCwQsAJCBQghEOADQgUI9gcQZEIFCDsQwgNCBQgiELgDQgUI9QcQZEIFCDQQ8AFCBQg1EOADQgUIORDwAUIFCDEQtAFCBQiBCBBkQgUI+wcQZEIFCCAQtAEqZhgBKMicCTCTATgPQgUIOxDCA0IFCCEQ4ANCBQj7BxBkQgUI9QcQZEIFCDQQ8AFCBQg1EOADQgUILBCwAkIFCDEQtAFCBQggELQBQgUI9gcQZEIFCIEIEGRCBQgiELgDQgUIORDwASpmGAMo1rMHMJoBOAFCBQg5EPABQgUI+wcQZEIFCDQQ8AFCBQiBCBBkQgUIMRC0AUIFCPYHEGRCBQgiELgDQgUINRDgA0IFCDsQwgNCBQj1BxBkQgUILBCwAkIFCCAQtAFCBQghEOADYjcyNSoMCAEQBFgDaObRqakBKgwIARAEWARo6t+vqQEqDAgBEARYBWj226+pASoJEAlYhQFogLAVYpoCepcCCh4SBBAFKAESBBAKKAISBBAUKAMSBBAoKAQSBBAZKAUaC2gE2lcGGAF45rYHGgtoBdpXBhgBeJaRBhoLaAXaVwYYAXiClwkaC2gE2lcGGAF4jsgIGgtoAdpXBhgBeIa9B2gCerEBspAGlpEG3t0Gpt8G7uAG4uQG7qsH0qwHtq0H4q8HqrEHjrIH8rIH1rMHurQHnrUH5rYHyrcHkrkH9rkH2roHorwHhr0H4voHqvwHjv0H8v0H1v4Huv8H5oEIyoIIroMI9oQIhogIxsYIqscIjsgI8sgI1skIusoInssIgswI5swIys0Iks8I9s8IvtEI1pQJupUJnpYJgpcJ5pcJypgJrpkJ2psJvpwJop0Jhp4JhvEwYgIaAGK3BBK0BBocEggIk5OL7wEwARIHCJ/JuAEwARIHCJrKx18wASgCMgJgGzICYBkyAmAGOPEVQAFaAGIXCAEoAUIREIG7sCEyAoQHOI7RBWAFcAFiGAgCKAFCEhCDu7AhMgFlOO3OBUABYApwAmIQCAMoAUIKEIW7sCEyAQFwBWIWCAQoAUIQEIa7sCEyAXE47s4FYApwA2ImCAUoAUIgEIi7sCEaBdAj9pwGMgKGBzis0AVYAWAKcAF6BJnM7gRiFggGKAFCEBCJu7AhMgFpOMjPBWAPcARiGwgBKAJCFRCKu7AhIAEyAskBOPfOBUABYApwAmIQCAIoAkIKEIy7sCEyAQJwBWIXCAMoAkIREI27sCEyAtUBOPjOBWAKcANiFwgEKAJCERCPu7AhMgKHBzit0AVgCnABYhcIBSgCQhEQkLuwITICzQE4yc8FYA9wBGIZCAEoA0ITEJG7sCEyAq0COIHPBUABYApwAmIQCAIoA0IKEJO7sCEyAQNwBWIXCAMoA0IREJS7sCEyAs0COILPBWAKcANiFwgEKANCERCWu7AhMgKIBziu0AVgCnABYhUIBSgDQg8Ql7uwITIC6xc4ys8FcARqbuICa1AZWmUKKlgBYggIuoD/7gEwAWIICPrKwv4CMAFiCAjwlJmPATABYggI6fD+7gEwAwozWAJiCAjwlJmPATACYggI8JSZjwEwAmIHCLmC5DAwAWIICKq7wv4CMAFiCAiqu8L+AjABWPfOBWgBcgIaAGIKQghiBigBWIWOBmIzUjFCCTIExz7JPljHPkIJMgSDVoRWWINWWgcIppQGYgEGWgcItp4MYgEGWgcIqqIMYgEGYgBiHmocMEtCCQjSlgYQ1/CgMEIJCMKTBhDXu4gwWI+cPWIS0mcPCg0I0awHEHpImgFo1rMHYgIqAGINgnwKGggIAUD2nAZIAnCFnQE=" + "client_version": "4.6.53", + "assembly_sha256": "c5e02e6e984f7e5577c78b995a8efeee2d06ee22e67d484ad5e859c8bf21e144", + "revision": 989, + "wire_data": "GkBCHAoAMgoIARIGCCE4BmgBMgoIAhIGCEg4BmgCWAJiIBCFnQEgAkoQGgAiACoAMgA6AFoAcgB6AFIAWAFyAHgG", + "runtime": { + "run_id": 951 + } } }, + "currency_wars_commands": [ + { + "id": "ab08c817e3047182cb494d22b45e1ffc", + "uid": 52022, + "run_id": 951, + "field": "role", + "value": 120402, + "quantity": 1, + "star": 4 + } + ], "dressed_skins": {}, "enable_aha_global": true, "enable_castorice_global": true, @@ -39,7 +45,7 @@ "2": 1313, "3": 1414 }, - "main_character": "MaleRemembrance", + "main_character": "MaleElation", "march_type": "MarchHunt", "player_outfits": [ 1013, @@ -47,10 +53,10 @@ 4001 ], "position": { - "rot_y": 270000, - "x": 57408, - "y": 678, - "z": 753626 + "rot_y": 45000, + "x": 28515, + "y": -4933, + "z": 1080 }, "profile": { "display_avatars": [ @@ -79,9 +85,9 @@ "signature": "test" }, "scene": { - "entry_id": 2057102, - "floor_id": 20571001, - "plane_id": 20571 + "entry_id": 102020107, + "floor_id": 10202001, + "plane_id": 10202 }, "scene_group_properties": { "20571001": { @@ -246,6 +252,50 @@ ] ] }, + "20285": { + "buffs": [ + 3031353, + 3031354, + 3031353 + ], + "nodes": { + "0": { + "result": "EgcIkk4Q5KwCEgcI0Q8Q5KwCEgcI0g8Q5KwCMOSsAkgBemoKFwjhCxADIFAyDAhQEJK0ARhQIAYoAToAChkI3gsQAxgBIFAyDAhNEI60ARhQIAYoAToAChkIwT4QAxgCIFAyDAhZEMC7ARhQIAYoBToAChkIwQkQAxgDIFAyDAgmEOmzARhQIAYoBToA", + "score": 38500, + "won": true + }, + "1": { + "result": "EgcIkk4QoJkCEgcI0Q8QoJkCEgcI0g8QoJkCMKCZAkABSAF6agoXCOYLEAMgUDIMCFUQlLQBGFAgBigFOgAKGQiaChADGAEgUDIMCDMQ27MBGFAgBigBOgAKGQiECxADGAIgUDIMCEgQiLQBGFAgBigFOgAKGQiGCxADGAMgUDIMCEoQi7QBGFAgBigBOgA=", + "score": 36000, + "won": true + }, + "2": { + "result": "EgcIkk4QvOYBEgcI0Q8QvOYBEgcI0g8QvOYBMLzmAUACSAF6agoXCOILEAMgUDIMCFEQkbQBGFAgBigFOgAKGQjdCxADGAEgUDIMCEwQjbQBGFAgBigBOgAKGQjnCxADGAIgUDIMCFYQmbQBGFAgBigBOgAKGQjfCxADGAMgUDIMCE4QyKQBGFAgBigFOgA=", + "score": 29500, + "won": true + } + }, + "teams": [ + [ + 1505, + 1502, + 8001, + 1217 + ], + [ + 1510, + 1306, + 1412, + 1414 + ], + [ + 1506, + 1501, + 1511, + 1503 + ] + ] + }, "30215": { "buffs": [ 3111094, diff --git a/resources/CurrencyWarsAugments.json b/resources/CurrencyWarsAugments.json new file mode 100644 index 0000000..241b274 --- /dev/null +++ b/resources/CurrencyWarsAugments.json @@ -0,0 +1,4319 @@ +{ + "format_version": 1, + "target_client_version": "4.6.53", + "target_assembly_sha256": "c5e02e6e984f7e5577c78b995a8efeee2d06ee22e67d484ad5e859c8bf21e144", + "provenance": { + "repository": "https://gaytea.cod-algieba.ts.net/xaodev/Currency-War-SR", + "commit": "7d342af29fc9e4fec49bf5202c0c8207c1ba9f71", + "license": "AGPL-3.0-only", + "inputs_sha256": { + "augments.json": "649d3c4c7c75bf75935b3ed45ad5fafdc23c2db9ad9428dc1b56f01a07610fc6" + } + }, + "season_id": 2, + "augments": [ + { + "id": 100101, + "quality": 1, + "chapters": [ + 1 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 100102, + "quality": 1, + "chapters": [ + 2 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 100201, + "quality": 1, + "chapters": [ + 1 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 100301, + "quality": 1, + "chapters": [ + 2, + 3 + ], + "oc": true, + "grants": [ + { + "kind": "refresh", + "count": 11 + } + ], + "grants_complete": true + }, + { + "id": 100401, + "quality": 1, + "chapters": [ + 1 + ], + "oc": true, + "grants": [ + { + "kind": "cost_role", + "count": 4, + "cost": 2 + } + ], + "grants_complete": true + }, + { + "id": 100402, + "quality": 1, + "chapters": [ + 2 + ], + "oc": true, + "grants": [ + { + "kind": "cost_role", + "count": 4, + "cost": 3 + } + ], + "grants_complete": true + }, + { + "id": 100403, + "quality": 1, + "chapters": [ + 3 + ], + "oc": true, + "grants": [ + { + "kind": "cost_role", + "count": 4, + "cost": 4 + } + ], + "grants_complete": true + }, + { + "id": 100501, + "quality": 1, + "chapters": [ + 1 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 100601, + "quality": 1, + "chapters": [ + 1, + 2 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 100701, + "quality": 1, + "chapters": [ + 1 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 100702, + "quality": 1, + "chapters": [ + 2 + ], + "oc": true, + "grants": [ + { + "kind": "basic", + "count": 1 + }, + { + "kind": "gold", + "count": 4 + } + ], + "grants_complete": false + }, + { + "id": 100901, + "quality": 1, + "chapters": [ + 1 + ], + "oc": true, + "grants": [ + { + "kind": "gold", + "count": 4 + } + ], + "grants_complete": true + }, + { + "id": 101001, + "quality": 1, + "chapters": [ + 2, + 3 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 101101, + "quality": 1, + "chapters": [ + 1 + ], + "oc": false, + "grants": [], + "grants_complete": true + }, + { + "id": 101102, + "quality": 1, + "chapters": [ + 2 + ], + "oc": false, + "grants": [], + "grants_complete": true + }, + { + "id": 101201, + "quality": 1, + "chapters": [ + 2 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 101202, + "quality": 1, + "chapters": [ + 3 + ], + "oc": true, + "grants": [ + { + "kind": "gold", + "count": 6 + } + ], + "grants_complete": true + }, + { + "id": 101301, + "quality": 1, + "chapters": [ + 1, + 2 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 101401, + "quality": 1, + "chapters": [ + 1 + ], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 99999 + }, + { + "kind": "gold", + "count": 4 + } + ], + "grants_complete": true + }, + { + "id": 101402, + "quality": 1, + "chapters": [ + 2 + ], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 99999 + }, + { + "kind": "gold", + "count": 8 + } + ], + "grants_complete": true + }, + { + "id": 101501, + "quality": 1, + "chapters": [ + 2 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 101601, + "quality": 1, + "chapters": [ + 1 + ], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 350105 + } + ], + "grants_complete": true + }, + { + "id": 101602, + "quality": 1, + "chapters": [ + 2 + ], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 350105 + } + ], + "grants_complete": true + }, + { + "id": 101701, + "quality": 1, + "chapters": [ + 1, + 2 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 101801, + "quality": 1, + "chapters": [ + 2 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 101901, + "quality": 1, + "chapters": [ + 1 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 102001, + "quality": 1, + "chapters": [ + 1 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 102101, + "quality": 1, + "chapters": [ + 1 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 102201, + "quality": 1, + "chapters": [ + 2 + ], + "oc": true, + "grants": [ + { + "kind": "refresh", + "count": 5 + } + ], + "grants_complete": true + }, + { + "id": 102301, + "quality": 1, + "chapters": [ + 3 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 102401, + "quality": 1, + "chapters": [ + 1 + ], + "oc": true, + "grants": [ + { + "kind": "cost_role", + "count": 2, + "cost": 1 + }, + { + "kind": "cost_role", + "count": 2, + "cost": 2 + }, + { + "kind": "cost_role", + "count": 1, + "cost": 3 + } + ], + "grants_complete": true + }, + { + "id": 102501, + "quality": 1, + "chapters": [ + 1, + 2 + ], + "oc": true, + "grants": [ + { + "kind": "basic", + "count": 1 + }, + { + "kind": "cost_role", + "count": 2, + "cost": 3 + } + ], + "grants_complete": true + }, + { + "id": 102502, + "quality": 1, + "chapters": [ + 3 + ], + "oc": true, + "grants": [ + { + "kind": "basic", + "count": 1 + }, + { + "kind": "cost_role", + "count": 1, + "cost": 4 + }, + { + "kind": "gold", + "count": 6 + } + ], + "grants_complete": true + }, + { + "id": 102601, + "quality": 1, + "chapters": [ + 1 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 102701, + "quality": 1, + "chapters": [ + 2 + ], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 3, + "id": 350101 + } + ], + "grants_complete": true + }, + { + "id": 102901, + "quality": 1, + "chapters": [ + 1 + ], + "oc": true, + "grants": [ + { + "kind": "refresh", + "count": 4 + } + ], + "grants_complete": true + }, + { + "id": 103001, + "quality": 1, + "chapters": [ + 1 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 103101, + "quality": 1, + "chapters": [ + 1 + ], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 2, + "id": 99994 + }, + { + "kind": "gold", + "count": 4 + } + ], + "grants_complete": true + }, + { + "id": 103102, + "quality": 1, + "chapters": [ + 3 + ], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 2, + "id": 99993 + }, + { + "kind": "gold", + "count": 5 + } + ], + "grants_complete": true + }, + { + "id": 103201, + "quality": 1, + "chapters": [ + 1 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 103202, + "quality": 1, + "chapters": [ + 2 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 103301, + "quality": 1, + "chapters": [ + 2, + 3 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 103401, + "quality": 1, + "chapters": [ + 1 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 103402, + "quality": 1, + "chapters": [ + 2, + 3 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 103501, + "quality": 1, + "chapters": [ + 1 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 103701, + "quality": 1, + "chapters": [ + 1 + ], + "oc": true, + "grants": [ + { + "kind": "exp", + "count": 4 + } + ], + "grants_complete": true + }, + { + "id": 103802, + "quality": 1, + "chapters": [ + 1 + ], + "oc": true, + "grants": [ + { + "kind": "hp", + "count": 10 + }, + { + "kind": "gold", + "count": 6 + } + ], + "grants_complete": true + }, + { + "id": 103901, + "quality": 1, + "chapters": [ + 1, + 2, + 3 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 104001, + "quality": 1, + "chapters": [ + 1, + 2 + ], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 350105 + } + ], + "grants_complete": true + }, + { + "id": 104101, + "quality": 1, + "chapters": [ + 2, + 3 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 104201, + "quality": 1, + "chapters": [ + 1, + 2 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 104301, + "quality": 1, + "chapters": [ + 1, + 2 + ], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 350108 + } + ], + "grants_complete": true + }, + { + "id": 110101, + "quality": 1, + "chapters": [ + 1 + ], + "oc": true, + "grants": [ + { + "kind": "emblem", + "count": 1 + } + ], + "grants_complete": true + }, + { + "id": 110102, + "quality": 1, + "chapters": [ + 2, + 3 + ], + "oc": true, + "grants": [ + { + "kind": "emblem", + "count": 1 + }, + { + "kind": "item", + "count": 1, + "id": 350103 + } + ], + "grants_complete": true + }, + { + "id": 130101, + "quality": 1, + "chapters": [ + 1, + 2 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 130201, + "quality": 1, + "chapters": [ + 1 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 130301, + "quality": 1, + "chapters": [ + 1, + 2 + ], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 35030404 + } + ], + "grants_complete": true + }, + { + "id": 130401, + "quality": 1, + "chapters": [ + 1, + 2 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 130501, + "quality": 1, + "chapters": [ + 1 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 130601, + "quality": 1, + "chapters": [ + 1 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 130701, + "quality": 1, + "chapters": [ + 1, + 2 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 130801, + "quality": 1, + "chapters": [], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 350102 + }, + { + "kind": "basic", + "count": 1 + } + ], + "grants_complete": true + }, + { + "id": 130901, + "quality": 1, + "chapters": [ + 1, + 2 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 131001, + "quality": 1, + "chapters": [ + 1, + 2 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 131201, + "quality": 1, + "chapters": [ + 1, + 2 + ], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 352101 + }, + { + "kind": "gold", + "count": 8 + } + ], + "grants_complete": true + }, + { + "id": 150101, + "quality": 1, + "chapters": [ + 2, + 3 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 150201, + "quality": 1, + "chapters": [ + 2, + 3 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 150301, + "quality": 1, + "chapters": [], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 150501, + "quality": 1, + "chapters": [ + 2, + 3 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 150701, + "quality": 1, + "chapters": [ + 1 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 150801, + "quality": 1, + "chapters": [], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 150901, + "quality": 1, + "chapters": [], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 151001, + "quality": 1, + "chapters": [ + 1, + 2 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 151101, + "quality": 1, + "chapters": [ + 1, + 2 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 151201, + "quality": 1, + "chapters": [ + 2, + 3 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 151301, + "quality": 1, + "chapters": [ + 2 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 200101, + "quality": 2, + "chapters": [ + 1 + ], + "oc": true, + "grants": [ + { + "kind": "gold", + "count": 4 + } + ], + "grants_complete": true + }, + { + "id": 200201, + "quality": 2, + "chapters": [ + 2 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 200301, + "quality": 2, + "chapters": [ + 1 + ], + "oc": true, + "grants": [ + { + "kind": "refresh", + "count": 1 + }, + { + "kind": "gold", + "count": 8 + } + ], + "grants_complete": true + }, + { + "id": 200401, + "quality": 2, + "chapters": [ + 1 + ], + "oc": false, + "grants": [ + { + "kind": "cost_role", + "count": 1, + "cost": 1, + "star": 2 + } + ], + "grants_complete": false + }, + { + "id": 200501, + "quality": 2, + "chapters": [ + 1 + ], + "oc": true, + "grants": [ + { + "kind": "basic", + "count": 1 + } + ], + "grants_complete": true + }, + { + "id": 200502, + "quality": 2, + "chapters": [ + 2 + ], + "oc": true, + "grants": [ + { + "kind": "basic", + "count": 2 + } + ], + "grants_complete": true + }, + { + "id": 200601, + "quality": 2, + "chapters": [ + 1 + ], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 2, + "id": 350105 + }, + { + "kind": "gold", + "count": 7 + } + ], + "grants_complete": true + }, + { + "id": 200602, + "quality": 2, + "chapters": [ + 2 + ], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 2, + "id": 350105 + }, + { + "kind": "gold", + "count": 13 + } + ], + "grants_complete": true + }, + { + "id": 200701, + "quality": 2, + "chapters": [ + 2, + 3 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 200801, + "quality": 2, + "chapters": [ + 1 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 200901, + "quality": 2, + "chapters": [ + 2 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 201001, + "quality": 2, + "chapters": [], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 201101, + "quality": 2, + "chapters": [ + 1 + ], + "oc": false, + "grants": [], + "grants_complete": true + }, + { + "id": 201102, + "quality": 2, + "chapters": [ + 2 + ], + "oc": false, + "grants": [], + "grants_complete": true + }, + { + "id": 201201, + "quality": 2, + "chapters": [], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 201301, + "quality": 2, + "chapters": [ + 2 + ], + "oc": true, + "grants": [ + { + "kind": "gold", + "count": 7 + } + ], + "grants_complete": true + }, + { + "id": 201302, + "quality": 2, + "chapters": [ + 3 + ], + "oc": true, + "grants": [ + { + "kind": "gold", + "count": 9 + } + ], + "grants_complete": true + }, + { + "id": 201601, + "quality": 2, + "chapters": [ + 2, + 3 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 201701, + "quality": 2, + "chapters": [ + 1, + 2 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 201801, + "quality": 2, + "chapters": [ + 1 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 201802, + "quality": 2, + "chapters": [ + 2 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 201901, + "quality": 2, + "chapters": [ + 1 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 202001, + "quality": 2, + "chapters": [ + 1 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 202101, + "quality": 2, + "chapters": [ + 2 + ], + "oc": true, + "grants": [ + { + "kind": "refresh", + "count": 9 + } + ], + "grants_complete": true + }, + { + "id": 202201, + "quality": 2, + "chapters": [ + 3 + ], + "oc": true, + "grants": [ + { + "kind": "gold", + "count": 12 + } + ], + "grants_complete": true + }, + { + "id": 202401, + "quality": 2, + "chapters": [ + 1 + ], + "oc": true, + "grants": [ + { + "kind": "basic", + "count": 1 + } + ], + "grants_complete": true + }, + { + "id": 202402, + "quality": 2, + "chapters": [ + 2 + ], + "oc": true, + "grants": [ + { + "kind": "basic", + "count": 1 + } + ], + "grants_complete": true + }, + { + "id": 202501, + "quality": 2, + "chapters": [ + 2 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 202601, + "quality": 2, + "chapters": [ + 1 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 202701, + "quality": 2, + "chapters": [ + 1 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 202801, + "quality": 2, + "chapters": [ + 1 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 202901, + "quality": 2, + "chapters": [ + 2 + ], + "oc": true, + "grants": [ + { + "kind": "gold", + "count": 8 + } + ], + "grants_complete": false + }, + { + "id": 203001, + "quality": 2, + "chapters": [ + 1 + ], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 350202 + }, + { + "kind": "item", + "count": 1, + "id": 350203 + } + ], + "grants_complete": false + }, + { + "id": 203101, + "quality": 2, + "chapters": [ + 3 + ], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 350106 + }, + { + "kind": "gold", + "count": 10 + } + ], + "grants_complete": true + }, + { + "id": 203201, + "quality": 2, + "chapters": [ + 1, + 2 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 203301, + "quality": 2, + "chapters": [ + 2 + ], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 2, + "id": 99992 + }, + { + "kind": "gold", + "count": 4 + } + ], + "grants_complete": true + }, + { + "id": 203302, + "quality": 2, + "chapters": [ + 3 + ], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 2, + "id": 99992 + }, + { + "kind": "gold", + "count": 12 + } + ], + "grants_complete": true + }, + { + "id": 203501, + "quality": 2, + "chapters": [ + 2 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 203601, + "quality": 2, + "chapters": [ + 2 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 204001, + "quality": 2, + "chapters": [ + 1, + 2 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 204101, + "quality": 2, + "chapters": [ + 1 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 204701, + "quality": 2, + "chapters": [], + "oc": true, + "grants": [ + { + "kind": "emblem", + "count": 2 + } + ], + "grants_complete": true + }, + { + "id": 204801, + "quality": 2, + "chapters": [ + 1 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 204802, + "quality": 2, + "chapters": [ + 2 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 204803, + "quality": 2, + "chapters": [ + 3 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 204901, + "quality": 2, + "chapters": [ + 1 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 205001, + "quality": 2, + "chapters": [ + 2 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 205101, + "quality": 2, + "chapters": [ + 1 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 205102, + "quality": 2, + "chapters": [ + 2 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 205301, + "quality": 2, + "chapters": [ + 2 + ], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 99999 + } + ], + "grants_complete": true + }, + { + "id": 211501, + "quality": 2, + "chapters": [], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 355100102 + }, + { + "kind": "role", + "count": 1, + "id": 1210 + } + ], + "grants_complete": true + }, + { + "id": 211601, + "quality": 2, + "chapters": [], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 355100202 + }, + { + "kind": "role", + "count": 1, + "id": 1405 + } + ], + "grants_complete": true + }, + { + "id": 211701, + "quality": 2, + "chapters": [], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 355100302 + }, + { + "kind": "role", + "count": 1, + "id": 1501 + } + ], + "grants_complete": true + }, + { + "id": 211801, + "quality": 2, + "chapters": [], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 355100402 + }, + { + "kind": "role", + "count": 1, + "id": 1225 + } + ], + "grants_complete": true + }, + { + "id": 211901, + "quality": 2, + "chapters": [], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 355100502 + }, + { + "kind": "role", + "count": 1, + "id": 1004 + } + ], + "grants_complete": true + }, + { + "id": 212101, + "quality": 2, + "chapters": [], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 355100802 + }, + { + "kind": "role", + "count": 1, + "id": 1310 + } + ], + "grants_complete": true + }, + { + "id": 212201, + "quality": 2, + "chapters": [], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 355100902 + }, + { + "kind": "role", + "count": 1, + "id": 1412 + } + ], + "grants_complete": true + }, + { + "id": 212301, + "quality": 2, + "chapters": [], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 355101002 + }, + { + "kind": "role", + "count": 1, + "id": 1307 + } + ], + "grants_complete": true + }, + { + "id": 212401, + "quality": 2, + "chapters": [], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 355101202 + }, + { + "kind": "role", + "count": 1, + "id": 1404 + } + ], + "grants_complete": true + }, + { + "id": 221201, + "quality": 2, + "chapters": [], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 355200102 + }, + { + "kind": "role", + "count": 1, + "id": 1112 + } + ], + "grants_complete": true + }, + { + "id": 221301, + "quality": 2, + "chapters": [], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 355200202 + }, + { + "kind": "role", + "count": 1, + "id": 1210 + } + ], + "grants_complete": true + }, + { + "id": 221401, + "quality": 2, + "chapters": [], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 355200302 + }, + { + "kind": "role", + "count": 1, + "id": 1004 + } + ], + "grants_complete": true + }, + { + "id": 221501, + "quality": 2, + "chapters": [], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 355200402 + }, + { + "kind": "role", + "count": 1, + "id": 1405 + } + ], + "grants_complete": true + }, + { + "id": 221601, + "quality": 2, + "chapters": [], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 355200502 + }, + { + "kind": "role", + "count": 1, + "id": 1225 + } + ], + "grants_complete": true + }, + { + "id": 221701, + "quality": 2, + "chapters": [], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 355200602 + }, + { + "kind": "role", + "count": 1, + "id": 1204 + } + ], + "grants_complete": true + }, + { + "id": 221801, + "quality": 2, + "chapters": [], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 355200702 + }, + { + "kind": "role", + "count": 1, + "id": 1501 + } + ], + "grants_complete": true + }, + { + "id": 221901, + "quality": 2, + "chapters": [], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 355200802 + }, + { + "kind": "role", + "count": 1, + "id": 1221 + } + ], + "grants_complete": true + }, + { + "id": 222001, + "quality": 2, + "chapters": [], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 355200902 + }, + { + "kind": "role", + "count": 1, + "id": 1505 + } + ], + "grants_complete": true + }, + { + "id": 222101, + "quality": 2, + "chapters": [], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 355201002 + }, + { + "kind": "role", + "count": 1, + "id": 1222 + } + ], + "grants_complete": true + }, + { + "id": 222201, + "quality": 2, + "chapters": [], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 355201102 + }, + { + "kind": "role", + "count": 1, + "id": 1503 + } + ], + "grants_complete": true + }, + { + "id": 230101, + "quality": 2, + "chapters": [], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 350204 + } + ], + "grants_complete": false + }, + { + "id": 230201, + "quality": 2, + "chapters": [], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 350202 + } + ], + "grants_complete": false + }, + { + "id": 230301, + "quality": 2, + "chapters": [], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 350203 + } + ], + "grants_complete": false + }, + { + "id": 230601, + "quality": 2, + "chapters": [], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 230701, + "quality": 2, + "chapters": [ + 1 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 230702, + "quality": 2, + "chapters": [ + 2, + 3 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 230801, + "quality": 2, + "chapters": [ + 1, + 2 + ], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 2, + "id": 350103 + } + ], + "grants_complete": true + }, + { + "id": 230901, + "quality": 2, + "chapters": [ + 1 + ], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 99991 + }, + { + "kind": "gold", + "count": 4 + } + ], + "grants_complete": true + }, + { + "id": 230902, + "quality": 2, + "chapters": [ + 2, + 3 + ], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 99991 + }, + { + "kind": "gold", + "count": 8 + } + ], + "grants_complete": true + }, + { + "id": 231001, + "quality": 2, + "chapters": [ + 1, + 2 + ], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 35030203 + } + ], + "grants_complete": true + }, + { + "id": 231101, + "quality": 2, + "chapters": [ + 2, + 3 + ], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 350701 + } + ], + "grants_complete": true + }, + { + "id": 231201, + "quality": 2, + "chapters": [], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 350601 + } + ], + "grants_complete": true + }, + { + "id": 231301, + "quality": 2, + "chapters": [], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 350602 + } + ], + "grants_complete": true + }, + { + "id": 231401, + "quality": 2, + "chapters": [], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 350102 + }, + { + "kind": "item", + "count": 1, + "id": 350103 + }, + { + "kind": "basic", + "count": 2 + } + ], + "grants_complete": true + }, + { + "id": 231501, + "quality": 2, + "chapters": [], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 350107 + }, + { + "kind": "gold", + "count": 3 + } + ], + "grants_complete": true + }, + { + "id": 231601, + "quality": 2, + "chapters": [ + 2, + 3 + ], + "oc": true, + "grants": [ + { + "kind": "basic", + "count": 1 + }, + { + "kind": "gold", + "count": 3 + } + ], + "grants_complete": false + }, + { + "id": 231701, + "quality": 2, + "chapters": [ + 2 + ], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 352201 + }, + { + "kind": "gold", + "count": 15 + } + ], + "grants_complete": true + }, + { + "id": 240101, + "quality": 2, + "chapters": [], + "oc": true, + "grants": [ + { + "kind": "role", + "count": 1, + "id": 1405 + } + ], + "grants_complete": false + }, + { + "id": 240201, + "quality": 2, + "chapters": [], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 99999 + } + ], + "grants_complete": false + }, + { + "id": 240301, + "quality": 2, + "chapters": [ + 2, + 3 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 240401, + "quality": 2, + "chapters": [ + 1 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 240402, + "quality": 2, + "chapters": [ + 2 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 240601, + "quality": 2, + "chapters": [], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 350208 + }, + { + "kind": "gold", + "count": 5 + } + ], + "grants_complete": false + }, + { + "id": 240701, + "quality": 2, + "chapters": [], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 240801, + "quality": 2, + "chapters": [], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 240901, + "quality": 2, + "chapters": [ + 1, + 2, + 3 + ], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 99999 + } + ], + "grants_complete": false + }, + { + "id": 250101, + "quality": 2, + "chapters": [ + 2, + 3 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 250201, + "quality": 2, + "chapters": [ + 1 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 250401, + "quality": 2, + "chapters": [ + 1 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 250501, + "quality": 2, + "chapters": [ + 1 + ], + "oc": true, + "grants": [ + { + "kind": "gold", + "count": 3 + } + ], + "grants_complete": true + }, + { + "id": 250601, + "quality": 2, + "chapters": [ + 2, + 3 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 250701, + "quality": 2, + "chapters": [ + 2, + 3 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 250801, + "quality": 2, + "chapters": [], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 250901, + "quality": 2, + "chapters": [ + 2, + 3 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 251201, + "quality": 2, + "chapters": [], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 251301, + "quality": 2, + "chapters": [ + 2, + 3 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 251401, + "quality": 2, + "chapters": [ + 2, + 3 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 251501, + "quality": 2, + "chapters": [], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 251601, + "quality": 2, + "chapters": [], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 350601 + } + ], + "grants_complete": true + }, + { + "id": 253101, + "quality": 2, + "chapters": [], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 253301, + "quality": 2, + "chapters": [ + 1, + 2 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 253901, + "quality": 2, + "chapters": [ + 1 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 254001, + "quality": 2, + "chapters": [ + 1 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 254101, + "quality": 2, + "chapters": [], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 260601, + "quality": 2, + "chapters": [ + 1, + 2 + ], + "oc": true, + "grants": [ + { + "kind": "gold", + "count": 8 + } + ], + "grants_complete": false + }, + { + "id": 300101, + "quality": 3, + "chapters": [ + 1 + ], + "oc": false, + "grants": [ + { + "kind": "gold", + "count": 10 + } + ], + "grants_complete": true + }, + { + "id": 300201, + "quality": 3, + "chapters": [ + 1 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 300401, + "quality": 3, + "chapters": [ + 1, + 2 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 300501, + "quality": 3, + "chapters": [ + 1 + ], + "oc": true, + "grants": [ + { + "kind": "cost_role", + "count": 1, + "cost": 1, + "star": 3 + }, + { + "kind": "item", + "count": 1, + "id": 99999 + } + ], + "grants_complete": true + }, + { + "id": 300601, + "quality": 3, + "chapters": [ + 1 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 300701, + "quality": 3, + "chapters": [ + 3 + ], + "oc": true, + "grants": [ + { + "kind": "cost_role", + "count": 3, + "cost": 4, + "star": 2 + }, + { + "kind": "item", + "count": 1, + "id": 350106 + }, + { + "kind": "refresh", + "count": 4 + } + ], + "grants_complete": true + }, + { + "id": 300801, + "quality": 3, + "chapters": [ + 1 + ], + "oc": false, + "grants": [ + { + "kind": "gold", + "count": 25 + } + ], + "grants_complete": true + }, + { + "id": 300901, + "quality": 3, + "chapters": [ + 1 + ], + "oc": false, + "grants": [ + { + "kind": "gold", + "count": 15 + } + ], + "grants_complete": true + }, + { + "id": 301001, + "quality": 3, + "chapters": [ + 2 + ], + "oc": true, + "grants": [ + { + "kind": "gold", + "count": 26 + } + ], + "grants_complete": true + }, + { + "id": 301002, + "quality": 3, + "chapters": [ + 3 + ], + "oc": true, + "grants": [ + { + "kind": "gold", + "count": 45 + } + ], + "grants_complete": true + }, + { + "id": 301101, + "quality": 3, + "chapters": [ + 1 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 301201, + "quality": 3, + "chapters": [ + 1 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 301202, + "quality": 3, + "chapters": [ + 2, + 3 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 301301, + "quality": 3, + "chapters": [ + 2, + 3 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 301401, + "quality": 3, + "chapters": [ + 1 + ], + "oc": true, + "grants": [ + { + "kind": "gold", + "count": 3 + } + ], + "grants_complete": true + }, + { + "id": 301501, + "quality": 3, + "chapters": [ + 1 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 301601, + "quality": 3, + "chapters": [ + 1, + 2 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 301701, + "quality": 3, + "chapters": [ + 2, + 3 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 301801, + "quality": 3, + "chapters": [ + 1 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 301901, + "quality": 3, + "chapters": [ + 3 + ], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 8, + "id": 350105 + } + ], + "grants_complete": true + }, + { + "id": 302001, + "quality": 3, + "chapters": [ + 1 + ], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 2, + "id": 99999 + } + ], + "grants_complete": true + }, + { + "id": 302101, + "quality": 3, + "chapters": [ + 1 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 302201, + "quality": 3, + "chapters": [ + 1, + 2 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 302301, + "quality": 3, + "chapters": [ + 1 + ], + "oc": true, + "grants": [ + { + "kind": "gold", + "count": 50 + } + ], + "grants_complete": false + }, + { + "id": 302401, + "quality": 3, + "chapters": [ + 1 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 302501, + "quality": 3, + "chapters": [ + 2, + 3 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 302601, + "quality": 3, + "chapters": [ + 1 + ], + "oc": true, + "grants": [ + { + "kind": "cost_role", + "count": 1, + "cost": 2, + "star": 2 + } + ], + "grants_complete": false + }, + { + "id": 302701, + "quality": 3, + "chapters": [ + 3 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 302801, + "quality": 3, + "chapters": [ + 2 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 302901, + "quality": 3, + "chapters": [ + 2, + 3 + ], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 350701 + }, + { + "kind": "gold", + "count": 32 + } + ], + "grants_complete": true + }, + { + "id": 303001, + "quality": 3, + "chapters": [ + 3 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 303101, + "quality": 3, + "chapters": [], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 303201, + "quality": 3, + "chapters": [ + 2, + 3 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 303501, + "quality": 3, + "chapters": [ + 1, + 2 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 303601, + "quality": 3, + "chapters": [ + 1 + ], + "oc": true, + "grants": [ + { + "kind": "gold", + "count": 15 + } + ], + "grants_complete": true + }, + { + "id": 303701, + "quality": 3, + "chapters": [ + 2 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 303801, + "quality": 3, + "chapters": [ + 1, + 2, + 3 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 303901, + "quality": 3, + "chapters": [ + 2, + 3 + ], + "oc": true, + "grants": [ + { + "kind": "gold", + "count": 5 + } + ], + "grants_complete": true + }, + { + "id": 304001, + "quality": 3, + "chapters": [ + 1 + ], + "oc": false, + "grants": [], + "grants_complete": true + }, + { + "id": 304101, + "quality": 3, + "chapters": [ + 1 + ], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 2, + "id": 99999 + } + ], + "grants_complete": false + }, + { + "id": 304201, + "quality": 3, + "chapters": [ + 1 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 304301, + "quality": 3, + "chapters": [ + 2, + 3 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 304701, + "quality": 3, + "chapters": [ + 1 + ], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 2, + "id": 99999 + } + ], + "grants_complete": true + }, + { + "id": 304702, + "quality": 3, + "chapters": [ + 2 + ], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 3, + "id": 99999 + } + ], + "grants_complete": true + }, + { + "id": 304901, + "quality": 3, + "chapters": [ + 2 + ], + "oc": true, + "grants": [ + { + "kind": "cost_role", + "count": 1, + "cost": 3 + } + ], + "grants_complete": true + }, + { + "id": 305001, + "quality": 3, + "chapters": [ + 2 + ], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 99999 + }, + { + "kind": "gold", + "count": 3 + } + ], + "grants_complete": true + }, + { + "id": 305101, + "quality": 3, + "chapters": [ + 3 + ], + "oc": true, + "grants": [ + { + "kind": "cost_role", + "count": 3, + "cost": 4 + } + ], + "grants_complete": true + }, + { + "id": 305201, + "quality": 3, + "chapters": [ + 2, + 3 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 311501, + "quality": 3, + "chapters": [], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 355100102 + }, + { + "kind": "role", + "count": 1, + "id": 1210 + }, + { + "kind": "item", + "count": 1, + "id": 47030108 + } + ], + "grants_complete": true + }, + { + "id": 311601, + "quality": 3, + "chapters": [], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 355100202 + }, + { + "kind": "role", + "count": 1, + "id": 1405 + }, + { + "kind": "item", + "count": 1, + "id": 35030204 + } + ], + "grants_complete": true + }, + { + "id": 311701, + "quality": 3, + "chapters": [], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 355100302 + }, + { + "kind": "role", + "count": 1, + "id": 1501 + }, + { + "kind": "item", + "count": 1, + "id": 35030204 + } + ], + "grants_complete": true + }, + { + "id": 311801, + "quality": 3, + "chapters": [], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 355100402 + }, + { + "kind": "role", + "count": 1, + "id": 1225 + }, + { + "kind": "item", + "count": 1, + "id": 35030707 + } + ], + "grants_complete": true + }, + { + "id": 311901, + "quality": 3, + "chapters": [], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 355100502 + }, + { + "kind": "role", + "count": 1, + "id": 1004 + }, + { + "kind": "item", + "count": 1, + "id": 35030204 + } + ], + "grants_complete": true + }, + { + "id": 312101, + "quality": 3, + "chapters": [], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 355100802 + }, + { + "kind": "role", + "count": 1, + "id": 1310 + }, + { + "kind": "item", + "count": 1, + "id": 35030707 + } + ], + "grants_complete": true + }, + { + "id": 312201, + "quality": 3, + "chapters": [], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 355100902 + }, + { + "kind": "role", + "count": 1, + "id": 1412 + }, + { + "kind": "item", + "count": 1, + "id": 35030206 + } + ], + "grants_complete": true + }, + { + "id": 312301, + "quality": 3, + "chapters": [], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 355101002 + }, + { + "kind": "role", + "count": 1, + "id": 1307 + }, + { + "kind": "item", + "count": 1, + "id": 35030107 + } + ], + "grants_complete": true + }, + { + "id": 312401, + "quality": 3, + "chapters": [], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 355101202 + }, + { + "kind": "role", + "count": 1, + "id": 1404 + }, + { + "kind": "item", + "count": 1, + "id": 47030101 + } + ], + "grants_complete": true + }, + { + "id": 321201, + "quality": 3, + "chapters": [], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 355200102 + }, + { + "kind": "role", + "count": 1, + "id": 1112 + }, + { + "kind": "item", + "count": 1, + "id": 35030304 + } + ], + "grants_complete": true + }, + { + "id": 321301, + "quality": 3, + "chapters": [], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 355200202 + }, + { + "kind": "role", + "count": 1, + "id": 1210 + }, + { + "kind": "item", + "count": 1, + "id": 47030108 + } + ], + "grants_complete": true + }, + { + "id": 321401, + "quality": 3, + "chapters": [], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 355200302 + }, + { + "kind": "role", + "count": 1, + "id": 1004 + }, + { + "kind": "item", + "count": 1, + "id": 35030204 + } + ], + "grants_complete": true + }, + { + "id": 321501, + "quality": 3, + "chapters": [], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 355200402 + }, + { + "kind": "role", + "count": 1, + "id": 1405 + }, + { + "kind": "item", + "count": 1, + "id": 35030204 + } + ], + "grants_complete": true + }, + { + "id": 321601, + "quality": 3, + "chapters": [], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 355200502 + }, + { + "kind": "role", + "count": 1, + "id": 1225 + }, + { + "kind": "item", + "count": 1, + "id": 35030707 + } + ], + "grants_complete": true + }, + { + "id": 321701, + "quality": 3, + "chapters": [], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 355200602 + }, + { + "kind": "role", + "count": 1, + "id": 1204 + }, + { + "kind": "item", + "count": 1, + "id": 35030204 + } + ], + "grants_complete": true + }, + { + "id": 321801, + "quality": 3, + "chapters": [], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 355200702 + }, + { + "kind": "role", + "count": 1, + "id": 1501 + }, + { + "kind": "item", + "count": 1, + "id": 35030204 + } + ], + "grants_complete": true + }, + { + "id": 321901, + "quality": 3, + "chapters": [], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 355200802 + }, + { + "kind": "role", + "count": 1, + "id": 1221 + }, + { + "kind": "item", + "count": 1, + "id": 35030204 + } + ], + "grants_complete": true + }, + { + "id": 322001, + "quality": 3, + "chapters": [], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 355200902 + }, + { + "kind": "role", + "count": 1, + "id": 1505 + }, + { + "kind": "item", + "count": 1, + "id": 35030808 + } + ], + "grants_complete": true + }, + { + "id": 322101, + "quality": 3, + "chapters": [], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 355201002 + }, + { + "kind": "role", + "count": 1, + "id": 1222 + }, + { + "kind": "item", + "count": 1, + "id": 35030505 + } + ], + "grants_complete": true + }, + { + "id": 322201, + "quality": 3, + "chapters": [], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 355201102 + }, + { + "kind": "role", + "count": 1, + "id": 1503 + }, + { + "kind": "item", + "count": 1, + "id": 35030505 + } + ], + "grants_complete": true + }, + { + "id": 330101, + "quality": 3, + "chapters": [ + 2, + 3 + ], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 350104 + }, + { + "kind": "item", + "count": 1, + "id": 99999 + } + ], + "grants_complete": true + }, + { + "id": 330201, + "quality": 3, + "chapters": [ + 2, + 3 + ], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 350701 + } + ], + "grants_complete": false + }, + { + "id": 330301, + "quality": 3, + "chapters": [ + 2, + 3 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 330401, + "quality": 3, + "chapters": [], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 330501, + "quality": 3, + "chapters": [ + 1, + 2 + ], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 99999 + } + ], + "grants_complete": true + }, + { + "id": 330601, + "quality": 3, + "chapters": [ + 1, + 2 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 330701, + "quality": 3, + "chapters": [ + 2, + 3 + ], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 5, + "id": 350203 + } + ], + "grants_complete": true + }, + { + "id": 330801, + "quality": 3, + "chapters": [ + 2, + 3 + ], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 2, + "id": 350701 + } + ], + "grants_complete": true + }, + { + "id": 330901, + "quality": 3, + "chapters": [ + 1 + ], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 99991 + } + ], + "grants_complete": true + }, + { + "id": 331001, + "quality": 3, + "chapters": [ + 1, + 2 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 331301, + "quality": 3, + "chapters": [ + 2, + 3 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 331401, + "quality": 3, + "chapters": [ + 1 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 331501, + "quality": 3, + "chapters": [], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 350102 + }, + { + "kind": "item", + "count": 1, + "id": 35040404 + } + ], + "grants_complete": true + }, + { + "id": 331601, + "quality": 3, + "chapters": [ + 2 + ], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 350107 + } + ], + "grants_complete": true + }, + { + "id": 331701, + "quality": 3, + "chapters": [ + 1 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 331801, + "quality": 3, + "chapters": [ + 2 + ], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 35030507 + } + ], + "grants_complete": true + }, + { + "id": 332001, + "quality": 3, + "chapters": [ + 2, + 3 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 332301, + "quality": 3, + "chapters": [ + 2 + ], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 99999 + } + ], + "grants_complete": false + }, + { + "id": 332401, + "quality": 3, + "chapters": [ + 2 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 332501, + "quality": 3, + "chapters": [], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 350601 + }, + { + "kind": "basic", + "count": 3 + }, + { + "kind": "gold", + "count": 5 + } + ], + "grants_complete": true + }, + { + "id": 340101, + "quality": 3, + "chapters": [ + 1 + ], + "oc": true, + "grants": [ + { + "kind": "emblem", + "count": 3 + }, + { + "kind": "gold", + "count": 4 + } + ], + "grants_complete": true + }, + { + "id": 340102, + "quality": 3, + "chapters": [ + 2 + ], + "oc": true, + "grants": [ + { + "kind": "emblem", + "count": 3 + }, + { + "kind": "item", + "count": 1, + "id": 350103 + }, + { + "kind": "gold", + "count": 4 + } + ], + "grants_complete": true + }, + { + "id": 340201, + "quality": 3, + "chapters": [], + "oc": true, + "grants": [ + { + "kind": "emblem", + "count": 1 + } + ], + "grants_complete": true + }, + { + "id": 340301, + "quality": 3, + "chapters": [ + 1 + ], + "oc": true, + "grants": [ + { + "kind": "emblem", + "count": 1 + } + ], + "grants_complete": false + }, + { + "id": 340401, + "quality": 3, + "chapters": [], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 350101, + "quality": 3, + "chapters": [], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 350301, + "quality": 3, + "chapters": [ + 2, + 3 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 350401, + "quality": 3, + "chapters": [ + 1 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 350402, + "quality": 3, + "chapters": [ + 2 + ], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 99999 + } + ], + "grants_complete": true + }, + { + "id": 350403, + "quality": 3, + "chapters": [ + 3 + ], + "oc": true, + "grants": [ + { + "kind": "item", + "count": 1, + "id": 99998 + } + ], + "grants_complete": true + }, + { + "id": 350501, + "quality": 3, + "chapters": [], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 350601, + "quality": 3, + "chapters": [ + 1 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 350701, + "quality": 3, + "chapters": [ + 2, + 3 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 350801, + "quality": 3, + "chapters": [ + 2, + 3 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 350901, + "quality": 3, + "chapters": [ + 2, + 3 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 351101, + "quality": 3, + "chapters": [ + 2, + 3 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 351301, + "quality": 3, + "chapters": [ + 1 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 351401, + "quality": 3, + "chapters": [ + 2, + 3 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 351601, + "quality": 3, + "chapters": [ + 2, + 3 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 351701, + "quality": 3, + "chapters": [ + 2, + 3 + ], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 351801, + "quality": 3, + "chapters": [], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 351901, + "quality": 3, + "chapters": [], + "oc": true, + "grants": [], + "grants_complete": true + }, + { + "id": 352101, + "quality": 3, + "chapters": [ + 1 + ], + "oc": true, + "grants": [ + { + "kind": "gold", + "count": 10 + } + ], + "grants_complete": false + }, + { + "id": 352102, + "quality": 3, + "chapters": [ + 2 + ], + "oc": true, + "grants": [ + { + "kind": "gold", + "count": 14 + } + ], + "grants_complete": false + }, + { + "id": 352901, + "quality": 3, + "chapters": [ + 1 + ], + "oc": true, + "grants": [], + "grants_complete": true + } + ] +} diff --git a/resources/CurrencyWarsEquipment.json b/resources/CurrencyWarsEquipment.json index 53e4b90..28f2637 100644 --- a/resources/CurrencyWarsEquipment.json +++ b/resources/CurrencyWarsEquipment.json @@ -3,7 +3,7 @@ "target_client_version": "4.6.53", "target_assembly_sha256": "c5e02e6e984f7e5577c78b995a8efeee2d06ee22e67d484ad5e859c8bf21e144", "source_capture_sha256": "bc873b94868f27ddabc0eea137fb14424ccf86f85cea0b9c429eeff8e6d0f1b6", - "scope": "Initialized equipment handbook entries; consumables and hidden entries are excluded. Read-only native getters, four entries per heartbeat, paused during Battle.", + "scope": "Complete 241-row equipment handbook entries from official GridFightEquipConfigRow; format validated.", "entries": [ { "ItemID": 350201, @@ -312,6 +312,1015 @@ "EquipFuncParamList": [], "DressRuleParamList": [] }, + { + "ItemID": 352301, + "BaseItemID": 352301, + "NeedEquipSlot": true, + "EquipType": 1, + "EquipCategory": 13, + "EquipFunc": 0, + "DressRule": 6, + "AbilityName": "", + "GeneralPropertyList": [ + { + "Value": "0.3000000003", + "PropertyType": 1027 + } + ], + "AbilityParamList": [ + "3", + "0.2000000002", + "0.3000000003", + "0.5000000005" + ], + "EquipFuncParamList": [], + "DressRuleParamList": [ + 1006, + 15061, + 15062, + 15063, + 150602 + ] + }, + { + "ItemID": 352302, + "BaseItemID": 352302, + "NeedEquipSlot": true, + "EquipType": 1, + "EquipCategory": 13, + "EquipFunc": 0, + "DressRule": 6, + "AbilityName": "GridFight_Equipment_SilverWolf999_352302", + "GeneralPropertyList": [ + { + "Value": "0.3000000003", + "PropertyType": 1027 + } + ], + "AbilityParamList": [ + "0.3500000003" + ], + "EquipFuncParamList": [], + "DressRuleParamList": [ + 1006, + 15061, + 15062, + 15063, + 150602 + ] + }, + { + "ItemID": 352303, + "BaseItemID": 352303, + "NeedEquipSlot": true, + "EquipType": 1, + "EquipCategory": 13, + "EquipFunc": 0, + "DressRule": 6, + "AbilityName": "", + "GeneralPropertyList": [ + { + "Value": "0.3000000003", + "PropertyType": 1027 + } + ], + "AbilityParamList": [ + "5", + "0.1000000001", + "0.2000000002", + "0.1000000001" + ], + "EquipFuncParamList": [], + "DressRuleParamList": [ + 1006, + 15061, + 15062, + 15063, + 150602 + ] + }, + { + "ItemID": 352304, + "BaseItemID": 352304, + "NeedEquipSlot": true, + "EquipType": 1, + "EquipCategory": 13, + "EquipFunc": 0, + "DressRule": 6, + "AbilityName": "GridFight_Equipment_SilverWolf999_352507", + "GeneralPropertyList": [ + { + "Value": "0.3000000003", + "PropertyType": 1027 + } + ], + "AbilityParamList": [ + "0.1500000001" + ], + "EquipFuncParamList": [], + "DressRuleParamList": [ + 1006, + 15061, + 15062, + 15063, + 150602 + ] + }, + { + "ItemID": 352401, + "BaseItemID": 352401, + "NeedEquipSlot": true, + "EquipType": 1, + "EquipCategory": 13, + "EquipFunc": 0, + "DressRule": 6, + "AbilityName": "", + "GeneralPropertyList": [ + { + "Value": "0.4000000004", + "PropertyType": 1027 + } + ], + "AbilityParamList": [ + "3", + "0.2000000002", + "0.3000000003", + "0.5000000005" + ], + "EquipFuncParamList": [], + "DressRuleParamList": [ + 1006, + 15061, + 15062, + 15063, + 150602 + ] + }, + { + "ItemID": 352402, + "BaseItemID": 352402, + "NeedEquipSlot": true, + "EquipType": 1, + "EquipCategory": 13, + "EquipFunc": 12, + "DressRule": 6, + "AbilityName": "", + "GeneralPropertyList": [ + { + "Value": "0.4000000004", + "PropertyType": 1027 + } + ], + "AbilityParamList": [], + "EquipFuncParamList": [ + 2012 + ], + "DressRuleParamList": [ + 1006, + 15061, + 15062, + 15063, + 150602 + ] + }, + { + "ItemID": 352404, + "BaseItemID": 352404, + "NeedEquipSlot": true, + "EquipType": 1, + "EquipCategory": 13, + "EquipFunc": 12, + "DressRule": 6, + "AbilityName": "", + "GeneralPropertyList": [ + { + "Value": "0.4000000004", + "PropertyType": 1027 + } + ], + "AbilityParamList": [], + "EquipFuncParamList": [ + 1002 + ], + "DressRuleParamList": [ + 1006, + 15061, + 15062, + 15063, + 150602 + ] + }, + { + "ItemID": 352405, + "BaseItemID": 352405, + "NeedEquipSlot": true, + "EquipType": 1, + "EquipCategory": 13, + "EquipFunc": 0, + "DressRule": 6, + "AbilityName": "", + "GeneralPropertyList": [ + { + "Value": "0.4000000004", + "PropertyType": 1027 + } + ], + "AbilityParamList": [ + "0.2000000002", + "0.2000000002" + ], + "EquipFuncParamList": [], + "DressRuleParamList": [ + 1006, + 15061, + 15062, + 15063, + 150602 + ] + }, + { + "ItemID": 352406, + "BaseItemID": 352406, + "NeedEquipSlot": true, + "EquipType": 1, + "EquipCategory": 13, + "EquipFunc": 0, + "DressRule": 6, + "AbilityName": "GridFight_Equipment_SilverWolf999_352507", + "GeneralPropertyList": [ + { + "Value": "0.4000000004", + "PropertyType": 1027 + } + ], + "AbilityParamList": [ + "0.1500000001" + ], + "EquipFuncParamList": [], + "DressRuleParamList": [ + 1006, + 15061, + 15062, + 15063, + 150602 + ] + }, + { + "ItemID": 352501, + "BaseItemID": 352501, + "NeedEquipSlot": true, + "EquipType": 1, + "EquipCategory": 13, + "EquipFunc": 0, + "DressRule": 6, + "AbilityName": "", + "GeneralPropertyList": [ + { + "Value": "0.5000000005", + "PropertyType": 1027 + } + ], + "AbilityParamList": [ + "2", + "0.5000000005", + "0.3000000003" + ], + "EquipFuncParamList": [], + "DressRuleParamList": [ + 1006, + 15061, + 15062, + 15063, + 150602 + ] + }, + { + "ItemID": 352502, + "BaseItemID": 352502, + "NeedEquipSlot": true, + "EquipType": 1, + "EquipCategory": 13, + "EquipFunc": 12, + "DressRule": 6, + "AbilityName": "GridFight_Equipment_SilverWolf999_352502", + "GeneralPropertyList": [ + { + "Value": "0.5000000005", + "PropertyType": 1027 + } + ], + "AbilityParamList": [ + "0.5000000005", + "0.5000000005" + ], + "EquipFuncParamList": [ + 2012 + ], + "DressRuleParamList": [ + 1006, + 15061, + 15062, + 15063, + 150602 + ] + }, + { + "ItemID": 352503, + "BaseItemID": 352503, + "NeedEquipSlot": true, + "EquipType": 1, + "EquipCategory": 13, + "EquipFunc": 12, + "DressRule": 6, + "AbilityName": "GridFight_Equipment_SilverWolf999_352503", + "GeneralPropertyList": [ + { + "Value": "0.5000000005", + "PropertyType": 1027 + } + ], + "AbilityParamList": [], + "EquipFuncParamList": [ + 1002 + ], + "DressRuleParamList": [ + 1006, + 15061, + 15062, + 15063, + 150602 + ] + }, + { + "ItemID": 352504, + "BaseItemID": 352504, + "NeedEquipSlot": true, + "EquipType": 1, + "EquipCategory": 13, + "EquipFunc": 0, + "DressRule": 6, + "AbilityName": "", + "GeneralPropertyList": [ + { + "Value": "0.5000000005", + "PropertyType": 1027 + } + ], + "AbilityParamList": [ + "0.3000000003", + "0.3000000003" + ], + "EquipFuncParamList": [], + "DressRuleParamList": [ + 1006, + 15061, + 15062, + 15063, + 150602 + ] + }, + { + "ItemID": 352505, + "BaseItemID": 352505, + "NeedEquipSlot": true, + "EquipType": 1, + "EquipCategory": 13, + "EquipFunc": 0, + "DressRule": 6, + "AbilityName": "GridFight_Equipment_SilverWolf999_352505", + "GeneralPropertyList": [ + { + "Value": "0.5000000005", + "PropertyType": 1027 + } + ], + "AbilityParamList": [ + "0.6000000006", + "0.3000000003" + ], + "EquipFuncParamList": [], + "DressRuleParamList": [ + 1006, + 15061, + 15062, + 15063, + 150602 + ] + }, + { + "ItemID": 352506, + "BaseItemID": 352506, + "NeedEquipSlot": true, + "EquipType": 1, + "EquipCategory": 13, + "EquipFunc": 0, + "DressRule": 6, + "AbilityName": "GridFight_Equipment_SilverWolf999_352507", + "GeneralPropertyList": [ + { + "Value": "0.5000000005", + "PropertyType": 1027 + } + ], + "AbilityParamList": [ + "0.2500000002" + ], + "EquipFuncParamList": [], + "DressRuleParamList": [ + 1006, + 15061, + 15062, + 15063, + 150602 + ] + }, + { + "ItemID": 352507, + "BaseItemID": 352507, + "NeedEquipSlot": true, + "EquipType": 1, + "EquipCategory": 13, + "EquipFunc": 0, + "DressRule": 6, + "AbilityName": "", + "GeneralPropertyList": [ + { + "Value": "0.9900000009", + "PropertyType": 1027 + }, + { + "Value": "0.9900000009", + "PropertyType": 1020 + }, + { + "Value": "0.9900000009", + "PropertyType": 1014 + }, + { + "Value": "0.9900000009", + "PropertyType": 1018 + }, + { + "Value": "0.9900000009", + "PropertyType": 1024 + }, + { + "Value": "0.9900000009", + "PropertyType": 1025 + } + ], + "AbilityParamList": [ + "0.9900000009", + "0.9900000009", + "0.9900000009", + "0.9900000009", + "0.9900000009", + "0.9900000009" + ], + "EquipFuncParamList": [], + "DressRuleParamList": [ + 1006, + 15061, + 15062, + 15063, + 150602 + ] + }, + { + "ItemID": 352601, + "BaseItemID": 352601, + "NeedEquipSlot": true, + "EquipType": 1, + "EquipCategory": 14, + "EquipFunc": 0, + "DressRule": 0, + "AbilityName": "GridFight_Equipment_Fate_352601", + "GeneralPropertyList": [ + { + "Value": "0.3000000003", + "PropertyType": 1027 + }, + { + "Value": "0.3000000003", + "PropertyType": 1030 + } + ], + "AbilityParamList": [ + "0.2000000002", + "0.3000000003" + ], + "EquipFuncParamList": [], + "DressRuleParamList": [] + }, + { + "ItemID": 352602, + "BaseItemID": 352602, + "NeedEquipSlot": true, + "EquipType": 1, + "EquipCategory": 14, + "EquipFunc": 0, + "DressRule": 0, + "AbilityName": "GridFight_Equipment_Fate_352602", + "GeneralPropertyList": [ + { + "Value": "0.3000000003", + "PropertyType": 1027 + }, + { + "Value": "0.3000000003", + "PropertyType": 1030 + } + ], + "AbilityParamList": [ + "0.0100000000", + "0.0200000000" + ], + "EquipFuncParamList": [], + "DressRuleParamList": [] + }, + { + "ItemID": 352603, + "BaseItemID": 352603, + "NeedEquipSlot": true, + "EquipType": 1, + "EquipCategory": 14, + "EquipFunc": 0, + "DressRule": 0, + "AbilityName": "GridFight_Equipment_Fate_352603", + "GeneralPropertyList": [ + { + "Value": "0.3000000003", + "PropertyType": 1027 + }, + { + "Value": "0.3000000003", + "PropertyType": 1030 + } + ], + "AbilityParamList": [ + "0.3000000003", + "0.1000000001", + "0.6000000006", + "0.1200000001" + ], + "EquipFuncParamList": [], + "DressRuleParamList": [] + }, + { + "ItemID": 352604, + "BaseItemID": 352604, + "NeedEquipSlot": true, + "EquipType": 1, + "EquipCategory": 14, + "EquipFunc": 0, + "DressRule": 0, + "AbilityName": "GridFight_Equipment_Fate_352604", + "GeneralPropertyList": [ + { + "Value": "0.2000000002", + "PropertyType": 1014 + } + ], + "AbilityParamList": [], + "EquipFuncParamList": [], + "DressRuleParamList": [] + }, + { + "ItemID": 352605, + "BaseItemID": 352605, + "NeedEquipSlot": true, + "EquipType": 1, + "EquipCategory": 14, + "EquipFunc": 0, + "DressRule": 0, + "AbilityName": "GridFight_Equipment_Fate_352605", + "GeneralPropertyList": [ + { + "Value": "0.3000000003", + "PropertyType": 1027 + }, + { + "Value": "0.3000000003", + "PropertyType": 1020 + }, + { + "Value": "0.3000000003", + "PropertyType": 1014 + }, + { + "Value": "0.3000000003", + "PropertyType": 1030 + }, + { + "Value": "0.3000000003", + "PropertyType": 1024 + }, + { + "Value": "0.3000000003", + "PropertyType": 1025 + } + ], + "AbilityParamList": [ + "0.3000000003", + "0.3000000003", + "0.3000000003", + "0.3000000003", + "0.3000000003", + "0.3000000003" + ], + "EquipFuncParamList": [], + "DressRuleParamList": [] + }, + { + "ItemID": 352606, + "BaseItemID": 352606, + "NeedEquipSlot": true, + "EquipType": 1, + "EquipCategory": 14, + "EquipFunc": 0, + "DressRule": 0, + "AbilityName": "GridFight_Equipment_Fate_352606", + "GeneralPropertyList": [ + { + "Value": "0.9900000009", + "PropertyType": 1027 + }, + { + "Value": "0.9900000009", + "PropertyType": 1020 + }, + { + "Value": "0.9900000009", + "PropertyType": 1014 + }, + { + "Value": "0.9900000009", + "PropertyType": 1018 + }, + { + "Value": "0.9900000009", + "PropertyType": 1024 + }, + { + "Value": "0.9900000009", + "PropertyType": 1025 + } + ], + "AbilityParamList": [ + "0.9900000009", + "0.9900000009", + "0.9900000009", + "0.9900000009", + "0.9900000009", + "0.9900000009" + ], + "EquipFuncParamList": [], + "DressRuleParamList": [] + }, + { + "ItemID": 352607, + "BaseItemID": 352607, + "NeedEquipSlot": true, + "EquipType": 1, + "EquipCategory": 14, + "EquipFunc": 0, + "DressRule": 0, + "AbilityName": "GridFight_Equipment_Fate_352607", + "GeneralPropertyList": [ + { + "Value": "0.2000000002", + "PropertyType": 1024 + } + ], + "AbilityParamList": [ + "400", + "20" + ], + "EquipFuncParamList": [], + "DressRuleParamList": [] + }, + { + "ItemID": 352608, + "BaseItemID": 352608, + "NeedEquipSlot": true, + "EquipType": 1, + "EquipCategory": 14, + "EquipFunc": 0, + "DressRule": 0, + "AbilityName": "GridFight_Equipment_Fate_352608", + "GeneralPropertyList": [ + { + "Value": "0.1000000001", + "PropertyType": 1027 + }, + { + "Value": "0.1000000001", + "PropertyType": 1030 + } + ], + "AbilityParamList": [ + "4" + ], + "EquipFuncParamList": [], + "DressRuleParamList": [] + }, + { + "ItemID": 352701, + "BaseItemID": 352701, + "NeedEquipSlot": true, + "EquipType": 1, + "EquipCategory": 14, + "EquipFunc": 0, + "DressRule": 0, + "AbilityName": "GridFight_Equipment_Fate_352701", + "GeneralPropertyList": [ + { + "Value": "0.3000000003", + "PropertyType": 1027 + }, + { + "Value": "0.3000000003", + "PropertyType": 1030 + } + ], + "AbilityParamList": [ + "0.2000000002", + "0.3000000003" + ], + "EquipFuncParamList": [], + "DressRuleParamList": [] + }, + { + "ItemID": 352702, + "BaseItemID": 352702, + "NeedEquipSlot": true, + "EquipType": 1, + "EquipCategory": 14, + "EquipFunc": 0, + "DressRule": 0, + "AbilityName": "GridFight_Equipment_Fate_352702", + "GeneralPropertyList": [ + { + "Value": "0.3000000003", + "PropertyType": 1027 + }, + { + "Value": "0.3000000003", + "PropertyType": 1030 + } + ], + "AbilityParamList": [ + "0.0100000000", + "0.0200000000" + ], + "EquipFuncParamList": [], + "DressRuleParamList": [] + }, + { + "ItemID": 352703, + "BaseItemID": 352703, + "NeedEquipSlot": true, + "EquipType": 1, + "EquipCategory": 14, + "EquipFunc": 0, + "DressRule": 0, + "AbilityName": "GridFight_Equipment_Fate_352703", + "GeneralPropertyList": [ + { + "Value": "0.3000000003", + "PropertyType": 1027 + }, + { + "Value": "0.3000000003", + "PropertyType": 1030 + } + ], + "AbilityParamList": [ + "0.3000000003", + "0.1000000001", + "0.6000000006", + "0.1200000001", + "6" + ], + "EquipFuncParamList": [], + "DressRuleParamList": [] + }, + { + "ItemID": 352704, + "BaseItemID": 352704, + "NeedEquipSlot": true, + "EquipType": 1, + "EquipCategory": 14, + "EquipFunc": 0, + "DressRule": 0, + "AbilityName": "GridFight_Equipment_Fate_352704", + "GeneralPropertyList": [ + { + "Value": "0.2000000002", + "PropertyType": 1014 + } + ], + "AbilityParamList": [ + "3" + ], + "EquipFuncParamList": [], + "DressRuleParamList": [] + }, + { + "ItemID": 352705, + "BaseItemID": 352705, + "NeedEquipSlot": true, + "EquipType": 1, + "EquipCategory": 14, + "EquipFunc": 0, + "DressRule": 0, + "AbilityName": "GridFight_Equipment_Fate_352606", + "GeneralPropertyList": [ + { + "Value": "0.9900000009", + "PropertyType": 1027 + }, + { + "Value": "0.9900000009", + "PropertyType": 1020 + }, + { + "Value": "0.9900000009", + "PropertyType": 1014 + }, + { + "Value": "0.9900000009", + "PropertyType": 1018 + }, + { + "Value": "0.9900000009", + "PropertyType": 1024 + }, + { + "Value": "0.9900000009", + "PropertyType": 1025 + } + ], + "AbilityParamList": [ + "0.9900000009", + "0.9900000009", + "0.9900000009", + "0.9900000009", + "0.9900000009", + "0.9900000009" + ], + "EquipFuncParamList": [], + "DressRuleParamList": [] + }, + { + "ItemID": 352706, + "BaseItemID": 352706, + "NeedEquipSlot": true, + "EquipType": 1, + "EquipCategory": 14, + "EquipFunc": 0, + "DressRule": 0, + "AbilityName": "GridFight_Equipment_Fate_352606", + "GeneralPropertyList": [ + { + "Value": "0.9900000009", + "PropertyType": 1027 + }, + { + "Value": "0.9900000009", + "PropertyType": 1020 + }, + { + "Value": "0.9900000009", + "PropertyType": 1014 + }, + { + "Value": "0.9900000009", + "PropertyType": 1018 + }, + { + "Value": "0.9900000009", + "PropertyType": 1024 + }, + { + "Value": "0.9900000009", + "PropertyType": 1025 + } + ], + "AbilityParamList": [ + "0.9900000009", + "0.9900000009", + "0.9900000009", + "0.9900000009", + "0.9900000009", + "0.9900000009" + ], + "EquipFuncParamList": [], + "DressRuleParamList": [] + }, + { + "ItemID": 352801, + "BaseItemID": 352801, + "NeedEquipSlot": true, + "EquipType": 1, + "EquipCategory": 14, + "EquipFunc": 0, + "DressRule": 0, + "AbilityName": "GridFight_Equipment_Fate_352801", + "GeneralPropertyList": [ + { + "Value": "0.6000000006", + "PropertyType": 1027 + }, + { + "Value": "0.6000000006", + "PropertyType": 1030 + } + ], + "AbilityParamList": [ + "0.4000000004", + "0.6000000006" + ], + "EquipFuncParamList": [], + "DressRuleParamList": [] + }, + { + "ItemID": 352802, + "BaseItemID": 352802, + "NeedEquipSlot": true, + "EquipType": 1, + "EquipCategory": 14, + "EquipFunc": 0, + "DressRule": 0, + "AbilityName": "GridFight_Equipment_Fate_352802", + "GeneralPropertyList": [ + { + "Value": "0.6000000006", + "PropertyType": 1027 + }, + { + "Value": "0.6000000006", + "PropertyType": 1030 + } + ], + "AbilityParamList": [ + "0.0200000000", + "0.0400000000", + "200" + ], + "EquipFuncParamList": [], + "DressRuleParamList": [] + }, + { + "ItemID": 352803, + "BaseItemID": 352803, + "NeedEquipSlot": true, + "EquipType": 1, + "EquipCategory": 14, + "EquipFunc": 0, + "DressRule": 0, + "AbilityName": "GridFight_Equipment_Fate_352803", + "GeneralPropertyList": [ + { + "Value": "0.6000000006", + "PropertyType": 1027 + }, + { + "Value": "0.6000000006", + "PropertyType": 1030 + } + ], + "AbilityParamList": [ + "0.6000000006", + "0.2000000002", + "1.2000000002", + "0.2400000002", + "50" + ], + "EquipFuncParamList": [], + "DressRuleParamList": [] + }, + { + "ItemID": 352804, + "BaseItemID": 352804, + "NeedEquipSlot": true, + "EquipType": 1, + "EquipCategory": 14, + "EquipFunc": 0, + "DressRule": 0, + "AbilityName": "GridFight_Equipment_Fate_352804", + "GeneralPropertyList": [ + { + "Value": "0.4000000004", + "PropertyType": 1014 + } + ], + "AbilityParamList": [ + "0.7000000007" + ], + "EquipFuncParamList": [], + "DressRuleParamList": [] + }, { "ItemID": 352901, "BaseItemID": 352901, @@ -331,6 +1340,94 @@ "EquipFuncParamList": [], "DressRuleParamList": [] }, + { + "ItemID": 353001, + "BaseItemID": 353001, + "NeedEquipSlot": true, + "EquipType": 1, + "EquipCategory": 16, + "EquipFunc": 0, + "DressRule": 0, + "AbilityName": "GridFight_Equipment_Normal_020202", + "GeneralPropertyList": [ + { + "Value": "0.6000000006", + "PropertyType": 1027 + } + ], + "AbilityParamList": [ + "0.6000000006", + "1.1500000001" + ], + "EquipFuncParamList": [], + "DressRuleParamList": [] + }, + { + "ItemID": 353002, + "BaseItemID": 353002, + "NeedEquipSlot": true, + "EquipType": 1, + "EquipCategory": 16, + "EquipFunc": 0, + "DressRule": 0, + "AbilityName": "GridFight_Equipment_Normal_030302", + "GeneralPropertyList": [ + { + "Value": "0.6000000006", + "PropertyType": 1030 + } + ], + "AbilityParamList": [ + "0.6000000006", + "1.1500000001" + ], + "EquipFuncParamList": [], + "DressRuleParamList": [] + }, + { + "ItemID": 353003, + "BaseItemID": 353003, + "NeedEquipSlot": true, + "EquipType": 1, + "EquipCategory": 16, + "EquipFunc": 0, + "DressRule": 0, + "AbilityName": "GridFight_Equipment_Normal_0101", + "GeneralPropertyList": [ + { + "Value": "0.2500000002", + "PropertyType": 1020 + } + ], + "AbilityParamList": [ + "0.2500000002", + "0.1200000001" + ], + "EquipFuncParamList": [], + "DressRuleParamList": [] + }, + { + "ItemID": 353004, + "BaseItemID": 353004, + "NeedEquipSlot": true, + "EquipType": 1, + "EquipCategory": 16, + "EquipFunc": 0, + "DressRule": 0, + "AbilityName": "GridFight_Equipment_SpotLight_353004", + "GeneralPropertyList": [], + "AbilityParamList": [ + "0.0800000001", + "0.2000000002", + "70", + "22", + "6", + "2", + "2" + ], + "EquipFuncParamList": [], + "DressRuleParamList": [] + }, { "ItemID": 353005, "BaseItemID": 353005, @@ -379,6 +1476,20 @@ "EquipFuncParamList": [], "DressRuleParamList": [] }, + { + "ItemID": 353006, + "BaseItemID": 353006, + "NeedEquipSlot": true, + "EquipType": 1, + "EquipCategory": 16, + "EquipFunc": 0, + "DressRule": 0, + "AbilityName": "", + "GeneralPropertyList": [], + "AbilityParamList": [], + "EquipFuncParamList": [], + "DressRuleParamList": [] + }, { "ItemID": 353101, "BaseItemID": 353101, @@ -1259,6 +2370,86 @@ 150602 ] }, + { + "ItemID": 35030101, + "BaseItemID": 35030101, + "NeedEquipSlot": true, + "EquipType": 0, + "EquipCategory": 2, + "EquipFunc": 0, + "DressRule": 0, + "AbilityName": "GridFight_Equipment_Normal_0101", + "GeneralPropertyList": [ + { + "Value": "0.2000000002", + "PropertyType": 1020 + } + ], + "AbilityParamList": [ + "0.2000000002", + "0.1500000001" + ], + "EquipFuncParamList": [], + "DressRuleParamList": [] + }, + { + "ItemID": 35030102, + "BaseItemID": 35030102, + "NeedEquipSlot": true, + "EquipType": 0, + "EquipCategory": 2, + "EquipFunc": 0, + "DressRule": 0, + "AbilityName": "GridFight_Equipment_Normal_0102", + "GeneralPropertyList": [ + { + "Value": "0.1000000001", + "PropertyType": 1020 + }, + { + "Value": "0.2000000002", + "PropertyType": 1027 + }, + { + "Value": "0.2000000002", + "PropertyType": 1030 + } + ], + "AbilityParamList": [ + "0.1000000001", + "0.2000000002", + "0.0800000001" + ], + "EquipFuncParamList": [], + "DressRuleParamList": [] + }, + { + "ItemID": 35030103, + "BaseItemID": 35030103, + "NeedEquipSlot": true, + "EquipType": 0, + "EquipCategory": 2, + "EquipFunc": 0, + "DressRule": 0, + "AbilityName": "GridFight_Equipment_Normal_0103", + "GeneralPropertyList": [ + { + "Value": "0.2000000002", + "PropertyType": 1020 + }, + { + "Value": "0.3000000003", + "PropertyType": 1030 + } + ], + "AbilityParamList": [ + "0.2000000002", + "0.3000000003", + "0.5000000005" + ], + "EquipFuncParamList": [], + "DressRuleParamList": [] + }, { "ItemID": 35030104, "BaseItemID": 35030104, @@ -1287,6 +2478,68 @@ "EquipFuncParamList": [], "DressRuleParamList": [] }, + { + "ItemID": 35030105, + "BaseItemID": 35030105, + "NeedEquipSlot": true, + "EquipType": 0, + "EquipCategory": 2, + "EquipFunc": 0, + "DressRule": 0, + "AbilityName": "GridFight_Equipment_Normal_0105", + "GeneralPropertyList": [ + { + "Value": "0.1000000001", + "PropertyType": 1020 + }, + { + "Value": "0.1000000001", + "PropertyType": 1009 + }, + { + "Value": "0.1500000001", + "PropertyType": 1014 + } + ], + "AbilityParamList": [ + "0.1000000001", + "0.1000000001", + "0.1500000001", + "0.7000000007", + "0.3000000003" + ], + "EquipFuncParamList": [], + "DressRuleParamList": [] + }, + { + "ItemID": 35030106, + "BaseItemID": 35030106, + "NeedEquipSlot": true, + "EquipType": 0, + "EquipCategory": 2, + "EquipFunc": 0, + "DressRule": 0, + "AbilityName": "GridFight_Equipment_Normal_0106", + "GeneralPropertyList": [ + { + "Value": "0.1500000001", + "PropertyType": 1020 + }, + { + "Value": "0.1000000001", + "PropertyType": 1018 + } + ], + "AbilityParamList": [ + "0.1500000001", + "0.1000000001", + "0.1500000001", + "2", + "1" + ], + "EquipFuncParamList": [], + "DressRuleParamList": [] + }, { "ItemID": 35030107, "BaseItemID": 35030107, @@ -1316,6 +2569,59 @@ "EquipFuncParamList": [], "DressRuleParamList": [] }, + { + "ItemID": 35030108, + "BaseItemID": 35030108, + "NeedEquipSlot": true, + "EquipType": 0, + "EquipCategory": 2, + "EquipFunc": 0, + "DressRule": 1, + "AbilityName": "GridFight_Equipment_Normal_0108", + "GeneralPropertyList": [ + { + "Value": "0.2500000002", + "PropertyType": 1020 + }, + { + "Value": "30", + "PropertyType": 1045 + } + ], + "AbilityParamList": [ + "0.2500000002", + "30", + "0.1000000001" + ], + "EquipFuncParamList": [], + "DressRuleParamList": [] + }, + { + "ItemID": 35030202, + "BaseItemID": 35030202, + "NeedEquipSlot": true, + "EquipType": 0, + "EquipCategory": 2, + "EquipFunc": 0, + "DressRule": 0, + "AbilityName": "", + "GeneralPropertyList": [ + { + "Value": "0.6000000006", + "PropertyType": 1027 + }, + { + "Value": "0.1500000001", + "PropertyType": 1014 + } + ], + "AbilityParamList": [ + "0.6000000006", + "0.1500000001" + ], + "EquipFuncParamList": [], + "DressRuleParamList": [] + }, { "ItemID": 35030203, "BaseItemID": 35030203, @@ -1369,6 +2675,38 @@ "EquipFuncParamList": [], "DressRuleParamList": [] }, + { + "ItemID": 35030205, + "BaseItemID": 35030205, + "NeedEquipSlot": true, + "EquipType": 0, + "EquipCategory": 2, + "EquipFunc": 0, + "DressRule": 0, + "AbilityName": "GridFight_Equipment_Normal_0205", + "GeneralPropertyList": [ + { + "Value": "0.3000000003", + "PropertyType": 1027 + }, + { + "Value": "0.3000000003", + "PropertyType": 1030 + }, + { + "Value": "0.1000000001", + "PropertyType": 1009 + } + ], + "AbilityParamList": [ + "0.3000000003", + "0.1000000001", + "1", + "0.2500000002" + ], + "EquipFuncParamList": [], + "DressRuleParamList": [] + }, { "ItemID": 35030206, "BaseItemID": 35030206, @@ -1398,6 +2736,91 @@ "EquipFuncParamList": [], "DressRuleParamList": [] }, + { + "ItemID": 35030207, + "BaseItemID": 35030207, + "NeedEquipSlot": true, + "EquipType": 0, + "EquipCategory": 2, + "EquipFunc": 0, + "DressRule": 0, + "AbilityName": "GridFight_Equipment_Normal_0207", + "GeneralPropertyList": [ + { + "Value": "0.4000000004", + "PropertyType": 1027 + }, + { + "Value": "0.2500000002", + "PropertyType": 8 + } + ], + "AbilityParamList": [ + "0.4000000004", + "0.2500000002", + "3" + ], + "EquipFuncParamList": [], + "DressRuleParamList": [] + }, + { + "ItemID": 35030208, + "BaseItemID": 35030208, + "NeedEquipSlot": true, + "EquipType": 0, + "EquipCategory": 2, + "EquipFunc": 0, + "DressRule": 0, + "AbilityName": "GridFight_Equipment_Normal_0208", + "GeneralPropertyList": [ + { + "Value": "0.2000000002", + "PropertyType": 1027 + }, + { + "Value": "0.2000000002", + "PropertyType": 1030 + }, + { + "Value": "20", + "PropertyType": 1045 + } + ], + "AbilityParamList": [ + "0.2000000002", + "20", + "0.1000000001", + "0.1000000001" + ], + "EquipFuncParamList": [], + "DressRuleParamList": [] + }, + { + "ItemID": 35030303, + "BaseItemID": 35030303, + "NeedEquipSlot": true, + "EquipType": 0, + "EquipCategory": 2, + "EquipFunc": 0, + "DressRule": 0, + "AbilityName": "", + "GeneralPropertyList": [ + { + "Value": "0.6000000006", + "PropertyType": 1030 + }, + { + "Value": "0.1500000001", + "PropertyType": 1014 + } + ], + "AbilityParamList": [ + "0.6000000006", + "0.1500000001" + ], + "EquipFuncParamList": [], + "DressRuleParamList": [] + }, { "ItemID": 35030304, "BaseItemID": 35030304, @@ -1457,6 +2880,77 @@ "EquipFuncParamList": [], "DressRuleParamList": [] }, + { + "ItemID": 35030306, + "BaseItemID": 35030306, + "NeedEquipSlot": true, + "EquipType": 0, + "EquipCategory": 2, + "EquipFunc": 0, + "DressRule": 0, + "AbilityName": "GridFight_Equipment_Normal_0306", + "GeneralPropertyList": [ + { + "Value": "0.1000000001", + "PropertyType": 1018 + }, + { + "Value": "0.2000000002", + "PropertyType": 1027 + }, + { + "Value": "0.2000000002", + "PropertyType": 1030 + } + ], + "AbilityParamList": [ + "0.2000000002", + "0.1000000001", + "0.2500000002", + "20", + "0.2000000002", + "0.2000000002" + ], + "EquipFuncParamList": [], + "DressRuleParamList": [] + }, + { + "ItemID": 35030307, + "BaseItemID": 35030307, + "NeedEquipSlot": true, + "EquipType": 0, + "EquipCategory": 2, + "EquipFunc": 0, + "DressRule": 0, + "AbilityName": "GridFight_Equipment_Normal_0307", + "GeneralPropertyList": [ + { + "Value": "0.2000000002", + "PropertyType": 1027 + }, + { + "Value": "0.2000000002", + "PropertyType": 1030 + }, + { + "Value": "0.2500000002", + "PropertyType": 8 + }, + { + "Value": "0.1000000001", + "PropertyType": 1020 + } + ], + "AbilityParamList": [ + "0.2000000002", + "0.2500000002", + "0.4000000004", + "0.2500000002", + "0.1000000001" + ], + "EquipFuncParamList": [], + "DressRuleParamList": [] + }, { "ItemID": 35030308, "BaseItemID": 35030308, @@ -1547,6 +3041,39 @@ "EquipFuncParamList": [], "DressRuleParamList": [] }, + { + "ItemID": 35030406, + "BaseItemID": 35030406, + "NeedEquipSlot": true, + "EquipType": 0, + "EquipCategory": 2, + "EquipFunc": 0, + "DressRule": 0, + "AbilityName": "GridFight_Equipment_Normal_0406", + "GeneralPropertyList": [ + { + "Value": "0.2000000002", + "PropertyType": 1024 + }, + { + "Value": "0.1000000001", + "PropertyType": 1018 + }, + { + "Value": "0.2000000002", + "PropertyType": 1019 + } + ], + "AbilityParamList": [ + "0.2000000002", + "0.1000000001", + "0.2000000002", + "2.5000000005", + "3" + ], + "EquipFuncParamList": [], + "DressRuleParamList": [] + }, { "ItemID": 35030407, "BaseItemID": 35030407, @@ -1575,6 +3102,34 @@ "EquipFuncParamList": [], "DressRuleParamList": [] }, + { + "ItemID": 35030408, + "BaseItemID": 35030408, + "NeedEquipSlot": true, + "EquipType": 0, + "EquipCategory": 2, + "EquipFunc": 0, + "DressRule": 0, + "AbilityName": "GridFight_Equipment_Normal_0408", + "GeneralPropertyList": [ + { + "Value": "0.1000000001", + "PropertyType": 1024 + }, + { + "Value": "60", + "PropertyType": 1045 + } + ], + "AbilityParamList": [ + "0.1000000001", + "60", + "0.1000000001", + "5" + ], + "EquipFuncParamList": [], + "DressRuleParamList": [] + }, { "ItemID": 35030505, "BaseItemID": 35030505, @@ -1671,6 +3226,34 @@ "EquipFuncParamList": [], "DressRuleParamList": [] }, + { + "ItemID": 35030508, + "BaseItemID": 35030508, + "NeedEquipSlot": true, + "EquipType": 0, + "EquipCategory": 2, + "EquipFunc": 0, + "DressRule": 1, + "AbilityName": "GridFight_Equipment_Normal_0508", + "GeneralPropertyList": [ + { + "Value": "0.1500000001", + "PropertyType": 1009 + }, + { + "Value": "20", + "PropertyType": 1045 + } + ], + "AbilityParamList": [ + "0.1500000001", + "20", + "0.2000000002", + "10" + ], + "EquipFuncParamList": [], + "DressRuleParamList": [] + }, { "ItemID": 35030606, "BaseItemID": 35030606, @@ -1841,6 +3424,99 @@ "EquipFuncParamList": [], "DressRuleParamList": [] }, + { + "ItemID": 35040101, + "BaseItemID": 35040101, + "NeedEquipSlot": true, + "EquipType": 0, + "EquipCategory": 7, + "EquipFunc": 0, + "DressRule": 0, + "AbilityName": "GridFight_Equipment_Normal_0101", + "GeneralPropertyList": [ + { + "Value": "0.4000000004", + "PropertyType": 1020 + } + ], + "AbilityParamList": [ + "0.4000000004", + "0.3000000003", + "0", + "0", + "0", + "0", + "0" + ], + "EquipFuncParamList": [], + "DressRuleParamList": [] + }, + { + "ItemID": 35040102, + "BaseItemID": 35040102, + "NeedEquipSlot": true, + "EquipType": 0, + "EquipCategory": 7, + "EquipFunc": 0, + "DressRule": 0, + "AbilityName": "GridFight_Equipment_Normal_0102", + "GeneralPropertyList": [ + { + "Value": "0.2000000002", + "PropertyType": 1020 + }, + { + "Value": "0.4000000004", + "PropertyType": 1027 + }, + { + "Value": "0.4000000004", + "PropertyType": 1030 + } + ], + "AbilityParamList": [ + "0.2000000002", + "0.4000000004", + "0.1600000001", + "0", + "0", + "0", + "0" + ], + "EquipFuncParamList": [], + "DressRuleParamList": [] + }, + { + "ItemID": 35040103, + "BaseItemID": 35040103, + "NeedEquipSlot": true, + "EquipType": 0, + "EquipCategory": 7, + "EquipFunc": 0, + "DressRule": 0, + "AbilityName": "GridFight_Equipment_Normal_0103", + "GeneralPropertyList": [ + { + "Value": "0.4000000004", + "PropertyType": 1020 + }, + { + "Value": "0.6000000006", + "PropertyType": 1030 + } + ], + "AbilityParamList": [ + "0.4000000004", + "0.6000000006", + "0.5000000005", + "0", + "0", + "0", + "0" + ], + "EquipFuncParamList": [], + "DressRuleParamList": [] + }, { "ItemID": 35040104, "BaseItemID": 35030104, @@ -1872,6 +3548,72 @@ "EquipFuncParamList": [], "DressRuleParamList": [] }, + { + "ItemID": 35040105, + "BaseItemID": 35040105, + "NeedEquipSlot": true, + "EquipType": 0, + "EquipCategory": 7, + "EquipFunc": 0, + "DressRule": 0, + "AbilityName": "GridFight_Equipment_Normal_0105", + "GeneralPropertyList": [ + { + "Value": "0.2000000002", + "PropertyType": 1020 + }, + { + "Value": "0.2000000002", + "PropertyType": 1009 + }, + { + "Value": "0.3000000003", + "PropertyType": 1014 + } + ], + "AbilityParamList": [ + "0.2000000002", + "0.2000000002", + "0.3000000003", + "0.5000000005", + "0.6000000006", + "0", + "0" + ], + "EquipFuncParamList": [], + "DressRuleParamList": [] + }, + { + "ItemID": 35040106, + "BaseItemID": 35040106, + "NeedEquipSlot": true, + "EquipType": 0, + "EquipCategory": 7, + "EquipFunc": 0, + "DressRule": 0, + "AbilityName": "GridFight_Equipment_Normal_0106", + "GeneralPropertyList": [ + { + "Value": "0.3000000003", + "PropertyType": 1020 + }, + { + "Value": "0.2000000002", + "PropertyType": 1018 + } + ], + "AbilityParamList": [ + "0.3000000003", + "0.2000000002", + "0.3000000003", + "2", + "2", + "0", + "0" + ], + "EquipFuncParamList": [], + "DressRuleParamList": [] + }, { "ItemID": 35040107, "BaseItemID": 35030107, @@ -1903,6 +3645,68 @@ "EquipFuncParamList": [], "DressRuleParamList": [] }, + { + "ItemID": 35040108, + "BaseItemID": 35040108, + "NeedEquipSlot": true, + "EquipType": 0, + "EquipCategory": 7, + "EquipFunc": 0, + "DressRule": 1, + "AbilityName": "GridFight_Equipment_Normal_0108", + "GeneralPropertyList": [ + { + "Value": "0.5000000005", + "PropertyType": 1020 + }, + { + "Value": "60", + "PropertyType": 1045 + } + ], + "AbilityParamList": [ + "0.5000000005", + "60", + "0.2000000002", + "0", + "0", + "0", + "0" + ], + "EquipFuncParamList": [], + "DressRuleParamList": [] + }, + { + "ItemID": 35040202, + "BaseItemID": 35040202, + "NeedEquipSlot": true, + "EquipType": 0, + "EquipCategory": 7, + "EquipFunc": 0, + "DressRule": 0, + "AbilityName": "", + "GeneralPropertyList": [ + { + "Value": "1.2000000002", + "PropertyType": 1027 + }, + { + "Value": "0.3000000003", + "PropertyType": 1014 + } + ], + "AbilityParamList": [ + "1.2000000002", + "0.3000000003", + "0", + "0", + "0", + "0", + "0" + ], + "EquipFuncParamList": [], + "DressRuleParamList": [] + }, { "ItemID": 35040203, "BaseItemID": 35030203, @@ -1965,6 +3769,41 @@ "EquipFuncParamList": [], "DressRuleParamList": [] }, + { + "ItemID": 35040205, + "BaseItemID": 35040205, + "NeedEquipSlot": true, + "EquipType": 0, + "EquipCategory": 7, + "EquipFunc": 0, + "DressRule": 0, + "AbilityName": "GridFight_Equipment_Normal_0205", + "GeneralPropertyList": [ + { + "Value": "0.6000000006", + "PropertyType": 1027 + }, + { + "Value": "0.6000000006", + "PropertyType": 1030 + }, + { + "Value": "0.2000000002", + "PropertyType": 1009 + } + ], + "AbilityParamList": [ + "0.6000000006", + "0.2000000002", + "1", + "0.5000000005", + "0", + "0", + "0" + ], + "EquipFuncParamList": [], + "DressRuleParamList": [] + }, { "ItemID": 35040206, "BaseItemID": 35030206, @@ -1996,6 +3835,103 @@ "EquipFuncParamList": [], "DressRuleParamList": [] }, + { + "ItemID": 35040207, + "BaseItemID": 35040207, + "NeedEquipSlot": true, + "EquipType": 0, + "EquipCategory": 7, + "EquipFunc": 0, + "DressRule": 0, + "AbilityName": "GridFight_Equipment_Normal_0207", + "GeneralPropertyList": [ + { + "Value": "0.8000000007", + "PropertyType": 1027 + }, + { + "Value": "0.5000000005", + "PropertyType": 8 + } + ], + "AbilityParamList": [ + "0.8000000007", + "0.5000000005", + "6", + "0", + "4", + "0", + "0" + ], + "EquipFuncParamList": [], + "DressRuleParamList": [] + }, + { + "ItemID": 35040208, + "BaseItemID": 35040208, + "NeedEquipSlot": true, + "EquipType": 0, + "EquipCategory": 7, + "EquipFunc": 0, + "DressRule": 0, + "AbilityName": "GridFight_Equipment_Normal_0208", + "GeneralPropertyList": [ + { + "Value": "0.4000000004", + "PropertyType": 1027 + }, + { + "Value": "0.4000000004", + "PropertyType": 1030 + }, + { + "Value": "40", + "PropertyType": 1045 + } + ], + "AbilityParamList": [ + "0.4000000004", + "40", + "0.2000000002", + "0.2000000002", + "0", + "0", + "0" + ], + "EquipFuncParamList": [], + "DressRuleParamList": [] + }, + { + "ItemID": 35040303, + "BaseItemID": 35040303, + "NeedEquipSlot": true, + "EquipType": 0, + "EquipCategory": 7, + "EquipFunc": 0, + "DressRule": 0, + "AbilityName": "", + "GeneralPropertyList": [ + { + "Value": "1.2000000002", + "PropertyType": 1030 + }, + { + "Value": "0.3000000003", + "PropertyType": 1014 + } + ], + "AbilityParamList": [ + "1.2000000002", + "0.3000000003", + "0", + "0", + "0", + "0", + "0" + ], + "EquipFuncParamList": [], + "DressRuleParamList": [] + }, { "ItemID": 35040304, "BaseItemID": 35030304, @@ -2062,6 +3998,80 @@ "EquipFuncParamList": [], "DressRuleParamList": [] }, + { + "ItemID": 35040306, + "BaseItemID": 35040306, + "NeedEquipSlot": true, + "EquipType": 0, + "EquipCategory": 7, + "EquipFunc": 0, + "DressRule": 0, + "AbilityName": "GridFight_Equipment_Normal_0306", + "GeneralPropertyList": [ + { + "Value": "0.2000000002", + "PropertyType": 1018 + }, + { + "Value": "0.4000000004", + "PropertyType": 1027 + }, + { + "Value": "0.4000000004", + "PropertyType": 1030 + } + ], + "AbilityParamList": [ + "0.4000000004", + "0.2000000002", + "0.5000000005", + "20", + "0.4000000004", + "0.4000000004", + "0" + ], + "EquipFuncParamList": [], + "DressRuleParamList": [] + }, + { + "ItemID": 35040307, + "BaseItemID": 35040307, + "NeedEquipSlot": true, + "EquipType": 0, + "EquipCategory": 7, + "EquipFunc": 0, + "DressRule": 0, + "AbilityName": "GridFight_Equipment_Normal_0307", + "GeneralPropertyList": [ + { + "Value": "0.4000000004", + "PropertyType": 1027 + }, + { + "Value": "0.4000000004", + "PropertyType": 1030 + }, + { + "Value": "0.5000000005", + "PropertyType": 8 + }, + { + "Value": "0.2000000002", + "PropertyType": 1020 + } + ], + "AbilityParamList": [ + "0.4000000004", + "0.5000000005", + "0.8000000007", + "0.5000000005", + "0.2000000002", + "0", + "0" + ], + "EquipFuncParamList": [], + "DressRuleParamList": [] + }, { "ItemID": 35040308, "BaseItemID": 35030308, @@ -2159,6 +4169,41 @@ "EquipFuncParamList": [], "DressRuleParamList": [] }, + { + "ItemID": 35040406, + "BaseItemID": 35040406, + "NeedEquipSlot": true, + "EquipType": 0, + "EquipCategory": 7, + "EquipFunc": 0, + "DressRule": 0, + "AbilityName": "GridFight_Equipment_Normal_0406", + "GeneralPropertyList": [ + { + "Value": "0.4000000004", + "PropertyType": 1024 + }, + { + "Value": "0.2000000002", + "PropertyType": 1018 + }, + { + "Value": "0.4000000004", + "PropertyType": 1019 + } + ], + "AbilityParamList": [ + "0.4000000004", + "0.2000000002", + "0.4000000004", + "5", + "6", + "0", + "0" + ], + "EquipFuncParamList": [], + "DressRuleParamList": [] + }, { "ItemID": 35040407, "BaseItemID": 35030407, @@ -2190,6 +4235,37 @@ "EquipFuncParamList": [], "DressRuleParamList": [] }, + { + "ItemID": 35040408, + "BaseItemID": 35040408, + "NeedEquipSlot": true, + "EquipType": 0, + "EquipCategory": 7, + "EquipFunc": 0, + "DressRule": 0, + "AbilityName": "GridFight_Equipment_Normal_0408", + "GeneralPropertyList": [ + { + "Value": "0.2000000002", + "PropertyType": 1024 + }, + { + "Value": "120", + "PropertyType": 1045 + } + ], + "AbilityParamList": [ + "0.2000000002", + "120", + "0.2000000002", + "5", + "0", + "0", + "0" + ], + "EquipFuncParamList": [], + "DressRuleParamList": [] + }, { "ItemID": 35040505, "BaseItemID": 35030505, @@ -2292,6 +4368,37 @@ "EquipFuncParamList": [], "DressRuleParamList": [] }, + { + "ItemID": 35040508, + "BaseItemID": 35040508, + "NeedEquipSlot": true, + "EquipType": 0, + "EquipCategory": 7, + "EquipFunc": 0, + "DressRule": 1, + "AbilityName": "GridFight_Equipment_Normal_0508", + "GeneralPropertyList": [ + { + "Value": "0.3000000003", + "PropertyType": 1009 + }, + { + "Value": "40", + "PropertyType": 1045 + } + ], + "AbilityParamList": [ + "0.3000000003", + "40", + "0.4000000004", + "20", + "0", + "0", + "0" + ], + "EquipFuncParamList": [], + "DressRuleParamList": [] + }, { "ItemID": 35040606, "BaseItemID": 35030606, @@ -2482,6 +4589,747 @@ "EquipFuncParamList": [], "DressRuleParamList": [] }, + { + "ItemID": 35051001, + "BaseItemID": 35051001, + "NeedEquipSlot": true, + "EquipType": 0, + "EquipCategory": 3, + "EquipFunc": 10, + "DressRule": 5, + "AbilityName": "", + "GeneralPropertyList": [ + { + "Value": "0.1000000001", + "PropertyType": 1020 + } + ], + "AbilityParamList": [], + "EquipFuncParamList": [ + 1001 + ], + "DressRuleParamList": [ + 1001 + ] + }, + { + "ItemID": 35051003, + "BaseItemID": 35051003, + "NeedEquipSlot": true, + "EquipType": 0, + "EquipCategory": 3, + "EquipFunc": 10, + "DressRule": 5, + "AbilityName": "", + "GeneralPropertyList": [ + { + "Value": "30", + "PropertyType": 1045 + } + ], + "AbilityParamList": [], + "EquipFuncParamList": [ + 1003 + ], + "DressRuleParamList": [ + 1003 + ] + }, + { + "ItemID": 35051005, + "BaseItemID": 35051005, + "NeedEquipSlot": true, + "EquipType": 0, + "EquipCategory": 3, + "EquipFunc": 10, + "DressRule": 5, + "AbilityName": "", + "GeneralPropertyList": [ + { + "Value": "0.1000000001", + "PropertyType": 1018 + } + ], + "AbilityParamList": [], + "EquipFuncParamList": [ + 1005 + ], + "DressRuleParamList": [ + 1005 + ] + }, + { + "ItemID": 35051006, + "BaseItemID": 35051006, + "NeedEquipSlot": true, + "EquipType": 0, + "EquipCategory": 3, + "EquipFunc": 10, + "DressRule": 5, + "AbilityName": "", + "GeneralPropertyList": [ + { + "Value": "0.2500000002", + "PropertyType": 8 + } + ], + "AbilityParamList": [], + "EquipFuncParamList": [ + 1006 + ], + "DressRuleParamList": [ + 1006 + ] + }, + { + "ItemID": 35051007, + "BaseItemID": 35051007, + "NeedEquipSlot": true, + "EquipType": 0, + "EquipCategory": 3, + "EquipFunc": 10, + "DressRule": 5, + "AbilityName": "", + "GeneralPropertyList": [ + { + "Value": "0.2000000002", + "PropertyType": 1027 + } + ], + "AbilityParamList": [], + "EquipFuncParamList": [ + 1007 + ], + "DressRuleParamList": [ + 1007 + ] + }, + { + "ItemID": 35051008, + "BaseItemID": 35051008, + "NeedEquipSlot": true, + "EquipType": 0, + "EquipCategory": 3, + "EquipFunc": 10, + "DressRule": 5, + "AbilityName": "", + "GeneralPropertyList": [ + { + "Value": "0.1000000001", + "PropertyType": 1009 + } + ], + "AbilityParamList": [], + "EquipFuncParamList": [ + 1008 + ], + "DressRuleParamList": [ + 1008 + ] + }, + { + "ItemID": 35051010, + "BaseItemID": 35051010, + "NeedEquipSlot": true, + "EquipType": 0, + "EquipCategory": 3, + "EquipFunc": 10, + "DressRule": 5, + "AbilityName": "", + "GeneralPropertyList": [ + { + "Value": "0.1000000001", + "PropertyType": 1024 + } + ], + "AbilityParamList": [], + "EquipFuncParamList": [ + 1010 + ], + "DressRuleParamList": [ + 1010 + ] + }, + { + "ItemID": 35051011, + "BaseItemID": 35051011, + "NeedEquipSlot": true, + "EquipType": 0, + "EquipCategory": 3, + "EquipFunc": 10, + "DressRule": 5, + "AbilityName": "", + "GeneralPropertyList": [ + { + "Value": "0.1000000001", + "PropertyType": 1020 + } + ], + "AbilityParamList": [], + "EquipFuncParamList": [ + 1011 + ], + "DressRuleParamList": [ + 1011 + ] + }, + { + "ItemID": 35051012, + "BaseItemID": 35051012, + "NeedEquipSlot": true, + "EquipType": 0, + "EquipCategory": 3, + "EquipFunc": 10, + "DressRule": 5, + "AbilityName": "", + "GeneralPropertyList": [ + { + "Value": "0.2000000002", + "PropertyType": 1030 + } + ], + "AbilityParamList": [], + "EquipFuncParamList": [ + 1012 + ], + "DressRuleParamList": [ + 1012 + ] + }, + { + "ItemID": 35051013, + "BaseItemID": 35051013, + "NeedEquipSlot": true, + "EquipType": 0, + "EquipCategory": 3, + "EquipFunc": 10, + "DressRule": 5, + "AbilityName": "", + "GeneralPropertyList": [ + { + "Value": "0.1000000001", + "PropertyType": 1014 + } + ], + "AbilityParamList": [ + "0.2000000002", + "1" + ], + "EquipFuncParamList": [ + 1013 + ], + "DressRuleParamList": [ + 1013 + ] + }, + { + "ItemID": 35052001, + "BaseItemID": 35052001, + "NeedEquipSlot": true, + "EquipType": 0, + "EquipCategory": 3, + "EquipFunc": 11, + "DressRule": 5, + "AbilityName": "GridFight_Equipment_Emblem_2001", + "GeneralPropertyList": [ + { + "Value": "0.1000000001", + "PropertyType": 1020 + } + ], + "AbilityParamList": [ + "0.1000000001" + ], + "EquipFuncParamList": [ + 2001 + ], + "DressRuleParamList": [ + 2001 + ] + }, + { + "ItemID": 35052002, + "BaseItemID": 35052002, + "NeedEquipSlot": true, + "EquipType": 0, + "EquipCategory": 3, + "EquipFunc": 11, + "DressRule": 5, + "AbilityName": "", + "GeneralPropertyList": [ + { + "Value": "0.2500000002", + "PropertyType": 8 + }, + { + "Value": "1.5000000005", + "PropertyType": 59 + } + ], + "AbilityParamList": [ + "0.2500000002", + "1.5000000005" + ], + "EquipFuncParamList": [ + 2002 + ], + "DressRuleParamList": [ + 2002 + ] + }, + { + "ItemID": 35052003, + "BaseItemID": 35052003, + "NeedEquipSlot": true, + "EquipType": 0, + "EquipCategory": 3, + "EquipFunc": 11, + "DressRule": 5, + "AbilityName": "GridFight_Equipment_Emblem_2003", + "GeneralPropertyList": [ + { + "Value": "0.1000000001", + "PropertyType": 1024 + } + ], + "AbilityParamList": [ + "0.0800000001" + ], + "EquipFuncParamList": [ + 2003 + ], + "DressRuleParamList": [ + 2003 + ] + }, + { + "ItemID": 35052004, + "BaseItemID": 35052004, + "NeedEquipSlot": true, + "EquipType": 0, + "EquipCategory": 3, + "EquipFunc": 11, + "DressRule": 5, + "AbilityName": "GridFight_Equipment_Emblem_2004", + "GeneralPropertyList": [ + { + "Value": "30", + "PropertyType": 1045 + } + ], + "AbilityParamList": [ + "10" + ], + "EquipFuncParamList": [ + 2004 + ], + "DressRuleParamList": [ + 2004 + ] + }, + { + "ItemID": 35052005, + "BaseItemID": 35052005, + "NeedEquipSlot": true, + "EquipType": 0, + "EquipCategory": 3, + "EquipFunc": 11, + "DressRule": 5, + "AbilityName": "GridFight_Equipment_Emblem_2005", + "GeneralPropertyList": [ + { + "Value": "0.1000000001", + "PropertyType": 1018 + } + ], + "AbilityParamList": [ + "0.1000000001" + ], + "EquipFuncParamList": [ + 2005 + ], + "DressRuleParamList": [ + 2005 + ] + }, + { + "ItemID": 35052006, + "BaseItemID": 35052006, + "NeedEquipSlot": true, + "EquipType": 0, + "EquipCategory": 3, + "EquipFunc": 11, + "DressRule": 5, + "AbilityName": "GridFight_Equipment_Emblem_2006", + "GeneralPropertyList": [ + { + "Value": "0.1000000001", + "PropertyType": 1009 + } + ], + "AbilityParamList": [ + "0.2000000002", + "0.4000000004" + ], + "EquipFuncParamList": [ + 2006 + ], + "DressRuleParamList": [ + 2006 + ] + }, + { + "ItemID": 35052007, + "BaseItemID": 35052007, + "NeedEquipSlot": true, + "EquipType": 0, + "EquipCategory": 3, + "EquipFunc": 11, + "DressRule": 5, + "AbilityName": "GridFight_Equipment_Emblem_2007", + "GeneralPropertyList": [ + { + "Value": "0.2000000002", + "PropertyType": 1030 + } + ], + "AbilityParamList": [ + "0.0500000000", + "5" + ], + "EquipFuncParamList": [ + 2007 + ], + "DressRuleParamList": [ + 2007 + ] + }, + { + "ItemID": 35052008, + "BaseItemID": 35052008, + "NeedEquipSlot": true, + "EquipType": 0, + "EquipCategory": 3, + "EquipFunc": 11, + "DressRule": 5, + "AbilityName": "GridFight_Equipment_Emblem_2008", + "GeneralPropertyList": [ + { + "Value": "0.1000000001", + "PropertyType": 1020 + } + ], + "AbilityParamList": [ + "3" + ], + "EquipFuncParamList": [ + 2008 + ], + "DressRuleParamList": [ + 2008 + ] + }, + { + "ItemID": 35052009, + "BaseItemID": 35052009, + "NeedEquipSlot": true, + "EquipType": 0, + "EquipCategory": 3, + "EquipFunc": 11, + "DressRule": 5, + "AbilityName": "GridFight_Equipment_Emblem_2009", + "GeneralPropertyList": [ + { + "Value": "0.1000000001", + "PropertyType": 1024 + } + ], + "AbilityParamList": [ + "0.1000000001" + ], + "EquipFuncParamList": [ + 2009 + ], + "DressRuleParamList": [ + 2009 + ] + }, + { + "ItemID": 35052010, + "BaseItemID": 35052010, + "NeedEquipSlot": true, + "EquipType": 0, + "EquipCategory": 3, + "EquipFunc": 11, + "DressRule": 5, + "AbilityName": "GridFight_Equipment_Emblem_2010", + "GeneralPropertyList": [ + { + "Value": "0.1000000001", + "PropertyType": 1018 + } + ], + "AbilityParamList": [ + "1" + ], + "EquipFuncParamList": [ + 2010 + ], + "DressRuleParamList": [ + 2010 + ] + }, + { + "ItemID": 35052011, + "BaseItemID": 35052011, + "NeedEquipSlot": true, + "EquipType": 0, + "EquipCategory": 3, + "EquipFunc": 11, + "DressRule": 5, + "AbilityName": "GridFight_Equipment_Emblem_2011", + "GeneralPropertyList": [ + { + "Value": "0.2000000002", + "PropertyType": 1027 + } + ], + "AbilityParamList": [ + "0.2000000002" + ], + "EquipFuncParamList": [ + 2011 + ], + "DressRuleParamList": [ + 2011 + ] + }, + { + "ItemID": 35052012, + "BaseItemID": 35052012, + "NeedEquipSlot": true, + "EquipType": 0, + "EquipCategory": 3, + "EquipFunc": 11, + "DressRule": 5, + "AbilityName": "GridFight_Equipment_Emblem_2012", + "GeneralPropertyList": [ + { + "Value": "0.1000000001", + "PropertyType": 1014 + } + ], + "AbilityParamList": [ + "0.2000000002", + "1" + ], + "EquipFuncParamList": [ + 2012 + ], + "DressRuleParamList": [ + 2012 + ] + }, + { + "ItemID": 35100001, + "BaseItemID": 35100001, + "NeedEquipSlot": true, + "EquipType": 0, + "EquipCategory": 5, + "EquipFunc": 0, + "DressRule": 4, + "AbilityName": "GridFight_Equipment_Day_0001", + "GeneralPropertyList": [ + { + "Value": "0.1000000001", + "PropertyType": 1020 + }, + { + "Value": "0.2500000002", + "PropertyType": 8 + } + ], + "AbilityParamList": [ + "0.1000000001", + "0.2500000002", + "10", + "0.0200000000", + "0.0100000000", + "0" + ], + "EquipFuncParamList": [], + "DressRuleParamList": [ + 1011 + ] + }, + { + "ItemID": 35100002, + "BaseItemID": 35100002, + "NeedEquipSlot": true, + "EquipType": 0, + "EquipCategory": 5, + "EquipFunc": 0, + "DressRule": 4, + "AbilityName": "GridFight_Equipment_Day_0002", + "GeneralPropertyList": [ + { + "Value": "0.2000000002", + "PropertyType": 1027 + }, + { + "Value": "0.2000000002", + "PropertyType": 1030 + } + ], + "AbilityParamList": [ + "0.2000000002", + "0.2500000002", + "0" + ], + "EquipFuncParamList": [], + "DressRuleParamList": [ + 1011 + ] + }, + { + "ItemID": 35100003, + "BaseItemID": 35100003, + "NeedEquipSlot": true, + "EquipType": 0, + "EquipCategory": 5, + "EquipFunc": 0, + "DressRule": 4, + "AbilityName": "GridFight_Equipment_Day_0003", + "GeneralPropertyList": [ + { + "Value": "0.2000000002", + "PropertyType": 1027 + }, + { + "Value": "0.2000000002", + "PropertyType": 1030 + }, + { + "Value": "0.2500000002", + "PropertyType": 8 + }, + { + "Value": "0.1000000001", + "PropertyType": 1020 + } + ], + "AbilityParamList": [ + "0.2000000002", + "0.2500000002", + "0.4000000004", + "0.2500000002", + "0.1000000001" + ], + "EquipFuncParamList": [], + "DressRuleParamList": [ + 1011 + ] + }, + { + "ItemID": 35100011, + "BaseItemID": 35100011, + "NeedEquipSlot": true, + "EquipType": 0, + "EquipCategory": 5, + "EquipFunc": 0, + "DressRule": 4, + "AbilityName": "GridFight_Equipment_Day_0001_2", + "GeneralPropertyList": [ + { + "Value": "0.1500000001", + "PropertyType": 1020 + }, + { + "Value": "0.2500000002", + "PropertyType": 8 + } + ], + "AbilityParamList": [ + "0.2000000002", + "0.5000000005", + "10", + "0.0400000000", + "0.0200000000", + "0.0100000000" + ], + "EquipFuncParamList": [], + "DressRuleParamList": [ + 1011 + ] + }, + { + "ItemID": 35100012, + "BaseItemID": 35100012, + "NeedEquipSlot": true, + "EquipType": 0, + "EquipCategory": 5, + "EquipFunc": 0, + "DressRule": 4, + "AbilityName": "GridFight_Equipment_Day_0002", + "GeneralPropertyList": [ + { + "Value": "0.4000000004", + "PropertyType": 1027 + }, + { + "Value": "0.4000000004", + "PropertyType": 1030 + } + ], + "AbilityParamList": [ + "0.4000000004", + "0.5000000005", + "0.1000000001" + ], + "EquipFuncParamList": [], + "DressRuleParamList": [ + 1011 + ] + }, + { + "ItemID": 35100013, + "BaseItemID": 35100013, + "NeedEquipSlot": true, + "EquipType": 0, + "EquipCategory": 5, + "EquipFunc": 0, + "DressRule": 4, + "AbilityName": "GridFight_Equipment_Day_0003", + "GeneralPropertyList": [ + { + "Value": "0.4000000004", + "PropertyType": 1027 + }, + { + "Value": "0.4000000004", + "PropertyType": 1030 + }, + { + "Value": "0.5000000005", + "PropertyType": 8 + }, + { + "Value": "0.2000000002", + "PropertyType": 1020 + } + ], + "AbilityParamList": [ + "0.4000000004", + "0.5000000005", + "1", + "0.7500000007", + "0.2000000002" + ], + "EquipFuncParamList": [], + "DressRuleParamList": [ + 1011 + ] + }, { "ItemID": 35130999, "BaseItemID": 35130999, diff --git a/resources/CurrencyWarsNodeDrops.json b/resources/CurrencyWarsNodeDrops.json new file mode 100644 index 0000000..e32d711 --- /dev/null +++ b/resources/CurrencyWarsNodeDrops.json @@ -0,0 +1,64 @@ +{ + "format_version": 1, + "target_client_version": "4.6.53", + "target_assembly_sha256": "c5e02e6e984f7e5577c78b995a8efeee2d06ee22e67d484ad5e859c8bf21e144", + "provenance": { + "repository": "https://gaytea.cod-algieba.ts.net/xaodev/Currency-War-SR", + "commit": "7d342af29fc9e4fec49bf5202c0c8207c1ba9f71", + "license": "AGPL-3.0-only", + "inputs_sha256": { + "orb_rewards.json": "4c0116bd747886c2d23637a27b8a0f552d7a07f6afaa25b889655ef40e9f9e57" + } + }, + "node_drops": { + "900": { + "rolls": 4, + "weights": { + "102": 1, + "199": 1, + "203": 1, + "204": 1 + } + }, + "901": { + "rolls": 5, + "weights": { + "103": 1, + "199": 2, + "203": 1, + "204": 1 + } + }, + "902": { + "rolls": 8, + "weights": { + "102": 1, + "103": 1, + "203": 2, + "205": 1, + "206": 3 + } + }, + "903": { + "rolls": 10, + "weights": { + "102": 1, + "103": 1, + "199": 2, + "203": 3, + "205": 1, + "207": 2 + } + }, + "904": { + "rolls": 6, + "weights": { + "103": 1, + "203": 1, + "208": 2, + "305": 1, + "120031": 1 + } + } + } +} diff --git a/resources/CurrencyWarsOrbRecipes.json b/resources/CurrencyWarsOrbRecipes.json index ded7771..5a6f822 100644 --- a/resources/CurrencyWarsOrbRecipes.json +++ b/resources/CurrencyWarsOrbRecipes.json @@ -8971,6 +8971,20 @@ } ] ] + }, + { + "ID": 305, + "Status": "Implemented", + "Engine": "Orb", + "Choices": [ + [ + { + "Reward": "Item", + "ItemID": 350107, + "Count": 1 + } + ] + ] } ] -} +} \ No newline at end of file diff --git a/resources/CurrencyWarsScore.json b/resources/CurrencyWarsScore.json new file mode 100644 index 0000000..e473219 --- /dev/null +++ b/resources/CurrencyWarsScore.json @@ -0,0 +1,1981 @@ +{ + "format_version": 1, + "target_client_version": "4.6.53", + "target_assembly_sha256": "c5e02e6e984f7e5577c78b995a8efeee2d06ee22e67d484ad5e859c8bf21e144", + "provenance": { + "repository": "https://gaytea.cod-algieba.ts.net/xaodev/Currency-War-SR", + "commit": "7d342af29fc9e4fec49bf5202c0c8207c1ba9f71", + "license": "AGPL-3.0-only", + "inputs_sha256": { + "score.json": "67b86fc156018ddd398b6ab927923b00c31ddb8929f8d9f18f508a9601ecb643", + "offering_levels.json": "e663e3adbd3f5a5148a836e0774c102a84150a53bb5835f0c67e2990c08b4e69" + } + }, + "weekly_cap": 18000, + "weekly_ranks": [ + { + "rank": 1, + "score": 1500, + "reward": 312901 + }, + { + "rank": 2, + "score": 3000, + "reward": 312902 + }, + { + "rank": 3, + "score": 4500, + "reward": 312903 + }, + { + "rank": 4, + "score": 6000, + "reward": 312904 + }, + { + "rank": 5, + "score": 7500, + "reward": 312905 + }, + { + "rank": 6, + "score": 9000, + "reward": 312906 + }, + { + "rank": 7, + "score": 10500, + "reward": 312907 + }, + { + "rank": 8, + "score": 12000, + "reward": 312908 + }, + { + "rank": 9, + "score": 13500, + "reward": 312909 + }, + { + "rank": 10, + "score": 15000, + "reward": 312910 + }, + { + "rank": 11, + "score": 16500, + "reward": 312911 + }, + { + "rank": 12, + "score": 18000, + "reward": 312912 + } + ], + "divisions": { + "1": { + "rule": 801, + "oc_rule": 801, + "weekly_modify": 100, + "exp_modify": 100 + }, + "2": { + "rule": 802, + "oc_rule": 802, + "weekly_modify": 100, + "exp_modify": 100 + }, + "10101": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 100 + }, + "10102": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 100 + }, + "10103": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 100 + }, + "10201": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 100 + }, + "10202": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 105 + }, + "10203": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 110 + }, + "10301": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 115 + }, + "10302": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 120 + }, + "10303": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 125 + }, + "10401": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 130 + }, + "10402": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 135 + }, + "10403": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 140 + }, + "10404": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 145 + }, + "10405": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 150 + }, + "10501": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 155 + }, + "10502": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 160 + }, + "10503": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 165 + }, + "10504": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 170 + }, + "10505": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 175 + }, + "10601": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "10602": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "10603": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "10604": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "10605": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "10606": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "10607": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "10608": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "10701": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "10702": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "10703": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "10704": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "10705": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "10706": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "10707": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "10708": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "10801": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "10802": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "10803": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "10804": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "10805": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "10806": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "10807": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "10808": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "10809": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "10810": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "10901": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "10902": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "10903": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "10904": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "10905": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "10906": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "10907": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "10908": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "10909": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "10910": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "10911": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "10912": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "10913": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "10914": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "10915": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "10916": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "10917": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "10918": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "10919": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "10920": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "10921": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "10922": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "10923": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "10924": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "10925": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "10926": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "10927": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "10928": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "10929": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "10930": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "10931": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "10932": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "10933": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "10934": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "10935": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "10936": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "10937": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "10938": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "10939": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "10940": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "10941": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "10942": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "10943": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "10944": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "10945": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "10946": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "10947": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "10948": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "10949": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "10950": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "20101": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 100 + }, + "20102": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 100 + }, + "20103": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 100 + }, + "20201": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 100 + }, + "20202": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 105 + }, + "20203": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 110 + }, + "20301": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 115 + }, + "20302": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 120 + }, + "20303": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 125 + }, + "20401": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 130 + }, + "20402": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 135 + }, + "20403": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 140 + }, + "20404": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 145 + }, + "20405": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 150 + }, + "20501": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 155 + }, + "20502": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 160 + }, + "20503": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 165 + }, + "20504": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 170 + }, + "20505": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 175 + }, + "20506": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "20507": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "20601": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "20602": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "20603": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "20604": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "20605": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "20606": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "20607": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "20608": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "20609": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "20610": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "20701": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "20702": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "20703": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "20704": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "20705": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "20706": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "20707": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "20708": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "20709": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "20710": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "20801": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "20802": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "20803": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "20804": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "20805": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "20806": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "20807": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "20808": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "20809": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "20810": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "20901": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "20902": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "20903": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "20904": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "20905": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "20906": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "20907": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "20908": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "20909": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "20910": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "20911": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "20912": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "20913": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "20914": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "20915": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "20916": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "20917": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "20918": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "20919": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + }, + "20920": { + "rule": 1, + "oc_rule": 2, + "weekly_modify": 100, + "exp_modify": 180 + } + }, + "rules": { + "1": [ + { + "chapter": 1, + "section": 1, + "weekly": 1200, + "exp": 25 + }, + { + "chapter": 1, + "section": 2, + "weekly": 2400, + "exp": 50 + }, + { + "chapter": 1, + "section": 3, + "weekly": 3600, + "exp": 125 + }, + { + "chapter": 1, + "section": 4, + "weekly": 4800, + "exp": 200 + }, + { + "chapter": 1, + "section": 5, + "weekly": 4800, + "exp": 200 + }, + { + "chapter": 1, + "section": 6, + "weekly": 6000, + "exp": 275 + }, + { + "chapter": 1, + "section": 7, + "weekly": 7200, + "exp": 350 + }, + { + "chapter": 1, + "section": 8, + "weekly": 8400, + "exp": 425 + }, + { + "chapter": 1, + "section": 9, + "weekly": 9600, + "exp": 500 + }, + { + "chapter": 2, + "section": 1, + "weekly": 10800, + "exp": 575 + }, + { + "chapter": 2, + "section": 2, + "weekly": 12000, + "exp": 650 + }, + { + "chapter": 2, + "section": 3, + "weekly": 12000, + "exp": 650 + }, + { + "chapter": 2, + "section": 4, + "weekly": 13200, + "exp": 725 + }, + { + "chapter": 2, + "section": 5, + "weekly": 14400, + "exp": 800 + }, + { + "chapter": 2, + "section": 6, + "weekly": 15600, + "exp": 875 + }, + { + "chapter": 2, + "section": 7, + "weekly": 16800, + "exp": 950 + }, + { + "chapter": 3, + "section": 1, + "weekly": 18000, + "exp": 1050 + }, + { + "chapter": 3, + "section": 2, + "weekly": 18000, + "exp": 1125 + }, + { + "chapter": 3, + "section": 3, + "weekly": 18000, + "exp": 1125 + }, + { + "chapter": 3, + "section": 4, + "weekly": 18000, + "exp": 1225 + }, + { + "chapter": 3, + "section": 5, + "weekly": 18000, + "exp": 1325 + }, + { + "chapter": 3, + "section": 6, + "weekly": 18000, + "exp": 1425 + }, + { + "chapter": 3, + "section": 7, + "weekly": 18000, + "exp": 1500 + } + ], + "2": [ + { + "chapter": 1, + "section": 1, + "weekly": 1800, + "exp": 40 + }, + { + "chapter": 1, + "section": 2, + "weekly": 3600, + "exp": 120 + }, + { + "chapter": 1, + "section": 3, + "weekly": 3600, + "exp": 120 + }, + { + "chapter": 1, + "section": 4, + "weekly": 5400, + "exp": 200 + }, + { + "chapter": 1, + "section": 5, + "weekly": 7200, + "exp": 280 + }, + { + "chapter": 1, + "section": 6, + "weekly": 9000, + "exp": 360 + }, + { + "chapter": 2, + "section": 1, + "weekly": 10800, + "exp": 440 + }, + { + "chapter": 2, + "section": 2, + "weekly": 10800, + "exp": 440 + }, + { + "chapter": 2, + "section": 3, + "weekly": 12600, + "exp": 520 + }, + { + "chapter": 2, + "section": 4, + "weekly": 14400, + "exp": 600 + }, + { + "chapter": 2, + "section": 5, + "weekly": 16200, + "exp": 680 + }, + { + "chapter": 3, + "section": 1, + "weekly": 18000, + "exp": 760 + }, + { + "chapter": 3, + "section": 2, + "weekly": 18000, + "exp": 760 + }, + { + "chapter": 3, + "section": 3, + "weekly": 18000, + "exp": 840 + }, + { + "chapter": 3, + "section": 4, + "weekly": 18000, + "exp": 920 + }, + { + "chapter": 3, + "section": 5, + "weekly": 18000, + "exp": 1000 + } + ], + "801": [ + { + "chapter": 1, + "section": 1, + "weekly": 0, + "exp": 0 + }, + { + "chapter": 1, + "section": 2, + "weekly": 0, + "exp": 0 + }, + { + "chapter": 1, + "section": 3, + "weekly": 900, + "exp": 180 + }, + { + "chapter": 1, + "section": 4, + "weekly": 1800, + "exp": 360 + }, + { + "chapter": 1, + "section": 5, + "weekly": 2700, + "exp": 540 + }, + { + "chapter": 1, + "section": 6, + "weekly": 3600, + "exp": 720 + }, + { + "chapter": 1, + "section": 7, + "weekly": 4500, + "exp": 900 + } + ], + "802": [ + { + "chapter": 1, + "section": 1, + "weekly": 0, + "exp": 0 + }, + { + "chapter": 1, + "section": 2, + "weekly": 630, + "exp": 120 + }, + { + "chapter": 1, + "section": 3, + "weekly": 1260, + "exp": 240 + }, + { + "chapter": 1, + "section": 4, + "weekly": 1890, + "exp": 360 + }, + { + "chapter": 1, + "section": 5, + "weekly": 2520, + "exp": 480 + }, + { + "chapter": 1, + "section": 6, + "weekly": 3150, + "exp": 600 + }, + { + "chapter": 2, + "section": 1, + "weekly": 3780, + "exp": 720 + }, + { + "chapter": 2, + "section": 2, + "weekly": 4410, + "exp": 840 + }, + { + "chapter": 2, + "section": 3, + "weekly": 5040, + "exp": 960 + }, + { + "chapter": 2, + "section": 4, + "weekly": 5670, + "exp": 1080 + }, + { + "chapter": 2, + "section": 5, + "weekly": 6300, + "exp": 1200 + } + ], + "803": [ + { + "chapter": 1, + "section": 1, + "weekly": 480, + "exp": 75 + }, + { + "chapter": 1, + "section": 2, + "weekly": 960, + "exp": 150 + }, + { + "chapter": 1, + "section": 3, + "weekly": 1440, + "exp": 225 + }, + { + "chapter": 1, + "section": 4, + "weekly": 1920, + "exp": 300 + }, + { + "chapter": 1, + "section": 5, + "weekly": 1920, + "exp": 300 + }, + { + "chapter": 1, + "section": 6, + "weekly": 2400, + "exp": 375 + }, + { + "chapter": 1, + "section": 7, + "weekly": 2880, + "exp": 450 + }, + { + "chapter": 1, + "section": 8, + "weekly": 3360, + "exp": 525 + }, + { + "chapter": 1, + "section": 9, + "weekly": 3840, + "exp": 600 + }, + { + "chapter": 2, + "section": 1, + "weekly": 4320, + "exp": 675 + }, + { + "chapter": 2, + "section": 2, + "weekly": 4800, + "exp": 750 + }, + { + "chapter": 2, + "section": 3, + "weekly": 4800, + "exp": 750 + }, + { + "chapter": 2, + "section": 4, + "weekly": 5280, + "exp": 825 + }, + { + "chapter": 2, + "section": 5, + "weekly": 5760, + "exp": 900 + }, + { + "chapter": 2, + "section": 6, + "weekly": 6240, + "exp": 975 + }, + { + "chapter": 2, + "section": 7, + "weekly": 6720, + "exp": 1050 + }, + { + "chapter": 3, + "section": 1, + "weekly": 7200, + "exp": 1125 + }, + { + "chapter": 3, + "section": 2, + "weekly": 7200, + "exp": 1200 + }, + { + "chapter": 3, + "section": 3, + "weekly": 7200, + "exp": 1200 + }, + { + "chapter": 3, + "section": 4, + "weekly": 7200, + "exp": 1275 + }, + { + "chapter": 3, + "section": 5, + "weekly": 7200, + "exp": 1350 + }, + { + "chapter": 3, + "section": 6, + "weekly": 7200, + "exp": 1425 + }, + { + "chapter": 3, + "section": 7, + "weekly": 7200, + "exp": 1500 + } + ] + }, + "offering_levels": [ + { + "level": 1, + "item_cost": 800, + "reward_id": 331001 + }, + { + "level": 2, + "item_cost": 800, + "reward_id": 331002 + }, + { + "level": 3, + "item_cost": 800, + "reward_id": 331003 + }, + { + "level": 4, + "item_cost": 800, + "reward_id": 331004 + }, + { + "level": 5, + "item_cost": 800, + "reward_id": 331005 + }, + { + "level": 6, + "item_cost": 800, + "reward_id": 331006 + }, + { + "level": 7, + "item_cost": 800, + "reward_id": 331007 + }, + { + "level": 8, + "item_cost": 800, + "reward_id": 331008 + }, + { + "level": 9, + "item_cost": 800, + "reward_id": 331009 + }, + { + "level": 10, + "item_cost": 800, + "reward_id": 331010 + }, + { + "level": 11, + "item_cost": 800, + "reward_id": 331011 + }, + { + "level": 12, + "item_cost": 800, + "reward_id": 331012 + }, + { + "level": 13, + "item_cost": 800, + "reward_id": 331013 + }, + { + "level": 14, + "item_cost": 800, + "reward_id": 331014 + }, + { + "level": 15, + "item_cost": 800, + "reward_id": 331015 + }, + { + "level": 16, + "item_cost": 800, + "reward_id": 331016 + }, + { + "level": 17, + "item_cost": 800, + "reward_id": 331017 + }, + { + "level": 18, + "item_cost": 800, + "reward_id": 331018 + }, + { + "level": 19, + "item_cost": 800, + "reward_id": 331019 + }, + { + "level": 20, + "item_cost": 800, + "reward_id": 331020 + }, + { + "level": 21, + "item_cost": 800, + "reward_id": 331021 + }, + { + "level": 22, + "item_cost": 800, + "reward_id": 331022 + }, + { + "level": 23, + "item_cost": 800, + "reward_id": 331023 + }, + { + "level": 24, + "item_cost": 800, + "reward_id": 331024 + }, + { + "level": 25, + "item_cost": 800, + "reward_id": 331025 + }, + { + "level": 26, + "item_cost": 800, + "reward_id": 331026 + }, + { + "level": 27, + "item_cost": 800, + "reward_id": 331027 + }, + { + "level": 28, + "item_cost": 800, + "reward_id": 331028 + }, + { + "level": 29, + "item_cost": 800, + "reward_id": 331029 + }, + { + "level": 30, + "item_cost": 800, + "reward_id": 331030 + }, + { + "level": 31, + "item_cost": 800, + "reward_id": 331031 + }, + { + "level": 32, + "item_cost": 800, + "reward_id": 331032 + }, + { + "level": 33, + "item_cost": 800, + "reward_id": 331033 + }, + { + "level": 34, + "item_cost": 800, + "reward_id": 331034 + }, + { + "level": 35, + "item_cost": 800, + "reward_id": 331035 + }, + { + "level": 36, + "item_cost": 800, + "reward_id": 331036 + }, + { + "level": 37, + "item_cost": 800, + "reward_id": 331037 + }, + { + "level": 38, + "item_cost": 800, + "reward_id": 331038 + }, + { + "level": 39, + "item_cost": 800, + "reward_id": 331039 + }, + { + "level": 40, + "item_cost": 800, + "reward_id": 331040 + }, + { + "level": 41, + "item_cost": 800, + "reward_id": 331041 + }, + { + "level": 42, + "item_cost": 800, + "reward_id": 331042 + }, + { + "level": 43, + "item_cost": 800, + "reward_id": 331043 + }, + { + "level": 44, + "item_cost": 800, + "reward_id": 331044 + }, + { + "level": 45, + "item_cost": 800, + "reward_id": 331045 + }, + { + "level": 46, + "item_cost": 800, + "reward_id": 331046 + }, + { + "level": 47, + "item_cost": 800, + "reward_id": 331047 + }, + { + "level": 48, + "item_cost": 800, + "reward_id": 331048 + }, + { + "level": 49, + "item_cost": 800, + "reward_id": 331049 + }, + { + "level": 50, + "item_cost": 800, + "reward_id": 331050 + }, + { + "level": 51, + "item_cost": 800, + "reward_id": 331051 + }, + { + "level": 52, + "item_cost": 800, + "reward_id": 331052 + }, + { + "level": 53, + "item_cost": 800, + "reward_id": 331053 + }, + { + "level": 54, + "item_cost": 800, + "reward_id": 331054 + }, + { + "level": 55, + "item_cost": 800, + "reward_id": 331055 + }, + { + "level": 56, + "item_cost": 800, + "reward_id": 331056 + }, + { + "level": 57, + "item_cost": 800, + "reward_id": 331057 + }, + { + "level": 58, + "item_cost": 800, + "reward_id": 331058 + }, + { + "level": 59, + "item_cost": 800, + "reward_id": 331059 + }, + { + "level": 60, + "item_cost": 800, + "reward_id": 331060 + }, + { + "level": 61, + "item_cost": 800, + "reward_id": 331061 + }, + { + "level": 62, + "item_cost": 800, + "reward_id": 331062 + }, + { + "level": 63, + "item_cost": 800, + "reward_id": 331063 + }, + { + "level": 64, + "item_cost": 800, + "reward_id": 331064 + }, + { + "level": 65, + "item_cost": 800, + "reward_id": 331065 + }, + { + "level": 66, + "item_cost": 800, + "reward_id": 331066 + }, + { + "level": 67, + "item_cost": 800, + "reward_id": 331067 + }, + { + "level": 68, + "item_cost": 800, + "reward_id": 331068 + }, + { + "level": 69, + "item_cost": 800, + "reward_id": 331069 + }, + { + "level": 70, + "item_cost": 800, + "reward_id": 331070 + }, + { + "level": 71, + "item_cost": 800, + "reward_id": 331071 + }, + { + "level": 72, + "item_cost": 800, + "reward_id": 331072 + }, + { + "level": 73, + "item_cost": 800, + "reward_id": 331073 + }, + { + "level": 74, + "item_cost": 800, + "reward_id": 331074 + }, + { + "level": 75, + "item_cost": 800, + "reward_id": 331075 + }, + { + "level": 76, + "item_cost": 800, + "reward_id": 331076 + }, + { + "level": 77, + "item_cost": 800, + "reward_id": 331077 + }, + { + "level": 78, + "item_cost": 800, + "reward_id": 331078 + }, + { + "level": 79, + "item_cost": 800, + "reward_id": 331079 + }, + { + "level": 80, + "item_cost": 800, + "reward_id": 331080 + } + ] +} diff --git a/resources/CurrencyWarsTrailblaze.json b/resources/CurrencyWarsTrailblaze.json new file mode 100644 index 0000000..d0fb65d --- /dev/null +++ b/resources/CurrencyWarsTrailblaze.json @@ -0,0 +1,499 @@ +{ + "goods": [ + { + "id": 999, + "kind": "refresh", + "cost": 1, + "father": 0, + "params": [] + }, + { + "id": 101, + "kind": "maze_buff", + "cost": 0, + "father": 0, + "params": [ + 110000021 + ] + }, + { + "id": 201, + "kind": "maze_buff", + "cost": 10, + "father": 101, + "params": [ + 110000022 + ] + }, + { + "id": 202, + "kind": "power", + "cost": 1, + "father": 0, + "params": [ + 30 + ] + }, + { + "id": 203, + "kind": "maze_buff", + "cost": 40, + "father": 0, + "params": [ + 110000023 + ] + }, + { + "id": 204, + "kind": "maze_buff", + "cost": 40, + "father": 0, + "params": [ + 110000024 + ] + }, + { + "id": 205, + "kind": "maze_buff", + "cost": 40, + "father": 0, + "params": [ + 110000027 + ] + }, + { + "id": 301, + "kind": "maze_buff", + "cost": 10, + "father": 201, + "params": [ + 110000025 + ] + }, + { + "id": 302, + "kind": "power", + "cost": 1, + "father": 0, + "params": [ + 60 + ] + }, + { + "id": 303, + "kind": "item", + "cost": 100, + "father": 0, + "params": [ + 353201 + ] + }, + { + "id": 304, + "kind": "item", + "cost": 100, + "father": 0, + "params": [ + 353202 + ] + }, + { + "id": 305, + "kind": "item", + "cost": 100, + "father": 0, + "params": [ + 353203 + ] + }, + { + "id": 306, + "kind": "item", + "cost": 100, + "father": 0, + "params": [ + 353204 + ] + }, + { + "id": 307, + "kind": "item", + "cost": 100, + "father": 0, + "params": [ + 353205 + ] + }, + { + "id": 308, + "kind": "item", + "cost": 100, + "father": 0, + "params": [ + 353206 + ] + }, + { + "id": 400, + "kind": "maze_buff", + "cost": 0, + "father": 301, + "params": [ + 110000026 + ] + }, + { + "id": 401, + "kind": "item", + "cost": 30, + "father": 0, + "params": [ + 350113 + ] + }, + { + "id": 402, + "kind": "stat", + "cost": 15, + "father": 0, + "params": [ + 1027, + 50 + ] + }, + { + "id": 403, + "kind": "stat", + "cost": 15, + "father": 0, + "params": [ + 1020, + 20 + ] + }, + { + "id": 404, + "kind": "stat", + "cost": 30, + "father": 0, + "params": [ + 1027, + 100 + ] + }, + { + "id": 405, + "kind": "stat", + "cost": 30, + "father": 0, + "params": [ + 1020, + 40 + ] + }, + { + "id": 406, + "kind": "stat", + "cost": 50, + "father": 0, + "params": [ + 1027, + 150 + ] + }, + { + "id": 407, + "kind": "stat", + "cost": 50, + "father": 0, + "params": [ + 1020, + 60 + ] + }, + { + "id": 408, + "kind": "stat", + "cost": 100, + "father": 0, + "params": [ + 1027, + 750 + ] + }, + { + "id": 414, + "kind": "stat", + "cost": 15, + "father": 0, + "params": [ + 1030, + 50 + ] + }, + { + "id": 415, + "kind": "stat", + "cost": 30, + "father": 0, + "params": [ + 1030, + 100 + ] + }, + { + "id": 416, + "kind": "stat", + "cost": 50, + "father": 0, + "params": [ + 1030, + 150 + ] + }, + { + "id": 417, + "kind": "stat", + "cost": 100, + "father": 0, + "params": [ + 1030, + 750 + ] + }, + { + "id": 409, + "kind": "stat", + "cost": 100, + "father": 0, + "params": [ + 1020, + 300 + ] + }, + { + "id": 410, + "kind": "stat", + "cost": 50, + "father": 0, + "params": [ + 1024, + 300 + ] + }, + { + "id": 418, + "kind": "stat", + "cost": 15, + "father": 0, + "params": [ + 1025, + 20 + ] + }, + { + "id": 419, + "kind": "stat", + "cost": 30, + "father": 0, + "params": [ + 1025, + 40 + ] + }, + { + "id": 420, + "kind": "stat", + "cost": 50, + "father": 0, + "params": [ + 1025, + 60 + ] + }, + { + "id": 421, + "kind": "stat", + "cost": 50, + "father": 0, + "params": [ + 1025, + 300 + ] + }, + { + "id": 422, + "kind": "stat", + "cost": 15, + "father": 0, + "params": [ + 1024, + 20 + ] + }, + { + "id": 423, + "kind": "stat", + "cost": 30, + "father": 0, + "params": [ + 1024, + 40 + ] + }, + { + "id": 424, + "kind": "stat", + "cost": 50, + "father": 0, + "params": [ + 1024, + 60 + ] + }, + { + "id": 411, + "kind": "stat", + "cost": 100, + "father": 0, + "params": [ + 1014, + 300 + ] + }, + { + "id": 412, + "kind": "stat", + "cost": 100, + "father": 0, + "params": [ + 1018, + 300 + ] + }, + { + "id": 413, + "kind": "item", + "cost": 60, + "father": 0, + "params": [ + 350114 + ] + }, + { + "id": 501, + "kind": "role", + "cost": 15, + "father": 0, + "params": [ + 122402, + 1 + ] + }, + { + "id": 502, + "kind": "role", + "cost": 15, + "father": 0, + "params": [ + 100402, + 1 + ] + }, + { + "id": 503, + "kind": "role", + "cost": 35, + "father": 0, + "params": [ + 131302, + 1 + ] + }, + { + "id": 504, + "kind": "role", + "cost": 50, + "father": 0, + "params": [ + 800902, + 1 + ] + }, + { + "id": 505, + "kind": "role", + "cost": 75, + "father": 0, + "params": [ + 151002, + 1 + ] + }, + { + "id": 506, + "kind": "role", + "cost": 100, + "father": 0, + "params": [ + 121302, + 1 + ] + }, + { + "id": 507, + "kind": "role", + "cost": 100, + "father": 0, + "params": [ + 604202, + 1 + ] + } + ], + "provenance": { + "asset": "BakedConfig/ExcelOutput/GridFightTrailblazeGoods.bytes", + "hash": 9408645648513926690, + "offset": 5200532, + "length": 2947, + "blob": "StarRail_Data/Persistent/DesignData/Windows/f746eff2afbc6005b3147e1e1895762f.bytes", + "blob_sha256": "58b8fd1a6af748b597994f618bc45b3bec4fd80ef354195992af0377bc52c2da", + "asset_sha256": "e1df66ec8a78d87af2da6bbffacc6003428b17c723fc219c6f6182f6eab206c2" + }, + "layer_params": { + "2": [ + 1, + 20, + 12, + 999 + ], + "4": [ + 2, + 45, + 12, + 999 + ], + "6": [ + 3, + 75, + 12, + 999 + ], + "8": [ + 4, + 125, + 12, + 999 + ], + "10": [ + 5, + 225, + 12, + 999 + ] + }, + "pom_threshold": 300, + "initial_goods_id": 101, + "refresh_goods_id": 999, + "pom_goods_id": 507 +} diff --git a/resources/CurrencyWarsTraits.json b/resources/CurrencyWarsTraits.json new file mode 100644 index 0000000..e1cd56e --- /dev/null +++ b/resources/CurrencyWarsTraits.json @@ -0,0 +1,981 @@ +{ + "format_version": 1, + "target_client_version": "4.6.53", + "target_assembly_sha256": "c5e02e6e984f7e5577c78b995a8efeee2d06ee22e67d484ad5e859c8bf21e144", + "provenance": { + "repository": "https://gaytea.cod-algieba.ts.net/xaodev/Currency-War-SR", + "commit": "7d342af29fc9e4fec49bf5202c0c8207c1ba9f71", + "license": "AGPL-3.0-only", + "inputs_sha256": { + "traits.json": "c330092cb3a4f04c884b0df182608631e1fc3b948339210a8908c9c55475f277", + "trait_effects.json": "1b0bd8d577ba093f31858f46fab56f5ff6f93e43ee5838da4451c3860d79841e" + } + }, + "traits": [ + { + "id": 1001, + "name": "Express Cohort", + "thresholds": [ + 2, + 4, + 6 + ], + "activation_type": 1, + "trait_type": 0, + "season_id": 1 + }, + { + "id": 1002, + "name": "Stellaron Hunters", + "thresholds": [ + 2, + 3, + 4 + ], + "activation_type": 1, + "trait_type": 0, + "season_id": 1 + }, + { + "id": 1003, + "name": "Cosmic Scholar", + "thresholds": [ + 2, + 4, + 6 + ], + "activation_type": 1, + "trait_type": 0, + "season_id": 1 + }, + { + "id": 1004, + "name": "IPC", + "thresholds": [ + 2, + 3 + ], + "activation_type": 1, + "trait_type": 0, + "season_id": 1 + }, + { + "id": 1005, + "name": "Belobog", + "thresholds": [ + 2, + 4 + ], + "activation_type": 1, + "trait_type": 0, + "season_id": 1 + }, + { + "id": 1006, + "name": "Galactic Voyager", + "thresholds": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7 + ], + "activation_type": 1, + "trait_type": 0, + "season_id": 1 + }, + { + "id": 1007, + "name": "Xianzhou", + "thresholds": [ + 3, + 5, + 7, + 10 + ], + "activation_type": 1, + "trait_type": 0, + "season_id": 1 + }, + { + "id": 1008, + "name": "Wolf Hunt", + "thresholds": [ + 3, + 5, + 6, + 8 + ], + "activation_type": 1, + "trait_type": 0, + "season_id": 1 + }, + { + "id": 1009, + "name": "Galaxy Ranger", + "thresholds": [ + 1, + 2, + 3, + 4 + ], + "activation_type": 1, + "trait_type": 0, + "season_id": 1 + }, + { + "id": 1010, + "name": "The Planet of Festivities", + "thresholds": [ + 2, + 3, + 4, + 5, + 6 + ], + "activation_type": 1, + "trait_type": 0, + "season_id": 1 + }, + { + "id": 1011, + "name": "Day Demigod", + "thresholds": [ + 3, + 4, + 6, + 9 + ], + "activation_type": 1, + "trait_type": 0, + "season_id": 1 + }, + { + "id": 1012, + "name": "Night Demigod", + "thresholds": [ + 2, + 4, + 7, + 9 + ], + "activation_type": 1, + "trait_type": 0, + "season_id": 1 + }, + { + "id": 1013, + "name": "Holy Grail of Destiny", + "thresholds": [ + 2, + 3, + 4, + 5 + ], + "activation_type": 1, + "trait_type": 0, + "season_id": 1 + }, + { + "id": 1014, + "name": "Master and Disciple", + "thresholds": [ + 2, + 3 + ], + "activation_type": 1, + "trait_type": 0, + "season_id": 1 + }, + { + "id": 2001, + "name": "Follow-Up ATK", + "thresholds": [ + 3, + 5, + 7, + 9 + ], + "activation_type": 1, + "trait_type": 1, + "season_id": 1 + }, + { + "id": 2002, + "name": "Break", + "thresholds": [ + 2, + 4, + 6, + 9 + ], + "activation_type": 1, + "trait_type": 1, + "season_id": 1 + }, + { + "id": 2003, + "name": "AoE ATK", + "thresholds": [ + 3, + 5, + 7, + 9 + ], + "activation_type": 1, + "trait_type": 1, + "season_id": 1 + }, + { + "id": 2004, + "name": "Energy", + "thresholds": [ + 3, + 5, + 7, + 10 + ], + "activation_type": 1, + "trait_type": 1, + "season_id": 1 + }, + { + "id": 2005, + "name": "Heal", + "thresholds": [ + 2, + 4, + 6 + ], + "activation_type": 1, + "trait_type": 1, + "season_id": 1 + }, + { + "id": 2006, + "name": "Bloodflame", + "thresholds": [ + 2, + 4, + 6, + 8 + ], + "activation_type": 1, + "trait_type": 1, + "season_id": 1 + }, + { + "id": 2007, + "name": "Debuff", + "thresholds": [ + 2, + 4, + 6, + 8 + ], + "activation_type": 1, + "trait_type": 1, + "season_id": 1 + }, + { + "id": 2008, + "name": "DoT", + "thresholds": [ + 2, + 4, + 6 + ], + "activation_type": 1, + "trait_type": 1, + "season_id": 1 + }, + { + "id": 2009, + "name": "Quantum Resonance", + "thresholds": [ + 2, + 3, + 4, + 5 + ], + "activation_type": 1, + "trait_type": 1, + "season_id": 1 + }, + { + "id": 2010, + "name": "Shield", + "thresholds": [ + 2, + 4 + ], + "activation_type": 1, + "trait_type": 1, + "season_id": 1 + }, + { + "id": 2011, + "name": "Skill Points", + "thresholds": [ + 2, + 4, + 6, + 8 + ], + "activation_type": 1, + "trait_type": 1, + "season_id": 1 + }, + { + "id": 2012, + "name": "Elation", + "thresholds": [ + 3, + 4, + 5, + 7 + ], + "activation_type": 1, + "trait_type": 1, + "season_id": 1 + }, + { + "id": 3001, + "name": "Supreme Guardian", + "thresholds": [ + 1 + ], + "activation_type": 1, + "trait_type": 2, + "season_id": 1 + }, + { + "id": 3002, + "name": "Destiny Diviner", + "thresholds": [ + 1 + ], + "activation_type": 1, + "trait_type": 2, + "season_id": 1 + }, + { + "id": 3003, + "name": "Magician", + "thresholds": [ + 1 + ], + "activation_type": 1, + "trait_type": 2, + "season_id": 1 + }, + { + "id": 3004, + "name": "Beloved One", + "thresholds": [ + 1 + ], + "activation_type": 1, + "trait_type": 2, + "season_id": 1 + }, + { + "id": 3005, + "name": "Deliverer", + "thresholds": [ + 1 + ], + "activation_type": 1, + "trait_type": 2, + "season_id": 1 + }, + { + "id": 3006, + "name": "Top Player", + "thresholds": [ + 1 + ], + "activation_type": 1, + "trait_type": 2, + "season_id": 1 + }, + { + "id": 3007, + "name": "Navigator", + "thresholds": [ + 1 + ], + "activation_type": 1, + "trait_type": 2, + "season_id": 1 + }, + { + "id": 100102, + "name": "Xianzhou Fleet", + "thresholds": [ + 2, + 4, + 6, + 9 + ], + "activation_type": 1, + "trait_type": 0, + "season_id": 2 + }, + { + "id": 100202, + "name": "Beast Garden", + "thresholds": [ + 3, + 5, + 7, + 10 + ], + "activation_type": 1, + "trait_type": 0, + "season_id": 2 + }, + { + "id": 100302, + "name": "Phantasmoon Games", + "thresholds": [ + 3, + 5, + 7, + 10 + ], + "activation_type": 1, + "trait_type": 0, + "season_id": 2 + }, + { + "id": 100402, + "name": "Galactic Voyager", + "thresholds": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8 + ], + "activation_type": 1, + "trait_type": 0, + "season_id": 2 + }, + { + "id": 100502, + "name": "Trailblaze Silver Rail", + "thresholds": [ + 2, + 4, + 6, + 8, + 10 + ], + "activation_type": 1, + "trait_type": 0, + "season_id": 2 + }, + { + "id": 100602, + "name": "Astropolis Merchant Guild", + "thresholds": [ + 2 + ], + "activation_type": 1, + "trait_type": 0, + "season_id": 2 + }, + { + "id": 100702, + "name": "High-Cloud Quintet", + "thresholds": [ + 2, + 3, + 4, + 5 + ], + "activation_type": 1, + "trait_type": 0, + "season_id": 2 + }, + { + "id": 100802, + "name": "Stellaron Hunters", + "thresholds": [ + 2, + 3, + 5, + 7 + ], + "activation_type": 1, + "trait_type": 0, + "season_id": 2 + }, + { + "id": 100902, + "name": "Little Giant", + "thresholds": [ + 2, + 3, + 4, + 5, + 6 + ], + "activation_type": 1, + "trait_type": 0, + "season_id": 2 + }, + { + "id": 101002, + "name": "Intertwined Dreamscapes", + "thresholds": [ + 2, + 3, + 4, + 5 + ], + "activation_type": 1, + "trait_type": 0, + "season_id": 2 + }, + { + "id": 101102, + "name": "Girls Band", + "thresholds": [ + 1, + 4 + ], + "activation_type": 3, + "trait_type": 0, + "season_id": 2 + }, + { + "id": 101202, + "name": "The Grand Restaurant", + "thresholds": [ + 2, + 3, + 4 + ], + "activation_type": 1, + "trait_type": 0, + "season_id": 2 + }, + { + "id": 200102, + "name": "Follow-Up ATK", + "thresholds": [ + 3, + 5, + 7, + 10 + ], + "activation_type": 1, + "trait_type": 1, + "season_id": 2 + }, + { + "id": 200202, + "name": "DoT", + "thresholds": [ + 3, + 5, + 7, + 9 + ], + "activation_type": 1, + "trait_type": 1, + "season_id": 2 + }, + { + "id": 200302, + "name": "AoE ATK", + "thresholds": [ + 2, + 4, + 6, + 8 + ], + "activation_type": 1, + "trait_type": 1, + "season_id": 2 + }, + { + "id": 200402, + "name": "Combo", + "thresholds": [ + 2, + 4, + 6, + 8 + ], + "activation_type": 1, + "trait_type": 1, + "season_id": 2 + }, + { + "id": 200502, + "name": "Break", + "thresholds": [ + 2, + 4, + 6, + 8 + ], + "activation_type": 1, + "trait_type": 1, + "season_id": 2 + }, + { + "id": 200602, + "name": "Summon", + "thresholds": [ + 2, + 4, + 6, + 8 + ], + "activation_type": 1, + "trait_type": 1, + "season_id": 2 + }, + { + "id": 200702, + "name": "Elation", + "thresholds": [ + 3, + 5, + 6, + 8 + ], + "activation_type": 1, + "trait_type": 1, + "season_id": 2 + }, + { + "id": 200802, + "name": "Counter", + "thresholds": [ + 2, + 4, + 6 + ], + "activation_type": 1, + "trait_type": 1, + "season_id": 2 + }, + { + "id": 200902, + "name": "Energy", + "thresholds": [ + 2, + 4, + 6 + ], + "activation_type": 1, + "trait_type": 1, + "season_id": 2 + }, + { + "id": 201002, + "name": "Heal", + "thresholds": [ + 2, + 4, + 6 + ], + "activation_type": 1, + "trait_type": 1, + "season_id": 2 + }, + { + "id": 201102, + "name": "Protection", + "thresholds": [ + 2, + 3 + ], + "activation_type": 1, + "trait_type": 1, + "season_id": 2 + }, + { + "id": 300102, + "name": "Twin Dragons' Call", + "thresholds": [ + 1, + 2 + ], + "activation_type": 1, + "trait_type": 2, + "season_id": 2 + }, + { + "id": 300202, + "name": "Flame Reaver", + "thresholds": [ + 1 + ], + "activation_type": 1, + "trait_type": 2, + "season_id": 2 + }, + { + "id": 300302, + "name": "Galaxy Ranger", + "thresholds": [ + 1, + 2 + ], + "activation_type": 1, + "trait_type": 2, + "season_id": 2 + }, + { + "id": 300402, + "name": "Black Market Manager", + "thresholds": [ + 1 + ], + "activation_type": 1, + "trait_type": 2, + "season_id": 2 + }, + { + "id": 300502, + "name": "Ingenious Artificer", + "thresholds": [ + 1 + ], + "activation_type": 1, + "trait_type": 2, + "season_id": 2 + }, + { + "id": 300602, + "name": "Master of Phantasia", + "thresholds": [ + 1 + ], + "activation_type": 1, + "trait_type": 2, + "season_id": 2 + }, + { + "id": 400102, + "name": "Follow-Up ATK Partner", + "thresholds": [ + 1, + 2 + ], + "activation_type": 1, + "trait_type": 3, + "season_id": 2 + }, + { + "id": 400202, + "name": "Four-Dimensional Frenzy", + "thresholds": [ + 1, + 2 + ], + "activation_type": 1, + "trait_type": 3, + "season_id": 2 + }, + { + "id": 400302, + "name": "Star Hunt Oath", + "thresholds": [ + 1, + 2 + ], + "activation_type": 1, + "trait_type": 3, + "season_id": 2 + }, + { + "id": 400402, + "name": "Deliverer", + "thresholds": [ + 1 + ], + "activation_type": 1, + "trait_type": 3, + "season_id": 2 + }, + { + "id": 400502, + "name": "Apprenticeship", + "thresholds": [ + 1 + ], + "activation_type": 1, + "trait_type": 3, + "season_id": 2 + } + ], + "effects": { + "10021": 7, + "10031": 2, + "10041": 8, + "10061": 8, + "10081": 2, + "10091": 11, + "10101": 5, + "10111": 1, + "10121": 2, + "20041": 6, + "25031": 8, + "25051": 8, + "25061": 8, + "30021": 9, + "30041": 8, + "30051": 10, + "888888": 1, + "20121": 12, + "30061": 9, + "30062": 9, + "30063": 9, + "30071": 14, + "10131": 15, + "101311": 13, + "1008021": 16, + "1012021": 16, + "1012022": 8, + "1010021": 17, + "3002021": 18, + "3003021": 32, + "1011021": 8, + "1006021": 20, + "1001021": 21, + "1001022": 8, + "2004021": 22, + "1004021": 8, + "1005021": 23, + "10030211": 2, + "10030221": 8, + "10030231": 8, + "10030232": 25, + "1003021": 24, + "2007021": 26, + "1002021": 27, + "2008021": 7, + "3004021": 29, + "3005021": 31, + "3005022": 8, + "3001021": 30, + "3001022": 28, + "4004021": 10, + "3006021": 33, + "4002021": 8, + "4005021": 34 + }, + "constants": { + "GridFight_PhanionEffectSpecialRoleIDList": [ + 140202, + 140302, + 140402, + 140502, + 140602, + 140702, + 140802, + 140902, + 141002, + 141202, + 141302, + 141402, + 141502 + ], + "GridFight_EquipType_Attack": [ + 47030101, + 47030102, + 47030105, + 35030107, + 47030108, + 47030202, + 35030204, + 47030205, + 47030207, + 47030208, + 47030303, + 35030304, + 47030307, + 35030405, + 47030406, + 47030408, + 35030707, + 35030808, + 47040101, + 47040102, + 47040105, + 35040107, + 47040108, + 47040202, + 35040204, + 47040205, + 47040207, + 47040208, + 47040303, + 35040304, + 47040307, + 35040405, + 47040406, + 47040408, + 35040707, + 35040808 + ], + "GridFight_EquipType_Support": [ + 47030103, + 35030104, + 47030106, + 35030203, + 35030206, + 35030305, + 47030306, + 35030308, + 35030404, + 35030407, + 35030505, + 35030506, + 35030507, + 47030508, + 35030606, + 35030607, + 35030608, + 35030708, + 47040103, + 35040104, + 47040106, + 35040203, + 35040206, + 35040305, + 47040306, + 35040308, + 35040404, + 35040407, + 35040505, + 35040506, + 35040507, + 47040508, + 35040606, + 35040607, + 35040608, + 35040708 + ], + "GridFight_TrailblazeShop_ItemNum": 4, + "GridFight_TrailblazeShop_InitialGoodsID": 101, + "GridFight_Origin_MoonGame_SubtraitList": [ + 1003021, + 1003022, + 1003023 + ], + "GridFight_AhaMaxStar_JsonPath": "Config/Level/GridFight/Role/GridFightRole151132_MaxStar.json" + } +} diff --git a/resources/CurrencyWarsXaodev.json b/resources/CurrencyWarsXaodev.json new file mode 100644 index 0000000..a40c436 --- /dev/null +++ b/resources/CurrencyWarsXaodev.json @@ -0,0 +1,4340 @@ +{ + "format_version": 1, + "target_client_version": "4.6.53", + "target_assembly_sha256": "c5e02e6e984f7e5577c78b995a8efeee2d06ee22e67d484ad5e859c8bf21e144", + "provenance": { + "repository": "https://gaytea.cod-algieba.ts.net/xaodev/Currency-War-SR", + "commit": "7d342af29fc9e4fec49bf5202c0c8207c1ba9f71", + "license": "AGPL-3.0-only", + "inputs_sha256": { + "resources/grid_fight/elite_rewards.json": "60596d11cc3ae7e4ac579c1bcf6f28bfcebd9dee5bba1cb3a40bfcc2ab987f45", + "resources/grid_fight/rounds.raw.json": "f473e2a398314a17a75be4d97024fd95b3f32cdb5cc5dad2faded01a719beefa", + "resources/grid_fight/score.json": "67b86fc156018ddd398b6ab927923b00c31ddb8929f8d9f18f508a9601ecb643", + "resources/grid_fight/offering_levels.json": "e663e3adbd3f5a5148a836e0774c102a84150a53bb5835f0c67e2990c08b4e69", + "resources/grid_fight/traits.json": "c330092cb3a4f04c884b0df182608631e1fc3b948339210a8908c9c55475f277", + "resources/grid_fight/trait_effects.json": "1b0bd8d577ba093f31858f46fab56f5ff6f93e43ee5838da4451c3860d79841e", + "resources/grid_fight/augments.json": "649d3c4c7c75bf75935b3ed45ad5fafdc23c2db9ad9428dc1b56f01a07610fc6", + "resources/grid_fight/orb_rewards.json": "4c0116bd747886c2d23637a27b8a0f552d7a07f6afaa25b889655ef40e9f9e57", + "gameserver/src/services/grid_fight.zig": "aa82263a157620dd71036c3f57a03c93b98b3aefb98c07ef8765fa7587c87ff4", + "gameserver/src/services/grid_fight_rewards.zig": "99aad13d95d0740bc19cdd329b44161a9e6f00054b9bd600e3418e268de5e06a", + "gameserver/src/services/grid_fight_strategy.zig": "0d95044b89c79e01a9f126fc7f5348ecdfb2935586ee9b1578492b6cf14bca57", + "gameserver/src/services/grid_fight_traits.zig": "451567e47eb165c0f3290bcacca4ace3a2ccf1fa9cc07cfb36f3a57dc870c9e3", + "gameserver/src/services/grid_fight_bonds.zig": "9e96a7822a4825db2e198c09505774cbf3996dfd78c204f2376d0d07563ea3b6", + "gameserver/src/services/grid_fight_talent.zig": "9cf938ef5af70811a1c49a1712abefba1c4cfebe4154a26b31890d95be5c9365" + }, + "native_source": { + "dump_sha256": "797387b16956134facdfc3741805d3f483da2578316849cb770de91b71c88914", + "game_assembly_sha256": "c5e02e6e984f7e5577c78b995a8efeee2d06ee22e67d484ad5e859c8bf21e144", + "tables": [ + "GridFightBinaryNodeRuleConfigRow", + "GridFightBinaryDiffAddRuleConfigRow", + "GridFightSpecialGoodsConfigRow", + "GridFightProjectionConfigRow", + "GridFightEnemyDifficultyLvConfigRow" + ] + }, + "adapted_functions": [ + "eliteOffers", + "powerfulQuality", + "encounter", + "branchDropPreview", + "sendEliteSelected", + "roundExp" + ] + }, + "node_rules": [ + { + "id": 1, + "family": 0, + "quality": 1, + "perform_level": 1 + }, + { + "id": 2, + "family": 0, + "quality": 2, + "perform_level": 2 + }, + { + "id": 3, + "family": 0, + "quality": 3, + "perform_level": 3 + }, + { + "id": 4, + "family": 0, + "quality": 4, + "perform_level": 4 + }, + { + "id": 10301, + "family": 103, + "quality": 1, + "perform_level": 1 + }, + { + "id": 10302, + "family": 103, + "quality": 2, + "perform_level": 2 + }, + { + "id": 10303, + "family": 103, + "quality": 3, + "perform_level": 3 + }, + { + "id": 10304, + "family": 103, + "quality": 4, + "perform_level": 4 + }, + { + "id": 11301, + "family": 113, + "quality": 1, + "perform_level": 1 + }, + { + "id": 11302, + "family": 113, + "quality": 2, + "perform_level": 2 + }, + { + "id": 11303, + "family": 113, + "quality": 3, + "perform_level": 3 + }, + { + "id": 11304, + "family": 113, + "quality": 4, + "perform_level": 4 + }, + { + "id": 12301, + "family": 123, + "quality": 1, + "perform_level": 1 + }, + { + "id": 12302, + "family": 123, + "quality": 2, + "perform_level": 2 + }, + { + "id": 12303, + "family": 123, + "quality": 3, + "perform_level": 3 + }, + { + "id": 12304, + "family": 123, + "quality": 4, + "perform_level": 4 + }, + { + "id": 20301, + "family": 203, + "quality": 1, + "perform_level": 1 + }, + { + "id": 20302, + "family": 203, + "quality": 2, + "perform_level": 2 + }, + { + "id": 20303, + "family": 203, + "quality": 3, + "perform_level": 3 + }, + { + "id": 20304, + "family": 203, + "quality": 4, + "perform_level": 4 + }, + { + "id": 21301, + "family": 213, + "quality": 1, + "perform_level": 1 + }, + { + "id": 21302, + "family": 213, + "quality": 2, + "perform_level": 2 + }, + { + "id": 21303, + "family": 213, + "quality": 3, + "perform_level": 3 + }, + { + "id": 21304, + "family": 213, + "quality": 4, + "perform_level": 4 + }, + { + "id": 22301, + "family": 223, + "quality": 1, + "perform_level": 1 + }, + { + "id": 22302, + "family": 223, + "quality": 2, + "perform_level": 2 + }, + { + "id": 22303, + "family": 223, + "quality": 3, + "perform_level": 3 + }, + { + "id": 22304, + "family": 223, + "quality": 4, + "perform_level": 4 + }, + { + "id": 30301, + "family": 303, + "quality": 1, + "perform_level": 1 + }, + { + "id": 30302, + "family": 303, + "quality": 2, + "perform_level": 2 + }, + { + "id": 30303, + "family": 303, + "quality": 3, + "perform_level": 3 + }, + { + "id": 30304, + "family": 303, + "quality": 4, + "perform_level": 4 + }, + { + "id": 31301, + "family": 313, + "quality": 1, + "perform_level": 1 + }, + { + "id": 31302, + "family": 313, + "quality": 2, + "perform_level": 2 + }, + { + "id": 31303, + "family": 313, + "quality": 3, + "perform_level": 3 + }, + { + "id": 31304, + "family": 313, + "quality": 4, + "perform_level": 4 + }, + { + "id": 32301, + "family": 323, + "quality": 1, + "perform_level": 1 + }, + { + "id": 32302, + "family": 323, + "quality": 2, + "perform_level": 2 + }, + { + "id": 32303, + "family": 323, + "quality": 3, + "perform_level": 3 + }, + { + "id": 32304, + "family": 323, + "quality": 4, + "perform_level": 4 + }, + { + "id": 33301, + "family": 333, + "quality": 1, + "perform_level": 1 + }, + { + "id": 33302, + "family": 333, + "quality": 2, + "perform_level": 2 + }, + { + "id": 33303, + "family": 333, + "quality": 3, + "perform_level": 3 + }, + { + "id": 33304, + "family": 333, + "quality": 4, + "perform_level": 4 + } + ], + "diff_add": [ + { + "rule": 1, + "quality": 1, + "add": 0 + }, + { + "rule": 1, + "quality": 2, + "add": 5 + }, + { + "rule": 1, + "quality": 3, + "add": 10 + }, + { + "rule": 1, + "quality": 4, + "add": 20 + }, + { + "rule": 2, + "quality": 1, + "add": 0 + }, + { + "rule": 2, + "quality": 2, + "add": 10 + }, + { + "rule": 2, + "quality": 3, + "add": 20 + }, + { + "rule": 2, + "quality": 4, + "add": 30 + } + ], + "difficulty_levels": [ + { + "chapter": 1, + "level": 0 + }, + { + "chapter": 1, + "level": 1 + }, + { + "chapter": 1, + "level": 2 + }, + { + "chapter": 1, + "level": 3 + }, + { + "chapter": 1, + "level": 4 + }, + { + "chapter": 1, + "level": 5 + }, + { + "chapter": 1, + "level": 6 + }, + { + "chapter": 1, + "level": 7 + }, + { + "chapter": 1, + "level": 8 + }, + { + "chapter": 1, + "level": 9 + }, + { + "chapter": 1, + "level": 10 + }, + { + "chapter": 1, + "level": 11 + }, + { + "chapter": 1, + "level": 12 + }, + { + "chapter": 1, + "level": 13 + }, + { + "chapter": 1, + "level": 14 + }, + { + "chapter": 1, + "level": 15 + }, + { + "chapter": 1, + "level": 16 + }, + { + "chapter": 1, + "level": 17 + }, + { + "chapter": 1, + "level": 18 + }, + { + "chapter": 1, + "level": 19 + }, + { + "chapter": 1, + "level": 20 + }, + { + "chapter": 1, + "level": 21 + }, + { + "chapter": 1, + "level": 22 + }, + { + "chapter": 1, + "level": 23 + }, + { + "chapter": 1, + "level": 24 + }, + { + "chapter": 1, + "level": 25 + }, + { + "chapter": 1, + "level": 26 + }, + { + "chapter": 1, + "level": 27 + }, + { + "chapter": 1, + "level": 28 + }, + { + "chapter": 1, + "level": 29 + }, + { + "chapter": 1, + "level": 30 + }, + { + "chapter": 1, + "level": 31 + }, + { + "chapter": 1, + "level": 32 + }, + { + "chapter": 1, + "level": 33 + }, + { + "chapter": 1, + "level": 34 + }, + { + "chapter": 1, + "level": 35 + }, + { + "chapter": 1, + "level": 36 + }, + { + "chapter": 1, + "level": 37 + }, + { + "chapter": 1, + "level": 38 + }, + { + "chapter": 1, + "level": 39 + }, + { + "chapter": 1, + "level": 40 + }, + { + "chapter": 1, + "level": 41 + }, + { + "chapter": 1, + "level": 42 + }, + { + "chapter": 1, + "level": 43 + }, + { + "chapter": 1, + "level": 44 + }, + { + "chapter": 1, + "level": 45 + }, + { + "chapter": 1, + "level": 46 + }, + { + "chapter": 1, + "level": 47 + }, + { + "chapter": 1, + "level": 48 + }, + { + "chapter": 1, + "level": 49 + }, + { + "chapter": 1, + "level": 50 + }, + { + "chapter": 1, + "level": 51 + }, + { + "chapter": 1, + "level": 52 + }, + { + "chapter": 1, + "level": 53 + }, + { + "chapter": 1, + "level": 54 + }, + { + "chapter": 1, + "level": 55 + }, + { + "chapter": 1, + "level": 56 + }, + { + "chapter": 1, + "level": 57 + }, + { + "chapter": 1, + "level": 58 + }, + { + "chapter": 1, + "level": 59 + }, + { + "chapter": 1, + "level": 60 + }, + { + "chapter": 1, + "level": 61 + }, + { + "chapter": 1, + "level": 62 + }, + { + "chapter": 1, + "level": 63 + }, + { + "chapter": 1, + "level": 64 + }, + { + "chapter": 1, + "level": 65 + }, + { + "chapter": 1, + "level": 66 + }, + { + "chapter": 1, + "level": 67 + }, + { + "chapter": 1, + "level": 68 + }, + { + "chapter": 1, + "level": 69 + }, + { + "chapter": 1, + "level": 70 + }, + { + "chapter": 1, + "level": 71 + }, + { + "chapter": 1, + "level": 72 + }, + { + "chapter": 1, + "level": 73 + }, + { + "chapter": 1, + "level": 74 + }, + { + "chapter": 1, + "level": 75 + }, + { + "chapter": 1, + "level": 76 + }, + { + "chapter": 1, + "level": 77 + }, + { + "chapter": 1, + "level": 78 + }, + { + "chapter": 1, + "level": 79 + }, + { + "chapter": 1, + "level": 80 + }, + { + "chapter": 1, + "level": 81 + }, + { + "chapter": 1, + "level": 82 + }, + { + "chapter": 1, + "level": 83 + }, + { + "chapter": 1, + "level": 84 + }, + { + "chapter": 1, + "level": 85 + }, + { + "chapter": 1, + "level": 86 + }, + { + "chapter": 1, + "level": 87 + }, + { + "chapter": 1, + "level": 88 + }, + { + "chapter": 1, + "level": 89 + }, + { + "chapter": 1, + "level": 90 + }, + { + "chapter": 1, + "level": 91 + }, + { + "chapter": 1, + "level": 92 + }, + { + "chapter": 1, + "level": 93 + }, + { + "chapter": 1, + "level": 94 + }, + { + "chapter": 1, + "level": 95 + }, + { + "chapter": 1, + "level": 96 + }, + { + "chapter": 1, + "level": 97 + }, + { + "chapter": 1, + "level": 98 + }, + { + "chapter": 1, + "level": 99 + }, + { + "chapter": 1, + "level": 100 + }, + { + "chapter": 1, + "level": 101 + }, + { + "chapter": 1, + "level": 102 + }, + { + "chapter": 1, + "level": 103 + }, + { + "chapter": 1, + "level": 104 + }, + { + "chapter": 1, + "level": 105 + }, + { + "chapter": 1, + "level": 106 + }, + { + "chapter": 1, + "level": 107 + }, + { + "chapter": 1, + "level": 108 + }, + { + "chapter": 1, + "level": 109 + }, + { + "chapter": 1, + "level": 110 + }, + { + "chapter": 1, + "level": 111 + }, + { + "chapter": 1, + "level": 112 + }, + { + "chapter": 1, + "level": 113 + }, + { + "chapter": 1, + "level": 114 + }, + { + "chapter": 1, + "level": 115 + }, + { + "chapter": 1, + "level": 116 + }, + { + "chapter": 1, + "level": 117 + }, + { + "chapter": 1, + "level": 118 + }, + { + "chapter": 1, + "level": 119 + }, + { + "chapter": 1, + "level": 120 + }, + { + "chapter": 1, + "level": 121 + }, + { + "chapter": 1, + "level": 122 + }, + { + "chapter": 1, + "level": 123 + }, + { + "chapter": 1, + "level": 124 + }, + { + "chapter": 1, + "level": 125 + }, + { + "chapter": 1, + "level": 126 + }, + { + "chapter": 1, + "level": 127 + }, + { + "chapter": 1, + "level": 128 + }, + { + "chapter": 1, + "level": 129 + }, + { + "chapter": 1, + "level": 130 + }, + { + "chapter": 1, + "level": 131 + }, + { + "chapter": 1, + "level": 132 + }, + { + "chapter": 1, + "level": 133 + }, + { + "chapter": 1, + "level": 134 + }, + { + "chapter": 1, + "level": 135 + }, + { + "chapter": 1, + "level": 136 + }, + { + "chapter": 1, + "level": 137 + }, + { + "chapter": 1, + "level": 138 + }, + { + "chapter": 1, + "level": 139 + }, + { + "chapter": 1, + "level": 140 + }, + { + "chapter": 1, + "level": 141 + }, + { + "chapter": 1, + "level": 142 + }, + { + "chapter": 1, + "level": 143 + }, + { + "chapter": 1, + "level": 144 + }, + { + "chapter": 1, + "level": 145 + }, + { + "chapter": 1, + "level": 146 + }, + { + "chapter": 1, + "level": 147 + }, + { + "chapter": 1, + "level": 148 + }, + { + "chapter": 1, + "level": 149 + }, + { + "chapter": 1, + "level": 150 + }, + { + "chapter": 1, + "level": 151 + }, + { + "chapter": 1, + "level": 152 + }, + { + "chapter": 1, + "level": 153 + }, + { + "chapter": 1, + "level": 154 + }, + { + "chapter": 1, + "level": 155 + }, + { + "chapter": 1, + "level": 156 + }, + { + "chapter": 1, + "level": 157 + }, + { + "chapter": 1, + "level": 158 + }, + { + "chapter": 1, + "level": 159 + }, + { + "chapter": 1, + "level": 160 + }, + { + "chapter": 1, + "level": 161 + }, + { + "chapter": 1, + "level": 162 + }, + { + "chapter": 1, + "level": 163 + }, + { + "chapter": 1, + "level": 164 + }, + { + "chapter": 1, + "level": 165 + }, + { + "chapter": 1, + "level": 166 + }, + { + "chapter": 1, + "level": 167 + }, + { + "chapter": 1, + "level": 168 + }, + { + "chapter": 1, + "level": 169 + }, + { + "chapter": 1, + "level": 170 + }, + { + "chapter": 1, + "level": 171 + }, + { + "chapter": 1, + "level": 172 + }, + { + "chapter": 1, + "level": 173 + }, + { + "chapter": 1, + "level": 174 + }, + { + "chapter": 1, + "level": 175 + }, + { + "chapter": 1, + "level": 176 + }, + { + "chapter": 1, + "level": 177 + }, + { + "chapter": 1, + "level": 178 + }, + { + "chapter": 1, + "level": 179 + }, + { + "chapter": 1, + "level": 180 + }, + { + "chapter": 1, + "level": 181 + }, + { + "chapter": 1, + "level": 182 + }, + { + "chapter": 1, + "level": 183 + }, + { + "chapter": 1, + "level": 184 + }, + { + "chapter": 1, + "level": 185 + }, + { + "chapter": 1, + "level": 186 + }, + { + "chapter": 1, + "level": 187 + }, + { + "chapter": 1, + "level": 188 + }, + { + "chapter": 1, + "level": 189 + }, + { + "chapter": 1, + "level": 190 + }, + { + "chapter": 1, + "level": 191 + }, + { + "chapter": 1, + "level": 192 + }, + { + "chapter": 1, + "level": 193 + }, + { + "chapter": 1, + "level": 194 + }, + { + "chapter": 1, + "level": 195 + }, + { + "chapter": 1, + "level": 196 + }, + { + "chapter": 1, + "level": 197 + }, + { + "chapter": 1, + "level": 198 + }, + { + "chapter": 1, + "level": 199 + }, + { + "chapter": 1, + "level": 200 + }, + { + "chapter": 1, + "level": 201 + }, + { + "chapter": 1, + "level": 202 + }, + { + "chapter": 1, + "level": 203 + }, + { + "chapter": 1, + "level": 204 + }, + { + "chapter": 1, + "level": 205 + }, + { + "chapter": 1, + "level": 206 + }, + { + "chapter": 1, + "level": 207 + }, + { + "chapter": 1, + "level": 208 + }, + { + "chapter": 1, + "level": 209 + }, + { + "chapter": 1, + "level": 210 + }, + { + "chapter": 1, + "level": 211 + }, + { + "chapter": 1, + "level": 212 + }, + { + "chapter": 1, + "level": 213 + }, + { + "chapter": 1, + "level": 214 + }, + { + "chapter": 1, + "level": 215 + }, + { + "chapter": 1, + "level": 216 + }, + { + "chapter": 1, + "level": 217 + }, + { + "chapter": 1, + "level": 218 + }, + { + "chapter": 1, + "level": 219 + }, + { + "chapter": 1, + "level": 220 + }, + { + "chapter": 1, + "level": 221 + }, + { + "chapter": 1, + "level": 222 + }, + { + "chapter": 1, + "level": 223 + }, + { + "chapter": 1, + "level": 224 + }, + { + "chapter": 1, + "level": 225 + }, + { + "chapter": 1, + "level": 226 + }, + { + "chapter": 1, + "level": 227 + }, + { + "chapter": 1, + "level": 228 + }, + { + "chapter": 1, + "level": 229 + }, + { + "chapter": 1, + "level": 230 + }, + { + "chapter": 1, + "level": 231 + }, + { + "chapter": 1, + "level": 232 + }, + { + "chapter": 1, + "level": 233 + }, + { + "chapter": 1, + "level": 234 + }, + { + "chapter": 1, + "level": 235 + }, + { + "chapter": 1, + "level": 236 + }, + { + "chapter": 1, + "level": 237 + }, + { + "chapter": 1, + "level": 238 + }, + { + "chapter": 1, + "level": 239 + }, + { + "chapter": 1, + "level": 240 + }, + { + "chapter": 1, + "level": 241 + }, + { + "chapter": 1, + "level": 242 + }, + { + "chapter": 1, + "level": 243 + }, + { + "chapter": 1, + "level": 244 + }, + { + "chapter": 1, + "level": 245 + }, + { + "chapter": 1, + "level": 246 + }, + { + "chapter": 1, + "level": 247 + }, + { + "chapter": 1, + "level": 248 + }, + { + "chapter": 1, + "level": 249 + }, + { + "chapter": 1, + "level": 250 + }, + { + "chapter": 1, + "level": 251 + }, + { + "chapter": 1, + "level": 252 + }, + { + "chapter": 1, + "level": 253 + }, + { + "chapter": 1, + "level": 254 + }, + { + "chapter": 1, + "level": 255 + }, + { + "chapter": 1, + "level": 256 + }, + { + "chapter": 1, + "level": 257 + }, + { + "chapter": 1, + "level": 258 + }, + { + "chapter": 1, + "level": 259 + }, + { + "chapter": 1, + "level": 260 + }, + { + "chapter": 1, + "level": 261 + }, + { + "chapter": 1, + "level": 262 + }, + { + "chapter": 1, + "level": 263 + }, + { + "chapter": 1, + "level": 264 + }, + { + "chapter": 1, + "level": 265 + }, + { + "chapter": 1, + "level": 266 + }, + { + "chapter": 1, + "level": 267 + }, + { + "chapter": 1, + "level": 268 + }, + { + "chapter": 1, + "level": 269 + }, + { + "chapter": 1, + "level": 270 + }, + { + "chapter": 1, + "level": 271 + }, + { + "chapter": 1, + "level": 272 + }, + { + "chapter": 1, + "level": 273 + }, + { + "chapter": 1, + "level": 274 + }, + { + "chapter": 1, + "level": 275 + }, + { + "chapter": 1, + "level": 276 + }, + { + "chapter": 1, + "level": 277 + }, + { + "chapter": 1, + "level": 278 + }, + { + "chapter": 1, + "level": 279 + }, + { + "chapter": 1, + "level": 280 + }, + { + "chapter": 1, + "level": 281 + }, + { + "chapter": 1, + "level": 282 + }, + { + "chapter": 1, + "level": 283 + }, + { + "chapter": 1, + "level": 284 + }, + { + "chapter": 1, + "level": 285 + }, + { + "chapter": 1, + "level": 286 + }, + { + "chapter": 1, + "level": 287 + }, + { + "chapter": 1, + "level": 288 + }, + { + "chapter": 1, + "level": 289 + }, + { + "chapter": 1, + "level": 290 + }, + { + "chapter": 1, + "level": 291 + }, + { + "chapter": 1, + "level": 292 + }, + { + "chapter": 1, + "level": 293 + }, + { + "chapter": 1, + "level": 294 + }, + { + "chapter": 1, + "level": 295 + }, + { + "chapter": 1, + "level": 296 + }, + { + "chapter": 1, + "level": 297 + }, + { + "chapter": 1, + "level": 298 + }, + { + "chapter": 1, + "level": 299 + }, + { + "chapter": 1, + "level": 300 + }, + { + "chapter": 2, + "level": 0 + }, + { + "chapter": 2, + "level": 1 + }, + { + "chapter": 2, + "level": 2 + }, + { + "chapter": 2, + "level": 3 + }, + { + "chapter": 2, + "level": 4 + }, + { + "chapter": 2, + "level": 5 + }, + { + "chapter": 2, + "level": 6 + }, + { + "chapter": 2, + "level": 7 + }, + { + "chapter": 2, + "level": 8 + }, + { + "chapter": 2, + "level": 9 + }, + { + "chapter": 2, + "level": 10 + }, + { + "chapter": 2, + "level": 11 + }, + { + "chapter": 2, + "level": 12 + }, + { + "chapter": 2, + "level": 13 + }, + { + "chapter": 2, + "level": 14 + }, + { + "chapter": 2, + "level": 15 + }, + { + "chapter": 2, + "level": 16 + }, + { + "chapter": 2, + "level": 17 + }, + { + "chapter": 2, + "level": 18 + }, + { + "chapter": 2, + "level": 19 + }, + { + "chapter": 2, + "level": 20 + }, + { + "chapter": 2, + "level": 21 + }, + { + "chapter": 2, + "level": 22 + }, + { + "chapter": 2, + "level": 23 + }, + { + "chapter": 2, + "level": 24 + }, + { + "chapter": 2, + "level": 25 + }, + { + "chapter": 2, + "level": 26 + }, + { + "chapter": 2, + "level": 27 + }, + { + "chapter": 2, + "level": 28 + }, + { + "chapter": 2, + "level": 29 + }, + { + "chapter": 2, + "level": 30 + }, + { + "chapter": 2, + "level": 31 + }, + { + "chapter": 2, + "level": 32 + }, + { + "chapter": 2, + "level": 33 + }, + { + "chapter": 2, + "level": 34 + }, + { + "chapter": 2, + "level": 35 + }, + { + "chapter": 2, + "level": 36 + }, + { + "chapter": 2, + "level": 37 + }, + { + "chapter": 2, + "level": 38 + }, + { + "chapter": 2, + "level": 39 + }, + { + "chapter": 2, + "level": 40 + }, + { + "chapter": 2, + "level": 41 + }, + { + "chapter": 2, + "level": 42 + }, + { + "chapter": 2, + "level": 43 + }, + { + "chapter": 2, + "level": 44 + }, + { + "chapter": 2, + "level": 45 + }, + { + "chapter": 2, + "level": 46 + }, + { + "chapter": 2, + "level": 47 + }, + { + "chapter": 2, + "level": 48 + }, + { + "chapter": 2, + "level": 49 + }, + { + "chapter": 2, + "level": 50 + }, + { + "chapter": 2, + "level": 51 + }, + { + "chapter": 2, + "level": 52 + }, + { + "chapter": 2, + "level": 53 + }, + { + "chapter": 2, + "level": 54 + }, + { + "chapter": 2, + "level": 55 + }, + { + "chapter": 2, + "level": 56 + }, + { + "chapter": 2, + "level": 57 + }, + { + "chapter": 2, + "level": 58 + }, + { + "chapter": 2, + "level": 59 + }, + { + "chapter": 2, + "level": 60 + }, + { + "chapter": 2, + "level": 61 + }, + { + "chapter": 2, + "level": 62 + }, + { + "chapter": 2, + "level": 63 + }, + { + "chapter": 2, + "level": 64 + }, + { + "chapter": 2, + "level": 65 + }, + { + "chapter": 2, + "level": 66 + }, + { + "chapter": 2, + "level": 67 + }, + { + "chapter": 2, + "level": 68 + }, + { + "chapter": 2, + "level": 69 + }, + { + "chapter": 2, + "level": 70 + }, + { + "chapter": 2, + "level": 71 + }, + { + "chapter": 2, + "level": 72 + }, + { + "chapter": 2, + "level": 73 + }, + { + "chapter": 2, + "level": 74 + }, + { + "chapter": 2, + "level": 75 + }, + { + "chapter": 2, + "level": 76 + }, + { + "chapter": 2, + "level": 77 + }, + { + "chapter": 2, + "level": 78 + }, + { + "chapter": 2, + "level": 79 + }, + { + "chapter": 2, + "level": 80 + }, + { + "chapter": 2, + "level": 81 + }, + { + "chapter": 2, + "level": 82 + }, + { + "chapter": 2, + "level": 83 + }, + { + "chapter": 2, + "level": 84 + }, + { + "chapter": 2, + "level": 85 + }, + { + "chapter": 2, + "level": 86 + }, + { + "chapter": 2, + "level": 87 + }, + { + "chapter": 2, + "level": 88 + }, + { + "chapter": 2, + "level": 89 + }, + { + "chapter": 2, + "level": 90 + }, + { + "chapter": 2, + "level": 91 + }, + { + "chapter": 2, + "level": 92 + }, + { + "chapter": 2, + "level": 93 + }, + { + "chapter": 2, + "level": 94 + }, + { + "chapter": 2, + "level": 95 + }, + { + "chapter": 2, + "level": 96 + }, + { + "chapter": 2, + "level": 97 + }, + { + "chapter": 2, + "level": 98 + }, + { + "chapter": 2, + "level": 99 + }, + { + "chapter": 2, + "level": 100 + }, + { + "chapter": 2, + "level": 101 + }, + { + "chapter": 2, + "level": 102 + }, + { + "chapter": 2, + "level": 103 + }, + { + "chapter": 2, + "level": 104 + }, + { + "chapter": 2, + "level": 105 + }, + { + "chapter": 2, + "level": 106 + }, + { + "chapter": 2, + "level": 107 + }, + { + "chapter": 2, + "level": 108 + }, + { + "chapter": 2, + "level": 109 + }, + { + "chapter": 2, + "level": 110 + }, + { + "chapter": 2, + "level": 111 + }, + { + "chapter": 2, + "level": 112 + }, + { + "chapter": 2, + "level": 113 + }, + { + "chapter": 2, + "level": 114 + }, + { + "chapter": 2, + "level": 115 + }, + { + "chapter": 2, + "level": 116 + }, + { + "chapter": 2, + "level": 117 + }, + { + "chapter": 2, + "level": 118 + }, + { + "chapter": 2, + "level": 119 + }, + { + "chapter": 2, + "level": 120 + }, + { + "chapter": 2, + "level": 121 + }, + { + "chapter": 2, + "level": 122 + }, + { + "chapter": 2, + "level": 123 + }, + { + "chapter": 2, + "level": 124 + }, + { + "chapter": 2, + "level": 125 + }, + { + "chapter": 2, + "level": 126 + }, + { + "chapter": 2, + "level": 127 + }, + { + "chapter": 2, + "level": 128 + }, + { + "chapter": 2, + "level": 129 + }, + { + "chapter": 2, + "level": 130 + }, + { + "chapter": 2, + "level": 131 + }, + { + "chapter": 2, + "level": 132 + }, + { + "chapter": 2, + "level": 133 + }, + { + "chapter": 2, + "level": 134 + }, + { + "chapter": 2, + "level": 135 + }, + { + "chapter": 2, + "level": 136 + }, + { + "chapter": 2, + "level": 137 + }, + { + "chapter": 2, + "level": 138 + }, + { + "chapter": 2, + "level": 139 + }, + { + "chapter": 2, + "level": 140 + }, + { + "chapter": 2, + "level": 141 + }, + { + "chapter": 2, + "level": 142 + }, + { + "chapter": 2, + "level": 143 + }, + { + "chapter": 2, + "level": 144 + }, + { + "chapter": 2, + "level": 145 + }, + { + "chapter": 2, + "level": 146 + }, + { + "chapter": 2, + "level": 147 + }, + { + "chapter": 2, + "level": 148 + }, + { + "chapter": 2, + "level": 149 + }, + { + "chapter": 2, + "level": 150 + }, + { + "chapter": 2, + "level": 151 + }, + { + "chapter": 2, + "level": 152 + }, + { + "chapter": 2, + "level": 153 + }, + { + "chapter": 2, + "level": 154 + }, + { + "chapter": 2, + "level": 155 + }, + { + "chapter": 2, + "level": 156 + }, + { + "chapter": 2, + "level": 157 + }, + { + "chapter": 2, + "level": 158 + }, + { + "chapter": 2, + "level": 159 + }, + { + "chapter": 2, + "level": 160 + }, + { + "chapter": 2, + "level": 161 + }, + { + "chapter": 2, + "level": 162 + }, + { + "chapter": 2, + "level": 163 + }, + { + "chapter": 2, + "level": 164 + }, + { + "chapter": 2, + "level": 165 + }, + { + "chapter": 2, + "level": 166 + }, + { + "chapter": 2, + "level": 167 + }, + { + "chapter": 2, + "level": 168 + }, + { + "chapter": 2, + "level": 169 + }, + { + "chapter": 2, + "level": 170 + }, + { + "chapter": 2, + "level": 171 + }, + { + "chapter": 2, + "level": 172 + }, + { + "chapter": 2, + "level": 173 + }, + { + "chapter": 2, + "level": 174 + }, + { + "chapter": 2, + "level": 175 + }, + { + "chapter": 2, + "level": 176 + }, + { + "chapter": 2, + "level": 177 + }, + { + "chapter": 2, + "level": 178 + }, + { + "chapter": 2, + "level": 179 + }, + { + "chapter": 2, + "level": 180 + }, + { + "chapter": 2, + "level": 181 + }, + { + "chapter": 2, + "level": 182 + }, + { + "chapter": 2, + "level": 183 + }, + { + "chapter": 2, + "level": 184 + }, + { + "chapter": 2, + "level": 185 + }, + { + "chapter": 2, + "level": 186 + }, + { + "chapter": 2, + "level": 187 + }, + { + "chapter": 2, + "level": 188 + }, + { + "chapter": 2, + "level": 189 + }, + { + "chapter": 2, + "level": 190 + }, + { + "chapter": 2, + "level": 191 + }, + { + "chapter": 2, + "level": 192 + }, + { + "chapter": 2, + "level": 193 + }, + { + "chapter": 2, + "level": 194 + }, + { + "chapter": 2, + "level": 195 + }, + { + "chapter": 2, + "level": 196 + }, + { + "chapter": 2, + "level": 197 + }, + { + "chapter": 2, + "level": 198 + }, + { + "chapter": 2, + "level": 199 + }, + { + "chapter": 2, + "level": 200 + }, + { + "chapter": 2, + "level": 201 + }, + { + "chapter": 2, + "level": 202 + }, + { + "chapter": 2, + "level": 203 + }, + { + "chapter": 2, + "level": 204 + }, + { + "chapter": 2, + "level": 205 + }, + { + "chapter": 2, + "level": 206 + }, + { + "chapter": 2, + "level": 207 + }, + { + "chapter": 2, + "level": 208 + }, + { + "chapter": 2, + "level": 209 + }, + { + "chapter": 2, + "level": 210 + }, + { + "chapter": 2, + "level": 211 + }, + { + "chapter": 2, + "level": 212 + }, + { + "chapter": 2, + "level": 213 + }, + { + "chapter": 2, + "level": 214 + }, + { + "chapter": 2, + "level": 215 + }, + { + "chapter": 2, + "level": 216 + }, + { + "chapter": 2, + "level": 217 + }, + { + "chapter": 2, + "level": 218 + }, + { + "chapter": 2, + "level": 219 + }, + { + "chapter": 2, + "level": 220 + }, + { + "chapter": 2, + "level": 221 + }, + { + "chapter": 2, + "level": 222 + }, + { + "chapter": 2, + "level": 223 + }, + { + "chapter": 2, + "level": 224 + }, + { + "chapter": 2, + "level": 225 + }, + { + "chapter": 2, + "level": 226 + }, + { + "chapter": 2, + "level": 227 + }, + { + "chapter": 2, + "level": 228 + }, + { + "chapter": 2, + "level": 229 + }, + { + "chapter": 2, + "level": 230 + }, + { + "chapter": 2, + "level": 231 + }, + { + "chapter": 2, + "level": 232 + }, + { + "chapter": 2, + "level": 233 + }, + { + "chapter": 2, + "level": 234 + }, + { + "chapter": 2, + "level": 235 + }, + { + "chapter": 2, + "level": 236 + }, + { + "chapter": 2, + "level": 237 + }, + { + "chapter": 2, + "level": 238 + }, + { + "chapter": 2, + "level": 239 + }, + { + "chapter": 2, + "level": 240 + }, + { + "chapter": 2, + "level": 241 + }, + { + "chapter": 2, + "level": 242 + }, + { + "chapter": 2, + "level": 243 + }, + { + "chapter": 2, + "level": 244 + }, + { + "chapter": 2, + "level": 245 + }, + { + "chapter": 2, + "level": 246 + }, + { + "chapter": 2, + "level": 247 + }, + { + "chapter": 2, + "level": 248 + }, + { + "chapter": 2, + "level": 249 + }, + { + "chapter": 2, + "level": 250 + }, + { + "chapter": 2, + "level": 251 + }, + { + "chapter": 2, + "level": 252 + }, + { + "chapter": 2, + "level": 253 + }, + { + "chapter": 2, + "level": 254 + }, + { + "chapter": 2, + "level": 255 + }, + { + "chapter": 2, + "level": 256 + }, + { + "chapter": 2, + "level": 257 + }, + { + "chapter": 2, + "level": 258 + }, + { + "chapter": 2, + "level": 259 + }, + { + "chapter": 2, + "level": 260 + }, + { + "chapter": 2, + "level": 261 + }, + { + "chapter": 2, + "level": 262 + }, + { + "chapter": 2, + "level": 263 + }, + { + "chapter": 2, + "level": 264 + }, + { + "chapter": 2, + "level": 265 + }, + { + "chapter": 2, + "level": 266 + }, + { + "chapter": 2, + "level": 267 + }, + { + "chapter": 2, + "level": 268 + }, + { + "chapter": 2, + "level": 269 + }, + { + "chapter": 2, + "level": 270 + }, + { + "chapter": 2, + "level": 271 + }, + { + "chapter": 2, + "level": 272 + }, + { + "chapter": 2, + "level": 273 + }, + { + "chapter": 2, + "level": 274 + }, + { + "chapter": 2, + "level": 275 + }, + { + "chapter": 2, + "level": 276 + }, + { + "chapter": 2, + "level": 277 + }, + { + "chapter": 2, + "level": 278 + }, + { + "chapter": 2, + "level": 279 + }, + { + "chapter": 2, + "level": 280 + }, + { + "chapter": 2, + "level": 281 + }, + { + "chapter": 2, + "level": 282 + }, + { + "chapter": 2, + "level": 283 + }, + { + "chapter": 2, + "level": 284 + }, + { + "chapter": 2, + "level": 285 + }, + { + "chapter": 2, + "level": 286 + }, + { + "chapter": 2, + "level": 287 + }, + { + "chapter": 2, + "level": 288 + }, + { + "chapter": 2, + "level": 289 + }, + { + "chapter": 2, + "level": 290 + }, + { + "chapter": 2, + "level": 291 + }, + { + "chapter": 2, + "level": 292 + }, + { + "chapter": 2, + "level": 293 + }, + { + "chapter": 2, + "level": 294 + }, + { + "chapter": 2, + "level": 295 + }, + { + "chapter": 2, + "level": 296 + }, + { + "chapter": 2, + "level": 297 + }, + { + "chapter": 2, + "level": 298 + }, + { + "chapter": 2, + "level": 299 + }, + { + "chapter": 2, + "level": 300 + }, + { + "chapter": 3, + "level": 0 + }, + { + "chapter": 3, + "level": 1 + }, + { + "chapter": 3, + "level": 2 + }, + { + "chapter": 3, + "level": 3 + }, + { + "chapter": 3, + "level": 4 + }, + { + "chapter": 3, + "level": 5 + }, + { + "chapter": 3, + "level": 6 + }, + { + "chapter": 3, + "level": 7 + }, + { + "chapter": 3, + "level": 8 + }, + { + "chapter": 3, + "level": 9 + }, + { + "chapter": 3, + "level": 10 + }, + { + "chapter": 3, + "level": 11 + }, + { + "chapter": 3, + "level": 12 + }, + { + "chapter": 3, + "level": 13 + }, + { + "chapter": 3, + "level": 14 + }, + { + "chapter": 3, + "level": 15 + }, + { + "chapter": 3, + "level": 16 + }, + { + "chapter": 3, + "level": 17 + }, + { + "chapter": 3, + "level": 18 + }, + { + "chapter": 3, + "level": 19 + }, + { + "chapter": 3, + "level": 20 + }, + { + "chapter": 3, + "level": 21 + }, + { + "chapter": 3, + "level": 22 + }, + { + "chapter": 3, + "level": 23 + }, + { + "chapter": 3, + "level": 24 + }, + { + "chapter": 3, + "level": 25 + }, + { + "chapter": 3, + "level": 26 + }, + { + "chapter": 3, + "level": 27 + }, + { + "chapter": 3, + "level": 28 + }, + { + "chapter": 3, + "level": 29 + }, + { + "chapter": 3, + "level": 30 + }, + { + "chapter": 3, + "level": 31 + }, + { + "chapter": 3, + "level": 32 + }, + { + "chapter": 3, + "level": 33 + }, + { + "chapter": 3, + "level": 34 + }, + { + "chapter": 3, + "level": 35 + }, + { + "chapter": 3, + "level": 36 + }, + { + "chapter": 3, + "level": 37 + }, + { + "chapter": 3, + "level": 38 + }, + { + "chapter": 3, + "level": 39 + }, + { + "chapter": 3, + "level": 40 + }, + { + "chapter": 3, + "level": 41 + }, + { + "chapter": 3, + "level": 42 + }, + { + "chapter": 3, + "level": 43 + }, + { + "chapter": 3, + "level": 44 + }, + { + "chapter": 3, + "level": 45 + }, + { + "chapter": 3, + "level": 46 + }, + { + "chapter": 3, + "level": 47 + }, + { + "chapter": 3, + "level": 48 + }, + { + "chapter": 3, + "level": 49 + }, + { + "chapter": 3, + "level": 50 + }, + { + "chapter": 3, + "level": 51 + }, + { + "chapter": 3, + "level": 52 + }, + { + "chapter": 3, + "level": 53 + }, + { + "chapter": 3, + "level": 54 + }, + { + "chapter": 3, + "level": 55 + }, + { + "chapter": 3, + "level": 56 + }, + { + "chapter": 3, + "level": 57 + }, + { + "chapter": 3, + "level": 58 + }, + { + "chapter": 3, + "level": 59 + }, + { + "chapter": 3, + "level": 60 + }, + { + "chapter": 3, + "level": 61 + }, + { + "chapter": 3, + "level": 62 + }, + { + "chapter": 3, + "level": 63 + }, + { + "chapter": 3, + "level": 64 + }, + { + "chapter": 3, + "level": 65 + }, + { + "chapter": 3, + "level": 66 + }, + { + "chapter": 3, + "level": 67 + }, + { + "chapter": 3, + "level": 68 + }, + { + "chapter": 3, + "level": 69 + }, + { + "chapter": 3, + "level": 70 + }, + { + "chapter": 3, + "level": 71 + }, + { + "chapter": 3, + "level": 72 + }, + { + "chapter": 3, + "level": 73 + }, + { + "chapter": 3, + "level": 74 + }, + { + "chapter": 3, + "level": 75 + }, + { + "chapter": 3, + "level": 76 + }, + { + "chapter": 3, + "level": 77 + }, + { + "chapter": 3, + "level": 78 + }, + { + "chapter": 3, + "level": 79 + }, + { + "chapter": 3, + "level": 80 + }, + { + "chapter": 3, + "level": 81 + }, + { + "chapter": 3, + "level": 82 + }, + { + "chapter": 3, + "level": 83 + }, + { + "chapter": 3, + "level": 84 + }, + { + "chapter": 3, + "level": 85 + }, + { + "chapter": 3, + "level": 86 + }, + { + "chapter": 3, + "level": 87 + }, + { + "chapter": 3, + "level": 88 + }, + { + "chapter": 3, + "level": 89 + }, + { + "chapter": 3, + "level": 90 + }, + { + "chapter": 3, + "level": 91 + }, + { + "chapter": 3, + "level": 92 + }, + { + "chapter": 3, + "level": 93 + }, + { + "chapter": 3, + "level": 94 + }, + { + "chapter": 3, + "level": 95 + }, + { + "chapter": 3, + "level": 96 + }, + { + "chapter": 3, + "level": 97 + }, + { + "chapter": 3, + "level": 98 + }, + { + "chapter": 3, + "level": 99 + }, + { + "chapter": 3, + "level": 100 + }, + { + "chapter": 3, + "level": 101 + }, + { + "chapter": 3, + "level": 102 + }, + { + "chapter": 3, + "level": 103 + }, + { + "chapter": 3, + "level": 104 + }, + { + "chapter": 3, + "level": 105 + }, + { + "chapter": 3, + "level": 106 + }, + { + "chapter": 3, + "level": 107 + }, + { + "chapter": 3, + "level": 108 + }, + { + "chapter": 3, + "level": 109 + }, + { + "chapter": 3, + "level": 110 + }, + { + "chapter": 3, + "level": 111 + }, + { + "chapter": 3, + "level": 112 + }, + { + "chapter": 3, + "level": 113 + }, + { + "chapter": 3, + "level": 114 + }, + { + "chapter": 3, + "level": 115 + }, + { + "chapter": 3, + "level": 116 + }, + { + "chapter": 3, + "level": 117 + }, + { + "chapter": 3, + "level": 118 + }, + { + "chapter": 3, + "level": 119 + }, + { + "chapter": 3, + "level": 120 + }, + { + "chapter": 3, + "level": 121 + }, + { + "chapter": 3, + "level": 122 + }, + { + "chapter": 3, + "level": 123 + }, + { + "chapter": 3, + "level": 124 + }, + { + "chapter": 3, + "level": 125 + }, + { + "chapter": 3, + "level": 126 + }, + { + "chapter": 3, + "level": 127 + }, + { + "chapter": 3, + "level": 128 + }, + { + "chapter": 3, + "level": 129 + }, + { + "chapter": 3, + "level": 130 + }, + { + "chapter": 3, + "level": 131 + }, + { + "chapter": 3, + "level": 132 + }, + { + "chapter": 3, + "level": 133 + }, + { + "chapter": 3, + "level": 134 + }, + { + "chapter": 3, + "level": 135 + }, + { + "chapter": 3, + "level": 136 + }, + { + "chapter": 3, + "level": 137 + }, + { + "chapter": 3, + "level": 138 + }, + { + "chapter": 3, + "level": 139 + }, + { + "chapter": 3, + "level": 140 + }, + { + "chapter": 3, + "level": 141 + }, + { + "chapter": 3, + "level": 142 + }, + { + "chapter": 3, + "level": 143 + }, + { + "chapter": 3, + "level": 144 + }, + { + "chapter": 3, + "level": 145 + }, + { + "chapter": 3, + "level": 146 + }, + { + "chapter": 3, + "level": 147 + }, + { + "chapter": 3, + "level": 148 + }, + { + "chapter": 3, + "level": 149 + }, + { + "chapter": 3, + "level": 150 + }, + { + "chapter": 3, + "level": 151 + }, + { + "chapter": 3, + "level": 152 + }, + { + "chapter": 3, + "level": 153 + }, + { + "chapter": 3, + "level": 154 + }, + { + "chapter": 3, + "level": 155 + }, + { + "chapter": 3, + "level": 156 + }, + { + "chapter": 3, + "level": 157 + }, + { + "chapter": 3, + "level": 158 + }, + { + "chapter": 3, + "level": 159 + }, + { + "chapter": 3, + "level": 160 + }, + { + "chapter": 3, + "level": 161 + }, + { + "chapter": 3, + "level": 162 + }, + { + "chapter": 3, + "level": 163 + }, + { + "chapter": 3, + "level": 164 + }, + { + "chapter": 3, + "level": 165 + }, + { + "chapter": 3, + "level": 166 + }, + { + "chapter": 3, + "level": 167 + }, + { + "chapter": 3, + "level": 168 + }, + { + "chapter": 3, + "level": 169 + }, + { + "chapter": 3, + "level": 170 + }, + { + "chapter": 3, + "level": 171 + }, + { + "chapter": 3, + "level": 172 + }, + { + "chapter": 3, + "level": 173 + }, + { + "chapter": 3, + "level": 174 + }, + { + "chapter": 3, + "level": 175 + }, + { + "chapter": 3, + "level": 176 + }, + { + "chapter": 3, + "level": 177 + }, + { + "chapter": 3, + "level": 178 + }, + { + "chapter": 3, + "level": 179 + }, + { + "chapter": 3, + "level": 180 + }, + { + "chapter": 3, + "level": 181 + }, + { + "chapter": 3, + "level": 182 + }, + { + "chapter": 3, + "level": 183 + }, + { + "chapter": 3, + "level": 184 + }, + { + "chapter": 3, + "level": 185 + }, + { + "chapter": 3, + "level": 186 + }, + { + "chapter": 3, + "level": 187 + }, + { + "chapter": 3, + "level": 188 + }, + { + "chapter": 3, + "level": 189 + }, + { + "chapter": 3, + "level": 190 + }, + { + "chapter": 3, + "level": 191 + }, + { + "chapter": 3, + "level": 192 + }, + { + "chapter": 3, + "level": 193 + }, + { + "chapter": 3, + "level": 194 + }, + { + "chapter": 3, + "level": 195 + }, + { + "chapter": 3, + "level": 196 + }, + { + "chapter": 3, + "level": 197 + }, + { + "chapter": 3, + "level": 198 + }, + { + "chapter": 3, + "level": 199 + }, + { + "chapter": 3, + "level": 200 + }, + { + "chapter": 3, + "level": 201 + }, + { + "chapter": 3, + "level": 202 + }, + { + "chapter": 3, + "level": 203 + }, + { + "chapter": 3, + "level": 204 + }, + { + "chapter": 3, + "level": 205 + }, + { + "chapter": 3, + "level": 206 + }, + { + "chapter": 3, + "level": 207 + }, + { + "chapter": 3, + "level": 208 + }, + { + "chapter": 3, + "level": 209 + }, + { + "chapter": 3, + "level": 210 + }, + { + "chapter": 3, + "level": 211 + }, + { + "chapter": 3, + "level": 212 + }, + { + "chapter": 3, + "level": 213 + }, + { + "chapter": 3, + "level": 214 + }, + { + "chapter": 3, + "level": 215 + }, + { + "chapter": 3, + "level": 216 + }, + { + "chapter": 3, + "level": 217 + }, + { + "chapter": 3, + "level": 218 + }, + { + "chapter": 3, + "level": 219 + }, + { + "chapter": 3, + "level": 220 + }, + { + "chapter": 3, + "level": 221 + }, + { + "chapter": 3, + "level": 222 + }, + { + "chapter": 3, + "level": 223 + }, + { + "chapter": 3, + "level": 224 + }, + { + "chapter": 3, + "level": 225 + }, + { + "chapter": 3, + "level": 226 + }, + { + "chapter": 3, + "level": 227 + }, + { + "chapter": 3, + "level": 228 + }, + { + "chapter": 3, + "level": 229 + }, + { + "chapter": 3, + "level": 230 + }, + { + "chapter": 3, + "level": 231 + }, + { + "chapter": 3, + "level": 232 + }, + { + "chapter": 3, + "level": 233 + }, + { + "chapter": 3, + "level": 234 + }, + { + "chapter": 3, + "level": 235 + }, + { + "chapter": 3, + "level": 236 + }, + { + "chapter": 3, + "level": 237 + }, + { + "chapter": 3, + "level": 238 + }, + { + "chapter": 3, + "level": 239 + }, + { + "chapter": 3, + "level": 240 + }, + { + "chapter": 3, + "level": 241 + }, + { + "chapter": 3, + "level": 242 + }, + { + "chapter": 3, + "level": 243 + }, + { + "chapter": 3, + "level": 244 + }, + { + "chapter": 3, + "level": 245 + }, + { + "chapter": 3, + "level": 246 + }, + { + "chapter": 3, + "level": 247 + }, + { + "chapter": 3, + "level": 248 + }, + { + "chapter": 3, + "level": 249 + }, + { + "chapter": 3, + "level": 250 + }, + { + "chapter": 3, + "level": 251 + }, + { + "chapter": 3, + "level": 252 + }, + { + "chapter": 3, + "level": 253 + }, + { + "chapter": 3, + "level": 254 + }, + { + "chapter": 3, + "level": 255 + }, + { + "chapter": 3, + "level": 256 + }, + { + "chapter": 3, + "level": 257 + }, + { + "chapter": 3, + "level": 258 + }, + { + "chapter": 3, + "level": 259 + }, + { + "chapter": 3, + "level": 260 + }, + { + "chapter": 3, + "level": 261 + }, + { + "chapter": 3, + "level": 262 + }, + { + "chapter": 3, + "level": 263 + }, + { + "chapter": 3, + "level": 264 + }, + { + "chapter": 3, + "level": 265 + }, + { + "chapter": 3, + "level": 266 + }, + { + "chapter": 3, + "level": 267 + }, + { + "chapter": 3, + "level": 268 + }, + { + "chapter": 3, + "level": 269 + }, + { + "chapter": 3, + "level": 270 + }, + { + "chapter": 3, + "level": 271 + }, + { + "chapter": 3, + "level": 272 + }, + { + "chapter": 3, + "level": 273 + }, + { + "chapter": 3, + "level": 274 + }, + { + "chapter": 3, + "level": 275 + }, + { + "chapter": 3, + "level": 276 + }, + { + "chapter": 3, + "level": 277 + }, + { + "chapter": 3, + "level": 278 + }, + { + "chapter": 3, + "level": 279 + }, + { + "chapter": 3, + "level": 280 + }, + { + "chapter": 3, + "level": 281 + }, + { + "chapter": 3, + "level": 282 + }, + { + "chapter": 3, + "level": 283 + }, + { + "chapter": 3, + "level": 284 + }, + { + "chapter": 3, + "level": 285 + }, + { + "chapter": 3, + "level": 286 + }, + { + "chapter": 3, + "level": 287 + }, + { + "chapter": 3, + "level": 288 + }, + { + "chapter": 3, + "level": 289 + }, + { + "chapter": 3, + "level": 290 + }, + { + "chapter": 3, + "level": 291 + }, + { + "chapter": 3, + "level": 292 + }, + { + "chapter": 3, + "level": 293 + }, + { + "chapter": 3, + "level": 294 + }, + { + "chapter": 3, + "level": 295 + }, + { + "chapter": 3, + "level": 296 + }, + { + "chapter": 3, + "level": 297 + }, + { + "chapter": 3, + "level": 298 + }, + { + "chapter": 3, + "level": 299 + }, + { + "chapter": 3, + "level": 300 + } + ], + "divisions": [ + { + "DivisionID": 20101, + "EnemyDifficultyLevel": 1, + "BinaryNodeDiffAddRule": 0 + }, + { + "DivisionID": 20102, + "EnemyDifficultyLevel": 2, + "BinaryNodeDiffAddRule": 0 + }, + { + "DivisionID": 20103, + "EnemyDifficultyLevel": 3, + "BinaryNodeDiffAddRule": 0 + }, + { + "DivisionID": 20201, + "EnemyDifficultyLevel": 6, + "BinaryNodeDiffAddRule": 0 + }, + { + "DivisionID": 20202, + "EnemyDifficultyLevel": 7, + "BinaryNodeDiffAddRule": 0 + }, + { + "DivisionID": 20203, + "EnemyDifficultyLevel": 8, + "BinaryNodeDiffAddRule": 0 + }, + { + "DivisionID": 20301, + "EnemyDifficultyLevel": 11, + "BinaryNodeDiffAddRule": 1 + }, + { + "DivisionID": 20302, + "EnemyDifficultyLevel": 12, + "BinaryNodeDiffAddRule": 1 + }, + { + "DivisionID": 20303, + "EnemyDifficultyLevel": 13, + "BinaryNodeDiffAddRule": 1 + }, + { + "DivisionID": 20401, + "EnemyDifficultyLevel": 16, + "BinaryNodeDiffAddRule": 1 + }, + { + "DivisionID": 20402, + "EnemyDifficultyLevel": 17, + "BinaryNodeDiffAddRule": 1 + }, + { + "DivisionID": 20403, + "EnemyDifficultyLevel": 18, + "BinaryNodeDiffAddRule": 1 + }, + { + "DivisionID": 20404, + "EnemyDifficultyLevel": 19, + "BinaryNodeDiffAddRule": 1 + }, + { + "DivisionID": 20405, + "EnemyDifficultyLevel": 20, + "BinaryNodeDiffAddRule": 1 + }, + { + "DivisionID": 20501, + "EnemyDifficultyLevel": 23, + "BinaryNodeDiffAddRule": 2 + }, + { + "DivisionID": 20502, + "EnemyDifficultyLevel": 24, + "BinaryNodeDiffAddRule": 2 + }, + { + "DivisionID": 20503, + "EnemyDifficultyLevel": 25, + "BinaryNodeDiffAddRule": 2 + }, + { + "DivisionID": 20504, + "EnemyDifficultyLevel": 26, + "BinaryNodeDiffAddRule": 2 + }, + { + "DivisionID": 20505, + "EnemyDifficultyLevel": 27, + "BinaryNodeDiffAddRule": 2 + }, + { + "DivisionID": 20506, + "EnemyDifficultyLevel": 28, + "BinaryNodeDiffAddRule": 2 + }, + { + "DivisionID": 20507, + "EnemyDifficultyLevel": 29, + "BinaryNodeDiffAddRule": 2 + }, + { + "DivisionID": 20601, + "EnemyDifficultyLevel": 32, + "BinaryNodeDiffAddRule": 2 + }, + { + "DivisionID": 20602, + "EnemyDifficultyLevel": 33, + "BinaryNodeDiffAddRule": 2 + }, + { + "DivisionID": 20603, + "EnemyDifficultyLevel": 34, + "BinaryNodeDiffAddRule": 2 + }, + { + "DivisionID": 20604, + "EnemyDifficultyLevel": 35, + "BinaryNodeDiffAddRule": 2 + }, + { + "DivisionID": 20605, + "EnemyDifficultyLevel": 36, + "BinaryNodeDiffAddRule": 2 + }, + { + "DivisionID": 20606, + "EnemyDifficultyLevel": 37, + "BinaryNodeDiffAddRule": 2 + }, + { + "DivisionID": 20607, + "EnemyDifficultyLevel": 38, + "BinaryNodeDiffAddRule": 2 + }, + { + "DivisionID": 20608, + "EnemyDifficultyLevel": 39, + "BinaryNodeDiffAddRule": 2 + }, + { + "DivisionID": 20609, + "EnemyDifficultyLevel": 40, + "BinaryNodeDiffAddRule": 2 + }, + { + "DivisionID": 20610, + "EnemyDifficultyLevel": 48, + "BinaryNodeDiffAddRule": 2 + }, + { + "DivisionID": 20701, + "EnemyDifficultyLevel": 43, + "BinaryNodeDiffAddRule": 2 + }, + { + "DivisionID": 20702, + "EnemyDifficultyLevel": 44, + "BinaryNodeDiffAddRule": 2 + }, + { + "DivisionID": 20703, + "EnemyDifficultyLevel": 45, + "BinaryNodeDiffAddRule": 2 + }, + { + "DivisionID": 20704, + "EnemyDifficultyLevel": 46, + "BinaryNodeDiffAddRule": 2 + }, + { + "DivisionID": 20705, + "EnemyDifficultyLevel": 47, + "BinaryNodeDiffAddRule": 2 + }, + { + "DivisionID": 20706, + "EnemyDifficultyLevel": 48, + "BinaryNodeDiffAddRule": 2 + }, + { + "DivisionID": 20707, + "EnemyDifficultyLevel": 49, + "BinaryNodeDiffAddRule": 2 + }, + { + "DivisionID": 20708, + "EnemyDifficultyLevel": 50, + "BinaryNodeDiffAddRule": 2 + }, + { + "DivisionID": 20709, + "EnemyDifficultyLevel": 51, + "BinaryNodeDiffAddRule": 2 + }, + { + "DivisionID": 20710, + "EnemyDifficultyLevel": 58, + "BinaryNodeDiffAddRule": 2 + }, + { + "DivisionID": 20801, + "EnemyDifficultyLevel": 55, + "BinaryNodeDiffAddRule": 2 + }, + { + "DivisionID": 20802, + "EnemyDifficultyLevel": 56, + "BinaryNodeDiffAddRule": 2 + }, + { + "DivisionID": 20803, + "EnemyDifficultyLevel": 57, + "BinaryNodeDiffAddRule": 2 + }, + { + "DivisionID": 20804, + "EnemyDifficultyLevel": 58, + "BinaryNodeDiffAddRule": 2 + }, + { + "DivisionID": 20805, + "EnemyDifficultyLevel": 59, + "BinaryNodeDiffAddRule": 2 + }, + { + "DivisionID": 20806, + "EnemyDifficultyLevel": 60, + "BinaryNodeDiffAddRule": 2 + }, + { + "DivisionID": 20807, + "EnemyDifficultyLevel": 61, + "BinaryNodeDiffAddRule": 2 + }, + { + "DivisionID": 20808, + "EnemyDifficultyLevel": 62, + "BinaryNodeDiffAddRule": 2 + }, + { + "DivisionID": 20809, + "EnemyDifficultyLevel": 63, + "BinaryNodeDiffAddRule": 2 + }, + { + "DivisionID": 20810, + "EnemyDifficultyLevel": 72, + "BinaryNodeDiffAddRule": 2 + }, + { + "DivisionID": 20901, + "EnemyDifficultyLevel": 67, + "BinaryNodeDiffAddRule": 2 + }, + { + "DivisionID": 20902, + "EnemyDifficultyLevel": 68, + "BinaryNodeDiffAddRule": 2 + }, + { + "DivisionID": 20903, + "EnemyDifficultyLevel": 69, + "BinaryNodeDiffAddRule": 2 + }, + { + "DivisionID": 20904, + "EnemyDifficultyLevel": 70, + "BinaryNodeDiffAddRule": 2 + }, + { + "DivisionID": 20905, + "EnemyDifficultyLevel": 71, + "BinaryNodeDiffAddRule": 2 + }, + { + "DivisionID": 20906, + "EnemyDifficultyLevel": 72, + "BinaryNodeDiffAddRule": 2 + }, + { + "DivisionID": 20907, + "EnemyDifficultyLevel": 73, + "BinaryNodeDiffAddRule": 2 + }, + { + "DivisionID": 20908, + "EnemyDifficultyLevel": 74, + "BinaryNodeDiffAddRule": 2 + }, + { + "DivisionID": 20909, + "EnemyDifficultyLevel": 75, + "BinaryNodeDiffAddRule": 2 + }, + { + "DivisionID": 20910, + "EnemyDifficultyLevel": 76, + "BinaryNodeDiffAddRule": 2 + }, + { + "DivisionID": 20911, + "EnemyDifficultyLevel": 80, + "BinaryNodeDiffAddRule": 2 + }, + { + "DivisionID": 20912, + "EnemyDifficultyLevel": 81, + "BinaryNodeDiffAddRule": 2 + }, + { + "DivisionID": 20913, + "EnemyDifficultyLevel": 82, + "BinaryNodeDiffAddRule": 2 + }, + { + "DivisionID": 20914, + "EnemyDifficultyLevel": 83, + "BinaryNodeDiffAddRule": 2 + }, + { + "DivisionID": 20915, + "EnemyDifficultyLevel": 84, + "BinaryNodeDiffAddRule": 2 + }, + { + "DivisionID": 20916, + "EnemyDifficultyLevel": 85, + "BinaryNodeDiffAddRule": 2 + }, + { + "DivisionID": 20917, + "EnemyDifficultyLevel": 86, + "BinaryNodeDiffAddRule": 2 + }, + { + "DivisionID": 20918, + "EnemyDifficultyLevel": 87, + "BinaryNodeDiffAddRule": 2 + }, + { + "DivisionID": 20919, + "EnemyDifficultyLevel": 88, + "BinaryNodeDiffAddRule": 2 + }, + { + "DivisionID": 20920, + "EnemyDifficultyLevel": 89, + "BinaryNodeDiffAddRule": 2 + } + ], + "generation_policy": { + "source": "grid_fight.zig encounter fallback for EliteBranch nodes without a node_profile; server-authored, not official wave data", + "monster_count": 3, + "monster_tier": 2, + "star_cap": 3, + "normal_quality": 1, + "powerful_qualities": [ + 2, + 3, + 4 + ], + "seed_policy": "sha256 of run id, route, chapter and section; local deterministic sampling, not upstream Wyhash", + "reward_policy": "preview_base_gold_already_paid_by_settlement", + "extra_rewards_supported": false + }, + "gameplay_verified": false +} diff --git a/scripts/freecam.lua b/scripts/freecam.lua index 10f4f10..5149cd7 100644 --- a/scripts/freecam.lua +++ b/scripts/freecam.lua @@ -65,6 +65,62 @@ local function get_active_cam() return nil end +-- Resolve the controlled member or current battle actor through the Client's +-- entity state, and anchor to that model's observed head bone. If the current +-- phase does not expose either, do not guess a world position. +local function model_for(candidate) + local ok, result = pcall(function() + local component = candidate and candidate:GetComponent(typeof(CS.RPG.GameCore.CharacterModelComponent)) + return component and component:GetModelGO() + end) + if ok then return result end + return nil +end + +local function is_player_avatar(entity, model) + if not entity or not model then return false end + local name = tostring(model.name or '') + if name:find('Monster', 1, true) then return false end + if name:find('Avatar_00', 1, true) then return false end + if name:find('Avatar_', 1, true) or name:find('Player_', 1, true) then + return true + end + local ok, isHuman = pcall(function() + local anim = model:GetComponentInChildren(typeof(CS.UnityEngine.Animator)) + return anim and anim.isHuman + end) + return ok and (isHuman == true) +end + +local function find_head_bone(model) + if not model then return nil end + local ok, bone = pcall(function() + local animator = model:GetComponentInChildren(typeof(CS.UnityEngine.Animator)) + if animator and animator.isHuman then + return animator:GetBoneTransform(CS.UnityEngine.HumanBodyBones.Head) + end + return nil + end) + if ok and bone and bone.gameObject.activeInHierarchy then + return bone + end + local nodes = model:GetComponentsInChildren(typeof(CS.UnityEngine.Transform), true) + for i = 0, nodes.Length - 1 do + local node = nodes[i] + if node.name == 'Head_M' and node.gameObject.activeInHierarchy then + return node + end + end + for i = 0, nodes.Length - 1 do + local node = nodes[i] + local lname = node.name:lower() + if (lname == 'head' or lname == 'bip001 head' or lname:find('head_m', 1, true)) and node.gameObject.activeInHierarchy then + return node + end + end + return nil +end + -- Resolve the controlled member or current battle actor through the Client's -- entity state, and anchor to that model's observed head bone. If the current -- phase does not expose either, do not guess a world position. @@ -74,40 +130,83 @@ local function current_facecam_target() local field = typeof(CS.RPG.Client.GamePhaseManager):GetField('_CurrentPhase', flags) local phase = manager and field and field:GetValue(manager) if not phase then return nil, nil, nil, false end + + local now = Time.unscaledTime or 0 local entity, mode + if typeof(CS.RPG.Client.BattleGamePhase):IsInstanceOfType(phase) then local instance = phase.BattleInstanceRef mode = instance and instance.TurnBasedGameModeRef - entity = mode and mode.CurrentTurnOwnerEntity - -- Inserted attacks can act outside the turn owner's turn. Prefer the - -- most recently attacking entity while the Client reports one. if mode then + -- 1. Attacking Avatar (skill, ultimate, basic attack) local attackers = mode:GetAttackingEntityList() - if attackers and attackers.Count > 0 then entity = attackers[attackers.Count - 1] end + if attackers and attackers.Count > 0 then + for i = attackers.Count - 1, 0, -1 do + local cand = attackers[i] + local candModel = model_for(cand) + if is_player_avatar(cand, candModel) then + entity = cand + _G.__FC_BATTLE_LAST_ATTACKER = cand + _G.__FC_BATTLE_ATTACK_TIME = now + break + end + end + end + + -- 2. Keep tracking recent ultimate / attacking avatar during cutscenes (up to 4.5s) + if not entity and _G.__FC_BATTLE_LAST_ATTACKER then + local lastModel = model_for(_G.__FC_BATTLE_LAST_ATTACKER) + if lastModel and (now - (_G.__FC_BATTLE_ATTACK_TIME or 0)) < 4.5 then + entity = _G.__FC_BATTLE_LAST_ATTACKER + end + end + + -- 3. Turn owner (only if player avatar) + if not entity then + local turnOwner = mode.CurrentTurnOwnerEntity + local turnModel = model_for(turnOwner) + if is_player_avatar(turnOwner, turnModel) then + entity = turnOwner + _G.__FC_BATTLE_LAST_AVATAR = turnOwner + end + end + + -- 4. Stick to last active player avatar if turn owner is an enemy monster + if not entity and _G.__FC_BATTLE_LAST_AVATAR then + local lastModel = model_for(_G.__FC_BATTLE_LAST_AVATAR) + if lastModel then + entity = _G.__FC_BATTLE_LAST_AVATAR + end + end + + -- 5. Fallback: MVP entity if it's an avatar + if not entity and mode.CurrentMVPEntity then + local mvp = mode.CurrentMVPEntity + local mvpModel = model_for(mvp) + if is_player_avatar(mvp, mvpModel) then + entity = mvp + _G.__FC_BATTLE_LAST_AVATAR = mvp + end + end end elseif phase:GetType().Name == 'AdventurePhase' then entity = phase.CurrentPlayingTeamMember else return nil, nil, nil, false end + if not entity then return nil, nil, nil, true end - local function model_for(candidate) - local ok, result = pcall(function() - local component = candidate and candidate:GetComponent(typeof(CS.RPG.GameCore.CharacterModelComponent)) - return component and component:GetModelGO() - end) - if ok then return result end - return nil - end + local model = model_for(entity) - -- In a transformed turn the Client can report a model-less turn owner; - -- the live battle's MVP entity points to the visible transformed actor. if not model and mode then local fallback = mode.CurrentMVPEntity local fallbackModel = model_for(fallback) - if fallbackModel then entity, model = fallback, fallbackModel end + if fallbackModel and is_player_avatar(fallback, fallbackModel) then + entity, model = fallback, fallbackModel + end end if not model then return nil, nil, nil, true end + local cachedAlive, cachedActive = pcall(function() return _G.__FC_FACE_HEAD and _G.__FC_FACE_ROOT and _G.__FC_FACE_HEAD.gameObject.activeInHierarchy and _G.__FC_FACE_ROOT.gameObject.activeInHierarchy @@ -115,13 +214,13 @@ local function current_facecam_target() if _G.__FC_FACE_ENTITY == entity.RuntimeID and cachedAlive and cachedActive then return _G.__FC_FACE_HEAD, _G.__FC_FACE_ROOT, entity.RuntimeID, true end - local nodes = model:GetComponentsInChildren(typeof(CS.UnityEngine.Transform), true) - for i = 0, nodes.Length - 1 do - if nodes[i].name == 'Head_M' and nodes[i].gameObject.activeInHierarchy then - return nodes[i], model.transform, entity.RuntimeID, true - end + + local head = find_head_bone(model) + if head then + return head, model.transform, entity.RuntimeID, true end - return nil, nil, nil, true + + return nil, model.transform, entity.RuntimeID, true end -- Log only changes, so diagnostics stay useful during long battles. @@ -158,7 +257,9 @@ local function facecam_target(cam) local battleHead, battleRoot, id, tracked = current_facecam_target() if tracked then _G.__FC_FACE_ENTITY = id - return battleHead, battleRoot + if battleHead and battleRoot then + return battleHead, battleRoot + end end local brain = cam:GetComponent(typeof(CS.Cinemachine.CinemachineBrain)) local virtual = brain and brain.ActiveVirtualCamera @@ -179,22 +280,37 @@ end local function update_facecam(cam, settings) local now = Time.unscaledTime or 0 if now >= (_G.__FC_FACE_NEXT or 0) then - _G.__FC_FACE_NEXT = now + 0.15 + _G.__FC_FACE_NEXT = now + 0.08 local ok, head, root, id, tracked = pcall(current_facecam_target) log_facecam_target(ok, head, root, id, tracked) - if ok and tracked and head and root then - _G.__FC_FACE_HEAD, _G.__FC_FACE_ROOT, _G.__FC_FACE_ENTITY = head, root, id + if ok and tracked then + if head and root then + _G.__FC_FACE_HEAD, _G.__FC_FACE_ROOT, _G.__FC_FACE_ENTITY = head, root, id + _G.__FC_FACE_MISSING_SINCE = nil + end end end + local head, root = _G.__FC_FACE_HEAD, _G.__FC_FACE_ROOT local alive, active = pcall(function() return head and root and head.gameObject.activeInHierarchy end) if not alive or not active then - -- Character transformations can briefly unload a model while keeping - -- the same entity ID. Give the replacement time to become available. + -- Character transformations or ultimate cutscenes can briefly unload/hide a model. + -- Hold last valid camera transform only within a short grace period (3s). + -- If character remains unavailable (e.g. battle finished or scene changed), + -- return false so FaceCam automatically deactivates and restores Cinemachine. _G.__FC_FACE_MISSING_SINCE = _G.__FC_FACE_MISSING_SINCE or now - return now - _G.__FC_FACE_MISSING_SINCE < 2 + if now - _G.__FC_FACE_MISSING_SINCE > 3.0 then + return false + end + if _G.__FC_POS and _G.__FC_EULER then + cam.transform.position = _G.__FC_POS + cam.transform.eulerAngles = _G.__FC_EULER + return true + end + return false end _G.__FC_FACE_MISSING_SINCE = nil + local distance = _G.__FC_FACE_DISTANCE or tonumber(settings.distance) or 2 local scroll = Input.mouseScrollDelta.y if scroll ~= 0 then distance = distance - scroll * 0.2 end @@ -203,21 +319,53 @@ local function update_facecam(cam, settings) if scroll ~= 0 then show_ui('[FaceCam: ON] Distance: ' .. string.format('%.1f', distance) .. '') end - local target = head.position - local anchor = _G.__FC_FACE_ANCHOR - if not anchor or anchor.transform.parent ~= head then - if anchor then CS.UnityEngine.Object.Destroy(anchor) end - anchor = GameObject('GaC_FaceCamAnchor') - anchor.transform:SetParent(head, false) - _G.__FC_FACE_ANCHOR = anchor + + -- Target: Center of the face/eyes (~0.08m above neck pivot Head_M) + local eyeOffset = 0.08 + local faceCenter = head.position + Vector3(0, eyeOffset, 0) + + -- In HSR rigs, head.up is aligned with the facial forward axis. + -- Project onto horizontal plane (Y=0) so camera remains level and doesn't tilt/pitch with nodding. + local fwdX = head.up.x + local fwdZ = head.up.z + local fwdMagSq = fwdX * fwdX + fwdZ * fwdZ + local fwd + if fwdMagSq > 0.001 then + local invMag = 1.0 / math.sqrt(fwdMagSq) + fwd = Vector3(fwdX * invMag, 0, fwdZ * invMag) + elseif root then + local rX = root.forward.x + local rZ = root.forward.z + local rMagSq = rX * rX + rZ * rZ + if rMagSq > 0.001 then + local invR = 1.0 / math.sqrt(rMagSq) + fwd = Vector3(rX * invR, 0, rZ * invR) + else + fwd = Vector3(0, 0, 1) + end + else + fwd = Vector3(0, 0, 1) end - -- The observed game rig uses Head_M's local up axis as its facial - -- direction; model-root forward does not follow the animated pose. - local direction = head.up - anchor.transform.position = target + direction * distance - anchor.transform.rotation = CS.UnityEngine.Quaternion.LookRotation(target - anchor.transform.position, Vector3.up) - cam.transform.position = anchor.transform.position - cam.transform.rotation = anchor.transform.rotation + + local desiredPos = faceCenter + fwd * distance + local lookDir = faceCenter - desiredPos + local desiredRot = CS.UnityEngine.Quaternion.LookRotation(lookDir, Vector3.up) + + -- Smooth interpolation to remove micro-jitter while tracking + local dt = Time.unscaledDeltaTime or 0.016 + if dt <= 0 or dt > 0.1 then dt = 0.016 end + local smoothSpeed = 16.0 + local factor = math.min(1.0, dt * smoothSpeed) + + if _G.__FC_LAST_TRACKED_ENTITY ~= _G.__FC_FACE_ENTITY or not _G.__FC_POS then + _G.__FC_LAST_TRACKED_ENTITY = _G.__FC_FACE_ENTITY + cam.transform.position = desiredPos + cam.transform.rotation = desiredRot + else + cam.transform.position = Vector3.Lerp(cam.transform.position, desiredPos, factor) + cam.transform.rotation = CS.UnityEngine.Quaternion.Slerp(cam.transform.rotation, desiredRot, factor) + end + _G.__FC_POS = cam.transform.position _G.__FC_EULER = cam.transform.eulerAngles return true @@ -233,6 +381,19 @@ local function set_cinemachine_enabled(cam, enabled) pcall(function() if state.brain then state.brain.enabled = state.enabled end end) _G.__FC_BRAINS[id] = nil end + pcall(function() + if cam then + local brain = cam:GetComponent(typeof(CS.Cinemachine.CinemachineBrain)) + if brain then brain.enabled = true end + cam.enabled = true + end + local mainCam = CS.UnityEngine.Camera.main + if mainCam and mainCam ~= cam then + local brain = mainCam:GetComponent(typeof(CS.Cinemachine.CinemachineBrain)) + if brain then brain.enabled = true end + mainCam.enabled = true + end + end) return end pcall(function() @@ -255,7 +416,7 @@ _G.__FC_MOUSE_GATE = _G.__FC_MOUSE_GATE or {modules={}, bindings={}} local function restore_mouse_gate() local gate = _G.__FC_MOUSE_GATE for _, state in ipairs(gate.modules) do - pcall(function() state.module.allowMouseInput = state.allowed end) + pcall(function() state.module.allowMouseInput = true end) end for _, state in ipairs(gate.bindings) do pcall(function() @@ -265,6 +426,13 @@ local function restore_mouse_gate() end end) end + pcall(function() + local eventSystem = CS.UnityEngine.EventSystems.EventSystem.current + if eventSystem then + local module = eventSystem:GetComponent(typeof(CS.InControl.InControlInputModule)) + if module then module.allowMouseInput = true end + end + end) gate.modules, gate.bindings, gate.sets = {}, {}, {} gate.active = false end @@ -305,7 +473,11 @@ local function update_mouse_gate() if not recorded then gate.modules[#gate.modules + 1] = {module=module, allowed=module.allowMouseInput} end - module.allowMouseInput = false + if Input.GetMouseButton(1) then + module.allowMouseInput = false + else + module.allowMouseInput = true + end end end gate.sets = gate.sets or {} @@ -352,7 +524,7 @@ function _G.__FREECAM_TICK() return end _G.__FC_INPUT_FRAME = frame - if Input.GetKeyDown(KeyCode.Tab) then + if Input.GetKeyDown(KeyCode.F8) or Input.GetKeyDown(KeyCode.Insert) then _G.__FREECAM_ENABLED = not _G.__FREECAM_ENABLED _G.__FC_LAST_TIME = nil local cam = get_active_cam() @@ -362,13 +534,11 @@ function _G.__FREECAM_TICK() _G.__FC_FACE_DISTANCE = tonumber(settings.distance) or 2 local head, root if cam then head, root = facecam_target(cam) end - if not head or not root then - _G.__FREECAM_ENABLED = false - show_ui("FaceCam: character head unavailable") - CS.UnityEngine.Debug.Log("[FaceCam] active camera follow target has no humanoid head") - return + if head and root then + _G.__FC_FACE_HEAD, _G.__FC_FACE_ROOT = head, root + else + _G.__FC_FACE_HEAD, _G.__FC_FACE_ROOT = nil, nil end - _G.__FC_FACE_HEAD, _G.__FC_FACE_ROOT = head, root end if cam then _G.__FC_POS = cam.transform.position @@ -376,9 +546,9 @@ function _G.__FREECAM_TICK() set_cinemachine_enabled(cam, false) end if settings.mode == "facecam" then - show_ui("[FaceCam: ON] | Tab: Close") + show_ui("[FaceCam: ON] | F8/Insert: Close") else - show_ui("[FreeCam: ON] Speed: " .. string.format("%.1f", _G.__FREECAM_SPEED) .. " | Arrows: Fly | PgUp/PgDn: Up/Down | RMB: Look") + show_ui("[FreeCam: ON] Speed: " .. string.format("%.1f", _G.__FREECAM_SPEED) .. " | Arrows: Fly | PgUp/PgDn: Up/Down | RMB: Look | F8/Insert: Close") end else local settings = _G.__GAC_CAMERA_SETTINGS or {mode="freecam"} @@ -387,6 +557,9 @@ function _G.__FREECAM_TICK() end _G.__FC_FACE_HEAD, _G.__FC_FACE_ROOT = nil, nil _G.__FC_FACE_ENTITY = nil + _G.__FC_LAST_TRACKED_ENTITY = nil + _G.__FC_BATTLE_LAST_ATTACKER = nil + _G.__FC_BATTLE_LAST_AVATAR = nil _G.__FC_FACE_DISTANCE, _G.__FC_FACE_LOCAL_FORWARD = nil, nil _G.__FC_FACE_MISSING_SINCE = nil if _G.__FC_FACE_ANCHOR then CS.UnityEngine.Object.Destroy(_G.__FC_FACE_ANCHOR); _G.__FC_FACE_ANCHOR = nil end @@ -409,6 +582,43 @@ function _G.__FREECAM_TICK() CS.UnityEngine.Debug.Log("[FreeCam] Mouse gate error: " .. tostring(gateError)) end + -- Detect game phase changes (e.g. Battle <-> Adventure or Scene loading transitions) + -- Immediately restore Cinemachine and deactivate FreeCam/FaceCam so the camera does not + -- get pinned in the void (black screen) when returning to overworld scenes like Dewlight Pavilion. + pcall(function() + local manager = CS.RPG.Client.GlobalVars.s_GamePhaseManager + local phaseField = _G.__FC_PHASE_FIELD + if not phaseField then + local flags = CS.System.Enum.ToObject(typeof(CS.System.Reflection.BindingFlags), 60) + phaseField = typeof(CS.RPG.Client.GamePhaseManager):GetField("_CurrentPhase", flags) + _G.__FC_PHASE_FIELD = phaseField + end + local currentPhase = manager and phaseField and phaseField:GetValue(manager) + local currentPhaseName = currentPhase and currentPhase:GetType().Name or "" + if _G.__FC_LAST_PHASE_NAME and _G.__FC_LAST_PHASE_NAME ~= currentPhaseName and currentPhaseName ~= "" then + local cam = get_active_cam() + set_cinemachine_enabled(cam, true) + restore_mouse_gate() + _G.__FREECAM_ENABLED = false + _G.__FC_POS = nil + _G.__FC_EULER = nil + _G.__FC_FACE_HEAD, _G.__FC_FACE_ROOT = nil, nil + _G.__FC_FACE_ENTITY = nil + _G.__FC_LAST_TRACKED_ENTITY = nil + _G.__FC_BATTLE_LAST_ATTACKER = nil + _G.__FC_BATTLE_LAST_AVATAR = nil + _G.__FC_FACE_DISTANCE, _G.__FC_FACE_LOCAL_FORWARD = nil, nil + _G.__FC_FACE_MISSING_SINCE = nil + if _G.__FC_FACE_ANCHOR then + CS.UnityEngine.Object.Destroy(_G.__FC_FACE_ANCHOR) + _G.__FC_FACE_ANCHOR = nil + end + end + if currentPhaseName ~= "" then + _G.__FC_LAST_PHASE_NAME = currentPhaseName + end + end) + if _G.__FREECAM_ENABLED then local cam = get_active_cam() if cam then @@ -419,6 +629,9 @@ function _G.__FREECAM_TICK() _G.__FREECAM_ENABLED = false _G.__FC_FACE_HEAD, _G.__FC_FACE_ROOT = nil, nil _G.__FC_FACE_ENTITY = nil + _G.__FC_LAST_TRACKED_ENTITY = nil + _G.__FC_BATTLE_LAST_ATTACKER = nil + _G.__FC_BATTLE_LAST_AVATAR = nil _G.__FC_FACE_DISTANCE, _G.__FC_FACE_LOCAL_FORWARD = nil, nil _G.__FC_FACE_MISSING_SINCE = nil if _G.__FC_FACE_ANCHOR then CS.UnityEngine.Object.Destroy(_G.__FC_FACE_ANCHOR); _G.__FC_FACE_ANCHOR = nil end @@ -441,11 +654,11 @@ function _G.__FREECAM_TICK() -- Speed adjustment (+ / -) if Input.GetKeyDown(KeyCode.Equals) or Input.GetKeyDown(KeyCode.KeypadPlus) then _G.__FREECAM_SPEED = math.min(300.0, _G.__FREECAM_SPEED * 1.5) - show_ui("[FreeCam: ON] Speed: " .. string.format("%.1f", _G.__FREECAM_SPEED) .. " | Arrows: Fly | PgUp/PgDn: Up/Down | RMB: Look") + show_ui("[FreeCam: ON] Speed: " .. string.format("%.1f", _G.__FREECAM_SPEED) .. " | Arrows: Fly | PgUp/PgDn: Up/Down | RMB: Look | F8/Insert: Close") end if Input.GetKeyDown(KeyCode.Minus) or Input.GetKeyDown(KeyCode.KeypadMinus) then _G.__FREECAM_SPEED = math.max(0.5, _G.__FREECAM_SPEED / 1.5) - show_ui("[FreeCam: ON] Speed: " .. string.format("%.1f", _G.__FREECAM_SPEED) .. " | Arrows: Fly | PgUp/PgDn: Up/Down | RMB: Look") + show_ui("[FreeCam: ON] Speed: " .. string.format("%.1f", _G.__FREECAM_SPEED) .. " | Arrows: Fly | PgUp/PgDn: Up/Down | RMB: Look | F8/Insert: Close") end -- Raw Unity mouse input remains available to the camera while diff --git a/third_party/currency-war-sr/LICENSE b/third_party/currency-war-sr/LICENSE new file mode 100644 index 0000000..45cbc5f --- /dev/null +++ b/third_party/currency-war-sr/LICENSE @@ -0,0 +1,235 @@ +GNU AFFERO GENERAL PUBLIC LICENSE +Version 3, 19 November 2007 + +Copyright (C) 2007 Free Software Foundation, Inc. + +Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. + + Preamble + +The GNU Affero General Public License is a free, copyleft license for software and other kinds of works, specifically designed to ensure cooperation with the community in the case of network server software. + +The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, our General Public Licenses are intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. + +When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. + +Developers that use our General Public Licenses protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License which gives you legal permission to copy, distribute and/or modify the software. + +A secondary benefit of defending all users' freedom is that improvements made in alternate versions of the program, if they receive widespread use, become available for other developers to incorporate. Many developers of free software are heartened and encouraged by the resulting cooperation. However, in the case of software used on network servers, this result may fail to come about. The GNU General Public License permits making a modified version and letting the public access it on a server without ever releasing its source code to the public. + +The GNU Affero General Public License is designed specifically to ensure that, in such cases, the modified source code becomes available to the community. It requires the operator of a network server to provide the source code of the modified version running there to the users of that server. Therefore, public use of a modified version, on a publicly accessible server, gives the public access to the source code of the modified version. + +An older license, called the Affero General Public License and published by Affero, was designed to accomplish similar goals. This is a different license, not a version of the Affero GPL, but Affero has released a new version of the Affero GPL which permits relicensing under this license. + +The precise terms and conditions for copying, distribution and modification follow. + + TERMS AND CONDITIONS + +0. Definitions. + +"This License" refers to version 3 of the GNU Affero General Public License. + +"Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. + +"The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. + +To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. + +A "covered work" means either the unmodified Program or a work based on the Program. + +To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. + +To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. + +An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. + +1. Source Code. +The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. + +A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. + +The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. + +The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those +subprograms and other parts of the work. + +The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. + +The Corresponding Source for a work in source code form is that same work. + +2. Basic Permissions. +All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. + +You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. + +Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. + +3. Protecting Users' Legal Rights From Anti-Circumvention Law. +No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. + +When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. + +4. Conveying Verbatim Copies. +You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. + +You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. + +5. Conveying Modified Source Versions. +You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". + + c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. + +A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. + +6. Conveying Non-Source Forms. +You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: + + a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. + + d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. + +A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. + +A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. + +"Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. + +If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). + +The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. + +Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. + +7. Additional Terms. +"Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. + +When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. + +Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or authors of the material; or + + e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. + +All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. + +If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. + +Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. + +8. Termination. + +You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). + +However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. + +Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. + +Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. + +9. Acceptance Not Required for Having Copies. + +You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. + +10. Automatic Licensing of Downstream Recipients. + +Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. + +An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. + +You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. + +11. Patents. + +A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". + +A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. + +Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. + +In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. + +If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. + +If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. + +A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. + +Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. + +12. No Surrender of Others' Freedom. + +If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. + +13. Remote Network Interaction; Use with the GNU General Public License. + +Notwithstanding any other provision of this License, if you modify the Program, your modified version must prominently offer all users interacting with it remotely through a computer network (if your version supports such interaction) an opportunity to receive the Corresponding Source of your version by providing access to the Corresponding Source from a network server at no charge, through some standard or customary means of facilitating copying of software. This Corresponding Source shall include the Corresponding Source for any work covered by version 3 of the GNU General Public License that is incorporated pursuant to the following paragraph. + +Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the work with which it is combined will remain governed by version 3 of the GNU General Public License. + +14. Revised Versions of this License. + +The Free Software Foundation may publish revised and/or new versions of the GNU Affero General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU Affero General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU Affero General Public License, you may choose any version ever published by the Free Software Foundation. + +If the Program specifies that a proxy can decide which future versions of the GNU Affero General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. + +Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. + +15. Disclaimer of Warranty. + +THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +16. Limitation of Liability. + +IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +17. Interpretation of Sections 15 and 16. + +If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. + +END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + +If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. + +To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. + + Remielle + Copyright (C) 2026 ReversedRooms + + This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + +If your software can interact with users remotely through a computer network, you should also make sure that it provides a way for users to get its source. For example, if your program is a web application, its interface could display a "Source" link that leads users to an archive of the code. There are many ways you could offer source, and different solutions will be better for different programs; see section 13 for the specific requirements. + +You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU AGPL, see . diff --git a/third_party/currency-war-sr/NOTICE.md b/third_party/currency-war-sr/NOTICE.md new file mode 100644 index 0000000..91e0307 --- /dev/null +++ b/third_party/currency-war-sr/NOTICE.md @@ -0,0 +1,19 @@ +# Currency-War-SR attribution + +Copyright (C) 2026 xaodev. Upstream declares **AGPL-3.0-only**. +The upstream license is retained in `LICENSE` in this directory. + +Source: https://gaytea.cod-algieba.ts.net/xaodev/Currency-War-SR + +Imported revision: `7d342af29fc9e4fec49bf5202c0c8207c1ba9f71`. + +The adapter in `internal/common/currency_wars_xaodev.go` and +`internal/gameserver/currency_wars_xaodev.go` adapts the branch-table lookup, +Encounter generation policy, difficulty projection and node EXP selection. +`resources/CurrencyWarsXaodev.json` contains the normalized subset of upstream +Client data, input file hashes and an explicitly labelled server-authored +generation policy. The integration call sites are recorded in +`docs/CW-XAODEV-CLAUDE-HANDOFF.md`. + +This import is a partial port. It does not contain the complete PearlSR +server, protocol module or a gameplay verification claim. diff --git a/tools/import_xaodev_cw.py b/tools/import_xaodev_cw.py new file mode 100644 index 0000000..9e746a2 --- /dev/null +++ b/tools/import_xaodev_cw.py @@ -0,0 +1,214 @@ +"""Import the revision-bound subset used by our CW adapter; no player files change.""" +import argparse +import hashlib +import json +import pathlib +import subprocess + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--source", type=pathlib.Path, required=True) + parser.add_argument("--root", type=pathlib.Path, default=pathlib.Path(__file__).resolve().parents[1]) + args = parser.parse_args() + source, root = args.source.resolve(), args.root.resolve() + catalog = json.loads((root / "resources/CurrencyWarsCatalog.json").read_text(encoding="utf-8")) + inputs = [ + "resources/grid_fight/elite_rewards.json", + "resources/grid_fight/rounds.raw.json", + "resources/grid_fight/score.json", + "resources/grid_fight/offering_levels.json", + "resources/grid_fight/traits.json", + "resources/grid_fight/trait_effects.json", + "resources/grid_fight/augments.json", + "resources/grid_fight/orb_rewards.json", + "gameserver/src/services/grid_fight.zig", + "gameserver/src/services/grid_fight_rewards.zig", + "gameserver/src/services/grid_fight_strategy.zig", + "gameserver/src/services/grid_fight_traits.zig", + "gameserver/src/services/grid_fight_bonds.zig", + "gameserver/src/services/grid_fight_talent.zig", + ] + hashes = {name: hashlib.sha256((source / name).read_bytes()).hexdigest() for name in inputs} + elite = json.loads((source / inputs[0]).read_text(encoding="utf-8")) + rounds = json.loads((source / inputs[1]).read_text(encoding="utf-8")) + score_raw = json.loads((source / inputs[2]).read_text(encoding="utf-8")) + offering_raw = json.loads((source / inputs[3]).read_text(encoding="utf-8")) + traits_raw = json.loads((source / inputs[4]).read_text(encoding="utf-8")) + effects_raw = json.loads((source / inputs[5]).read_text(encoding="utf-8")) + augments_raw = json.loads((source / inputs[6]).read_text(encoding="utf-8")) + orbs_raw = json.loads((source / inputs[7]).read_text(encoding="utf-8")) + + assembly = catalog["provenance"]["game_assembly_sha256"] + if elite["source"]["game_assembly_sha256"] != assembly: + raise ValueError("source Client assembly does not match installed CW catalog") + + commit = subprocess.check_output(["git", "-C", str(source), "rev-parse", "HEAD"], text=True).strip() + + active = {row["ID"] for row in catalog["active_stage_wrappers"]} + divisions = [{key: row.get(key, 0) for key in + ("DivisionID", "EnemyDifficultyLevel", "BinaryNodeDiffAddRule")} + for row in rounds["GridFightDivisionStageRow"] if row["DivisionID"] in active] + if {row["DivisionID"] for row in divisions} != active or len(divisions) != len(active): + raise ValueError("source division table does not cover captured stages uniquely") + for row in divisions: + captured = next(stage for stage in catalog["active_stage_wrappers"] if stage["ID"] == row["DivisionID"]) + if row["EnemyDifficultyLevel"] != captured["EnemyHardLevel"]: + raise ValueError("source division difficulty differs from installed capture") + + output_cw = { + "format_version": 1, + "target_client_version": catalog["client_version"], + "target_assembly_sha256": assembly, + "provenance": { + "repository": "https://gaytea.cod-algieba.ts.net/xaodev/Currency-War-SR", + "commit": commit, + "license": "AGPL-3.0-only", + "inputs_sha256": hashes, + "native_source": elite["source"], + "adapted_functions": ["eliteOffers", "powerfulQuality", "encounter", "branchDropPreview", "sendEliteSelected", "roundExp"] + }, + "node_rules": elite["node_rules"], + "diff_add": elite["diff_add"], + "difficulty_levels": [{"chapter": row["chapter"], "level": row["level"]} for row in elite["difficulty"]], + "divisions": sorted(divisions, key=lambda row: row["DivisionID"]), + "generation_policy": { + "source": "grid_fight.zig encounter fallback for EliteBranch nodes without a node_profile; server-authored, not official wave data", + "monster_count": 3, "monster_tier": 2, "star_cap": 3, + "normal_quality": 1, "powerful_qualities": [2, 3, 4], + "seed_policy": "sha256 of run id, route, chapter and section; local deterministic sampling, not upstream Wyhash", + "reward_policy": "preview_base_gold_already_paid_by_settlement", + "extra_rewards_supported": False + }, + "gameplay_verified": False + } + destination = root / "resources/CurrencyWarsXaodev.json" + destination.write_text(json.dumps(output_cw, indent=2, ensure_ascii=False) + "\n", encoding="utf-8") + + # 1. Score & Offering Levels + offering_s2 = [ + {"level": row["Level"], "item_cost": row["ItemCost"], "reward_id": row["RewardID"]} + for row in offering_raw if row.get("TypeID") == 16 + ] + offering_s2.sort(key=lambda x: x["level"]) + score_output = { + "format_version": 1, + "target_client_version": catalog["client_version"], + "target_assembly_sha256": assembly, + "provenance": { + "repository": "https://gaytea.cod-algieba.ts.net/xaodev/Currency-War-SR", + "commit": commit, + "license": "AGPL-3.0-only", + "inputs_sha256": { + "score.json": hashes["resources/grid_fight/score.json"], + "offering_levels.json": hashes["resources/grid_fight/offering_levels.json"], + } + }, + "weekly_cap": score_raw.get("weekly_cap", 18000), + "weekly_ranks": score_raw.get("weekly_ranks", []), + "divisions": score_raw.get("divisions", {}), + "rules": score_raw.get("rules", {}), + "offering_levels": offering_s2 + } + (root / "resources/CurrencyWarsScore.json").write_text( + json.dumps(score_output, indent=2, ensure_ascii=False) + "\n", encoding="utf-8" + ) + + # 2. Traits & Effects + traits_list = [ + { + "id": t["id"], + "name": t.get("name", ""), + "thresholds": t.get("thresholds", []), + "activation_type": t.get("raw", {}).get("base", {}).get("ActivationType", 1), + "trait_type": t.get("raw", {}).get("base", {}).get("TraitType", 0), + "season_id": t.get("raw", {}).get("base", {}).get("SeasonID", 1) + } + for t in traits_raw + ] + traits_list.sort(key=lambda x: x["id"]) + traits_output = { + "format_version": 1, + "target_client_version": catalog["client_version"], + "target_assembly_sha256": assembly, + "provenance": { + "repository": "https://gaytea.cod-algieba.ts.net/xaodev/Currency-War-SR", + "commit": commit, + "license": "AGPL-3.0-only", + "inputs_sha256": { + "traits.json": hashes["resources/grid_fight/traits.json"], + "trait_effects.json": hashes["resources/grid_fight/trait_effects.json"], + } + }, + "traits": traits_list, + "effects": effects_raw.get("effects", {}), + "constants": effects_raw.get("constants", {}) + } + (root / "resources/CurrencyWarsTraits.json").write_text( + json.dumps(traits_output, indent=2, ensure_ascii=False) + "\n", encoding="utf-8" + ) + + # 3. Augments + augments_list = [ + { + "id": a["id"], + "quality": a.get("quality", 1), + "chapters": a.get("chapters", []), + "oc": a.get("oc", False), + "grants": a.get("grants", []), + "grants_complete": a.get("grants_complete", True) + } + for a in augments_raw.get("augments", []) + ] + augments_list.sort(key=lambda x: x["id"]) + augments_output = { + "format_version": 1, + "target_client_version": catalog["client_version"], + "target_assembly_sha256": assembly, + "provenance": { + "repository": "https://gaytea.cod-algieba.ts.net/xaodev/Currency-War-SR", + "commit": commit, + "license": "AGPL-3.0-only", + "inputs_sha256": { + "augments.json": hashes["resources/grid_fight/augments.json"], + } + }, + "season_id": augments_raw.get("season_id", 2), + "augments": augments_list + } + (root / "resources/CurrencyWarsAugments.json").write_text( + json.dumps(augments_output, indent=2, ensure_ascii=False) + "\n", encoding="utf-8" + ) + + # 4. Node Drops + node_drops_output = { + "format_version": 1, + "target_client_version": catalog["client_version"], + "target_assembly_sha256": assembly, + "provenance": { + "repository": "https://gaytea.cod-algieba.ts.net/xaodev/Currency-War-SR", + "commit": commit, + "license": "AGPL-3.0-only", + "inputs_sha256": { + "orb_rewards.json": hashes["resources/grid_fight/orb_rewards.json"], + } + }, + "node_drops": orbs_raw.get("node_drops", {}) + } + (root / "resources/CurrencyWarsNodeDrops.json").write_text( + json.dumps(node_drops_output, indent=2, ensure_ascii=False) + "\n", encoding="utf-8" + ) + + license_dir = root / "third_party/currency-war-sr" + license_dir.mkdir(parents=True, exist_ok=True) + (license_dir / "LICENSE").write_bytes((source / "LICENSE").read_bytes()) + print(f"Imported CW resources:") + print(f" - CurrencyWarsXaodev.json: {len(output_cw['node_rules'])} branch rules, {len(divisions)} divisions") + print(f" - CurrencyWarsScore.json: {len(score_output['divisions'])} score divisions, {len(offering_s2)} offering levels") + print(f" - CurrencyWarsTraits.json: {len(traits_list)} traits") + print(f" - CurrencyWarsAugments.json: {len(augments_list)} augments") + print(f" - CurrencyWarsNodeDrops.json: {len(node_drops_output['node_drops'])} drop rules") + + +if __name__ == "__main__": + main()