/****
* Classes
****/
var ConfigContainer = Container.expand(function (config) {
var self = Container.call(this);
config = config || {};
var destroyCalled = false;
self.tags = {};
self.id = id++;
self.x = config.x || 0;
self.y = config.y || 0;
self.rotation = config.rotation || 0;
self.alpha = config.alpha !== undefined ? config.alpha : 1.0;
if (config.scale !== undefined || config.scaleX !== undefined || config.scaleY !== undefined) {
var scaleX = config.scaleX !== undefined ? config.scaleX : config.scale !== undefined ? config.scale : 1;
var scaleY = config.scaleY !== undefined ? config.scaleY : config.scale !== undefined ? config.scale : 1;
self.scale.set(scaleX, scaleY);
}
self.callDestroy = function () {
if (!destroyCalled) {
destroyCalled = true;
self.onDestroy();
self.destroy();
}
};
self.onDestroy = function () {};
return self;
});
var WeaponFireball = ConfigContainer.expand(function (config) {
var self = ConfigContainer.call(this, config);
var projectileLaunchers = [];
var rotationTicks = 0;
self.cooldown = WEAPON_FIREBALL_COOLDOWN_BASE;
self.update = function () {
updateLaunchers();
self.rotation = ++rotationTicks * WEAPON_FIREBALL_ROTATION_SPEED;
self.cooldown = WEAPON_FIREBALL_COOLDOWN_BASE + WEAPON_FIREBALL_COOLDOWN_SCALING * minorBoonLevels[BOON_REARM];
if (!isPaused && rotationTicks % self.cooldown === 0) {
self.launchProjectiles();
}
};
self.launchProjectiles = function () {
var scale = 1 + WEAPON_FIREBALL_RADIUS_SCALING * minorBoonLevels[BOON_SCALE];
var pierce = WEAPON_FIREBALL_PIERCE_BASE + WEAPON_FIREBALL_PIERCE_SCALING * minorBoonLevels[BOON_RANGE];
var damage = WEAPON_FIREBALL_DAMAGE_BASE + WEAPON_FIREBALL_DAMAGE_SCALING * minorBoonLevels[BOON_DAMAGE];
var duration = WEAPON_FIREBALL_DURATION_BASE + WEAPON_FIREBALL_DURATION_SCALING * minorBoonLevels[BOON_DURATION];
var growthRate = WEAPON_FIREBALL_GROWTH_SCALING * majorBoonLevels[BOON_GROWTH];
var angle = MATH_2_PI / projectileLaunchers.length;
var angleOffset = rotationTicks % (2 * cooldown) === 0 ? 0 : angle / 2;
for (var i = 0; i < projectileLaunchers.length; i++) {
LK.effects.flashObject(projectileLaunchers[i], WEAPON_FIREBALL_FLASH_COLOUR, 1000);
var direction = angleOffset + angle * i - MATH_HALF_PI;
// midgroundContainer.addChild(new FireballProjectile({
// x: hero.x + Math.cos(direction) * WEAPON_FIREBALL_PROJECTILE_OFFSET,
// y: hero.y + Math.sin(direction) * WEAPON_FIREBALL_PROJECTILE_OFFSET,
// growthRate: growthRate,
// direction: direction,
// duration: duration,
// damage: damage,
// pierce: pierce,
// scale: scale
// }));
}
LK.getSound('weaponFireballLaunch').play();
};
function updateLaunchers() {
var count = 2 + majorBoonLevels['Split'];
if (projectileLaunchers.length < count) {
rotationTicks = 0;
var angle = 2 * Math.PI / count;
for (var i = 0; i < count; i++) {
if (i == projectileLaunchers.length) {
projectileLaunchers.push(self.attachAsset('weaponFireball', {
anchorX: 0.5,
anchorY: 2
}));
}
projectileLaunchers[i].rotation = angle * i;
}
}
}
return self;
});
var Weapon = ConfigContainer.expand(function (config) {
var self = ConfigContainer.call(this, config);
self.cooldown = 0;
self.update = function () {
if (!isPaused && --self.cooldown <= 0) {
self.launchProjectiles();
}
};
self.launchProjectiles = function () {};
return self;
});
var WeaponCross = Weapon.expand(function (config) {
var self = Weapon.call(this, config);
self.cooldown = WEAPON_CROSS_COOLDOWN_BASE;
self.launchProjectiles = function () {
if (enemyCount > 0) {
var attackSpeed = 1 + WEAPON_CROSS_COOLDOWN_SCALING * minorBoonLevels[BOON_REARM];
var scale = 1 + WEAPON_CROSS_SCALE_SCALING * minorBoonLevels[BOON_SCALE];
var range = WEAPON_CROSS_RANGE_BASE + WEAPON_CROSS_RANGE_SCALING * minorBoonLevels[BOON_RANGE];
var damage = WEAPON_CROSS_DAMAGE_BASE + WEAPON_CROSS_DAMAGE_SCALING * minorBoonLevels[BOON_DAMAGE];
var linger = WEAPON_CROSS_LINGER_BASE + WEAPON_CROSS_LINGER_SCALING * minorBoonLevels[BOON_DURATION];
var growthRate = WEAPON_CROSS_GROWTH_SCALING * majorBoonLevels[BOON_GROWTH];
var splitCount = 1 + majorBoonLevels[BOON_SPLIT];
var closestEnemy = null;
var minDistanceSqr = Infinity;
for (var i = 0; i < midgroundContainer.children.length; i++) {
var child = midgroundContainer.children[i];
if (child.tags[TAG_ENEMY] && !child.tags[TAG_PROJECTILE]) {
var dx = hero.x - child.x;
var dy = (hero.y - child.y) / GAME_PERSPECTIVE;
var distanceSqr = dx * dx + dy * dy;
if (distanceSqr < minDistanceSqr) {
minDistanceSqr = distanceSqr;
closestEnemy = child;
}
}
}
var dx = closestEnemy.x - hero.x;
var dy = (closestEnemy.y - hero.y) / GAME_PERSPECTIVE;
var direction = Math.atan2(dy, dx) - (splitCount - 1) * WEAPON_CROSS_SPLIT_INCREMENT / 2;
for (var i = 0; i < splitCount; i++) {
var splitDirection = splitCount > 1 ? i * WEAPON_CROSS_SPLIT_INCREMENT : 0;
midgroundContainer.addChild(new ProjectileCross({
x: hero.x,
y: hero.y,
elevation: WEAPON_CROSS_ELEVATION,
direction: direction + splitDirection,
linger: linger + Math.floor(WEAPON_CROSS_LINGER_VARIANCE * Math.random()),
range: range * (1 + WEAPON_CROSS_RANGE_VARIATION * (1 - 2 * Math.random())),
growthRate: growthRate,
damage: damage,
scale: scale
}));
}
self.cooldown = WEAPON_CROSS_COOLDOWN_BASE / attackSpeed;
LK.getSound('weaponCrossLaunch').play();
}
};
return self;
});
var UiCountdownTimer = ConfigContainer.expand(function (config) {
var self = ConfigContainer.call(this, config);
var countdown = config.countdown;
var ticker = 60;
var countdownTxt = self.addChild(new BorderedText('', {
anchorX: .5,
anchorY: 0
}));
self.update = function () {
if (!isPaused && countdown > 0 && --ticker <= 0) {
ticker = 60;
if (--countdown === 0) {
countdownTxt.setFill('#AA0000');
enemySpawner.spawnClass(EnemyBoss);
LK.getSound('clockChime').play();
}
adjustLabel();
}
difficultyScale = 1 - countdown / config.countdown;
};
function adjustLabel() {
var minutes = Math.floor(countdown / 60);
var seconds = Math.abs(countdown) % 60;
var minutesString = (minutes < 10 ? '0' : '') + minutes;
var secondsString = (seconds < 10 ? '0' : '') + seconds;
countdownTxt.setText(minutesString + ':' + secondsString);
}
adjustLabel();
return self;
});
var UiBoonUpgradeButton = ConfigContainer.expand(function (config) {
var self = ConfigContainer.call(this, config);
var hidden = false;
var count = 0;
self.attachAsset('shapeEllipse', {
width: config.size - 5,
height: config.size - 5,
anchorX: 0.5,
anchorY: 0.5,
tint: 0xA0A0A0,
alpha: 0.5
});
var button = self.attachAsset('uiBoonSelection', {
width: config.size,
height: config.size,
anchorX: 0.5,
anchorY: 0.5,
tint: 0x000000
});
var countTxt = self.addChild(new BorderedText('0', {
fill: config.fill,
anchorX: .5,
anchorY: .4
}));
self.visible = false;
button.on('down', config.callback);
self.setHidden = function (newHidden) {
hidden = newHidden;
self.checkHidden();
};
self.setCount = function (newCount) {
count = newCount;
countTxt.setText(newCount);
self.checkHidden();
};
self.checkHidden = function () {
self.visible = !hidden && count > 0;
};
return self;
});
var UiBoonSelection = ConfigContainer.expand(function (config) {
var self = ConfigContainer.call(this, config);
var selectedBoons;
var availableBoons = Object.keys(config.boons).filter(function (boon) {
return config.boons[boon] < BOON_MAX_LEVEL;
});
if (availableBoons.length <= BOON_OPTIONS) {
selectedBoons = availableBoons;
} else {
selectedBoons = [];
while (selectedBoons.length < BOON_OPTIONS && availableBoons.length > 0) {
var boonIndex = Math.floor(Math.random() * availableBoons.length);
selectedBoons.push(availableBoons.splice(boonIndex, 1)[0]);
}
}
if (selectedBoons.length < BOON_OPTIONS) {
selectedBoons.push(config.type === 'Minor' ? BOON_MINOR_HEAL : BOON_FULL_HEAL);
}
self.attachAsset('uiBoonBackground', {
y: 50,
anchorX: 0.5,
anchorY: 0.5,
tint: 0xFFDEC4 // Slightly brown tint
});
self.addChild(new BorderedText('Choose ' + config.count, {
y: -220,
size: 60,
anchorX: .5,
anchorY: 0
}));
self.addChild(new BorderedText(config.type + ' Boon' + (config.count === 1 ? '' : 's'), {
y: -150,
size: 50,
anchorX: .5,
anchorY: 0
}));
for (var i = 0; i < selectedBoons.length; i++) {
var boon = selectedBoons[i];
var boonButton = self.attachAsset('uiBoonUpgrade', {
y: i * 120,
x: -120,
anchorX: 0.5,
anchorY: 0.5,
tint: 0xDEC1AB // Slightly darker brown tint
});
boonButton.boon = boon;
boonButton.on('down', function () {
self.destroy();
config.callback(this.boon);
});
var boonLevel = self.addChild(new BorderedText(config.boons[boon] !== undefined ? config.boons[boon] : '∞', {
x: boonButton.x,
y: boonButton.y,
size: 50,
anchorX: .5,
anchorY: .5
}));
var boonName = self.addChild(new BorderedText(boon, {
x: boonButton.x + 60,
y: boonButton.y,
size: 50,
anchorX: 0,
anchorY: .5
}));
}
return self;
});
var ProjectileBoss = ConfigContainer.expand(function (config) {
var self = ConfigContainer.call(this, config);
self.graphics = self.attachAsset('projectileBoss', {
anchorX: -0.5,
anchorY: 0.5
});
self.update = function () {
self.rotation += ENEMY_BOSS_PROJECTILE_ROTATION;
self.graphics.scale.x = 1 - GAME_PERSPECTIVE + GAME_PERSPECTIVE * Math.abs(Math.cos(self.rotation));
};
return self;
});
var ProgressBar = ConfigContainer.expand(function (config) {
var self = ConfigContainer.call(this, config);
var width = config.width || 120;
var height = config.height || 15;
var weight = config.weight || 6;
self.attachAsset('shapeBox', {
width: width + 2 * weight,
height: height + 2 * weight,
anchorX: 0.5,
anchorY: 0.5,
tint: 0x000000
});
var bar = self.attachAsset('shapeBox', {
x: -width / 2,
width: width,
height: height,
anchorY: 0.5,
tint: config.tint || 0xFFFFFF
});
self.setPercentage = function (percentage) {
bar.scale.x = Math.min(1.0, Math.max(0, percentage));
};
if (config.percentage !== undefined) {
self.setPercentage(config.percentage);
}
return self;
});
var JoystickKnob = ConfigContainer.expand(function (config) {
var self = ConfigContainer.call(this, config);
self.attachAsset('shapeEllipse', {
width: config.size,
height: config.size,
anchorX: 0.5,
anchorY: 0.5,
tint: 0x808080,
alpha: 0.5
});
self.attachAsset('joystickKnob', {
width: config.size * 0.65,
height: config.size * 0.65,
anchorX: 0.5,
anchorY: 0.5,
tint: 0x000000
});
self.attachAsset('outlineSmall', {
width: config.size + 5,
height: config.size + 5,
anchorX: 0.5,
anchorY: 0.5,
tint: 0x000000
});
return self;
});
var Joystick = ConfigContainer.expand(function (config) {
var self = ConfigContainer.call(this, config);
var size = config.size || 100;
var maxDistance = size / 2;
var base = self.attachAsset('shapeEllipse', {
width: size,
height: size,
anchorX: 0.5,
anchorY: 0.5,
tint: 0xA0A0A0,
alpha: 0.5
});
self.attachAsset('outlineLarge', {
width: size + 5,
height: size + 5,
anchorX: 0.5,
anchorY: 0.5,
tint: 0x000000
});
var knob = self.addChild(new JoystickKnob({
size: size / 2
}));
var pressed = false;
self.magnitude = 0;
self.direction = undefined;
self.down = function (x, y, obj) {
movePosition = undefined; // Clear the global move postion
if (x === 0 && y === 0) {
self.magnitude = 0;
self.direction = undefined;
} else {
var distance = Math.sqrt(x * x + y * y);
if (distance <= size / 2) {
pressed = true;
knob.x = x;
knob.y = y;
self.magnitude = distance / maxDistance;
self.direction = Math.atan2(y, x);
}
}
};
self.movement = function (x, y, obj) {
if (pressed) {
if (x === 0 && y === 0) {
self.magnitude = 0;
self.direction = undefined;
} else {
var distance = Math.sqrt(x * x + y * y);
var direction = Math.atan2(y, x);
if (distance > maxDistance) {
x = Math.cos(direction) * maxDistance;
y = Math.sin(direction) * maxDistance;
distance = maxDistance;
}
knob.x = x;
knob.y = y;
self.magnitude = distance / maxDistance;
self.direction = direction;
}
}
};
self.release = function (x, y, obj) {
pressed = false;
self.magnitude = 0;
self.direction = undefined;
knob.x = 0;
knob.y = 0;
};
return self;
});
var GameInstance = ConfigContainer.expand(function (config) {
var self = ConfigContainer.call(this, config);
self.graphics = undefined;
self.radius = config.radius || 0;
self.elevation = config.elevation || 0;
self.shadow = self.attachAsset('shapeEllipse', {
anchorX: 0.5,
anchorY: 0.5,
width: self.radius * 2,
height: self.radius * 2 * GAME_PERSPECTIVE,
tint: 0x000000,
alpha: 0.5
});
self.moveInDirection = function (direction, speed) {
self.x += Math.cos(direction) * speed;
self.y += Math.sin(direction) * speed * GAME_PERSPECTIVE;
};
self.intersectsInstance = function (instance) {
return self.intersectsRadius(instance.x, instance.y, instance.radius);
};
self.intersectsRadius = function (pointX, pointY, radius) {
var dx = pointX - self.x;
var dy = (pointY - self.y) / GAME_PERSPECTIVE;
var distance = self.radius + radius;
return distance * distance >= dx * dx + dy * dy;
};
self.intersectsPoint = function (pointX, pointY) {
var dx = pointX - self.x;
var dy = (pointY - self.y) / GAME_PERSPECTIVE;
return self.radius * self.radius >= dx * dx + dy * dy;
};
self.setRadius = function (newRadius) {
self.radius = newRadius;
self.shadow.width = newRadius * 2;
self.shadow.height = newRadius * 2 * GAME_PERSPECTIVE;
self.onRadiusChanged(newRadius);
};
self.setElevation = function (newElevation) {
self.elevation = newElevation;
if (self.graphics) {
self.graphics.y = -newElevation;
}
self.onElevationChanged(newElevation);
};
self.onRadiusChanged = function (newRadius) {};
self.onElevationChanged = function (newElevation) {};
return self;
});
var ProjectileEnemy = GameInstance.expand(function (config) {
var self = GameInstance.call(this, config);
self.tags[TAG_ENEMY] = true;
self.tags[TAG_PROJECTILE] = true;
self.graphics = self.attachAsset('projectileEnemy', {
anchorX: 0.5,
anchorY: 0.5,
rotation: config.direction - MATH_QUARTER_PI
});
self.update = function () {
if (!isPaused) {
self.moveInDirection(config.direction, ENEMY_RANGED_PROJECTILE_SPEED);
if (LK.ticks % 15 === 0) {
self.graphics.scale.x *= -1;
self.graphics.scale.y *= -1;
self.graphics.rotation += Math.PI;
}
if (self.intersectsInstance(hero)) {
hero.takeDamage(config.damage);
self.callDestroy();
} else if (!checkBounds(self.x, self.y, ENEMY_BORDER_SPAWN)) {
self.callDestroy();
}
}
};
self.setElevation(self.elevation);
return self;
});
var ProjectileCross = GameInstance.expand(function (config) {
var self = GameInstance.call(this, config);
var linger = config.linger;
var range = config.range;
var scale = config.scale;
var hitMap = {};
var growth = 1;
var speed = WEAPON_CROSS_SPEED_BASE;
self.graphics = self.attachAsset('projectileCross', {
y: -config.elevation,
anchorX: 0.5,
anchorY: 0.5,
scaleX: config.scale,
scaleY: config.scale,
rotation: Math.random() * MATH_2_PI
});
self.update = function () {
self.graphics.rotation += WEAPON_CROSS_ROTATION;
if (!isPaused) {
self.moveInDirection(config.direction, speed);
// Grow the projectile
if (config.growthRate > 0) {
growth += config.growthRate;
scale = config.scale * growth;
self.graphics.scale.x = scale;
self.graphics.scale.y = scale;
self.setRadius(WEAPON_CROSS_RADIUS_BASE * scale);
}
if (range <= 0) {
if (speed > -WEAPON_CROSS_SPEED_BASE) {
if (speed <= 0 && linger-- > 0) {
speed = 0;
} else {
speed -= WEAPON_CROSS_SPEED_DECREMENT;
}
} else {
speed = -WEAPON_CROSS_SPEED_BASE;
range -= Math.abs(speed);
}
} else {
range -= Math.abs(speed);
}
// Check for hits and deal damage
var currentTick = LK.ticks;
var damage = config.damage * (1 + (scale - 1) * WEAPON_CROSS_SCALE_DAMAGE_FACTOR);
for (var i = midgroundContainer.children.length - 1; i >= 0; i--) {
var child = midgroundContainer.children[i];
if (child) {
var lastHitTick = hitMap[child.id];
if (child.tags[TAG_ENEMY] && !child.tags[TAG_PROJECTILE] && (!lastHitTick || currentTick - lastHitTick > WEAPON_CROSS_COOLDOWN_HIT) && self.intersectsInstance(child)) {
hitMap[child.id] = currentTick;
child.takeDamage(damage);
LK.getSound('weaponCrossImpact').play();
}
}
}
// Destroy the projectile
if (speed <= 0 && self.intersectsInstance(hero) || range < -config.range && !checkBounds(self.x, self.y, WEAPON_CROSS_BORDER)) {
self.callDestroy();
}
}
};
self.setRadius(WEAPON_CROSS_RADIUS_BASE * config.scale);
return self;
});
var Pickup = GameInstance.expand(function (config) {
var self = GameInstance.call(this, config);
self.tags[TAG_PICKUP] = true;
self.activated = false;
self.update = function () {
if (!isPaused && !self.activated && self.intersectsInstance(hero)) {
self.activate();
}
};
self.activate = function () {
self.activated = true;
self.onActivate();
};
self.onActivate = function () {};
return self;
});
var PickupWeapon = Pickup.expand(function (config) {
var self = Pickup.call(this, config);
var updateBase = self.update;
self.tags[TAG_WEAPON] = true;
self.graphics = self.attachAsset(config.graphics, {
anchorX: 0.5,
anchorY: 0.5,
rotation: -Math.PI / 8
});
self.update = function () {
updateBase();
self.setElevation(WEAPON_HEIGHT_BASE + Math.sin(LK.ticks / WEAPON_HEIGHT_PERIOD) * WEAPON_HEIGHT_MAGNITUDE);
};
self.onActivate = function () {
LK.getSound('pickupWeapon').play();
hero.addChild(new config.weaponClass());
// Destroy all other weapons
for (var i = backgroundContainer.children.length - 1; i >= 0; i--) {
var child = backgroundContainer.children[i];
if (child.tags[TAG_PICKUP] && child.tags[TAG_WEAPON]) {
child.callDestroy();
}
}
self.callDestroy();
};
self.setElevation(WEAPON_HEIGHT_BASE);
self.setRadius(self.graphics.width / 2);
return self;
});
var PickupHealing = Pickup.expand(function (config) {
var self = Pickup.call(this, config);
var updateBase = self.update;
self.tags[TAG_HEALING] = true;
self.graphics = self.attachAsset('pickupHealing', {
anchorX: 0.5,
anchorY: 1.0
});
self.update = function () {
if (hero.health < hero.healthMax) {
updateBase();
}
};
self.onActivate = function () {
hero.healPercentage(PICKUP_HEAL_MINOR);
self.callDestroy();
};
self.onDestroy = function () {
pickupHealthCount--;
};
pickupHealthCount++;
self.setRadius(self.graphics.width / 2);
return self;
});
var PickupExperience = Pickup.expand(function (config) {
var self = Pickup.call(this, config);
self.experience = config.experience;
self.tags[TAG_EXPERIENCE] = true;
// Try combine experience
// var combineCount = 1;
// var combineX = self.x;
// var combineY = self.y;
// for (var i = backgroundContainer.children.length - 1; i >= 0; i--) {
// var child = backgroundContainer.children[i];
// if (child && child.tags[TAG_PICKUP] && child.tags[TAG_EXPERIENCE] && child.intersectsRadius(self.x, self.y, PICKUP_XP_COMBINE_RANGE)) {
// self.experience += child.experience;
// combineX += child.x;
// combineY += child.y;
// child.callDestroy();
// }
// }
// self.x = combineX / combineCount;
// self.y = combineY / combineCount;
// Set the graphics
var size = self.experience >= PICKUP_XP_LARGE ? 'Large' : self.experience >= PICKUP_XP_MEDIUM ? 'Medium' : 'Small';
self.graphics = self.createAsset('pickupExperience' + size, {
anchorX: 0.5,
anchorY: 1.0
});
self.update = function () {
if (!isPaused) {
if (self.activated) {
var direction = Math.atan2(hero.y - self.y, hero.x - self.x);
self.moveInDirection(direction, PICKUP_XP_SPEED);
if (self.intersectsInstance(hero)) {
self.callDestroy();
}
} else if (self.intersectsRadius(hero.x, hero.y, PICKUP_XP_ACTIVATE_RANGE)) {
self.activate();
}
}
};
self.onDestroy = function () {
hero.addExperience(self.experience);
};
self.setRadius(self.graphics.width / 2);
return self;
});
var PickupCrucifix = Pickup.expand(function (config) {
var self = Pickup.call(this, config);
self.tags[TAG_CRUCIFIX] = true;
self.graphics = self.attachAsset('pickupCrucifix', {
anchorX: 0.5,
anchorY: 1.0
});
self.onActivate = function () {
for (var i = midgroundContainer.children.length - 1; i >= 0; i--) {
var child = midgroundContainer.children[i];
if (child && child.tags[TAG_ENEMY] && !child.tags[TAG_BOSS]) {
child.callDestroy();
}
}
for (var i = backgroundContainer.children.length - 1; i >= 0; i--) {
var child = backgroundContainer.children[i];
if (child && child.tags[TAG_PICKUP] && (child.tags[TAG_EXPERIENCE] || child.tags[TAG_HEALING])) {
child.activate();
}
}
LK.getSound('pickupCrucifix').play();
self.callDestroy();
};
self.onDestroy = function () {
pickupCrucifixCount--;
};
pickupCrucifixCount++;
self.setRadius(self.graphics.width / 2);
return self;
});
var Hero = GameInstance.expand(function (config) {
var self = GameInstance.call(this, config);
var level = 1;
var experience = 0;
var levelRequirement = HERO_XP_REQUIRED;
self.speed = HERO_SPEED_BASE;
self.healthMax = HERO_HEALTH_BASE;
self.health = self.healthMax;
self.graphics = self.attachAsset('hero', {
anchorX: 0.5,
anchorY: 1.0
});
var experienceBar = self.addChild(new ProgressBar({
y: -self.graphics.height - 25,
percentage: 0.01
}));
var healthBar = self.addChild(new ProgressBar({
y: -self.graphics.height - 35,
tint: HERO_COLOUR
}));
var levelTxt = self.addChild(new BorderedText('1', {
y: -self.graphics.height - 30,
anchorX: 0.5,
anchorY: 0.5,
size: 65
}));
self.update = function () {
if (!isPaused) {
if (joystick.direction !== undefined) {
self.moveInDirection(joystick.direction, joystick.magnitude * self.speed);
self.graphics.scale.x = Math.cos(joystick.direction) < 0 ? -1 : 1;
} else if (movePosition !== undefined && self.x !== movePosition.x && self.y !== movePosition.y) {
var dy = (movePosition.y - hero.y) / GAME_PERSPECTIVE;
var dx = movePosition.x - hero.x;
var distance = Math.sqrt(dy * dy + dx * dx);
if (distance <= self.speed) {
self.x = movePosition.x;
self.y = movePosition.y;
} else {
var direction = Math.atan2(dy, dx);
self.moveInDirection(direction, self.speed);
}
self.graphics.scale.x = dx < 0 ? -1 : 1;
}
if (self.x < HERO_BORDER) {
self.x = HERO_BORDER;
} else if (self.x > GAME_WIDTH - HERO_BORDER) {
self.x = GAME_WIDTH - HERO_BORDER;
}
if (self.y < HERO_BORDER) {
self.y = HERO_BORDER;
} else if (self.y > GAME_HEIGHT - HERO_BORDER) {
self.y = GAME_HEIGHT - HERO_BORDER;
}
}
};
self.addExperience = function (amount) {
experience += amount;
LK.getSound('pickupExperience').play();
if (experience >= levelRequirement) {
level++;
experience -= levelRequirement;
levelRequirement = Math.floor(levelRequirement * 1.2);
levelTxt.setText(level);
if (level % BOON_MAJOR_LEVEL) {
minorBoonCount++;
} else {
majorBoonCount++;
}
upgradeButton.setCount(minorBoonCount + majorBoonCount);
LK.getSound('heroLeveled').play();
}
experienceBar.setPercentage(experience / levelRequirement);
};
self.takeDamage = function (amount) {
self.health = Math.max(0, self.health - amount);
LK.getSound('heroImpact').play();
healthBar.setPercentage(self.health / self.healthMax);
LK.effects.flashObject(self.graphics, 0xAA0000, 1000);
if (self.health <= 0) {
LK.effects.flashScreen(0xAA0000, 1000);
LK.showGameOver();
LK.getSound('heroDeath').play();
}
};
self.healPercentage = function (percentage) {
if (percentage > 0) {
self.health = Math.min(self.healthMax, self.health + self.healthMax * percentage);
LK.getSound('heroHealed').play();
LK.effects.flashObject(self.graphics, HERO_COLOUR, 1000);
}
healthBar.setPercentage(self.health / self.healthMax);
};
return self;
});
var Enemy = GameInstance.expand(function (config) {
var self = GameInstance.call(this, config);
var damageTaken = 0;
var cooldown = 0;
self.tags[TAG_ENEMY] = true;
self.healthBar;
self.health = 0;
self.healthOffset = 0;
self.speed = 0;
self.fleeRange = 0;
self.attackRange = 0;
self.attackCooldown = 0;
self.animationAlpha = 0;
self.xpDropBase = 0;
self.xpDropChance = 0;
self.xpDropExtraRolls = 0;
self.update = function () {
if (!isPaused) {
var state = ENEMY_STATE_MOVE;
var dx = hero.x - self.x;
var dy = hero.y - self.y;
var distanceSqr = dx * dx + dy * dy;
if (checkBounds(self.x, self.y, ENEMY_BORDER)) {
if (self.fleeRange > 0 && hero.intersectsRadius(self.x, self.y, self.fleeRange)) {
state = ENEMY_STATE_FLEE;
} else if (self.attackRange > 0 && hero.intersectsRadius(self.x, self.y, self.attackRange)) {
state = ENEMY_STATE_ATTACK;
}
}
var stateValues = self.adjustState(state, {
cooldownAdjustment: 0,
speedFactor: 0,
canAttack: false
});
// Perform the movement
if (stateValues.speedFactor !== 0) {
self.moveInDirection(Math.atan2(dy, dx), self.speed * stateValues.speedFactor);
}
// Perform the attack if it is ready
cooldown += stateValues.cooldownAdjustment;
if (stateValues.canAttack && cooldown <= 0) {
cooldown = self.attackCooldown;
self.onAttack();
}
}
self.animationAlpha++;
if (LK.ticks % 15 === 0) {
self.graphics.scale.x *= -1;
}
self.onUpdate();
};
self.adjustState = function (state, stateValues) {
switch (state) {
case ENEMY_STATE_MOVE:
stateValues.speedFactor = 1;
break;
case ENEMY_STATE_FLEE:
stateValues.cooldownAdjustment = self.attackCooldown - cooldown; // Reset cooldown
stateValues.speedFactor = -1;
break;
case ENEMY_STATE_ATTACK:
stateValues.cooldownAdjustment = -1;
stateValues.canAttack = true;
break;
}
return stateValues;
};
self.takeDamage = function (amount) {
damageTaken += amount;
if (damageTaken >= self.health) {
self.callDestroy();
} else {
if (!self.healthBar) {
self.healthBar = self.addChild(new ProgressBar({
y: -self.elevation - self.graphics.height * self.graphics.anchor.x + self.healthOffset,
tint: ENEMY_COLOUR
}));
}
self.healthBar.setPercentage(Math.max(0, 1 - damageTaken / self.health));
LK.effects.flashObject(self.graphics, ENEMY_COLOUR, 1000);
}
};
self.onDestroy = function () {
enemyCount--;
dropExperience();
foregroundContainer.addChild(new EffectEnemyDeath({
x: self.x,
y: self.y - self.elevation - self.graphics.height * self.graphics.anchor.y
}));
LK.getSound('enemyDeath').play();
};
self.onUpdate = function () {};
self.onAttack = function () {};
function dropExperience() {
if (checkBounds(self.x, self.y, HERO_BORDER)) {
var amount = self.xpDropBase;
if (self.xpDropChance > 0) {
for (var i = 0; i <= self.xpDropExtraRolls + minorBoonLevels[BOON_LUCK]; i++) {
if (Math.random() < self.xpDropChance) {
amount++;
}
}
}
if (amount > 0) {
backgroundContainer.addChild(new PickupExperience({
x: self.x + ENEMY_XP_RANGE * (1 - 2 * Math.random()),
y: self.y + ENEMY_XP_RANGE * (1 - 2 * Math.random()),
experience: amount
}));
}
}
}
enemyCount++;
return self;
});
var EnemyRanged = Enemy.expand(function (config) {
var self = Enemy.call(this, config);
var adjustStateBase = self.adjustState;
self.speed = ENEMY_RANGED_SPEED;
self.attackRange = ENEMY_RANGED_RANGE_MIN + ENEMY_RANGED_RANGE_VAR * Math.random();
self.attackCooldown = ENEMY_RANGED_ATTACK_COOLDOWN;
self.fleeRange = self.attackRange * ENEMY_RANGED_FLEE_DISTANCE_FACTOR;
self.health = ENEMY_RANGED_HEALTH_BASE + ENEMY_RANGED_HEALTH_SCALE * difficultyScale;
self.healthOffset = ENEMY_RANGED_HEALTH_OFFSET;
self.xpDropBase = ENEMY_RANGED_XP_DROP_BASE;
self.xpDropChance = ENEMY_RANGED_XP_DROP_CHANCE;
self.xpDropExtraRolls = ENEMY_RANGED_XP_EXTRA_ROLLS;
self.graphics = self.attachAsset('enemyRanged', {
anchorX: 0.5,
anchorY: 0.6
});
self.onUpdate = function () {
// Scale the enemy up and down
var newScale = 1 + Math.sin(self.animationAlpha / ENEMY_RANGED_SCALE_PERIOD) * ENEMY_RANGED_SCALE_MAGNITUDE;
self.graphics.scale.x = newScale;
self.graphics.scale.y = newScale;
self.setRadius(ENEMY_RANGED_RADIUS_BASE * newScale);
};
self.adjustState = function (state, stateValues) {
var newStateValues = adjustStateBase(state, stateValues);
switch (state) {
case ENEMY_STATE_MOVE:
newStateValues.cooldownAdjustment += ENEMY_RANGED_MOVE_COOLDOWN_ADJUSTMENT;
break;
case ENEMY_STATE_FLEE:
newStateValues.speedFactor *= ENEMY_RANGED_FLEE_SPEED_FACTOR;
break;
}
return newStateValues;
};
self.onAttack = function () {
LK.effects.flashObject(self.graphics, 0x000000, 500);
var dx = hero.x - self.x;
var dy = (hero.y - self.y) / GAME_PERSPECTIVE;
midgroundContainer.addChild(new ProjectileEnemy({
x: self.x,
y: self.y,
direction: Math.atan2(dy, dx),
elevation: self.elevation,
radius: ENEMY_RANGED_PROJECTILE_RADIUS,
damage: ENEMY_RANGED_ATTACK_DAMAGE
}));
};
self.setElevation(ENEMY_RANGED_ELEVATION);
self.setRadius(ENEMY_RANGED_RADIUS_BASE);
return self;
});
var EnemyBoss = Enemy.expand(function (config) {
var self = Enemy.call(this, config);
var adjustStateBase = self.adjustState;
var container = self.addChild(new SortingContainer(function (a, b) {
return Math.sin(a.rotation) - Math.sin(b.rotation);
}));
self.tags[TAG_BOSS] = true;
self.speed = ENEMY_BOSS_SPEED;
self.health = ENEMY_BOSS_HEALTH;
self.attackRange = ENEMY_BOSS_ATTACK_RANGE;
self.attackCooldown = ENEMY_BOSS_ATTACK_COOLDOWN;
self.fleeRange = ENEMY_BOSS_FLEE_RANGE;
self.graphics = container.attachAsset('enemyBoss', {
anchorX: 0.5,
anchorY: 1.0
});
for (var i = 0; i < ENEMY_BOSS_PROJECTILE_COUNT; i++) {
var angle = i * 2 * Math.PI / ENEMY_BOSS_PROJECTILE_COUNT;
container.addChild(new ProjectileBoss({
y: ENEMY_BOSS_PROJECTILE_OFFSET,
rotation: angle
}));
}
self.adjustState = function (state, stateValues) {
var newStateValues = adjustStateBase(state, stateValues);
switch (state) {
case ENEMY_STATE_FLEE:
newStateValues.speedFactor = 0;
newStateValues.cooldownAdjustment = -1;
newStateValues.canAttack = true;
break;
case ENEMY_STATE_ATTACK:
newStateValues.speedFactor = 1;
break;
}
return newStateValues;
};
self.onAttack = function () {
hero.takeDamage(ENEMY_BOSS_ATTACK_DAMAGE);
};
self.onDestroy = function () {
LK.effects.flashScreen(0x000000, 1000);
LK.getSound('enemyRoar').play();
enemyCount--;
enemySpawner.setEnabled(false);
upgradeButton.setHidden(true);
for (var i = midgroundContainer.children.length - 1; i >= 0; i--) {
var child = midgroundContainer.children[i];
if (child && child.tags[TAG_ENEMY] && !child.tags[TAG_BOSS]) {
child.callDestroy();
}
}
for (var i = backgroundContainer.children.length - 1; i >= 0; i--) {
var child = backgroundContainer.children[i];
if (child && child.tags[TAG_PICKUP] && (child.tags[TAG_EXPERIENCE] || child.tags[TAG_HEALING])) {
child.activate();
}
}
backgroundContainer.addChild(new PickupWeapon({
x: self.x,
y: self.y,
scale: 1.25,
graphics: 'weaponBoss',
weaponClass: WeaponBoss
}));
LK.gui.top.addChild(new BorderedText('VICTORY', {
y: 200,
size: 200,
fill: '#FFFFFF',
border: '#000000',
anchorX: 0.5,
anchorY: 0.5
}));
};
self.healthOffset = -self.graphics.height + ENEMY_BOSS_HEALTH_OFFSET;
self.setRadius(ENEMY_BOSS_FLEE_RANGE);
self.takeDamage(0);
return self;
});
var EnemyBasic = Enemy.expand(function (config) {
var self = Enemy.call(this, config);
var onDestroyBase = self.onDestroy;
self.speed = ENEMY_BASIC_SPEED;
self.health = ENEMY_BASIC_HEALTH_BASE + ENEMY_BASIC_HEALTH_SCALE * difficultyScale;
self.attackRange = ENEMY_BASIC_ATTACK_RANGE;
self.attackCooldown = ENEMY_BASIC_ATTACK_COOLDOWN;
self.xpDropChance = ENEMY_BASIC_XP_DROP_CHANCE;
self.xpDropExtraRolls = ENEMY_BASIC_XP_EXTRA_ROLLS;
self.graphics = self.attachAsset('enemyBasic', {
anchorX: 0.5,
anchorY: 1.0
});
self.onUpdate = function () {
self.setElevation(ENEMY_BASIC_GRAPHICS_OFFSET + ENEMY_BASIC_BOB_MAGNITUDE * Math.sin(self.animationAlpha / ENEMY_BASIC_BOB_PERIOD));
if (self.healthBar) {
self.healthOffset = -self.elevation - self.graphics.height + ENEMY_BASIC_HEALTH_OFFSET;
self.healthBar.y = self.healthOffset;
}
};
self.onAttack = function () {
hero.takeDamage(ENEMY_BASIC_ATTACK_DAMAGE);
};
self.onDestroy = function () {
onDestroyBase();
if (checkBounds(self.x, self.y, HERO_BORDER)) {
if (pickupCrucifixCount < PICKUP_CRUCIFIX_COUNT && Math.random() < Math.sqrt(minorBoonLevels['Luck']) / 100) {
backgroundContainer.addChild(new PickupCrucifix({
x: self.x,
y: self.y
}));
} else if (pickupHealthCount < PICKUP_HEALTH_COUNT) {
for (var k = 0; k <= 1 + minorBoonLevels['Luck']; k++) {
if (Math.random() < PICKUP_HEALTH_CHANCE) {
backgroundContainer.addChild(new PickupHealing({
x: self.x,
y: self.y
}));
break;
}
}
}
}
};
self.setElevation(ENEMY_BASIC_GRAPHICS_OFFSET);
self.setRadius(45);
return self;
});
var EffectEnemyDeath = ConfigContainer.expand(function (config) {
var self = ConfigContainer.call(this, config);
var initialLifetime = 0.2 * GAME_TICKS;
var remainingLifetime = initialLifetime;
self.attachAsset('effectEnemyDeath', {
anchorX: 0.5,
anchorY: 0.5
});
self.update = function () {
if (--remainingLifetime <= 0) {
self.callDestroy();
}
var lifetime = remainingLifetime / initialLifetime;
var newScale = 2 - lifetime;
self.alpha = lifetime;
self.scale.x = newScale;
self.scale.y = newScale;
};
return self;
});
var BorderedText = ConfigContainer.expand(function (text, config) {
var self = ConfigContainer.call(this, config);
config = config || {};
var anchorX = config.anchorX !== undefined ? config.anchorX : 0;
var anchorY = config.anchorY !== undefined ? config.anchorY : 1;
var size = config.size !== undefined ? config.size : TEXT_DEFAULT_SIZE;
var weight = config.weight !== undefined ? config.weight : TEXT_DEFAULT_WEIGHT;
var font = config.font !== undefined ? config.font : TEXT_DEFAULT_FONT;
var textFill = config.fill !== undefined ? config.fill : TEXT_DEFAULT_FILL;
var borderFill = config.border !== undefined ? config.border : TEXT_DEFAULT_BORDER;
var textAssets = [];
var mainAsset;
self.setText = setText;
self.setFill = setFill;
function setText(newText) {
for (var i = 0; i < textAssets.length; i++) {
textAssets[i].setText(newText);
}
}
function setFill(newFill) {
textFill = newFill;
mainAsset.fill = newFill;
}
function buildTextAssets(newText) {
for (var i = 0; i < TEXT_OFFSETS.length; i++) {
var main = i === TEXT_OFFSETS.length - 1;
var fill = main ? textFill : borderFill;
var textAsset = textAssets[i];
if (textAsset) {
textAsset.destroy();
}
textAsset = self.addChild(new Text2(newText, {
fill: fill,
font: font,
size: size
}));
textAsset.anchor = {
x: anchorX,
y: anchorY
}; // NOTE: Cannot be set in config
textAsset.x = TEXT_OFFSETS[i][0] * weight; // NOTE: Cannot be set in config
textAsset.y = TEXT_OFFSETS[i][1] * weight; // NOTE: Cannot be set in config
textAssets[i] = textAsset;
}
mainAsset = textAssets[TEXT_OFFSETS.length - 1];
}
;
buildTextAssets(text);
return self;
});
var EnemySpawner = Container.expand(function () {
var self = Container.call(this);
var borderedWidth = GAME_WIDTH - 2 * ENEMY_BORDER_SPAWN;
var borderedHeight = GAME_HEIGHT - 2 * ENEMY_BORDER_SPAWN;
var spawnTimer = ENEMY_SPAWN_DELAY;
var enabled = true;
self.update = function () {
if (!isPaused && enabled && enemyCount < ENEMY_LIMIT && --spawnTimer <= 0) {
spawnTimer = Math.max(ENEMY_SPAWN_RATE_MIN, ENEMY_SPAWN_RATE_BASE - Math.floor(ENEMY_SPAWN_RATE_SCALE * difficultyScale));
var isRanged = difficultyScale >= ENEMY_RANGED_SPAWN_DIFFICULTY && Math.random() < Math.max(ENEMY_RANGED_SPAWN_CHANCE_MIN, ENEMY_RANGED_SPAWN_CHANCE_FACTOR * difficultyScale);
var enemyClass = isRanged ? EnemyRanged : EnemyBasic;
self.spawnClass(enemyClass);
}
};
self.spawnClass = function (enemyClass) {
var spawnX, spawnY;
var side = Math.floor(Math.random() * 4);
var distance = Math.random();
switch (side) {
case 0:
spawnX = borderedWidth * distance;
spawnY = ENEMY_BORDER_SPAWN;
break;
case 1:
spawnX = GAME_WIDTH - ENEMY_BORDER_SPAWN;
spawnY = borderedHeight * distance;
break;
case 2:
spawnX = borderedWidth * distance;
spawnY = GAME_HEIGHT - ENEMY_BORDER_SPAWN;
break;
case 3:
spawnX = ENEMY_BORDER_SPAWN;
spawnY = borderedHeight * distance;
break;
}
midgroundContainer.addChild(new enemyClass({
x: spawnX,
y: spawnY
}));
};
self.setEnabled = function (newEnabled) {
enabled = newEnabled;
};
return self;
});
var SortingContainer = Container.expand(function (sortFunction) {
var self = Container.call(this);
self.update = function () {
self.children.sort(sortFunction || defaultSort);
};
function defaultSort(a, b) {
return a.y - b.y;
}
return self;
});
var WeaponBoss = Container.expand(function (config) {
var self = Container.call(this, config);
LK.showGameOver();
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// Play background ambient sound every 10 seconds
LK.setInterval(function () {
backgroundAmbientSound.play();
}, 10000);
// Play background ambient sound on game start
var backgroundAmbientSound = LK.getSound('backgroundAmbient');
backgroundAmbientSound.play();
;
//==============================================================================
// Global constants & settings
//==============================================================================
;
// Math constants / pre-calculations
var MATH_2_PI = Math.PI * 2;
var MATH_HALF_PI = Math.PI / 2;
var MATH_QUARTER_PI = Math.PI / 4;
var MATH_HALF_ROOT_3 = Math.sqrt(3) / 2; // Required by: TEXT_OFFSETS, BorderedText, BorderedSymbol, BorderedShape, SymbolText
var MATH_APPROX_ZERO = 0.0000001;
;
// Text settings
var TEXT_OFFSETS = [[0, 1], [MATH_HALF_ROOT_3, 0.5], [MATH_HALF_ROOT_3, -0.5], [0, -1], [-MATH_HALF_ROOT_3, -0.5], [-MATH_HALF_ROOT_3, 0.5], [0, 0]]; // Required by: BorderedText, BorderedSymbol, BorderedShape, SymbolText
var TEXT_DEFAULT_WEIGHT = 4; // Required by: BorderedText, BorderedSymbol, BorderedShape, SymbolText
var TEXT_DEFAULT_BORDER = '#000000'; // Required by: BorderedText, BorderedSymbol, BorderedShape, SymbolText
var TEXT_DEFAULT_FILL = '#FFFFFF'; // Required by: BorderedText, SymbolText
var TEXT_DEFAULT_FONT = 'Consolas'; // Required by: BorderedText, SymbolText
var TEXT_DEFAULT_SIZE = 120; // Required by: BorderedText, SymbolText
;
// Game constants
var GAME_TICKS = 60;
var GAME_WIDTH = 2048;
var GAME_HEIGHT = 2732;
var GAME_PERSPECTIVE = 0.5;
;
// Tags
var TAG_ENEMY = 'Enemy';
var TAG_BOSS = 'Boss';
var TAG_PROJECTILE = 'Projectile';
var TAG_PICKUP = 'Pickup';
var TAG_EXPERIENCE = 'Experience';
var TAG_CRUCIFIX = 'Crucifix';
var TAG_WEAPON = 'Weapon';
var TAG_HEALING = 'Healing';
;
// Hero settings
var HERO_COLOUR = 0x0FA0FF;
var HERO_BORDER = 250;
var HERO_HEALTH_BASE = 100;
var HERO_HEALTH_BONUS = 30;
var HERO_SPEED_BASE = 10;
var HERO_SPEED_BONUS = 3;
var HERO_XP_REQUIRED = 15;
var HERO_XP_SCALING = 1.15;
;
// Boon settings
var BOON_OPTIONS = 3;
var BOON_MAX_LEVEL = 3;
var BOON_MAJOR_LEVEL = 5;
var BOON_LUCK = 'Luck';
var BOON_SCALE = 'Scale';
var BOON_RANGE = 'Range';
var BOON_DAMAGE = 'Damage';
var BOON_REARM = 'Rearm';
var BOON_DURATION = 'Duration';
var BOON_HEALTH = 'Health';
var BOON_SPEED = 'Speed';
var BOON_GROWTH = 'Growth';
var BOON_SPLIT = 'Split';
var BOON_FULL_HEAL = 'Full Heal';
var BOON_MINOR_HEAL = 'Minor Heal';
;
// Weapon settings
var WEAPON_OFFSET = 350;
var WEAPON_HEIGHT_BASE = 50;
var WEAPON_HEIGHT_PERIOD = 10;
var WEAPON_HEIGHT_MAGNITUDE = 10;
;
var WEAPON_CROSS_SPLIT_INCREMENT = 20 / 180 * Math.PI;
var WEAPON_CROSS_RANGE_BASE = 200;
var WEAPON_CROSS_RANGE_SCALING = 125;
var WEAPON_CROSS_RANGE_VARIATION = 0.1;
var WEAPON_CROSS_RADIUS_BASE = 50;
var WEAPON_CROSS_SCALE_SCALING = 0.2;
var WEAPON_CROSS_SCALE_DAMAGE_FACTOR = 0.1;
var WEAPON_CROSS_COOLDOWN_HIT = GAME_TICKS / 6;
var WEAPON_CROSS_COOLDOWN_BASE = 120;
var WEAPON_CROSS_COOLDOWN_SCALING = 0.15;
var WEAPON_CROSS_DAMAGE_BASE = 15;
var WEAPON_CROSS_DAMAGE_SCALING = 10;
var WEAPON_CROSS_LINGER_BASE = 0;
var WEAPON_CROSS_LINGER_VARIANCE = GAME_TICKS / 6;
var WEAPON_CROSS_LINGER_SCALING = 0.25 * GAME_TICKS;
var WEAPON_CROSS_GROWTH_SCALING = 0.20 / GAME_TICKS;
var WEAPON_CROSS_SPEED_BASE = 30;
var WEAPON_CROSS_SPEED_DECREMENT = 1;
var WEAPON_CROSS_ELEVATION = 175;
var WEAPON_CROSS_ROTATION = 0.2;
var WEAPON_CROSS_BORDER = -1000;
;
// Enemy settings
var ENEMY_COLOUR = 0xAA0000;
var ENEMY_BORDER = 50; // Forced minimum move distance from the edge of the screen
var ENEMY_BORDER_SPAWN = -300;
var ENEMY_SPAWN_DELAY = 2 * GAME_TICKS;
var ENEMY_SPAWN_RATE_BASE = 1 * GAME_TICKS;
var ENEMY_SPAWN_RATE_SCALE = 0.75 * GAME_TICKS;
var ENEMY_SPAWN_RATE_MIN = 0.25 * GAME_TICKS;
var ENEMY_LIMIT = 40;
var ENEMY_XP_RANGE = 50;
var ENEMY_STATE_MOVE = 'Move';
var ENEMY_STATE_FLEE = 'Flee';
var ENEMY_STATE_ATTACK = 'Attack';
;
var ENEMY_BASIC_SPEED = 2.5;
var ENEMY_BASIC_HEALTH_BASE = 10;
var ENEMY_BASIC_HEALTH_SCALE = 50;
var ENEMY_BASIC_HEALTH_OFFSET = -30;
var ENEMY_BASIC_ATTACK_RANGE = 100;
var ENEMY_BASIC_ATTACK_DAMAGE = 10;
var ENEMY_BASIC_ATTACK_COOLDOWN = GAME_TICKS / 3;
var ENEMY_BASIC_GRAPHICS_OFFSET = 115;
var ENEMY_BASIC_BOB_MAGNITUDE = 15;
var ENEMY_BASIC_BOB_PERIOD = 15;
var ENEMY_BASIC_XP_DROP_CHANCE = 0.3; // Per level of Luck
var ENEMY_BASIC_XP_EXTRA_ROLLS = 2;
;
var ENEMY_RANGED_SPEED = 1.5;
var ENEMY_RANGED_HEALTH_BASE = 20;
var ENEMY_RANGED_HEALTH_SCALE = 60;
var ENEMY_RANGED_HEALTH_OFFSET = -20;
var ENEMY_RANGED_RANGE_MIN = 600;
var ENEMY_RANGED_RANGE_VAR = 250;
var ENEMY_RANGED_ATTACK_DAMAGE = 5;
var ENEMY_RANGED_ATTACK_COOLDOWN = 4 * GAME_TICKS;
var ENEMY_RANGED_MOVE_COOLDOWN_ADJUSTMENT = -0.25;
var ENEMY_RANGED_ELEVATION = 150;
var ENEMY_RANGED_FLEE_DISTANCE_FACTOR = 0.5;
var ENEMY_RANGED_FLEE_SPEED_FACTOR = 2.0;
var ENEMY_RANGED_SCALE_PERIOD = 20;
var ENEMY_RANGED_SCALE_MAGNITUDE = 0.05;
var ENEMY_RANGED_RADIUS_BASE = 60;
var ENEMY_RANGED_XP_DROP_BASE = 2;
var ENEMY_RANGED_XP_DROP_CHANCE = 0.5; // Per level of Luck
var ENEMY_RANGED_XP_EXTRA_ROLLS = 0;
var ENEMY_RANGED_SPAWN_DIFFICULTY = 0.2;
var ENEMY_RANGED_SPAWN_CHANCE_MIN = 0.2;
var ENEMY_RANGED_SPAWN_CHANCE_FACTOR = 0.6;
var ENEMY_RANGED_PROJECTILE_SPEED = 10;
var ENEMY_RANGED_PROJECTILE_RADIUS = 25;
;
var ENEMY_BOSS_SPEED = 5.0;
var ENEMY_BOSS_HEALTH = 5000;
var ENEMY_BOSS_HEALTH_OFFSET = -30;
var ENEMY_BOSS_ATTACK_RANGE = 250;
var ENEMY_BOSS_ATTACK_DAMAGE = 5;
var ENEMY_BOSS_ATTACK_COOLDOWN = GAME_TICKS / 6;
var ENEMY_BOSS_FLEE_RANGE = 90;
var ENEMY_BOSS_PROJECTILE_COUNT = 5;
var ENEMY_BOSS_PROJECTILE_ROTATION = MATH_2_PI / GAME_TICKS;
var ENEMY_BOSS_PROJECTILE_OFFSET = -100;
;
// Pickup settings
var PICKUP_HEAL_MINOR = 0.1;
var PICKUP_HEAL_MAJOR = 1.0;
var PICKUP_HEALTH_COUNT = 3;
var PICKUP_HEALTH_CHANCE = 0.02; // Per level of luck
var PICKUP_CRUCIFIX_COUNT = 1;
var PICKUP_CRUCIFIX_RANGE = 200;
var PICKUP_WEAPON_RANGE = 200;
var PICKUP_XP_MEDIUM = 5;
var PICKUP_XP_LARGE = 10;
var PICKUP_XP_SPEED = 20;
var PICKUP_XP_COMBINE_RANGE = 150;
var PICKUP_XP_ACTIVATE_RANGE = 300;
;
//==============================================================================
// Instances & variables
//==============================================================================
;
// Variables
var id = 0;
var isPaused = false;
var movePosition;
var enemyCount = 0;
var minorBoonCount = 1;
var majorBoonCount = 0;
var pickupHealthCount = 0;
var pickupCrucifixCount = 0;
var difficultyScale = 0;
var startingWeapons = [];
var minorBoonLevels = {
'Luck': 0,
'Scale': 0,
'Range': 0,
'Damage': 0,
'Rearm': 0,
'Duration': 0,
'Health': 0,
'Speed': 0
};
var majorBoonLevels = {
'Growth': 0,
'Split': 0
};
;
// Game instances
game.attachAsset('grassTop', {
y: HERO_BORDER - 100,
x: GAME_WIDTH / 2,
anchorX: 0.5
});
game.attachAsset('grassBot', {
y: GAME_HEIGHT - HERO_BORDER + 100,
x: GAME_WIDTH / 2,
anchorX: 0.5,
anchorY: 1.0
});
var enemySpawner = game.addChild(new EnemySpawner());
var backgroundContainer = game.addChild(new SortingContainer());
var midgroundContainer = game.addChild(new SortingContainer());
var foregroundContainer = game.addChild(new Container());
var interfaceContainer = game.addChild(new Container());
var hero = midgroundContainer.addChild(new Hero({
x: GAME_WIDTH / 2,
y: GAME_HEIGHT / 2,
radius: 65
}));
/*
backgroundContainer.addChild(new PickupWeapon({
x: GAME_WIDTH / 4 * 2,
y: GAME_HEIGHT / 4,
scale: 1.25,
graphics: 'weaponFireball',
weaponClass: WeaponFireball
}));
*/
backgroundContainer.addChild(new PickupWeapon({
x: GAME_WIDTH / 2 - WEAPON_OFFSET,
y: GAME_HEIGHT / 2 + WEAPON_OFFSET * GAME_PERSPECTIVE,
scale: 1.25,
graphics: 'weaponCross',
weaponClass: WeaponCross
}));
;
// Interface instances
var joystick = game.addChild(new Joystick({
size: 400,
x: GAME_WIDTH - 400,
y: GAME_HEIGHT - 400
}));
var upgradeButton = interfaceContainer.addChild(new UiBoonUpgradeButton({
x: GAME_WIDTH - 200,
y: GAME_HEIGHT - 750,
size: 200,
callback: function callback() {
if (minorBoonCount + majorBoonCount > 0) {
showBoonSelection();
}
}
}));
var countdownTimer = interfaceContainer.addChild(new UiCountdownTimer({
x: GAME_WIDTH / 2,
y: 20,
countdown: 300
}));
upgradeButton.setCount(minorBoonCount + majorBoonCount);
;
//==============================================================================
// Global events
//==============================================================================
;
// Add event listeners for the joystick
game.down = function (x, y) {
if (!isPaused) {
movePosition = {
x: x,
y: y
};
}
};
game.move = function (x, y, obj) {
if (!isPaused) {
if (!movePosition) {
joystick.movement(x - joystick.x, y - joystick.y, obj);
} else {
movePosition = {
x: x,
y: y
};
}
}
};
game.up = function (x, y, obj) {
joystick.release(x, y, obj);
movePosition = undefined;
};
;
//==============================================================================
// Global helper functions
//==============================================================================
;
function checkBounds(x, y, border) {
return !(x < border || x > GAME_WIDTH - border || y < border || y > GAME_HEIGHT - border);
}
function showBoonSelection() {
var boonSelection;
if (minorBoonCount) {
boonSelection = LK.gui.center.addChild(new UiBoonSelection({
boons: minorBoonLevels,
type: 'Minor',
count: minorBoonCount,
callback: function callback(boon) {
minorBoonCount--;
if (checkBoonActions(boon)) {
minorBoonLevels[boon]++;
upgradeButton.setCount(minorBoonCount + majorBoonCount);
}
showBoonSelection();
}
}));
} else if (majorBoonCount) {
boonSelection = LK.gui.center.addChild(new UiBoonSelection({
boons: majorBoonLevels,
type: 'Major',
count: majorBoonCount,
callback: function callback(boon) {
majorBoonCount--;
if (checkBoonActions(boon)) {
majorBoonLevels[boon]++;
upgradeButton.setCount(minorBoonCount + majorBoonCount);
}
showBoonSelection();
}
}));
}
isPaused = !!boonSelection;
upgradeButton.setHidden(isPaused);
}
function checkBoonActions(boon) {
switch (boon) {
case BOON_HEALTH:
hero.healthMax += HERO_HEALTH_BONUS;
hero.healPercentage(0); // Update the health bar
return true;
case BOON_SPEED:
hero.speed += HERO_SPEED_BONUS;
return true;
case BOON_MINOR_HEAL:
hero.healPercentage(PICKUP_HEAL_MINOR);
return false;
case BOON_FULL_HEAL:
hero.healPercentage(PICKUP_HEAL_MAJOR);
return false;
}
return true;
}
pixel art cross with blue accents Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows.
pixel art of a white orb. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows.
pixel art of a white orb with a halo. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows.
pixel art of a pulsating white heart with a halo. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows.
pixel art of a dark goo projectile with red highlights. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows.
pixel art tall blue fireball. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows.
pixel art of an evil fantasy sword facing downward. Minor red details. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows.
backgroundAmbient
Sound effect
heroHealed
Sound effect
pickupExperience
Sound effect
heroLeveled
Sound effect
weaponCrossImpact
Sound effect
heroImpact
Sound effect
enemyDeath
Sound effect
pickupWeapon
Sound effect
pickupCrucifix
Sound effect
weaponCrossLaunch
Sound effect
heroDeath
Sound effect
enemyRoar
Sound effect
clockChime
Sound effect