1/* The Torchbearer â Fury and the weapon Ultimates. 2 * 3 * The combat loop had no crescendo: every fight was the same three verbs at 4 * the same intensity. Fury fixes that. You build it by fighting well â hits 5 * feed it a little, finishers and parries feed it a lot, taking damage burns 6 * it â and when it fills you spend it on a weapon-specific Ultimate. 7 * 8 * The meter is the reward for aggression; the Ultimate is the release. 9 */ 10window.SK = window.SK || {}; 11 12SK.FURY_MAX = 100; 13 14SK.FURY_GAIN = { 15 hit: 4, 16 finisher: 10, 17 charged: 12, 18 kill: 7, 19 elite: 15, 20 parry: 25, // parrying is the fastest route to an Ultimate 21 hurt: -20 // and getting hit is the fastest way to lose one 22}; 23 24/* Each weapon spends Fury differently â same button, three fantasies. */ 25SK.ULTIMATES = { 26 sword: { 27 name: 'BLADE STORM', 28 descr: 'Seven whirlwinds in four seconds.', 29 kind: 'storm', 30 waves: 7, interval: 190, radius: 150, damage: 34, knock: 260 31 }, 32 spear: { 33 name: 'THOUSAND LANCES', 34 descr: 'A spear for every direction, three times over.', 35 kind: 'lances', 36 volleys: 3, interval: 260, spears: 10, range: 300, damage: 62, knock: 200 37 }, 38 axe: { 39 name: 'CATACLYSM', 40 descr: 'Three quakes, each wider than the last.', 41 kind: 'cataclysm', 42 quakes: 3, interval: 420, radius: [180, 260, 340], damage: 70, knock: 520 43 }, 44 bow: { 45 name: 'ARROW STORM', 46 descr: 'Five volleys of piercing shafts, sweeping the hall.', 47 kind: 'volley', 48 volleys: 5, interval: 220, shots: 9, spread: 0.5, 49 damage: 38, knock: 180, speed: 780 50 }, 51 staff: { 52 name: 'STARFALL', 53 descr: 'Four expanding rings of bursting light.', 54 kind: 'rings', 55 rings: 4, interval: 300, bolts: 14, damage: 44, knock: 220, 56 speed: 430, burst: 60 57 }, 58 59 /* These three shipped without an Ultimate at all, so pressing E while 60 * carrying one did nothing AND still emptied the meter. Every weapon in 61 * SK.WEAPON_IDS must have an entry here; there is a test for it now. */ 62 dagger: { 63 name: 'THOUSAND CUTS', 64 descr: 'Twelve strikes in three seconds. Nothing survives the middle.', 65 kind: 'storm', 66 waves: 12, interval: 110, radius: 118, damage: 22, knock: 170 67 }, 68 crossbow: { 69 name: 'SIEGE VOLLEY', 70 descr: 'Four salvos of bolts that punch through everything.', 71 kind: 'volley', 72 volleys: 4, interval: 300, shots: 7, spread: 0.42, 73 damage: 60, knock: 300, speed: 900 74 }, 75 froststaff: { 76 name: 'WHITEOUT', 77 descr: 'Five rings of killing frost. The hall stops moving.', 78 kind: 'rings', 79 rings: 5, interval: 260, bolts: 12, damage: 36, knock: 180, 80 speed: 400, burst: 44 81 } 82}; 83 84SK.Fury = class Fury { 85 constructor(scene) { 86 this.scene = scene; 87 this.value = 0; 88 this.active = false; // an Ultimate is currently playing 89 this.readyPulsed = false; 90 } 91 92 add(kind) { 93 if (this.active) return; 94 let g = SK.FURY_GAIN[kind] || 0; 95 // The Magister burns hotter. 96 const fm = this.scene.run.mods.furyMult || 1; 97 if (g > 0) g *= fm; 98 const before = this.value; 99 this.value = Phaser.Math.Clamp(this.value + g, 0, SK.FURY_MAX); 100 101 // Announce the moment it fills â the player must know it is available. 102 if (before < SK.FURY_MAX && this.value >= SK.FURY_MAX) { 103 this.readyPulsed = true; 104 const p = this.scene.player; 105 SK.juice.popup(this.scene, p.x, p.y - 60, 'FURY READY [E]', SK.CSS.gold, 20); 106 SK.juice.shockwave(this.scene, p.x, p.y, 0xffcc33, 140); 107 SK.synth.chargeReady(); 108 if (this.scene.lighting) { 109 this.scene.lighting.flash(p.x, p.y, 260, 0xffcc33, 300); 110 } 111 } 112 } 113 114 get ready() { return this.value >= SK.FURY_MAX && !this.active; } 115 116 /* Spend the meter. Returns false if it was not full. */ 117 unleash() { 118 if (!this.ready) return false; 119 120 const scene = this.scene; 121 const w = SK.currentWeapon(scene.run); 122 const ult = SK.ULTIMATES[w.id]; 123 124 /* Check BEFORE spending. The meter used to be zeroed on the line above 125 * this guard, so carrying a weapon with no Ultimate meant filling Fury to 126 * 100, pressing E, and watching the whole bar vanish for nothing. */ 127 if (!ult || typeof this['_' + ult.kind] !== 'function') { 128 SK.juice.popup(scene, scene.player.x, scene.player.y - 40,
129 'NO ULTIMATE FOR THIS ARM', SK.CSS.red, 15); 130 return false; 131 } 132 133 this.value = 0; 134 this.active = true; 135 136 scene.run.stats.ultimates = (scene.run.stats.ultimates || 0) + 1; 137 138 // The announcement beat. 139 scene.cameras.main.flash(260, 255, 220, 150); 140 SK.shake(scene, 14, 400); 141 SK.shock(scene, 1); 142 SK.juice.slowmo(scene, 0.4, 420); 143 SK.juice.popup(scene, scene.player.x, scene.player.y - 70, ult.name, 144 SK.CSS.gold, 28); 145 SK.synth.victory(); 146 scene.player.iframesUntil = scene.time.now + 900; // untouchable mid-ult 147 148 this['_' + ult.kind](scene, ult, w); 149 return true; 150 } 151 152 _storm(scene, ult, w) { 153 for (let i = 0; i < ult.waves; i++) { 154 scene.time.delayedCall(i * ult.interval, () => { 155 if (!scene.player || !scene.player.sprite) return; 156 scene.ultStrike({ 157 x: scene.player.x, y: scene.player.y, angle: 0, 158 range: ult.radius, arc: Math.PI * 2, 159 dmg: ult.damage, knock: ult.knock, weapon: w, 160 stagger: true, charged: true, finisher: true 161 }); 162 SK.juice.shockwave(scene, scene.player.x, scene.player.y, w.glow, ult.radius); 163 if (SK.synth.shoot) SK.synth.shoot(w.kind, w.pitch, true); 164 else SK.synth.swing(w.pitch, true); 165 if (i === ult.waves - 1) this.active = false; 166 }); 167 } 168 } 169 170 _lances(scene, ult, w) { 171 for (let v = 0; v < ult.volleys; v++) { 172 scene.time.delayedCall(v * ult.interval, () => { 173 if (!scene.player || !scene.player.sprite) return; 174 const off = (v * Math.PI) / ult.spears; 175 // One damage instance per target for the whole volley: ten spears 176 // used to each hit the boss, which deleted it outright. 177 const volleyHits = new Set(); 178 for (let i = 0; i < ult.spears; i++) { 179 const a = off + (i / ult.spears) * Math.PI * 2; 180 scene.ultStrike({ 181 hitSet: volleyHits, 182 x: scene.player.x, y: scene.player.y, angle: a, 183 range: ult.range, arc: 0.42, 184 dmg: ult.damage, knock: ult.knock, weapon: w, 185 stagger: true, charged: true, finisher: true 186 }); 187 scene.spawnLance(scene.player.x, scene.player.y, a, ult.range, w.glow); 188 } 189 SK.synth.swing(w.pitch, true); 190 if (v === ult.volleys - 1) this.active = false; 191 }); 192 } 193 } 194 195 /* Ranged Ultimates fire real projectiles, so they inherit pierce and burst 196 * from the weapon rather than needing their own hit resolution. */ 197 _volley(scene, ult, w) { 198 for (let v = 0; v < ult.volleys; v++) { 199 scene.time.delayedCall(v * ult.interval, () => { 200 if (!scene.player || !scene.player.sprite) return; 201 const base = scene.player.aimAngle; 202 const shared = new Set(); // the volley may hit a target once 203 for (let i = 0; i < ult.shots; i++) { 204 const a = base + (i - (ult.shots - 1) / 2) * (ult.spread / ult.shots) * 2; 205 scene.spawnPlayerBolt(scene.player.x, scene.player.y, a, { 206 speed: ult.speed, dmg: ult.damage, weapon: w, range: 700, 207 knock: ult.knock, pierce: true, finisher: true, charged: true, 208 // The volley has ONE report, played below. Without this a 5-shot 209 // lance volley fires five stacked samples and clips the master. 210 silent: true, 211 hitSet: shared 212 }); 213 } 214 SK.synth.swing(w.pitch, true); 215 if (v === ult.volleys - 1) this.active = false; 216 }); 217 } 218 } 219 220 _rings(scene, ult, w) { 221 for (let r = 0; r < ult.rings; r++) { 222 scene.time.delayedCall(r * ult.interval, () => { 223 if (!scene.player || !scene.player.sprite) return; 224 const off = r * 0.22; 225 const shared = new Set(); 226 for (let i = 0; i < ult.bolts; i++) { 227 scene.spawnPlayerBolt(scene.player.x, scene.player.y, 228 off + (i / ult.bolts) * Math.PI * 2, { 229 speed: ult.speed, dmg: ult.damage, weapon: w, range: 520, 230 knock: ult.knock, burst: ult.burst, finisher: true, charged: true, 231 silent: true, // a 12-bolt ring is one sound, not twelve 232 hitSet: shared 233 }); 234 } 235 SK.shake(scene, 8, 200); 236 SK.synth.swing(w.pitch, true); 237 if (r === ult.rings - 1) this.active = false;
238 }); 239 } 240 } 241 242 _cataclysm(scene, ult, w) { 243 for (let i = 0; i < ult.quakes; i++) { 244 scene.time.delayedCall(i * ult.interval, () => { 245 if (!scene.player || !scene.player.sprite) return; 246 const r = ult.radius[i] || ult.radius[ult.radius.length - 1]; 247 scene.ultStrike({ 248 x: scene.player.x, y: scene.player.y, angle: 0, 249 range: r, arc: Math.PI * 2, 250 dmg: ult.damage, knock: ult.knock, weapon: w, 251 stagger: true, charged: true, finisher: true, slam: true 252 }); 253 SK.juice.shockwave(scene, scene.player.x, scene.player.y, w.glow, r); 254 SK.shake(scene, 10 + i * 4, 260); 255 if (scene.lighting) { 256 scene.lighting.flash(scene.player.x, scene.player.y, r * 1.5, w.glow, 260); 257 } 258 SK.synth.swing(w.pitch * 0.8, true); 259 if (i === ult.quakes - 1) this.active = false; 260 }); 261 } 262 } 263};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.