User prompt
The number of throwables should not suddenly decrease from 5 to 0.
User prompt
Every time you press the throw button, the throwables decrease by 1
User prompt
You only have 5 throwables and it says on the top left how many throwables you have left
User prompt
Please fix the bug: 'Cannot read properties of undefined (reading 'x')' in or related to this line: 'dodgeCooldownOverlay.x = btnDodge.x;' Line Number: 502
User prompt
make a timer and make it visible like the spc attack cooldown for the dodge
User prompt
make a 3 second cooldown for the dodge
User prompt
move the throw button higher
User prompt
revert the throw button to its old position
User prompt
a little more
User prompt
move the dodge button higher
User prompt
move the dodge button above the throw button do not swap them
User prompt
move the dodge key above the throw key
User prompt
move the dodge button on top of the thrtow button
User prompt
do not have the same attack effect for the projectile with regulkar attack
User prompt
throw a projectile when throw button pressed
User prompt
add a projectile class
User prompt
Handle projectile collision and update in game loop
User prompt
increase it a little bit
User prompt
reduce dodge distance
User prompt
add a dodge mechanic
User prompt
the combo text will appear only when 1.5 second timer is actve
User prompt
When a warrior takes damage, their health bar momentarily turns red.
User prompt
not that slow, a little bit faster than this
User prompt
make the characters slower
User prompt
The text will shake and shake every time the warriors' combo count increases
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// --- Arena class ---
// Provides a background and groundY property for fighter placement
var Arena = Container.expand(function () {
var self = Container.call(this);
// Add arena background image, anchored at top-left
var bg = self.attachAsset('arena_bg', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
// Set groundY to a reasonable value for fighter placement
// Place ground near the bottom of the visible area, but above the very bottom
self.groundY = 2048 + 300; // 2048 is the base height, +300 for a bit above the bottom
return self;
});
// --- Attack class ---
// Represents a regular or special attack hitbox, with owner and damage
var Attack = Container.expand(function () {
var self = Container.call(this);
// Arguments: owner (Fighter), isSpecial (bool)
self.owner = arguments[0] || null;
self.isSpecial = !!arguments[1];
self.hit = false;
self.destroyed = false;
// Damage values
self.damage = self.isSpecial ? 24 : 8;
// Visual
var assetId = self.isSpecial ? 'special_attack' : 'attack';
var sprite = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 0
});
// Set size for hitbox (use asset size)
self.width = sprite.width;
self.height = sprite.height;
// Set owner after creation if needed
self.setOwner = function (fighter) {
self.owner = fighter;
};
// Update: attacks last for a short time, then destroy
self.lifetime = self.isSpecial ? 30 : 18; // special lasts longer
self.update = function () {
if (self.destroyed) return;
self.lifetime--;
if (self.lifetime <= 0) {
self.destroy();
}
};
self.destroy = function () {
if (self.destroyed) return;
self.destroyed = true;
if (self.parent && self.parent.removeChild) {
self.parent.removeChild(self);
}
};
return self;
});
// --- Fighter class ---
// Handles player and AI fighter logic, health, attacks, movement, and health bar
var Fighter = Container.expand(function () {
var self = Container.call(this);
// --- Properties ---
self.maxHealth = 100;
self.health = self.maxHealth;
self.moveSpeed = 13;
self.jumpStrength = 60;
self.gravity = 6;
self.isJumping = false;
self.isAttacking = false;
self.isSpecialReady = true;
self.specialCooldown = 360; // 6 seconds at 60fps
self.specialTimer = 0;
self.immobile = false;
self.isFacingRight = true;
self.attackCooldown = 72; // 1.2 seconds at 60fps
self.attackTimer = 0;
self.lastJumpPressed = false;
// --- Dodge mechanic ---
self.isDodging = false;
self.dodgeDuration = 18; // 0.3s at 60fps
self.dodgeTimer = 0;
self.dodgeSpeed = 30; // Slightly increased dodge distance
self.dodgeInvuln = false;
self.lastDodgePressed = false;
self.dodgeCooldown = 60; // 1s cooldown
self.dodgeCooldownTimer = 0;
// Start dodge if not already dodging or on cooldown
self.dodge = function () {
if (self.isDodging || self.immobile || self.dodgeCooldownTimer > 0 || self.isJumping) return;
self.isDodging = true;
self.dodgeTimer = self.dodgeDuration;
self.dodgeInvuln = true;
self.dodgeCooldownTimer = self.dodgeCooldown;
// Visual feedback: flash fighter blue for dodge duration
LK.effects.flashObject(self, 0x00aaff, self.dodgeDuration * 16);
};
// --- Visuals ---
var sprite = self.attachAsset('fighter', {
anchorX: 0.5,
anchorY: 1.0,
x: 0,
y: 0
});
// Health bar container
self.healthBar = new Container();
// Health bar outline (drawn behind the background)
var healthBarOutline = new Container();
var outlineThickness = 6;
var outlineW = 300 + outlineThickness * 2;
var outlineH = 50 + outlineThickness * 2;
var outlineRect = LK.getAsset('healthbar_bg', {
anchorX: 0,
anchorY: 0.5,
x: -outlineThickness,
y: 0,
width: outlineW,
height: outlineH,
color: 0xffffff // white outline
});
healthBarOutline.addChild(outlineRect);
self.healthBar.addChild(healthBarOutline);
// Health bar background
var healthBarBg = LK.getAsset('healthbar_bg', {
anchorX: 0,
anchorY: 0.5,
x: 0,
y: 0
});
self.healthBar.addChild(healthBarBg);
// Health bar foreground
var healthBarFg = LK.getAsset('healthbar_fg', {
anchorX: 0,
anchorY: 0.5,
x: 0,
y: 0
});
self.healthBar.addChild(healthBarFg);
self.healthBar.width = 300;
self.healthBar.height = 50;
self.healthBarFg = healthBarFg;
self.updateHealthBar = function () {
var pct = Math.max(0, self.health / self.maxHealth);
self.healthBarFg.width = 300 * pct;
};
self.updateHealthBar();
// --- Methods ---
self.setFacing = function (right) {
self.isFacingRight = right;
sprite.scaleX = right ? 1 : -1;
};
self.jump = function () {
if (!self.isJumping && !self.immobile) {
self.isJumping = true;
self.vy = -self.jumpStrength;
}
};
self.attack = function () {
if (self.isAttacking || self.immobile || self.attackTimer > 0) return;
self.isAttacking = true;
self.attackTimer = self.attackCooldown;
// Create attack object
var atk = new Attack(self, false);
atk.x = self.x + (self.isFacingRight ? 120 : -120);
atk.y = self.y - 200;
attacks.push(atk);
game.addChild(atk);
// End attack after short delay
LK.setTimeout(function () {
self.isAttacking = false;
}, 300);
};
self.special = function () {
if (!self.isSpecialReady || self.immobile) return;
self.isSpecialReady = false;
self.specialTimer = self.specialCooldown;
// Create special attack object
var atk = new Attack(self, true);
atk.x = self.x + (self.isFacingRight ? 180 : -180);
atk.y = self.y - 200;
attacks.push(atk);
game.addChild(atk);
};
self.takeDamage = function (amount, dir, knockback) {
if (self.immobile) return;
self.health -= amount;
self.updateHealthBar();
// Flash health bar foreground red for 200ms when taking damage
LK.effects.flashObject(self.healthBarFg, 0xff0000, 200);
// Knockback
self.x += (dir || 1) * (knockback || 32);
// Clamp to arena
self.x = Math.max(120, Math.min(2048 - 120, self.x));
};
self.update = function () {
// Dodge cooldown
if (self.dodgeCooldownTimer > 0) {
self.dodgeCooldownTimer--;
}
// Dodge logic
if (self.isDodging) {
// Move quickly in facing direction
var dir = self.isFacingRight ? 1 : -1;
self.x += dir * self.dodgeSpeed;
// Clamp to arena
self.x = Math.max(120, Math.min(2048 - 120, self.x));
self.dodgeTimer--;
if (self.dodgeTimer <= 0) {
self.isDodging = false;
self.dodgeInvuln = false;
}
}
// Attack cooldown
if (self.attackTimer > 0) {
self.attackTimer--;
}
// Special cooldown
if (!self.isSpecialReady) {
if (self.specialTimer > 0) {
self.specialTimer--;
}
if (self.specialTimer <= 0) {
self.isSpecialReady = true;
}
}
// Jumping physics
if (self.isJumping) {
if (typeof self.vy === "undefined") self.vy = 0;
self.y += self.vy;
self.vy += self.gravity;
// Land on ground
if (self.y >= arena.groundY) {
self.y = arena.groundY;
self.isJumping = false;
self.vy = 0;
}
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// --- Arena setup ---
//Note game dimensions are 2048x2732
//Minimalistic tween library which should be used for animations over time, including tinting / colouring an object, scaling, rotating, or changing any game object property.
var arena = new Arena();
arena.x = 0;
arena.y = 0;
game.addChild(arena);
// --- Fixed ground platform (visual) ---
var groundHeight = 60;
var groundTileWidth = LK.getAsset('ground', {
anchorX: 0,
anchorY: 0
}).width;
var groundY = arena.groundY - 100; // Move ground even further down by reducing the offset (was -300)
var groundTiles = [];
var numGroundTiles = Math.ceil(2048 / groundTileWidth); // Only enough to fill the screen
for (var i = 0; i < numGroundTiles; i++) {
var groundTile = LK.getAsset('ground', {
anchorX: 0,
anchorY: 0,
x: i * groundTileWidth,
y: groundY
});
game.addChild(groundTile);
groundTiles.push(groundTile);
}
// No infinite scrolling, ground is fixed and does not move
// --- Fighters ---
var fighter1 = new Fighter();
fighter1.lastJumpPressed = false;
fighter1.lastHitTime = -9999; // Track last time this fighter hit the other
fighter1.comboCount = 0; // Current combo count
var fighter2 = new Fighter();
fighter2.lastHitTime = -9999;
fighter2.comboCount = 0;
// Place fighters on opposite sides
fighter1.x = 600;
fighter1.y = arena.groundY;
fighter1.setFacing(true);
fighter2.x = 2048 - 600;
fighter2.y = arena.groundY;
fighter2.setFacing(false);
game.addChild(fighter1);
game.addChild(fighter2);
// --- Health bar GUI ---
// Removed number healthbars (Text2 healthBar1 and healthBar2)
// Move the box healthbars (fighter healthBar containers) to a more visible, symmetric place below the timer at the top center
// Remove from fighter containers and add to GUI, position symmetrically below timer
fighter1.removeChild(fighter1.healthBar);
fighter2.removeChild(fighter2.healthBar);
// Place both healthbars below the timer, offset horizontally from center
fighter1.healthBar.x = -550;
fighter1.healthBar.y = 170;
fighter2.healthBar.x = 250;
fighter2.healthBar.y = 170;
LK.gui.top.addChild(fighter1.healthBar);
LK.gui.top.addChild(fighter2.healthBar);
// --- Combo counters under health bars ---
var comboText1 = new Text2('Combo: 0', {
size: 48,
fill: 0xFF0000,
stroke: 0xffffff,
strokeThickness: 8
});
comboText1.anchor.set(0.5, 0);
comboText1.x = fighter1.healthBar.x + 150; // center under health bar (300px wide)
comboText1.y = fighter1.healthBar.y + 60; // just below health bar
var comboText2 = new Text2('Combo: 0', {
size: 48,
fill: 0xFF0000,
stroke: 0xffffff,
strokeThickness: 8
});
comboText2.anchor.set(0.5, 0);
comboText2.x = fighter2.healthBar.x + 150;
comboText2.y = fighter2.healthBar.y + 60;
LK.gui.top.addChild(comboText1);
LK.gui.top.addChild(comboText2);
// Combo timer variables (in frames, 1.5s = 90 frames)
var comboTimer1 = 0;
var comboTimer2 = 0;
// --- Round and timer ---
var round = 1;
var maxRounds = 3;
var wins1 = 0;
var wins2 = 0;
var roundTime = 60 * 30; // 30 seconds at 60fps
var roundTimer = roundTime;
var timerText = new Text2('30', {
size: 80,
fill: "#fff"
});
timerText.anchor.set(0.5, 0);
LK.gui.top.addChild(timerText);
// --- Touch controls (mobile-friendly) ---
var leftPressed = false,
rightPressed = false,
atkPressed = false,
jumpPressed = false,
spcPressed = false,
throwPressed = false,
dodgePressed = false; // New for dodge
// Special attack cooldown overlay for button
var spcCooldownOverlay = LK.getAsset('healthbar_bg', {
anchorX: 0.5,
anchorY: 0.5,
width: 180,
height: 180,
color: 0x000000
});
spcCooldownOverlay.alpha = 0.5;
spcCooldownOverlay.visible = false;
spcCooldownOverlay.x = 2048 - 350;
spcCooldownOverlay.y = 2450;
game.addChild(spcCooldownOverlay);
var spcCooldownText = new Text2('3', {
size: 90,
fill: "#fff"
});
spcCooldownText.anchor.set(0.5, 0.5);
spcCooldownText.x = 2048 - 350;
spcCooldownText.y = 2450;
spcCooldownText.visible = false;
game.addChild(spcCooldownText);
// Special attack cooldown counter above the special attack button
var spcCooldownCounter = new Text2('', {
size: 60,
fill: "#fff"
});
spcCooldownCounter.anchor.set(0.5, 1.0);
// Place above the special button (btnSpc)
spcCooldownCounter.x = 2048 - 350;
spcCooldownCounter.y = 2450 - 120;
spcCooldownCounter.visible = false;
game.addChild(spcCooldownCounter);
// Control buttons (simple rectangles for now)
var btnLeft = LK.getAsset('btn_left', {
anchorX: 0.5,
anchorY: 0.5,
x: 200,
y: 2600,
scaleX: 1.8,
scaleY: 1.8
});
var btnRight = LK.getAsset('btn_right', {
anchorX: 0.5,
anchorY: 0.5,
x: 500,
y: 2600,
scaleX: 1.8,
scaleY: 1.8
});
var btnAtk = LK.getAsset('btn_atk', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 - 500,
y: 2600,
scaleX: 1.8,
scaleY: 1.8
});
var btnJump = LK.getAsset('btn_jump', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 - 200,
y: 2600,
scaleX: 1.8,
scaleY: 1.8
});
var btnSpc = LK.getAsset('btn_spc', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 - 350,
y: 2450,
scaleX: 1.8,
scaleY: 1.8
});
// Add throw button (use unique btn_throw asset, place visually above the jump button)
var btnThrow = LK.getAsset('btn_throw', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 - 200,
// Move throw button slightly further up above the jump button (offset y by -220px)
y: 2600 - 220,
scaleX: 1.8,
scaleY: 1.8
});
// Add dodge button (reuse btn_right asset, place between left and right buttons, lower opacity)
var btnDodge = LK.getAsset('btn_right', {
anchorX: 0.5,
anchorY: 0.5,
x: 350,
y: 2600,
scaleX: 1.8,
scaleY: 1.8
});
btnDodge.alpha = 0.7;
game.addChild(btnLeft);
game.addChild(btnRight);
game.addChild(btnAtk);
game.addChild(btnJump);
game.addChild(btnSpc);
game.addChild(btnThrow);
game.addChild(btnDodge);
// Button event helpers
btnLeft.down = function () {
leftPressed = true;
};
btnLeft.up = function () {
leftPressed = false;
};
btnRight.down = function () {
rightPressed = true;
};
btnRight.up = function () {
rightPressed = false;
};
btnAtk.down = function () {
atkPressed = true;
};
btnAtk.up = function () {
atkPressed = false;
};
btnJump.down = function () {
jumpPressed = true;
};
btnJump.up = function () {
jumpPressed = false;
};
btnSpc.down = function () {
spcPressed = true;
};
btnSpc.up = function () {
spcPressed = false;
};
btnThrow.down = function () {
throwPressed = true;
};
btnThrow.up = function () {
throwPressed = false;
};
btnDodge.down = function () {
dodgePressed = true;
};
btnDodge.up = function () {
dodgePressed = false;
};
// --- Attacks array ---
var attacks = [];
// --- Game update loop ---
game.update = function () {
// --- Timer ---
if (roundTimer > 0) {
roundTimer--;
timerText.setText(Math.ceil(roundTimer / 60));
}
// --- Ground is fixed, no update needed ---
// --- Player 1 controls (left side) ---
if (!fighter1.immobile) {
if (leftPressed) {
fighter1.x = Math.max(120, fighter1.x - fighter1.moveSpeed);
fighter1.setFacing(false);
}
if (rightPressed) {
fighter1.x = Math.min(2048 - 120, fighter1.x + fighter1.moveSpeed);
fighter1.setFacing(true);
}
// Only trigger jump on the frame the button is pressed (rising edge)
if (jumpPressed && !fighter1.lastJumpPressed) {
fighter1.jump();
}
// Only trigger dodge on the frame the button is pressed (rising edge)
if (dodgePressed && !fighter1.lastDodgePressed) {
fighter1.dodge();
}
if (atkPressed) {
fighter1.attack();
}
if (throwPressed && typeof fighter1.throwObject === "function") {
fighter1.throwObject();
}
if (spcPressed) {
if (fighter1.isSpecialReady) {
fighter1.special();
}
}
}
fighter1.lastJumpPressed = jumpPressed;
fighter1.lastDodgePressed = dodgePressed;
// --- Special attack cooldown overlay update ---
if (!fighter1.isSpecialReady) {
spcCooldownOverlay.visible = true;
spcCooldownText.visible = true;
var secondsLeft = Math.ceil(fighter1.specialTimer / 60);
spcCooldownText.setText(secondsLeft);
// Show and update the counter above the special button
spcCooldownCounter.visible = true;
spcCooldownCounter.setText(secondsLeft + "s");
} else {
spcCooldownOverlay.visible = false;
spcCooldownText.visible = false;
spcCooldownCounter.visible = false;
}
// --- AI for fighter2 (simple: move toward player, attack if close) ---
if (!fighter2.immobile) {
var dx = fighter1.x - fighter2.x;
if (Math.abs(dx) > 180) {
if (dx < 0) {
fighter2.x -= fighter2.moveSpeed * 0.7;
fighter2.setFacing(false);
} else {
fighter2.x += fighter2.moveSpeed * 0.7;
fighter2.setFacing(true);
}
} else if (!fighter2.isAttacking && Math.random() < 0.04) {
fighter2.attack();
}
if (!fighter2.isJumping && Math.random() < 0.01) {
fighter2.jump();
}
if (fighter2.isSpecialReady && Math.random() < 0.008) {
fighter2.special();
}
}
// --- Update fighters ---
fighter1.update();
fighter2.update();
// --- Update attacks ---
for (var i = attacks.length - 1; i >= 0; i--) {
var atk = attacks[i];
// Store last position for possible future use (e.g. for projectiles)
if (typeof atk.lastX === "undefined") atk.lastX = atk.x;
if (typeof atk.lastY === "undefined") atk.lastY = atk.y;
atk.update();
// Remove if destroyed
if (atk.destroyed) {
attacks.splice(i, 1);
continue;
}
// Set owner if not set
if (!atk.owner) {
atk.setOwner(atk.isSpecial ? atk.x < 2048 / 2 ? fighter1 : fighter2 : fighter1);
}
// Handle collision with the other fighter
var target = atk.owner === fighter1 ? fighter2 : fighter1;
// If target is dodging and invulnerable, skip hit
if (target.dodgeInvuln) {
atk.lastX = atk.x;
atk.lastY = atk.y;
continue;
}
// Only trigger on the exact frame of collision (rising edge)
var wasIntersecting = atk.lastWasIntersecting || false;
var isIntersecting = atk.intersects(target);
if (!wasIntersecting && isIntersecting && !atk.hit) {
// Combo logic: check if this hit is within 1.5 seconds (90 frames) of the last hit by this attacker
var nowTick = LK.ticks || 0;
if (typeof atk.owner.lastHitTime === "undefined") atk.owner.lastHitTime = -9999;
if (typeof atk.owner.comboCount === "undefined") atk.owner.comboCount = 0;
var prevCombo = atk.owner.comboCount;
if (nowTick - atk.owner.lastHitTime <= 90) {
atk.owner.comboCount++;
// Start combo timer for this fighter
if (atk.owner === fighter1 && atk.owner.comboCount > prevCombo) {
comboTimer1 = 90;
}
if (atk.owner === fighter2 && atk.owner.comboCount > prevCombo) {
comboTimer2 = 90;
}
// Impact effect on combo text when combo increases
if (atk.owner === fighter1 && atk.owner.comboCount > prevCombo) {
comboText1.scaleX = comboText1.scaleY = 1.3;
// Impact scale
tween(comboText1, {
scaleX: 1,
scaleY: 1
}, {
duration: 180
});
// Shake effect
var shakeTimes = 10;
var shakeMagnitude = 18;
var shakeDuration = 180;
var shakeStep = Math.floor(shakeDuration / shakeTimes);
var origX = comboText1.x;
var origY = comboText1.y;
for (var s = 0; s < shakeTimes; s++) {
(function (s) {
LK.setTimeout(function () {
// Alternate shake direction
var dx = (s % 2 === 0 ? 1 : -1) * shakeMagnitude * (1 - s / shakeTimes);
var dy = (s % 2 === 0 ? -1 : 1) * shakeMagnitude * 0.5 * (1 - s / shakeTimes);
comboText1.x = origX + dx;
comboText1.y = origY + dy;
// Restore at end
if (s === shakeTimes - 1) {
LK.setTimeout(function () {
comboText1.x = origX;
comboText1.y = origY;
}, shakeStep);
}
}, s * shakeStep);
})(s);
}
} else if (atk.owner === fighter2 && atk.owner.comboCount > prevCombo) {
comboText2.scaleX = comboText2.scaleY = 1.3;
tween(comboText2, {
scaleX: 1,
scaleY: 1
}, {
duration: 180
});
// Shake effect
var shakeTimes2 = 10;
var shakeMagnitude2 = 18;
var shakeDuration2 = 180;
var shakeStep2 = Math.floor(shakeDuration2 / shakeTimes2);
var origX2 = comboText2.x;
var origY2 = comboText2.y;
for (var s2 = 0; s2 < shakeTimes2; s2++) {
(function (s2) {
LK.setTimeout(function () {
var dx2 = (s2 % 2 === 0 ? 1 : -1) * shakeMagnitude2 * (1 - s2 / shakeTimes2);
var dy2 = (s2 % 2 === 0 ? -1 : 1) * shakeMagnitude2 * 0.5 * (1 - s2 / shakeTimes2);
comboText2.x = origX2 + dx2;
comboText2.y = origY2 + dy2;
if (s2 === shakeTimes2 - 1) {
LK.setTimeout(function () {
comboText2.x = origX2;
comboText2.y = origY2;
}, shakeStep2);
}
}, s2 * shakeStep2);
})(s2);
}
}
} else {
atk.owner.comboCount = 1;
// Start combo timer for this fighter
if (atk.owner === fighter1 && atk.owner.comboCount > prevCombo) {
comboTimer1 = 90;
}
if (atk.owner === fighter2 && atk.owner.comboCount > prevCombo) {
comboTimer2 = 90;
}
// Impact effect on combo text when combo increases from 0
if (atk.owner === fighter1 && atk.owner.comboCount > prevCombo) {
comboText1.scaleX = comboText1.scaleY = 1.3;
tween(comboText1, {
scaleX: 1,
scaleY: 1
}, {
duration: 180
});
// Shake effect
var shakeTimes = 10;
var shakeMagnitude = 18;
var shakeDuration = 180;
var shakeStep = Math.floor(shakeDuration / shakeTimes);
var origX = comboText1.x;
var origY = comboText1.y;
for (var s = 0; s < shakeTimes; s++) {
(function (s) {
LK.setTimeout(function () {
var dx = (s % 2 === 0 ? 1 : -1) * shakeMagnitude * (1 - s / shakeTimes);
var dy = (s % 2 === 0 ? -1 : 1) * shakeMagnitude * 0.5 * (1 - s / shakeTimes);
comboText1.x = origX + dx;
comboText1.y = origY + dy;
if (s === shakeTimes - 1) {
LK.setTimeout(function () {
comboText1.x = origX;
comboText1.y = origY;
}, shakeStep);
}
}, s * shakeStep);
})(s);
}
} else if (atk.owner === fighter2 && atk.owner.comboCount > prevCombo) {
comboText2.scaleX = comboText2.scaleY = 1.3;
tween(comboText2, {
scaleX: 1,
scaleY: 1
}, {
duration: 180
});
// Shake effect
var shakeTimes2 = 10;
var shakeMagnitude2 = 18;
var shakeDuration2 = 180;
var shakeStep2 = Math.floor(shakeDuration2 / shakeTimes2);
var origX2 = comboText2.x;
var origY2 = comboText2.y;
for (var s2 = 0; s2 < shakeTimes2; s2++) {
(function (s2) {
LK.setTimeout(function () {
var dx2 = (s2 % 2 === 0 ? 1 : -1) * shakeMagnitude2 * (1 - s2 / shakeTimes2);
var dy2 = (s2 % 2 === 0 ? -1 : 1) * shakeMagnitude2 * 0.5 * (1 - s2 / shakeTimes2);
comboText2.x = origX2 + dx2;
comboText2.y = origY2 + dy2;
if (s2 === shakeTimes2 - 1) {
LK.setTimeout(function () {
comboText2.x = origX2;
comboText2.y = origY2;
}, shakeStep2);
}
}, s2 * shakeStep2);
})(s2);
}
}
}
atk.owner.lastHitTime = nowTick;
// Knockback direction: +1 if attacker is facing right, -1 if left
var knockbackDir = atk.owner && atk.owner.isFacingRight ? 1 : -1;
// Knockback strength: special attacks knock back SIGNIFICANTLY more (much stronger effect)
var knockbackStrength = atk.isSpecial ? 480 : 64;
target.takeDamage(atk.damage, knockbackDir, knockbackStrength);
// If hit by a special attack, make target immobile for 1 second (60 frames)
if (atk.isSpecial) {
target.immobile = true;
LK.setTimeout(function () {
target.immobile = false;
}, 1000);
}
atk.hit = true;
atk.destroy();
// Flash on hit
LK.effects.flashObject(target, 0xff0000, 200);
// Show a visible attack effect at the hit location, rotated to match the attack direction
var hitEffect = LK.getAsset(atk.isSpecial ? 'special_attack' : 'attack', {
anchorX: 0.5,
anchorY: 0.5,
x: target.x,
y: target.y - 200
});
// Rotate the effect to match the direction the attacker is facing
if (atk.owner && atk.owner.isFacingRight === false) {
hitEffect.rotation = Math.PI; // Face left
} else {
hitEffect.rotation = 0; // Face right (default)
}
game.addChild(hitEffect);
// Animate the effect: scale up and fade out, then destroy
hitEffect.scaleX = hitEffect.scaleY = 1.2;
hitEffect.alpha = 1;
tween(hitEffect, {
scaleX: 2.0,
scaleY: 2.0,
alpha: 0
}, {
duration: 350,
onFinish: function onFinish() {
if (hitEffect && hitEffect.destroy) hitEffect.destroy();
}
});
}
// Update last intersection state for next frame
atk.lastWasIntersecting = isIntersecting;
atk.lastX = atk.x;
atk.lastY = atk.y;
}
// --- Health bar updates ---
// Removed number healthbars update
// --- Combo reset logic: reset combo if 1.5 seconds (90 frames) have passed since last hit ---
var nowTick = LK.ticks || 0;
if (nowTick - fighter1.lastHitTime > 90) {
fighter1.comboCount = 0;
}
if (nowTick - fighter2.lastHitTime > 90) {
fighter2.comboCount = 0;
}
// Decrement combo timers
if (comboTimer1 > 0) comboTimer1--;
if (comboTimer2 > 0) comboTimer2--;
// Update combo counters under health bars, only show if timer is active
if (comboTimer1 > 0 && fighter1.comboCount > 0) {
comboText1.visible = true;
comboText1.setText(('COMBO: ' + (fighter1.comboCount || 0) + '!').toUpperCase());
} else {
comboText1.visible = false;
}
if (comboTimer2 > 0 && fighter2.comboCount > 0) {
comboText2.visible = true;
comboText2.setText(('COMBO: ' + (fighter2.comboCount || 0) + '!').toUpperCase());
} else {
comboText2.visible = false;
}
// --- Win/lose/round logic ---
var roundOver = false;
var winner = null;
if (fighter1.health <= 0) {
wins2++;
roundOver = true;
winner = 2;
} else if (fighter2.health <= 0) {
wins1++;
roundOver = true;
winner = 1;
} else if (roundTimer <= 0) {
if (fighter1.health > fighter2.health) {
wins1++;
winner = 1;
} else if (fighter2.health > fighter1.health) {
wins2++;
winner = 2;
} else {
// Draw, no one gets a win
}
roundOver = true;
}
if (roundOver) {
if (wins1 >= 2) {
LK.showYouWin();
return;
} else if (wins2 >= 2) {
LK.showGameOver();
return;
}
// Next round
round++;
roundTimer = roundTime;
fighter1.health = fighter1.maxHealth;
fighter2.health = fighter2.maxHealth;
fighter1.updateHealthBar();
fighter2.updateHealthBar();
fighter1.x = 600;
fighter2.x = 2048 - 600;
fighter1.setFacing(true);
fighter2.setFacing(false);
// Remove all attacks
for (var j = attacks.length - 1; j >= 0; j--) {
if (attacks[j].destroy) attacks[j].destroy();
}
attacks = [];
}
}; ===================================================================
--- original.js
+++ change.js
@@ -258,11 +258,11 @@
/****
* Game Code
****/
-//Minimalistic tween library which should be used for animations over time, including tinting / colouring an object, scaling, rotating, or changing any game object property.
-//Note game dimensions are 2048x2732
// --- Arena setup ---
+//Note game dimensions are 2048x2732
+//Minimalistic tween library which should be used for animations over time, including tinting / colouring an object, scaling, rotating, or changing any game object property.
var arena = new Arena();
arena.x = 0;
arena.y = 0;
game.addChild(arena);