├── .gitignore ├── LICENSE ├── config.json ├── dispatch.js ├── guides ├── 3202.js ├── 9034.js ├── 9044.js ├── 9720.js ├── 9920.js ├── 9920_old.js ├── 9950.js └── 9970.js ├── index.js ├── lib.js ├── manifest.json ├── module.json └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | createManifest.py 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Kasea 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "enabled": true, 3 | "debug": { 4 | "debug": false, 5 | "hp": false, 6 | "abnormal": false, 7 | "skill": false, 8 | "boss": false, 9 | "chat": false, 10 | "dm": false, 11 | "qb": false 12 | }, 13 | "stream": false, 14 | "chat-name": "Guide" 15 | } -------------------------------------------------------------------------------- /dispatch.js: -------------------------------------------------------------------------------- 1 | class DispatchWrapper { 2 | constructor(dispatch) { 3 | this._dispatch = dispatch; 4 | this._hooks = []; 5 | } 6 | 7 | hook(...args) { 8 | this._hooks.push(this._dispatch.hook(...args)); 9 | } 10 | 11 | hookOnce(...args) { 12 | this._dispatch.hookOnce(...args); 13 | } 14 | 15 | unhook(...args) { 16 | throw new Error("unhook not supported for tera-guide"); 17 | } 18 | 19 | _remove_all_hooks() { 20 | for(const hook of this._hooks) this._dispatch.unhook(hook); 21 | } 22 | 23 | get require() { 24 | return this._dispatch.require; 25 | } 26 | 27 | toServer(...args) { return this.send(...args); } 28 | toClient(...args) { return this.send(...args); } 29 | send(...args) { return this._dispatch.send(...args); } 30 | } 31 | 32 | module.exports = DispatchWrapper; -------------------------------------------------------------------------------- /guides/3202.js: -------------------------------------------------------------------------------- 1 | // DAHM -- Written by Vanq 2 | 3 | module.exports = { 4 | 5 | // Out>In 6 | "s-3202-1000-2124-0": [{"type": "text","sub_type": "notification","message": " Out>In"}], 7 | "s-3202-1000-1124-0": [{"type": "text","sub_type": "notification","message": " Out>In"}], 8 | 9 | // In>Out 10 | "s-3202-1000-2121-0": [{"type": "text","sub_type": "notification","message": " In>Out"}], 11 | "s-3202-1000-1121-0": [{"type": "text","sub_type": "notification","message": " In>Out"}], 12 | 13 | // Back Push 14 | "s-3202-1000-2112-0": [{"type": "text","sub_type": "notification","message": " Back Push"}], 15 | "s-3202-1000-1112-0": [{"type": "text","sub_type": "notification","message": " Back Push"}], 16 | 17 | // Front Stun 18 | "s-3202-1000-2105-0": [{"type": "text","sub_type": "notification","message": " Front Stun"}], 19 | "s-3202-1000-1105-0": [{"type": "text","sub_type": "notification","message": " Front Stun"}], 20 | 21 | // Stomp & Spin 22 | "s-3202-1000-2109-0": [{"type": "text","sub_type": "notification","message": " Stomp & Spin"}], 23 | "s-3202-1000-1109-0": [{"type": "text","sub_type": "notification","message": " Stomp & Spin"}], 24 | 25 | // Donut-Wave 26 | "s-3202-1000-2110-0": [{"type": "text","sub_type": "notification","message": " Donut-Wave"}], 27 | "s-3202-1000-1110-0": [{"type": "text","sub_type": "notification","message": " Donut-Wave"}] 28 | 29 | }; -------------------------------------------------------------------------------- /guides/9034.js: -------------------------------------------------------------------------------- 1 | let player, entity, library, effect; 2 | 3 | // Helper Functions 4 | 5 | function single_stage_callout(message, handlers, event, entity) { 6 | if (entity.stage == 0) { 7 | handlers['text']({ 8 | "sub_type": "notification", 9 | "message": message, 10 | }); 11 | } 12 | } 13 | 14 | // First Floor 15 | 16 | // Knockback 17 | let knockbackCounter = 0; 18 | let knockbackTimer; 19 | 20 | function knockback_firstfloor(handlers) { 21 | clearTimeout(knockbackTimer); 22 | knockbackCounter++; 23 | 24 | if(knockbackCounter >= 2) { 25 | handlers['text']({ 26 | "sub_type": "notification", 27 | "message": "KNOCKBACK", 28 | }); 29 | knockbackCounter = 0; 30 | } 31 | 32 | knockbackTimer = setTimeout(()=>{ 33 | knockbackCounter = 0; 34 | }, 5000); 35 | } 36 | 37 | // Third Floor 38 | 39 | // Cage Mechanic 40 | const PizzaA = { 41 | offsets: [0.24, 1.29, 2.33, -2.88, -1.84, -0.8], 42 | distance: 200 43 | }; 44 | 45 | const PizzaB = { 46 | offsets: [-0.26, 0.79, 1.83, 2.9, -2.34, -1.3], 47 | distance: 200 48 | }; 49 | 50 | const PizzaC = { 51 | offsets: [-0.26, 1.29, 2.9, -1.84], 52 | distance: 200 53 | }; 54 | 55 | const CounterPizzaC = { 56 | offsets: [0.24, 2.33, -2.88, -0.8, 0.79, 1.83, -2.34, -1.3], 57 | distance: 200 58 | } 59 | 60 | const Inner = { 61 | offsets: [0.24, 1.29, 2.33, -2.88, -1.84, -0.8, -0.26, 0.79, 1.83, 2.9, -2.34, -1.3], 62 | distance: 275 63 | }; 64 | 65 | const Outer = { 66 | offsets: [0.24, 1.29, 2.33, -2.88, -1.84, -0.8, -0.26, 0.79, 1.83, 2.9, -2.34, -1.3], 67 | distance: 150 68 | }; 69 | 70 | PizzaA.counter = PizzaB; 71 | PizzaB.counter = PizzaA; 72 | PizzaC.counter = CounterPizzaC; 73 | Inner.counter = Outer; 74 | Outer.counter = Inner; 75 | 76 | const Mechanics = { 77 | 1122: { 78 | order: [PizzaA, Inner, Outer, PizzaB, PizzaC], 79 | delays: [0, 1000, 2000, 3000, 4000] 80 | }, 81 | 1123: { 82 | order: [PizzaB, PizzaA, Outer, Inner, PizzaC], 83 | delays: [200, 1200, 2200, 3200, 4200] 84 | }, 85 | 1124: { 86 | order: [Inner, PizzaB, PizzaA, Outer, PizzaC], 87 | delays: [0, 1000, 2000, 3000, 4000] 88 | }, 89 | 1127: { 90 | order: [PizzaA, PizzaB, Inner, Outer, PizzaC], 91 | delays: [200, 1200, 2200, 3200, 4200] 92 | } 93 | } 94 | 95 | let debuffs_thirdfloor = [false, false, false, false, false] // False = Blue (Avoid Hit), True = Red (Take Hit) 96 | 97 | function cage_mechanic_thirdfloor(id, handlers, event, entity) { 98 | let mechanic = Mechanics[id]; 99 | 100 | if (mechanic && entity.stage == 0) { 101 | let flower_id = 559; 102 | 103 | for (let i in mechanic.order) { 104 | let pattern = !debuffs_thirdfloor[i] ? mechanic.order[i] : mechanic.order[i].counter; 105 | 106 | for (let offset of pattern.offsets) { 107 | handlers['spawn']({ 108 | "id": flower_id, 109 | "delay": mechanic.delays[i], 110 | "sub_delay": mechanic.delays[i] + 1900, 111 | "distance": pattern.distance, 112 | "offset": offset 113 | }, entity); 114 | } 115 | flower_id = flower_id == 559 ? 556 : 559; 116 | } 117 | } 118 | } 119 | 120 | function cage_set_debuff(id, bool) { 121 | debuffs_thirdfloor[id] = bool; 122 | } 123 | 124 | // Fifth Floor 125 | 126 | // Debuff Tracker 127 | 128 | let debuff_tracker_started = false; 129 | 130 | let debuffs_fifthfloor = { 131 | 90340501: {name: "FIRE", count: 0}, 132 | 90340502: {name: "ICE", count: 0}, 133 | }; 134 | 135 | let debuff_call_event = null; 136 | 137 | function start_fifthfloor(handlers, event, entity, dispatch) { 138 | const abnormality_change = (added, event) => { 139 | if ((player.isMe(event.target) || player.playersInParty.includes(event.target.toString())) && debuffs_fifthfloor[event.id]) { 140 | if (added) { 141 | debuffs_fifthfloor[event.id].count++; 142 | } else { 143 | debuffs_fifthfloor[event.id].count--; 144 | } 145 | 146 | if (debuff_call_event) { 147 | clearTimeout(debuff_call_event); 148 | } 149 | 150 | debuff_call_event = setTimeout(() => { 151 | let buffer = []; 152 | 153 | for (const entry of Object.values(debuffs_fifthfloor)) { 154 | buffer.push(`${entry.name}: ${entry.count}`); 155 | } 156 | 157 | handlers['text']({ 158 | "sub_type": "notification", 159 | "message": buffer.join(" - ") 160 | }); 161 | 162 | debuff_call_event = null; 163 | }, 200); 164 | } 165 | }; 166 | 167 | if (!debuff_tracker_started) { 168 | dispatch.hook('S_ABNORMALITY_BEGIN', 2, abnormality_change.bind(null, true)); 169 | dispatch.hook('S_ABNORMALITY_END', 1, abnormality_change.bind(null, false)); 170 | debuff_tracker_started = true; 171 | } 172 | } 173 | 174 | // Mobs Wave Attack Flowers 175 | 176 | let mobflowers_fifthfloor = []; 177 | for (let distance = 20; distance < 480; distance += 20) { 178 | mobflowers_fifthfloor.push({ 179 | "type": "spawn", 180 | "id": 603, 181 | "sub_delay": 3000, 182 | "distance": distance, 183 | "offset": 0.35 184 | }); 185 | mobflowers_fifthfloor.push({ 186 | "type": "spawn", 187 | "id": 603, 188 | "sub_delay": 3000, 189 | "distance": distance, 190 | "offset": -0.35 191 | }); 192 | } 193 | for (let offset = 0.35; offset >= -0.35; offset -= 0.05) { 194 | mobflowers_fifthfloor.push({ 195 | "type": "spawn", 196 | "id": 603, 197 | "sub_delay": 3000, 198 | "distance": 480, 199 | "offset": offset 200 | }); 201 | } 202 | 203 | // 7th Floor 204 | 205 | // Rings Flowers 206 | 207 | function rings_seventhfloor(handlers, event, entity) { 208 | let shield_loc = entity['loc'].clone(); 209 | shield_loc.w = entity['loc'].w; 210 | 211 | library.applyDistance(shield_loc, 50); 212 | 213 | for (let angle = -Math.PI; angle <= Math.PI; angle += 2 * Math.PI / 40) { 214 | handlers['spawn']({ 215 | "id": 603, 216 | "sub_delay": 5000, 217 | "distance": 200, 218 | "offset": angle 219 | }, {loc: shield_loc}); 220 | } 221 | 222 | for (let angle = -Math.PI; angle <= Math.PI; angle += 2 * Math.PI / 70) { 223 | handlers['spawn']({ 224 | "id": 603, 225 | "sub_delay": 5000, 226 | "distance": 350, 227 | "offset": angle 228 | }, {loc: shield_loc}); 229 | } 230 | 231 | } 232 | 233 | // Rings IN + OUT and OUT + IN Flowers 234 | 235 | let rings_inout_seventhfloor = []; 236 | for (let angle = -Math.PI; angle <= Math.PI; angle += 2 * Math.PI / 40) { 237 | rings_inout_seventhfloor.push({ 238 | "type": "spawn", 239 | "id": 603, 240 | "sub_delay": 6000, 241 | "distance": 250, 242 | "offset": angle 243 | }); 244 | } 245 | 246 | // Lasers Flowers and Signs 247 | 248 | let lasers_markers_seventhfloor = []; 249 | let inverted_lasers_markers_seventhfloor = []; 250 | const sign_offsets_seventhfloor = [-0.32, -0.94, -1.57, -2.2, -2.83, 2.83, 2.2, 1.57, 0.94, 0.32]; 251 | 252 | for (let offset of sign_offsets_seventhfloor) { 253 | const event = { 254 | "type": "spawn", 255 | "sub_type": "build_object", 256 | "id": 1, 257 | "sub_delay": 4000, 258 | "distance": 450, 259 | "ownerName": "SAFE SPOT", 260 | "message": "SAFE", 261 | "offset": offset 262 | } 263 | 264 | lasers_markers_seventhfloor.push(event); 265 | inverted_lasers_markers_seventhfloor.push(event); 266 | } 267 | 268 | for (let distance = 175; distance <= 425; distance += 25) { 269 | lasers_markers_seventhfloor.push({ 270 | "type": "spawn", 271 | "id": 603, 272 | "sub_delay": 4000, 273 | "distance": distance, 274 | "offset": 0 275 | }); 276 | lasers_markers_seventhfloor.push({ 277 | "type": "spawn", 278 | "id": 603, 279 | "sub_delay": 4000, 280 | "distance": distance, 281 | "offset": 1.25 282 | }); 283 | lasers_markers_seventhfloor.push({ 284 | "type": "spawn", 285 | "id": 603, 286 | "sub_delay": 4000, 287 | "distance": distance, 288 | "offset": 2.5 289 | }); 290 | lasers_markers_seventhfloor.push({ 291 | "type": "spawn", 292 | "id": 603, 293 | "sub_delay": 4000, 294 | "distance": distance, 295 | "offset": -2.5 296 | }); 297 | lasers_markers_seventhfloor.push({ 298 | "type": "spawn", 299 | "id": 603, 300 | "sub_delay": 4000, 301 | "distance": distance, 302 | "offset": -1.25 303 | }); 304 | 305 | inverted_lasers_markers_seventhfloor.push({ 306 | "type": "spawn", 307 | "id": 603, 308 | "sub_delay": 4000, 309 | "distance": distance, 310 | "offset": 0.62 311 | }); 312 | inverted_lasers_markers_seventhfloor.push({ 313 | "type": "spawn", 314 | "id": 603, 315 | "sub_delay": 4000, 316 | "distance": distance, 317 | "offset": 1.87 318 | }); 319 | inverted_lasers_markers_seventhfloor.push({ 320 | "type": "spawn", 321 | "id": 603, 322 | "sub_delay": 4000, 323 | "distance": distance, 324 | "offset": 3.12 325 | }); 326 | inverted_lasers_markers_seventhfloor.push({ 327 | "type": "spawn", 328 | "id": 603, 329 | "sub_delay": 4000, 330 | "distance": distance, 331 | "offset": -1.88 332 | }); 333 | inverted_lasers_markers_seventhfloor.push({ 334 | "type": "spawn", 335 | "id": 603, 336 | "sub_delay": 4000, 337 | "distance": distance, 338 | "offset": -0.63 339 | }); 340 | } 341 | 342 | module.exports = { 343 | load(dispatch) { 344 | ({ player, entity, library, effect } = dispatch.require.library); 345 | }, 346 | 347 | // First Floor 348 | 349 | // Clone Mechanic 350 | "ab-434-1000-90340105": [{"type": "text","sub_type": "notification","message": "STUN IT"}], 351 | 352 | // Backstep + Knockback 353 | "s-434-1000-1304": [{"type": "text","sub_type": "notification","message": "BACKSTEP + KNOCKBACK"}], 354 | 355 | // Not Enraged 356 | "s-434-1000-1102": [{"type": "func","func": knockback_firstfloor}], 357 | "s-434-1000-1105": [{"type": "text","sub_type": "notification","message": "PUKE"}], 358 | "s-434-1000-1203": [{"type": "text","sub_type": "notification","message": "SLEEP"}], 359 | 360 | // Enraged 361 | "s-434-1000-2102": [{"type": "func","func": knockback_firstfloor}], 362 | "s-434-1000-2105": [{"type": "text","sub_type": "notification","message": "PUKE"}], 363 | "s-434-1000-2203": [{"type": "text","sub_type": "notification","message": "SLEEP"}], 364 | 365 | // Second Floor 366 | 367 | // Not Enraged 368 | "s-434-2000-1101": [{"type": "text","sub_type": "notification","message": "SMASH COMING"}], 369 | "s-434-2000-1102": [{"type": "text","sub_type": "notification","message": "SPIN"}], 370 | "s-434-2000-1107": [{"type": "text","sub_type": "notification","message": "BACK"}], 371 | "s-434-2000-1108": [{"type": "text","sub_type": "notification","message": "FRONT"}], 372 | "s-434-2000-1109": [{"type": "func","func": single_stage_callout.bind(null, "BACK SPIN")}], 373 | "s-434-2000-1110": [{"type": "text","sub_type": "notification","message": "OUT"}], 374 | "s-434-2000-1119": [{"type": "text","sub_type": "notification","message": "PULL"}], 375 | "s-434-2000-1122": [{"type": "text","sub_type": "notification","message": "IN"}], 376 | 377 | 378 | // Enraged 379 | "s-434-2000-2101": [{"type": "text","sub_type": "notification","message": "SMASH COMING"}], 380 | "s-434-2000-2102": [{"type": "text","sub_type": "notification","message": "SPIN"}], 381 | "s-434-2000-2107": [{"type": "text","sub_type": "notification","message": "BACK"}], 382 | "s-434-2000-2108": [{"type": "text","sub_type": "notification","message": "FRONT"}], 383 | "s-434-2000-2109": [{"type": "func","func": single_stage_callout.bind(null, "BACK SPIN")}], 384 | "s-434-2000-2110": [{"type": "text","sub_type": "notification","message": "OUT"}], 385 | "s-434-2000-2119": [{"type": "text","sub_type": "notification","message": "PULL"}], 386 | "s-434-2000-2122": [{"type": "text","sub_type": "notification","message": "IN"}], 387 | 388 | // Third Floor 389 | 390 | // Cage Mechanic 391 | "s-434-3000-1122": [{"type": "func","func": cage_mechanic_thirdfloor.bind(null, 1122)}], 392 | "s-434-3000-1123": [{"type": "func","func": cage_mechanic_thirdfloor.bind(null, 1123)}], 393 | "s-434-3000-1124": [{"type": "func","func": cage_mechanic_thirdfloor.bind(null, 1124)}], 394 | "s-434-3000-1127": [{"type": "func","func": cage_mechanic_thirdfloor.bind(null, 1127)}], 395 | 396 | "ae-0-0-90340306": [{"type": "func","func": cage_set_debuff.bind(null, 0, true)}], 397 | "ae-0-0-90340307": [{"type": "func","func": cage_set_debuff.bind(null, 0, false)}], 398 | "ae-0-0-90340308": [{"type": "func","func": cage_set_debuff.bind(null, 1, true)}], 399 | "ae-0-0-90340309": [{"type": "func","func": cage_set_debuff.bind(null, 1, false)}], 400 | "ae-0-0-90340310": [{"type": "func","func": cage_set_debuff.bind(null, 2, true)}], 401 | "ae-0-0-90340311": [{"type": "func","func": cage_set_debuff.bind(null, 2, false)}], 402 | "ae-0-0-90340312": [{"type": "func","func": cage_set_debuff.bind(null, 3, true)}], 403 | "ae-0-0-90340313": [{"type": "func","func": cage_set_debuff.bind(null, 3, false)}], 404 | "ae-0-0-90340314": [{"type": "func","func": cage_set_debuff.bind(null, 4, true)}], 405 | "ae-0-0-90340315": [{"type": "func","func": cage_set_debuff.bind(null, 4, false)}], 406 | 407 | // Not Enraged 408 | "s-434-3000-1112": [{"type": "func","func": single_stage_callout.bind(null, "STAB + KNOCKUP")}], 409 | "s-434-3000-1130": [{"type": "text","sub_type": "notification","message": "LEFT SWIPE"}, {"type":"spawn", "sub_type": "build_object", "id": 1, "sub_delay": 2000, "distance": 100, "offset": 2.3, "ownerName": "SAFE SPOT", "message": "SAFE"}, {"type":"spawn", "sub_type": "build_object", "id": 1, "sub_delay": 2000, "distance": 100, "offset": 1, "ownerName": "SAFE SPOT", "message": "SAFE"}, {"type":"spawn", "sub_type": "item", "id": 98260, "sub_delay": 2000, "distance": 100, "offset": 2.3}, {"type":"spawn", "sub_type": "item", "id": 98260, "sub_delay": 2000, "distance": 100, "offset": 1}], 410 | "s-434-3000-1131": [{"type": "text","sub_type": "notification","message": "RIGHT SWIPE"}, {"type":"spawn", "sub_type": "build_object", "id": 1, "sub_delay": 2000, "distance": 100, "offset": -2.3, "ownerName": "SAFE SPOT", "message": "SAFE"}, {"type":"spawn", "sub_type": "build_object", "id": 1, "sub_delay": 2000, "distance": 100, "offset": -1, "ownerName": "SAFE SPOT", "message": "SAFE"}, {"type":"spawn", "sub_type": "item", "id": 98260, "sub_delay": 2000, "distance": 100, "offset": -2.3}, {"type":"spawn", "sub_type": "item", "id": 98260, "sub_delay": 2000, "distance": 100, "offset": -1}], 411 | "s-434-3000-1134": [{"type": "func","func": single_stage_callout.bind(null, "DEBUFF")}], 412 | "s-434-3000-1502": [{"type": "func","func": single_stage_callout.bind(null, "FORCED CAGE")}], 413 | 414 | 415 | // Enraged 416 | "s-434-3000-2112": [{"type": "func","func": single_stage_callout.bind(null, "STAB + KNOCKUP")}], 417 | "s-434-3000-2130": [{"type": "text","sub_type": "notification","message": "LEFT SWIPE"}, {"type":"spawn", "sub_type": "build_object", "id": 1, "sub_delay": 2000, "distance": 100, "offset": 2.3, "ownerName": "SAFE SPOT", "message": "SAFE"}, {"type":"spawn", "sub_type": "build_object", "id": 1, "sub_delay": 2000, "distance": 100, "offset": 1, "ownerName": "SAFE SPOT", "message": "SAFE"}, {"type":"spawn", "sub_type": "item", "id": 98260, "sub_delay": 2000, "distance": 100, "offset": 2.3}, {"type":"spawn", "sub_type": "item", "id": 98260, "sub_delay": 2000, "distance": 100, "offset": 1}], 418 | "s-434-3000-2131": [{"type": "text","sub_type": "notification","message": "RIGHT SWIPE"}, {"type":"spawn", "sub_type": "build_object", "id": 1, "sub_delay": 2000, "distance": 100, "offset": -2.3, "ownerName": "SAFE SPOT", "message": "SAFE"}, {"type":"spawn", "sub_type": "build_object", "id": 1, "sub_delay": 2000, "distance": 100, "offset": -1, "ownerName": "SAFE SPOT", "message": "SAFE"}, {"type":"spawn", "sub_type": "item", "id": 98260, "sub_delay": 2000, "distance": 100, "offset": -2.3}, {"type":"spawn", "sub_type": "item", "id": 98260, "sub_delay": 2000, "distance": 100, "offset": -1}], 419 | "s-434-3000-2134": [{"type": "func","func": single_stage_callout.bind(null, "DEBUFF")}], 420 | "s-434-3000-2502": [{"type": "func","func": single_stage_callout.bind(null, "FORCED CAGE")}], 421 | 422 | // Fourth Floor 423 | 424 | // Not Enraged 425 | "s-434-4000-1102": [{"type": "func","func": single_stage_callout.bind(null, "INNER RINGS")}], 426 | "s-434-4000-1103": [{"type": "func","func": single_stage_callout.bind(null, "OUTER RINGS")}], 427 | "s-434-4000-1107": [{"type": "func","func": single_stage_callout.bind(null, "BACK")}], 428 | "s-434-4000-1108": [{"type": "text","sub_type": "notification","message": "LINES"}], 429 | "s-434-4000-1109": [{"type": "func","func": single_stage_callout.bind(null, "SINGLE LASER")}], 430 | "s-434-4000-1114": [{"type": "text","sub_type": "notification","message": "SECONDARY AOE"}], 431 | "s-434-4000-1205": [{"type": "text","sub_type": "notification","message": "360 LASER + WORMS"}], 432 | "s-434-4000-1206": [{"type": "text","sub_type": "notification","message": "TRIPLE LASER"}], 433 | 434 | // Enraged 435 | "s-434-4000-2102": [{"type": "func","func": single_stage_callout.bind(null, "INNER RINGS")}], 436 | "s-434-4000-2103": [{"type": "func","func": single_stage_callout.bind(null, "OUTER RINGS")}], 437 | "s-434-4000-2107": [{"type": "func","func": single_stage_callout.bind(null, "BACK")}], 438 | "s-434-4000-2108": [{"type": "text","sub_type": "notification","message": "LINES"}], 439 | "s-434-4000-2109": [{"type": "func","func": single_stage_callout.bind(null, "SINGLE LASER")}], 440 | "s-434-4000-2114": [{"type": "text","sub_type": "notification","message": "SECONDARY AOE"}], 441 | "s-434-4000-2205": [{"type": "text","sub_type": "notification","message": "360 LASER + WORMS"}], 442 | "s-434-4000-2206": [{"type": "text","sub_type": "notification","message": "TRIPLE LASER"}], 443 | 444 | 445 | // Fifth Floor 446 | 447 | // Debuff Tracker 448 | // "h-434-5000-100": [{"type": "func","func": start_fifthfloor}], 449 | 450 | // Mob Wave Attack 451 | "s-434-5002-3106": mobflowers_fifthfloor, 452 | "s-434-5003-3101": mobflowers_fifthfloor, 453 | 454 | // Not Enraged 455 | "s-434-5000-1103": [{"type": "text","sub_type": "notification","message": "TAIL"}], 456 | "s-434-5000-1104": [{"type": "text","sub_type": "notification","message": "ICE"}], 457 | "s-434-5000-1105": [{"type": "text","sub_type": "notification","message": "FIRE"}], 458 | "s-434-5000-1107": [{"type": "text","sub_type": "notification","message": "DOUBLE PAW"}], 459 | "s-434-5000-1108": [{"type": "text","sub_type": "notification","message": "FIRE SPIN"}], 460 | "s-434-5000-1109": [{"type": "text","sub_type": "notification","message": "ICE SPIN"}], 461 | "s-434-5000-1118": [{"type": "func","func": single_stage_callout.bind(null, "BIG JUMP")}], 462 | "s-434-5000-1119": [{"type": "text","sub_type": "notification","message": "STUN"}], 463 | "s-434-5000-1120": [{"type": "text","sub_type": "notification","message": "STUN"}], 464 | "s-434-5000-1124": [{"type": "text","sub_type": "notification","message": "SMALL JUMP"}], 465 | 466 | // Enraged 467 | "s-434-5000-2103": [{"type": "text","sub_type": "notification","message": "TAIL"}], 468 | "s-434-5000-2104": [{"type": "text","sub_type": "notification","message": "ICE"}], 469 | "s-434-5000-2105": [{"type": "text","sub_type": "notification","message": "FIRE"}], 470 | "s-434-5000-2107": [{"type": "text","sub_type": "notification","message": "DOUBLE PAW"}], 471 | "s-434-5000-2108": [{"type": "text","sub_type": "notification","message": "FIRE SPIN"}], 472 | "s-434-5000-2109": [{"type": "text","sub_type": "notification","message": "ICE SPIN"}], 473 | "s-434-5000-2118": [{"type": "func","func": single_stage_callout.bind(null, "BIG JUMP")}], 474 | "s-434-5000-2119": [{"type": "text","sub_type": "notification","message": "STUN"}], 475 | "s-434-5000-2120": [{"type": "text","sub_type": "notification","message": "STUN"}], 476 | "s-434-5000-2124": [{"type": "text","sub_type": "notification","message": "SMALL JUMP"}], 477 | 478 | // Sixth Floor 479 | 480 | // Crystal Spawn 481 | "dm-0-0-9034601": [{"type": "text","sub_type": "notification","message": "CRYSTAL SPAWNED"}], 482 | 483 | // Not Enraged 484 | "s-434-6000-1101": [{"type": "text","sub_type": "notification","message": "PRISON"}], 485 | "s-434-6000-1103": [{"type": "func","func": single_stage_callout.bind(null, "SLAM")}], 486 | "s-434-6000-1104": [{"type": "func","func": single_stage_callout.bind(null, "SLAM + BACK")}], 487 | "s-434-6000-1106": [{"type": "func","func": single_stage_callout.bind(null, "BARRAGE + SLAM")}], 488 | "s-434-6000-1107": [{"type": "func","func": single_stage_callout.bind(null, "BOMB")}], 489 | "s-434-6000-1108": [{"type": "func","func": single_stage_callout.bind(null, "TRIPLE BOMB")}], 490 | "s-434-6000-1109": [{"type": "func","func": single_stage_callout.bind(null, "SINGLE SWING")}], 491 | "s-434-6000-1110": [{"type": "func","func": single_stage_callout.bind(null, "DOUBLE SWING")}], 492 | "s-434-6000-1113": [{"type": "func","func": single_stage_callout.bind(null, "LASER")}], 493 | "s-434-6000-1133": [{"type": "func","func": single_stage_callout.bind(null, "SLAM")}], 494 | "s-434-6000-1134": [{"type": "func","func": single_stage_callout.bind(null, "SLAM + BACK")}], 495 | 496 | // Enraged 497 | "s-434-6000-2101": [{"type": "text","sub_type": "notification","message": "PRISON"}], 498 | "s-434-6000-2103": [{"type": "func","func": single_stage_callout.bind(null, "SLAM")}], 499 | "s-434-6000-2104": [{"type": "func","func": single_stage_callout.bind(null, "SLAM + BACK")}], 500 | "s-434-6000-2106": [{"type": "func","func": single_stage_callout.bind(null, "BARRAGE + SLAM")}], 501 | "s-434-6000-2107": [{"type": "func","func": single_stage_callout.bind(null, "BOMB")}], 502 | "s-434-6000-2108": [{"type": "func","func": single_stage_callout.bind(null, "TRIPLE BOMB")}], 503 | "s-434-6000-2109": [{"type": "func","func": single_stage_callout.bind(null, "SINGLE SWING")}], 504 | "s-434-6000-2110": [{"type": "func","func": single_stage_callout.bind(null, "DOUBLE SWING")}], 505 | "s-434-6000-2113": [{"type": "func","func": single_stage_callout.bind(null, "LASER")}], 506 | "s-434-6000-2133": [{"type": "func","func": single_stage_callout.bind(null, "SLAM")}], 507 | "s-434-6000-2134": [{"type": "func","func": single_stage_callout.bind(null, "SLAM + BACK")}], 508 | 509 | // Seventh Floor 510 | 511 | // Lasers + Mechanic 512 | "s-434-7000-1901": [{"type": "text","sub_type": "notification","message": "DEBUFF (CLOSEST)"}].concat(lasers_markers_seventhfloor), 513 | "s-434-7000-1902": [{"type": "text","sub_type": "notification","message": "DEBUFF (FURTHEST)"}].concat(inverted_lasers_markers_seventhfloor), 514 | "s-434-7000-1903": [{"type": "text","sub_type": "notification","message": "GATHER + CLEANSE"}].concat(lasers_markers_seventhfloor), 515 | "s-434-7000-1904": [{"type": "text","sub_type": "notification","message": "GATHER + NO CLEANSE"}].concat(inverted_lasers_markers_seventhfloor), 516 | "s-434-7000-1905": [{"type": "text","sub_type": "notification","message": "SPREAD"}].concat(lasers_markers_seventhfloor), 517 | "s-434-7000-1906": [{"type": "text","sub_type": "notification","message": "GATHER"}].concat(inverted_lasers_markers_seventhfloor), 518 | 519 | // Hold (BACK + STAB) 520 | "s-434-7000-1701": [{"type": "text","sub_type": "notification","message": "BACK + STAB"}], 521 | 522 | // Not Enraged 523 | "s-434-7000-1152": [{"type": "func","func": single_stage_callout.bind(null, "STUN + BACK")}], 524 | "s-434-7000-1138": rings_inout_seventhfloor, 525 | "s-434-7000-1154": [{"type": "text","sub_type": "notification","message": "OUT + IN"}], 526 | "s-434-7000-1155": [{"type": "text","sub_type": "notification","message": "IN + OUT"}], 527 | "s-434-7000-1240": [{"type": "func","func": rings_seventhfloor}], 528 | "s-434-7000-1401": [{"type": "text","sub_type": "notification","message": "PLAGUE/REGRESS"}], 529 | "s-434-7000-1402": [{"type": "text","sub_type": "notification","message": "SLEEP"}], 530 | 531 | // Enraged 532 | "s-434-7000-2152": [{"type": "func","func": single_stage_callout.bind(null, "STUN + BACK")}], 533 | "s-434-7000-2138": rings_inout_seventhfloor, 534 | "s-434-7000-2154": [{"type": "text","sub_type": "notification","message": "OUT + IN"}], 535 | "s-434-7000-2155": [{"type": "text","sub_type": "notification","message": "IN + OUT"}], 536 | "s-434-7000-2240": [{"type": "func","func": rings_seventhfloor}], 537 | "s-434-7000-2401": [{"type": "text","sub_type": "notification","message": "PLAGUE/REGRESS"}], 538 | "s-434-7000-2402": [{"type": "text","sub_type": "notification","message": "SLEEP"}], 539 | }; -------------------------------------------------------------------------------- /guides/9044.js: -------------------------------------------------------------------------------- 1 | let player, entity, library, effect, handlers_reference; 2 | // Bahaar 3 | 4 | 5 | 6 | // fire at edge 7 | 8 | let entities_gameId_at_edge = []; 9 | let fire_at_edge = 0; 10 | 11 | function reset_fire_at_edge(handlers) { 12 | despawn_entities_at_edge(handlers); 13 | fire_at_edge = 0; 14 | } 15 | 16 | function despawn_entities_at_edge(handlers) { 17 | for(const gameId of entities_gameId_at_edge) { 18 | handlers['despawn']({ 19 | id: gameId 20 | }); 21 | } 22 | 23 | entities_gameId_at_edge = []; 24 | } 25 | 26 | function fire_at_edge_event(hp, handlers, event, ent, dispatch) { 27 | const default_alive_time = 60 * 60 * 1000; // 60 min 28 | const amount_of_entities = 50; 29 | 30 | switch(hp) { 31 | case 30: { 32 | if(fire_at_edge >= 3) break; 33 | 34 | despawn_entities_at_edge(handlers); 35 | fire_at_edge = 3; 36 | 37 | entities_gameId_at_edge.push(...handlers['lib']['create_entities_in_circle'](handlers, { 38 | id: 553, 39 | sub_delay: default_alive_time 40 | }, amount_of_entities, { x: -115237, y: 115071, z: 4022 }, 800)); 41 | break; 42 | } 43 | case 60: { 44 | if(fire_at_edge >= 2) break; 45 | 46 | despawn_entities_at_edge(handlers); 47 | fire_at_edge = 2; 48 | 49 | entities_gameId_at_edge.push(...handlers['lib']['create_entities_in_circle'](handlers, { 50 | id: 553, 51 | sub_delay: default_alive_time 52 | }, amount_of_entities, { x: -115237, y: 115071, z: 4022 }, 900)); 53 | break; 54 | } 55 | case 97: { 56 | if(fire_at_edge >= 1) break; 57 | 58 | despawn_entities_at_edge(handlers); 59 | fire_at_edge = 1; 60 | 61 | entities_gameId_at_edge.push(...handlers['lib']['create_entities_in_circle'](handlers, { 62 | id: 553, 63 | sub_delay: default_alive_time 64 | }, amount_of_entities, { x: -115237, y: 115071, z: 4022 }, 1090)); 65 | break; 66 | } 67 | } 68 | } 69 | 70 | module.exports = { 71 | load(dispatch) { 72 | ({ player, entity, library, effect } = dispatch.require.library); 73 | fire_at_edge = 0; 74 | 75 | 76 | // flame 77 | dispatch.hook('S_ACTION_STAGE', 9, e=> { 78 | if(e.templateId === 2100 && e.skill.huntingZoneId === 444) return false; 79 | }); 80 | }, 81 | 82 | // flame 83 | "h-444-2000-99": [ 84 | { 85 | "type": "func", 86 | "func": reset_fire_at_edge 87 | } 88 | ], 89 | 90 | "h-444-2000-97": [ 91 | { 92 | "type": "func", 93 | "func": fire_at_edge_event.bind(null, 97) 94 | } 95 | ], 96 | 97 | "h-444-2000-60": [ 98 | { 99 | "type": "func", 100 | "func": fire_at_edge_event.bind(null, 60) 101 | } 102 | ], 103 | 104 | "h-444-2000-30": [ 105 | { 106 | "type": "func", 107 | "func": fire_at_edge_event.bind(null, 30) 108 | } 109 | ], 110 | 111 | }; -------------------------------------------------------------------------------- /guides/9720.js: -------------------------------------------------------------------------------- 1 | // AANM 2 | //made by Yuyuko 3 | 4 | let counter = 0; 5 | let timer; 6 | 7 | const TIMER_DELAY = 600; 8 | 9 | const ITEM_SPAWNED_ON_SWIPE_ID = 556; 10 | const ITEM_SPAWNED_ON_SWIPE_SUB_DELAY = 2500; 11 | const ITEM_SPAWNED_ON_SWIPE_DISTANCE = 150; 12 | 13 | //一王击飞躲避提示延迟推送 14 | // fistboss: Knock up mech, AKA 'Your flesh will be deleted' callout 15 | 16 | const EVENT_DELAY_FIRST_BOSS_NM = [ 17 | { 18 | "type": "text", 19 | "sub_type": "notification", 20 | "delay": TIMER_DELAY, 21 | "message": "Dodge!", 22 | "message_TW": "注意躲避" 23 | }, 24 | ]; 25 | 26 | //三王后砸前置计数 27 | // thirdboss: counter of back attacks 28 | 29 | function back_attack_NM(handlers) { 30 | clearTimeout(timer); 31 | counter++; 32 | if(counter >= 2) { 33 | handlers['text']({ 34 | "sub_type": "notification", 35 | "message": "Back attack", 36 | "message_TW": "后砸" 37 | }); 38 | } 39 | timer = setTimeout(()=>{ 40 | counter = 0; 41 | }, 3000); 42 | } 43 | 44 | module.exports = { 45 | 46 | //一王 不愤怒 47 | //firstboss, not enraged 48 | 49 | //后跳+内外圈,安全区域:站里面+站外面 50 | //Backstep+donuts, stay in, get out 51 | 52 | "s-720-1000-1117": [{"type": "text","sub_type": "notification","message": "stay in↑ + get out↓","message_TW": "站里面↑+站外面↓"}], 53 | 54 | //原地抬腿+内外圈,安全区域:站外面+站里面 55 | //Stomp+donuts, get out, stay in 56 | 57 | "s-720-1000-1116": [{"type": "text","sub_type": "notification","message": "get out↓ + stay in↑","message_TW": "站外面↓+站里面↑"}], 58 | 59 | //剑戳地2下+后方挥盾 60 | //ground thrust 2x+shield swing 61 | 62 | "s-720-1000-1109": [{"type": "text","sub_type": "notification","message": "back attack","message_TW": "后方攻击"}], 63 | 64 | //一王 愤怒 65 | //firstboss, enraged 66 | 67 | //后跳+内外圈,安全区域:站里面+站外面 68 | //Backstep+donuts, stay in, get out 69 | 70 | "s-720-1000-2117": [{"type": "text","sub_type": "notification","message": "stay in↑ + get out↓","message_TW": "站里面↑+站外面↓"}], 71 | 72 | //原地抬腿+内外圈,安全区域:站外面+站里面 73 | //Stomp+donuts, get out, stay in 74 | 75 | "s-720-1000-2116": [{"type": "text","sub_type": "notification","message": "get out↓ + stay in↑","message_TW": "站外面↓+站里面↑"}], 76 | 77 | //剑戳地2下+后方挥盾 78 | //ground thrust 2x+shield swing 79 | 80 | "s-720-1000-2109": [{"type": "text","sub_type": "notification","message": "back attack","message_TW": "后方攻击"}], 81 | 82 | //一王特殊技能 83 | //firstboss special attack 84 | 85 | //击飞攻击 86 | //knockup attack 87 | 88 | "s-720-1000-1300": EVENT_DELAY_FIRST_BOSS_NM, 89 | 90 | //二王 不愤怒 91 | //secondboss, not enraged 92 | 93 | //转圈攻击(1106) 94 | //Spin attack 95 | 96 | "s-720-2000-1106": [{"type": "text","sub_type": "notification","message": "Spin attack","message_TW": "转圈攻击"}], 97 | 98 | //后方攻击(1105) 99 | //Back attack 100 | 101 | "s-720-2000-1105": [{"type": "text","sub_type": "notification","message": "Back attack","message_TW": "后方攻击"}], 102 | 103 | //点名大跳晕人 104 | //Random aggro stun 105 | 106 | "s-720-2000-1104": [{"type": "text","sub_type": "notification","message": "Random jump","message_TW": "点名大跳"}], 107 | 108 | //眩晕攻击 109 | //Stun attack 110 | 111 | "s-720-2000-1110": [{"type": "text","sub_type": "notification","message": "Stun attack","message_TW": "眩晕攻击"}], 112 | 113 | //BOSS右手边划刀,安全区域:坦右,打左 114 | //right hand side swing, tank goes to right, dps goes to left 115 | 116 | "s-720-2000-1112": [{"type": "text","class_position":"tank","sub_type": "notification","message": "right→","message_TW": "右→"}, 117 | {"type": "text","class_position":"dps","sub_type": "notification","message": "left←","message_TW": "左←"}, 118 | {"type": "text","class_position":"heal","sub_type": "notification","message": "left←","message_TW": "左←"}], 119 | 120 | //BOSS左手边划刀,安全区域:坦左,打右 121 | //left hand side swing, tank goes to left, dps goes to right 122 | 123 | "s-720-2000-1111": [{"type": "text","class_position":"tank","sub_type": "notification","message": "left←","message_TW": "左←"}, 124 | {"type": "text","class_position":"dps","sub_type": "notification","message": "right→","message_TW": "右→"}, 125 | {"type": "text","class_position":"heal","sub_type": "notification","message": "right→","message_TW": "右→"}], 126 | 127 | //二王 愤怒 128 | //secondboss,enraged 129 | 130 | //转圈攻击(2106) 131 | //Spin attack 132 | 133 | "s-720-2000-2106": [{"type": "text","sub_type": "notification","message": "Spin attack","message_TW": "转圈攻击"}], 134 | 135 | //愤怒后方攻击(2105) 136 | //enraged back attack 137 | 138 | "s-720-2000-2105": [{"type": "text","sub_type": "notification","message": "back attack","message_TW": "后方攻击"}], 139 | 140 | //点名大跳晕人 141 | //Random aggro stun 142 | 143 | "s-720-2000-2104": [{"type": "text","sub_type": "notification","message": "Random aggro jump","message_TW": "点名大跳"}], 144 | 145 | //BOSS右手边划刀,安全区域:坦右,打左 146 | //right hand side swing, tank goes to right, dps goes to left 147 | 148 | "s-720-2000-2112": [{"type": "text","class_position":"tank","sub_type": "notification","message": "right→","message_TW": "右→"}, 149 | {"type": "text","class_position":"dps","sub_type": "notification","message": "left←","message_TW": "左←"}, 150 | {"type": "text","class_position":"heal","sub_type": "notification","message": "left←","message_TW": "左←"}], 151 | 152 | //BOSS左手边划刀,安全区域:坦左,打右 153 | //left hand side swing, tank goes to left, dps goes to right 154 | 155 | "s-720-2000-2111": [{"type": "text","class_position":"tank","sub_type": "notification","message": "left←","message_TW": "左←"}, 156 | {"type": "text","class_position":"dps","sub_type": "notification","message": "right→","message_TW": "右→"}, 157 | {"type": "text","class_position":"heal","sub_type": "notification","message": "right→","message_TW": "右→"}], 158 | 159 | //眩晕攻击 160 | //Stun attack 161 | 162 | "s-720-2000-2110": [{"type": "text","sub_type": "notification","message": "Stun attack","message_TW": "眩晕攻击"}], 163 | 164 | //二王特殊技能 165 | //secondboss, special attacks 166 | 167 | //红色鉴定,安全区域:15M外 168 | //Red, stay 15m away 169 | 170 | "s-720-2000-3119": [{"type": "text","sub_type": "notification","message": "red: get out↓","message_TW": "红色:外↓"}], 171 | 172 | //蓝色鉴定,安全区域:15M内(3220) 173 | //Blue, stay near within 15m 174 | 175 | "s-720-2000-3220": [{"type": "text","sub_type": "notification","message": "blue: stay in↑","message_TW": "蓝色:内↑"}], 176 | 177 | //眩晕圈(3116)+外圈伤害(3118) 178 | //stun+donut 179 | 180 | "s-720-2000-3116": [{"type": "text","sub_type": "notification","message": "dodge + stay in↑","message_TW": "躲避+内↑"}], 181 | 182 | //点名喷长条毒(3107) 183 | //random aggro poison(3107) 184 | 185 | //三王 不愤怒 186 | //thirdboss, unenraged 187 | 188 | //进场的推人红圈 189 | //pushback when engaging 190 | 191 | "s-720-3000-1315": [{"type": "text","sub_type": "notification","message": "Pushback","message_TW": "开场推人"}], 192 | 193 | //点名大跳晕人(1107) 194 | //random aggro stun 195 | 196 | "s-720-3000-1107": [{"type": "text","sub_type": "notification","message": "Random jump","message_TW": "点名大跳"}], 197 | 198 | //点名出剑刃风暴,手里握篮球挥篮球(1120),剑刃风暴(1121) 199 | //random aggro, holding blue orb, energy beam 200 | 201 | "s-720-3000-1204": [{"type": "text","sub_type": "notification","message": "energy beam","message_TW": "点名剑气"}], 202 | 203 | //剑舞前戳+逆时针旋转+右手扇形攻击 注:坦右边安全,打左边安全请自行更改 204 | //heart thrust+anticlockwise spin+right swipe 205 | 206 | "s-720-3000-1109": [{"type": "text","class_position":"tank","sub_type": "notification","message": "right→","message_TW": "右→"}, 207 | {"type": "text","class_position":"dps","sub_type": "notification","message": "left←","message_TW": "左←"}, 208 | {"type": "text","class_position":"heal","sub_type": "notification","message": "left←","message_TW": "左←"}, 209 | 210 | // Courtesy of Kasea ;) 211 | { 212 | "type": "spawn", 213 | "id": ITEM_SPAWNED_ON_SWIPE_ID, 214 | "sub_delay": ITEM_SPAWNED_ON_SWIPE_SUB_DELAY, 215 | "distance": ITEM_SPAWNED_ON_SWIPE_DISTANCE, 216 | "offset": -1 217 | }, 218 | { 219 | "type": "spawn", 220 | "id": ITEM_SPAWNED_ON_SWIPE_ID, 221 | "sub_delay": ITEM_SPAWNED_ON_SWIPE_SUB_DELAY, 222 | "distance": ITEM_SPAWNED_ON_SWIPE_DISTANCE, 223 | "offset": -2.3 224 | } 225 | ], 226 | 227 | //剑舞前戳+顺时针旋转+左手扇形攻击 注:坦左边安全,打右边安全请自行更改 228 | //heart thrust+clockwise spin+left swipe 229 | 230 | "s-720-3000-1111": [{"type": "text","class_position":"tank","sub_type": "notification","message": "left←","message_TW": "左←"}, 231 | {"type": "text","class_position":"dps","sub_type": "notification","message": "right→","message_TW": "右→"}, 232 | {"type": "text","class_position":"heal","sub_type": "notification","message": "right→","message_TW": "右→"}, 233 | 234 | // Courtesy of Kasea ;) 235 | { 236 | "type": "spawn", 237 | "id": ITEM_SPAWNED_ON_SWIPE_ID, 238 | "sub_delay": ITEM_SPAWNED_ON_SWIPE_SUB_DELAY, 239 | "distance": ITEM_SPAWNED_ON_SWIPE_DISTANCE, 240 | "offset": 1 241 | }, 242 | { 243 | "type": "spawn", 244 | "id": ITEM_SPAWNED_ON_SWIPE_ID, 245 | "sub_delay": ITEM_SPAWNED_ON_SWIPE_SUB_DELAY, 246 | "distance": ITEM_SPAWNED_ON_SWIPE_DISTANCE, 247 | "offset": 2.3 248 | } 249 | ], 250 | 251 | //前后砸 252 | //front, back slam 253 | 254 | "s-720-3000-1113": [{"type": "text","sub_type": "notification","message": "front, back slam","message_TW": "前后砸"}], 255 | 256 | //旋转攻击 257 | //spinning attack 258 | 259 | "s-720-3000-1115": [{"type": "text","sub_type": "notification","message": "spinning attack","message_TW": "旋转攻击"}], 260 | 261 | //2连斜上挥(1104)后接的后砸(1119) 262 | //golf swing x2 + back slam 263 | 264 | "s-720-3000-1104": [{"type": "func","func": back_attack_NM}], 265 | 266 | //后闪+旋转or前后砸 267 | //teleport back+ spin or front, back slam 268 | 269 | "s-720-3000-1202": [{"type": "text","sub_type": "notification","message": "spin or front,back slam","message_TW": "旋转or前后砸"}], 270 | 271 | //三王 愤怒 272 | //thirdboss, enraged 273 | 274 | //点名出剑刃风暴,手里握篮球挥篮球(1204),剑刃风暴(1121) 275 | //random aggro, holding blue orb, energy beam 276 | 277 | "s-720-3000-2204": [{"type": "text","sub_type": "notification","message": "enraged:energy beam","message_TW": "愤怒:点名剑气"}], 278 | 279 | //剑舞前戳+逆时针旋转+右手扇形攻击 注:坦右边安全,打左边安全请自行更改 280 | //heart thrust+anticlockwise spin+right swipe 281 | 282 | "s-720-3000-2109": [{"type": "text","class_position":"tank","sub_type": "notification","message": "right→","message_TW": "右→"}, 283 | {"type": "text","class_position":"dps","sub_type": "notification","message": "left←","message_TW": "左←"}, 284 | {"type": "text","class_position":"heal","sub_type": "notification","message": "left←","message_TW": "左←"}, 285 | 286 | // Courtesy of Kasea ;) 287 | { 288 | "type": "spawn", 289 | "id": ITEM_SPAWNED_ON_SWIPE_ID, 290 | "sub_delay": ITEM_SPAWNED_ON_SWIPE_SUB_DELAY, 291 | "distance": ITEM_SPAWNED_ON_SWIPE_DISTANCE, 292 | "offset": -1 293 | }, 294 | { 295 | "type": "spawn", 296 | "id": ITEM_SPAWNED_ON_SWIPE_ID, 297 | "sub_delay": ITEM_SPAWNED_ON_SWIPE_SUB_DELAY, 298 | "distance": ITEM_SPAWNED_ON_SWIPE_DISTANCE, 299 | "offset": -2.3 300 | } 301 | ], 302 | 303 | //剑舞前戳+顺时针旋转+左手扇形攻击 注:坦左边安全,打右边安全请自行更改 304 | //heart thrust+clockwise spin+left swipe 305 | 306 | "s-720-3000-2111": [{"type": "text","class_position":"tank","sub_type": "notification","message": "left←","message_TW": "左←"}, 307 | {"type": "text","class_position":"dps","sub_type": "notification","message": "right→","message_TW": "右→"}, 308 | {"type": "text","class_position":"heal","sub_type": "notification","message": "right→","message_TW": "右→"}, 309 | 310 | // Courtesy of Kasea ;) 311 | { 312 | "type": "spawn", 313 | "id": ITEM_SPAWNED_ON_SWIPE_ID, 314 | "sub_delay": ITEM_SPAWNED_ON_SWIPE_SUB_DELAY, 315 | "distance": ITEM_SPAWNED_ON_SWIPE_DISTANCE, 316 | "offset": 1 317 | }, 318 | { 319 | "type": "spawn", 320 | "id": ITEM_SPAWNED_ON_SWIPE_ID, 321 | "sub_delay": ITEM_SPAWNED_ON_SWIPE_SUB_DELAY, 322 | "distance": ITEM_SPAWNED_ON_SWIPE_DISTANCE, 323 | "offset": 2.3 324 | } 325 | ], 326 | 327 | //前后砸 328 | //front, back slam 329 | 330 | "s-720-3000-2113": [{"type": "text","sub_type": "notification","message": "front, back slam","message_TW": "前后砸"}], 331 | 332 | //愤怒2连斜上挥(2104)后接的后砸(2119) 333 | //golf swing x2 + back slam 334 | "s-720-3000-2104": [{"type": "func","func": back_attack_NM}], 335 | 336 | //旋转攻击 337 | //spinning attack 338 | 339 | "s-720-3000-2115": [{"type": "text","sub_type": "notification","message": "spinning attack","message_TW": "旋转攻击"}], 340 | 341 | //点名大跳晕人(1107) 342 | //random aggro stun 343 | 344 | "s-720-3000-2107": [{"type": "text","sub_type": "notification","message": "Random jump","message_TW": "点名大跳"}], 345 | 346 | //后闪+旋转or前后砸 347 | //teleport back+ spin or front, back slam 348 | 349 | "s-720-3000-2202": [{"type": "text","sub_type": "notification","message": "spin or front,back slam","message_TW": "旋转or前后砸"}], 350 | 351 | //三王特殊技能 352 | //thirdboss, special attacks 353 | 354 | //召唤分身出点名剑气 355 | //clones, random aggro, energy beam 356 | 357 | "s-720-3000-1400": [{"type": "text","sub_type": "notification","message": "beam","message_TW": "召唤分身:点名剑气"}], 358 | 359 | //召唤分身出旋转攻击 360 | //clones, random aggro, spin attack 361 | 362 | "s-720-3000-1401": [{"type": "text","sub_type": "notification","message": "spin","message_TW": "召唤分身:旋转攻击"}] 363 | 364 | }; -------------------------------------------------------------------------------- /guides/9920.js: -------------------------------------------------------------------------------- 1 | const SPAWN_CIRCLES = false; 2 | 3 | // AAHM 4 | //made by Yuyuko 5 | 6 | let counter = 0;//后砸计数 count for back attacks 7 | let timer;//后砸统计重置时间 reset time 8 | let print = true;//二王HP提示开关 secondboss Health 9 | 10 | let SPAWNING_FIRST_CIRCLE_FLOWERS = []; 11 | let SPAWNING_SECOND_CIRCLE_FLOWERS = []; 12 | 13 | const TIMER_DELAY = 600; 14 | const ITEM_SPAWNED_ON_SWIPE_ID = 556; 15 | const ITEM_SPAWNED_ON_SWIPE_SUB_DELAY = 2500; 16 | const ITEM_SPAWNED_ON_SWIPE_DISTANCE = 150; 17 | 18 | const stepone = 2 * Math.PI / 40;//40 flowers in total 19 | const steptwo = 2 * Math.PI / 72;//72 flowers in total 20 | 21 | //内圈 22 | //inner flower circle 23 | 24 | for(let angle = -Math.PI; angle <= Math.PI; angle += stepone) { 25 | if(!SPAWN_CIRCLES) continue; 26 | SPAWNING_FIRST_CIRCLE_FLOWERS.push({ 27 | "type": "spawn", 28 | "id": 603, 29 | "sub_delay": 6000, 30 | "distance": 143, 31 | "offset": angle 32 | }); 33 | 34 | SPAWNING_SECOND_CIRCLE_FLOWERS.push({ 35 | "type": "spawn", 36 | "id": 603, 37 | "sub_delay": 6000, 38 | "distance": 157, 39 | "offset": angle 40 | }); 41 | } 42 | //外圈 43 | //outer flower circle 44 | 45 | for(let angle = -Math.PI; angle <= Math.PI; angle += steptwo) { 46 | if(!SPAWN_CIRCLES) continue; 47 | SPAWNING_FIRST_CIRCLE_FLOWERS.push({ 48 | "type": "spawn", 49 | "id": 603, 50 | "sub_delay": 6000, 51 | "distance": 293, 52 | "offset": angle 53 | }); 54 | 55 | SPAWNING_SECOND_CIRCLE_FLOWERS.push({ 56 | "type": "spawn", 57 | "id": 603, 58 | "sub_delay": 6000, 59 | "distance": 307, 60 | "offset": angle 61 | }); 62 | } 63 | 64 | 65 | //剑舞前戳+逆时针旋转+右手扇形攻击+外到内甜甜圈 66 | //heart thrust+anticlockwise spin+right swipe+AOEs from out to in 67 | 68 | SPAWNING_FIRST_CIRCLE_FLOWERS.push({"type": "text","class_position":"tank","sub_type": "notification","message": "right→>out to in","message_TW": "右→ + 从外到内"}); 69 | SPAWNING_FIRST_CIRCLE_FLOWERS.push({"type": "text","class_position":"dps","sub_type": "notification","message": "left←>out to in","message_TW": "左← + 从外到内"}); 70 | SPAWNING_FIRST_CIRCLE_FLOWERS.push({"type": "text","class_position":"heal","sub_type": "notification","message": "left←>out to in","message_TW": "左← + 从外到内"}); 71 | SPAWNING_FIRST_CIRCLE_FLOWERS.push({"type": "spawn","id": ITEM_SPAWNED_ON_SWIPE_ID,"sub_delay": ITEM_SPAWNED_ON_SWIPE_SUB_DELAY,"distance": ITEM_SPAWNED_ON_SWIPE_DISTANCE,"offset": -1}); 72 | SPAWNING_FIRST_CIRCLE_FLOWERS.push({"type": "spawn","id": ITEM_SPAWNED_ON_SWIPE_ID,"sub_delay": ITEM_SPAWNED_ON_SWIPE_SUB_DELAY,"distance": ITEM_SPAWNED_ON_SWIPE_DISTANCE,"offset": -2.3}); 73 | 74 | //剑舞前戳+顺时针旋转+左手扇形攻击+内到外甜甜圈 75 | //heart thrust+clockwise spin+left swipe+AOEs from in to out 76 | 77 | SPAWNING_SECOND_CIRCLE_FLOWERS.push({"type": "text","class_position":"tank","sub_type": "notification","message": "left←>in to out","message_TW": "左← + 从内到外"}); 78 | SPAWNING_SECOND_CIRCLE_FLOWERS.push({"type": "text","class_position":"dps","sub_type": "notification","message": "right→>in to out","message_TW": "右→ + 从内到外"}); 79 | SPAWNING_SECOND_CIRCLE_FLOWERS.push({"type": "text","class_position":"heal","sub_type": "notification","message": "right→>in to out","message_TW": "右→ + 从内到外"}); 80 | SPAWNING_SECOND_CIRCLE_FLOWERS.push({"type": "spawn","id": ITEM_SPAWNED_ON_SWIPE_ID,"sub_delay": ITEM_SPAWNED_ON_SWIPE_SUB_DELAY,"distance": ITEM_SPAWNED_ON_SWIPE_DISTANCE,"offset": 1}); 81 | SPAWNING_SECOND_CIRCLE_FLOWERS.push({"type": "spawn","id": ITEM_SPAWNED_ON_SWIPE_ID,"sub_delay": ITEM_SPAWNED_ON_SWIPE_SUB_DELAY,"distance": ITEM_SPAWNED_ON_SWIPE_DISTANCE,"offset": 2.3}); 82 | 83 | //一王击飞躲避提示延迟推送 84 | //Ghergof: Knock up mech, AKA 'Your flesh will be deleted' callout 85 | 86 | const EVENT_DELAY_FIRST_BOSS_HM = [ 87 | { 88 | "type": "text", 89 | "sub_type": "notification", 90 | "delay": TIMER_DELAY, 91 | "message": "Dodge!", 92 | "message_TW": "注意躲避" 93 | }, 94 | ]; 95 | 96 | 97 | 98 | //二王血量显示 99 | //secondboss Health tips 100 | 101 | function start_boss() { 102 | print = true; 103 | } 104 | function print_fifty(handlers) { 105 | if(print) { 106 | handlers['text']({ 107 | "sub_type": "notification", 108 | "message": "50%", 109 | "message_TW": "50%" 110 | }); 111 | } 112 | print = false; 113 | } 114 | function print_twenty(handlers) { 115 | if(print) { 116 | handlers['text']({ 117 | "sub_type": "notification", 118 | "message": "20%", 119 | "message_TW": "20%" 120 | }); 121 | } 122 | print = false; 123 | } 124 | 125 | //三王后砸前置计数 126 | //thirdboss: counter of back attacks 127 | function back_attack_HM(handlers) { 128 | clearTimeout(timer); 129 | counter++; 130 | if(counter >= 2) { 131 | handlers['text']({ 132 | "sub_type": "notification", 133 | "message": "Back attack", 134 | "message_TW": "后砸" 135 | }); 136 | } 137 | timer = setTimeout(()=>{ 138 | counter = 0; 139 | }, 3000); 140 | } 141 | 142 | //三王色鉴,针对色盲玩家的特别帮助 143 | //thirdboss: color marks in cage 144 | /* ------------------------------------------- */ 145 | let colour_to_use = null; 146 | const COLOURS_OFFSETS = { 147 | "red": 0, 148 | "yellow": 2.5, 149 | "blue": -2.5, 150 | }; 151 | 152 | function set_clockwise(clockwise, handlers, _, third_boss_entity) { 153 | setTimeout(()=> { 154 | // Get the colour rotation 155 | const colour_rotation = clockwise ? ["red", "yellow", "blue"] : ["blue", "yellow", "red"]; 156 | 157 | // Loop thru the three cage rotations 158 | for(let i = 0; i < 3; i++) { 159 | let current_colour = colour_rotation[(colour_rotation.indexOf(colour_to_use) + i) % 3]; 160 | 161 | handlers['spawn']({ 162 | "id": 567, 163 | "delay": i * 2600, 164 | "sub_delay": (i + 1) * 3000, 165 | "distance": 150, 166 | "offset": COLOURS_OFFSETS[current_colour] 167 | }, third_boss_entity); 168 | } 169 | 170 | // clear out clockwise 171 | setTimeout(()=> { 172 | clockwise = null; 173 | }, 12000); 174 | }, 1000); 175 | } 176 | 177 | function change_colour(colour) { 178 | colour_to_use = colour; 179 | } 180 | /* ------------------------------------------- */ 181 | 182 | 183 | module.exports = { 184 | 185 | //一王 不愤怒 186 | //firstboss, not enraged 187 | 188 | //后跳+内外圈,安全区域:站里面+站外面 189 | //Backstep+donuts, stay in, get out 190 | 191 | "s-920-1000-1117": [{"type": "text","sub_type": "notification","message": "stay in↑ + get out↓","message_TW": "站里面↑+站外面↓"}], 192 | 193 | //原地抬腿+内外圈,安全区域:站外面+站里面 194 | //Stomp+donuts, get out, stay in 195 | 196 | "s-920-1000-1116": [{"type": "text","sub_type": "notification","message": "get out↓ + stay in↑","message_TW": "站外面↓+站里面↑"}], 197 | 198 | //剑戳地2下+后方挥盾 199 | //ground thrust 2x+shield swing 200 | 201 | "s-920-1000-1109": [{"type": "text","sub_type": "notification","message": "back attack","message_TW": "后方攻击"}], 202 | 203 | //三圈顺序:全+外+内 站里面站外面 204 | //Massive In-Out Big AoE+Outer AoE+Inner AoE 205 | 206 | "s-920-1000-1130": [{"type": "text","sub_type": "notification","message": "full>outer>inner","message_TW": "伤害顺序:全>外>内"}], 207 | 208 | 209 | //一王愤怒 210 | //firstboss, enraged 211 | 212 | //后跳+内外圈,安全区域:站里面+站外面 213 | //Backstep+donuts, stay in, get out 214 | 215 | "s-920-1000-2117": [{"type": "text","sub_type": "notification","message": "stay in↑ + get out↓","message_TW": "站里面↑+站外面↓"}], 216 | 217 | //原地抬腿+内外圈,安全区域:站外面+站里面 218 | //Stomp+donuts, get out, stay in 219 | 220 | "s-920-1000-2116": [{"type": "text","sub_type": "notification","message": "get out↓ + stay in↑","message_TW": "站外面↓+站里面↑"}], 221 | 222 | //剑戳地2下+后方挥盾 223 | //ground thrust 2x+shield swing 224 | 225 | "s-920-1000-2109": [{"type": "text","sub_type": "notification","message": "back attack","message_TW": "后方攻击"}], 226 | 227 | //愤怒时 三圈顺序:全+内+外 228 | //enraged Massive In-Out Big AoE+Inner AoE+Outer AoE 229 | 230 | "s-920-1000-2130": [{"type": "text","sub_type": "notification","message": "full>inner>outer","message_TW": "伤害顺序:全>内>外"}], 231 | 232 | //一王特殊技能 233 | //firstboss special attack 234 | 235 | //击飞攻击 236 | //knockup attack 237 | 238 | "s-920-1000-1300": EVENT_DELAY_FIRST_BOSS_HM, 239 | 240 | //二王 不愤怒 241 | //secondboss, not enraged 242 | 243 | //点名转身划刀 244 | //target one player then turn aroud with swing 245 | 246 | "s-920-2000-1108": [{"type": "text","sub_type": "notification","message": "Target swing","message_TW": "点名划刀攻击"}], 247 | 248 | //点名转身划刀,左手边伤害 249 | //target one player then turn aroud with left swing 250 | 251 | "s-920-2000-1113": [{"type": "text","sub_type": "notification","message": "right hand swing","message_TW": "左手伤害"}], 252 | 253 | //点名转身划刀,右手边伤害 254 | //target one player then turn aroud with right swing 255 | 256 | "s-920-2000-1114": [{"type": "text","sub_type": "notification","message": "left hand swing","message_TW": "右手伤害"}], 257 | 258 | //转圈攻击(1106) 259 | //Spin attack 260 | 261 | "s-920-2000-1106": [{"type": "text","sub_type": "notification","message": "Spin attack","message_TW": "转圈攻击"}], 262 | 263 | //后方攻击(1105) 264 | //Back attack 265 | 266 | "s-920-2000-1105": [{"type": "text","sub_type": "notification","message": "Back attack","message_TW": "后方攻击"}], 267 | 268 | //点名大跳晕人 269 | //Random aggro stun 270 | 271 | "s-920-2000-1104": [{"type": "text","sub_type": "notification","message": "Random jump","message_TW": "点名大跳"}], 272 | 273 | 274 | //眩晕攻击 275 | //Stun attack 276 | 277 | "s-920-2000-1110": [{"type": "text","sub_type": "notification","message": "Stun attack","message_TW": "眩晕攻击"}], 278 | 279 | //BOSS右手边划刀,安全区域:坦右,打左 280 | //right hand side swing, tank goes to right, dps goes to left 281 | 282 | "s-920-2000-1112": [{"type": "text","class_position":"tank","sub_type": "notification","message": "right→","message_TW": "右→"}, 283 | {"type": "text","class_position":"dps","sub_type": "notification","message": "left←","message_TW": "左←"}, 284 | {"type": "text","class_position":"heal","sub_type": "notification","message": "left←","message_TW": "左←"}], 285 | 286 | //BOSS左手边划刀,安全区域:坦左,打右 287 | //left hand side swing, tank goes to left, dps goes to right 288 | 289 | "s-920-2000-1111": [{"type": "text","class_position":"tank","sub_type": "notification","message": "left←","message_TW": "左←"}, 290 | {"type": "text","class_position":"dps","sub_type": "notification","message": "right→","message_TW": "右→"}, 291 | {"type": "text","class_position":"heal","sub_type": "notification","message": "right→","message_TW": "右→"}], 292 | 293 | //二王 愤怒 294 | //secondboss,enraged 295 | 296 | //转圈攻击(1106) 297 | //Spin attack 298 | 299 | "s-920-2000-2106": [{"type": "text","sub_type": "notification","message": "Spin attack","message_TW": "转圈攻击"}], 300 | 301 | //愤怒后方攻击(2105) 302 | //enraged back attack 303 | 304 | "s-920-2000-2105": [{"type": "text","sub_type": "notification","message": "back attack","message_TW": "后方攻击"}], 305 | 306 | //点名大跳晕人 307 | //Random aggro stun 308 | 309 | "s-920-2000-2104": [{"type": "text","sub_type": "notification","message": "Random jump","message_TW": "点名大跳"}], 310 | 311 | //BOSS右手边划刀,安全区域:坦右,打左 312 | //right hand side swing, tank goes to right, dps goes to left 313 | 314 | "s-920-2000-2112": [{"type": "text","class_position":"tank","sub_type": "notification","message": "right→","message_TW": "右→"}, 315 | {"type": "text","class_position":"dps","sub_type": "notification","message": "left←","message_TW": "左←"}, 316 | {"type": "text","class_position":"heal","sub_type": "notification","message": "left←","message_TW": "左←"}], 317 | 318 | //BOSS左手边划刀,安全区域:坦左,打右 319 | //left hand side swing, tank goes to left, dps goes to right 320 | 321 | "s-920-2000-2111": [{"type": "text","class_position":"tank","sub_type": "notification","message": "left←","message_TW": "左←"}, 322 | {"type": "text","class_position":"dps","sub_type": "notification","message": "right→","message_TW": "右→"}, 323 | {"type": "text","class_position":"heal","sub_type": "notification","message": "right→","message_TW": "右→"}], 324 | 325 | //眩晕攻击 326 | //Stun attack 327 | 328 | "s-920-2000-2110": [{"type": "text","sub_type": "notification","message": "Stun attack","message_TW": "眩晕攻击"}], 329 | 330 | //点名转身划刀 331 | //target one player then turn aroud with swing 332 | 333 | "s-920-2000-2108": [{"type": "text","sub_type": "notification","message": "Target swing","message_TW": "点名划刀攻击"}], 334 | 335 | //点名转身划刀,左手边伤害 336 | //target one player then turn aroud with left swing 337 | 338 | "s-920-2000-2113": [{"type": "text","sub_type": "notification","message": "right hand swing","message_TW": "左手伤害"}], 339 | 340 | //点名转身划刀,右手边伤害 341 | //target one player then turn aroud with right swing 342 | 343 | "s-920-2000-2114": [{"type": "text","sub_type": "notification","message": "left hand swing","message_TW": "右手伤害"}], 344 | 345 | 346 | //二王特殊技能 347 | //secondboss, special attacks 348 | 349 | //红色鉴定,安全区域:15M外 350 | //Red, stay 15m away 351 | 352 | "s-920-2000-3119": [{"type": "text","sub_type": "notification","message": "red: get out↓","message_TW": "红色:外↓"}], 353 | 354 | //蓝色鉴定,安全区域:15M内(3220) 355 | //Blue, stay near within 15m 356 | 357 | "s-920-2000-3220": [{"type": "text","sub_type": "notification","message": "blue: stay in↑","message_TW": "蓝色:内↑"}], 358 | 359 | //眩晕圈(3116)+外圈伤害(3118) 360 | //stun+donut 361 | 362 | "s-920-2000-3116": [{"type": "text","sub_type": "notification","message": "dodge + stay in↑","message_TW": "躲避+内↑"}], 363 | 364 | //点名喷长条毒(3107) 365 | //random aggro poison(3107) 366 | 367 | 368 | //二王HP血量检测 369 | //secondboss Health tips 370 | 371 | //BOSS血量低于50%后,左右划刀变成双面 372 | //50% 373 | 374 | "h-920-2000-99": [{"type": "func","func": start_boss}], 375 | "h-920-2000-50": [{"type": "func","func": print_fifty}], 376 | 377 | //BOSS血量20%后,会有概率出现连续鉴定 378 | //20% 379 | 380 | "h-920-2000-21": [{"type": "func","func": start_boss}], 381 | "h-920-2000-20": [ {"type": "func","func": print_twenty}], 382 | 383 | //三王 不愤怒 384 | //thirdboss, unenraged 385 | 386 | //进场的推人红圈 387 | //pushback when engaging 388 | 389 | "s-920-3000-1315": [{"type": "text","sub_type": "notification","message": "Pushback","message_TW": "开场推人"}], 390 | 391 | //点名大跳晕人(1107) 392 | //random aggro stun 393 | 394 | "s-920-3000-1107": [{"type": "text","sub_type": "notification","message": "Random jump","message_TW": "点名大跳"}], 395 | 396 | //点名出剑刃风暴(1204) 397 | //random target energy beam 398 | "s-920-3000-1204": [{"type": "text","sub_type": "notification","message": "energy beam","message": "点名剑气"}], 399 | 400 | //剑舞前戳+逆时针旋转+右手扇形攻击+外到内甜甜圈 注:坦右边安全,打左边安全请自行更改 401 | //heart thrust+anticlockwise spin+right swipe+AOEs from out to in 402 | 403 | "s-920-3000-1109": SPAWNING_FIRST_CIRCLE_FLOWERS, 404 | 405 | //剑舞前戳+顺时针旋转+左手扇形攻击+内到外甜甜圈 注:坦左边安全,打右边安全请自行更改 406 | //heart thrust+clockwise spin+left swipe+AOEs from in to out 407 | 408 | "s-920-3000-1111": SPAWNING_SECOND_CIRCLE_FLOWERS, 409 | 410 | //前后砸 411 | //front, back slam 412 | 413 | "s-920-3000-1113": [{"type": "text","sub_type": "notification","message": "front, back slam","message_TW": "前后砸"}], 414 | 415 | //旋转攻击 416 | //spinning attack 417 | 418 | "s-920-3000-1115": [{"type": "text","sub_type": "notification","message": "spinning attack","message_TW": "旋转攻击"}], 419 | 420 | //2连斜上挥(1104)后接的后砸(1119) 421 | //golf swing x2 + back slam 422 | 423 | "s-920-3000-1104": [{"type": "func","func": back_attack_HM}], 424 | 425 | //后闪+旋转or前后砸 426 | //teleport back+ spin or front, back slam 427 | 428 | "s-920-3000-1202": [{"type": "text","sub_type": "notification","message": "spin or front,back slam","message_TW": "旋转or前后砸"}], 429 | 430 | //蓝球+镭射 431 | //target energy beam(almost tank ?_?) 432 | 433 | "s-920-3000-1120": [{"type": "text","sub_type": "notification","message": "energy beam","message_TW": "剑气攻击"}], 434 | 435 | //三王 愤怒 436 | //thirdboss, enraged 437 | 438 | //点名出剑刃风暴,手里握篮球挥篮球(1204),剑刃风暴(1121) 439 | //random aggro, holding blue orb, energy beam 440 | 441 | "s-920-3000-2204": [{"type": "text","sub_type": "notification","message": "enraged:energy beam","message_TW": "愤怒:点名剑气"}], 442 | 443 | //剑舞前戳+逆时针旋转+右手扇形攻击+外到内甜甜圈 注:坦右边安全,打左边安全请自行更改 444 | //heart thrust+anticlockwise spin+right swipe+AOEs from out to in 445 | 446 | "s-920-3000-2109": SPAWNING_FIRST_CIRCLE_FLOWERS, 447 | 448 | //剑舞前戳+顺时针旋转+左手扇形攻击+内到外甜甜圈 注:坦左边安全,打右边安全请自行更改 449 | //heart thrust+clockwise spin+left swipe+AOEs from in to out 450 | 451 | "s-920-3000-2111": SPAWNING_SECOND_CIRCLE_FLOWERS, 452 | 453 | //前后砸 454 | //front, back slam 455 | 456 | "s-920-3000-2113": [{"type": "text","sub_type": "notification","message": "front, back slam","message_TW": "前后砸"}], 457 | 458 | //2连斜上挥(1104)后接的后砸(1119) 459 | //golf swing x2 + back slam 460 | 461 | "s-920-3000-2104": [{"type": "func","func": back_attack_HM}], 462 | 463 | //旋转攻击 464 | //spinning attack 465 | 466 | "s-920-3000-2115": [{"type": "text","sub_type": "notification","message": "spinning attack","message_TW": "旋转攻击"}], 467 | 468 | //点名大跳晕人(1107) 469 | //random aggro stun 470 | 471 | "s-920-3000-2107": [{"type": "text","sub_type": "notification","message": "Random jump","message_TW": "点名大跳"}], 472 | 473 | //后闪+旋转or前后砸 474 | //teleport back+ spin or front, back slam 475 | 476 | "s-920-3000-2202": [{"type": "text","sub_type": "notification","message": "spin or front,back slam","message_TW": "旋转or前后砸"}], 477 | 478 | //蓝球+镭射 479 | //target energy beam(almost tank ?_?) 480 | 481 | "s-920-3000-2120": [{"type": "text","sub_type": "notification","message": "energy beam","message_TW": "剑气攻击"}], 482 | 483 | //三王特殊技能 484 | //thirdboss, special attacks 485 | 486 | //召唤分身出点名剑气 487 | //clones, random aggro, energy beam 488 | 489 | "s-920-3000-1400": [{"type": "text","sub_type": "notification","message": "beam","message_TW": "召唤分身:点名剑气"}], 490 | 491 | //召唤分身出旋转攻击 492 | //clones, random aggro, spin attack 493 | 494 | "s-920-3000-1401": [{"type": "text","sub_type": "notification","message": "spin","message_TW": "召唤分身:旋转攻击"}], 495 | 496 | //三王色鉴颜色位置标识 497 | //color marks in cage 498 | /* -------------------------------- */ 499 | // 红色 red 500 | "ae-0-0-9203037": [{"type": "text","sub_type": "notification","message": "Red","message_TW": "红色"}, 501 | {"type": "func","func": change_colour.bind(null, 'red')}], 502 | 503 | // 黄色 yellow 504 | "ae-0-0-9203038": [{"type": "text","sub_type": "notification","message": "Yellow","message_TW": "黄色"}, 505 | {"type": "func","func": change_colour.bind(null, 'yellow')}], 506 | 507 | 508 | // 蓝色 blue 509 | "ae-0-0-9203039": [{"type": "text","sub_type": "notification","message": "Blue","message_TW": "蓝色"}, 510 | {"type": "func","func": change_colour.bind(null, 'blue')}], 511 | 512 | // 逆时针色鉴 513 | // anti-clockwise 514 | 515 | "s-920-3000-1317": [{"type": "func","func": set_clockwise.bind(null, false)}], 516 | 517 | // 顺时针色鉴 518 | // clockwise 519 | 520 | "s-920-3000-1318": [{"type": "func","func": set_clockwise.bind(null, true)}] 521 | 522 | /* -------------------------------- */ 523 | }; -------------------------------------------------------------------------------- /guides/9920_old.js: -------------------------------------------------------------------------------- 1 | // AAHM 2 | 3 | module.exports = { 4 | //Third Boss 5 | "s-920-3000-1109": [{"type": "text","sub_type": "notification","message": " Spin-> left side safe >Out-in"}], 6 | "s-920-3000-1111": [{"type": "text","sub_type": "notification","message": " Spin-> right side safe >in-out"}], 7 | "s-920-3000-1113": [{"type": "text","sub_type": "notification","message": " Front hammer into Back hammer "}], 8 | "s-920-3000-1115": [{"type": "text","sub_type": "notification","message": " Spin"}], 9 | "s-920-3000-1202": [{"type": "text","sub_type": "notification","message": " BACKSTAB"}], 10 | "s-920-3000-1205": [{"type": "text","sub_type": "notification","message": " BACKSTAB - 2"}], 11 | "s-920-3000-1206": [{"type": "text","sub_type": "notification","message": " BACKSTAB - 3"}], 12 | 13 | //Boss Enraged 14 | "s-920-3000-2109": [{"type": "text","sub_type": "notification","message": " Spin-> left side safe >Out-in"}], 15 | "s-920-3000-2111": [{"type": "text","sub_type": "notification","message": " Spin-> right side safe >in-out"}], 16 | "s-920-3000-2113": [{"type": "text","sub_type": "notification","message": " Front hammer into Back hammer"}], 17 | }; -------------------------------------------------------------------------------- /guides/9950.js: -------------------------------------------------------------------------------- 1 | // Harrowhold 2 | 3 | // 10 minutes 4 | DESPAWN_DELAY = 600000; 5 | ITEM_SPAWN_ID = 556; 6 | 7 | module.exports = { 8 | 9 | 10 | // 4th Phase 11 | 12 | // 97% hp, spawn firewall indicators 13 | "h-950-4000-97": [ 14 | { 15 | "type": "spawn", 16 | "id": ITEM_SPAWN_ID, 17 | "sub_delay": DESPAWN_DELAY, 18 | "pos": { 19 | x: -7374, 20 | y: -83192, 21 | z: 1 22 | } 23 | }, 24 | { 25 | "type": "spawn", 26 | "id": ITEM_SPAWN_ID, 27 | "sub_delay": DESPAWN_DELAY, 28 | "pos": { 29 | x: -7894, 30 | y: -83163, 31 | z: 1 32 | } 33 | }, 34 | { 35 | "type": "spawn", 36 | "id": ITEM_SPAWN_ID, 37 | "sub_delay": DESPAWN_DELAY, 38 | "pos": { 39 | x: -8618, 40 | y: -83528, 41 | z: 1 42 | } 43 | }, 44 | { 45 | "type": "spawn", 46 | "id": ITEM_SPAWN_ID, 47 | "sub_delay": DESPAWN_DELAY, 48 | "pos": { 49 | x: -8897, 50 | y: -83998, 51 | z: 1 52 | } 53 | }, 54 | { 55 | "type": "spawn", 56 | "id": ITEM_SPAWN_ID, 57 | "sub_delay": DESPAWN_DELAY, 58 | "pos": { 59 | x: -8938, 60 | y: -84861, 61 | z: 1 62 | } 63 | }, 64 | { 65 | "type": "spawn", 66 | "id": ITEM_SPAWN_ID, 67 | "sub_delay": DESPAWN_DELAY, 68 | "pos": { 69 | x: -7391, 70 | y: -85812, 71 | z: 1 72 | } 73 | }, 74 | { 75 | "type": "spawn", 76 | "id": ITEM_SPAWN_ID, 77 | "sub_delay": DESPAWN_DELAY, 78 | "pos": { 79 | x: -6686, 80 | y: -85442, 81 | z: 1 82 | } 83 | }, 84 | { 85 | "type": "spawn", 86 | "id": ITEM_SPAWN_ID, 87 | "sub_delay": DESPAWN_DELAY, 88 | "pos": { 89 | x: -6348, 90 | y: -84873, 91 | z: 1 92 | } 93 | }, 94 | { 95 | "type": "spawn", 96 | "id": ITEM_SPAWN_ID, 97 | "sub_delay": DESPAWN_DELAY, 98 | "pos": { 99 | x: -6356, 100 | y: -84053, 101 | z: 1 102 | } 103 | } 104 | ] 105 | }; -------------------------------------------------------------------------------- /guides/9970.js: -------------------------------------------------------------------------------- 1 | // RMHM 2 | const FIRST_TIMER_DELAY = 40000; 3 | const SECOND_TIMER_DELAY = 55000; 4 | 5 | const EVENT_FOR_DEBUFFS = [ 6 | { 7 | "type": "stop_timer", 8 | "id": 1 9 | }, 10 | { 11 | "type": "stop_timer", 12 | "id": 2 13 | }, 14 | { 15 | "type": "text", 16 | "sub_type": "message", 17 | "id": 1, 18 | "delay": FIRST_TIMER_DELAY, 19 | "message": "Debuff swap will happen soon" 20 | }, 21 | { 22 | "type": "text", 23 | "sub_type": "message", 24 | "id": 2, 25 | "delay": SECOND_TIMER_DELAY, 26 | "message": "Debuff swap will happen soon" 27 | } 28 | ]; 29 | 30 | module.exports = { 31 | // First boss 32 | 33 | // Start(first debuff applied) 34 | "ae-0-0-97000042": EVENT_FOR_DEBUFFS, 35 | "ae-0-0-97000043": EVENT_FOR_DEBUFFS, 36 | 37 | // Debuff rotation happening 38 | "s-970-1000-1307": EVENT_FOR_DEBUFFS, 39 | 40 | // Meh, fill in with stop_timer id 1 below 70% hp, but cba 41 | 42 | // Second boss 43 | "s-970-2000-2106-0": [{"type": "text","sub_type": "notification","message": " STUN"}], 44 | 45 | // Third boss 46 | "s-970-3000-1102-0": [{"type": "text","sub_type": "notification","message": " Left Hand"}], 47 | "s-970-3000-2102-0": [{"type": "text","sub_type": "notification","message": " Left Hand"}], 48 | 49 | //Right Hand 50 | "s-970-3000-1101-0": [{"type": "text","sub_type": "notification","message": " Right Hand"}], 51 | "s-970-3000-2101-0": [{"type": "text","sub_type": "notification","message": " Right Hand"}], 52 | 53 | //Tail Slam 54 | "s-970-3000-1103-0": [{"type": "text","sub_type": "notification","message": " Tail Slam"}], 55 | "s-970-3000-2103-0": [{"type": "text","sub_type": "notification","message": " Tail Slam"}], 56 | 57 | //FATE Avoid Circles 58 | "s-970-3000-1301-0": [{"type": "text","sub_type": "notification","message": " FATE Avoid Circles"}], 59 | 60 | //Tail AOE (jump in front) 61 | "s-970-3000-2110-0": [{"type": "text","sub_type": "notification","message": " Tail AOE (jump in front)"}], 62 | "s-970-3000-1110-0": [{"type": "text","sub_type": "notification","message": " Tail AOE (jump in front)"}], 63 | 64 | //Get Ready ! (for in out mechanic) 65 | "s-970-3000-1304-0": [{"type": "text","sub_type": "notification","message": " Get Ready ! (for in out mechanic)"}], 66 | "s-970-3000-1303-0": [{"type": "text","sub_type": "notification","message": " Get Ready ! (for in out mechanic)"}], 67 | 68 | //GO OUT then come in 69 | "s-970-3000-2113-0": [{"type": "text","sub_type": "notification","message": " OUT -> IN"}], 70 | "s-970-3000-1113-0": [{"type": "text","sub_type": "notification","message": " OUT -> IN"}], 71 | 72 | //STAY IN then go out 73 | "s-970-3000-2116-0": [{"type": "text","sub_type": "notification","message": " IN -> OUT"}], 74 | "s-970-3000-1116-0": [{"type": "text","sub_type": "notification","message": " IN -> OUT"}], 75 | 76 | //GET RED SKULL !! 77 | "s-970-3000-1318-0": [{"type": "text","sub_type": "notification","message": " GET RED SKULL !!"}], 78 | "s-970-3000-1317-0": [{"type": "text","sub_type": "notification","message": " GET RED SKULL !!"}], 79 | "s-970-3000-1319-0": [{"type": "text","sub_type": "notification","message": " GET RED SKULL !!"}], 80 | 81 | //DODGE the PATTERNS ! 82 | "s-970-3000-1322-0": [{"type": "text","sub_type": "notification","message": " DODGE the PATTERNS !"}], 83 | 84 | //GATHER FOR CLEANSE ! ! 85 | "s-970-3000-1311-0": [{"type": "text","sub_type": "notification","message": " GATHER FOR CLEANSE !"}] 86 | }; -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const DispatchWrapper = require('./dispatch'); 2 | const config = require('./config'); 3 | 4 | // Try to silently import the say dependency 5 | let say = null; 6 | try { say = require('say') } 7 | catch(e) { say = null; } 8 | 9 | // Tank class ids(brawler + lancer) 10 | const TANK_CLASS_IDS = [1, 10]; 11 | 12 | // Dps class ids(not counting warrior) 13 | const DPS_CLASS_IDS = [2, 3, 4, 5, 8, 9, 11, 12]; 14 | 15 | // Healer class ids 16 | const HEALER_CLASS_IDS = [6, 7]; 17 | 18 | // Warrior Defence stance abnormality ids 19 | const WARRIOR_TANK_IDS = [100200, 100201]; 20 | 21 | class TeraGuide{ 22 | constructor(dispatch) { 23 | const fake_dispatch = new DispatchWrapper(dispatch); 24 | const { player, entity, library, effect } = dispatch.require.library; 25 | const command = dispatch.command; 26 | 27 | // An object of types and their corresponding function handlers 28 | const function_event_handlers = { 29 | "spawn": spawn_handler, 30 | "despawn": despawn_handler, 31 | "text": text_handler, 32 | "sound": sound_handler, 33 | "stop_timer": stop_timer_handler, 34 | "func": func_handler, 35 | "lib": require('./lib') 36 | }; 37 | // export functionality for 3rd party modules 38 | this.handlers = function_event_handlers; 39 | 40 | // A boolean for if the module is enabled or not 41 | let enabled = config['enabled']; 42 | // A boolean for the debugging settings 43 | let debug = config['debug']; 44 | // A boolean for streamer mode 45 | let stream = config['stream']; 46 | 47 | // A boolean indicating if a guide was found 48 | let guide_found = false; 49 | // The guide settings for the current zone 50 | let active_guide = {}; 51 | 52 | // All of the timers, where the key is the id 53 | let random_timer_id = 0xFFFFFFFA; // Used if no id is specified 54 | let timers = {}; 55 | 56 | /** HELPER FUNCTIONS **/ 57 | 58 | // Write generic debug message used when creating guides 59 | function debug_message(d, ...args) { 60 | if(d) { 61 | console.log(`[${Date.now() % 100000}][Guide]`, ...args); 62 | if(debug.chat) command.message(args.toString()); 63 | } 64 | } 65 | 66 | // Makes sure the event passes the class position check 67 | function class_position_check(class_position) { 68 | // if it's not defined we assume that it's for everyone 69 | if(!class_position) return true; 70 | // If it's an array 71 | if(Array.isArray(class_position)) { 72 | // If one of the class_positions pass, we can accept it 73 | for(let ent of class_position) if(class_position_check(ent)) return true; 74 | 75 | // All class_positions failed, so we return false 76 | return false; 77 | } 78 | 79 | switch(class_position) { 80 | case "tank": { 81 | // if it's a warrior with dstance abnormality 82 | if(player.job === 0) { 83 | // Loop thru tank abnormalities 84 | for(let id of WARRIOR_TANK_IDS) { 85 | // if we have the tank abnormality return true 86 | if(effect.hasAbnormality(id)) return true; 87 | } 88 | } 89 | 90 | // if it's a tank return true 91 | if(TANK_CLASS_IDS.includes(player.job)) return true; 92 | break; 93 | } 94 | case "dps": { 95 | // If it's a warrior with dstance abnormality 96 | if(player.job === 0) { 97 | // Loop thru tank abnormalities 98 | for(let id of WARRIOR_TANK_IDS) { 99 | // if we have the tank abnormality return false 100 | if(effect.hasAbnormality(id)) return false; 101 | } 102 | // warrior didn't have tank abnormality 103 | return true; 104 | } 105 | 106 | // if it's a dps return true 107 | if(DPS_CLASS_IDS.includes(player.job)) return true; 108 | break; 109 | } 110 | case "heal": { 111 | // if it's a healer return true 112 | if(HEALER_CLASS_IDS.includes(player.job)) return true; 113 | break; 114 | } 115 | default: { 116 | debug_message(true, "Failed to find class_position value:", class_position); 117 | } 118 | } 119 | return false; 120 | } 121 | 122 | // Handle events such as boss skill and abnormalities triggered 123 | function handle_event(ent, id, called_from_identifier, prefix_identifier, d, speed=1.0, stage=false) { 124 | const unique_id = `${prefix_identifier}-${ent['huntingZoneId']}-${ent['templateId']}`; 125 | const key = `${unique_id}-${id}`; 126 | const stage_string = (stage===false ? '' : `-${stage}`); 127 | 128 | debug_message(d, `${called_from_identifier}: ${id} | Started by: ${unique_id} | key: ${key + stage_string}`); 129 | 130 | if(stage !== false) { 131 | const entry = active_guide[key + stage_string]; 132 | if(entry) start_events(entry, ent, speed); 133 | } 134 | 135 | const entry = active_guide[key]; 136 | if(entry) start_events(entry, ent, speed); 137 | } 138 | 139 | // This is where all the magic happens 140 | function start_events(events=[], ent, speed=1.0) { 141 | // Loop over the events 142 | for(let event of events) { 143 | const func = function_event_handlers[event['type']]; 144 | // The function couldn't be found, so it's an invalid type 145 | if(!func) debug_message(true, "An event has invalid type:", event['type']); 146 | // If the function is found and it passes the class position check, we start the event 147 | else if(class_position_check(event['class_position'])) func(event, ent, speed=1.0); 148 | } 149 | } 150 | 151 | /** S_ACTION_STAGE **/ 152 | 153 | // Boss skill action 154 | function s_action_stage(e) { 155 | // If the guide module is active and a guide for the current dungeon is found 156 | if(enabled && guide_found) { 157 | const ent = entity['mobs'][e.gameId.toString()]; 158 | // Due to a bug for some bizare reason(probably proxy fucking itself) we do this ugly hack 159 | e.loc.w = e.w; 160 | // We've confirmed it's a mob, so it's plausible we want to act on this 161 | if(ent) return handle_event(Object.assign({}, ent, e), e.skill.id, 'Skill', 's', debug.debug || debug.skill || (ent['templateId'] % 1000 === 0 ? debug.boss : false), e.speed, e.stage); 162 | } 163 | } 164 | dispatch.hook('S_ACTION_STAGE', 9, {order: 15}, s_action_stage); 165 | 166 | /** ABNORMALITY **/ 167 | 168 | // Boss abnormality triggered 169 | function abnormality_triggered(e) { 170 | // If the guide module is active and a guide for the current dungeon is found 171 | if(enabled && guide_found) { 172 | // avoid errors ResidentSleeper (neede for abnormality refresh) 173 | if(!e.source) e.source = 0n; 174 | 175 | // If the boss/mob get's a abnormality applied to it 176 | const target_ent = entity['mobs'][e.target.toString()]; 177 | 178 | // If the boss/mob is the cause for the abnormality 179 | const source_ent = entity['mobs'][e.source.toString()]; 180 | 181 | // If the mob/boss applies an abnormality to me, it's plausible we want to act on this 182 | if(source_ent && player.isMe(e.target)) handle_event(source_ent, e.id, 'Abnormality', 'am', debug.debug || debug.abnormal); 183 | 184 | // If "nothing"/server applies an abnormality to me, it's plausible we want to act on this. (spam rip) 185 | if(player.isMe(e.target) && 0 == (e.source || 0)) handle_event({ 186 | huntingZoneId: 0, 187 | templateId: 0 188 | }, e.id, 'Abnormality', 'ae', debug.debug || debug.abnormal); 189 | 190 | // If it's a mob/boss getting an abnormality applied to itself, it's plausible we want to act on it 191 | if(target_ent) handle_event(target_ent, e.id, 'Abnormality', 'ab', debug.debug || debug.abnormal); 192 | } 193 | } 194 | dispatch.hook('S_ABNORMALITY_BEGIN', 3, {order: 15}, abnormality_triggered); 195 | dispatch.hook('S_ABNORMALITY_REFRESH', 1, {order: 15}, abnormality_triggered); 196 | 197 | /** HEALTH **/ 198 | 199 | // Boss health bar triggered 200 | dispatch.hook('S_BOSS_GAGE_INFO', 3, e=> { 201 | // If the guide module is active and a guide for the current dungeon is found 202 | if(enabled && guide_found) { 203 | const ent = entity['mobs'][e.id.toString()]; 204 | // We've confirmed it's a mob, so it's plausible we want to act on this 205 | if(ent) return handle_event(ent, Math.floor(Number(e.curHp) / Number(e.maxHp) * 100), 'Health', 'h', debug.debug || debug.hp); 206 | } 207 | }); 208 | 209 | /** S_DUNGEON_EVENT_MESSAGE **/ 210 | 211 | dispatch.hook('S_DUNGEON_EVENT_MESSAGE', 2, e=> { 212 | if (enabled && guide_found) { 213 | const result = /@dungeon:(\d+)/g.exec(e.message); 214 | if (result) { 215 | handle_event({ 216 | huntingZoneId: 0, 217 | templateId: 0 218 | }, parseInt(result[1]), 'Dungeon Message', 'dm', debug.debug || debug.dm); 219 | } 220 | } 221 | }); 222 | 223 | /** S_QUEST_BALLOON **/ 224 | 225 | dispatch.hook('S_QUEST_BALLOON', 1, e=> { 226 | if (enabled && guide_found) { 227 | const source_ent = entity['mobs'][e.source.toString()]; 228 | const result = /@monsterBehavior:(\d+)/g.exec(e.message); 229 | if (result && source_ent) { 230 | handle_event(source_ent, parseInt(result[1]), 'Quest Balloon', 'qb', debug.debug || debug.qb); 231 | } 232 | } 233 | }); 234 | 235 | /** MISC **/ 236 | 237 | // Load guide and clear out timers 238 | dispatch.hook('S_LOAD_TOPO', 3, e=> { 239 | // Clear out the timers 240 | for(let key in timers) clearTimeout(timers[key]); 241 | timers = {}; 242 | 243 | // Clear out previous hooks, that our previous guide module hooked 244 | fake_dispatch._remove_all_hooks(); 245 | 246 | // Send debug message 247 | debug_message(debug.debug, 'Entered zone:', e.zone); 248 | 249 | // Remove potential cached guide from require cache, so that we don't need to relog to refresh guide 250 | try { 251 | delete require.cache[require.resolve('./guides/' + e.zone)]; 252 | }catch(e) {} 253 | 254 | 255 | // Try loading a guide 256 | try { 257 | active_guide = require('./guides/' + e.zone); 258 | guide_found = true; 259 | }catch(e) { 260 | active_guide = {}; 261 | guide_found = false; 262 | debug_message(debug.debug, e); 263 | } 264 | 265 | // Try calling the "load" function 266 | try { 267 | active_guide.load(fake_dispatch); 268 | }catch(e) { debug_message(debug.debug, e); } 269 | }); 270 | 271 | // Guide command 272 | command.add('guide', { 273 | // Toggle debug settings 274 | debug(arg1) { 275 | if(!arg1 || debug[arg1] === undefined) return command.message(`Invalid sub command for debug mode. ${arg1}`); 276 | debug[arg1] = !debug[arg1]; 277 | command.message(`Guide module debug(${arg1}) mode has been ${debug[arg1]?"enabled":"disabled"}.`); 278 | }, 279 | // Testing events 280 | event(arg1, arg2) { 281 | // If we didn't get a second argument or the argument value isn't an event type, we return 282 | if(arg1 === "trigger" ? (!active_guide[arg2]) : (!arg1 || !function_event_handlers[arg1] || !arg2)) return command.message(`Invalid values for sub command "event" ${arg1} | ${arg2}`); 283 | 284 | // if arg2 is "trigger". It means we want to trigger a event 285 | if(arg1 === "trigger") 286 | start_events(active_guide[arg2], player); 287 | else 288 | // Call a function handler with the event we got from arg2 with yourself as the entity 289 | function_event_handlers[arg1](JSON.parse(arg2), player); 290 | }, 291 | stream() { 292 | stream = !stream; 293 | command.message(`Streamer mode has been ${stream?"enabled":"disabled"}.`); 294 | }, 295 | $default() { 296 | enabled = !enabled; 297 | command.message(`Guide module has been ${enabled?"enabled":"disabled"}.`); 298 | } 299 | }); 300 | 301 | /** Function/event handlers for types **/ 302 | 303 | // Spawn handler 304 | function spawn_handler(event, ent, speed=1.0) { 305 | // Make sure id is defined 306 | if(!event['id']) return debug_message(true, "Spawn handler needs a id"); 307 | // Make sure sub_delay is defined 308 | if(!event['sub_delay']) return debug_message(true, "Spawn handler needs a sub_delay"); 309 | // Make sure distance is defined 310 | //if(!event['distance']) return debug_message(true, "Spawn handler needs a distance"); 311 | // Ignore if streamer mode is enabled 312 | if(stream) return; 313 | 314 | // Set sub_type to be collection as default for backward compatibility 315 | const sub_type = event['sub_type'] || 'collection'; 316 | 317 | // The unique spawned id this item will be using. 318 | const item_unique_id = event['force_gameId'] || random_timer_id--; 319 | 320 | // The location of the item spawned 321 | let loc = ent['loc'].clone(); 322 | 323 | // if pos is set, we use that 324 | if(event['pos']) loc = event['pos']; 325 | 326 | loc.w = (ent['loc'].w || 0) + (event['offset'] || 0); 327 | library.applyDistance(loc, event['distance'] || 0); 328 | 329 | let sending_event = { 330 | gameId: item_unique_id, 331 | loc: loc, 332 | w: loc.w 333 | }; 334 | 335 | const despawn_event = { 336 | gameId: item_unique_id, 337 | unk: 0, // used in S_DESPAWN_BUILD_OBJECT 338 | collected: false // used in S_DESPAWN_COLLECTION 339 | }; 340 | 341 | // Create the sending event 342 | switch(sub_type) { 343 | // If it's type collection, it's S_SPAWN_COLLECTION 344 | case "collection": { 345 | Object.assign(sending_event, { 346 | id: event['id'], 347 | amount: 1, 348 | extractor: false, 349 | extractorDisabled: false, 350 | extractorDisabledTime: 0 351 | }); 352 | break; 353 | } 354 | // If it's type item, it's S_SPAWN_DROPITEM 355 | case "item": { 356 | Object.assign(sending_event, { 357 | item: event['id'], 358 | amount: 1, 359 | expiry: 0, 360 | explode: false, 361 | masterwork: false, 362 | enchant: 0, 363 | debug: false, 364 | owners: [] 365 | }); 366 | break; 367 | } 368 | // If it's type build_object, it's S_SPAWN_BUILD_OBJECT 369 | case "build_object": { 370 | Object.assign(sending_event, { 371 | itemId : event['id'], 372 | unk : 0, 373 | ownerName : event['ownerName'] || '', 374 | message : event['message'] || '' 375 | }); 376 | break; 377 | } 378 | // If we haven't implemented the sub_type the event asks for 379 | default: { 380 | return debug_message(true, "Invalid sub_type for spawn handler:", event['sub_type']); 381 | } 382 | } 383 | 384 | // Create the timer for spawning the item 385 | timers[item_unique_id] = setTimeout(()=> { 386 | switch(sub_type) { 387 | case "collection": return dispatch.toClient('S_SPAWN_COLLECTION', 4, sending_event); 388 | case "item": return dispatch.toClient('S_SPAWN_DROPITEM', 8, sending_event); 389 | case "build_object": return dispatch.toClient('S_SPAWN_BUILD_OBJECT', 2, sending_event); 390 | } 391 | }, event['delay'] || 0 / speed); 392 | 393 | // Create the timer for despawning the item 394 | timers[random_timer_id--] = setTimeout(()=> { 395 | switch(sub_type) { 396 | case "collection": return dispatch.toClient('S_DESPAWN_COLLECTION', 2, despawn_event); 397 | case "item": return dispatch.toClient('S_DESPAWN_DROPITEM', 4, despawn_event); 398 | case "build_object": return dispatch.toClient('S_DESPAWN_BUILD_OBJECT', 2, despawn_event); 399 | } 400 | }, event['sub_delay'] / speed); 401 | } 402 | 403 | // Despawn handler 404 | function despawn_handler(event) { 405 | // Make sure id is defined 406 | if(!event['id']) return debug_message(true, "Spawn handler needs a id"); 407 | // Ignore if streamer mode is enabled 408 | if(stream) return; 409 | 410 | // Set sub_type to be collection as default for backward compatibility 411 | const sub_type = event['sub_type'] || 'collection'; 412 | 413 | const despawn_event = { 414 | gameId: event['id'], 415 | unk: 0, // used in S_DESPAWN_BUILD_OBJECT 416 | collected: false // used in S_DESPAWN_COLLECTION 417 | }; 418 | 419 | switch(sub_type) { 420 | case "collection": return dispatch.toClient('S_DESPAWN_COLLECTION', 2, despawn_event); 421 | case "item": return dispatch.toClient('S_DESPAWN_DROPITEM', 4, despawn_event); 422 | case "build_object": return dispatch.toClient('S_DESPAWN_BUILD_OBJECT', 2, despawn_event); 423 | default: return debug_message(true, "Invalid sub_type for despawn handler:", event['sub_type']); 424 | } 425 | } 426 | 427 | // Text handler 428 | function text_handler(event, ent, speed=1.0) { 429 | // Fetch the message(with region tag) 430 | const message = event[`message_${dispatch.language}`] || event[`message_${dispatch.language.toUpperCase()}`] || event['message']; 431 | // Make sure sub_type is defined 432 | if(!event['sub_type']) return debug_message(true, "Text handler needs a sub_type"); 433 | // Make sure message is defined 434 | if(!message) return debug_message(true, "Text handler needs a message"); 435 | 436 | let sending_event = {}; 437 | // Create the sending event 438 | switch(event['sub_type']) { 439 | // If it's type message, it's S_DUNGEON_EVENT_MESSAGE with type 41 440 | case "message": { 441 | sending_event = { 442 | message, 443 | type: 41, 444 | chat: false, 445 | channel: 0 446 | }; 447 | break; 448 | } 449 | // If it's type notification, it's S_CHAT with channel 21 450 | case "notification": { 451 | sending_event = { 452 | channel: 21, 453 | authorName: config['chat-name'], 454 | message 455 | }; 456 | break; 457 | } 458 | // If it's type speech, it's text to speech. But since it isn't "required" to a try/catch 459 | case "speech": { 460 | // Ignore if streamer mode is enabled 461 | if(stream) return; 462 | 463 | // if the say dependency was found 464 | if(say) { 465 | timers[event['id'] || random_timer_id--] = setTimeout(()=> { 466 | say.speak(message); 467 | }, (event['delay'] || 0 ) / speed); 468 | } 469 | return; 470 | } 471 | // If we haven't implemented the sub_type the event asks for 472 | default: { 473 | return debug_message(true, "Invalid sub_type for text handler:", event['sub_type']); 474 | } 475 | } 476 | 477 | // Create the timer 478 | timers[event['id'] || random_timer_id--] = setTimeout(()=> { 479 | if (!stream) { 480 | switch(event['sub_type']) { 481 | case "message": return dispatch.toClient('S_DUNGEON_EVENT_MESSAGE', 2, sending_event); 482 | case "notification": return dispatch.toClient('S_CHAT', 3, sending_event); 483 | } 484 | } else { 485 | // If streamer mode is enabled, send message all messages to party chat instead 486 | return dispatch.toClient('S_CHAT', 3, { channel: 1, name: config['chat-name'], message }); 487 | } 488 | }, (event['delay'] || 0 ) / speed); 489 | } 490 | 491 | // Sound handler 492 | function sound_handler(event, ent, speed=1.0) { 493 | // Make sure id is defined 494 | if(!event['id']) return debug_message(true, "Sound handler needs a id"); 495 | // Ignore if streamer mode is enabled 496 | if(stream) return; 497 | 498 | // Create the timer 499 | timers[event['id']] = setTimeout(()=> { 500 | // Send the sound 501 | dispatch.toClient('S_PLAY_SOUND', 1, { 502 | SoundID: event['id'] 503 | }); 504 | }); 505 | } 506 | 507 | // Stop timer handler 508 | function stop_timer_handler(event, ent, speed=1.0) { 509 | // Make sure id is defined 510 | if(!event['id']) return debug_message(true, "Stop timer handler needs a id"); 511 | 512 | // Check if that entry exists, if it doesn't print out a debug message. This is because users can make mistakes 513 | if(!timers[event['id']]) return debug_message(true, `There isn't a timer with tie id: ${event['id']} active`); 514 | 515 | // clearout the timer 516 | clearTimeout(timers[event['id']]); 517 | } 518 | 519 | // Func handler 520 | function func_handler(event, ent, speed=1.0) { 521 | // Make sure func is defined 522 | if(!event['func']) return debug_message(true, "Func handler needs a func"); 523 | 524 | // Start the timer for the function call 525 | timers[event['id'] || random_timer_id--] = setTimeout(event['func'], (event['delay'] || 0) / speed, function_event_handlers, event, ent, fake_dispatch); 526 | } 527 | } 528 | } 529 | 530 | module.exports = TeraGuide; -------------------------------------------------------------------------------- /lib.js: -------------------------------------------------------------------------------- 1 | const {Vec3} = require('tera-data-parser').types; 2 | 3 | let global_gameId_tracker = 1; 4 | 5 | /** 6 | * Create entities in a circle 7 | * @param {*} handlers handlers reference passed from main 8 | * @param {*} event_data parameteres for the data - see docs 9 | * @param {*} entity_count how many of the entity to spawn 10 | * @param {*} center where the center location is 11 | * @param {*} distance_from_center how far from center the entity should be spawned 12 | * 13 | * Returns an array of all the gameIds used to spawn these entities 14 | */ 15 | function create_entities_in_circle(handlers, event_data, entity_count, center, distance_from_center) { 16 | center = new Vec3(center); 17 | let ret = []; 18 | 19 | for(let angle = -Math.PI; angle <= Math.PI; angle += (2 * Math.PI / entity_count)) { 20 | const gameId = global_gameId_tracker++; 21 | ret.push(gameId); 22 | 23 | handlers['spawn'](Object.assign(event_data, { 24 | offset: angle, 25 | distance: distance_from_center, 26 | force_gameId: gameId 27 | }), { 28 | loc: center 29 | }); 30 | } 31 | 32 | return ret; 33 | } 34 | 35 | module.exports = { 36 | create_entities_in_circle 37 | }; -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": { 3 | "config.json": "b56701c0166c8ce6fa8e22c7b9d775ae2797ead50411acec53d5a9a13431b115", 4 | "dispatch.js": "521dce3d7a3f4aa1a985998a635cfeb6114155d762dc211bb8d27162de1aea90", 5 | "index.js": "dae665e3ed45b574cd20a0fe6fd09ce5eda17011cd76fabb5277bc5b3a9b0ec1", 6 | "lib.js": "f4eb0275e953f0abb72708b7e068c0f3b0a84e09afa0eca1d80f13f2b316421a", 7 | "LICENSE": "7f2ae2125b491e75c546a5d538f82ad4431d5d6b1bbd4aea724864a21daeca63", 8 | "readme.md": "f280a8d0dd0678983d82a1986f410aca986890b3ee70434b44fee73bbef899ad", 9 | "guides/9034.js": "19c64a4edcc64c03b0d491bd6ad7a8bc530c29f896e6627ef10869a0f519e30e", 10 | "guides/9044.js": { 11 | "hash": "41e94ed19b68473d65a5fb731665a9448b6ac37af7d45d07694d00a7ba045dac", 12 | "overwrite": false 13 | }, 14 | "guides/9720.js": "97e22d38be7eef7329f8dc66b6d21eca6acaa02cfec3450a24934ae9e83d85ca", 15 | "guides/9920.js": "02948b350cbcb93e1ad593d40fd4d5f2cecae6b4a1c3ae726a99d25e6c35d087", 16 | "guides/9920_old.js": "bf2ec9b452d2bbedc77dfc3a1ee61c522fff054f8b8afe3ebc265ef5252c166f", 17 | "guides/9950.js": "640beb30746d70b83d2662db3655697de0a3d04a7a8d7f5e7ee67f105c408775", 18 | "guides/9970.js": "7e657275587befea92e50f2d011f6dfb3732396bef1ca8dbcf795fab65e53adb", 19 | "guides/3202.js": { 20 | "hash": "8797c4d6e930118b54ddb723965d25d0ff61ef93a104eebcb5c96a8513cedeeb", 21 | "overwrite": false 22 | } 23 | }, 24 | "defs": {} 25 | } -------------------------------------------------------------------------------- /module.json: -------------------------------------------------------------------------------- 1 | { 2 | "servers": ["https://raw.githubusercontent.com/tera-toolbox-mods/tera-guide/master/"], 3 | "supportUrl": "https://github.com/tera-toolbox-mods/tera-guide/issues", 4 | "name": "tera-guide", 5 | "author": "Kasea", 6 | "dependencies": { 7 | "library": "https://raw.githubusercontent.com/tera-toolbox-mods/library/master/module.json" 8 | }, 9 | "options": { 10 | "guiName": "Tera Guide" 11 | }, 12 | "description": "Guides you in Tera" 13 | } 14 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Tera-Guide 2 | 3 | Is a module for creating dungeon guides extremely easily by simple "scripting". 4 | 5 | ## Dependencies 6 | https://github.com/Kaseaa/library (it also has a readme) 7 | 8 | The say node library used for text-to-speech(works without it, so not needed). Install it by running "`npm install say`" in your proxy folder. 9 | 10 | ## Commands 11 | Note: All commands starts with "guide" 12 | 13 | Command | Usage | Arguments 14 | --- | --- | --- 15 | \- | Toggles the guide module | \- 16 | debug | Toggles debug mode | debug(enables everything), hp, abnormal, skill, boss, chat(prints to in chat rather than console), dm, qb 17 | event | triggeres an event, where you are the entity. Used for testing | First argument is a **type** from "Options". The second argument contains the values(or the keyword `trigger`). Example: `/8 guide event spawn '{"id": 500, "sub_delay": 5000}'`, `/8 guide event trigger "h-444-2000-97"` 18 | stream | Toggles streamer mode | \- 19 | 20 | ## Supported 21 | Feel free to create your own guide/add-on and send a pull-request :=) 22 | 23 | Dungeon | How much is it scripted 24 | --- | --- 25 | AANM | All bosses 26 | AAHM | All bosses 27 | HH P4 | Firewalls 28 | RMHM | Debuff mechanic on first boss 29 | 30 | 31 | ## How to create a guide 32 | First enable debug setting in config.json, then go into the dungeon you wish to create a guide for. 33 | Once you've entered look in console for the id. Once you have the id create a file under the guides folder. 34 | The file should start with the number followed by ".js". 35 | 36 | It's important to note you will need to include the very basic information for the file to get loaded and for the module to print out information. It won't print out anything if it can't find number.js file. (`module.exports = {};`) 37 | 38 | When the module get's loaded, you're allowed to hook hooks using a "load" key. This key will get called with a dispatch argument passed upon getting loaded. The hooks will be automatically unloaded once the module finishes it's runtime. 39 | 40 | So, now to create actions have debug option on and log the attacks/abnormalities then script what you want to happen during those attacks. Please refer below to options(things you can trigger upon the abnormality or attack). 41 | 42 | ### Naming keys 43 | Prefix a means handle a abnormality. However in addition to this there are subtypes to abnormalities. 44 | 45 | Sub type | Effect 46 | --- | --- 47 | m | The abnormality was applied by a mob to you. (m = mob) 48 | e | The abnormality was applied by "nothing"/server to you. (e = empty) 49 | b | The abnormality was applied to the bam. (b = bam) 50 | 51 | Prefix s means handle a skill. 52 | 53 | Prefix h means health. For instance "h-huntingZoneId-templateId-60", means it'll trigger on 60% 54 | 55 | Prefix dm means dungeon message. 56 | 57 | Prefix qb means quest balloon. 58 | 59 | When creating an entry use a prefix, followed by "-huntingZoneId-templateId-" after the last "-" put the abnormality id, skill id or hp %. 60 | 61 | ### Options 62 | Key | Required | Values | Usage 63 | --- | --- | --- | --- 64 | type | Yes | spawn, text, sound, stop_timer, func | Determines what action(s) will be taken when the event is called 65 | sub_type | Yes(text), Maybe(spawn), No | notification(text), message(text), speech(text), collection(spawn(default)), item(spawn), build_object(spawn) | Used in conjunction with certain types. 66 | id | Yes(spawn, sound, stop_timer), No | A unique identifier not used "anywhere" else in the file. | It's used for spawning item(the item id), stopping a notification/text in progress and id of a sound. **DO NOT USE 0 FOR THE ID IN ANY CASE!** 67 | delay | No | A delay in milliseconds | Creates a timer that can be used to delay ANY event. 68 | sub_delay | Yes(spawn), No | How long in milliseconds before it acts upon it. | Used with spawning items 69 | distance | Maybe(spawn), No | Distance in units(1meter = 25 units) | Used when spawning something. (How far away it'll be spawned from the boss) 70 | offset | Maybe(spawn), No | [-PI, PI] | Used when spawning something. (How it will be spawned in relation to the boss) **use this or pos** 71 | pos | Maybe(spawn), No | {x, y, z} | Used when spawning something. (The location on the map) **use this or offset** 72 | message | Yes(text), No | The text you wish to display when it gets triggered | Use with type text 73 | func | Yes(func), No | A function which should get called when an entry is called. | The callback will get passed the function handlers(spawn, text, sound, stop_timer, func) in a object, the event it was called from, what entity information that triggered the event and the dispatch 74 | class_position | No | tank, dps, heal | if this is used, it will only do the event for that class position 75 | 76 | ## Todo(in the extremely distance future of probably never) 77 | Add interface for other modules to require/import the script and use it 78 | 79 | Allow other modules to bind names to mob indexes. Such as 920-4000 -> Vergos_Phase4 Allowing config files to say "h-Vergos_Phase4-97" for 97% hp --------------------------------------------------------------------------------