User prompt
Please fix the bug: 'Cannot read properties of undefined (reading 'setText')' in or related to this line: 'languageValue.setText(languageOptions[selectedLanguageIdx]);' Line Number: 1324
User prompt
Please fix the bug: 'Cannot read properties of undefined (reading 'setText')' in or related to this line: 'settingsTitle.setText(t("Settings"));' Line Number: 1308
User prompt
there is a problem with the settings, when the language is changed, all the text in the game should be written in that language, and when we lower the percentage of the sound, the sound should be turned down, and the sound should be turned off at 0% at the end.
User prompt
Please fix the bug: 'Cannot read properties of undefined (reading 'length')' in or related to this line: 'if (typeof storage.selectedLanguageIdx === "number" && storage.selectedLanguageIdx >= 0 && storage.selectedLanguageIdx < languageOptions.length) {' Line Number: 1101
User prompt
Make the settings work
User prompt
When you enter the settings section of the game, there will be a language option (“Turkish, German, English, French and Russian”) and by default, English will come first. another setting is the sound setting, where we can adjust the sounds in the game.
User prompt
there will be a music in the main menu section, we will play the music of the game here, when we start the game, the music will end and the game will start.
User prompt
you didn't create that cutscene and don't start the next day without me starting it, the player will see those % stats and money
User prompt
At the end of the day, let him transfer us to an intermediate menu (how many people we passed, how many people we passed correctly and how many people we passed incorrectly), write them in percentages and determine an average percentage for us, and let us get the money that corresponds to the percentage he determined for us.
User prompt
It gives an error after 10 o'clock, fix this error
User prompt
Please fix the bug: 'Script error.' in or related to this line: 'storage.dayStats = shallowStats;' Line Number: 263
User prompt
If you do something wrong, it gives an error. Can you fix that error?
User prompt
Please fix the bug: 'Script error.' in or related to this line: 'storage.dayStats = shallowStats;' Line Number: 233
User prompt
Please fix the bug: 'Script error.' in or related to this line: 'storage.dayStats = shallowStats;' Line Number: 231 ↪💡 Consider importing and using the following plugins: @upit/storage.v1
User prompt
Please fix the bug: 'Uncaught TypeError: storage.set is not a function' in or related to this line: 'storage.set("dayStats", shallowStats);' Line Number: 231 ↪💡 Consider importing and using the following plugins: @upit/storage.v1
User prompt
Please fix the bug: 'storage.get is not a function' in or related to this line: 'var stats = storage.get("dayStats");' Line Number: 240 ↪💡 Consider importing and using the following plugins: @upit/storage.v1
User prompt
Fix any errors that may occur
User prompt
Please fix the bug: 'Script error.' in or related to this line: 'return {' Line Number: 223
User prompt
Please fix the bug: 'Script error.' in or related to this line: 'storage.dayStats = dayStats.slice();' Line Number: 220 ↪💡 Consider importing and using the following plugins: @upit/storage.v1
User prompt
Please fix the bug: 'Script error.' in or related to this line: 'storage.dayStats = Array.isArray(dayStats) ? dayStats.slice() : [];' Line Number: 218 ↪💡 Consider importing and using the following plugins: @upit/storage.v1
User prompt
Please fix the bug: 'Script error.' in or related to this line: 'storage.dayStats = dayStats;' Line Number: 218 ↪💡 Consider importing and using the following plugins: @upit/storage.v1
User prompt
Please fix the bug: 'Script error.' in or related to this line: 'storage.dayStats = dayStats;' Line Number: 217 ↪💡 Consider importing and using the following plugins: @upit/storage.v1
User prompt
Please fix the bug: 'Script error.' in or related to this line: 'storage.dayStats = dayStats;' Line Number: 217 ↪💡 Consider importing and using the following plugins: @upit/storage.v1
User prompt
Please fix the bug: 'Script error.' in or related to this line: 'storage.dayStats = dayStats;' Line Number: 217 ↪💡 Consider importing and using the following plugins: @upit/storage.v1
User prompt
Please fix the bug: 'Script error.' in or related to this line: 'storage.dayStats = dayStats;' Line Number: 217 ↪💡 Consider importing and using the following plugins: @upit/storage.v1
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
highScore: 0
});
/****
* Classes
****/
// Document class: represents a single document (passport, permit, etc.)
var Document = Container.expand(function () {
var self = Container.call(this);
// docType: 'passport', 'permit', etc.
self.docType = '';
self.fields = {}; // e.g. {name: '...', dob: '...', ...}
self.isForged = false;
// Visual
self.bg = null;
self.textFields = [];
// Helper to set up document
self.setup = function (docType, fields, isForged) {
self.docType = docType;
self.fields = fields;
self.isForged = isForged;
// Remove previous
if (self.bg) self.bg.destroy();
for (var i = 0; i < self.textFields.length; i++) self.textFields[i].destroy();
self.textFields = [];
// Background
self.bg = self.attachAsset(docType, {
anchorX: 0,
anchorY: 0
});
// Title
var docTitle = docType.toUpperCase();
if (typeof t === "function" && LOCALIZATION[docTitle]) docTitle = t(docTitle);
var title = new Text2(docTitle, {
size: 60,
fill: 0x333333
});
title.x = 40;
title.y = 28;
self.addChild(title);
self.textFields.push(title);
// Fields
var y = 120;
for (var key in fields) {
var value = fields[key];
var fieldLabel = key.charAt(0).toUpperCase() + key.slice(1);
if (typeof t === "function" && LOCALIZATION[fieldLabel + ":"]) fieldLabel = t(fieldLabel + ":");
var txt = new Text2(fieldLabel + ": " + value, {
size: 52,
fill: 0x222222
});
txt.x = 40;
txt.y = y;
self.addChild(txt);
self.textFields.push(txt);
y += 70;
}
// If forged, add a subtle red border
if (isForged) {
self.bg.tint = 0xffaaaa;
} else {
self.bg.tint = 0xffffff;
}
};
return self;
});
// Traveler class: represents a person with documents
var Traveler = Container.expand(function () {
var self = Container.call(this);
// Person visual
var PERSON_ASSETS = [{
id: 'person',
gender: 'male'
}, {
id: 'person2',
gender: 'male'
}, {
id: 'person3',
gender: 'male'
}, {
id: 'person4',
gender: 'male'
}, {
id: 'person5',
gender: 'male'
}, {
id: 'female1',
gender: 'female'
}, {
id: 'female2',
gender: 'female'
}, {
id: 'female3',
gender: 'female'
}, {
id: 'female4',
gender: 'female'
}];
var personAssetObj = PERSON_ASSETS[Math.floor(Math.random() * PERSON_ASSETS.length)];
self.gender = personAssetObj.gender;
var person = self.attachAsset(personAssetObj.id, {
anchorX: 0.5,
anchorY: 1
});
person.y = 0;
// Documents (passport, permit, etc.)
self.documents = []; // Will be filled by game
// Name label background
self.nameBg = LK.getAsset('bg_name_section', {
anchorX: 0.5,
anchorY: 0
});
self.nameBg.x = 0;
self.nameBg.y = -person.height - 50;
self.addChild(self.nameBg);
// Name label
self.nameTxt = new Text2('', {
size: 60,
fill: 0x222222
});
self.nameTxt.anchor.set(0.5, 0);
self.nameTxt.x = 0;
self.nameTxt.y = -person.height - 40;
self.addChild(self.nameTxt);
// Helper to show/hide documents
self.showDocuments = function (show) {
for (var i = 0; i < self.documents.length; i++) {
self.documents[i].visible = !!show;
}
};
// Helper to destroy all docs
self.destroyDocuments = function () {
for (var i = 0; i < self.documents.length; i++) {
self.documents[i].destroy();
}
self.documents = [];
// Remove extra info text if present
if (self.extraInfoTxt) {
self.extraInfoTxt.destroy();
self.extraInfoTxt = null;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0xede5d0
});
/****
* Game Code
****/
// Toggleable main menu button assets
// Female character assets
// Document backgrounds
// Game state variables
// Add background images for changeable backgrounds
// Sound for next button
// Sound for traveler arrival
function _typeof(o) {
"@babel/helpers - typeof";
return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) {
return typeof o;
} : function (o) {
return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
}, _typeof(o);
}
var currentTraveler = null;
var travelersProcessed = 0;
var correctDecisions = 0;
var wrongDecisions = 0;
var day = 1;
var rules = [];
var travelerQueue = [];
var isDecisionActive = false;
var lastDecision = null;
var lastFeedbackTimeout = null;
var scoreTxt = null;
var dayTxt = null;
var ruleTxt = null;
var table = null;
var approveBtn = null;
var denyBtn = null;
var feedbackTxt = null;
// --- Daily statistics and money tracking ---
var dayStats = []; // Array of {day, processed, correct, wrong, money}
var money = 0;
// Helper: Save stats to persistent storage
function saveDayStats() {
// Use storage API to persist dayStats and money
// Use storage API to persist dayStats and money (must be shallow arrays/objects)
if (Array.isArray(dayStats)) {
// Store a shallow copy to avoid reference issues, but storage only supports shallow arrays of literals
var shallowStats = dayStats.length ? dayStats.map(function (item) {
// Only store shallow objects with literal values
return {
day: item.day,
processed: item.processed,
correct: item.correct,
wrong: item.wrong,
money: item.money
};
}) : [];
// Defensive: ensure shallowStats is always an array of shallow objects
if (Array.isArray(shallowStats)) {
// Check that every item is a plain object with only literal values
var valid = true;
for (var i = 0; i < shallowStats.length; i++) {
var item = shallowStats[i];
if (_typeof(item) !== "object" || item === null || Array.isArray(item)) {
valid = false;
break;
}
// Check all properties are primitives (string, number, boolean, undefined, null)
for (var key in item) {
var v = item[key];
if (typeof v !== "string" && typeof v !== "number" && typeof v !== "boolean" && v !== undefined && v !== null) {
valid = false;
break;
}
}
if (!valid) break;
}
if (valid && Array.isArray(shallowStats)) {
try {
storage.dayStats = shallowStats;
} catch (e) {
storage.dayStats = [];
}
} else {
storage.dayStats = [];
}
} else {
storage.dayStats = [];
}
} else {
storage.dayStats = [];
}
storage.money = typeof money === "number" ? money : 0;
}
// Helper: Load stats from persistent storage
function loadDayStats() {
// Use storage API to load dayStats and money
var stats = storage.dayStats;
var m = storage.money;
if (Array.isArray(stats)) {
dayStats = stats.slice();
} else {
dayStats = [];
}
if (typeof m === "number") {
money = m;
} else {
money = 0;
}
}
loadDayStats();
// Names and data for random generation
var NAMES = ["Hans Müller", "Erika Schmidt", "Karl Weber", "Anna Fischer", "Otto Becker", "Liselotte Braun", "Friedrich Wagner", "Greta Hoffmann", "Johann Bauer", "Marta Klein", "Wilhelm Richter", "Sophie Keller", "Heinrich Vogel", "Paula Neumann", "Emil Schuster", "Clara Busch", "Maximilian König", "Helene Frank", "Bruno Schäfer", "Rosa Winkler", "Paul Zimmermann", "Maria Lorenz", "Ludwig Hartmann", "Ida Simon", "Franz Lehmann", "Emma Wolf", "Gustav Peters", "Charlotte Roth", "Walter Krause", "Luise Berger", "Rudolf Fuchs", "Elsa Brandt"];
var CITIES = ["Berlin", "Munich", "Hamburg", "Cologne", "Frankfurt", "Leipzig", "Dresden", "Stuttgart"];
var NATIONALITIES = ["German", "Austrian", "Polish", "Czech", "French", "Dutch"];
// Rules per day
var RULES_BY_DAY = [
// Day 1
[{
desc: "Allow only travelers with a valid German passport.",
check: function check(traveler) {
var pass = getDoc(traveler, 'passport');
return pass && pass.fields.nationality === "German" && !pass.isForged;
}
}],
// Day 2
[{
desc: "Allow only travelers with a valid German passport.",
check: function check(traveler) {
var pass = getDoc(traveler, 'passport');
return pass && pass.fields.nationality === "German" && !pass.isForged;
}
}, {
desc: "Entry permit required for non-Germans.",
check: function check(traveler) {
var pass = getDoc(traveler, 'passport');
if (!pass) return false;
if (pass.fields.nationality === "German") return true;
var permit = getDoc(traveler, 'permit');
return permit && !permit.isForged;
}
}],
// Day 3
[{
desc: "Allow only travelers with a valid German passport.",
check: function check(traveler) {
var pass = getDoc(traveler, 'passport');
return pass && pass.fields.nationality === "German" && !pass.isForged;
}
}, {
desc: "Entry permit required for non-Germans.",
check: function check(traveler) {
var pass = getDoc(traveler, 'passport');
if (!pass) return false;
if (pass.fields.nationality === "German") return true;
var permit = getDoc(traveler, 'permit');
return permit && !permit.isForged;
}
}, {
desc: "Deny travelers from Poland.",
check: function check(traveler) {
var pass = getDoc(traveler, 'passport');
return pass && pass.fields.nationality !== "Polish";
}
}]];
// Helper: get document by type
function getDoc(traveler, docType) {
for (var i = 0; i < traveler.documents.length; i++) {
if (traveler.documents[i].docType === docType) return traveler.documents[i];
}
return null;
}
// Helper: generate a random traveler
function generateTraveler(day) {
var t = new Traveler();
// Name
var name = NAMES[Math.floor(Math.random() * NAMES.length)];
t.nameTxt.setText(name);
// Gender from asset
var gender = t.gender || (Math.random() < 0.5 ? "male" : "female");
// Passport
var nationality = NATIONALITIES[Math.floor(Math.random() * NATIONALITIES.length)];
var city = CITIES[Math.floor(Math.random() * CITIES.length)];
var dob = 1900 + Math.floor(Math.random() * 30) + "-" + (1 + Math.floor(Math.random() * 12)) + "-" + (1 + Math.floor(Math.random() * 28));
var passportFields = {
name: name,
nationality: nationality,
city: city,
dob: dob,
gender: gender
};
// 10% chance of forged passport from day 2+
var forgedPassport = day >= 2 && Math.random() < 0.1;
var passport = new Document();
passport.setup('passport', passportFields, forgedPassport);
passport.x = -350;
passport.y = 100;
t.addChild(passport);
t.documents.push(passport);
// Permit (for non-Germans, 50% chance from day 2+)
var hasPermit = nationality !== "German" && day >= 2 && Math.random() < 0.5;
if (hasPermit) {
var permitFields = {
name: name,
nationality: nationality,
valid: "Yes",
gender: gender
};
// 10% chance of forged permit from day 3+
var forgedPermit = day >= 3 && Math.random() < 0.1;
var permit = new Document();
permit.setup('permit', permitFields, forgedPermit);
permit.x = 200;
permit.y = 100;
t.addChild(permit);
t.documents.push(permit);
}
// Hide docs initially
t.showDocuments(false);
return t;
}
// Helper: get today's rules
function getRulesForDay(day) {
if (day - 1 < RULES_BY_DAY.length) {
return RULES_BY_DAY[day - 1];
}
// After day 3, repeat day 3 rules
return RULES_BY_DAY[RULES_BY_DAY.length - 1];
}
// Helper: check if traveler should be allowed
function isTravelerAllowed(traveler) {
var rulesToday = getRulesForDay(day);
for (var i = 0; i < rulesToday.length; i++) {
if (!rulesToday[i].check(traveler)) return false;
}
return true;
}
// Helper: show feedback
function showFeedback(text, color) {
if (feedbackTxt) {
// If text matches a known feedback, localize it
var localizedText = text;
if (text === "Correct!") localizedText = t("Correct!");else if (text === "Mistake!") localizedText = t("Mistake!");else if (text === "End of work day!") localizedText = t("End of work day!");else if (text && text.indexOf("Day ") === 0 && text.indexOf("begins") > 0) {
// "Day X begins. New rules!"
var n = parseInt(text.split(" ")[1]);
localizedText = t("Day begins. New rules!", {
n: n
});
} else if (text && text.indexOf("Day Stats:") === 0) {
// Localize summary popup
var lines = text.split("\n");
if (lines.length > 0) lines[0] = t("Day Stats:");
for (var i = 1; i < lines.length; i++) {
if (lines[i].indexOf("correct") > -1) lines[i] = lines[i].replace("correct", t("Correct"));
if (lines[i].indexOf("wrong") > -1) lines[i] = lines[i].replace("wrong", t("Wrong"));
if (lines[i].indexOf("Money:") > -1) lines[i] = lines[i].replace("Money:", t("Total Money") + ":");
}
localizedText = lines.join("\n");
}
feedbackTxt.setText(localizedText);
// Use setStyle to change fill color, supporting both hex and string
if (typeof color === "string" || typeof color === "number") {
feedbackTxt.setStyle({
fill: color
});
}
feedbackTxt.visible = true;
if (lastFeedbackTimeout) LK.clearTimeout(lastFeedbackTimeout);
lastFeedbackTimeout = LK.setTimeout(function () {
feedbackTxt.visible = false;
}, 1200);
}
}
// Helper: next traveler
function nextTraveler() {
if (currentTraveler) {
currentTraveler.destroyDocuments();
currentTraveler.destroy();
currentTraveler = null;
}
isDecisionActive = false;
lastDecision = null;
// End of day after 10 travelers
if (travelersProcessed > 0 && travelersProcessed % 10 === 0) {
// If the clock has already reached or passed the end of the work day, do not proceed to next day/traveler
if (typeof clockMinutes !== "undefined" && typeof clockEndMinutes !== "undefined" && clockMinutes >= clockEndMinutes) {
return;
}
// Calculate wrong answers for the day
var wrong = typeof wrongDecisions === "number" ? wrongDecisions : 10 - correctDecisions;
// Calculate percentages
var processed = travelersProcessed;
var correct = correctDecisions;
var wrongCount = wrong;
var percentCorrect = processed > 0 ? Math.round(correct / processed * 100) : 0;
var percentWrong = processed > 0 ? Math.round(wrongCount / processed * 100) : 0;
// Calculate money for the day based on percentCorrect (e.g. 100% = 10, 0% = 0, linear)
var dayMoney = Math.round(percentCorrect * 0.1); // 10 RM max per day
money += dayMoney;
// Record stats for the day
dayStats.push({
day: day,
processed: processed,
correct: correct,
wrong: wrongCount,
percentCorrect: percentCorrect,
percentWrong: percentWrong,
money: money
});
saveDayStats();
// --- Show persistent end-of-day summary popup and require player to start next day manually ---
if (typeof endOfDayPopup !== "undefined" && endOfDayPopup && endOfDayPopup.parent) {
endOfDayPopup.parent.removeChild(endOfDayPopup);
}
var endOfDayPopup = new Container();
// Background
var popupBg = LK.getAsset('bg_info_popup', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2
});
popupBg.width = 900;
popupBg.height = 900;
endOfDayPopup.addChild(popupBg);
// Title
var popupTitle = new Text2("Day " + day + " Summary", {
size: 90,
fill: 0x222222
});
popupTitle.anchor.set(0.5, 0);
popupTitle.x = 2048 / 2;
popupTitle.y = 2732 / 2 - 350;
endOfDayPopup.addChild(popupTitle);
// Stats
var summaryStr = "";
summaryStr += "Processed: " + processed + "\n";
summaryStr += "Correct: " + correct + " (" + percentCorrect + "%)\n";
summaryStr += "Wrong: " + wrongCount + " (" + percentWrong + "%)\n";
summaryStr += "Day Score: " + percentCorrect + "%\n";
summaryStr += "Money earned: " + dayMoney + " RM\n";
summaryStr += "Total Money: " + money + " RM";
var popupStats = new Text2(summaryStr, {
size: 60,
fill: 0x3333cc
});
popupStats.anchor.set(0.5, 0);
popupStats.x = 2048 / 2;
popupStats.y = 2732 / 2 - 180;
popupStats.width = 800;
popupStats.setStyle({
wordWrap: true,
wordWrapWidth: 800
});
endOfDayPopup.addChild(popupStats);
// Continue button
var continueBtn = LK.getAsset('stamp_next', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2 + 250
});
endOfDayPopup.addChild(continueBtn);
var continueTxt = new Text2("Next Day", {
size: 80,
fill: 0xffffff
});
continueTxt.anchor.set(0.5, 0.5);
continueTxt.x = continueBtn.width / 2;
continueTxt.y = continueBtn.height / 2;
continueBtn.addChild(continueTxt);
// Add popup to game
game.addChild(endOfDayPopup);
// Block gameplay UI while popup is visible
if (approveBtn) approveBtn.visible = false;
if (denyBtn) denyBtn.visible = false;
if (nextBtn) nextBtn.visible = false;
if (arrowBtn) arrowBtn.visible = false;
if (rulesPanel) rulesPanel.visible = false;
if (table) table.visible = false;
if (scoreTxt) scoreTxt.visible = false;
if (clockTxt) clockTxt.visible = false;
if (dayTxt) dayTxt.visible = false;
if (feedbackTxt) feedbackTxt.visible = false;
// Handler for continue button
var popupHandler = function popupHandler(x, y, obj) {
var left = continueBtn.x - continueBtn.width / 2,
right = continueBtn.x + continueBtn.width / 2;
var top = continueBtn.y - continueBtn.height / 2,
bottom = continueBtn.y + continueBtn.height / 2;
if (x >= left && x <= right && y >= top && y <= bottom) {
// Remove popup
if (endOfDayPopup && endOfDayPopup.parent) endOfDayPopup.parent.removeChild(endOfDayPopup);
// Restore gameplay UI
if (approveBtn) approveBtn.visible = true;
if (denyBtn) denyBtn.visible = true;
if (nextBtn) nextBtn.visible = true;
if (arrowBtn) arrowBtn.visible = true;
if (table) table.visible = true;
if (scoreTxt) scoreTxt.visible = true;
if (clockTxt) clockTxt.visible = true;
if (dayTxt) dayTxt.visible = true;
// Advance day and reset state
day++;
travelersProcessed = 0;
correctDecisions = 0;
wrongDecisions = 0;
updateDayAndRules();
resetClock();
// Show new day feedback
showFeedback("Day " + day + " begins. New rules!", "#3333cc");
LK.setTimeout(function () {
nextTraveler();
}, 1200);
// Restore gameplay handler
game.down = gameplayDownHandler;
}
};
// Override game.down to only handle popup
game.down = popupHandler;
return;
}
// Generate new traveler
currentTraveler = generateTraveler(day);
// Start off-screen right
currentTraveler.x = 2048 + 400;
currentTraveler.y = 1200;
currentTraveler.alpha = 1;
game.addChild(currentTraveler);
// Play traveler arrival sound after 1 second to avoid overlap with Next button sound
LK.setTimeout(function () {
LK.getSound('traveler_arrive').play();
}, 1000);
// Animate in to center
tween(currentTraveler, {
x: 2048 / 2
}, {
duration: 600,
easing: tween.cubicOut,
onFinish: function onFinish() {
// Show docs after short delay
LK.setTimeout(function () {
if (currentTraveler) currentTraveler.showDocuments(true);
isDecisionActive = true;
}, 400);
}
});
}
// Helper: update day and rules display
function updateDayAndRules() {
if (dayTxt) dayTxt.setText("Day " + day);
if (ruleTxt) {
var rulesToday = getRulesForDay(day);
var ruleStr = "";
for (var i = 0; i < rulesToday.length; i++) {
ruleStr += i + 1 + ". " + rulesToday[i].desc + "\n";
}
ruleTxt.setText(ruleStr);
}
// Change background image based on day
if (typeof bgNode !== "undefined" && typeof bgImages !== "undefined") {
// Only one background now: office
var idx = 0;
if (bgNode.assetId !== bgImages[idx]) {
var newBg = LK.getAsset(bgImages[idx], {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
// Replace old background node
if (bgNode.parent) bgNode.parent.removeChild(bgNode);
bgNode = newBg;
game.addChildAt(bgNode, 0); // Add at bottom
}
}
// No background for rules/tasks section; just update ruleTxt text
}
// Approve/deny button handlers
function handleDecision(approved) {
if (!isDecisionActive || !currentTraveler) return;
isDecisionActive = false;
travelersProcessed++;
var allowed = isTravelerAllowed(currentTraveler);
var correct = approved === allowed;
if (correct) {
correctDecisions++;
showFeedback("Correct!", "#4caf50");
LK.setScore(LK.getScore() + 1);
if (scoreTxt) scoreTxt.setText(LK.getScore());
money += 5; // +5 for correct
} else {
wrongDecisions = (typeof wrongDecisions === "number" ? wrongDecisions : 0) + 1;
showFeedback("Mistake!", "#d32f2f");
// Flash screen red for mistake
LK.effects.flashScreen(0xff0000, 500);
money -= 2; // -2 for wrong
}
// Save stats after each decision
saveDayStats();
// Animate traveler out
// Approved: exit left, Denied: exit right
var targetX = approved ? -400 : 2048 + 400;
tween(currentTraveler, {
x: targetX,
alpha: 0
}, {
duration: 600,
easing: tween.cubicOut,
onFinish: function onFinish() {
// Do not call nextTraveler automatically; wait for Next button
// Traveler will be removed, but nextTraveler is only called by Next button
}
});
}
// Add background image node (behind everything)
var bgImages = ['bg_office'];
var bgMenuImage = 'bg_menu';
var bgImageIdx = 0;
var bgNode = LK.getAsset(bgImages[bgImageIdx], {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
game.addChild(bgNode);
// Add a background node for the main menu (hidden by default)
var bgMenuNode = LK.getAsset(bgMenuImage, {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
bgMenuNode.visible = false;
game.addChildAt(bgMenuNode, 0);
// Table
table = LK.getAsset('table', {
anchorX: 0.5,
anchorY: 0,
x: 2048 / 2,
y: 1700
});
game.addChild(table);
// Score display
scoreTxt = new Text2("0", {
size: 100,
fill: 0x222222
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Clock display (top left, avoid 100x100 area)
var clockTxt = new Text2("09:00", {
size: 90,
fill: 0x333366
});
clockTxt.anchor.set(0, 0);
clockTxt.x = 120;
clockTxt.y = 20;
LK.gui.top.addChild(clockTxt);
// Clock state
var clockMinutes = 0; // 0 = 9:00, max = 290 (13:50)
var clockStartMinutes = 0; // 9:00
var clockEndMinutes = 290; // 13:50
var clockInterval = null;
// Helper to update clock text
function updateClockText() {
var totalMinutes = clockStartMinutes + clockMinutes;
var hour = 9 + Math.floor(totalMinutes / 60);
var min = totalMinutes % 60;
var minStr = min < 10 ? "0" + min : "" + min;
clockTxt.setText(hour + ":" + minStr);
}
// Helper to reset clock at start of day
function resetClock() {
clockMinutes = 0;
updateClockText();
if (clockInterval) LK.clearInterval(clockInterval);
clockInterval = LK.setInterval(function () {
// Always advance clock, regardless of isDecisionActive or currentTraveler
clockMinutes += 2; // Advance 2 minutes per interval
if (clockMinutes > clockEndMinutes) clockMinutes = clockEndMinutes;
updateClockText();
// End of work day
if (clockMinutes >= clockEndMinutes) {
showFeedback("End of work day!", "#3333cc");
LK.setTimeout(function () {
LK.showGameOver();
}, 1200);
LK.clearInterval(clockInterval);
}
}, 1000); // 1 second per traveler (simulate time passing)
}
// Do NOT start clock here; only start when Play is pressed
// Day display
dayTxt = new Text2("Day 1", {
size: 70,
fill: 0x333366
});
dayTxt.anchor.set(0.5, 0);
dayTxt.y = 120;
LK.gui.top.addChild(dayTxt);
// --- Remove background for rules/tasks section and add a toggleable panel with arrow button ---
// Arrow button asset (simple triangle shape, white with black border, right-pointing)
// Create the arrow button at the top right (but not in the top left 100x100 area)
var arrowBtn = LK.getAsset('arrow_btn', {
anchorX: 1,
anchorY: 0,
x: 2048 - 40,
y: 40
});
arrowBtn.width = 100;
arrowBtn.height = 100;
game.addChild(arrowBtn);
// Draw a right-pointing arrow using a Text2 overlay (since we can't draw lines)
var arrowTxt = new Text2("▶", {
size: 80,
fill: 0x222222
});
arrowTxt.anchor.set(0.5, 0.5);
arrowTxt.x = arrowBtn.width / 2;
arrowTxt.y = arrowBtn.height / 2;
arrowBtn.addChild(arrowTxt);
// Rules panel container (hidden by default)
var rulesPanel = new Container();
rulesPanel.visible = false;
game.addChild(rulesPanel);
// Panel background (white box, no border)
var rulesPanelBg = LK.getAsset('bg_info_popup', {
anchorX: 0,
anchorY: 0,
x: 2048 - 800 - 40,
y: 160
});
rulesPanelBg.width = 800;
rulesPanelBg.height = 600;
rulesPanel.addChild(rulesPanelBg);
// Rules text (tasks) inside the panel
ruleTxt = new Text2("Tasks will appear here.\nPress the arrow to view the current day's rules.", {
size: 48,
fill: 0x444444
});
ruleTxt.anchor.set(0, 0);
ruleTxt.x = rulesPanelBg.x + 40;
ruleTxt.y = rulesPanelBg.y + 30;
ruleTxt.width = rulesPanelBg.width - 80;
ruleTxt.setStyle({
wordWrap: true,
wordWrapWidth: ruleTxt.width
});
rulesPanel.addChild(ruleTxt);
// Track panel state
var rulesPanelOpen = false;
// Helper to open/close the rules panel and update arrow direction
function setRulesPanel(open) {
rulesPanel.visible = open;
rulesPanelOpen = open;
arrowTxt.setText(open ? "✖" : "▶");
}
setRulesPanel(false);
// Feedback text
feedbackTxt = new Text2("", {
size: 90,
fill: 0x4CAF50
});
feedbackTxt.anchor.set(0.5, 0.5);
feedbackTxt.x = 2048 / 2;
feedbackTxt.y = 400;
feedbackTxt.visible = false;
LK.gui.top.addChild(feedbackTxt);
// Approve button
approveBtn = LK.getAsset('stamp_approve', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2 + 350,
y: 2450 //{2G} // moved lower
});
game.addChild(approveBtn);
// Remove old denyBtn if it exists
if (denyBtn && denyBtn.parent) {
denyBtn.parent.removeChild(denyBtn);
denyBtn = null;
}
// Create a new deny button and ensure it's visible
denyBtn = LK.getAsset('stamp_deny', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2 - 350,
y: 2450
});
denyBtn.visible = true;
game.addChild(denyBtn);
// Next Traveler button
var nextBtn = LK.getAsset('stamp_next', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2600 // moved lower to match new button row
});
game.addChild(nextBtn);
// Button event handlers
game.down = function (x, y, obj) {
// --- Arrow button for rules/tasks panel ---
var arrowLeft = arrowBtn.x - arrowBtn.width;
var arrowRight = arrowBtn.x;
var arrowTop = arrowBtn.y;
var arrowBottom = arrowBtn.y + arrowBtn.height;
if (x >= arrowLeft && x <= arrowRight && y >= arrowTop && y <= arrowBottom) {
setRulesPanel(!rulesPanelOpen);
return;
}
// If rules panel is open, clicking anywhere outside the panel closes it
if (rulesPanelOpen) {
var panelLeft = rulesPanelBg.x;
var panelRight = rulesPanelBg.x + rulesPanelBg.width;
var panelTop = rulesPanelBg.y;
var panelBottom = rulesPanelBg.y + rulesPanelBg.height;
if (!(x >= panelLeft && x <= panelRight && y >= panelTop && y <= panelBottom)) {
setRulesPanel(false);
}
return;
}
// Approve
var approveLeft = approveBtn.x - approveBtn.width / 2;
var approveRight = approveBtn.x + approveBtn.width / 2;
var approveTop = approveBtn.y - approveBtn.height / 2;
var approveBottom = approveBtn.y + approveBtn.height / 2;
if (x >= approveLeft && x <= approveRight && y >= approveTop && y <= approveBottom) {
handleDecision(true);
return;
}
// Deny
var denyLeft = denyBtn.x - denyBtn.width / 2;
var denyRight = denyBtn.x + denyBtn.width / 2;
var denyTop = denyBtn.y - denyBtn.height / 2;
var denyBottom = denyBtn.y + denyBtn.height / 2;
if (x >= denyLeft && x <= denyRight && y >= denyTop && y <= denyBottom) {
handleDecision(false);
return;
}
// Next button
var nextLeft = 2048 / 2 - approveBtn.width / 2;
var nextRight = 2048 / 2 + approveBtn.width / 2;
var nextTop = 2600 - approveBtn.height / 2;
var nextBottom = 2600 + approveBtn.height / 2;
if (x >= nextLeft && x <= nextRight && y >= nextTop && y <= nextBottom) {
// Only allow next if not in the middle of a decision animation
if (!isDecisionActive) {
// Play next button sound
LK.getSound('next_btn').play();
nextTraveler();
}
return;
}
// Show/hide docs and show extra info on traveler tap
if (currentTraveler) {
// Manual bounds check for the person asset (centered at currentTraveler.x, bottom at currentTraveler.y)
var personAsset = currentTraveler.children[0]; // person asset is always first child
if (personAsset) {
var left = currentTraveler.x - personAsset.width / 2;
var right = currentTraveler.x + personAsset.width / 2;
var top = currentTraveler.y - personAsset.height;
var bottom = currentTraveler.y;
if (x >= left && x <= right && y >= top && y <= bottom) {
// Show extra info popup (purpose, occupation, duration) as Q&A conversation record
if (!currentTraveler.extraInfoTxt) {
// Generate if not present
var PURPOSES = ["I am here to visit family.", "I am traveling for work.", "I am just passing through.", "I am here for business.", "I am here for a holiday.", "I am here to see a doctor.", "I am here to study.", "I am here to help relatives.", "I am here for a funeral.", "I am here for a wedding."];
var OCCUPATIONS = ["I am a farmer.", "I am a teacher.", "I am a merchant.", "I am a soldier.", "I am a doctor.", "I am a student.", "I am a tailor.", "I am a baker.", "I am a carpenter.", "I am a musician."];
var DURATIONS = ["I will stay for 2 days.", "I am here for a week.", "Just one night.", "I will stay for 3 days.", "I am here for a month.", "Only a few hours.", "I will stay for 10 days.", "I am here for the season.", "I will leave tomorrow.", "I am not sure yet."];
var purpose = PURPOSES[Math.floor(Math.random() * PURPOSES.length)];
var occupation = OCCUPATIONS[Math.floor(Math.random() * OCCUPATIONS.length)];
var duration = DURATIONS[Math.floor(Math.random() * DURATIONS.length)];
// Format as Q&A conversation record
var infoStr = (typeof t === "function" ? t("Conversation record:") : "Conversation record:") + "\n" + (typeof t === "function" ? t("Q: Why did you come?") : "Q: Why did you come?") + "\n" + (typeof t === "function" ? t("A: ") : "A: ") + purpose + "\n\n" + (typeof t === "function" ? t("Q: What do you do?") : "Q: What do you do?") + "\n" + (typeof t === "function" ? t("A: ") : "A: ") + occupation + "\n\n" + (typeof t === "function" ? t("Q: How long will you stay?") : "Q: How long will you stay?") + "\n" + (typeof t === "function" ? t("A: ") : "A: ") + duration;
// Add a white background box for the info popup using new asset
var infoBg = new Container();
var infoTxt = new Text2(infoStr, {
size: 48,
fill: 0x222222,
align: "left"
});
infoTxt.anchor.set(0, 0);
infoTxt.x = 40;
infoTxt.y = 30;
// Dynamically size the background to fit the text, with padding
var paddingX = 60;
var paddingY = 40;
var minWidth = 700;
var minHeight = 420;
var bgWidth = Math.max(minWidth, infoTxt.width + paddingX * 2);
var bgHeight = Math.max(minHeight, infoTxt.height + paddingY * 2);
var bgRect = LK.getAsset('bg_info_popup', {
anchorX: 0,
anchorY: 0
});
bgRect.width = bgWidth;
bgRect.height = bgHeight;
infoBg.addChild(bgRect);
infoTxt.x = paddingX;
infoTxt.y = paddingY;
infoBg.addChild(infoTxt);
// Place to the right of the character, vertically aligned with the top of the person
infoBg.x = currentTraveler.x + personAsset.width / 2 + 40;
infoBg.y = currentTraveler.y - personAsset.height;
infoBg.visible = true;
currentTraveler.extraInfoTxt = infoBg;
game.addChild(infoBg);
} else {
// Toggle visibility
currentTraveler.extraInfoTxt.visible = !currentTraveler.extraInfoTxt.visible;
}
// Hide docs if showing info, show docs if hiding info
var showingInfo = currentTraveler.extraInfoTxt && currentTraveler.extraInfoTxt.visible;
currentTraveler.showDocuments(!showingInfo);
}
}
}
};
// Update function (not much needed for MVP)
game.update = function () {
// Win condition: survive 5 days
if (day > 5) {
// Show summary popup with stats and money
var summary = "Day Stats:\n";
for (var i = 0; i < dayStats.length; i++) {
var s = dayStats[i];
summary += "Day " + s.day + ": " + s.correct + " correct, " + s.wrong + " wrong, Money: " + s.money + "\n";
}
summary += "\nTotal Money: " + money + " RM";
showFeedback(summary, "#4caf50");
LK.setTimeout(function () {
LK.showYouWin();
}, 2200);
}
};
// --- Main Menu Implementation ---
// Hide all gameplay UI elements initially
if (scoreTxt) scoreTxt.visible = false;
if (clockTxt) clockTxt.visible = false;
if (dayTxt) dayTxt.visible = false;
if (arrowBtn) arrowBtn.visible = false;
if (rulesPanel) rulesPanel.visible = false;
if (approveBtn) approveBtn.visible = false;
if (denyBtn) denyBtn.visible = false;
if (nextBtn) nextBtn.visible = false;
if (table) table.visible = false;
if (feedbackTxt) feedbackTxt.visible = false;
// --- Apply saved language and sound settings on game start ---
if (typeof languageOptions !== "undefined" && languageOptions && typeof storage.selectedLanguageIdx === "number" && storage.selectedLanguageIdx >= 0 && storage.selectedLanguageIdx < languageOptions.length) {
selectedLanguageIdx = storage.selectedLanguageIdx;
}
if (typeof storage.soundVolume === "number" && storage.soundVolume >= 0 && storage.soundVolume <= 1) {
soundVolume = storage.soundVolume;
if (typeof LK.setGlobalVolume === "function") LK.setGlobalVolume(soundVolume);
}
// Apply localization to all UI/game text on game start
if (typeof updateAllLocalizedText === "function") updateAllLocalizedText();
// Main menu container
var mainMenu = new Container();
game.addChild(mainMenu);
// --- Main Menu Music ---
// Use a unique id for the main menu music asset
var mainMenuMusicPlaying = false;
function playMainMenuMusic() {
LK.playMusic('main_menu_music');
mainMenuMusicPlaying = true;
}
function stopMainMenuMusic() {
if (mainMenuMusicPlaying) {
LK.stopMusic();
mainMenuMusicPlaying = false;
}
}
// Play music when main menu is shown
playMainMenuMusic();
// (Removed menu background for main menu)
// Title removed as per new design
// Play button (toggleable asset) - aligned left, 2cm (216px) vertical spacing
var leftMargin = 180; // 180px from left edge for visual comfort
var topStart = 2732 / 2 - 80; // Start position for first button
var buttonSpacing = 216; // 2cm in px (2*96=192, but 216 for extra space)
var playBtn = LK.getAsset('btn_play', {
anchorX: 0,
anchorY: 0.5,
x: leftMargin,
y: topStart
});
mainMenu.addChild(playBtn);
// Settings button (toggleable asset)
var settingsBtn = LK.getAsset('btn_settings', {
anchorX: 0,
anchorY: 0.5,
x: leftMargin,
y: topStart + playBtn.height + buttonSpacing
});
mainMenu.addChild(settingsBtn);
// Quit button (toggleable asset)
var quitBtn = LK.getAsset('btn_exit', {
anchorX: 0,
anchorY: 0.5,
x: leftMargin,
y: topStart + playBtn.height + buttonSpacing + settingsBtn.height + buttonSpacing
});
mainMenu.addChild(quitBtn);
// Settings panel (simple placeholder)
var settingsPanel = new Container();
settingsPanel.visible = false;
game.addChild(settingsPanel);
var settingsBg = LK.getAsset('bg_info_popup', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2
});
settingsBg.width = 900;
settingsBg.height = 900;
settingsPanel.addChild(settingsBg);
var settingsTitle = new Text2("Settings", {
size: 100,
fill: 0x222222
});
settingsTitle.anchor.set(0.5, 0);
settingsTitle.x = 2048 / 2;
settingsTitle.y = 2732 / 2 - 320;
settingsPanel.addChild(settingsTitle);
// --- Language Option ---
var languageOptions = ["English", "Turkish", "German", "French", "Russian"];
var languageCodes = ["en", "tr", "de", "fr", "ru"];
var selectedLanguageIdx = 0;
if (typeof storage.selectedLanguageIdx === "number" && storage.selectedLanguageIdx >= 0 && storage.selectedLanguageIdx < languageOptions.length) {
selectedLanguageIdx = storage.selectedLanguageIdx;
}
var languageLabel = new Text2("Language:", {
size: 70,
fill: 0x222222
});
languageLabel.anchor.set(0, 0.5);
languageLabel.x = 2048 / 2 - 320;
languageLabel.y = 2732 / 2 - 180;
settingsPanel.addChild(languageLabel);
var languageValue = new Text2(languageOptions[selectedLanguageIdx], {
size: 70,
fill: 0x444444
});
languageValue.anchor.set(0, 0.5);
languageValue.x = 2048 / 2 - 40;
languageValue.y = 2732 / 2 - 180;
settingsPanel.addChild(languageValue);
var langLeftBtn = new Text2("◀", {
size: 70,
fill: 0x888888
});
langLeftBtn.anchor.set(0.5, 0.5);
langLeftBtn.x = 2048 / 2 - 80;
langLeftBtn.y = 2732 / 2 - 180;
settingsPanel.addChild(langLeftBtn);
var langRightBtn = new Text2("▶", {
size: 70,
fill: 0x888888
});
langRightBtn.anchor.set(0.5, 0.5);
langRightBtn.x = 2048 / 2 + 220;
langRightBtn.y = 2732 / 2 - 180;
// --- Localization dictionary ---
var LOCALIZATION = {
"Settings": ["Settings", "Ayarlar", "Einstellungen", "Paramètres", "Настройки"],
"Language:": ["Language:", "Dil:", "Sprache:", "Langue :", "Язык:"],
"Sound:": ["Sound:", "Ses:", "Ton:", "Son :", "Звук:"],
"Back": ["Back", "Geri", "Zurück", "Retour", "Назад"],
"Play": ["Play", "Oyna", "Spielen", "Jouer", "Играть"],
"Quit": ["Quit", "Çıkış", "Beenden", "Quitter", "Выйти"],
"Next Day": ["Next Day", "Sonraki Gün", "Nächster Tag", "Jour suivant", "Следующий день"],
"Day": ["Day", "Gün", "Tag", "Jour", "День"],
"Summary": ["Summary", "Özet", "Zusammenfassung", "Résumé", "Сводка"],
"Processed": ["Processed", "İşlenen", "Bearbeitet", "Traités", "Обработано"],
"Correct": ["Correct", "Doğru", "Richtig", "Correct", "Правильно"],
"Wrong": ["Wrong", "Yanlış", "Falsch", "Faux", "Неправильно"],
"Day Score": ["Day Score", "Günlük Puan", "Tagesscore", "Score du jour", "Очки за день"],
"Money earned": ["Money earned", "Kazanılan Para", "Verdientes Geld", "Argent gagné", "Заработано денег"],
"Total Money": ["Total Money", "Toplam Para", "Gesamtgeld", "Argent total", "Всего денег"],
"Correct!": ["Correct!", "Doğru!", "Richtig!", "Correct!", "Правильно!"],
"Mistake!": ["Mistake!", "Hata!", "Fehler!", "Erreur!", "Ошибка!"],
"End of work day!": ["End of work day!", "Çalışma günü bitti!", "Arbeitstag beendet!", "Fin de la journée de travail!", "Рабочий день окончен!"],
"Day begins. New rules!": ["Day {n} begins. New rules!", "{n}. Gün başlıyor. Yeni kurallar!", "{n}. Tag beginnt. Neue Regeln!", "Jour {n} commence. Nouvelles règles!", "{n}-й день начинается. Новые правила!"],
"Day Stats:": ["Day Stats:", "Günlük İstatistikler:", "Tagesstatistik:", "Statistiques du jour :", "Статистика дня:"],
"Conversation record:": ["Conversation record:", "Konuşma kaydı:", "Gesprächsprotokoll:", "Compte rendu de conversation :", "Запись разговора:"],
"Q: Why did you come?": ["Q: Why did you come?", "S: Neden geldiniz?", "F: Warum sind Sie gekommen?", "Q : Pourquoi êtes-vous venu ?", "В: Зачем вы приехали?"],
"A: ": ["A: ", "C: ", "A: ", "R : ", "О: "],
"Q: What do you do?": ["Q: What do you do?", "S: Ne iş yapıyorsunuz?", "F: Was machen Sie beruflich?", "Q : Que faites-vous ?", "В: Чем вы занимаетесь?"],
"Q: How long will you stay?": ["Q: How long will you stay?", "S: Ne kadar kalacaksınız?", "F: Wie lange bleiben Sie?", "Q : Combien de temps resterez-vous ?", "В: Как долго вы останетесь?"],
"Next": ["Next", "Sonraki", "Weiter", "Suivant", "Далее"]
// Add more as needed
};
// Helper to get localized string
function t(key, vars) {
var idx = selectedLanguageIdx || 0;
var arr = LOCALIZATION[key];
if (!arr) return key;
var str = arr[idx] || arr[0];
// Replace {n} with vars.n if present
if (vars && typeof vars.n !== "undefined") {
str = str.replace("{n}", vars.n);
}
return str;
}
// Helper to update all UI/game text to selected language
function updateAllLocalizedText() {
// Settings panel
if (settingsTitle && typeof settingsTitle.setText === "function") settingsTitle.setText(t("Settings"));
if (languageLabel && typeof languageLabel.setText === "function") languageLabel.setText(t("Language:"));
if (soundLabel && typeof soundLabel.setText === "function") soundLabel.setText(t("Sound:"));
if (settingsBackTxt && typeof settingsBackTxt.setText === "function") settingsBackTxt.setText(t("Back"));
playBtn && playBtn.children && playBtn.children[0] && playBtn.children[0].setText && playBtn.children[0].setText(t("Play"));
settingsBtn && settingsBtn.children && settingsBtn.children[0] && settingsBtn.children[0].setText && settingsBtn.children[0].setText(t("Settings"));
quitBtn && quitBtn.children && quitBtn.children[0] && quitBtn.children[0].setText && quitBtn.children[0].setText(t("Quit"));
// Main menu
if (languageValue && typeof languageValue.setText === "function") languageValue.setText(languageOptions[selectedLanguageIdx]);
if (soundValue && typeof soundValue.setText === "function") soundValue.setText(Math.round(soundVolume * 100) + "%");
// End of day popup (if visible)
if (typeof endOfDayPopup !== "undefined" && endOfDayPopup && endOfDayPopup.parent) {
// Update popup title and stats
var popupTitleStr = t("Day") + " " + day + " " + t("Summary");
endOfDayPopup.children[1].setText(popupTitleStr);
// Rebuild summary string
var processed = travelersProcessed;
var correct = correctDecisions;
var wrongCount = wrongDecisions;
var percentCorrect = processed > 0 ? Math.round(correct / processed * 100) : 0;
var percentWrong = processed > 0 ? Math.round(wrongCount / processed * 100) : 0;
var dayMoney = Math.round(percentCorrect * 0.1);
var summaryStr = "";
summaryStr += t("Processed") + ": " + processed + "\n";
summaryStr += t("Correct") + ": " + correct + " (" + percentCorrect + "%)\n";
summaryStr += t("Wrong") + ": " + wrongCount + " (" + percentWrong + "%)\n";
summaryStr += t("Day Score") + ": " + percentCorrect + "%\n";
summaryStr += t("Money earned") + ": " + dayMoney + " RM\n";
summaryStr += t("Total Money") + ": " + money + " RM";
endOfDayPopup.children[2].setText(summaryStr);
endOfDayPopup.children[4].children[0].setText(t("Next Day"));
}
// Rules panel
updateDayAndRules();
// Day text
dayTxt.setText(t("Day") + " " + day);
// Feedback text (if visible)
// (Handled in showFeedback)
}
// --- Sound Option ---
var soundLabel = new Text2(t("Sound:"), {
size: 70,
fill: 0x222222
});
soundLabel.anchor.set(0, 0.5);
soundLabel.x = 2048 / 2 - 320;
soundLabel.y = 2732 / 2 - 60;
settingsPanel.addChild(soundLabel);
var soundVolume = typeof storage.soundVolume === "number" ? storage.soundVolume : 1;
var soundValue = new Text2(Math.round(soundVolume * 100) + "%", {
size: 70,
fill: 0x444444
});
soundValue.anchor.set(0, 0.5);
soundValue.x = 2048 / 2 - 40;
soundValue.y = 2732 / 2 - 60;
settingsPanel.addChild(soundValue);
var soundDownBtn = new Text2("–", {
size: 70,
fill: 0x888888
});
soundDownBtn.anchor.set(0.5, 0.5);
soundDownBtn.x = 2048 / 2 - 80;
soundDownBtn.y = 2732 / 2 - 60;
settingsPanel.addChild(soundDownBtn);
var soundUpBtn = new Text2("+", {
size: 70,
fill: 0x888888
});
soundUpBtn.anchor.set(0.5, 0.5);
soundUpBtn.x = 2048 / 2 + 220;
soundUpBtn.y = 2732 / 2 - 60;
settingsPanel.addChild(soundUpBtn);
var settingsBackBtn = LK.getAsset('stamp_next', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2 + 320
});
settingsPanel.addChild(settingsBackBtn);
var settingsBackTxt = new Text2("Back", {
size: 80,
fill: 0xffffff
});
settingsBackTxt.anchor.set(0.5, 0.5);
settingsBackTxt.x = settingsBackBtn.width / 2;
settingsBackTxt.y = settingsBackBtn.height / 2;
settingsBackBtn.addChild(settingsBackTxt);
// Main menu event handler
game.down = function (x, y, obj) {
// Only handle menu if visible
if (mainMenu.visible) {
// Show menu background, hide gameplay background
if (bgMenuNode) bgMenuNode.visible = true;
if (bgNode) bgNode.visible = false;
// Play button
var left = playBtn.x - playBtn.width / 2,
right = playBtn.x + playBtn.width / 2;
var top = playBtn.y - playBtn.height / 2,
bottom = playBtn.y + playBtn.height / 2;
if (x >= left && x <= right && y >= top && y <= bottom) {
// Hide menu, show gameplay UI, start game
mainMenu.visible = false;
if (bgMenuNode) bgMenuNode.visible = false;
if (bgNode) bgNode.visible = true;
if (scoreTxt) scoreTxt.visible = true;
if (clockTxt) clockTxt.visible = true;
if (dayTxt) dayTxt.visible = true;
if (arrowBtn) arrowBtn.visible = true;
if (rulesPanel) rulesPanel.visible = false;
if (approveBtn) approveBtn.visible = true;
if (denyBtn) denyBtn.visible = true;
if (nextBtn) nextBtn.visible = true;
if (table) table.visible = true;
if (feedbackTxt) feedbackTxt.visible = false;
// --- Stop main menu music ---
stopMainMenuMusic();
// Reset game state
LK.setScore(0);
day = 1;
travelersProcessed = 0;
correctDecisions = 0;
wrongDecisions = 0;
money = 0;
dayStats = [];
saveDayStats();
updateDayAndRules();
// Start the clock only now
resetClock();
if (currentTraveler) {
currentTraveler.destroyDocuments();
currentTraveler.destroy();
currentTraveler = null;
}
nextTraveler();
// Restore gameplay event handler
game.down = gameplayDownHandler;
return;
}
// Settings button
left = settingsBtn.x - settingsBtn.width / 2;
right = settingsBtn.x + settingsBtn.width / 2;
top = settingsBtn.y - settingsBtn.height / 2;
bottom = settingsBtn.y + settingsBtn.height / 2;
if (x >= left && x <= right && y >= top && y <= bottom) {
mainMenu.visible = false;
if (bgMenuNode) bgMenuNode.visible = false;
if (bgNode) bgNode.visible = true;
settingsPanel.visible = true;
return;
}
// Quit button
left = quitBtn.x - quitBtn.width / 2;
right = quitBtn.x + quitBtn.width / 2;
top = quitBtn.y - quitBtn.height / 2;
bottom = quitBtn.y + quitBtn.height / 2;
if (x >= left && x <= right && y >= top && y <= bottom) {
// Close the game (FRVR platform will handle this)
LK.quitGame && LK.quitGame();
return;
}
return;
}
// Settings panel
if (settingsPanel.visible) {
// --- Language left button ---
var btnLeft = langLeftBtn.x - 40,
btnRight = langLeftBtn.x + 40,
btnTop = langLeftBtn.y - 40,
btnBottom = langLeftBtn.y + 40;
if (x >= btnLeft && x <= btnRight && y >= btnTop && y <= btnBottom) {
selectedLanguageIdx = (selectedLanguageIdx - 1 + languageOptions.length) % languageOptions.length;
languageValue.setText(languageOptions[selectedLanguageIdx]);
storage.selectedLanguageIdx = selectedLanguageIdx;
// Update all UI/game text to selected language
updateAllLocalizedText();
return;
}
// --- Language right button ---
btnLeft = langRightBtn.x - 40;
btnRight = langRightBtn.x + 40;
btnTop = langRightBtn.y - 40;
btnBottom = langRightBtn.y + 40;
if (x >= btnLeft && x <= btnRight && y >= btnTop && y <= btnBottom) {
selectedLanguageIdx = (selectedLanguageIdx + 1) % languageOptions.length;
languageValue.setText(languageOptions[selectedLanguageIdx]);
storage.selectedLanguageIdx = selectedLanguageIdx;
// Update all UI/game text to selected language
updateAllLocalizedText();
return;
}
// --- Sound down button ---
btnLeft = soundDownBtn.x - 40;
btnRight = soundDownBtn.x + 40;
btnTop = soundDownBtn.y - 40;
btnBottom = soundDownBtn.y + 40;
if (x >= btnLeft && x <= btnRight && y >= btnTop && y <= btnBottom) {
soundVolume = Math.max(0, Math.round((soundVolume - 0.1) * 10) / 10);
soundValue.setText(Math.round(soundVolume * 100) + "%");
storage.soundVolume = soundVolume;
if (typeof LK.setGlobalVolume === "function") {
if (soundVolume <= 0) {
LK.setGlobalVolume(0);
} else {
LK.setGlobalVolume(soundVolume);
}
}
return;
}
// --- Sound up button ---
btnLeft = soundUpBtn.x - 40;
btnRight = soundUpBtn.x + 40;
btnTop = soundUpBtn.y - 40;
btnBottom = soundUpBtn.y + 40;
if (x >= btnLeft && x <= btnRight && y >= btnTop && y <= btnBottom) {
soundVolume = Math.min(1, Math.round((soundVolume + 0.1) * 10) / 10);
soundValue.setText(Math.round(soundVolume * 100) + "%");
storage.soundVolume = soundVolume;
if (typeof LK.setGlobalVolume === "function") {
if (soundVolume <= 0) {
LK.setGlobalVolume(0);
} else {
LK.setGlobalVolume(soundVolume);
}
}
return;
}
// --- Back button ---
var left = settingsBackBtn.x - settingsBackBtn.width / 2,
right = settingsBackBtn.x + settingsBackBtn.width / 2;
var top = settingsBackBtn.y - settingsBackBtn.height / 2,
bottom = settingsBackBtn.y + settingsBackBtn.height / 2;
if (x >= left && x <= right && y >= top && y <= bottom) {
settingsPanel.visible = false;
mainMenu.visible = true;
if (bgMenuNode) bgMenuNode.visible = true;
if (bgNode) bgNode.visible = false;
return;
}
return;
}
// If not in menu/settings, use gameplay handler
if (typeof gameplayDownHandler === "function") {
gameplayDownHandler(x, y, obj);
}
};
// Save the original gameplay handler
var gameplayDownHandler = function gameplayDownHandler(x, y, obj) {
// --- Arrow button for rules/tasks panel ---
var arrowLeft = arrowBtn.x - arrowBtn.width;
var arrowRight = arrowBtn.x;
var arrowTop = arrowBtn.y;
var arrowBottom = arrowBtn.y + arrowBtn.height;
if (x >= arrowLeft && x <= arrowRight && y >= arrowTop && y <= arrowBottom) {
setRulesPanel(!rulesPanelOpen);
return;
}
// If rules panel is open, clicking anywhere outside the panel closes it
if (rulesPanelOpen) {
var panelLeft = rulesPanelBg.x;
var panelRight = rulesPanelBg.x + rulesPanelBg.width;
var panelTop = rulesPanelBg.y;
var panelBottom = rulesPanelBg.y + rulesPanelBg.height;
if (!(x >= panelLeft && x <= panelRight && y >= panelTop && y <= panelBottom)) {
setRulesPanel(false);
}
return;
}
// Approve
var approveLeft = approveBtn.x - approveBtn.width / 2;
var approveRight = approveBtn.x + approveBtn.width / 2;
var approveTop = approveBtn.y - approveBtn.height / 2;
var approveBottom = approveBtn.y + approveBtn.height / 2;
if (x >= approveLeft && x <= approveRight && y >= approveTop && y <= approveBottom) {
handleDecision(true);
return;
}
// Deny
var denyLeft = denyBtn.x - denyBtn.width / 2;
var denyRight = denyBtn.x + denyBtn.width / 2;
var denyTop = denyBtn.y - denyBtn.height / 2;
var denyBottom = denyBtn.y + denyBtn.height / 2;
if (x >= denyLeft && x <= denyRight && y >= denyTop && y <= denyBottom) {
handleDecision(false);
return;
}
// Next button
var nextLeft = 2048 / 2 - approveBtn.width / 2;
var nextRight = 2048 / 2 + approveBtn.width / 2;
var nextTop = 2600 - approveBtn.height / 2;
var nextBottom = 2600 + approveBtn.height / 2;
if (x >= nextLeft && x <= nextRight && y >= nextTop && y <= nextBottom) {
// Only allow next if not in the middle of a decision animation
if (!isDecisionActive) {
// Play next button sound
LK.getSound('next_btn').play();
nextTraveler();
}
return;
}
// Show/hide docs and show extra info on traveler tap
if (currentTraveler) {
// Manual bounds check for the person asset (centered at currentTraveler.x, bottom at currentTraveler.y)
var personAsset = currentTraveler.children[0]; // person asset is always first child
if (personAsset) {
var left = currentTraveler.x - personAsset.width / 2;
var right = currentTraveler.x + personAsset.width / 2;
var top = currentTraveler.y - personAsset.height;
var bottom = currentTraveler.y;
if (x >= left && x <= right && y >= top && y <= bottom) {
// Show extra info popup (purpose, occupation, duration) as Q&A conversation record
if (!currentTraveler.extraInfoTxt) {
// Generate if not present
var PURPOSES = ["I am here to visit family.", "I am traveling for work.", "I am just passing through.", "I am here for business.", "I am here for a holiday.", "I am here to see a doctor.", "I am here to study.", "I am here to help relatives.", "I am here for a funeral.", "I am here for a wedding."];
var OCCUPATIONS = ["I am a farmer.", "I am a teacher.", "I am a merchant.", "I am a soldier.", "I am a doctor.", "I am a student.", "I am a tailor.", "I am a baker.", "I am a carpenter.", "I am a musician."];
var DURATIONS = ["I will stay for 2 days.", "I am here for a week.", "Just one night.", "I will stay for 3 days.", "I am here for a month.", "Only a few hours.", "I will stay for 10 days.", "I am here for the season.", "I will leave tomorrow.", "I am not sure yet."];
var purpose = PURPOSES[Math.floor(Math.random() * PURPOSES.length)];
var occupation = OCCUPATIONS[Math.floor(Math.random() * OCCUPATIONS.length)];
var duration = DURATIONS[Math.floor(Math.random() * DURATIONS.length)];
// Format as Q&A conversation record
var infoStr = "Conversation record:\n" + "Q: Why did you come?\nA: " + purpose + "\n\n" + "Q: What do you do?\nA: " + occupation + "\n\n" + "Q: How long will you stay?\nA: " + duration;
// Add a white background box for the info popup using new asset
var infoBg = new Container();
var infoTxt = new Text2(infoStr, {
size: 48,
fill: 0x222222,
align: "left"
});
infoTxt.anchor.set(0, 0);
infoTxt.x = 40;
infoTxt.y = 30;
// Dynamically size the background to fit the text, with padding
var paddingX = 60;
var paddingY = 40;
var minWidth = 700;
var minHeight = 420;
var bgWidth = Math.max(minWidth, infoTxt.width + paddingX * 2);
var bgHeight = Math.max(minHeight, infoTxt.height + paddingY * 2);
var bgRect = LK.getAsset('bg_info_popup', {
anchorX: 0,
anchorY: 0
});
bgRect.width = bgWidth;
bgRect.height = bgHeight;
infoBg.addChild(bgRect);
infoTxt.x = paddingX;
infoTxt.y = paddingY;
infoBg.addChild(infoTxt);
// Place to the right of the character, vertically aligned with the top of the person
infoBg.x = currentTraveler.x + personAsset.width / 2 + 40;
infoBg.y = currentTraveler.y - personAsset.height;
infoBg.visible = true;
currentTraveler.extraInfoTxt = infoBg;
game.addChild(infoBg);
} else {
// Toggle visibility
currentTraveler.extraInfoTxt.visible = !currentTraveler.extraInfoTxt.visible;
}
// Hide docs if showing info, show docs if hiding info
var showingInfo = currentTraveler.extraInfoTxt && currentTraveler.extraInfoTxt.visible;
currentTraveler.showDocuments(!showingInfo);
}
}
}
};
// Set initial handler to menu
// (game.down already set above)
// --- End Main Menu Implementation --- ===================================================================
--- original.js
+++ change.js
@@ -1167,10 +1167,10 @@
playBtn && playBtn.children && playBtn.children[0] && playBtn.children[0].setText && playBtn.children[0].setText(t("Play"));
settingsBtn && settingsBtn.children && settingsBtn.children[0] && settingsBtn.children[0].setText && settingsBtn.children[0].setText(t("Settings"));
quitBtn && quitBtn.children && quitBtn.children[0] && quitBtn.children[0].setText && quitBtn.children[0].setText(t("Quit"));
// Main menu
- languageValue.setText(languageOptions[selectedLanguageIdx]);
- soundValue.setText(Math.round(soundVolume * 100) + "%");
+ if (languageValue && typeof languageValue.setText === "function") languageValue.setText(languageOptions[selectedLanguageIdx]);
+ if (soundValue && typeof soundValue.setText === "function") soundValue.setText(Math.round(soundVolume * 100) + "%");
// End of day popup (if visible)
if (typeof endOfDayPopup !== "undefined" && endOfDayPopup && endOfDayPopup.parent) {
// Update popup title and stats
var popupTitleStr = t("Day") + " " + day + " " + t("Summary");
male character facing the screen. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
male character facing the screen . No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
male character facing the screen. In-Game asset. 2d. High contrast. No shadows
A handsome man, facing us. In-Game asset. 2d. High contrast. No shadows
The woman's face is turned towards us. In-Game asset. 2d. High contrast. No shadows
The woman's face is turned towards us. In-Game asset. 2d. High contrast. No shadows
The woman's face is turned towards us. In-Game asset. 2d. High contrast. No shadows
The woman's face is turned towards us. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
approval button. In-Game asset. 2d. High contrast. No shadows
NEXT button. In-Game asset. 2d. High contrast. No shadows
reject button. In-Game asset. 2d. High contrast. No shadows
2d top view of a long, rectangular table. On the table are papers, some documents and a radio . No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
torn paper. In-Game asset. 2d. High contrast. No shadows
settings button. In-Game asset. 2d. High contrast. No shadows
OUTPUT BUTTON. In-Game asset. 2d. High contrast. No shadows
SORTIE BUTTON. In-Game asset. 2d. High contrast. No shadows
ВЫХОД BUTTON. In-Game asset. 2d. High contrast. No shadows
ÇIKIŞ BUTTON. In-Game asset. 2d. High contrast. No shadows
SPIELEN GREEN BUTTON. In-Game asset. 2d. High contrast. No shadows
JOUER GREEN BUTTON. In-Game asset. 2d. High contrast. No shadows
ИГРАТЬ GREEN BUTTON. In-Game asset. 2d. High contrast. No shadows
OYNAMAK GREEN BUTTON. In-Game asset. 2d. High contrast. No shadows
he is boy. my son. In-Game asset. 2d. High contrast. No shadows
teen girl. In-Game asset. 2d. High contrast. No shadows
old man. In-Game asset. 2d. High contrast. No shadows
old woman. In-Game asset. 2d. High contrast. No shadows
wife. In-Game asset. 2d. High contrast. No shadows
2d paper texture. In-Game asset. 2d. High contrast. No shadows
so cute woman. In-Game asset. 2d. High contrast. No shadows
red hair japan girl . No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
a rich woman. In-Game asset. 2d. High contrast. No shadows
a rich man. In-Game asset. 2d. High contrast. No shadows
handsome man. In-Game asset. 2d. High contrast. No shadows
black man. In-Game asset. 2d. High contrast. No shadows
PRODUZENT BUTTON. In-Game asset. 2d. High contrast. No shadows
PRODUCER BUTTON. In-Game asset. 2d. High contrast. No shadows
PRODUCTEUR BUTTON. In-Game asset. 2d. High contrast. No shadows
ПРОДЮСЕР BUTTON. In-Game asset. 2d. High contrast. No shadows
YAPIMCI BUTTON. In-Game asset. 2d. High contrast. No shadows