├── .github
└── workflows
│ └── cache.yml
├── README.md
├── data
├── attributes.json
├── characters
│ ├── aalto.json
│ ├── baizhi.json
│ ├── calcharo.json
│ ├── changli.json
│ ├── chixia.json
│ ├── danjin.json
│ ├── encore.json
│ ├── jianxin.json
│ ├── jinhsi.json
│ ├── jiyan.json
│ ├── lingyang.json
│ ├── mortefi.json
│ ├── rover.json
│ ├── sanhua.json
│ ├── shorekeeper.json
│ ├── taoqi.json
│ ├── verina.json
│ ├── xiangli_yao.json
│ ├── yangyang.json
│ ├── yinlin.json
│ ├── youhu.json
│ ├── yuanwu.json
│ └── zhezhi.json
├── codes.json
├── echoes.json
├── echoes
│ ├── stats.json
│ └── substats.json
├── sonatas.json
└── weapons.json
├── go.mod
├── go.sum
├── handlers
├── attribute_handler.go
├── character_handler.go
├── code_handler.go
├── common_handler.go
├── echo_handler.go
├── sonata_handler.go
├── stat_handler.go
├── substat_handler.go
└── weapon_handler.go
├── main.go
├── models
├── attribute.go
├── character.go
├── code.go
├── echo.go
├── emoji.go
├── sonata.go
├── stat.go
├── substat.go
└── weapon.go
└── utils
├── loaders.go
└── middleware.go
/.github/workflows/cache.yml:
--------------------------------------------------------------------------------
1 | name: cache purge
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | paths-ignore:
8 | - '**/README.md'
9 |
10 | jobs:
11 | build:
12 | runs-on: ubuntu-latest
13 | steps:
14 | - name: purge cache
15 | run: |
16 | curl -X POST "https://api.cloudflare.com/client/v4/zones/${{ secrets.CF_ZONE_ID }}/purge_cache" \
17 | -H "Authorization: Bearer ${{ secrets.CF_API_TOKEN }}" \
18 | -H "Content-Type: application/json" \
19 | --data '{"purge_everything":true}'
20 |
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
I AM TAKING A BREAK, IF YOU WANT TO HELP, PULL REQUEST ON API
2 |
3 | # 🔗 Unofficial API for Wuthering Waves on [**api.resonance.rest**](https://api.resonance.rest) - made with [**Go**](https://go.dev/), stored data in **JSON** and deployed on [**Railway**](https://railway.app).
4 |
5 | ### This is **not** a final version, I need people to complete the game data, if you are interested, write me on [**Telegram**](https://t.me/whosneksio).
6 |
7 |
8 | # API Reference
9 |
10 | ## Base URL
11 |
12 | ```http
13 | https://api.resonance.rest/
14 | ```
15 |
16 | ## Characters
17 |
18 | #### Get character list
19 |
20 | ```http
21 | GET https://api.resonance.rest/characters
22 | ```
23 |
24 | #### Get character's data
25 |
26 | ```http
27 | GET https://api.resonance.rest/characters/:name
28 | ```
29 |
30 | | Parameter | Type | Description |
31 | | :-------- | :------- | :----------------------------------- |
32 | | `name` | `string` | **Required** · name of a character |
33 |
34 | #### Get a character's image
35 |
36 | ```http
37 | GET https://api.resonance.rest/characters/:name/:type
38 | ```
39 |
40 | | Parameter | Type | Description |
41 | | :-------- | :------- | :----------------------------------------------|
42 | | `name` | `string` | **Required** · name of a character |
43 | | `type` | `string` | **Required** · `icon`, `portrait` or `circle` |
44 |
45 | ## Emojis
46 |
47 | #### Get character's emoji list
48 |
49 | ```http
50 | GET https://api.resonance.rest/characters/:name/emojis
51 | ```
52 |
53 | | Parameter | Type | Description |
54 | | :-------- | :------- | :----------------------------------- |
55 | | `name` | `string` | **Required** · name of a character |
56 |
57 | #### Get the emoji of a character
58 |
59 | ```http
60 | GET https://api.resonance.rest/characters/:name/emojis/:number
61 | ```
62 |
63 | | Parameter | Type | Description |
64 | | :-------- | :------- | :----------------------------------- |
65 | | `name` | `string` | **Required** · name of a character |
66 | | `number` | `int` | **Required** · number of a emoji |
67 |
68 | ## Attributes
69 |
70 | #### Get attribute list
71 |
72 | ```http
73 | GET https://api.resonance.rest/attributes
74 | ```
75 |
76 | #### Get attribute's data
77 |
78 | ```http
79 | GET https://api.resonance.rest/attributes/:name
80 | ```
81 |
82 | | Parameter | Type | Description |
83 | | :-------- | :------- | :----------------------------------- |
84 | | `name` | `string` | **Required** · name of a attribute |
85 |
86 | #### Get a attribute's icon
87 |
88 | ```http
89 | GET https://api.resonance.rest/attributes/:name/icon
90 | ```
91 |
92 | | Parameter | Type | Description |
93 | | :-------- | :------- | :----------------------------------- |
94 | | `name` | `string` | **Required** · name of a attribute |
95 |
96 | ## Weapons
97 |
98 | #### Get weapon list
99 |
100 | ```http
101 | GET https://api.resonance.rest/weapons
102 | ```
103 |
104 | #### Get weapons in type
105 |
106 | ```http
107 | GET https://api.resonance.rest/weapons/:type
108 | ```
109 |
110 | | Parameter | Type | Description |
111 | | :-------- | :------- | :----------------------------------- |
112 | | `type` | `string` | **Required** · type of a weapon |
113 |
114 | #### Get a weapons's data
115 |
116 | ```http
117 | GET https://api.resonance.rest/weapons/:type:/:name
118 | ```
119 |
120 | | Parameter | Type | Description |
121 | | :-------- | :------- | :----------------------------------- |
122 | | `type` | `string` | **Required** · type of a weapon |
123 | | `name` | `string` | **Required** · name of a weapon |
124 |
125 | #### Get a weapons's image
126 |
127 | ```http
128 | GET https://api.resonance.rest/weapons/:type:/:name/icon
129 | ```
130 |
131 | | Parameter | Type | Description |
132 | | :-------- | :------- | :----------------------------------- |
133 | | `type` | `string` | **Required** · type of a weapon |
134 | | `name` | `string` | **Required** · name of a weapon |
135 |
136 | ## Echoes
137 |
138 | #### Get echoes list
139 |
140 | ```http
141 | GET https://api.resonance.rest/echoes
142 | ```
143 |
144 | #### Get echo by name
145 |
146 | ```http
147 | GET https://api.resonance.rest/echoes/:name
148 | ```
149 |
150 | | Parameter | Type | Description |
151 | | :-------- | :------- | :----------------------------------- |
152 | | `name` | `string` | **Required** · name of an echo |
153 |
154 | #### Get sonata effects list
155 |
156 | ```http
157 | GET https://api.resonance.rest/echoes/sonatas
158 | ```
159 |
160 | #### Get sonata effect
161 |
162 | ```http
163 | GET https://api.resonance.rest/echoes/sonatas/:name
164 | ```
165 |
166 | | Parameter | Type | Description |
167 | | :-------- | :------- | :-------------------------------------- |
168 | | `name` | `string` | **Required** · name of an sonata effect |
169 |
170 | #### Get stats list
171 |
172 | ```http
173 | GET https://api.resonance.rest/echoes/stats
174 | ```
175 |
176 | #### Get stat
177 |
178 | ```http
179 | GET https://api.resonance.rest/echoes/stats/:name
180 | ```
181 |
182 | | Parameter | Type | Description |
183 | | :-------- | :------- | :-------------------------------------- |
184 | | `name` | `string` | **Required** · name of an stat |
185 |
186 | #### Get substats list
187 |
188 | ```http
189 | GET https://api.resonance.rest/echoes/substats
190 | ```
191 |
192 | #### Get substat
193 |
194 | ```http
195 | GET https://api.resonance.rest/echoes/substats/:name
196 | ```
197 |
198 | | Parameter | Type | Description |
199 | | :-------- | :------- | :-------------------------------------- |
200 | | `name` | `string` | **Required** · name of an substat |
201 |
202 |
203 |
--------------------------------------------------------------------------------
/data/attributes.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "Fusion",
4 | "characters": [
5 | {"name": "Chixia"},
6 | {"name": "Encore"},
7 | {"name": "Mortefi"},
8 | {"name": "Changli"}
9 | ]
10 | },
11 | {
12 | "name": "Glacio",
13 | "characters": [
14 | {"name": "Baizhi"},
15 | {"name": "Lingyang"},
16 | {"name": "Sanhua"},
17 | {"name": "Zhezhi"}
18 | ]
19 | },
20 | {
21 | "name": "Aero",
22 | "characters": [
23 | {"name": "Aalto"},
24 | {"name": "Jianxin"},
25 | {"name": "Jiyan"},
26 | {"name": "Yangyang"}
27 | ]
28 | },
29 | {
30 | "name": "Electro",
31 | "characters": [
32 | {"name": "Calcharo"},
33 | {"name": "Yinlin"},
34 | {"name": "Yuanwu"},
35 | {"name": "Xiangli Yao"}
36 | ]
37 | },
38 | {
39 | "name": "Spectro",
40 | "characters": [
41 | {"name": "Verina"},
42 | {"name": "Jinhsi"}
43 | ]
44 | },
45 | {
46 | "name": "Havoc",
47 | "characters": [
48 | {"name": "Danjin"},
49 | {"name": "Taoqi"}
50 | ]
51 | }
52 | ]
53 |
--------------------------------------------------------------------------------
/data/characters/aalto.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Aalto",
3 | "quote": "Well, if it isn't my loyal patron! What do you wish to inquire about today?",
4 | "attribute": "Aero",
5 | "weapon": "Pistols",
6 | "rarity": 4,
7 | "class": "Congenital",
8 | "birthplace": "New Federation",
9 | "birthday": "June 11"
10 | }
--------------------------------------------------------------------------------
/data/characters/baizhi.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Baizhi",
3 | "quote": "No, I cannot place my faith in it, but my anticipation stems precisely from this distrust. A claim is always verifiable before it is discredited entirely.",
4 | "attribute": "Glacio",
5 | "weapon": "Rectifier",
6 | "rarity": 4,
7 | "class": "Mutant",
8 | "birthplace": "Huanglong",
9 | "birthday": "September 10"
10 | }
--------------------------------------------------------------------------------
/data/characters/calcharo.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Calcharo",
3 | "quote": "They'll make an offer we like. I'll make sure of it.",
4 | "attribute": "Electro",
5 | "weapon": "Broadblade",
6 | "rarity": 5,
7 | "class": "Natural",
8 | "birthplace": "New Federation",
9 | "birthday": "July 8"
10 | }
--------------------------------------------------------------------------------
/data/characters/changli.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Changli",
3 | "quote": "Eons of time on this vast land, all encapsulated in a humble game... I am fortunate to have you as my opponent.",
4 | "attribute": "Fusion",
5 | "weapon": "Sword",
6 | "rarity": 5,
7 | "class": "Natural",
8 | "birthplace": "Huanglong",
9 | "birthday": "June 6"
10 | }
--------------------------------------------------------------------------------
/data/characters/chixia.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Chixia",
3 | "quote": "Jinzhou Patroller, Chixia. You can always call on me if you ever find yourself in a pickle!",
4 | "attribute": "Fusion",
5 | "weapon": "Pistols",
6 | "rarity": 4,
7 | "class": "Mutant",
8 | "birthday": "April 18"
9 | }
--------------------------------------------------------------------------------
/data/characters/danjin.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Danjin",
3 | "quote": "Wherever I go, I swear to vanquish evil, whether it's out in the daylight or hiding in the dark.",
4 | "attribute": "Havoc",
5 | "weapon": "Sword",
6 | "rarity": 4,
7 | "class": "Mutant",
8 | "birthplace": "Huanglong",
9 | "birthday": "August 31"
10 | }
--------------------------------------------------------------------------------
/data/characters/encore.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Encore",
3 | "quote": "A long, long time ago... Cosmos told Encore about a fun spot! Come on! Let's go check it out!",
4 | "attribute": "Fusion",
5 | "weapon": "Rectifier",
6 | "rarity": 5,
7 | "class": "Congenital",
8 | "birthplace": "New Federation",
9 | "birthday": "March 21"
10 | }
--------------------------------------------------------------------------------
/data/characters/jianxin.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Jianxin",
3 | "quote": "Release the wants, and the mind quiets. Cleanse the thoughts, and the soul clears.",
4 | "attribute": "Aero",
5 | "weapon": "Gauntlet",
6 | "rarity": 5,
7 | "class": "Natural",
8 | "birthplace": "Huanglong",
9 | "birthday": "April 6"
10 | }
--------------------------------------------------------------------------------
/data/characters/jinhsi.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Jinhsi",
3 | "quote": "There's still much to be done. Please rest assured, I am here to guide you through it.",
4 | "attribute": "Spectro",
5 | "weapon": "Broadblade",
6 | "rarity": 5,
7 | "class": "Congenital",
8 | "birthplace": "Huanglong",
9 | "birthday": "March 6"
10 | }
--------------------------------------------------------------------------------
/data/characters/jiyan.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Jiyan",
3 | "quote": "I have never regretted to brave the long night.",
4 | "attribute": "Aero",
5 | "weapon": "Broadblade",
6 | "rarity": 5,
7 | "class": "Mutant",
8 | "birthplace": "Huanglong",
9 | "birthday": "December 14"
10 | }
--------------------------------------------------------------------------------
/data/characters/lingyang.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Lingyang",
3 | "quote": "Awoo\u00e2\u20ac\u201dLion up! Victory is mine at this year's Greens-plucking Tournament!",
4 | "attribute": "Glacio",
5 | "weapon": "Gauntlet",
6 | "rarity": 5,
7 | "class": "Natural",
8 | "birthplace": "Huanglong",
9 | "birthday": "August 8"
10 | }
--------------------------------------------------------------------------------
/data/characters/mortefi.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Mortefi",
3 | "quote": "Fine, since you've done this much for me... Go ahead, tell me the wildest inventions you can think of, and watch me make them happen for you.",
4 | "attribute": "Fusion",
5 | "weapon": "Pistols",
6 | "rarity": 4,
7 | "class": "Mutant",
8 | "birthplace": "New Federation",
9 | "birthday": "November 6"
10 | }
--------------------------------------------------------------------------------
/data/characters/rover.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Rover",
3 | "quote": "Is this the beginning of a new journey? Brimming with novel sounds, and untold stories...",
4 | "attribute": "Multi",
5 | "weapon": "Sword",
6 | "rarity": 5
7 | }
--------------------------------------------------------------------------------
/data/characters/sanhua.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Sanhua",
3 | "quote": "Please don't stay too far away from me. I am confident to not let you get harmed in the slightest.",
4 | "attribute": "Glacio",
5 | "weapon": "Sword",
6 | "rarity": 4,
7 | "class": "Mutant",
8 | "birthplace": "Huanglong",
9 | "birthday": "January 20"
10 | }
--------------------------------------------------------------------------------
/data/characters/shorekeeper.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Shorekeeper",
3 | "quote": "\"The Shorekeeper\"... This name suits me well enough. It aligns with my purpose and drive: they only exist because of you",
4 | "attribute": "Spectro",
5 | "weapon": "Rectifier",
6 | "rarity": 5,
7 | "class": "Unknown",
8 | "birthplace": "Black Shores",
9 | "birthday": "February 27"
10 | }
--------------------------------------------------------------------------------
/data/characters/taoqi.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Taoqi",
3 | "quote": "Phew... All set. Time for a little break.",
4 | "attribute": "Havoc",
5 | "weapon": "Broadblade",
6 | "rarity": 4,
7 | "class": "Natural",
8 | "birthplace": "Huanglong",
9 | "birthday": "February 25"
10 | }
--------------------------------------------------------------------------------
/data/characters/verina.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Verina",
3 | "quote": "Plants talk in a silent and sincere language. I can translate their talk for you, if you want.",
4 | "attribute": "Spectro",
5 | "weapon": "Rectifier",
6 | "rarity": 5,
7 | "class": "Congenital",
8 | "birthplace": "New Federation"
9 | }
--------------------------------------------------------------------------------
/data/characters/xiangli_yao.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Xiangli Yao",
3 | "quote": "As truthseekers, we gauge the universe from within a nutshell. I'd love to hear your insights.",
4 | "attribute": "Electro",
5 | "weapon": "Gauntlet",
6 | "rarity": 5,
7 | "class": "Mutant",
8 | "birthplace": "Huanglong",
9 | "birthday": "Unknown"
10 | }
--------------------------------------------------------------------------------
/data/characters/yangyang.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Yangyang",
3 | "quote": "I hope I can be the one to embrace all that you are, and share with you all that you carry.",
4 | "attribute": "Aero",
5 | "weapon": "Sword",
6 | "rarity": 4,
7 | "class": "Natural",
8 | "birthplace": "Huanglong",
9 | "birthday": "October 11"
10 | }
--------------------------------------------------------------------------------
/data/characters/yinlin.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Yinlin",
3 | "quote": "Name's Yinlin. As for what I do... Shhh, we don't talk about that in public.",
4 | "attribute": "Electro",
5 | "weapon": "Rectifier",
6 | "rarity": 5,
7 | "class": "Congenital",
8 | "birthplace": "Huanglong",
9 | "birthday": "September 17"
10 | }
--------------------------------------------------------------------------------
/data/characters/youhu.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Youhu",
3 | "quote": "Here you go, a special edition of my poetry collection, hand-signed by moi! You are welcome!",
4 | "attribute": "Glacio",
5 | "weapon": "Gauntlets",
6 | "rarity": 4,
7 | "class": "Unknown",
8 | "birthplace": "Huanglong",
9 | "birthday": "October 13"
10 | }
--------------------------------------------------------------------------------
/data/characters/yuanwu.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Yuanwu",
3 | "quote": "Assuming I've thought my decisions through and acted with integrity, I am content.",
4 | "attribute": "Electro",
5 | "weapon": "Gauntlet",
6 | "rarity": 4,
7 | "class": "Natural",
8 | "birthplace": "Huanglong",
9 | "birthday": "October 2"
10 | }
--------------------------------------------------------------------------------
/data/characters/zhezhi.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Zhezhi",
3 | "quote": "What I wanted to say is, the sunlight on you right now is so beautiful. Can I... paint you?",
4 | "attribute": "Glacio",
5 | "weapon": "Congenital",
6 | "rarity": 5,
7 | "class": "Natural",
8 | "birthplace": "Huanglong",
9 | "birthday": "December 31"
10 | }
--------------------------------------------------------------------------------
/data/codes.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "WUTHERINGGIFT",
4 | "reward": "2 Premium resonance potions, 2 Medium revival inhalers, 2 Medium energy bags, 10k Shell credits, 50 Astrite"
5 | },
6 | {
7 | "name": "DCARD3VN7M",
8 | "reward": "5k Shell credits, 5 Medium resonance potions, 5 Medium energy cores"
9 | },
10 | {
11 | "name": "BAHAMUTKXMHM",
12 | "reward": "5k Shell credits, 5 Medium resonance potions, 5 Medium energy cores"
13 | },
14 | {
15 | "name": "PTTMYZSOM",
16 | "reward": "5k Shell credits, 5 Medium resonance potions, 5 Medium energy cores"
17 | }
18 | ]
--------------------------------------------------------------------------------
/data/echoes.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "Aero Predator",
4 | "cost": 1,
5 | "sonataEffects": ["Sierra Gale", "Void Thunder"],
6 | "outline": "Summon an Aero Predator to deal Aero DMG.",
7 | "description": "Summon an Aero Predator that throws a dart forward. The dart will bounce between enemies up to three times, dealing {0}% Aero DMG each time it hits.",
8 | "ranks": [20.7, 23.4, 26.1, 28.8],
9 | "cooldown": "8s"
10 | },
11 | {
12 | "name": "Chirpuff",
13 | "cost": 1,
14 | "sonataEffects": ["Sierra Gale", "Sun-sinking Eclipse"],
15 | "outline": "Summon a Chirpuff to blast a powerful gust of wind. Deals Aero DMG and knocks enemies back.",
16 | "description": "Summon a Chirpuff that self-inflates and blasts a powerful gust of wind forward 3 times. Each blast inflicts {0}% Aero DMG and pushes enemies backwards.",
17 | "ranks": [27.6, 31.2, 34.8, 38.4],
18 | "cooldown": "8s"
19 | },
20 | {
21 | "name": "Cruisewing",
22 | "cost": 1,
23 | "sonataEffects": ["Celestial Light", "Rejuvenating Glow", "Moonlit Clouds"],
24 | "outline": "Summon a Cruisewing to heal friendly units.",
25 | "description": "Summon a Cruisewing that restores HP for all current team characters by {0}% of their Max HPs plus an additional 80 points of HP, up to 4 times.",
26 | "ranks": [1.2, 1.4, 1.6, 1.8],
27 | "cooldown": "8s"
28 | },
29 | {
30 | "name": "Diamondclaw",
31 | "cost": 1,
32 | "sonataEffects": ["Moonlit Clouds", "Lingering Tunes"],
33 | "outline": "Transform into Diamondclaw to enter a Defense Stance. Counterattack to deal Physical DMG.",
34 | "description": "Transform into Crystal Scorpion and enter a Parry State. Counterattack when the Parry State is over, dealing {0}%+{1} Physical DMG.",
35 | "ranks": [
36 | { "2": 34.5, "3": 39.0, "4": 43.5, "5": 48.0 },
37 | { "2": 69, "3": 78, "4": 87, "5": 96 }
38 | ],
39 | "cooldown": "8s"
40 | },
41 | {
42 | "name": "Electro Predator",
43 | "cost": 1,
44 | "sonataEffects": ["Molten Rift", "Void Thunder"],
45 | "outline": "Summon an Electro Predator to fire a rain of arrows, dealing Electro DMG.",
46 | "description": "Summon an Electro Predator to shoot the enemy 5 times. The first 4 shots deals {0}% Electro DMG, and the last deals {1}% Electro DMG.",
47 | "ranks": [
48 | { "2": 12.42, "3": 14.04, "4": 15.66, "5": 17.28 },
49 | { "2": 33.12, "3": 37.44, "4": 41.76, "5": 46.08 }
50 | ],
51 | "cooldown": "8s"
52 | },
53 | {
54 | "name": "Excarat",
55 | "cost": 1,
56 | "sonataEffects": ["Freezing Frost", "Sun-sinking Eclipse"],
57 | "outline": "Transform into Excarat to quickly advance and become immune to damage.",
58 | "description": "Transform into an Excarat and tunnel underground to advance. In this state, you have the ability to change your direction and are immune to damage.",
59 | "ranks": [],
60 | "cooldown": "2s"
61 | },
62 | {
63 | "name": "Fission Junrock",
64 | "cost": 1,
65 | "sonataEffects": ["Void Thunder", "Rejuvenating Glow", "Moonlit Clouds"],
66 | "outline": "Summon a Fission Junrock for healing. If not in combat, it picks up minerals or plants nearby for you.",
67 | "description": "Summon a Fission Junrock. Generate a Resonance Effect that restores 2% HP for friendly units each time. If not in combat, you can pick up minerals or plants nearby.",
68 | "ranks": [],
69 | "cooldown": "15s"
70 | },
71 | {
72 | "name": "Fusion Dreadmane",
73 | "cost": 1,
74 | "sonataEffects": ["Molten Rift", "Rejuvenating Glow"],
75 | "outline": "Summon a Fusion Dreadmane to fiercely strike the enemies, dealing Fusion DMG.",
76 | "description": "Summon a Fusion Dreadmane that fiercely strike's the enemy, dealing {0}%+{1} Fusion DMG.",
77 | "ranks": [
78 | { "2": 23.0, "3": 26.0, "4": 29.0, "5": 32.0 },
79 | { "2": 46, "3": 52, "4": 58, "5": 64 }
80 | ],
81 | "cooldown": "8s"
82 | },
83 | {
84 | "name": "Fusion Prism",
85 | "cost": 1,
86 | "sonataEffects": ["Freezing Frost", "Molten Rift", "Lingering Tunes"],
87 | "outline": "Summon a Fusion Prism that deals Fusion DMG with crystal shards.",
88 | "description": "Summon a Fusion Prism to fire a crystal shard, dealing {0}%+{1} Fusion DMG.",
89 | "ranks": [
90 | { "2": 23.0, "3": 26.0, "4": 29.0, "5": 32.0 },
91 | { "2": 46, "3": 52, "4": 58, "5": 64 }
92 | ],
93 | "cooldown": "8s"
94 | },
95 | {
96 | "name": "Fusion Warrior",
97 | "cost": 1,
98 | "sonataEffects": ["Molten Rift", "Void Thunder", "Sierra Gale"],
99 | "outline": "Transform into Fusion Warrior to block enemy attacks. Deal Fusion DMG and reduce this Echo Skill's CD when the block is successful.",
100 | "description": "Transform into Fusion Warrior to perform a Counterattack. If the Counterattack is successful, the cooldown time of this skill will be reduced by 70%, and Fusion DMG will be dealt.",
101 | "ranks": [],
102 | "cooldown": "15s"
103 | },
104 | {
105 | "name": "Glacio Predator",
106 | "cost": 1,
107 | "sonataEffects": ["Freezing Frost", "Celestial Light"],
108 | "outline": "Summon a Glacio Predator to throw ice spears and deal Glacio DMG.",
109 | "description": "Summon a Glacio Predator that throws an ice spear, dealing {0}% Glacio DMG on hit. Deal {1}% Glacio DMG up to 10 times during the charging time, and {2}% Glacio DMG when the spear explodes.",
110 | "ranks": [
111 | { "2": 33.12, "3": 37.44, "4": 41.76, "5": 46.08 },
112 | { "2": 3.31, "3": 3.74, "4": 4.18, "5": 4.61 },
113 | { "2": 16.56, "3": 18.72, "4": 20.88, "5": 23.04 }
114 | ],
115 | "cooldown": "8s"
116 | },
117 | {
118 | "name": "Glacio Prism",
119 | "cost": 1,
120 | "sonataEffects": [
121 | "Freezing Frost",
122 | "Sun-sinking Eclipse",
123 | "Moonlit Clouds"
124 | ],
125 | "outline": "Summon a Glacio Prism that deals Glacio DMG with crystal shards.",
126 | "description": "Summon a Glacio Prism that continuously fires three crystal shards, each dealing {0}% Glacio DMG.",
127 | "ranks": [27.6, 31.2, 34.8, 38.4],
128 | "cooldown": "8s"
129 | },
130 | {
131 | "name": "Gulpuff",
132 | "cost": 1,
133 | "sonataEffects": ["Freezing Frost", "Celestial Light"],
134 | "outline": "Summon a Gulpuff to blow bubbles and cause Glacio damage.",
135 | "description": "Summon a Gulpuff that blows bubbles 5 times, each time dealing {0}% Glacio DMG.",
136 | "ranks": [16.56, 18.72, 20.88, 23.04],
137 | "cooldown": "8s"
138 | },
139 | {
140 | "name": "Havoc Prism",
141 | "cost": 1,
142 | "sonataEffects": ["Void Thunder", "Celestial Light", "Sun-sinking Eclipse"],
143 | "outline": "Summon a Havoc Prism that deals Havoc DMG with crystal shards.",
144 | "description": "Summon a Havoc Prism to fire five crystal shards, each dealing {0}% Havoc DMG.",
145 | "ranks": [16.56, 18.72, 20.88, 23.04],
146 | "cooldown": "8s"
147 | },
148 | {
149 | "name": "Havoc Warrior",
150 | "cost": 1,
151 | "sonataEffects": ["Celestial Light", "Sun-sinking Eclipse"],
152 | "outline": "Transform into a Havoc Warrior to continuously deal Havoc DMG.",
153 | "description": "Transform into Havoc Warrior to attack up to 3 times, dealing {0}% Havoc DMG each time.",
154 | "ranks": [123.43, 139.53, 155.63, 171.73],
155 | "cooldown": "15s"
156 | },
157 | {
158 | "name": "Hoartoise",
159 | "cost": 1,
160 | "sonataEffects": ["Freezing Frost", "Celestial Light"],
161 | "outline": "Transform into a Hoartoise and slowly restore HP.",
162 | "description": "Transform into Hoartoise and slowly restore HP. Use the Echo skill again to exit the transformation state.",
163 | "ranks": [],
164 | "cooldown": "2s"
165 | },
166 | {
167 | "name": "Hooscamp",
168 | "cost": 1,
169 | "sonataEffects": ["Sierra Gale", "Lingering Tunes"],
170 | "outline": "Transform into Hooscamp Flinger and pounce, dealing Aero DMG.",
171 | "description": "Transform into Hooscamp Flinger and pounce at the enemies, dealing {0}%+{1} Aero DMG.",
172 | "ranks": [
173 | { "2": 34.5, "3": 39.0, "4": 43.5, "5": 48.0 },
174 | { "2": 69, "3": 78, "4": 87, "5": 96 }
175 | ],
176 | "cooldown": "8s"
177 | },
178 | {
179 | "name": "Sabyr Boar",
180 | "cost": 1,
181 | "sonataEffects": ["Freezing Frost", "Sierra Gale", "Moonlit Clouds"],
182 | "outline": "Summon a Sabyr Boar to charge and deal Physical DMG.",
183 | "description": "Summon a Sabyr Boar to headbutt the enemy into the air, dealing {0}%+{1} Physical DMG.",
184 | "ranks": [
185 | { "2": 23.0, "3": 26.0, "4": 29.0, "5": 32.0 },
186 | { "2": 46, "3": 52, "4": 58, "5": 64 }
187 | ],
188 | "cooldown": "8s"
189 | },
190 | {
191 | "name": "Snip Snap",
192 | "cost": 1,
193 | "sonataEffects": ["Molten Rift", "Rejuvenating Glow", "Lingering Tunes"],
194 | "outline": "Summon a Snip Snap that throws fireballs, dealing Fusion DMG.",
195 | "description": "Summon a Snip Snap that throws fireballs at the enemy, dealing {0}%+{1} Fusion DMG on-hit.",
196 | "ranks": [
197 | { "2": 23.0, "3": 26.0, "4": 29.0, "5": 32.0 },
198 | { "2": 46, "3": 52, "4": 58, "5": 64 }
199 | ],
200 | "cooldown": "8s"
201 | },
202 | {
203 | "name": "Spectro Prism",
204 | "cost": 1,
205 | "sonataEffects": ["Molten Rift", "Void Thunder", "Celestial Light"],
206 | "outline": "Summon a Spectro Prism that deals Spectro DMG with laser.",
207 | "description": "Summon a Spectro Prism to emit a laser that hits the enemy up to 8 times, dealing {0}% Spectro DMG each time.",
208 | "ranks": [10.35, 11.7, 13.05, 14.4],
209 | "cooldown": "8s"
210 | },
211 | {
212 | "name": "Tick Tack",
213 | "cost": 1,
214 | "sonataEffects": [
215 | "Sun-sinking Eclipse",
216 | "Rejuvenating Glow",
217 | "Lingering Tunes"
218 | ],
219 | "outline": "Summon a Tick Tack that deals Havoc DMG and reduces enemy Vibration Strength.",
220 | "description": "Summon a Tick Tack that charges and bites the enemy. The charge from Tick Tack will deal {0}% Havoc DMG to the enemy, and the bite will deal {1}% Havoc DMG to the enemy. Reduces enemy Vibration Strength by up to 5.00% during 5s.",
221 | "ranks": [
222 | { "2": 49.22, "3": 55.64, "4": 62.06, "5": 68.48 },
223 | { "2": 73.83, "3": 83.46, "4": 93.09, "5": 102.72 }
224 | ],
225 | "cooldown": "15s"
226 | },
227 | {
228 | "name": "Traffic Illuminator",
229 | "cost": 1,
230 | "sonataEffects": ["Molten Rift", "Void Thunder", "Sierra Gale"],
231 | "outline": "Summon a Traffic Illuminator to immobilize the enemies.",
232 | "description": "Summon a Traffic Illuminator, immobilizing enemies for up to 1s. The immobilization will be lifted once the enemy is hit.",
233 | "ranks": [],
234 | "cooldown": "15s"
235 | },
236 | {
237 | "name": "Vanguard Junrock",
238 | "cost": 1,
239 | "sonataEffects": ["Void Thunder", "Rejuvenating Glow", "Lingering Tunes"],
240 | "outline": "Summon a Vanguard Junrock that charges forward, dealing Physical DMG to enemies in its path.",
241 | "description": "Summon a Vanguard Junrock that charges forward, dealing {0}%+{1} Physical DMG to enemies in its path.",
242 | "ranks": [
243 | { "2": 23.0, "3": 26.0, "4": 29.0, "5": 32.0 },
244 | { "2": 46, "3": 52, "4": 58, "5": 64 }
245 | ],
246 | "cooldown": "8s"
247 | },
248 | {
249 | "name": "Whiff Whaff",
250 | "cost": 1,
251 | "sonataEffects": ["Sierra Gale", "Rejuvenating Glow", "Moonlit Clouds"],
252 | "outline": "Summon a Whiff Whaff that deals Aero DMG and produces a Low-pressure Zone.",
253 | "description": "Summon a Whiff Whaff that triggers an air explosion, dealing {0}% Aero DMG and produce a Low-pressure Zone. The Low-pressure Zone continuously pulls enemies nearby towards the center for 2s, dealing {1}% Aero DMG up to 6 times.",
254 | "ranks": [
255 | { "2": 36.92, "3": 41.73, "4": 46.55, "5": 51.36 },
256 | { "2": 14.35, "3": 16.22, "4": 18.1, "5": 19.97 }
257 | ],
258 | "cooldown": "15s"
259 | },
260 | {
261 | "name": "Young Geohide Saurian",
262 | "cost": 1,
263 | "sonataEffects": ["Molten Rift", "Void Thunder", "Lingering Tunes"],
264 | "outline": "Transform into a Baby Viridblaze Saurian, rest in place to restore HP.",
265 | "description": "Transform into Baby Viridblaze Saurian to rest in place, and slowly restore HP.",
266 | "ranks": [],
267 | "cooldown": "2s"
268 | },
269 | {
270 | "name": "Young Roseshroom",
271 | "cost": 1,
272 | "sonataEffects": ["Sierra Gale", "Sun-sinking Eclipse"],
273 | "outline": "Summon a Baby Roseshroom to fire a laser, and deal Havoc DMG.",
274 | "description": "Summon a Baby Roseshroom that fires a laser, dealing {0}%+{1} Havoc DMG.",
275 | "ranks": [
276 | { "2": 23.0, "3": 26.0, "4": 29.0, "5": 32.0 },
277 | { "2": 46, "3": 52, "4": 58, "5": 64 }
278 | ],
279 | "cooldown": "8s"
280 | },
281 | {
282 | "name": "Zig Zag",
283 | "cost": 1,
284 | "sonataEffects": ["Celestial Light", "Moonlit Clouds", "Lingering Tunes"],
285 | "outline": "Summon a Zig Zag that deals Spectro DMG, and creates a Stagnation Zone.",
286 | "description": "Summon a Zig Zag that denotates Spectro energy, dealing 34.50%+69 Spectro DMG and creating a Stagnation Zone that lasts 1.8s.",
287 | "ranks": [
288 | { "2": 34.5, "3": 39.0, "4": 43.5, "5": 48.0 },
289 | { "2": 69, "3": 78, "4": 87, "5": 96 }
290 | ],
291 | "cooldown": "15s"
292 | },
293 | {
294 | "name": "Autopuppet Scout",
295 | "cost": 3,
296 | "sonataEffects": ["Freezing Frost", "Celestial Light"],
297 | "outline": "Transform into Autopuppet Scout, to deal Glacio DMG to enemies nearby and generate Ice Walls.",
298 | "description": "Transform into Autopuppet Scout, dealing {0}% Glacio DMG to the surroundings, and generate up to 3 Ice Walls to block off the enemies.",
299 | "ranks": [195.5, 221.0, 246.5, 272],
300 | "cooldown": "15s"
301 | },
302 | {
303 | "name": "Chaserazor",
304 | "cost": 3,
305 | "sonataEffects": ["Sierra Gale", "Moonlit Clouds"],
306 | "outline": "Transform into Carapace to perform spinning attacks, dealing Aero DMG.",
307 | "description": "Transform into Carapace to perform a spinning attack that deals {0}% Aero DMG, followed by a slash that deals {1}% Aero DMG.",
308 | "ranks": [
309 | { "2": 80.5, "3": 91.0, "4": 101.5, "5": 112.0 },
310 | { "2": 120.75, "3": 136.5, "4": 152.25, "5": 168.0 }
311 | ],
312 | "cooldown": "15s"
313 | },
314 | {
315 | "name": "Chasm Guardian",
316 | "cost": 3,
317 | "sonataEffects": ["Rejuvenating Glow", "Lingering Tunes"],
318 | "outline": "Transform into Chasm Guardian to perform a powerful strike that deals Havoc DMG. Lose some HP on hit and then recover HP over time.",
319 | "description": "Transform into Chasm Guardian to perform a Leap Strike that deals {0}% Havoc DMG on hit. Current character loses 10.00% HP after the hit lands. Periodically restore current character's HP after 5s for up to 10.00% of their Max HP.",
320 | "ranks": [196.65, 222.3, 247.95, 273.6],
321 | "cooldown": "15s"
322 | },
323 | {
324 | "name": "Cyan-Feathered Heron",
325 | "cost": 3,
326 | "sonataEffects": ["Sierra Gale", "Celestial Light"],
327 | "outline": "Transform into Cyan-Feathered Heron to charge at the enemies, dealing Aero DMG and interrupt enemy Special Skill.",
328 | "description": "Transform into Cyan-Feathered Heron and charge at the enemies, dealing {0}% Aero DMG. This Echo Skill interrupts enemy Special Skills upon dealing damage.",
329 | "ranks": [170.2, 192.4, 214.6, 236.8],
330 | "cooldown": "15s"
331 | },
332 | {
333 | "name": "Geohide Saurian",
334 | "cost": 3,
335 | "sonataEffects": ["Molten Rift", "Moonlit Clouds"],
336 | "outline": "Summon a Viridblaze Saurian to continuously spit fire, dealing Fusion DMG.",
337 | "description": "Summon a Viridblaze Saurian to continuously spit fire, dealing {0}% Fusion DMG 10 times.",
338 | "ranks": [12.31, 13.91, 15.52, 17.12],
339 | "cooldown": "15s"
340 | },
341 | {
342 | "name": "Havoc Dreadmane",
343 | "cost": 3,
344 | "sonataEffects": ["Molten Rift", "Sun-sinking Eclipse"],
345 | "outline": "Transform into a Havoc Dreadmane to launch tail strikes, dealing Havoc DMG.",
346 | "description": "Transform into Havoc Dreadmane to perform tail strikes up to 2 times. Each strike deals {0}% Havoc DMG. An additional strike will be performed on hit, dealing {1}% Havoc DMG.",
347 | "ranks": [
348 | { "2": 83.84, "3": 94.77, "4": 105.71, "5": 116.64 },
349 | { "2": 55.89, "3": 63.18, "4": 70.47, "5": 77.76 }
350 | ],
351 | "cooldown": "15s"
352 | },
353 | {
354 | "name": "Hoochief",
355 | "cost": 3,
356 | "sonataEffects": ["Sierra Gale", "Rejuvenating Glow"],
357 | "outline": "Transform into Hoochief Cyclone to deal Aero DMG.",
358 | "description": "Transform into Hoochief Cyclone and smack the enemies, dealing {0}% Aero DMG.",
359 | "ranks": [178.8, 208.6, 238.4, 268.2],
360 | "cooldown": "15s"
361 | },
362 | {
363 | "name": "Rocksteady Guardian",
364 | "cost": 3,
365 | "sonataEffects": ["Celestial Light", "Rejuvenating Glow"],
366 | "outline": "Transform into the Rocksteady Guardian to enter a Parry Stance. Counterattack when hit by Special Skill to interrupt enemy Special Skills and deal Spectro DMG based on Max HP.",
367 | "description": "Transform into Rocksteady Guardian and enter a Parry State. Upon being attacked, deal Spectro DMG equal to {0}% of the current character's Max HP, and perform a follow-up attack that deals Spectro DMG equal to {1}% of the current character's Max HP.",
368 | "ranks": [
369 | { "2": 5.96, "3": 6.73, "4": 7.51, "5": 8.29 },
370 | { "2": 5.96, "3": 6.73, "4": 7.51, "5": 8.29 }
371 | ],
372 | "cooldown": "15s"
373 | },
374 | {
375 | "name": "Roseshroom",
376 | "cost": 3,
377 | "sonataEffects": ["Freezing Frost", "Sun-sinking Eclipse"],
378 | "outline": "Summon a Roseshroom to fire a laser, and deal Havoc DMG.",
379 | "description": "Summon a Roseshroom that fires a laser, dealing {0}% Havoc DMG up to 3 times.",
380 | "ranks": [41.02, 46.37, 51.72, 57.07],
381 | "cooldown": "15s"
382 | },
383 | {
384 | "name": "Spearback",
385 | "cost": 3,
386 | "sonataEffects": ["Moonlit Clouds", "Lingering Tunes"],
387 | "outline": "Summon a Spearback to perform continuous claw strikes, dealing Physical DMG.",
388 | "description": "Summon a Spearback to perform 5 consecutive attacks. The first 4 attacks deal {0}% Physical DMG, and the last deals {1}% Physical DMG.",
389 | "ranks": [
390 | { "2": 21.53, "3": 24.34, "4": 27.15, "5": 29.96 },
391 | { "2": 36.92, "3": 41.73, "4": 46.55, "5": 51.36 }
392 | ],
393 | "cooldown": "15s"
394 | },
395 | {
396 | "name": "Stonewall Bracer",
397 | "cost": 3,
398 | "sonataEffects": ["Rejuvenating Glow", "Moonlit Clouds"],
399 | "outline": "Transform into Stonewall Bracer and charge forward. Successful hits grant a follow-up strike and a shield.",
400 | "description": "Transform into Stonewall Bracer and charge forward, dealing {0}% Physical DMG on-hit, then smash to deal {1}% Physical DMG, and gain a shield of 10.00% of current character's Max HP. Use the Echo skill again to exit the transformation state.",
401 | "ranks": [
402 | { "2": 80.96, "3": 91.52, "4": 102.08, "5": 112.64 },
403 | { "2": 121.44, "3": 137.28, "4": 153.12, "5": 168.96 }
404 | ],
405 | "cooldown": "15s"
406 | },
407 | {
408 | "name": "Tambourinist",
409 | "cost": 3,
410 | "sonataEffects": ["Freezing Frost", "Sun-sinking Eclipse"],
411 | "outline": "Summon a Tambourinist to let friendly units inflict additional Havoc DMG.",
412 | "description": "Summon a Tambourinist that periodically emits Melodies of Annihilation. Friendly units hit with Melodies of Annihilation deal an extra Havoc DMG of {0}% with their attacks, up to 10 times.",
413 | "ranks": [10.35, 11.7, 13.05, 14.4],
414 | "cooldown": "15s"
415 | },
416 | {
417 | "name": "Violet-Feathered Heron",
418 | "cost": 3,
419 | "sonataEffects": ["Molten Rift", "Void Thunder"],
420 | "outline": "Transform into Violet-Feathered Heron to block enemy attacks. Counterattack to deal Electro DMG and recover Concerto Energy.",
421 | "description": "Transform into Violet-Feathered Heron and enter a Parry Stance. Counterattack when the Parry Stance is over, dealing {0}% Electro DMG. If attacked during Parry Stance, you can counterattack in advance and additionally recover 5 Concerto Energy.",
422 | "ranks": [207.0, 234.0, 261.0, 288.0],
423 | "cooldown": "15s"
424 | },
425 | {
426 | "name": "Bell-Borne Geochelone",
427 | "class": "Calamity",
428 | "cost": 4,
429 | "sonataEffects": ["Rejuvenating Glow", "Moonlit Clouds"],
430 | "outline": "Activate the protection of Bell-Borne Geochelone, dealing Glacio DMG to nearby enemies based on current character's DEF. Reduce incoming damage, increase damage dealt.",
431 | "description": "Activate the protection of Bell-Borne Geochelone. Deal Glacio DMG based on {0}% of the current character's DEF to nearby enemies, and obtain a Bell-Borne Shield that lasts for 15s. The Bell-Borne Shield provides 50.00% DMG Reduction and 10.0% DMG Boost for the current team members, and disappears after the current character is hit for 3 times.",
432 | "ranks": [104.88, 118.56, 132.24, 145.92],
433 | "cooldown": "20s"
434 | },
435 | {
436 | "name": "Crownless",
437 | "class": "Overlord",
438 | "cost": 4,
439 | "sonataEffects": ["Sun-sinking Eclipse"],
440 | "outline": "Transform into Crownless and relentlessly assault enemies, dealing Havoc DMG. Increase current character's Havoc DMG and Resonance Skill DMG.",
441 | "description": "Transform into Crownless and perform up to 4 consecutive attacks. The first 2 attacks deal {0}% Havoc DMG each, the 3rd attack deals {1}% Havoc DMG 2 times, and the 4th attack deals {2}% Havoc DMG 3 times. After the transformation, increase current character's Havoc DMG by 12.00% and Resonance Skill DMG by 12.00% for 15s.",
442 | "ranks": [
443 | { "2": 96.37, "3": 108.94, "4": 121.51, "5": 134.08 },
444 | { "2": 72.28, "3": 81.71, "4": 91.13, "5": 100.56 },
445 | { "2": 48.19, "3": 54.47, "4": 60.76, "5": 67.04 }
446 | ],
447 | "cooldown": "20s"
448 | },
449 | {
450 | "name": "Feilian Beringal",
451 | "class": "Overlord",
452 | "cost": 4,
453 | "sonataEffects": ["Sierra Gale"],
454 | "outline": "Transform into Feilian Beringal to continuously attack enemies, dealing Aero DMG. Increase current character's Aero DMG and Heavy Attack DMG.",
455 | "description": "Transform into Feilian Beringal to perform a powerful kick. If the kick lands on an enemy, immediately perform a follow up strike. The kick deals {0}% Aero DMG, and the follow-up strike deals {1}% Aero DMG. After the follow-up strike hits, the current character's Aero DMG increases by 12.00%, and the Heavy Attack DMG increases by 12.00% for 15s.",
456 | "ranks": [
457 | { "2": 166.64, "3": 188.37, "4": 210.11, "5": 231.84 },
458 | { "2": 203.67, "3": 230.23, "4": 256.8, "5": 283.36 }
459 | ],
460 | "cooldown": "20s"
461 | },
462 | {
463 | "name": "Impermanence Heron",
464 | "class": "Overlord",
465 | "cost": 4,
466 | "sonataEffects": ["Moonlit Clouds"],
467 | "outline": "Transform into Impermanence Heron to deal Havoc DMG. Long press to spit flames and continuously deal Havoc DMG. Once the attacks hit any enemy, restore current character's Resonance Energy. Increase next character's damage dealt.",
468 | "description": "Transform into Impermanence Heron to fly up and smack down, dealing 223.22% Havoc DMG. Long press to stay as Impermanence Heron and continuously spit flames, each attack dealing 40.05% Havoc DMG. Once the initial attack lands on any enemy, the current character regains 10 Resonance Energy. If the current charact uses their Outro Skill within the next 15s, the next character's damage dealt will be boosted by 12% for 15s.",
469 | "ranks": [
470 | { "2": 223.22, "3": 252.33, "4": 281.45, "5": 310.56 },
471 | { "2": 40.05, "3": 45.28, "4": 50.5, "5": 55.73 }
472 | ],
473 | "cooldown": "20s"
474 | },
475 | {
476 | "name": "Inferno Rider",
477 | "class": "Overlord",
478 | "cost": 4,
479 | "sonataEffects": ["Molten Rift"],
480 | "outline": "Transform into Inferno Rider to perform consecutive strikes, dealing Fusion DMG. Increase current character's Fusion DMG and Basic Attack DMG. Long press Echo Skill to enter the Riding Mode.",
481 | "description": "Transform into Inferno Rider to launch up to 3 consecutive slashes in a row, each slash dealing {0}%, {1}%, and {2}% Fusion DMG respectively. After the final hit, increase the current character's Fusion DMG by 12.00% and Basic Attack DMG by 12.00% for 15s. Long press the Echo Skill to transform into Inferno Rider and enter the Riding Mode. When exiting the Riding Mode, deal 12.00% Fusion DMG to enemies in front.",
482 | "ranks": [
483 | { "2": 174.23, "3": 196.95, "4": 219.68, "5": 242.4 },
484 | { "2": 203.26, "3": 229.78, "4": 256.29, "5": 282.8 },
485 | { "2": 203.26, "3": 229.78, "4": 256.29, "5": 282.8 }
486 | ],
487 | "cooldown": "20s"
488 | },
489 | {
490 | "name": "Lampylumen Myriad",
491 | "class": "Overlord",
492 | "cost": 4,
493 | "sonataEffects": ["Freezing Frost"],
494 | "outline": "Transform into Lampylumen Myriad to continuously attack enemies, dealing Glacio damage. Increase current character's Glacio DMG and Resonance Skill DMG.",
495 | "description": "Transform into Lampylumen Myriad. Perform up to 3 consecutive attacks. Unleash a freezing shock by performing consecutive forward strikes, with the initial two strikes inflicting {0}% and {1}% Glacio DMG respectively, and the final strike dealing {2}% Glacio DMG. Enemies will be frozen on hit. Each shock increases the current character's Glacio DMG by 4.00% and Resonance Skill DMG dealt by 4.00% for 15s, stacking up to 3 times. ",
496 | "ranks": [
497 | { "2": 143.87, "3": 162.63, "4": 181.4, "5": 220.16 },
498 | { "2": 143.87, "3": 162.63, "4": 181.4, "5": 200.16 },
499 | { "2": 191.82, "3": 216.84, "4": 241.86, "5": 266.88 }
500 | ],
501 | "cooldown": "20s"
502 | },
503 | {
504 | "name": "Mech Abomination",
505 | "class": "Overlord",
506 | "cost": 4,
507 | "sonataEffects": ["Lingering Tunes"],
508 | "outline": "Deal Electro DMG to enemies nearby and create Mech Waste that explodes in a while. Increase current character's ATK.",
509 | "description": "Strike the enemies in front, dealing {0}% Electro DMG. Summon Mech Waste to attack enemies. Mech Waste deals {1}% Electro DMG on-hit and explode after a while to deal {2}% Electro DMG. After casting this Echo Skill, increase current character's ATK by 12.00% for 15s. Damage dealt by Mech Waste is considered Outro Skill DMG.",
510 | "ranks": [
511 | { "2": 34.96, "3": 39.52, "4": 44.08, "5": 48.64 },
512 | { "2": 230.0, "3": 260.0, "4": 290.0, "5": 320.0 },
513 | { "2": 115.0, "3": 130.0, "4": 145.0, "5": 160.0 }
514 | ],
515 | "cooldown": "15s"
516 | },
517 | {
518 | "name": "Mourning Aix",
519 | "class": "Overlord",
520 | "cost": 4,
521 | "sonataEffects": ["Celestial Light"],
522 | "outline": "Transform into Mourning Aix to continuously attack enemies, dealing Spectro damage. Increase current character's Spectro DMG and Resonance Liberation DMG.",
523 | "description": "Transform into Mourning Aix and perform 2 consecutive claw attacks, each attack dealing {0}% and {1}% Spectro DMG respectively. After the transformation, increase current character's Spectro DMG by 12.00% and Resonance Liberation DMG by 12.00% for 15s.",
524 | "ranks": [
525 | { "2": 113.16, "3": 127.92, "4": 142.68, "5": 157.44 },
526 | { "2": 169.74, "3": 191.88, "4": 214.02, "5": 236.16 }
527 | ],
528 | "cooldown": "20s"
529 | },
530 | {
531 | "name": "Tempest Mephis",
532 | "class": "Overlord",
533 | "cost": 4,
534 | "sonataEffects": ["Void Thunder"],
535 | "outline": "Transform into Tempest Mephis to continuously attack enemies, dealing Electro damage. Increase current character's Electro DMG & Heavy Attack DMG.",
536 | "description": "Transform into Tempest Mephis to perform tail swing attacks followed by a claw attack. The lightning strike summoned by the tail swing deals {0}% Electro DMG each time, while the claw attack deals {1}% Electro DMG. After the claw hit, increase the current character's Electro DMG by 12.00% and Heavy Attack DMG by 12.00% for 15s.",
537 | "ranks": [
538 | { "2": 73.66, "3": 83.27, "4": 92.87, "5": 102.48 },
539 | { "2": 126.27, "3": 142.74, "4": 159.21, "5": 175.68 }
540 | ],
541 | "cooldown": "20s"
542 | },
543 | {
544 | "name": "Thundering Mephis",
545 | "class": "Overlord",
546 | "cost": 4,
547 | "sonataEffects": ["Void Thunder"],
548 | "outline": "Transform into Thundering Mephis to deal Electro DMG. Increase the current character's Electro DMG and Resonance Liberation DMG.",
549 | "description": "Transform into Thundering Mephis, engaging in a rapid assault of up to 6 strikes. The first 5 strikes deal {0}% Electro DMG each, while the final strike inflicts {1}% Electro DMG, with an additional {2}% Electro DMG from the thunder. After the final hit, increase the current character's Electro DMG by 12.00% and Resonance Liberation DMG by 12.00% for 15s.",
550 | "ranks": [
551 | { "2": 95.31, "3": 107.74, "4": 120.18, "5": 132.61 },
552 | { "2": 131.16, "3": 153.92, "4": 171.68, "5": 189.44 },
553 | { "2": 22.69, "3": 25.65, "4": 28.61, "5": 31.57 }
554 | ],
555 | "cooldown": "20s"
556 | },
557 | {
558 | "name": "Dreamless",
559 | "class": "Calamity",
560 | "cost": 4,
561 | "sonataEffects": ["Sun-sinking Eclipse"],
562 | "outline": "Transform into Dreamless to continuously attack enemies and deal Havoc DMG. This Echo Skill deals more damage after Rover-Havoc casts their Resonance Liberation.",
563 | "description": "Transform into Dreamless and perform 6 consecutive attacks. The first 5 attacks deal {0}% Havoc DMG each, and the last attack deal {1}% Havoc DMG. The DMG of this Echo Skill is increased by 50.00% during the first 5s after Rover-Havoc casts Resonance Liberation: Deadening Abyss.",
564 | "ranks": [
565 | { "2": 38.87, "3": 43.94, "4": 49.01, "5": 54.08 },
566 | { "2": 194.35, "3": 219.7, "4": 245.05, "5": 270.4 }
567 | ],
568 | "cooldown": "20s"
569 | }
570 | ]
571 |
--------------------------------------------------------------------------------
/data/echoes/stats.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "cost": 4,
4 | "name": "Overload/Calamity",
5 | "primary": [
6 | { "name": "HP%", "ranks": [10.6, 14.6, 20.5, 33.0] },
7 | { "name": "ATK%", "ranks": [10.6, 14.6, 20.5, 33.0] },
8 | { "name": "DEF%", "ranks": [13.5, 18.7, 26.0, 41.5] },
9 | { "name": "CRIT Rate", "ranks": [7.1, 9.8, 13.8, 22.0] },
10 | { "name": "CRIT Dmg", "ranks": [14.3, 19.7, 27.7, 44.0] },
11 | { "name": "Healing Bonus", "ranks": [8.5, 11.9, 16.3, 26.0] },
12 | { "name": "Energy Regen", "ranks": [11, 15.6, 22.11, 35.2] }
13 | ],
14 | "secondary": [{ "name": "ATK", "ranks": [46, 68, 92, 150] }]
15 | },
16 | {
17 | "cost": 3,
18 | "name": "Elite",
19 | "primary": [
20 | { "name": "HP%", "ranks": [9.6, 14.0, 18.9, 30.0] },
21 | { "name": "ATK%", "ranks": [9.6, 14.0, 18.9, 30.0] },
22 | { "name": "DEF%", "ranks": [12.3, 17.0, 23.9, 38.0] },
23 | { "name": "Energy Regen", "ranks": [10.0, 14.2, 20.1, 32.0] },
24 | { "name": "[Element] DMG", "ranks": [9.6, 14.0, 18.9, 30.0] }
25 | ],
26 | "secondary": [{ "name": "ATK", "ranks": [31, 44, 63, 100] }]
27 | },
28 | {
29 | "cost": 1,
30 | "name": "Common",
31 | "primary": [
32 | { "name": "HP%", "ranks": [7.2, 10.2, 14.2, 22.5] },
33 | { "name": "ATK%", "ranks": [5.7, 8.1, 11.3, 18.0] },
34 | { "name": "DEF%", "ranks": [5.7, 8.1, 11.3, 18.0] }
35 | ],
36 | "secondary": [{ "name": "HP", "ranks": [296, 516, 957, 2280] }]
37 | }
38 | ]
39 |
--------------------------------------------------------------------------------
/data/echoes/substats.json:
--------------------------------------------------------------------------------
1 | [
2 | { "name": "HP", "min": 260, "max": 740 },
3 | { "name": "DEF", "min": 30, "max": 90 },
4 | { "name": "ATK", "min": 20, "max": 70 },
5 | { "name": "HP%", "min": 5.2, "max": 15 },
6 | { "name": "DEF%", "min": 6.6, "max": 16.9 },
7 | { "name": "ATK%", "min": 5.2, "max": 15 },
8 | { "name": "Crit Rate", "min": 4.9, "max": 10.4 },
9 | { "name": "Crit Dmg", "min": 9.8, "max": 20.4 },
10 | { "name": "Energy Regen", "min": 5.6, "max": 14.9 },
11 | { "name": "Basic Attack Dmg Bonus", "min": 4.2, "max": 11.8 },
12 | { "name": "Heavy Attack Dmg Bonus", "min": 4.2, "max": 11.8 },
13 | { "name": "Resonance Skill Dmg Bonus", "min": 4.3, "max": 12.0 },
14 | { "name": "Resonance Liberation Dmg Bonus", "min": 4.2, "max": 12.0 }
15 | ]
16 |
--------------------------------------------------------------------------------
/data/sonatas.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "Freezing Frost",
4 | "twoPiece": "Glacio damage increased by 10%",
5 | "fivePiece": "When releasing Basic Attack or Heavy Attack, Glacio damage is increased by 10%, stacking up to three times, lasting for 15 seconds"
6 | },
7 | {
8 | "name": "Molten Rift",
9 | "twoPiece": "Fusion damage is increased by 10%",
10 | "fivePiece": "When releasing Resonance Skill, Fusion damage is increased by 30% for 15s"
11 | },
12 | {
13 | "name": "Void Thunder",
14 | "twoPiece": "Electro damage is increased by 10%",
15 | "fivePiece": "When releasing Heavy Attack or Resonance Skill, Electro damage dealt is increased by 15%, stacking up to two times, each lasting for 15 seconds"
16 | },
17 | {
18 | "name": "Sierra Gale",
19 | "twoPiece": "Aero DMG increased by 10%",
20 | "fivePiece": "Aero DMG is increased by 30% for 15 seconds when Intro Skill is used"
21 | },
22 | {
23 | "name": "Celestial Light",
24 | "twoPiece": "Spectro DMG is increased by 10%",
25 | "fivePiece": "Increases Spectro damage by 30% over 15s when releasing Intro Skill"
26 | },
27 | {
28 | "name": "Sun-sinking Eclipse",
29 | "twoPiece": "Havoc DMG is increased by 10%",
30 | "fivePiece": "When releasing Basic Attack or Heavy Attack, Havoc DMG is increased by 7.5%, stacking up to four times for 15 seconds"
31 | },
32 | {
33 | "name": "Rejuvenating Glow",
34 | "twoPiece": "Healing is increased by 10%",
35 | "fivePiece": " When healing allies, ATK for the entire team is increased by 15%, lasting 30s"
36 | },
37 | {
38 | "name": "Moonlit Clouds",
39 | "twoPiece": "Energy Regen increased by 10%",
40 | "fivePiece": "After using Outro Skill, the ATK of the next Resonator is increased by 22.5% for 15 seconds"
41 | },
42 | {
43 | "name": "Lingering Tunes",
44 | "twoPiece": "ATK increases by 10%",
45 | "fivePiece": "When in effect, your ATK increases by 5% every 1.5 seconds, stacking up to four times. Outro Skill DMG is increased by 60%"
46 | }
47 | ]
48 |
--------------------------------------------------------------------------------
/data/weapons.json:
--------------------------------------------------------------------------------
1 | {
2 | "gauntlets": [
3 | {
4 | "name": "Abyss Surges",
5 | "description": "The Gauntlets pulsate with an uncontrollable force, emanating unspeakable anger from the depths of the unknown lake. As you don them, unleash your fury on helpless enemies. Feel its power surge through you.",
6 | "type": "Gauntlets",
7 | "rarity": 5,
8 | "stats": {
9 | "atk": 47,
10 | "substat": {
11 | "name": "ATK",
12 | "value": "8.1%"
13 | }
14 | },
15 | "skill": {
16 | "name": "Stormy Resolution",
17 | "description": "Energy Regen is increased by {0}. When Resonance Skill hits a target, Basic ATK Damage is increased by {1}. When Basic atk hits a target, Resonance Skill Damage is increased by {3} and lasts for {4}s.",
18 | "ranks": [
19 | {
20 | "0": "12.8%",
21 | "1": "16%",
22 | "3": "19.2%",
23 | "4": "22.4%",
24 | "5": "25.6%"
25 | },
26 | {
27 | "0": "10%",
28 | "1": "12.5%",
29 | "3": "15%",
30 | "4": "17.5%",
31 | "5": "20%"
32 | },
33 | {
34 | "0": "8",
35 | "1": "8",
36 | "3": "8",
37 | "4": "8",
38 | "5": "8"
39 | },
40 | {
41 | "0": "10%",
42 | "1": "12.5%",
43 | "3": "15%",
44 | "4": "17.5%",
45 | "5": "20%"
46 | },
47 | {
48 | "0": "8",
49 | "1": "8",
50 | "3": "8",
51 | "4": "8",
52 | "5": "8"
53 | }
54 | ]
55 | }
56 | },
57 | {
58 | "name": "Amity Accord",
59 | "description": "A weapon designed to commemorate the Midnight Rangers' comradeship. 'Thin garments yield to icy moons, while armor withstands the chill of stars.'",
60 | "type": "Gauntlets",
61 | "rarity": 4,
62 | "stats": {
63 | "atk": 27,
64 | "substat": {
65 | "name": "DEF",
66 | "value": "13.7%"
67 | }
68 | },
69 | "skill": {
70 | "name": "Camaraderie",
71 | "description": "When Intro Skill is released, increases Resonance Liberation DMG Bonus by {0}, lasting for {1}s.",
72 | "ranks": [
73 | {
74 | "0": "20%",
75 | "1": "25%",
76 | "3": "30%",
77 | "4": "35%",
78 | "5": "40%"
79 | },
80 | {
81 | "0": "15",
82 | "1": "15",
83 | "3": "15",
84 | "4": "15",
85 | "5": "15"
86 | }
87 | ]
88 | }
89 | },
90 | {
91 | "name": "Gauntlets of Night",
92 | "description": "May victory prevail the lasting night. In addition to the sworn oath of protection, these gauntlets also commemorates the sacrifices of those brave souls.",
93 | "type": "Gauntlets",
94 | "rarity": 3,
95 | "stats": {
96 | "atk": 26,
97 | "substat": {
98 | "name": "ATK",
99 | "value": "5.4%"
100 | }
101 | },
102 | "skill": {
103 | "name": "Assemble",
104 | "description": "Gains 3 stacks of Armor after releasing Resonance Liberation. Each stack increases ATK and DEF by {0}, up to 3 stacks. Loses 1 stack when receiving DMG.",
105 | "ranks": [
106 | {
107 | "0": "10%",
108 | "1": "5%",
109 | "3": "10%",
110 | "4": "5"
111 | }
112 | ]
113 | }
114 | },
115 | {
116 | "name": "Gauntlets of Voyager",
117 | "description": "The Gauntlets provided by the Pioneer Association for the far walkers, with bright colors, not easy to lose, hard texture, can cope with various extreme environments for a long time, and are very popular among the explorer group.",
118 | "type": "Gauntlets",
119 | "rarity": 3,
120 | "stats": {
121 | "atk": 26,
122 | "substat": {
123 | "name": "DEF",
124 | "value": "6.8%"
125 | }
126 | },
127 | "skill": {
128 | "name": "Crusade",
129 | "description": "When using the Resonance Skill, it restores {0} points of Resonance Energy, and can be triggered {2} times every {1}s.",
130 | "ranks": [
131 | {
132 | "0": "8",
133 | "1": "9",
134 | "3": "10",
135 | "4": "11",
136 | "5": "12"
137 | },
138 | {
139 | "0": "20",
140 | "1": "20",
141 | "3": "20",
142 | "4": "20",
143 | "5": "20"
144 | },
145 | {
146 | "0": "1",
147 | "1": "1",
148 | "3": "1",
149 | "4": "1",
150 | "5": "1"
151 | }
152 | ]
153 | }
154 | },
155 | {
156 | "name": "Gauntlets#21D",
157 | "description": "Huaxu Academy has independently improved and developed the first set of highly practical high-performance Gauntlets based on the previous source energy weapons, representing the care and warmth of the Academy, stable and lasting.",
158 | "type": "Gauntlets",
159 | "rarity": 4,
160 | "stats": {
161 | "atk": 31,
162 | "substat": {
163 | "name": "Energy Regen",
164 | "value": "8.64%"
165 | }
166 | },
167 | "skill": {
168 | "name": "Mastermind",
169 | "description": "When Dodging or Dashing, the ATK increases by {0}, the damage caused by the Dodge counter attack increases by {1}, and lasts for {2}s. When the Dodge counter attack hits the target, it restores {3} health to the Character, and can be triggered {5} times every {4}s.",
170 | "ranks": [
171 | {
172 | "0": "8%",
173 | "1": "10%",
174 | "3": "12%",
175 | "4": "14%",
176 | "5": "16%"
177 | },
178 | {
179 | "0": "50%",
180 | "1": "62.5%",
181 | "3": "75%",
182 | "4": "87.5%",
183 | "5": "100%"
184 | },
185 | {
186 | "0": "8",
187 | "2": "8",
188 | "3": "8",
189 | "4": "8",
190 | "5": "8"
191 | },
192 | {
193 | "0": "5%",
194 | "1": "6.25%",
195 | "3": "7.5%",
196 | "4": "8.75%",
197 | "5": "10%"
198 | },
199 | {
200 | "0": "6",
201 | "2": "6",
202 | "3": "6",
203 | "4": "6",
204 | "5": "6"
205 | },
206 | {
207 | "0": "1",
208 | "1": "1",
209 | "3": "1",
210 | "4": "1",
211 | "5": "1"
212 | }
213 |
214 | ]
215 | }
216 | },
217 | {
218 | "name": "Guardian Gauntlets",
219 | "description": "The first Jinzhou Magistrate, in memory of the mysterious person who helped defend the border and build the city, created the Guardian series under his guidance. Today, it has become an indispensable cornerstone in the development of new weapons.",
220 | "type": "Gauntlets",
221 | "rarity": 3,
222 | "stats": {
223 | "atk": 24,
224 | "substat": {
225 | "name": "DEF",
226 | "value": "8.6%"
227 | }
228 | },
229 | "skill": {
230 | "name": "Collective Strength",
231 | "description": "The damage of Resonance Liberation increases by {0}.",
232 | "ranks": [
233 | {
234 | "0": "12%",
235 | "1": "15%",
236 | "3": "18%",
237 | "4": "21%",
238 | "5": "24%"
239 | }
240 | ]
241 | }
242 | },
243 | {
244 | "name": "Stonard",
245 | "type": "Gauntlets",
246 | "rarity": 4,
247 | "stats": {
248 | "atk": 33,
249 | "substat": {
250 | "name": "Crit Rate",
251 | "value": "4.5%"
252 | }
253 | },
254 | "skill": {
255 | "name": "",
256 | "description": "After casting the Resonance Skill, the damage of your own Resonance Liberation increases by {0}, and the effect lasts for {1}s.",
257 | "ranks": [
258 | {
259 | "0": "18%",
260 | "1": "27%",
261 | "3": "36%",
262 | "4": "45%",
263 | "5": "54%"
264 | },
265 | {
266 | "0": "5",
267 | "1": "5",
268 | "3": "5",
269 | "4": "5",
270 | "5": "5"
271 | }
272 | ]
273 | }
274 | },
275 | {
276 | "name": "Training Gauntlets",
277 | "description": "These gauntlets are designed specifically for training and teaching, offering only the basic features.",
278 | "type": "Gauntlets",
279 | "rarity": 1,
280 | "stats": {
281 | "atk": 20,
282 | "substat": {
283 | "name": "ATK",
284 | "value": "2.5%"
285 | }
286 | },
287 | "skill": {
288 | "name": "Perserve",
289 | "description": "Increase ATK by {0}.",
290 | "ranks": [
291 | {
292 | "0": "4%",
293 | "1": "5%",
294 | "3": "6%",
295 | "4": "7%",
296 | "5": "8%"
297 | }
298 | ]
299 | }
300 | },
301 | {
302 | "name": "Tyro Gauntlets",
303 | "description": "The dawn of enduring endeavor. A pair of gauntlets designed for novice Resonators. Contains a power not to be underestimated under its simple outlook.",
304 | "type": "Gauntlets",
305 | "rarity": 2,
306 | "stats": {
307 | "atk": 22,
308 | "substat": {
309 | "name": "ATK",
310 | "value": "3.3%"
311 | }
312 | },
313 | "skill": {
314 | "name": "Prologue",
315 | "description": "Increase ATK by {0}.",
316 | "ranks": [
317 | {
318 | "0": "5%",
319 | "1": "6.5%",
320 | "3": "7.5%",
321 | "4": "8.75%",
322 | "5": "10%"
323 | }
324 | ]
325 | }
326 | }
327 | ],
328 | "rectifier": [
329 | {
330 | "name": "Augment",
331 | "description": "This Rectifier is a ceremonial weapon used by the Jinzhou Magistrate of Huanglong at the inauguration ceremony. The golden ginkgo leaf pattern represents that Huanglong should be like ginkgo, although it is left alone in the world, it is still prosperous and long-lasting.",
332 | "type": "Rectifier",
333 | "rarity": 4,
334 | "stats": {
335 | "atk": 33,
336 | "substat": {
337 | "name": "Crit Rate",
338 | "value": "4.5%"
339 | }
340 | },
341 | "skill": {
342 | "name": "Forgiving Resilience",
343 | "description": "When Resonance Liberation is released, increases the caster's ATK by {0}, lasting for {1}s.",
344 | "ranks": [
345 | {
346 | "0": "15%",
347 | "1": "23.25%",
348 | "3": "31.5%",
349 | "4": "39.75%",
350 | "5": "48%"
351 | },
352 | {
353 | "0": "15",
354 | "1": "15",
355 | "3": "15",
356 | "4": "15",
357 | "5": "15"
358 | }
359 | ]
360 | }
361 | },
362 | {
363 | "name": "Comet Flare",
364 | "description": "The Rectifier born from the aerial phenomenon. The forger integrates the feelings of witnessing the alien stars into the weapon, making it look delicate and light, but it can be stable and constant, and it can be responsive.",
365 | "type": "Rectifier",
366 | "rarity": 4,
367 | "stats": {
368 | "atk": 33,
369 | "substat": {
370 | "name": "HP",
371 | "value": "6.8%"
372 | }
373 | },
374 | "skill": {
375 | "name": "Luminous Protection",
376 | "description": "hen hitting a target with Basic ATKs or Heavy ATKs, increases Healing Bonus by {0}, stacking up to {2} time(s). This effect lasts for {1}s and can be triggered {4} time(s) every {3}s.",
377 | "ranks": [
378 | {
379 | "0": "3%",
380 | "1": "3.5%",
381 | "3": "4%",
382 | "4": "4.5%",
383 | "5": "5%"
384 | },
385 | {
386 | "0": "8",
387 | "1": "8",
388 | "3": "8",
389 | "4": "8",
390 | "5": "8"
391 | },
392 | {
393 | "0": "3",
394 | "1": "3",
395 | "3": "3",
396 | "4": "3",
397 | "5": "3"
398 | },
399 | {
400 | "0": "0.6",
401 | "1": "0.6",
402 | "3": "0.6",
403 | "4": "0.6",
404 | "5": "0.6"
405 | },
406 | {
407 | "0": "1",
408 | "1": "1",
409 | "3": "1",
410 | "4": "1",
411 | "5": "1"
412 | }
413 |
414 | ]
415 | }
416 | },
417 | {
418 | "name": "Cosmic Ripples",
419 | "description": "Take hold of the frozen Rectifier, a powerful tool imbued with the energy of the celestial lake. Let it lead you to ultimate knowledge and vanquish all obstacles in your path.",
420 | "type": "Rectifier",
421 | "rarity": 5,
422 | "stats": {
423 | "atk": 40,
424 | "substat": {
425 | "name": "ATK",
426 | "value": "12%"
427 | }
428 | },
429 | "skill": {
430 | "name": "Luminous Protection",
431 | "description": "When hitting a target with Basic ATKs or Heavy atks, increases Healing Bonus by {0}, stacking up to {2} time(s). This effect lasts for {1}s and can be triggered {4} time(s) every {3}s.",
432 | "ranks": [
433 | {
434 | "0": "3%",
435 | "1": "3.5%",
436 | "3": "4%",
437 | "4": "4.5%",
438 | "5": "5%"
439 | },
440 | {
441 | "0": "8",
442 | "1": "8",
443 | "3": "8",
444 | "4": "8",
445 | "5": "8"
446 | },
447 | {
448 | "0": "3",
449 | "1": "3",
450 | "3": "3",
451 | "4": "3",
452 | "5": "3"
453 | },
454 | {
455 | "0": "0.6",
456 | "1": "0.6",
457 | "3": "0.6",
458 | "4": "0.6",
459 | "5": "0.6"
460 | },
461 | {
462 | "0": "1",
463 | "1": "1",
464 | "3": "1",
465 | "4": "1",
466 | "5": "1"
467 | }
468 |
469 | ]
470 | }
471 | },
472 | {
473 | "name": "Jinzhou Keeper",
474 | "description": "A weapon designed to commemorate the Midnight Rangers' vigilant protection for Jinzhou. 'Cast your gaze towards the north, where the city's silhouette fades, its gates veiled by the tranquil rain.'",
475 | "type": "Rectifier",
476 | "rarity": 4,
477 | "stats": {
478 | "atk": 31,
479 | "substat": {
480 | "name": "ATK",
481 | "value": "8.1%"
482 | }
483 | },
484 | "skill": {
485 | "name": "Guardian",
486 | "description": "When Intro Skill is released, increases the caster's ATK by {0} and HP by {1}, lasting for {2}s.",
487 | "ranks": [
488 | {
489 | "0": "8%",
490 | "1": "10%",
491 | "3": "12%",
492 | "4": "14%",
493 | "5": "16%"
494 | },
495 | {
496 | "0": "10%",
497 | "1": "12.5%",
498 | "3": "15%",
499 | "4": "17.5%",
500 | "5": "20%"
501 | },
502 | {
503 | "0": "15",
504 | "1": "15",
505 | "3": "15",
506 | "4": "15",
507 | "5": "15"
508 | }
509 | ]
510 | }
511 | },
512 | {
513 | "name": "Originite: Type V",
514 | "description": "A rectifier model mass-produced in Huanglong. Codename: Chrono.",
515 | "type": "Rectifier",
516 | "rarity": 3,
517 | "stats": {
518 | "atk": 24,
519 | "substat": {
520 | "name": "HP",
521 | "value": "6.75%"
522 | }
523 | },
524 | "skill": {
525 | "name": "Augment",
526 | "description": "When Intro Skill is released, restores HP by {0}. This effect can be triggered {2} time(s) every {1}s.",
527 | "ranks": [
528 | {
529 | "0": "5%",
530 | "1": "6.25%",
531 | "3": "7.5%",
532 | "4": "8.75%",
533 | "5": "10%"
534 | },
535 | {
536 | "0": "20",
537 | "1": "20",
538 | "3": "20",
539 | "4": "20",
540 | "5": "20"
541 | },
542 | {
543 | "0": "1",
544 | "1": "1",
545 | "3": "1",
546 | "4": "1",
547 | "5": "1"
548 | }
549 | ]
550 | }
551 | },
552 | {
553 | "name": "Rectifier of Night",
554 | "description": "May victory prevail the lasting night. In addition to the sworn oath of protection, this rectifier signifies the confidence of those fighting souls.",
555 | "type": "Rectifier",
556 | "rarity": 3,
557 | "stats": {
558 | "atk": 26,
559 | "substat": {
560 | "name": "ATK",
561 | "value": "5.4%"
562 | }
563 | },
564 | "skill": {
565 | "name": "Valiance",
566 | "description": "When Intro Skill is released, increases ATK by {0}, lasting for {1}s.",
567 | "ranks": [
568 | {
569 | "0": "8%",
570 | "1": "10%",
571 | "3": "12%",
572 | "4": "14%",
573 | "5": "16%"
574 | },
575 | {
576 | "0": "10",
577 | "1": "10",
578 | "3": "10",
579 | "4": "10",
580 | "5": "10"
581 | }
582 | ]
583 | }
584 | },
585 | {
586 | "name": "Rectifier of Voyager",
587 | "description": "The Rectifier provided by the Pioneer Association for the explorers, with bright colors, not easy to lose, hard texture, can cope with various extreme environments for a long time, is very popular among the explorers.",
588 | "type": "Rectifier",
589 | "rarity": 3,
590 | "stats": {
591 | "atk": 24,
592 | "substat": {
593 | "name": "Energy Regen",
594 | "value": "7.2%"
595 | }
596 | },
597 | "skill": {
598 | "name": "Crusade",
599 | "description": "When Resonance Skill is released, restores Resonance Energy by {0}. This effect can be triggered {2} time(s) every {1}s.",
600 | "ranks": [
601 | {
602 | "0": "8",
603 | "1": "9",
604 | "3": "10",
605 | "4": "11",
606 | "5": "12"
607 | },
608 | {
609 | "0": "20",
610 | "1": "20",
611 | "3": "20",
612 | "4": "20",
613 | "5": "20"
614 | },
615 | {
616 | "0": "1",
617 | "1": "1",
618 | "3": "1",
619 | "4": "1",
620 | "5": "1"
621 | }
622 | ]
623 | }
624 | },
625 | {
626 | "name": "Rectifier#25",
627 | "description": "Improved version of a Rectifier prototype from the Huanglong next-gen mass production weaponry project. With surging power and highly purified lens, its sound is only played by the wise of steel-like will.",
628 | "type": "Rectifier",
629 | "rarity": 4,
630 | "stats": {
631 | "atk": 27,
632 | "substat": {
633 | "name": "Energy Regen",
634 | "value": "11.52%"
635 | }
636 | },
637 | "skill": {
638 | "name": "Dawnbringer",
639 | "description": "When Resonance Skill is released, if the Resonator's HP is below {0}, restores their HP by {1}. This effect can be triggered {3} time(s) every {2}s; if the Resonator's HP is above {4}, increases ATK by {5}, lasting for {6}s.",
640 | "ranks": [
641 | {
642 | "0": "60%",
643 | "1": "60%",
644 | "3": "60%",
645 | "4": "60%",
646 | "5": "60%"
647 | },
648 | {
649 | "0": "5%",
650 | "1": "6.25%",
651 | "3": "7.5%",
652 | "4": "8.75%",
653 | "5": "10%"
654 | },
655 | {
656 | "0": "8",
657 | "1": "8",
658 | "3": "8",
659 | "4": "8",
660 | "5": "8"
661 | },
662 | {
663 | "0": "1",
664 | "1": "1",
665 | "3": "1",
666 | "4": "1",
667 | "5": "1"
668 | },
669 | {
670 | "0": "60%",
671 | "1": "60%",
672 | "3": "60%",
673 | "4": "60%",
674 | "5": "60%"
675 | },
676 | {
677 | "0": "12%",
678 | "1": "15%",
679 | "3": "18%",
680 | "4": "21%",
681 | "5": "24%"
682 | },
683 | {
684 | "0": "10",
685 | "1": "10",
686 | "3": "10",
687 | "4": "10",
688 | "5": "10"
689 | }
690 | ]
691 | }
692 | },
693 | {
694 | "name": "Variation",
695 | "description": "A descending adagio. This rectifier opens up an insightful window into the situation, changing the tune of the intense battle concerto.",
696 | "type": "Rectifier",
697 | "rarity": 4,
698 | "stats": {
699 | "atk": 27,
700 | "substat": {
701 | "name": "Energy Regen",
702 | "value": "11.52%"
703 | }
704 | },
705 | "skill": {
706 | "name": "Ceaseless Aria",
707 | "description": "When Resonance Skill is released, restores Concerto Energy by {0}. This effect can be triggered 1 time every {1}s.",
708 | "ranks": [
709 | {
710 | "0": "8",
711 | "1": "10",
712 | "3": "12",
713 | "4": "14",
714 | "5": "16"
715 | },
716 | {
717 | "0": "20",
718 | "1": "20",
719 | "3": "20",
720 | "4": "20",
721 | "5": "20"
722 | },
723 | {
724 | "0": "1",
725 | "1": "1",
726 | "3": "1",
727 | "4": "1",
728 | "5": "1"
729 | }
730 | ]
731 | }
732 | },
733 | {
734 | "name": "Stringmaster",
735 | "description": "As the puppet palm unfurls, deadly strings are unleashed with a menacing hum. The air crackles with power, immobilizing any foes caught in its grasp. The rapid bolts of electricity leave unhealable scars on their victims.",
736 | "type": "Rectifier",
737 | "rarity": 5,
738 | "stats": {
739 | "atk": 40,
740 | "substat": {
741 | "name": "Crit Rate",
742 | "value": "8%"
743 | }
744 | },
745 | "skill": {
746 | "name": "Electric Amplification",
747 | "description": "Increases the DMG Bonus of all Resonance Attributes by {0}. When Resonance Skill hits a target, increases ATK by {1}, stacking up to {2}. When the equipped character is not on the field, increases their ATK by an additional {4}.",
748 | "ranks": [
749 | {
750 | "0": "12%",
751 | "1": "15%",
752 | "3": "18%",
753 | "4": "21%",
754 | "5": "24%"
755 | },
756 | {
757 | "0": "12%",
758 | "1": "15%",
759 | "3": "18%",
760 | "4": "21%",
761 | "5": "24%"
762 | },
763 | {
764 | "0": "2",
765 | "1": "2",
766 | "3": "2",
767 | "4": "2",
768 | "5": "2"
769 | },
770 | {
771 | "0": "5",
772 | "1": "5",
773 | "3": "5",
774 | "4": "5",
775 | "5": "5"
776 | },
777 | {
778 | "0": "12%",
779 | "1": "15%",
780 | "3": "18%",
781 | "4": "21%",
782 | "5": "24%"
783 | }
784 | ]
785 | }
786 | },
787 | {
788 | "name": "Training Rectifier",
789 | "description": "This rectifier is designed specifically for training and teaching, offering only the basic features.",
790 | "type": "Rectifier",
791 | "rarity": 1,
792 | "stats": {
793 | "atk": 20,
794 | "substat": {
795 | "name": "ATK",
796 | "value": "2.55%"
797 | }
798 | },
799 | "skill": {
800 | "name": "Persevere",
801 | "description": "Increases ATK by {0}.",
802 | "ranks": [
803 | {
804 | "0": "4%",
805 | "1": "5%",
806 | "3": "6%",
807 | "4": "7%",
808 | "5": "8%"
809 | }
810 | ]
811 | }
812 | },
813 | {
814 | "name": "Tyro Rectifier",
815 | "description": "The origin of universal genesis. A Rectifier designed for novice Resonators. Contains a power not to be underestimated under its simple outlook.",
816 | "type": "Rectifier",
817 | "rarity": 2,
818 | "stats": {
819 | "atk": 22,
820 | "substat": {
821 | "name": "ATK",
822 | "value": "3.3%"
823 | }
824 | },
825 | "skill": {
826 | "name": "Persevere",
827 | "description": "Increases ATK by {0}.",
828 | "ranks": [
829 | {
830 | "0": "5%",
831 | "1": "6.25%",
832 | "3": "7.5%",
833 | "4": "8.75%",
834 | "5": "10%"
835 | }
836 | ]
837 | }
838 | }
839 | ],
840 | "broadblade": [
841 | {
842 | "name": "Autumntrace",
843 | "type": "Broadblade",
844 | "rarity": 4,
845 | "url": "autumntrace",
846 | "IconMiddle": "/Game/Aki/UI/UIResources/Common/Image/IconWeapon160/T_IconWeapon160_21010074_UI"
847 | },
848 | {
849 | "name": "Broadblade of Night",
850 | "type": "Broadblade",
851 | "rarity": 3,
852 | "url": "broadblade-of-night",
853 | "IconMiddle": "/Game/Aki/UI/UIResources/Common/Image/IconWeapon160/T_IconWeapon160_21010013_UI"
854 | },
855 | {
856 | "name": "Broadblade of Voyager",
857 | "type": "Broadblade",
858 | "rarity": 3,
859 | "url": "broadblade-of-voyager",
860 | "IconMiddle": "/Game/Aki/UI/UIResources/Common/Image/IconWeapon160/T_IconWeapon160_21010043_UI"
861 | },
862 | {
863 | "name": "Broadblade#41",
864 | "type": "Broadblade",
865 | "rarity": 4,
866 | "url": "broadblade-41",
867 | "IconMiddle": "/Game/Aki/UI/UIResources/Common/Image/IconWeapon160/T_IconWeapon160_21010034_UI"
868 | },
869 | {
870 | "name": "Dauntless Evernight",
871 | "type": "Broadblade",
872 | "rarity": 4,
873 | "url": "dauntless-evernight",
874 | "IconMiddle": "/Game/Aki/UI/UIResources/Common/Image/IconWeapon160/T_IconWeapon160_21010044_UI"
875 | },
876 | {
877 | "name": "Discord",
878 | "type": "Broadblade",
879 | "rarity": 4,
880 | "url": "discord",
881 | "IconMiddle": "/Game/Aki/UI/UIResources/Common/Image/IconWeapon160/T_IconWeapon160_21010024_UI"
882 | },
883 | {
884 | "name": "Guardian Broadblade",
885 | "type": "Broadblade",
886 | "rarity": 3,
887 | "url": "guardian-broadblade",
888 | "IconMiddle": "/Game/Aki/UI/UIResources/Common/Image/IconWeapon160/T_IconWeapon160_21010053_UI"
889 | },
890 | {
891 | "name": "Helios Cleaver",
892 | "type": "Broadblade",
893 | "rarity": 4,
894 | "url": "helios-cleaver",
895 | "IconMiddle": "/Game/Aki/UI/UIResources/Common/Image/IconWeapon160/T_IconWeapon160_21010064_UI"
896 | },
897 | {
898 | "name": "Lustrous Razor",
899 | "type": "Broadblade",
900 | "rarity": 5,
901 | "url": "lustrous-razor",
902 | "IconMiddle": "/Game/Aki/UI/UIResources/Common/Image/IconWeapon160/T_IconWeapon160_21010015_UI"
903 | },
904 | {
905 | "name": "Scale: Slasher",
906 | "type": "Broadblade",
907 | "rarity": 4,
908 | "url": "scale-slasher",
909 | "IconMiddle": "/Game/Aki/UI/UIResources/Common/Image/IconWeapon160/T_IconWeapon160_21020024_UI"
910 | },
911 | {
912 | "name": "Verdant Summit",
913 | "type": "Broadblade",
914 | "rarity": 5,
915 | "url": "verdant-summit",
916 | "IconMiddle": "/Game/Aki/UI/UIResources/Common/Image/IconWeapon160/T_IconWeapon160_21010016_UI"
917 | },
918 | {
919 | "name": "Training Broadblade",
920 | "type": "Broadblade",
921 | "rarity": 1,
922 | "url": "training-broadblade",
923 | "IconMiddle": "/Game/Aki/UI/UIResources/Common/Image/IconWeapon160/T_IconWeapon160_21010011_UI"
924 | },
925 | {
926 | "name": "Tyro Broadblade",
927 | "type": "Broadblade",
928 | "rarity": 2,
929 | "url": "tyro-broadblade",
930 | "IconMiddle": "/Game/Aki/UI/UIResources/Common/Image/IconWeapon160/T_IconWeapon160_21010012_UI"
931 | }
932 | ],
933 | "pistols": [
934 | {
935 | "name": "Cadenza",
936 | "type": "Pistols",
937 | "rarity": 4,
938 | "url": "cadenza",
939 | "IconMiddle": "/Game/Aki/UI/UIResources/Common/Image/IconWeapon160/T_IconWeapon160_21030024_UI"
940 | },
941 | {
942 | "name": "Novaburst",
943 | "type": "Pistols",
944 | "rarity": 4,
945 | "url": "novaburst",
946 | "IconMiddle": "/Game/Aki/UI/UIResources/Common/Image/IconWeapon160/T_IconWeapon160_21030064_UI"
947 | },
948 | {
949 | "name": "Pistols of Night",
950 | "type": "Pistols",
951 | "rarity": 3,
952 | "url": "pistols-of-night",
953 | "IconMiddle": "/Game/Aki/UI/UIResources/Common/Image/IconWeapon160/T_IconWeapon160_21030013_UI"
954 | },
955 | {
956 | "name": "Pistols of Voyager",
957 | "type": "Pistols",
958 | "rarity": 3,
959 | "url": "pistols-of-voyager",
960 | "IconMiddle": "/Game/Aki/UI/UIResources/Common/Image/IconWeapon160/T_IconWeapon160_21030043_UI"
961 | },
962 | {
963 | "name": "Pistols#26",
964 | "type": "Pistols",
965 | "rarity": 4,
966 | "url": "pistols-26",
967 | "IconMiddle": "/Game/Aki/UI/UIResources/Common/Image/IconWeapon160/T_IconWeapon160_21030034_UI"
968 | },
969 | {
970 | "name": "Thunderbolt",
971 | "type": "Pistols",
972 | "rarity": 4,
973 | "url": "thunderbolt",
974 | "IconMiddle": "/Game/Aki/UI/UIResources/Common/Image/IconWeapon160/T_IconWeapon160_21030074_UI"
975 | },
976 | {
977 | "name": "Undying Flame",
978 | "type": "Pistols",
979 | "rarity": 4,
980 | "url": "undying-flame",
981 | "IconMiddle": "/Game/Aki/UI/UIResources/Common/Image/IconWeapon160/T_IconWeapon160_21030044_UI"
982 | },
983 | {
984 | "name": "Static Mist",
985 | "type": "Pistols",
986 | "rarity": 5,
987 | "url": "static-mist",
988 | "IconMiddle": "/Game/Aki/UI/UIResources/Common/Image/IconWeapon160/T_IconWeapon160_21030015_UI"
989 | },
990 | {
991 | "name": "Originite: Type III",
992 | "type": "Pistols",
993 | "rarity": 3,
994 | "url": "originite-type-iii",
995 | "IconMiddle": "/Game/Aki/UI/UIResources/Common/Image/IconWeapon160/T_IconWeapon160_21030023_UI"
996 | },
997 | {
998 | "name": "Training Pistols",
999 | "type": "Pistols",
1000 | "rarity": 1,
1001 | "url": "training-pistols",
1002 | "IconMiddle": "/Game/Aki/UI/UIResources/Common/Image/IconWeapon160/T_IconWeapon160_21030011_UI"
1003 | },
1004 | {
1005 | "name": "Tyro Pistols",
1006 | "type": "Pistols",
1007 | "rarity": 2,
1008 | "url": "tyro-pistols",
1009 | "IconMiddle": "/Game/Aki/UI/UIResources/Common/Image/IconWeapon160/T_IconWeapon160_21030012_UI"
1010 | }
1011 | ],
1012 | "sword": [
1013 | {
1014 | "name": "Commando of Conviction",
1015 | "type": "Sword",
1016 | "rarity": 4,
1017 | "url": "commando-of-conviction",
1018 | "IconMiddle": "/Game/Aki/UI/UIResources/Common/Image/IconWeapon160/T_IconWeapon160_21020044_UI"
1019 | },
1020 | {
1021 | "name": "Emerald of Genesis",
1022 | "type": "Sword",
1023 | "rarity": 5,
1024 | "url": "emerald-of-genesis",
1025 | "IconMiddle": "/Game/Aki/UI/UIResources/Common/Image/IconWeapon160/T_IconWeapon160_21020015_UI"
1026 | },
1027 | {
1028 | "name": "Lumingloss",
1029 | "type": "Sword",
1030 | "rarity": 4,
1031 | "url": "lumingloss",
1032 | "IconMiddle": "/Game/Aki/UI/UIResources/Common/Image/IconWeapon160/T_IconWeapon160_21020074_UI"
1033 | },
1034 | {
1035 | "name": "Lunar Cutter",
1036 | "type": "Sword",
1037 | "rarity": 4,
1038 | "url": "lunar-cutter",
1039 | "IconMiddle": "/Game/Aki/UI/UIResources/Common/Image/IconWeapon160/T_IconWeapon160_21020064_UI"
1040 | },
1041 | {
1042 | "name": "Originite: Type II",
1043 | "type": "Sword",
1044 | "rarity": 3,
1045 | "url": "originite-type-ii",
1046 | "IconMiddle": "/Game/Aki/UI/UIResources/Common/Image/IconWeapon160/T_IconWeapon160_21020023_UI"
1047 | },
1048 | {
1049 | "name": "Sword of Night",
1050 | "type": "Sword",
1051 | "rarity": 3,
1052 | "url": "sword-of-night",
1053 | "IconMiddle": "/Game/Aki/UI/UIResources/Common/Image/IconWeapon160/T_IconWeapon160_21020013_UI"
1054 | },
1055 | {
1056 | "name": "Sword of Voyager",
1057 | "type": "Sword",
1058 | "rarity": 3,
1059 | "url": "sword-of-voyager",
1060 | "IconMiddle": "/Game/Aki/UI/UIResources/Common/Image/IconWeapon160/T_IconWeapon160_21020043_UI"
1061 | },
1062 | {
1063 | "name": "Sword#18",
1064 | "type": "Sword",
1065 | "rarity": 4,
1066 | "url": "sword-18",
1067 | "IconMiddle": "/Game/Aki/UI/UIResources/Common/Image/IconWeapon160/T_IconWeapon160_21020034_UI"
1068 | },
1069 | {
1070 | "name": "Training Sword",
1071 | "type": "Sword",
1072 | "rarity": 1,
1073 | "url": "training-sword",
1074 | "IconMiddle": "/Game/Aki/UI/UIResources/Common/Image/IconWeapon160/T_IconWeapon160_21020011_UI"
1075 | },
1076 | {
1077 | "name": "Tyro Sword",
1078 | "type": "Sword",
1079 | "rarity": 2,
1080 | "url": "tyro-sword",
1081 | "IconMiddle": "/Game/Aki/UI/UIResources/Common/Image/IconWeapon160/T_IconWeapon160_21020012_UI"
1082 | }
1083 | ]
1084 | }
1085 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module api
2 |
3 | go 1.23.0
4 |
5 | require (
6 | github.com/gin-contrib/cors v1.7.2
7 | github.com/gin-gonic/gin v1.10.0
8 | )
9 |
10 | require (
11 | github.com/bytedance/sonic v1.11.6 // indirect
12 | github.com/bytedance/sonic/loader v0.1.1 // indirect
13 | github.com/cloudwego/base64x v0.1.4 // indirect
14 | github.com/cloudwego/iasm v0.2.0 // indirect
15 | github.com/gabriel-vasile/mimetype v1.4.3 // indirect
16 | github.com/gin-contrib/sse v0.1.0 // indirect
17 | github.com/go-playground/locales v0.14.1 // indirect
18 | github.com/go-playground/universal-translator v0.18.1 // indirect
19 | github.com/go-playground/validator/v10 v10.20.0 // indirect
20 | github.com/goccy/go-json v0.10.2 // indirect
21 | github.com/json-iterator/go v1.1.12 // indirect
22 | github.com/klauspost/cpuid/v2 v2.2.7 // indirect
23 | github.com/kr/text v0.2.0 // indirect
24 | github.com/leodido/go-urn v1.4.0 // indirect
25 | github.com/mattn/go-isatty v0.0.20 // indirect
26 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
27 | github.com/modern-go/reflect2 v1.0.2 // indirect
28 | github.com/pelletier/go-toml/v2 v2.2.2 // indirect
29 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
30 | github.com/ugorji/go/codec v1.2.12 // indirect
31 | golang.org/x/arch v0.8.0 // indirect
32 | golang.org/x/crypto v0.23.0 // indirect
33 | golang.org/x/net v0.25.0 // indirect
34 | golang.org/x/sys v0.20.0 // indirect
35 | golang.org/x/text v0.15.0 // indirect
36 | google.golang.org/protobuf v1.34.1 // indirect
37 | gopkg.in/yaml.v3 v3.0.1 // indirect
38 | )
39 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
2 | github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
3 | github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
4 | github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
5 | github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
6 | github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
7 | github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
8 | github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
9 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
10 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
11 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
12 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
13 | github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
14 | github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
15 | github.com/gin-contrib/cors v1.7.2 h1:oLDHxdg8W/XDoN/8zamqk/Drgt4oVZDvaV0YmvVICQw=
16 | github.com/gin-contrib/cors v1.7.2/go.mod h1:SUJVARKgQ40dmrzgXEVxj2m7Ig1v1qIboQkPDTQ9t2E=
17 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
18 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
19 | github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
20 | github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
21 | github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
22 | github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
23 | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
24 | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
25 | github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
26 | github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
27 | github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
28 | github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
29 | github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
30 | github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
31 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
32 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
33 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
34 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
35 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
36 | github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
37 | github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
38 | github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
39 | github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
40 | github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
41 | github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
42 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
43 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
44 | github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
45 | github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
46 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
47 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
48 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
49 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
50 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
51 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
52 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
53 | github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
54 | github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
55 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
56 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
57 | github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
58 | github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
59 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
60 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
61 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
62 | github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
63 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
64 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
65 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
66 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
67 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
68 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
69 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
70 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
71 | github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
72 | github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
73 | github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
74 | github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
75 | golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
76 | golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
77 | golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
78 | golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
79 | golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
80 | golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
81 | golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
82 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
83 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
84 | golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
85 | golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
86 | golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
87 | golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
88 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
89 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
90 | google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
91 | google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
92 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
93 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
94 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
95 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
96 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
97 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
98 | nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
99 | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
100 |
--------------------------------------------------------------------------------
/handlers/attribute_handler.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | "strings"
5 | "fmt"
6 | "io"
7 | "net/http"
8 |
9 | "github.com/gin-gonic/gin"
10 | "api/models"
11 | )
12 |
13 | func ListAttributesHandler(attributes []models.Attribute) gin.HandlerFunc {
14 | return func(c *gin.Context) {
15 | var attributeNames []string
16 | for _, attribute := range attributes {
17 | attributeNames = append(attributeNames, attribute.Name)
18 | }
19 | c.JSON(http.StatusOK, gin.H{"attributes": attributeNames})
20 | }
21 | }
22 |
23 | func GetAttributeHandler(attributes []models.Attribute) gin.HandlerFunc {
24 | return func(c *gin.Context) {
25 | name := strings.ToLower(c.Param("name"))
26 |
27 | for _, attribute := range attributes {
28 | if strings.ToLower(attribute.Name) == name {
29 | c.JSON(http.StatusOK, attribute)
30 | return
31 | }
32 | }
33 |
34 | NotFoundHandler(c, "Attribute not found")
35 | }
36 | }
37 |
38 | func AttributeIconHandler(c *gin.Context) {
39 | name := strings.ToLower(c.Param("name"))
40 | remoteURL := fmt.Sprintf("%sattributes/icon/%s.webp", cdnURL, name)
41 |
42 | resp, err := http.Get(remoteURL)
43 | if err != nil {
44 | NotFoundHandler(c, "Failed to fetch icon")
45 | return
46 | }
47 | defer resp.Body.Close()
48 |
49 | if resp.StatusCode != http.StatusOK {
50 | NotFoundHandler(c, "Failed to fetch icon")
51 | return
52 | }
53 |
54 | c.Header("Content-Type", "image/webp")
55 | _, err = io.Copy(c.Writer, resp.Body)
56 | if err != nil {
57 | NotFoundHandler(c, "Failed to serve icon")
58 | return
59 | }
60 | }
--------------------------------------------------------------------------------
/handlers/character_handler.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | "net/http"
7 | "strings"
8 |
9 | "github.com/gin-gonic/gin"
10 | "api/models"
11 | )
12 |
13 | func ListCharactersHandler(characters []models.Character) gin.HandlerFunc {
14 | return func(c *gin.Context) {
15 | var characterNames []string
16 | for _, character := range characters {
17 | characterNames = append(characterNames, character.Name)
18 | }
19 | c.JSON(http.StatusOK, gin.H{"characters": characterNames})
20 | }
21 | }
22 |
23 |
24 | func GetCharacterHandler(characters []models.Character) gin.HandlerFunc {
25 | return func(c *gin.Context) {
26 | name := strings.ToLower(c.Param("name"))
27 | name = strings.ReplaceAll(name, " ", "%20")
28 |
29 | for _, character := range characters {
30 | if strings.ToLower(strings.ReplaceAll(character.Name, " ", "%20")) == name {
31 | c.JSON(http.StatusOK, character)
32 | return
33 | }
34 | }
35 |
36 | NotFoundHandler(c, "Character not found")
37 | }
38 | }
39 |
40 | func CharacterEmojisHandler(c *gin.Context) {
41 | name := c.Param("name")
42 | name = strings.ReplaceAll(name, " ", "%20")
43 | emojiList, err := loadEmojis(name)
44 | if err != nil {
45 | NotFoundHandler(c, "Failed to load emojis")
46 | return
47 | }
48 | c.JSON(http.StatusOK, emojiList)
49 | }
50 |
51 | func CharacterEmojiHandler(c *gin.Context) {
52 | name := c.Param("name")
53 | name = strings.ReplaceAll(name, " ", "%20")
54 | index := c.Param("index")
55 |
56 | emojiURL := fmt.Sprintf("%semojis/%s/%s.png", cdnURL, name, index)
57 | resp, err := http.Get(emojiURL)
58 | if err != nil {
59 | NotFoundHandler(c, "Failed to fetch emoji")
60 | return
61 | }
62 | defer resp.Body.Close()
63 |
64 | if resp.StatusCode != http.StatusOK {
65 | NotFoundHandler(c, "Failed to fetch emoji")
66 | return
67 | }
68 |
69 | c.Header("Content-Type", "image/png")
70 |
71 | _, err = io.Copy(c.Writer, resp.Body)
72 | if err != nil {
73 | NotFoundHandler(c, "Failed to serve emoji")
74 | return
75 | }
76 | }
77 |
78 | func CharacterImageHandler(c *gin.Context) {
79 | name := strings.ToLower(c.Param("name"))
80 | name = strings.ReplaceAll(name, " ", "_")
81 | imageType := c.Param("imagetype")
82 |
83 | validTypes := map[string]bool{
84 | "portrait": true,
85 | "icon": true,
86 | "circle": true,
87 | "card": true,
88 | }
89 |
90 | if !validTypes[imageType] {
91 | c.JSON(http.StatusBadRequest, gin.H{"message": "Invalid image type", "docs": docsURL})
92 | return
93 | }
94 |
95 | remoteURL := fmt.Sprintf("%scharacters/%ss/%s.png", cdnURL, imageType, name)
96 |
97 | resp, err := http.Get(remoteURL)
98 | if err != nil {
99 | NotFoundHandler(c, "Character image not found")
100 | return
101 | }
102 | defer resp.Body.Close()
103 |
104 | if resp.StatusCode != http.StatusOK {
105 | NotFoundHandler(c, "Failed to fetch file")
106 | return
107 | }
108 |
109 | c.Header("Content-Type", "image/png")
110 |
111 | _, err = io.Copy(c.Writer, resp.Body)
112 | if err != nil {
113 | NotFoundHandler(c, "Failed to serve file")
114 | return
115 | }
116 | }
117 |
118 | func loadEmojis(charName string) (*models.Emojis, error) {
119 | emojiList := &models.Emojis{}
120 | for i := 0; ; i++ {
121 | emojiPath, err := emojisPerCharacter(charName, fmt.Sprintf("%d", i))
122 | if err != nil {
123 | break
124 | }
125 | emojiList.Emojis = append(emojiList.Emojis, emojiPath)
126 | }
127 | return emojiList, nil
128 | }
129 |
130 | func emojisPerCharacter(name string, index string) (string, error) {
131 | name = strings.ReplaceAll(name, " ", "_")
132 | emojiURL := fmt.Sprintf("%scharacters/emojis/%s/%s.png", cdnURL, name, index)
133 | resp, err := http.Get(emojiURL)
134 | if err != nil {
135 | return "", err
136 | }
137 | defer resp.Body.Close()
138 |
139 | if resp.StatusCode != http.StatusOK {
140 | return "", fmt.Errorf("Failed to fetch emoji")
141 | }
142 |
143 | return emojiURL, nil
144 | }
--------------------------------------------------------------------------------
/handlers/code_handler.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 | "net/http"
6 | "api/models"
7 | )
8 |
9 | func CodesHandler(codes []models.Code) gin.HandlerFunc {
10 | return func(c *gin.Context) {
11 | var codesWithRewards []gin.H
12 |
13 | for _, code := range codes {
14 | codesWithRewards = append(codesWithRewards, gin.H{
15 | "name": code.Name,
16 | "reward": code.Reward,
17 | })
18 | }
19 |
20 | c.JSON(http.StatusOK, gin.H{"codes": codesWithRewards})
21 | }
22 | }
--------------------------------------------------------------------------------
/handlers/common_handler.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 | "net/http"
6 | "api/models"
7 | )
8 |
9 | var (
10 | docsURL = "https://github.com/whosneksio/resonance.rest/blob/main/README.md"
11 | cdnURL = "http://cdn.resonance.rest/"
12 | )
13 |
14 | func HomeHandler(characters []models.Character, attributes []models.Attribute, weapons map[string][]models.Weapon, echoes []models.Echo) gin.HandlerFunc {
15 | return func(c *gin.Context) {
16 | c.JSON(http.StatusOK, gin.H{
17 | "version": "2.0",
18 | "statistics": gin.H{
19 | "attributes": len(attributes),
20 | "characters": len(characters),
21 | "weapons": len(weapons),
22 | "echoes": len(echoes),
23 | },
24 | })
25 | }
26 | }
27 |
28 | func NotFoundHandler(c *gin.Context, message ...string) {
29 | msg := "Not found"
30 | if len(message) > 0 {
31 | msg = message[0]
32 | }
33 | c.JSON(http.StatusNotFound, gin.H{"status": "error", "message": msg})
34 | }
35 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/handlers/echo_handler.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 | "net/http"
6 | "strings"
7 | "api/models"
8 | )
9 |
10 | func ListEchoesHandler(echoes []models.Echo) gin.HandlerFunc {
11 | return func(c *gin.Context) {
12 | var echoNames []string
13 | for _, echo := range echoes {
14 | echoNames = append(echoNames, echo.Name)
15 | }
16 | c.JSON(http.StatusOK, gin.H{"echoes": echoNames})
17 | }
18 | }
19 |
20 | func GetEchoHandler(echoes []models.Echo) gin.HandlerFunc {
21 | return func(c *gin.Context) {
22 | name := strings.ReplaceAll(strings.ToLower(c.Param("name")), "_", " ")
23 | for _, echo := range echoes {
24 | if strings.ToLower(echo.Name) == name {
25 | c.JSON(http.StatusOK, echo)
26 | return
27 | }
28 | }
29 | NotFoundHandler(c, "Echo not found")
30 | }
31 | }
--------------------------------------------------------------------------------
/handlers/sonata_handler.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 | "net/http"
6 | "strings"
7 | "api/models"
8 | )
9 |
10 | func ListSonatasHandler(sonatas []models.Sonata) gin.HandlerFunc {
11 | return func(c *gin.Context) {
12 | var sonataNames []string
13 | for _, sonata := range sonatas {
14 | sonataNames = append(sonataNames, sonata.Name)
15 | }
16 | c.JSON(http.StatusOK, gin.H{"sonatas": sonataNames})
17 | }
18 | }
19 |
20 | func GetSonataHandler(sonatas []models.Sonata) gin.HandlerFunc {
21 | return func(c *gin.Context) {
22 | name := strings.ReplaceAll(strings.ToLower(c.Param("name")), "_", " ")
23 | for _, sonata := range sonatas {
24 | if strings.ToLower(sonata.Name) == name {
25 | c.JSON(http.StatusOK, sonata)
26 | return
27 | }
28 | }
29 | NotFoundHandler(c, "Sonata not found")
30 | }
31 | }
--------------------------------------------------------------------------------
/handlers/stat_handler.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 | "net/http"
6 | "strings"
7 | "api/models"
8 | )
9 |
10 | func ListStatsHandler(stats []models.Stat) gin.HandlerFunc {
11 | return func(c *gin.Context) {
12 | var statNames []string
13 | for _, stat := range stats {
14 | statNames = append(statNames, stat.Name)
15 | }
16 | c.JSON(http.StatusOK, gin.H{"stats": statNames})
17 | }
18 | }
19 |
20 | func GetStatHandler(stats []models.Stat) gin.HandlerFunc {
21 | return func(c *gin.Context) {
22 | name := strings.ReplaceAll(strings.ToLower(c.Param("name")), "_", " ")
23 | for _, stat := range stats {
24 | if strings.ToLower(stat.Name) == name {
25 | c.JSON(http.StatusOK, stat)
26 | return
27 | }
28 | }
29 | NotFoundHandler(c, "Stat not found")
30 | }
31 | }
--------------------------------------------------------------------------------
/handlers/substat_handler.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 | "net/http"
6 | "strings"
7 | "api/models"
8 | )
9 |
10 | func ListSubstatsHandler(substats []models.Substat) gin.HandlerFunc {
11 | return func(c *gin.Context) {
12 | var substatNames []string
13 | for _, substat := range substats {
14 | substatNames = append(substatNames, substat.Name)
15 | }
16 | c.JSON(http.StatusOK, gin.H{"substats": substatNames})
17 | }
18 | }
19 |
20 | func GetSubstatHandler(substats []models.Substat) gin.HandlerFunc {
21 | return func(c *gin.Context) {
22 | name := strings.ReplaceAll(strings.ToLower(c.Param("name")), "_", " ")
23 | for _, substat := range substats {
24 | if strings.ToLower(substat.Name) == name {
25 | c.JSON(http.StatusOK, substat)
26 | return
27 | }
28 | }
29 | NotFoundHandler(c, "Substat not found")
30 | }
31 | }
--------------------------------------------------------------------------------
/handlers/weapon_handler.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 | "net/http"
6 | "fmt"
7 | "io"
8 | "strings"
9 | "api/models"
10 | )
11 |
12 | func ListWeaponTypesHandler(weapons map[string][]models.Weapon) gin.HandlerFunc {
13 | return func(c *gin.Context) {
14 | var weaponTypes []string
15 | for weaponType := range weapons {
16 | weaponTypes = append(weaponTypes, weaponType)
17 | }
18 | c.JSON(http.StatusOK, gin.H{"types": weaponTypes})
19 | }
20 | }
21 |
22 | func ListWeaponsHandler(weapons map[string][]models.Weapon) gin.HandlerFunc {
23 | return func(c *gin.Context) {
24 | weaponType := strings.ToLower(c.Param("type"))
25 |
26 | weaponsOfType, ok := weapons[weaponType]
27 | if !ok {
28 | NotFoundHandler(c, "Weapon type not found")
29 | return
30 | }
31 |
32 | var weaponNames []string
33 | for _, weapon := range weaponsOfType {
34 | weaponNames = append(weaponNames, weapon.Name)
35 | }
36 |
37 | c.JSON(http.StatusOK, gin.H{"weapons": weaponNames})
38 | }
39 | }
40 |
41 | func GetWeaponHandler(weapons map[string][]models.Weapon) gin.HandlerFunc {
42 | return func(c *gin.Context) {
43 | weaponType := strings.ToLower(c.Param("type"))
44 | weaponName := strings.ToLower(strings.ReplaceAll(c.Param("name"), "_", " "))
45 |
46 | weaponsOfType, ok := weapons[weaponType]
47 | if !ok {
48 | NotFoundHandler(c, "Weapon type not found")
49 | return
50 | }
51 |
52 | for i := range weaponsOfType {
53 | if strings.ToLower(weaponsOfType[i].Name) == weaponName {
54 | c.JSON(http.StatusOK, weaponsOfType[i])
55 | return
56 | }
57 | }
58 |
59 | NotFoundHandler(c, "Weapon not found")
60 | }
61 | }
62 |
63 | func WeaponIconHandler(c *gin.Context) {
64 | weaponType := strings.ToLower(c.Param("type"))
65 | weaponName := strings.ToLower(strings.ReplaceAll(c.Param("name"), "_", " "))
66 | weaponName = strings.ReplaceAll(weaponName, " ", "_")
67 |
68 | remoteURL := fmt.Sprintf("%sweapons/%s/%s.png", cdnURL, weaponType, weaponName)
69 | resp, err := http.Get(remoteURL)
70 | if err != nil {
71 | NotFoundHandler(c, "Failed to fetch file")
72 | return
73 | }
74 | defer resp.Body.Close()
75 |
76 | if resp.StatusCode != http.StatusOK {
77 | NotFoundHandler(c, "Failed to fetch file")
78 | return
79 | }
80 |
81 | c.Header("Content-Type", "image/png")
82 | _, err = io.Copy(c.Writer, resp.Body)
83 | if err != nil {
84 | NotFoundHandler(c, "Failed to serve file")
85 | return
86 | }
87 | }
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 |
6 | "github.com/gin-contrib/cors"
7 | "github.com/gin-gonic/gin"
8 | "api/handlers"
9 | "api/models"
10 | "api/utils"
11 | )
12 |
13 | var docsURL = "https://github.com/whosneksio/resonance.rest/blob/main/README.md"
14 |
15 | func main() {
16 | r := gin.Default()
17 |
18 | r.Use(utils.LowercaseMiddleware())
19 | r.Use(utils.RateLimitMiddleware())
20 |
21 | config := cors.DefaultConfig()
22 | config.AllowAllOrigins = true
23 | r.Use(cors.New(config))
24 |
25 | // Load data
26 | characters, err := utils.LoadCharacters("data/characters")
27 | if err != nil {
28 | log.Fatalf("Error loading characters: %v", err)
29 | }
30 |
31 | attributes, err := utils.LoadAttributes("data/attributes.json")
32 | if err != nil {
33 | log.Fatalf("Error loading attributes: %v", err)
34 | }
35 |
36 | weapons, err := utils.LoadWeapons("data/weapons.json")
37 | if err != nil {
38 | log.Fatalf("Error loading weapons: %v", err)
39 | }
40 |
41 | echoes, err := utils.LoadEchoes("data/echoes.json")
42 | if err != nil {
43 | log.Fatalf("Error loading echoes: %v", err)
44 | }
45 |
46 | sonatas, err := utils.LoadSonatas("data/sonatas.json")
47 | if err != nil {
48 | log.Fatalf("Error loading sonatas: %v", err)
49 | }
50 |
51 | stats, err := utils.LoadStats("data/echoes/stats.json")
52 | if err != nil {
53 | log.Fatalf("Error loading stats: %v", err)
54 | }
55 |
56 | substats, err := utils.LoadSubstats("data/echoes/substats.json")
57 | if err != nil {
58 | log.Fatalf("Error loading substats: %v", err)
59 | }
60 |
61 | codes, err := utils.LoadCodes("data/codes.json")
62 | if err != nil {
63 | log.Fatalf("Error loading codes: %v", err)
64 | }
65 |
66 | setupRoutes(r, characters, attributes, weapons, echoes, sonatas, stats, substats, codes)
67 |
68 | if err := r.Run(":8080"); err != nil {
69 | log.Fatalf("Failed to run server: %v", err)
70 | }
71 | }
72 |
73 | func setupRoutes(r *gin.Engine, characters []models.Character, attributes []models.Attribute, weapons map[string][]models.Weapon, echoes []models.Echo, sonatas []models.Sonata, stats []models.Stat, substats []models.Substat, codes []models.Code) {
74 | r.GET("/", handlers.HomeHandler(characters, attributes, weapons, echoes))
75 |
76 | r.NoRoute(func(c *gin.Context) {handlers.NotFoundHandler(c, "Route not found")})
77 |
78 | r.GET("/codes", handlers.CodesHandler(codes))
79 |
80 |
81 | // Character routes
82 | r.GET("/characters", handlers.ListCharactersHandler(characters))
83 | r.GET("/characters/:name", handlers.GetCharacterHandler(characters))
84 | r.GET("/characters/:name/emojis", handlers.CharacterEmojisHandler)
85 | r.GET("/characters/:name/emojis/:index", handlers.CharacterEmojiHandler)
86 | r.GET("/characters/:name/:imagetype", handlers.CharacterImageHandler)
87 |
88 | // Attribute routes
89 | r.GET("/attributes", handlers.ListAttributesHandler(attributes))
90 | r.GET("/attributes/:name", handlers.GetAttributeHandler(attributes))
91 | r.GET("/attributes/:name/icon", handlers.AttributeIconHandler)
92 |
93 | // Weapon routes
94 | r.GET("/weapons", handlers.ListWeaponTypesHandler(weapons))
95 | r.GET("/weapons/:type", handlers.ListWeaponsHandler(weapons))
96 | r.GET("/weapons/:type/:name", handlers.GetWeaponHandler(weapons))
97 | r.GET("/weapons/:type/:name/icon", handlers.WeaponIconHandler)
98 |
99 | // Echo routes
100 | r.GET("/echoes", handlers.ListEchoesHandler(echoes))
101 | r.GET("/echoes/:name", handlers.GetEchoHandler(echoes))
102 |
103 | // Sonata routes
104 | r.GET("/echoes/sonatas", handlers.ListSonatasHandler(sonatas))
105 | r.GET("/echoes/sonatas/:name", handlers.GetSonataHandler(sonatas))
106 |
107 | // Stat routes
108 | r.GET("/echoes/stats", handlers.ListStatsHandler(stats))
109 | r.GET("/echoes/stats/:name", handlers.GetStatHandler(stats))
110 |
111 | // Substat routes
112 | r.GET("/echoes/substats", handlers.ListSubstatsHandler(substats))
113 | r.GET("/echoes/substats/:name", handlers.GetSubstatHandler(substats))
114 |
115 |
116 | }
--------------------------------------------------------------------------------
/models/attribute.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | type Attribute struct {
4 | Name string `json:"name,omitempty"`
5 | Characters []Character `json:"characters,omitempty"`
6 | }
--------------------------------------------------------------------------------
/models/character.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | type Character struct {
4 | Name string `json:"name"`
5 | Quote string `json:"quote,omitempty"`
6 | Attribute string `json:"attribute,omitempty"`
7 | Weapon string `json:"weapon,omitempty"`
8 | Rarity int `json:"rarity,omitempty"`
9 | Class string `json:"class,omitempty"`
10 | Birthplace string `json:"birthplace,omitempty"`
11 | Birthday string `json:"birthday,omitempty"`
12 | }
--------------------------------------------------------------------------------
/models/code.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | type Code struct {
4 | Name string `json:"name,omitempty"`
5 | Reward string `json:"reward,omitempty"`
6 | }
--------------------------------------------------------------------------------
/models/echo.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | type Echo struct {
4 | Name string `json:"name,omitempty"`
5 | Cost int `json:"cost,omitempty"`
6 | SonataEffects []string `json:"sonataEffects,omitempty"`
7 | Outline string `json:"outline,omitempty"`
8 | Description string `json:"description,omitempty"`
9 | Ranks []interface{} `json:"ranks,omitempty"`
10 | Cooldown string `json:"cooldown,omitempty"`
11 | }
--------------------------------------------------------------------------------
/models/emoji.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | type Emojis struct {
4 | Emojis []string `json:"emojis,omitempty"`
5 | }
--------------------------------------------------------------------------------
/models/sonata.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | type Sonata struct {
4 | Name string `json:"name,omitempty"`
5 | TwoPiece string `json:"twoPiece,omitempty"`
6 | FivePiece string `json:"fivePiece,omitempty"`
7 | }
--------------------------------------------------------------------------------
/models/stat.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | type Stat struct {
4 | Cost int `json:"cost,omitempty"`
5 | Name string `json:"name,omitempty"`
6 | Primary []struct {
7 | Name string `json:"name,omitempty"`
8 | Ranks []float64 `json:"ranks,omitempty"`
9 | } `json:"primary,omitempty"`
10 | Secondary []struct {
11 | Name string `json:"name,omitempty"`
12 | Ranks []float64 `json:"ranks,omitempty"`
13 | } `json:"secondary,omitempty"`
14 | }
--------------------------------------------------------------------------------
/models/substat.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | type Substat struct {
4 | Name string `json:"name,omitempty"`
5 | Min float64 `json:"min,omitempty"`
6 | Max float64 `json:"max,omitempty"`
7 | }
--------------------------------------------------------------------------------
/models/weapon.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | type Weapon struct {
4 | Name string `json:"name,omitempty"`
5 | Description string `json:"description,omitempty"`
6 | Type string `json:"type,omitempty"`
7 | Rarity int `json:"rarity,omitempty"`
8 | Stats struct {
9 | Attack int `json:"atk,omitempty"`
10 | Substat struct {
11 | SubName string `json:"name,omitempty"`
12 | SubValue string `json:"value,omitempty"`
13 | } `json:"substat,omitempty"`
14 | } `json:"stats,omitempty"`
15 | Skill struct {
16 | Name string `json:"name,omitempty"`
17 | Description string `json:"description,omitempty"`
18 | Ranks []struct {
19 | Zero string `json:"0,omitempty"`
20 | One string `json:"1,omitempty"`
21 | Three string `json:"3,omitempty"`
22 | Four string `json:"4,omitempty"`
23 | Five string `json:"5,omitempty"`
24 | } `json:"ranks,omitempty"`
25 | } `json:"skill,omitempty"`
26 | }
--------------------------------------------------------------------------------
/utils/loaders.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "encoding/json"
5 | "os"
6 | "api/models"
7 | "fmt"
8 | "io/ioutil"
9 | "path/filepath"
10 | "strings"
11 | )
12 |
13 | func loadJSONFile(filename string, v interface{}) error {
14 | file, err := os.Open(filename)
15 | if err != nil {
16 | return fmt.Errorf("error opening file: %v", err)
17 | }
18 | defer file.Close()
19 |
20 | return json.NewDecoder(file).Decode(v)
21 | }
22 |
23 | func LoadCharacters(dirPath string) ([]models.Character, error) {
24 | var characters []models.Character
25 |
26 | files, err := ioutil.ReadDir(dirPath)
27 | if err != nil {
28 | return nil, fmt.Errorf("error reading directory: %v", err)
29 | }
30 |
31 | for _, file := range files {
32 | if file.IsDir() || !strings.HasSuffix(file.Name(), ".json") {
33 | continue
34 | }
35 |
36 | filePath := filepath.Join(dirPath, file.Name())
37 | var character models.Character
38 | if err := loadJSONFile(filePath, &character); err != nil {
39 | return nil, fmt.Errorf("error loading character from %s: %v", file.Name(), err)
40 | }
41 |
42 | character.Name = strings.ReplaceAll(character.Name, " ", "%20")
43 |
44 | characters = append(characters, character)
45 | }
46 |
47 | return characters, nil
48 | }
49 |
50 | func LoadAttributes(filename string) ([]models.Attribute, error) {
51 | var attributes []models.Attribute
52 | err := loadJSONFile(filename, &attributes)
53 | return attributes, err
54 | }
55 |
56 | func LoadWeapons(filename string) (map[string][]models.Weapon, error) {
57 | var weapons map[string][]models.Weapon
58 | err := loadJSONFile(filename, &weapons)
59 | return weapons, err
60 | }
61 |
62 | func LoadEchoes(filename string) ([]models.Echo, error) {
63 | var echoes []models.Echo
64 | err := loadJSONFile(filename, &echoes)
65 | return echoes, err
66 | }
67 |
68 | func LoadSonatas(filename string) ([]models.Sonata, error) {
69 | var sonatas []models.Sonata
70 | err := loadJSONFile(filename, &sonatas)
71 | return sonatas, err
72 | }
73 |
74 | func LoadStats(filename string) ([]models.Stat, error) {
75 | var stats []models.Stat
76 | err := loadJSONFile(filename, &stats)
77 | return stats, err
78 | }
79 |
80 | func LoadSubstats(filename string) ([]models.Substat, error) {
81 | var substats []models.Substat
82 | err := loadJSONFile(filename, &substats)
83 | return substats, err
84 | }
85 |
86 | func LoadCodes(filename string) ([]models.Code, error) {
87 | var codes []models.Code
88 | err := loadJSONFile(filename, &codes)
89 | return codes, err
90 | }
--------------------------------------------------------------------------------
/utils/middleware.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 | "net/http"
6 | "strings"
7 | "sync"
8 | "time"
9 | "net/url"
10 | )
11 |
12 | var (
13 | rateLimit = 200
14 | requestCount = make(map[string]int)
15 | mutex sync.Mutex
16 | )
17 |
18 | func RateLimitMiddleware() gin.HandlerFunc {
19 | return func(c *gin.Context) {
20 | ip := c.ClientIP()
21 |
22 | mutex.Lock()
23 | defer mutex.Unlock()
24 |
25 | requestCount[ip]++
26 |
27 | if requestCount[ip] > rateLimit {
28 | c.JSON(http.StatusTooManyRequests, gin.H{
29 | "status": "error",
30 | "error": "Rate limit exceeded",
31 | })
32 | c.Abort()
33 | return
34 | }
35 |
36 | go func() {
37 | time.Sleep(time.Minute)
38 | mutex.Lock()
39 | defer mutex.Unlock()
40 | requestCount[ip] = 0
41 | }()
42 |
43 | c.Next()
44 | }
45 | }
46 |
47 | func LowercaseMiddleware() gin.HandlerFunc {
48 | return func(c *gin.Context) {
49 | c.Request.URL.Path = strings.ToLower(c.Request.URL.Path)
50 |
51 | lowercaseQuery := make(url.Values)
52 | for key, values := range c.Request.URL.Query() {
53 | lowercaseKey := strings.ToLower(key)
54 | for _, value := range values {
55 | lowercaseQuery.Add(lowercaseKey, strings.ToLower(value))
56 | }
57 | }
58 | c.Request.URL.RawQuery = lowercaseQuery.Encode()
59 |
60 | c.Next()
61 | }
62 | }
--------------------------------------------------------------------------------