├── .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 | } --------------------------------------------------------------------------------