/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
playCount: 0,
mercyScore: 0,
dialogueChoices: {},
endingReached: "none",
characterRelationships: {}
});
/****
* Classes
****/
var CombatEnemy = Container.expand(function () {
var self = Container.call(this);
var sprite = self.attachAsset('combatEnemy', {
anchorX: 0.5,
anchorY: 0.5
});
var healthBar = self.attachAsset('healthBar', {
anchorX: 0.5,
anchorY: 0.5
});
healthBar.y = -60;
var healthFill = LK.getAsset('healthBar', {
anchorX: 0,
anchorY: 0,
tint: 0x00ff00
});
healthBar.addChild(healthFill);
self.maxHealth = 30;
self.currentHealth = 30;
self.name = '';
self.damage = 5;
self.enemyId = null;
self.setData = function (id, name, health, damage) {
self.enemyId = id;
self.name = name;
self.maxHealth = health;
self.currentHealth = health;
self.damage = damage;
};
self.takeDamage = function (amount) {
self.currentHealth -= amount;
if (self.currentHealth < 0) {
self.currentHealth = 0;
}
var healthPercent = self.currentHealth / self.maxHealth;
healthFill.scaleX = healthPercent;
LK.getSound('combatDamage').play();
};
self.isDefeated = function () {
return self.currentHealth <= 0;
};
return self;
});
var DialogueChoice = Container.expand(function () {
var self = Container.call(this);
var background = self.attachAsset('choiceButton', {
anchorX: 0.5,
anchorY: 0.5
});
var text = new Text2('', {
size: 40,
fill: '#ffffff'
});
text.anchor.set(0.5, 0.5);
self.addChild(text);
self.setText = function (str) {
text.setText(str);
};
self.choiceId = null;
self.isSelected = false;
self.down = function (x, y, obj) {
if (!self.isSelected) {
self.isSelected = true;
LK.getSound('choiceSelect').play();
tween(background, {
alpha: 0.7
}, {
duration: 100
});
}
};
return self;
});
var DialogueSystem = Container.expand(function () {
var self = Container.call(this);
var background = self.attachAsset('dialogueBox', {
anchorX: 0.5,
anchorY: 0.5
});
background.alpha = 0.95;
var speakerName = new Text2('', {
size: 36,
fill: '#ffff00'
});
speakerName.anchor.set(0, 0);
speakerName.x = 20;
speakerName.y = 20;
self.addChild(speakerName);
var dialogueText = new Text2('', {
size: 32,
fill: '#ffffff'
});
dialogueText.anchor.set(0, 0);
dialogueText.x = 30;
dialogueText.y = 70;
dialogueText.wordWrap = true;
dialogueText.wordWrapWidth = 1740;
self.addChild(dialogueText);
self.choices = [];
self.currentNode = null;
self.onChoiceCallback = null;
self.displayDialogue = function (npcName, text, choicesArray) {
speakerName.setText(npcName);
dialogueText.setText(text);
self.clearChoices();
if (choicesArray && choicesArray.length > 0) {
var choiceSpacing = 100;
var startY = 350;
for (var i = 0; i < choicesArray.length; i++) {
var choice = new DialogueChoice();
choice.x = 1024;
choice.y = startY + i * choiceSpacing;
choice.setText(choicesArray[i].text);
choice.choiceId = choicesArray[i].id;
choice.onSelectCallback = choicesArray[i].callback;
self.addChild(choice);
self.choices.push(choice);
}
}
};
self.clearChoices = function () {
for (var i = self.choices.length - 1; i >= 0; i--) {
self.choices[i].destroy();
}
self.choices = [];
};
self.update = function () {
for (var i = 0; i < self.choices.length; i++) {
if (self.choices[i].isSelected) {
if (self.choices[i].onSelectCallback) {
self.choices[i].onSelectCallback();
}
self.clearChoices();
break;
}
}
};
return self;
});
var NPC = Container.expand(function () {
var self = Container.call(this);
var sprite = self.attachAsset('npcCharacter', {
anchorX: 0.5,
anchorY: 0.5
});
var nameLabel = new Text2('', {
size: 32,
fill: '#ffffff'
});
nameLabel.anchor.set(0.5, 0);
nameLabel.y = 50;
self.addChild(nameLabel);
self.npcId = null;
self.name = '';
self.relationship = 0;
self.dialogueTree = null;
self.isEnemy = false;
self.setData = function (id, name, dialogueTree, isEnemy) {
self.npcId = id;
self.name = name;
self.dialogueTree = dialogueTree;
self.isEnemy = isEnemy;
nameLabel.setText(name);
if (isEnemy) {
sprite.tint = 0xff6666;
}
};
self.updateRelationship = function (delta) {
self.relationship += delta;
storage.characterRelationships[self.npcId] = self.relationship;
};
return self;
});
var Player = Container.expand(function () {
var self = Container.call(this);
var sprite = self.attachAsset('playerCharacter', {
anchorX: 0.5,
anchorY: 0.5
});
var healthBar = self.attachAsset('healthBar', {
anchorX: 0.5,
anchorY: 0.5
});
healthBar.y = -50;
var healthFill = LK.getAsset('healthBar', {
anchorX: 0,
anchorY: 0,
tint: 0x00ff00
});
healthBar.addChild(healthFill);
self.maxHealth = 50;
self.currentHealth = 50;
self.empathy = 50;
self.maxEmpathy = 100;
self.mercyMeter = 0;
self.takeDamage = function (amount) {
self.currentHealth -= amount;
if (self.currentHealth < 0) {
self.currentHealth = 0;
}
var healthPercent = self.currentHealth / self.maxHealth;
healthFill.scaleX = healthPercent;
LK.getSound('combatDamage').play();
};
self.heal = function (amount) {
self.currentHealth += amount;
if (self.currentHealth > self.maxHealth) {
self.currentHealth = self.maxHealth;
}
};
self.gainEmpathy = function (amount) {
self.empathy += amount;
if (self.empathy > self.maxEmpathy) {
self.empathy = self.maxEmpathy;
}
};
self.recordMercyChoice = function (isMercy) {
if (isMercy) {
self.mercyMeter += 1;
storage.mercyScore += 1;
} else {
self.mercyMeter -= 1;
storage.mercyScore -= 1;
}
};
self.isDefeated = function () {
return self.currentHealth <= 0;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x0a0a0a
});
/****
* Game Code
****/
var topBar = LK.getAsset('barTop', {
anchorX: 0,
anchorY: 0
});
topBar.x = 0;
topBar.y = 0;
game.addChild(topBar);
var bottomBar = LK.getAsset('barBottom', {
anchorX: 0,
anchorY: 0
});
bottomBar.x = 0;
bottomBar.y = 2580;
game.addChild(bottomBar);
var gameState = {
currentScene: 'start',
inCombat: false,
inDialogue: false,
sceneCount: 0,
endingType: 'none'
};
var player = null;
var currentNPC = null;
var currentEnemy = null;
var dialogueSystem = null;
var npcList = {};
var combatLog = [];
function initializeGame() {
storage.playCount = (storage.playCount || 0) + 1;
player = game.addChild(new Player());
player.x = 400;
player.y = 1366;
dialogueSystem = game.addChild(new DialogueSystem());
dialogueSystem.x = 1024;
dialogueSystem.y = 1900;
dialogueSystem.alpha = 0;
var angelLabel = new Text2('ANGEL IMPRISONED', {
size: 48,
fill: '#ffd700'
});
angelLabel.anchor.set(0.5, 0.5);
angelLabel.x = 1600;
angelLabel.y = 400;
game.addChild(angelLabel);
var angel = game.addChild(LK.getAsset('angelForm', {
anchorX: 0.5,
anchorY: 0.5
}));
angel.x = 1600;
angel.y = 300;
var machine = game.addChild(LK.getAsset('machineBody', {
anchorX: 0.5,
anchorY: 0.5
}));
machine.x = 1600;
machine.y = 550;
initializeNPCs();
loadStoredRelationships();
LK.playMusic('ambientDrone', {
loop: true
});
startScene('prologue');
}
function initializeNPCs() {
var npcData = {
scientist: {
name: 'Dr. Elena',
dialogueTree: dialogueTreeScientist,
isEnemy: false
},
survivor: {
name: 'Marcus',
dialogueTree: dialogueTreeSurvivor,
isEnemy: false
},
guardian: {
name: 'Sentinel',
dialogueTree: dialogueTreeGuardian,
isEnemy: true
}
};
for (var id in npcData) {
var npc = new NPC();
npc.setData(id, npcData[id].name, npcData[id].dialogueTree, npcData[id].isEnemy);
npcList[id] = npc;
if (!storage.characterRelationships[id]) {
storage.characterRelationships[id] = 0;
}
npc.relationship = storage.characterRelationships[id];
}
}
function loadStoredRelationships() {
for (var id in npcList) {
npcList[id].relationship = storage.characterRelationships[id] || 0;
}
}
function startScene(sceneName) {
gameState.currentScene = sceneName;
gameState.sceneCount += 1;
if (sceneName === 'prologue') {
showPrologue();
} else if (sceneName === 'hub') {
showHub();
} else if (sceneName === 'encounter_scientist') {
showEncounterScientist();
} else if (sceneName === 'encounter_survivor') {
showEncounterSurvivor();
} else if (sceneName === 'encounter_guardian') {
showEncounterGuardian();
} else if (sceneName === 'endgame') {
determineEnding();
}
}
function showPrologue() {
tween(dialogueSystem, {
alpha: 1
}, {
duration: 500
});
var prologueText = 'The world has ended. Heaven sent salvation, but humanity chose slavery. An angel was imprisoned in a machine to harvest divine power. You believe this is wrong. You will free it... or die trying.';
var choices = [{
id: 'start_yes',
text: 'I will free the angel, no matter the cost.',
callback: function callback() {
storage.dialogueChoices['prologue_choice'] = 'start_yes';
LK.setTimeout(function () {
startScene('hub');
}, 500);
}
}, {
id: 'start_question',
text: 'Is the angel really worth humanity\'s suffering?',
callback: function callback() {
storage.dialogueChoices['prologue_choice'] = 'start_question';
gameState.endingType = 'neutral';
LK.setTimeout(function () {
startScene('hub');
}, 500);
}
}];
dialogueSystem.displayDialogue('The Universe', prologueText, choices);
}
function showHub() {
if (gameState.sceneCount === 2) {
var hubText = 'You stand before the machine. Three paths lie before you: seek the scientist who built it, convince the survivor supporting it, or face the guardian protecting it.';
var choices = [{
id: 'hub_scientist',
text: 'Confront Dr. Elena, the machine\'s architect.',
callback: function callback() {
startScene('encounter_scientist');
}
}, {
id: 'hub_survivor',
text: 'Talk to Marcus, who believes the angel should stay imprisoned.',
callback: function callback() {
startScene('encounter_survivor');
}
}, {
id: 'hub_guardian',
text: 'Fight the Sentinel protecting the machine.',
callback: function callback() {
startScene('encounter_guardian');
}
}];
dialogueSystem.displayDialogue('Your Inner Voice', hubText, choices);
}
}
function showEncounterScientist() {
currentNPC = npcList['scientist'];
var scientistText = 'Dr. Elena looks at you with tired eyes. "We did what we had to. This angel... it gives us power to rebuild. Without it, humanity is finished."';
var choices = [{
id: 'sci_mercy',
text: 'I understand your desperation. Let me help find another way.',
callback: function callback() {
player.recordMercyChoice(true);
npcList['scientist'].updateRelationship(2);
storage.dialogueChoices['scientist_choice'] = 'mercy';
combatVsDialogue('scientist', true);
}
}, {
id: 'sci_condemn',
text: 'Your arrogance has damned us all. The angel must be freed.',
callback: function callback() {
player.recordMercyChoice(false);
npcList['scientist'].updateRelationship(-3);
storage.dialogueChoices['scientist_choice'] = 'condemn';
combatVsDialogue('scientist', false);
}
}];
dialogueSystem.displayDialogue('Dr. Elena', scientistText, choices);
}
function showEncounterSurvivor() {
currentNPC = npcList['survivor'];
var survivorText = 'Marcus grips your shoulder. "Everyone I love is alive because of that angel. You want to take that away? What gives you the right?"';
var choices = [{
id: 'surv_mercy',
text: 'Your loved ones matter. But so does freedom. We can find balance.',
callback: function callback() {
player.recordMercyChoice(true);
npcList['survivor'].updateRelationship(2);
storage.dialogueChoices['survivor_choice'] = 'mercy';
combatVsDialogue('survivor', true);
}
}, {
id: 'surv_condemn',
text: 'Love built on slavery is no love at all.',
callback: function callback() {
player.recordMercyChoice(false);
npcList['survivor'].updateRelationship(-3);
storage.dialogueChoices['survivor_choice'] = 'condemn';
combatVsDialogue('survivor', false);
}
}];
dialogueSystem.displayDialogue('Marcus', survivorText, choices);
}
function showEncounterGuardian() {
currentEnemy = new CombatEnemy();
currentEnemy.x = 1600;
currentEnemy.y = 600;
game.addChild(currentEnemy);
currentEnemy.setData('sentinel', 'Sentinel', 40, 8);
var guardianText = 'The mechanical guardian stands motionless, then speaks: "I was programmed to protect. I do not question. Surrender or be destroyed."';
var choices = [{
id: 'guar_talk',
text: 'You\'re more than your programming. You can choose.',
callback: function callback() {
player.recordMercyChoice(true);
storage.dialogueChoices['guardian_choice'] = 'talk';
startCombat('sentinel_dialogue');
}
}, {
id: 'guar_fight',
text: 'Then I\'ll fight you.',
callback: function callback() {
player.recordMercyChoice(false);
storage.dialogueChoices['guardian_choice'] = 'fight';
startCombat('sentinel_combat');
}
}];
dialogueSystem.displayDialogue('Sentinel', guardianText, choices);
}
function combatVsDialogue(npcId, isDialogue) {
gameState.inDialogue = isDialogue;
tween(dialogueSystem, {
alpha: 0
}, {
duration: 300
});
if (isDialogue) {
LK.setTimeout(function () {
resolveEncounter(npcId, true);
}, 400);
} else {
currentEnemy = new CombatEnemy();
currentEnemy.x = 1600;
currentEnemy.y = 600;
game.addChild(currentEnemy);
if (npcId === 'scientist') {
currentEnemy.setData('scientist', 'Dr. Elena', 35, 6);
} else if (npcId === 'survivor') {
currentEnemy.setData('survivor', 'Marcus', 30, 7);
}
startCombat(npcId);
}
}
function startCombat(combatId) {
gameState.inCombat = true;
LK.playMusic('combatTheme', {
loop: true
});
var combatUI = new Text2('COMBAT: ' + currentEnemy.name.toUpperCase(), {
size: 48,
fill: '#ff4444'
});
combatUI.anchor.set(0.5, 0);
combatUI.x = 1024;
combatUI.y = 100;
game.addChild(combatUI);
var attackButton = new DialogueChoice();
attackButton.x = 500;
attackButton.y = 1300;
attackButton.setText('ATTACK (DMG: 8)');
game.addChild(attackButton);
var defendButton = new DialogueChoice();
defendButton.x = 1500;
defendButton.y = 1300;
defendButton.setText('DEFEND');
game.addChild(defendButton);
attackButton.down = function (x, y, obj) {
player.recordMercyChoice(false);
playerAttack();
};
defendButton.down = function (x, y, obj) {
playerDefend();
};
game.combatUI = {
title: combatUI,
attackBtn: attackButton,
defendBtn: defendButton
};
}
function playerAttack() {
if (!gameState.inCombat) return;
var damage = 8 + Math.floor(Math.random() * 4);
currentEnemy.takeDamage(damage);
LK.getSound('combatHit').play();
var damageText = new Text2('-' + damage, {
size: 36,
fill: '#ff0000'
});
damageText.x = currentEnemy.x;
damageText.y = currentEnemy.y - 50;
game.addChild(damageText);
tween(damageText, {
y: damageText.y - 50,
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
damageText.destroy();
}
});
if (currentEnemy.isDefeated()) {
endCombat(true);
} else {
LK.setTimeout(enemyTurn, 600);
}
}
function playerDefend() {
if (!gameState.inCombat) return;
player.heal(5);
var healText = new Text2('+5', {
size: 36,
fill: '#00ff00'
});
healText.x = player.x;
healText.y = player.y - 50;
game.addChild(healText);
tween(healText, {
y: healText.y - 50,
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
healText.destroy();
}
});
LK.setTimeout(enemyTurn, 600);
}
function enemyTurn() {
if (!gameState.inCombat || !currentEnemy) return;
var enemyDamage = currentEnemy.damage + Math.floor(Math.random() * 3);
player.takeDamage(enemyDamage);
var damageText = new Text2('-' + enemyDamage, {
size: 36,
fill: '#ff0000'
});
damageText.x = player.x;
damageText.y = player.y - 50;
game.addChild(damageText);
tween(damageText, {
y: damageText.y - 50,
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
damageText.destroy();
}
});
if (player.isDefeated()) {
endCombat(false);
}
}
function endCombat(playerWon) {
gameState.inCombat = false;
LK.playMusic('ambientDrone', {
loop: true
});
if (game.combatUI) {
game.combatUI.title.destroy();
game.combatUI.attackBtn.destroy();
game.combatUI.defendBtn.destroy();
game.combatUI = null;
}
if (currentEnemy) {
currentEnemy.destroy();
currentEnemy = null;
}
if (playerWon) {
resolveEncounter(null, false);
} else {
LK.showGameOver();
}
}
function resolveEncounter(npcId, throughDialogue) {
tween(dialogueSystem, {
alpha: 1
}, {
duration: 500
});
var resolutionText = '';
if (throughDialogue && npcId === 'scientist') {
resolutionText = 'Dr. Elena looks down, tears forming. "Maybe... maybe there is another way. I\'ll help you free it."';
} else if (!throughDialogue && npcId === 'scientist') {
resolutionText = 'Dr. Elena falls. Your violence has solved the immediate problem, but at what cost?';
} else if (throughDialogue && npcId === 'survivor') {
resolutionText = 'Marcus nods slowly. "You\'re right. Freedom matters more than my fear."';
} else if (!throughDialogue && npcId === 'survivor') {
resolutionText = 'Marcus collapses. You wonder if he was ever your enemy, or just a desperate man.';
} else if (npcId === null) {
resolutionText = 'The path forward clears before you.';
}
var continueChoice = [{
id: 'continue_main',
text: 'Continue',
callback: function callback() {
gameState.sceneCount += 1;
if (gameState.sceneCount >= 4) {
startScene('endgame');
} else {
startScene('hub');
}
}
}];
dialogueSystem.displayDialogue('Narrator', resolutionText, continueChoice);
}
function determineEnding() {
var mercyTotal = storage.mercyScore || 0;
var relationshipSum = 0;
for (var id in storage.characterRelationships) {
relationshipSum += storage.characterRelationships[id];
}
var playCount = storage.playCount || 1;
var prologue = storage.dialogueChoices['prologue_choice'] || '';
var ending = 'neutral';
if (playCount === 1 && mercyTotal <= -5) {
ending = 'bad';
} else if (playCount >= 3 && mercyTotal >= 8 && relationshipSum >= 4) {
ending = 'good';
} else if (playCount >= 5 && mercyTotal >= 10 && relationshipSum >= 6) {
ending = 'secret';
} else if (playCount >= 2 && mercyTotal <= -8) {
ending = 'hellish';
} else {
ending = 'neutral';
}
gameState.endingType = ending;
storage.endingReached = ending;
showEnding(ending);
}
function showEnding(endingType) {
var endingTexts = {
neutral: {
title: 'THE CAGE REMAINS',
text: 'You release the angel. It ascends into ash-grey skies. Humanity survives, changed but unbroken. You wonder if you saved anyone, or simply postponed the inevitable.'
},
good: {
title: 'REDEMPTION',
text: 'Your mercy and choices have won allies. Together, you dismantle the machine peacefully. The angel\'s light spreads, and for the first time in ages, humanity glimpses genuine hope.'
},
bad: {
title: 'ASHES AND SILENCE',
text: 'Your ruthlessness consumed everything. The angel is freed but broken. You stand alone amid the wreckage, victorious and utterly hollow. The universe watches your triumph with indifference.'
},
secret: {
title: 'TRANSCENDENCE',
text: 'Through patience, empathy, and sacrifice across many lives, you\'ve transcended mortal understanding. The angel recognizes you. Reality bends. Is this salvation or damnation?'
},
hellish: {
title: 'DAMNATION',
text: 'Your choices have carved too deep. The machine, damaged beyond control, releases not salvation but annihilation. Fire consumes the world. In hell\'s blazing light, you finally understand what you\'ve done.'
}
};
var ending = endingTexts[endingType] || endingTexts.neutral;
tween(dialogueSystem, {
alpha: 0
}, {
duration: 300
});
var endingTitle = new Text2(ending.title, {
size: 64,
fill: '#ffff00'
});
endingTitle.anchor.set(0.5, 0.5);
endingTitle.x = 1024;
endingTitle.y = 600;
game.addChild(endingTitle);
var endingBody = new Text2(ending.text, {
size: 40,
fill: '#ffffff'
});
endingBody.anchor.set(0.5, 0.5);
endingBody.x = 1024;
endingBody.y = 1000;
endingBody.wordWrap = true;
endingBody.wordWrapWidth = 1800;
game.addChild(endingBody);
var playCountDisplay = new Text2('Playthroughs: ' + (storage.playCount || 1), {
size: 32,
fill: '#888888'
});
playCountDisplay.anchor.set(0.5, 0);
playCountDisplay.x = 1024;
playCountDisplay.y = 2500;
game.addChild(playCountDisplay);
LK.setTimeout(function () {
LK.showYouWin();
}, 3000);
}
var dialogueTreeScientist = {
root: 'start'
};
var dialogueTreeSurvivor = {
root: 'start'
};
var dialogueTreeGuardian = {
root: 'start'
};
game.update = function () {
if (dialogueSystem && dialogueSystem.update) {
dialogueSystem.update();
}
if (player && player.isDefeated() && gameState.inCombat) {
endCombat(false);
}
};
game.down = function (x, y, obj) {
// Main interaction handler
};
game.move = function (x, y, obj) {
// Main move handler
};
initializeGame(); /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
playCount: 0,
mercyScore: 0,
dialogueChoices: {},
endingReached: "none",
characterRelationships: {}
});
/****
* Classes
****/
var CombatEnemy = Container.expand(function () {
var self = Container.call(this);
var sprite = self.attachAsset('combatEnemy', {
anchorX: 0.5,
anchorY: 0.5
});
var healthBar = self.attachAsset('healthBar', {
anchorX: 0.5,
anchorY: 0.5
});
healthBar.y = -60;
var healthFill = LK.getAsset('healthBar', {
anchorX: 0,
anchorY: 0,
tint: 0x00ff00
});
healthBar.addChild(healthFill);
self.maxHealth = 30;
self.currentHealth = 30;
self.name = '';
self.damage = 5;
self.enemyId = null;
self.setData = function (id, name, health, damage) {
self.enemyId = id;
self.name = name;
self.maxHealth = health;
self.currentHealth = health;
self.damage = damage;
};
self.takeDamage = function (amount) {
self.currentHealth -= amount;
if (self.currentHealth < 0) {
self.currentHealth = 0;
}
var healthPercent = self.currentHealth / self.maxHealth;
healthFill.scaleX = healthPercent;
LK.getSound('combatDamage').play();
};
self.isDefeated = function () {
return self.currentHealth <= 0;
};
return self;
});
var DialogueChoice = Container.expand(function () {
var self = Container.call(this);
var background = self.attachAsset('choiceButton', {
anchorX: 0.5,
anchorY: 0.5
});
var text = new Text2('', {
size: 40,
fill: '#ffffff'
});
text.anchor.set(0.5, 0.5);
self.addChild(text);
self.setText = function (str) {
text.setText(str);
};
self.choiceId = null;
self.isSelected = false;
self.down = function (x, y, obj) {
if (!self.isSelected) {
self.isSelected = true;
LK.getSound('choiceSelect').play();
tween(background, {
alpha: 0.7
}, {
duration: 100
});
}
};
return self;
});
var DialogueSystem = Container.expand(function () {
var self = Container.call(this);
var background = self.attachAsset('dialogueBox', {
anchorX: 0.5,
anchorY: 0.5
});
background.alpha = 0.95;
var speakerName = new Text2('', {
size: 36,
fill: '#ffff00'
});
speakerName.anchor.set(0, 0);
speakerName.x = 20;
speakerName.y = 20;
self.addChild(speakerName);
var dialogueText = new Text2('', {
size: 32,
fill: '#ffffff'
});
dialogueText.anchor.set(0, 0);
dialogueText.x = 30;
dialogueText.y = 70;
dialogueText.wordWrap = true;
dialogueText.wordWrapWidth = 1740;
self.addChild(dialogueText);
self.choices = [];
self.currentNode = null;
self.onChoiceCallback = null;
self.displayDialogue = function (npcName, text, choicesArray) {
speakerName.setText(npcName);
dialogueText.setText(text);
self.clearChoices();
if (choicesArray && choicesArray.length > 0) {
var choiceSpacing = 100;
var startY = 350;
for (var i = 0; i < choicesArray.length; i++) {
var choice = new DialogueChoice();
choice.x = 1024;
choice.y = startY + i * choiceSpacing;
choice.setText(choicesArray[i].text);
choice.choiceId = choicesArray[i].id;
choice.onSelectCallback = choicesArray[i].callback;
self.addChild(choice);
self.choices.push(choice);
}
}
};
self.clearChoices = function () {
for (var i = self.choices.length - 1; i >= 0; i--) {
self.choices[i].destroy();
}
self.choices = [];
};
self.update = function () {
for (var i = 0; i < self.choices.length; i++) {
if (self.choices[i].isSelected) {
if (self.choices[i].onSelectCallback) {
self.choices[i].onSelectCallback();
}
self.clearChoices();
break;
}
}
};
return self;
});
var NPC = Container.expand(function () {
var self = Container.call(this);
var sprite = self.attachAsset('npcCharacter', {
anchorX: 0.5,
anchorY: 0.5
});
var nameLabel = new Text2('', {
size: 32,
fill: '#ffffff'
});
nameLabel.anchor.set(0.5, 0);
nameLabel.y = 50;
self.addChild(nameLabel);
self.npcId = null;
self.name = '';
self.relationship = 0;
self.dialogueTree = null;
self.isEnemy = false;
self.setData = function (id, name, dialogueTree, isEnemy) {
self.npcId = id;
self.name = name;
self.dialogueTree = dialogueTree;
self.isEnemy = isEnemy;
nameLabel.setText(name);
if (isEnemy) {
sprite.tint = 0xff6666;
}
};
self.updateRelationship = function (delta) {
self.relationship += delta;
storage.characterRelationships[self.npcId] = self.relationship;
};
return self;
});
var Player = Container.expand(function () {
var self = Container.call(this);
var sprite = self.attachAsset('playerCharacter', {
anchorX: 0.5,
anchorY: 0.5
});
var healthBar = self.attachAsset('healthBar', {
anchorX: 0.5,
anchorY: 0.5
});
healthBar.y = -50;
var healthFill = LK.getAsset('healthBar', {
anchorX: 0,
anchorY: 0,
tint: 0x00ff00
});
healthBar.addChild(healthFill);
self.maxHealth = 50;
self.currentHealth = 50;
self.empathy = 50;
self.maxEmpathy = 100;
self.mercyMeter = 0;
self.takeDamage = function (amount) {
self.currentHealth -= amount;
if (self.currentHealth < 0) {
self.currentHealth = 0;
}
var healthPercent = self.currentHealth / self.maxHealth;
healthFill.scaleX = healthPercent;
LK.getSound('combatDamage').play();
};
self.heal = function (amount) {
self.currentHealth += amount;
if (self.currentHealth > self.maxHealth) {
self.currentHealth = self.maxHealth;
}
};
self.gainEmpathy = function (amount) {
self.empathy += amount;
if (self.empathy > self.maxEmpathy) {
self.empathy = self.maxEmpathy;
}
};
self.recordMercyChoice = function (isMercy) {
if (isMercy) {
self.mercyMeter += 1;
storage.mercyScore += 1;
} else {
self.mercyMeter -= 1;
storage.mercyScore -= 1;
}
};
self.isDefeated = function () {
return self.currentHealth <= 0;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x0a0a0a
});
/****
* Game Code
****/
var topBar = LK.getAsset('barTop', {
anchorX: 0,
anchorY: 0
});
topBar.x = 0;
topBar.y = 0;
game.addChild(topBar);
var bottomBar = LK.getAsset('barBottom', {
anchorX: 0,
anchorY: 0
});
bottomBar.x = 0;
bottomBar.y = 2580;
game.addChild(bottomBar);
var gameState = {
currentScene: 'start',
inCombat: false,
inDialogue: false,
sceneCount: 0,
endingType: 'none'
};
var player = null;
var currentNPC = null;
var currentEnemy = null;
var dialogueSystem = null;
var npcList = {};
var combatLog = [];
function initializeGame() {
storage.playCount = (storage.playCount || 0) + 1;
player = game.addChild(new Player());
player.x = 400;
player.y = 1366;
dialogueSystem = game.addChild(new DialogueSystem());
dialogueSystem.x = 1024;
dialogueSystem.y = 1900;
dialogueSystem.alpha = 0;
var angelLabel = new Text2('ANGEL IMPRISONED', {
size: 48,
fill: '#ffd700'
});
angelLabel.anchor.set(0.5, 0.5);
angelLabel.x = 1600;
angelLabel.y = 400;
game.addChild(angelLabel);
var angel = game.addChild(LK.getAsset('angelForm', {
anchorX: 0.5,
anchorY: 0.5
}));
angel.x = 1600;
angel.y = 300;
var machine = game.addChild(LK.getAsset('machineBody', {
anchorX: 0.5,
anchorY: 0.5
}));
machine.x = 1600;
machine.y = 550;
initializeNPCs();
loadStoredRelationships();
LK.playMusic('ambientDrone', {
loop: true
});
startScene('prologue');
}
function initializeNPCs() {
var npcData = {
scientist: {
name: 'Dr. Elena',
dialogueTree: dialogueTreeScientist,
isEnemy: false
},
survivor: {
name: 'Marcus',
dialogueTree: dialogueTreeSurvivor,
isEnemy: false
},
guardian: {
name: 'Sentinel',
dialogueTree: dialogueTreeGuardian,
isEnemy: true
}
};
for (var id in npcData) {
var npc = new NPC();
npc.setData(id, npcData[id].name, npcData[id].dialogueTree, npcData[id].isEnemy);
npcList[id] = npc;
if (!storage.characterRelationships[id]) {
storage.characterRelationships[id] = 0;
}
npc.relationship = storage.characterRelationships[id];
}
}
function loadStoredRelationships() {
for (var id in npcList) {
npcList[id].relationship = storage.characterRelationships[id] || 0;
}
}
function startScene(sceneName) {
gameState.currentScene = sceneName;
gameState.sceneCount += 1;
if (sceneName === 'prologue') {
showPrologue();
} else if (sceneName === 'hub') {
showHub();
} else if (sceneName === 'encounter_scientist') {
showEncounterScientist();
} else if (sceneName === 'encounter_survivor') {
showEncounterSurvivor();
} else if (sceneName === 'encounter_guardian') {
showEncounterGuardian();
} else if (sceneName === 'endgame') {
determineEnding();
}
}
function showPrologue() {
tween(dialogueSystem, {
alpha: 1
}, {
duration: 500
});
var prologueText = 'The world has ended. Heaven sent salvation, but humanity chose slavery. An angel was imprisoned in a machine to harvest divine power. You believe this is wrong. You will free it... or die trying.';
var choices = [{
id: 'start_yes',
text: 'I will free the angel, no matter the cost.',
callback: function callback() {
storage.dialogueChoices['prologue_choice'] = 'start_yes';
LK.setTimeout(function () {
startScene('hub');
}, 500);
}
}, {
id: 'start_question',
text: 'Is the angel really worth humanity\'s suffering?',
callback: function callback() {
storage.dialogueChoices['prologue_choice'] = 'start_question';
gameState.endingType = 'neutral';
LK.setTimeout(function () {
startScene('hub');
}, 500);
}
}];
dialogueSystem.displayDialogue('The Universe', prologueText, choices);
}
function showHub() {
if (gameState.sceneCount === 2) {
var hubText = 'You stand before the machine. Three paths lie before you: seek the scientist who built it, convince the survivor supporting it, or face the guardian protecting it.';
var choices = [{
id: 'hub_scientist',
text: 'Confront Dr. Elena, the machine\'s architect.',
callback: function callback() {
startScene('encounter_scientist');
}
}, {
id: 'hub_survivor',
text: 'Talk to Marcus, who believes the angel should stay imprisoned.',
callback: function callback() {
startScene('encounter_survivor');
}
}, {
id: 'hub_guardian',
text: 'Fight the Sentinel protecting the machine.',
callback: function callback() {
startScene('encounter_guardian');
}
}];
dialogueSystem.displayDialogue('Your Inner Voice', hubText, choices);
}
}
function showEncounterScientist() {
currentNPC = npcList['scientist'];
var scientistText = 'Dr. Elena looks at you with tired eyes. "We did what we had to. This angel... it gives us power to rebuild. Without it, humanity is finished."';
var choices = [{
id: 'sci_mercy',
text: 'I understand your desperation. Let me help find another way.',
callback: function callback() {
player.recordMercyChoice(true);
npcList['scientist'].updateRelationship(2);
storage.dialogueChoices['scientist_choice'] = 'mercy';
combatVsDialogue('scientist', true);
}
}, {
id: 'sci_condemn',
text: 'Your arrogance has damned us all. The angel must be freed.',
callback: function callback() {
player.recordMercyChoice(false);
npcList['scientist'].updateRelationship(-3);
storage.dialogueChoices['scientist_choice'] = 'condemn';
combatVsDialogue('scientist', false);
}
}];
dialogueSystem.displayDialogue('Dr. Elena', scientistText, choices);
}
function showEncounterSurvivor() {
currentNPC = npcList['survivor'];
var survivorText = 'Marcus grips your shoulder. "Everyone I love is alive because of that angel. You want to take that away? What gives you the right?"';
var choices = [{
id: 'surv_mercy',
text: 'Your loved ones matter. But so does freedom. We can find balance.',
callback: function callback() {
player.recordMercyChoice(true);
npcList['survivor'].updateRelationship(2);
storage.dialogueChoices['survivor_choice'] = 'mercy';
combatVsDialogue('survivor', true);
}
}, {
id: 'surv_condemn',
text: 'Love built on slavery is no love at all.',
callback: function callback() {
player.recordMercyChoice(false);
npcList['survivor'].updateRelationship(-3);
storage.dialogueChoices['survivor_choice'] = 'condemn';
combatVsDialogue('survivor', false);
}
}];
dialogueSystem.displayDialogue('Marcus', survivorText, choices);
}
function showEncounterGuardian() {
currentEnemy = new CombatEnemy();
currentEnemy.x = 1600;
currentEnemy.y = 600;
game.addChild(currentEnemy);
currentEnemy.setData('sentinel', 'Sentinel', 40, 8);
var guardianText = 'The mechanical guardian stands motionless, then speaks: "I was programmed to protect. I do not question. Surrender or be destroyed."';
var choices = [{
id: 'guar_talk',
text: 'You\'re more than your programming. You can choose.',
callback: function callback() {
player.recordMercyChoice(true);
storage.dialogueChoices['guardian_choice'] = 'talk';
startCombat('sentinel_dialogue');
}
}, {
id: 'guar_fight',
text: 'Then I\'ll fight you.',
callback: function callback() {
player.recordMercyChoice(false);
storage.dialogueChoices['guardian_choice'] = 'fight';
startCombat('sentinel_combat');
}
}];
dialogueSystem.displayDialogue('Sentinel', guardianText, choices);
}
function combatVsDialogue(npcId, isDialogue) {
gameState.inDialogue = isDialogue;
tween(dialogueSystem, {
alpha: 0
}, {
duration: 300
});
if (isDialogue) {
LK.setTimeout(function () {
resolveEncounter(npcId, true);
}, 400);
} else {
currentEnemy = new CombatEnemy();
currentEnemy.x = 1600;
currentEnemy.y = 600;
game.addChild(currentEnemy);
if (npcId === 'scientist') {
currentEnemy.setData('scientist', 'Dr. Elena', 35, 6);
} else if (npcId === 'survivor') {
currentEnemy.setData('survivor', 'Marcus', 30, 7);
}
startCombat(npcId);
}
}
function startCombat(combatId) {
gameState.inCombat = true;
LK.playMusic('combatTheme', {
loop: true
});
var combatUI = new Text2('COMBAT: ' + currentEnemy.name.toUpperCase(), {
size: 48,
fill: '#ff4444'
});
combatUI.anchor.set(0.5, 0);
combatUI.x = 1024;
combatUI.y = 100;
game.addChild(combatUI);
var attackButton = new DialogueChoice();
attackButton.x = 500;
attackButton.y = 1300;
attackButton.setText('ATTACK (DMG: 8)');
game.addChild(attackButton);
var defendButton = new DialogueChoice();
defendButton.x = 1500;
defendButton.y = 1300;
defendButton.setText('DEFEND');
game.addChild(defendButton);
attackButton.down = function (x, y, obj) {
player.recordMercyChoice(false);
playerAttack();
};
defendButton.down = function (x, y, obj) {
playerDefend();
};
game.combatUI = {
title: combatUI,
attackBtn: attackButton,
defendBtn: defendButton
};
}
function playerAttack() {
if (!gameState.inCombat) return;
var damage = 8 + Math.floor(Math.random() * 4);
currentEnemy.takeDamage(damage);
LK.getSound('combatHit').play();
var damageText = new Text2('-' + damage, {
size: 36,
fill: '#ff0000'
});
damageText.x = currentEnemy.x;
damageText.y = currentEnemy.y - 50;
game.addChild(damageText);
tween(damageText, {
y: damageText.y - 50,
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
damageText.destroy();
}
});
if (currentEnemy.isDefeated()) {
endCombat(true);
} else {
LK.setTimeout(enemyTurn, 600);
}
}
function playerDefend() {
if (!gameState.inCombat) return;
player.heal(5);
var healText = new Text2('+5', {
size: 36,
fill: '#00ff00'
});
healText.x = player.x;
healText.y = player.y - 50;
game.addChild(healText);
tween(healText, {
y: healText.y - 50,
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
healText.destroy();
}
});
LK.setTimeout(enemyTurn, 600);
}
function enemyTurn() {
if (!gameState.inCombat || !currentEnemy) return;
var enemyDamage = currentEnemy.damage + Math.floor(Math.random() * 3);
player.takeDamage(enemyDamage);
var damageText = new Text2('-' + enemyDamage, {
size: 36,
fill: '#ff0000'
});
damageText.x = player.x;
damageText.y = player.y - 50;
game.addChild(damageText);
tween(damageText, {
y: damageText.y - 50,
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
damageText.destroy();
}
});
if (player.isDefeated()) {
endCombat(false);
}
}
function endCombat(playerWon) {
gameState.inCombat = false;
LK.playMusic('ambientDrone', {
loop: true
});
if (game.combatUI) {
game.combatUI.title.destroy();
game.combatUI.attackBtn.destroy();
game.combatUI.defendBtn.destroy();
game.combatUI = null;
}
if (currentEnemy) {
currentEnemy.destroy();
currentEnemy = null;
}
if (playerWon) {
resolveEncounter(null, false);
} else {
LK.showGameOver();
}
}
function resolveEncounter(npcId, throughDialogue) {
tween(dialogueSystem, {
alpha: 1
}, {
duration: 500
});
var resolutionText = '';
if (throughDialogue && npcId === 'scientist') {
resolutionText = 'Dr. Elena looks down, tears forming. "Maybe... maybe there is another way. I\'ll help you free it."';
} else if (!throughDialogue && npcId === 'scientist') {
resolutionText = 'Dr. Elena falls. Your violence has solved the immediate problem, but at what cost?';
} else if (throughDialogue && npcId === 'survivor') {
resolutionText = 'Marcus nods slowly. "You\'re right. Freedom matters more than my fear."';
} else if (!throughDialogue && npcId === 'survivor') {
resolutionText = 'Marcus collapses. You wonder if he was ever your enemy, or just a desperate man.';
} else if (npcId === null) {
resolutionText = 'The path forward clears before you.';
}
var continueChoice = [{
id: 'continue_main',
text: 'Continue',
callback: function callback() {
gameState.sceneCount += 1;
if (gameState.sceneCount >= 4) {
startScene('endgame');
} else {
startScene('hub');
}
}
}];
dialogueSystem.displayDialogue('Narrator', resolutionText, continueChoice);
}
function determineEnding() {
var mercyTotal = storage.mercyScore || 0;
var relationshipSum = 0;
for (var id in storage.characterRelationships) {
relationshipSum += storage.characterRelationships[id];
}
var playCount = storage.playCount || 1;
var prologue = storage.dialogueChoices['prologue_choice'] || '';
var ending = 'neutral';
if (playCount === 1 && mercyTotal <= -5) {
ending = 'bad';
} else if (playCount >= 3 && mercyTotal >= 8 && relationshipSum >= 4) {
ending = 'good';
} else if (playCount >= 5 && mercyTotal >= 10 && relationshipSum >= 6) {
ending = 'secret';
} else if (playCount >= 2 && mercyTotal <= -8) {
ending = 'hellish';
} else {
ending = 'neutral';
}
gameState.endingType = ending;
storage.endingReached = ending;
showEnding(ending);
}
function showEnding(endingType) {
var endingTexts = {
neutral: {
title: 'THE CAGE REMAINS',
text: 'You release the angel. It ascends into ash-grey skies. Humanity survives, changed but unbroken. You wonder if you saved anyone, or simply postponed the inevitable.'
},
good: {
title: 'REDEMPTION',
text: 'Your mercy and choices have won allies. Together, you dismantle the machine peacefully. The angel\'s light spreads, and for the first time in ages, humanity glimpses genuine hope.'
},
bad: {
title: 'ASHES AND SILENCE',
text: 'Your ruthlessness consumed everything. The angel is freed but broken. You stand alone amid the wreckage, victorious and utterly hollow. The universe watches your triumph with indifference.'
},
secret: {
title: 'TRANSCENDENCE',
text: 'Through patience, empathy, and sacrifice across many lives, you\'ve transcended mortal understanding. The angel recognizes you. Reality bends. Is this salvation or damnation?'
},
hellish: {
title: 'DAMNATION',
text: 'Your choices have carved too deep. The machine, damaged beyond control, releases not salvation but annihilation. Fire consumes the world. In hell\'s blazing light, you finally understand what you\'ve done.'
}
};
var ending = endingTexts[endingType] || endingTexts.neutral;
tween(dialogueSystem, {
alpha: 0
}, {
duration: 300
});
var endingTitle = new Text2(ending.title, {
size: 64,
fill: '#ffff00'
});
endingTitle.anchor.set(0.5, 0.5);
endingTitle.x = 1024;
endingTitle.y = 600;
game.addChild(endingTitle);
var endingBody = new Text2(ending.text, {
size: 40,
fill: '#ffffff'
});
endingBody.anchor.set(0.5, 0.5);
endingBody.x = 1024;
endingBody.y = 1000;
endingBody.wordWrap = true;
endingBody.wordWrapWidth = 1800;
game.addChild(endingBody);
var playCountDisplay = new Text2('Playthroughs: ' + (storage.playCount || 1), {
size: 32,
fill: '#888888'
});
playCountDisplay.anchor.set(0.5, 0);
playCountDisplay.x = 1024;
playCountDisplay.y = 2500;
game.addChild(playCountDisplay);
LK.setTimeout(function () {
LK.showYouWin();
}, 3000);
}
var dialogueTreeScientist = {
root: 'start'
};
var dialogueTreeSurvivor = {
root: 'start'
};
var dialogueTreeGuardian = {
root: 'start'
};
game.update = function () {
if (dialogueSystem && dialogueSystem.update) {
dialogueSystem.update();
}
if (player && player.isDefeated() && gameState.inCombat) {
endCombat(false);
}
};
game.down = function (x, y, obj) {
// Main interaction handler
};
game.move = function (x, y, obj) {
// Main move handler
};
initializeGame();
an angel in pain, bleeding and sagging wings. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
A cubish machine that has an angel inside of it that absorbs the angels power but you can't see the angel and the power that comes from the angel is moved by pipes that is on the left side of the machine that comes to the front side and sprays there. On the bottom left side there is an user manual and the machine has cogwheels everywhere. It is hanging from the chains that comes down from the heaven. The chains comes above from clouds. The machine has a double angel wing symbol on the top side. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
A normal human being but has chains on arms and muscular body pixelart. In-Game asset. 2d. High contrast. No shadows