diff options
| author | LLLL Colonq <llll@colonq> | 2026-07-17 15:19:40 -0400 |
|---|---|---|
| committer | LLLL Colonq <llll@colonq> | 2026-07-17 15:19:40 -0400 |
| commit | 631fbd3a6567afa5ed9c5f8bb5fe1f32480fabc6 (patch) | |
| tree | f584b775cb7832448b06cf3c222cc953d160bc91 /2026/games_submissions/cammymoop/js/goblin.js | |
| parent | 07a36f2b38a5cba402ebc5158710f5a85f443456 (diff) | |
Update
Diffstat (limited to '2026/games_submissions/cammymoop/js/goblin.js')
| -rw-r--r-- | 2026/games_submissions/cammymoop/js/goblin.js | 521 |
1 files changed, 521 insertions, 0 deletions
diff --git a/2026/games_submissions/cammymoop/js/goblin.js b/2026/games_submissions/cammymoop/js/goblin.js new file mode 100644 index 0000000..2514cdf --- /dev/null +++ b/2026/games_submissions/cammymoop/js/goblin.js @@ -0,0 +1,521 @@ + +M.Enemy = class extends Phaser.GameObjects.Container +{ + constructor(scene) { + super(scene); + this.sprites = {} + this.spriteNames = []; + this.homePos = M.Vec2.ZERO; + this.hint = null; + this.alive = true; + this.gone = false; + this.deathAnimating = false; + } + + setHomePos(newHomePos) { + this.setPosVec(newHomePos); + this.homePos = new M.Vec2(newHomePos.x, newHomePos.y); + } + + addToDisplayUpdate() { + this.addToDisplayList(); + this.addToUpdateList(); + } + + setupHint(height, hintData) { + this.addHintData(hintData); + this.hint.y = -height; + } + + addHintData(hintData) { + if (this.hint) { + this.remove(this.hint); + this.hint.destroy(); + } + this.hint = new M.Hint(this.scene); + this.add(this.hint); + this.hint.setup(hintData); + } + + hideHint() { + if (this.hint) { + this.hint.visible = false; + } + } + + showHint() { + if (this.hint) { + this.hint.visible = true; + } + } + + removeSprite(spriteName) { + if (!this.sprites[spriteName]) { + return; + } + if (this.exists(this.sprites[spriteName])) { + this.sprites[spriteName].destroy(); + this.remove(this.sprites[spriteName]); + } + const nameIndex = this.spriteNames.indexOf(spriteName); + if (nameIndex >= 0) { + this.spriteNames.splice(nameIndex, 1); + } + delete this.sprites[spriteName]; + } + + addSprite(spriteName, pos, textureKey, frameIdx) { + if (!pos) { + pos = {x: 0, y: 0}; + } + if (this.sprites[spriteName]) { + removeSprite(spriteName); + } + if (!this.spriteNames.includes(spriteName)) { + this.spriteNames.push(spriteName); + } + const spr = new Phaser.GameObjects.Sprite(this.scene, pos.x, pos.y, textureKey, frameIdx); + this.sprites[spriteName] = spr; + this.add(spr); + } + + tintSprites(newTint) { + for (let spName of this.spriteNames) { + this.sprites[spName].setTint(newTint); + } + } + + resetSpritesTint() { + for (let spName of this.spriteNames) { + this.sprites[spName].clearTint(); + } + } + + preUpdate (t, dt) { + super.preUpdate(t, dt); + } + + attack(diceValue) { + console.log("base enemy attacked: " + diceValue); + } + +} + +M.Goblin = class extends M.Enemy +{ + constructor(scene, withShield = false) { + super(scene); + this.baseAnimInterval = 160; + this.anim_interval = this.baseAnimInterval; + this.anim_timer = 0; + + this.current_animation = 1; + this.anim_state = { side: -1 } + + this.head_shift = 1; + + this.dyingTint = Phaser.Display.Color.IntegerToColor(0x003300); + + this.addSprite("body", M.Vec2.ZERO, "gob_small", 0); + this.addSprite("shield", M.Vec2.ZERO, "gob_small", 2); + this.addSprite("head", M.Vec2.ZERO, "gob_small", 4); + + this.shieldBreaking = false; + this.shielded = withShield; + if (!this.shielded) { + this.enemyName = "Goblin"; + this.sprites.shield.visible = false; + } else { + this.enemyName = "ShieldGoblin"; + } + + this.refreshHint(); + this.hideHint(); + } + + refreshHint() { + if (this.shielded) { + this.setupHint(20, [["less", "4", "", "arrow", "", "guard_break"]]); + } else { + this.setupHint(20, [["greater", "2", "", "arrow", "", "skull"]]); + } + } + + preUpdate (t, dt) { + + this.anim_timer += dt; + this.animProcess(dt); + if (this.anim_timer > this.anim_interval) { + this.anim_timer = 0; + this.animUpdate(); + } + } + + changeAnim(animNumber) { + this.current_animation = animNumber; + this.resetAnim(); + } + + resetAnim() { + this.sprites.head.y = 0; + this.anim_interval = this.baseAnimInterval; + switch (this.current_animation) { + case 1: + this.sprites.head.setFrame(4); + this.sprites.body.setFrame(0); + this.sendToBack(this.sprites.body); + break; + case 2: + this.sprites.head.setFrame(5); + this.sprites.head.scaleX = 1; + this.sprites.head.x = 0; + this.sprites.body.setFrame(1); + this.bringToTop(this.sprites.body); + this.anim_state.death_timer = 0; + this.anim_state.death_total_time = 400; + break; + case 3: // laugh + this.sprites.head.setFrame(4); + this.sprites.head.x = 0; + this.anim_interval = 60; + this.anim_timer = 30; + this.anim_state.anim_timer = 0; + this.anim_state.anim_total_time = 600; + break; + case 4: // shield break + this.sprites.head.setFrame(5); + this.bringToTop(this.sprites.body); + this.anim_state.anim_timer = 0; + this.anim_state.anim_total_time = 280; + break; + } + } + + animProcess (dt) { + switch (this.current_animation) { + case 2: + this.anim_state.death_timer += dt; + const progress = this.anim_state.death_timer / this.anim_state.death_total_time; + const tintProgress = Math.min(1, progress * 2.2); + + let tintColor = this.dyingTint; + if (tintProgress < 1) { + const colorWhite = Phaser.Display.Color.IntegerToColor(0xffffff); + tintColor = Phaser.Display.Color.Interpolate.ColorWithColor( + colorWhite, this.dyingTint, 1, tintProgress + ); + } + this.tintSprites(tintColor.color); + + const scaleProgress = Math.max(0, (M.Ease.Cubic.In(progress) * 1.5) - 0.5); + for (let spName of this.spriteNames) { + this.sprites[spName].scaleY = 1 - scaleProgress; + } + + if (progress >= 1) { + this.finishDie(); + } + break; + case 3: + this.anim_state.anim_timer += dt; + if (this.anim_state.anim_timer > this.anim_state.anim_total_time) { + this.changeAnim(1); + } + break; + case 4: + this.anim_state.anim_timer += dt; + if (this.anim_state.anim_timer > this.anim_state.anim_total_time) { + this.changeAnim(1); + } + break; + } + } + + animUpdate () { + const head = this.sprites.head; + switch (this.current_animation) { + case 1: + this.anim_state.side *= -1; + head.x = this.head_shift * this.anim_state.side; + if (Math.random() < 0.05) { + head.scaleX *= -1; + if (Math.random() < 0.7) { + const pitchScale = Math.random() * 0.3 + 0.9 + this.scene.sound.play('gob2_sfx', {pitch: pitchScale}); + } + } + break; + case 3: + if (head.y === 0) { + head.y = 1; + } else { + head.y = 0; + } + break; + } + } + + attack(diceValue) { + if (this.shielded) { + if (diceValue <= 3 || diceValue > 6) { + this.scene.sound.play('crack_sfx'); + this.breakShield(); + + if (Math.random() < 0.2) { + this.scene.sound.play('gob_grunt_sfx'); + } + } else { + const laughNum = Math.floor(Math.random() * 2 + 1); + this.scene.sound.play('laugh' + laughNum + '_sfx'); + this.changeAnim(3); // laugh + } + } else { + if (diceValue <= 2) { + const laughNum = Math.floor(Math.random() * 2 + 1); + this.scene.sound.play('laugh' + laughNum + '_sfx'); + this.changeAnim(3); // laugh + } else { + this.scene.sound.play('punch_sfx'); + if (Math.random() < 0.75) { + this.scene.sound.play('gob_grunt_sfx'); + } else { + this.scene.sound.play('gob1_sfx'); + } + this.startDie(); + } + } + } + + preattack(diceValue) { + if (this.shielded && !this.shieldBreaking) { + if (diceValue <= 3 || diceValue > 6) { + this.shieldBreaking = true; + } + } else { + if (diceValue > 2) { + this.alive = false; + } + } + } + + breakShield() { + this.enemyName = "Goblin"; + this.shielded = false; + this.sprites.shield.visible = false; + this.refreshHint(); + this.changeAnim(4); + } + + startDie() { + this.alive = false; + this.gone = true; + this.deathAnimating = true; + this.changeAnim(2); + } + + finishDie() { + this.deathAnimating = false; + this.removeFromDisplayList(); + this.removeFromUpdateList(); + } +} + +M.BombGnome = class extends M.Enemy +{ + constructor(scene) { + super(scene); + this.enemyName = "BombGnome"; + + this.anim_interval = 320; + this.anim_timer = 0; + this.total_time = 0; + + this.current_animation = 1; + this.anim_state = {}; + + this.arm_speed = 200 + Math.random() * 20; + this.arm_angle_delta = (Math.PI / 6); + this.snap = Math.PI / 64; + + this.addSprite("body", M.Vec2.ZERO, "bomb_gnome", 0); + this.addSprite("arm", M.Vec2.ZERO, "bomb_gnome", 2); + this.addSprite("bomb", M.Vec2.ZERO, "bomb_gnome", 4); + this.addSprite("head", M.Vec2.ZERO, "bomb_gnome", 6); + + this.dyingTint = Phaser.Display.Color.IntegerToColor(0x330000); + + this.setupHint(26, [ + ["greater", "1", "", "arrow", "", "skull", ""], + ["equal", "6", "", "arrow", "", "skull", "boom"], + ]); + this.hideHint(); + } + + preUpdate (t, dt) { + this.total_time += dt; + this.animProcess(dt); + + this.anim_timer += dt; + if (this.anim_timer > this.anim_interval) { + this.anim_timer = 0; + this.animInterval(); + } + } + + animProcess (dt) { + let progress + let tintProgress + switch (this.current_animation) { + case 1: + const arm = this.sprites.arm + const smoothAngle = this.arm_angle_delta * ( + Math.cos(this.total_time / this.arm_speed) / 2 - 0.5 + ); + arm.rotation = Math.round(smoothAngle / this.snap) * this.snap; + break; + case 2: + this.anim_state.death_timer += dt; + progress = this.anim_state.death_timer / this.anim_state.death_total_time; + + tintProgress = Math.min(1, progress * 2.2); + this.setDeathTint(tintProgress); + + this.setDeathScale(progress, 0.5); + + if (progress >= 1) { + this.finishDie(); + } + break; + case 3: + this.anim_state.death_timer += dt; + progress = this.anim_state.death_timer / this.anim_state.death_total_time; + + tintProgress = Math.min(1, progress * 4.2); + this.setDeathTint(tintProgress); + + this.setDeathScale(progress, 1.2); + + const boomThreshold = 0.45; + if (this.spriteNames.includes("boom")) { + if (progress < boomThreshold) { + const boomScaleProg = M.Ease.Back.Out(Math.min(1, progress * 3.0)); + this.sprites.boom.scale = 0.75 + boomScaleProg * 0.25 + } else { + this.removeSprite("boom"); + } + } + + if (progress >= 1) { + this.finishDie(); + } + break; + } + } + + setDeathTint(tintProgress) { + let tintColor = this.dyingTint; + if (tintProgress < 1) { + const colorWhite = Phaser.Display.Color.IntegerToColor(0xffffff); + tintColor = Phaser.Display.Color.Interpolate.ColorWithColor( + colorWhite, this.dyingTint, 1, tintProgress + ); + } + this.tintSprites(tintColor.color); + if (this.spriteNames.includes("boom")) { + this.sprites.boom.clearTint(); + } + } + + setDeathScale(deathProgress, shrinkOffset) { + const clampedAdjusted = Math.max(0, Math.min(1, (deathProgress * (1 + shrinkOffset)) - 0.5)) + const scaleProgress = M.Ease.Cubic.In(clampedAdjusted); + for (let spName of this.spriteNames) { + this.sprites[spName].scaleY = 1 - scaleProgress; + } + } + + animInterval () { + switch (this.current_animation) { + case 1: + const head = this.sprites.head; + head.y = head.y > 0 ? 0 : 1; + break; + } + } + + changeAnim(animNumber) { + this.current_animation = animNumber; + this.resetAnim(); + } + + resetAnim() { + this.sprites.arm.rotation = 0; + switch (this.current_animation) { + case 1: + this.sprites.body.setFrame(0); + this.sprites.arm.setFrame(2); + this.sprites.head.setFrame(6); + break; + case 2: + this.sprites.body.setFrame(1); + this.sprites.arm.setFrame(3); + this.sprites.head.setFrame(7); + + this.anim_state.death_timer = 0; + this.anim_state.death_total_time = 600; + break; + case 3: + this.addSprite("boom", M.Vec2.ZERO, "explosion"); + this.sprites.boom.scale = 0.75; + + this.sprites.body.setFrame(1); + this.sprites.arm.setFrame(3); + this.sprites.bomb.visible = false; + this.sprites.head.setFrame(7); + + this.anim_state.death_timer = 0; + this.anim_state.death_total_time = 900; + break; + } + } + + attack(diceValue) { + if (diceValue <= 1) { + this.scene.sound.play('hoo_sfx'); + } else if (diceValue >= 6) { + this.startDie(true); + } else { + this.scene.sound.play('punch_sfx'); + this.startDie(false); + } + } + + preattack(diceValue) { + if (diceValue > 1) { + this.alive = false; + if (diceValue >= 6) { + this.scene.preExplode(this); + } + } + } + + startDie(exploding) { + this.alive = false; + this.gone = true; + this.deathAnimating = true; + this.changeAnim(exploding ? 3 : 2); + if (exploding) { + setTimeout(() => this.scene.enemyExploded(this), 150); + //this.scene.enemyExploded(this); + } else { + const gruntNum = Math.floor(Math.random() * 3 + 1); + this.scene.sound.play('gnome_grunt' + gruntNum + '_sfx'); + } + } + + finishDie() { + this.deathAnimating = false; + this.removeFromDisplayList(); + this.removeFromUpdateList(); + } +} |
