User prompt
translate the producers section into the selected language (if Russian is selected, translate into Russian, if English is selected, translate into English) and so on
User prompt
replace āYOUR_USERNAMEā with āANKA_GAMES10ā in the makers section. change the text in this makers section to the language with audio
User prompt
fit the text in the makers section to full screen
User prompt
end the game if the player presses the exit button
User prompt
make the makers section full screen so that pressing the back button returns to the main menu
User prompt
put my username in the producer section of the game and immediately under my username write āI HAVE MADE THIS GAME THIS MUCH, ANYONE WHO WANTS TO UPDATE IT CAN UPDATE IT AND I WILL PLAY ITā
User prompt
make a big translation update that actually works
User prompt
Please fix the bug: 'Cannot read properties of undefined (reading 'length')' in or related to this line: 'var producerBtnAsset = Array.isArray(producerBtnAssets) && typeof selectedLanguageIdx === "number" && selectedLanguageIdx >= 0 && selectedLanguageIdx < producerBtnAssets.length ? producerBtnAssets[selectedLanguageIdx] : Array.isArray(producerBtnAssets) && producerBtnAssets.length > 0 ? producerBtnAssets[0] : undefined;' Line Number: 2331
User prompt
Please fix the bug: 'Cannot read properties of undefined (reading 'length')' in or related to this line: 'var producerBtn = LK.getAsset(producerBtnAssets[selectedLanguageIdx], {' Line Number: 2331
User prompt
Please fix the bug: 'Cannot read properties of undefined (reading 'undefined')' in or related to this line: 'var producerAsset = producerBtnAssets[selectedLanguageIdx];' Line Number: 2342
User prompt
add an extra button at the bottom of the main menu, for each language, add a modifiable version of that button in the assets section, our button will be the āPRODUCERā button
User prompt
Let's put the finishing touches on the game to make it look exactly like āpapers pleaseā.
User prompt
make a nice update but don't access the player's camera or microphone
User prompt
give the game a big update
User prompt
when we buy the needs we buy in the family section, our money decreases according to the price of the need
User prompt
give the game a final polish
User prompt
Please fix the bug: 'Cannot read properties of undefined (reading 'length')' in or related to this line: 'for (var j = 0; j < c.names.length; j++) {' Line Number: 438
User prompt
Create a list of female names and male names.if the incoming person is a woman, randomly select a woman's name from the list of women if the incoming person is male, choose a random name from the list of male names.and randomly select a last name from the last name list also give first and last names by country
User prompt
at the end of the day, a sound will ring and the player will know that the day is over
User prompt
on day 2, even if all documents are correct, the game considers them incorrect and does not award points. fix this bug
User prompt
let's have other countries (Turkey, Russia, Italy, Italy, England, France...) and let's have the names and surnames of the people from these countries and let's add the cities of the countries where the bidet is located
User prompt
On the 2nd day, add the wrong things from the sobet record to the pass documents, the player will examine them and pass them if they are correct, and if they are in accordance with the one-to-one pass document, the person can pass. On days five and multiples of five, a policeman will come and give the player a paper with the names of the people who should not be allowed inside, and we will be able to access that paper by clicking on the table. Write different new names and surnames, make 6 new characters (3 female, 3 male) and they can be changed in the assets section āŖš” Consider importing and using the following plugins: @upit/storage.v1
User prompt
On day 2, add a background behind the entry documents, but make it changeable from the assest section. In the family section between the days, there will be slots that can be selected (rent, food, heating) and if someone in the family is sick, the āmedicineā option will be added, we will choose the appropriate one of these ticks to spend our money sparingly and keep our money. My scores for each episode should also be reset. On day 2, although the entry documents are not correct, it accepts it wrong when I pass it, let's make a correction to it
User prompt
On the 2nd day, you are doing all the permission documents correctly, mix them up so that they are not all similar to each other, and even when it is correct, it gives an error when it passes.
User prompt
Please fix the bug: 'Timeout.tick error: t is not a function' in or related to this line: 'var PURPOSES = [t("I am here to visit family."), t("I am traveling for work."), t("I am just passing through."), t("I am here for business."), t("I am here for a holiday."), t("I am here to see a doctor."), t("I am here to study."), t("I am here to help relatives."), t("I am here for a funeral."), t("I am here for a wedding.")];' Line Number: 432
/**** * 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 // On day 2+, use a changeable background asset for entry documents (permit) if (self.docType === "permit" && typeof day !== "undefined" && day >= 2) { // Use a changeable asset for permit background self.bg = self.attachAsset('bg_permit', { anchorX: 0, anchorY: 0 }); } else { self.bg = self.attachAsset(docType, { anchorX: 0, anchorY: 0 }); } // Title var docTitle = docType.toUpperCase(); if (typeof t === "function" && LOCALIZATION[docTitle]) docTitle = t(docTitle); // Add static passport inscriptions (e.g. "Passport", "Nationality", "Name", etc.) for passport if (docType === "passport") { // Example: Add "Passport" as a static label at the top var passportLabel = typeof t === "function" && LOCALIZATION["Passport"] ? t("Passport") : "Passport"; var passportTitle = new Text2(passportLabel, { size: 60, fill: 0x333333 }); passportTitle.x = 40; passportTitle.y = 28; self.addChild(passportTitle); self.textFields.push(passportTitle); } else { 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); // Try to localize the field label (e.g. "Name:", "Nationality:", etc.) if (typeof t === "function" && LOCALIZATION[fieldLabel + ":"]) fieldLabel = t(fieldLabel + ":"); // For permit extra fields, localize value if possible if (self.docType === "permit" && (key === "purpose" || key === "occupation" || key === "duration")) { if (typeof t === "function" && LOCALIZATION[value]) value = t(value); } 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' }, { id: 'female5', gender: 'female' }, { id: 'female6', gender: 'female' }, { id: 'female7', gender: 'female' }, { id: 'male6', gender: 'male' }, { id: 'male7', gender: 'male' }, { id: 'male8', gender: 'male' }]; 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 (removed) // Name label (removed) self.nameBg = null; self.nameTxt = { setText: function setText() {} }; // dummy for compatibility // 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 ****/ // Example: new background for permit // Document backgrounds // New family member avatars // Play/Exit button assets for each language (English, Turkish, German, French, Russian) // 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 // New unique character assets (3 female, 3 male) 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 // Expanded names, surnames, and cities for multiple countries var COUNTRY_DATA = [{ country: "German", cities: ["Berlin", "Munich", "Hamburg", "Cologne", "Frankfurt", "Leipzig", "Dresden", "Stuttgart"], maleNames: ["Hans", "Karl", "Otto", "Friedrich", "Johann", "Wilhelm", "Heinrich", "Emil", "Maximilian", "Bruno", "Paul", "Ludwig", "Franz", "Gustav", "Walter", "Rudolf", "Fritz", "Heinz", "Otmar", "Konrad"], femaleNames: ["Erika", "Anna", "Liselotte", "Greta", "Marta", "Sophie", "Paula", "Clara", "Helene", "Rosa", "Maria", "Ida", "Emma", "Charlotte", "Luise", "Elsa", "Johanna", "Marlene", "Ingrid", "Klara", "Gertrud", "Lina", "Hildegard", "Anneliese"], lastNames: ["Müller", "Schmidt", "Weber", "Fischer", "Becker", "Braun", "Wagner", "Hoffmann", "Bauer", "Klein", "Richter", "Keller", "Vogel", "Neumann", "Schuster", "Busch", "Kƶnig", "Frank", "SchƤfer", "Winkler", "Zimmermann", "Lorenz", "Hartmann", "Simon", "Lehmann", "Wolf", "Peters", "Roth", "Krause", "Berger", "Fuchs", "Brandt", "Adler", "Stein", "Baum", "Weiss", "Engel", "Stark", "Sommer", "Kranz", "Kurz", "Dietrich", "Busch"] }, { country: "Turkish", cities: ["Istanbul", "Ankara", "Izmir", "Bursa", "Antalya", "Adana", "Konya", "Gaziantep"], maleNames: ["Ahmet", "Mehmet", "Mustafa", "Ali", "Hüseyin", "Murat", "Hasan"], femaleNames: ["AyÅe", "Fatma", "Emine", "Zeynep", "Elif", "Hatice", "Aylin"], lastNames: ["Yılmaz", "Demir", "Kaya", "Åahin", "Ćelik", "KoƧ", "Aydın", "Arslan", "Ćz", "KılıƧ", "Aksoy", "Polat", "GüneÅ", "Yıldız"] }, { country: "Russian", cities: ["Moscow", "Saint Petersburg", "Novosibirsk", "Yekaterinburg", "Kazan", "Nizhny Novgorod", "Samara", "Omsk"], maleNames: ["Ivan", "Dmitry", "Sergey", "Alexey", "Mikhail"], femaleNames: ["Anna", "Olga", "Natalia", "Ekaterina", "Irina"], lastNames: ["Ivanov", "Petrov", "Smirnov", "Sokolov", "Popov", "Lebedev", "Morozov", "Volkov", "Kuznetsov", "Novikov"] }, { country: "Italian", cities: ["Rome", "Milan", "Naples", "Turin", "Palermo", "Genoa", "Bologna", "Florence"], maleNames: ["Giovanni", "Luca", "Marco", "Alessandro", "Matteo"], femaleNames: ["Maria", "Francesca", "Giulia", "Martina", "Chiara"], lastNames: ["Rossi", "Russo", "Ferrari", "Esposito", "Bianchi", "Romano", "Gallo", "Costa", "Greco", "Conti"] }, { country: "English", cities: ["London", "Manchester", "Birmingham", "Liverpool", "Leeds", "Sheffield", "Bristol", "Nottingham"], maleNames: ["James", "John", "Robert", "Michael", "William"], femaleNames: ["Mary", "Patricia", "Jennifer", "Linda", "Elizabeth"], lastNames: ["Smith", "Johnson", "Williams", "Brown", "Jones", "Taylor", "Davies", "Evans", "Thomas"] }, { country: "French", cities: ["Paris", "Marseille", "Lyon", "Toulouse", "Nice", "Nantes", "Strasbourg", "Montpellier"], maleNames: ["Jean", "Pierre", "Michel", "Alain", "Philippe"], femaleNames: ["Marie", "Nathalie", "Isabelle", "Sophie", "Catherine"], lastNames: ["Martin", "Bernard", "Dubois", "Thomas", "Robert", "Petit", "Richard", "Leroy", "Moreau", "Simon"] }, { country: "Polish", cities: ["Warsaw", "Krakow", "Lodz", "Wroclaw", "Poznan", "Gdansk", "Szczecin", "Bydgoszcz"], maleNames: ["Jan", "Piotr", "Tomasz", "Pawel"], femaleNames: ["Anna", "Katarzyna", "Agnieszka", "Ewa"], lastNames: ["Kowalski", "Nowak", "Zielinski", "Wojcik", "Kaminski", "Lewandowska", "Dabrowski", "Zielinska"] }, { country: "Czech", cities: ["Prague", "Brno", "Ostrava", "Plzen", "Liberec", "Olomouc", "Usti nad Labem", "Hradec Kralove"], maleNames: ["Jan", "Petr", "Tomas", "Martin"], femaleNames: ["Petra", "Jana", "Lucie", "Eva"], lastNames: ["Novak", "Svoboda", "Dvorak", "Prochazka", "Cerny", "Kral", "Polak", "Blaha"] }, { country: "Austrian", cities: ["Vienna", "Graz", "Linz", "Salzburg", "Innsbruck", "Klagenfurt", "Villach", "Wels"], maleNames: ["Lukas", "David", "Florian", "Markus"], femaleNames: ["Anna", "Julia", "Katharina", "Sarah"], lastNames: ["Gruber", "Huber", "Wagner", "Steiner", "Mayer", "Hofer", "Leitner", "Berger"] }, { country: "Dutch", cities: ["Amsterdam", "Rotterdam", "The Hague", "Utrecht", "Eindhoven", "Tilburg", "Groningen", "Almere"], maleNames: ["Jan", "Pieter", "Tom", "Daan"], femaleNames: ["Sanne", "Lisa", "Emma", "Sophie"], lastNames: ["de Vries", "Jansen", "Bakker", "Visser", "Smit", "Meijer", "de Boer", "Mulder"] }]; // Flatten for legacy code compatibility var NAMES = []; var CITIES = []; var NATIONALITIES = []; for (var i = 0; i < COUNTRY_DATA.length; i++) { var c = COUNTRY_DATA[i]; NATIONALITIES.push(c.country); // Defensive: flatten both maleNames and femaleNames if present if (c.maleNames && Array.isArray(c.maleNames)) { for (var j = 0; j < c.maleNames.length; j++) NAMES.push(c.maleNames[j]); } if (c.femaleNames && Array.isArray(c.femaleNames)) { for (var j = 0; j < c.femaleNames.length; j++) NAMES.push(c.femaleNames[j]); } for (var k = 0; k < c.cities.length; k++) CITIES.push(c.cities[k]); } // 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 traveler = new Traveler(); // Pick nationality first var nationality = NATIONALITIES[Math.floor(Math.random() * NATIONALITIES.length)]; // Find country data for this nationality var countryObj = null; for (var i = 0; i < COUNTRY_DATA.length; i++) { if (COUNTRY_DATA[i].country === nationality) { countryObj = COUNTRY_DATA[i]; break; } } // Defensive fallback if (!countryObj) countryObj = COUNTRY_DATA[0]; // Gender from asset var gender = traveler.gender || (Math.random() < 0.5 ? "male" : "female"); // Name and city from country, using gendered first names and last names var firstNameList = gender === "female" ? countryObj.femaleNames || [] : countryObj.maleNames || []; // Fallback if no gendered names if (!firstNameList.length) firstNameList = countryObj.maleNames || countryObj.femaleNames || []; var lastNameList = countryObj.lastNames || []; var firstName = firstNameList.length ? firstNameList[Math.floor(Math.random() * firstNameList.length)] : "Alex"; var lastName = lastNameList.length ? lastNameList[Math.floor(Math.random() * lastNameList.length)] : "Smith"; var name = firstName + " " + lastName; var city = countryObj.cities[Math.floor(Math.random() * countryObj.cities.length)]; traveler.nameTxt.setText(name); // Passport 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; traveler.addChild(passport); traveler.documents.push(passport); // Permit (for non-Germans, 50% chance from day 2+) var hasPermit = nationality !== "German" && day >= 2 && Math.random() < 0.5; if (hasPermit) { // Generate extra info for permit (purpose, duration, occupation) var PURPOSES = [t("I am here to visit family."), t("I am traveling for work."), t("I am just passing through."), t("I am here for business."), t("I am here for a holiday."), t("I am here to see a doctor."), t("I am here to study."), t("I am here to help relatives."), t("I am here for a funeral."), t("I am here for a wedding.")]; var OCCUPATIONS = [t("I am a farmer."), t("I am a teacher."), t("I am a merchant."), t("I am a soldier."), t("I am a doctor."), t("I am a student."), t("I am a tailor."), t("I am a baker."), t("I am a carpenter."), t("I am a musician.")]; var DURATIONS = [t("I will stay for 2 days."), t("I am here for a week."), t("Just one night."), t("I will stay for 3 days."), t("I am here for a month."), t("Only a few hours."), t("I will stay for 10 days."), t("I am here for the season."), t("I will leave tomorrow."), t("I am not sure yet.")]; // Mix up the info: sometimes the permit info does not match the actual answers // On day 2, introduce random mismatches and even when correct, sometimes mark as error var purposeIdx = Math.floor(Math.random() * PURPOSES.length); var occupationIdx = Math.floor(Math.random() * OCCUPATIONS.length); var durationIdx = Math.floor(Math.random() * DURATIONS.length); var purpose = PURPOSES[purposeIdx]; var occupation = OCCUPATIONS[occupationIdx]; var duration = DURATIONS[durationIdx]; // Store the "true" info for the traveler (what they will say) traveler.permitPurpose = purpose; traveler.permitOccupation = occupation; traveler.permitDuration = duration; // Now, for the permit document, sometimes use mismatched info var permitPurpose = purpose; var permitOccupation = occupation; var permitDuration = duration; // On day 2, 40% chance to mismatch one field, 20% chance to mismatch two fields if (day === 2) { // 50% chance to use wrong info from conversation record (simulate "wrong things from sobet record") var useWrongFromConversation = Math.random() < 0.5; if (useWrongFromConversation) { // Pick a random wrong purpose, occupation, duration (not matching the real one) var wrongPurposeIdx = (purposeIdx + 1 + Math.floor(Math.random() * (PURPOSES.length - 1))) % PURPOSES.length; var wrongOccupationIdx = (occupationIdx + 1 + Math.floor(Math.random() * (OCCUPATIONS.length - 1))) % OCCUPATIONS.length; var wrongDurationIdx = (durationIdx + 1 + Math.floor(Math.random() * (DURATIONS.length - 1))) % DURATIONS.length; permitPurpose = PURPOSES[wrongPurposeIdx]; permitOccupation = OCCUPATIONS[wrongOccupationIdx]; permitDuration = DURATIONS[wrongDurationIdx]; } else { var mismatchChance = Math.random(); if (mismatchChance < 0.4) { // Mismatch one field var which = Math.floor(Math.random() * 3); if (which === 0) { // Purpose var otherIdx = (purposeIdx + 1 + Math.floor(Math.random() * (PURPOSES.length - 1))) % PURPOSES.length; permitPurpose = PURPOSES[otherIdx]; } else if (which === 1) { // Occupation var otherIdx = (occupationIdx + 1 + Math.floor(Math.random() * (OCCUPATIONS.length - 1))) % OCCUPATIONS.length; permitOccupation = OCCUPATIONS[otherIdx]; } else { // Duration var otherIdx = (durationIdx + 1 + Math.floor(Math.random() * (DURATIONS.length - 1))) % DURATIONS.length; permitDuration = DURATIONS[otherIdx]; } } else if (mismatchChance < 0.6) { // Mismatch two fields var fields = [0, 1, 2]; // Shuffle for (var i = fields.length - 1; i > 0; i--) { var j = Math.floor(Math.random() * (i + 1)); var tmp = fields[i]; fields[i] = fields[j]; fields[j] = tmp; } // Mismatch first two for (var f = 0; f < 2; f++) { if (fields[f] === 0) { var otherIdx = (purposeIdx + 1 + Math.floor(Math.random() * (PURPOSES.length - 1))) % PURPOSES.length; permitPurpose = PURPOSES[otherIdx]; } else if (fields[f] === 1) { var otherIdx = (occupationIdx + 1 + Math.floor(Math.random() * (OCCUPATIONS.length - 1))) % OCCUPATIONS.length; permitOccupation = OCCUPATIONS[otherIdx]; } else { var otherIdx = (durationIdx + 1 + Math.floor(Math.random() * (DURATIONS.length - 1))) % DURATIONS.length; permitDuration = DURATIONS[otherIdx]; } } } } } var permitFields = { name: name, nationality: nationality, valid: "Yes", gender: gender, purpose: permitPurpose, occupation: permitOccupation, duration: permitDuration }; // 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; traveler.addChild(permit); traveler.documents.push(permit); // Start with only passport visible, permit hidden permit.visible = false; // Track which doc is currently shown traveler.currentDocIndex = 0; // Add toggle logic: click on passport toggles to permit, click on permit toggles back to passport passport.interactive = true; permit.interactive = true; passport.onDown = function () { passport.visible = false; permit.visible = true; }; permit.onDown = function () { permit.visible = false; passport.visible = true; }; // Attach event handlers to the doc containers passport.down = function (x, y, obj) { if (passport.visible) { passport.visible = false; permit.visible = true; } }; permit.down = function (x, y, obj) { if (permit.visible) { permit.visible = false; passport.visible = true; } }; // Defensive: ensure only one doc is visible at a time traveler.showDocuments = function (show) { for (var i = 0; i < traveler.documents.length; i++) { traveler.documents[i].visible = false; } if (show) { if (traveler.currentDocIndex === 1) { permit.visible = true; } else { passport.visible = true; } } }; // On day 2, only allow forceInfoError if info actually matches (simulate system error only on correct info) if (day === 2) { // Only set forceInfoError if all permit fields match the traveler's answers var infoMatches = permitPurpose === purpose && permitOccupation === occupation && permitDuration === duration; traveler.forceInfoError = infoMatches && Math.random() < 0.2; // 20% chance only if info matches } else { traveler.forceInfoError = false; } } // Hide docs initially traveler.showDocuments(false); // Localize all document fields and info popups for this traveler if (typeof updateAllLocalizedText === "function") updateAllLocalizedText(); return traveler; } // 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; } // On day 5 and multiples of 5, block entry for blacklisted names if (day % 5 === 0 && day > 0 && blacklistNames && blacklistNames.length > 0) { var pass = getDoc(traveler, 'passport'); if (pass && pass.fields && blacklistNames.indexOf(pass.fields.name) !== -1) { 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 }); } // Subtle animated feedback flash for polish feedbackTxt.alpha = 0.2; feedbackTxt.visible = true; tween(feedbackTxt, { alpha: 1 }, { duration: 180, easing: tween.cubicOut }); if (lastFeedbackTimeout) LK.clearTimeout(lastFeedbackTimeout); lastFeedbackTimeout = LK.setTimeout(function () { tween(feedbackTxt, { alpha: 0.2 }, { duration: 200, easing: tween.cubicIn, onFinish: function onFinish() { 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) { // Play end of day bell sound if (typeof LK.getSound === "function") { var endDaySound = LK.getSound('end_day_bell'); if (endDaySound && typeof endDaySound.play === "function") endDaySound.play(); } // 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; popupBg.alpha = 0.97; // Slight transparency for polish endOfDayPopup.addChild(popupBg); // Decorative border for popup (subtle polish) var border = new Text2("āāāāāāāāāāāāāāāāāāāāāāāāāāāā", { size: 48, fill: 0xcccccc }); border.anchor.set(0.5, 0); border.x = 2048 / 2; border.y = 2732 / 2 - 310; endOfDayPopup.addChild(border); // Title var popupTitle = new Text2("Day " + day + " Summary", { size: 100, fill: 0x222222, fontWeight: "bold" }); popupTitle.anchor.set(0.5, 0); popupTitle.x = 2048 / 2; popupTitle.y = 2732 / 2 - 350; endOfDayPopup.addChild(popupTitle); // Stats (with icons for polish) 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: 64, 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); // Decorative border for popup (bottom) var border2 = new Text2("āāāāāāāāāāāāāāāāāāāāāāāāāāāā", { size: 48, fill: 0xcccccc }); border2.anchor.set(0.5, 0); border2.x = 2048 / 2; border2.y = 2732 / 2 + 120; endOfDayPopup.addChild(border2); // Continue button var continueBtn = LK.getAsset('stamp_next', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2 + 250 }); continueBtn.width = 340; continueBtn.height = 170; endOfDayPopup.addChild(continueBtn); var continueTxt = new Text2("Next Day ā¶", { size: 90, 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 endOfDayPopup.alpha = 0; game.addChild(endOfDayPopup); tween(endOfDayPopup, { alpha: 1 }, { duration: 400, easing: tween.cubicOut }); // Play a soft popup sound if available if (typeof LK.getSound === "function") { var sfx = LK.getSound('next_btn'); if (sfx && typeof sfx.play === "function") sfx.play(); } // 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 (clockBg) clockBg.visible = false; if (dayTxt) dayTxt.visible = false; if (feedbackTxt) feedbackTxt.visible = false; // --- Family status screen variables --- if (typeof familyMembers === "undefined") { var familyMembers = [{ name: "Spouse", status: "well", hunger: 0, sick: false, cold: false, dead: false }, { name: "Child 1", status: "well", hunger: 0, sick: false, cold: false, dead: false }, { name: "Child 2", status: "well", hunger: 0, sick: false, cold: false, dead: false }, { name: "Mother-in-law", status: "well", hunger: 0, sick: false, cold: false, dead: false }, { name: "Father-in-law", status: "well", hunger: 0, sick: false, cold: false, dead: false }]; var familyScreen = null; } // 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); // --- Show family status screen with black background --- if (familyScreen && familyScreen.parent) familyScreen.parent.removeChild(familyScreen); familyScreen = new Container(); // Black background, wider and taller var famBg = LK.getAsset('bg_info_popup', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2 }); famBg.width = 1700; famBg.height = 1400; famBg.tint = 0x111111; famBg.alpha = 0.98; familyScreen.addChild(famBg); // Decorative border for polish var famBorder = new Text2("āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā", { size: 60, fill: 0x444444 }); famBorder.anchor.set(0.5, 0); famBorder.x = 2048 / 2; famBorder.y = 2732 / 2 - 570; familyScreen.addChild(famBorder); // Title var famTitle = new Text2("šŖ Family Status", { size: 120, fill: 0xffffff, fontWeight: "bold" }); famTitle.anchor.set(0.5, 0); famTitle.x = 2048 / 2; famTitle.y = 2732 / 2 - 600; familyScreen.addChild(famTitle); // --- Selectable slots for spending money --- var slotOptions = [{ key: "rent", label: "Rent", cost: 10 }, { key: "food", label: "Food", cost: 2 }, { key: "heating", label: "Heating", cost: 3 }]; // If anyone in the family is sick, add "medicine" var anySick = false; for (var i = 0; i < familyMembers.length; i++) { if (familyMembers[i].sick) anySick = true; } if (anySick) { slotOptions.push({ key: "medicine", label: "Medicine", cost: 5 }); } // Track selected slots var selectedSlots = {}; for (var i = 0; i < slotOptions.length; i++) { selectedSlots[slotOptions[i].key] = false; } var slotY = 2732 / 2 - 700; var slotX = 2048 / 2 + 400; var slotStep = 120; var slotBtns = []; for (var i = 0; i < slotOptions.length; i++) { (function (i) { var slot = slotOptions[i]; var btn = LK.getAsset('bg_info_popup', { anchorX: 0, anchorY: 0, x: slotX, y: slotY + i * slotStep }); btn.width = 320; btn.height = 100; btn.tint = 0x222222; familyScreen.addChild(btn); var txt = new Text2(slot.label + " (" + slot.cost + " RM)", { size: 60, fill: 0xffffff }); txt.anchor.set(0, 0.5); txt.x = slotX + 30; txt.y = slotY + i * slotStep + 50; familyScreen.addChild(txt); // Tick mark var tick = new Text2("ā", { size: 60, fill: 0x4caf50 }); tick.anchor.set(0, 0.5); tick.x = slotX + 250; tick.y = slotY + i * slotStep + 50; tick.visible = false; familyScreen.addChild(tick); slotBtns.push({ btn: btn, tick: tick, key: slot.key, cost: slot.cost }); // Add click handler btn.interactive = true; btn.down = function (x, y, obj) { selectedSlots[slot.key] = !selectedSlots[slot.key]; tick.visible = selectedSlots[slot.key]; // Subtle highlight for polish btn.tint = selectedSlots[slot.key] ? 0x4caf50 : 0x222222; }; })(i); } // Update family member status based on money and random events for (var i = 0; i < familyMembers.length; i++) { var member = familyMembers[i]; // Increase hunger each day, random chance of sickness/cold member.hunger = Math.min(3, member.hunger + 1); if (Math.random() < 0.15) member.sick = true; if (Math.random() < 0.10) member.cold = true; // Remove auto-spending, now handled by slot selection // Update status if (member.hunger >= 3) member.status = "starving";else if (member.hunger === 2) member.status = "hungry";else member.status = "well"; if (member.sick) member.status = "sick"; if (member.cold) member.status = "cold"; // Death if too hungry or sick for too long if (member.hunger >= 3 && Math.random() < 0.5 || member.sick && Math.random() < 0.2) { member.dead = true; member.status = "dead"; } } // Show family members, with avatars and more details var yStart = 2732 / 2 - 500; var yStep = 220; var xStart = 2048 / 2 - 800; var avatarSize = 200; var labelX = xStart + avatarSize + 80; for (var i = 0; i < familyMembers.length; i++) { var member = familyMembers[i]; var color = member.dead ? 0xff0000 : member.status === "sick" ? 0xffa500 : member.status === "hungry" ? 0xffff00 : member.status === "cold" ? 0x00bfff : 0xffffff; // Choose avatar asset by member type, use new photos for family members var avatarAsset = "person"; if (member.name.indexOf("Spouse") !== -1) avatarAsset = "family_spouse"; if (member.name.indexOf("Child 1") !== -1) avatarAsset = "family_child1"; if (member.name.indexOf("Child 2") !== -1) avatarAsset = "family_child2"; if (member.name.indexOf("Mother") !== -1) avatarAsset = "family_motherinlaw"; if (member.name.indexOf("Father") !== -1) avatarAsset = "family_fatherinlaw"; var avatar = LK.getAsset(avatarAsset, { anchorX: 0.5, anchorY: 0.5, x: xStart + avatarSize / 2, y: yStart + i * yStep + avatarSize / 2 }); avatar.width = avatarSize; avatar.height = avatarSize; if (member.dead) avatar.alpha = 0.3; familyScreen.addChild(avatar); // Name and status with icons var statusIcon = member.dead ? "ā ļø" : member.status === "sick" ? "š¤" : member.status === "hungry" ? "š" : member.status === "cold" ? "š„¶" : "š"; var txt = new Text2(statusIcon + " " + member.name + ": " + (member.dead ? "DEAD" : member.status.toUpperCase()), { size: 90, fill: color }); txt.anchor.set(0, 0); txt.x = labelX; txt.y = yStart + i * yStep; familyScreen.addChild(txt); // Details: hunger, sick, cold, alive (with icons) var details = ""; if (!member.dead) { details += "š½ļø Hunger: " + member.hunger + " "; details += "š¤ Sick: " + (member.sick ? "Yes" : "No") + " "; details += "š„¶ Cold: " + (member.cold ? "Yes" : "No") + " "; details += "š¢ Alive: Yes"; } else { details += "š½ļø Hunger: - š¤ Sick: - š„¶ Cold: - š“ Alive: No"; } var detailsTxt = new Text2(details, { size: 64, fill: member.dead ? 0xff0000 : 0xcccccc }); detailsTxt.anchor.set(0, 0); detailsTxt.x = labelX; detailsTxt.y = yStart + i * yStep + 100; familyScreen.addChild(detailsTxt); } // Show remaining money var moneyTxt = new Text2("Money: " + money + " RM", { size: 80, fill: 0xffffff }); moneyTxt.anchor.set(0.5, 0); moneyTxt.x = 2048 / 2; moneyTxt.y = 2732 / 2 + 540; familyScreen.addChild(moneyTxt); // Add a legend for status colors var legendY = 2732 / 2 + 650; var legendX = 2048 / 2 - 500; var legendItems = [{ label: "Well", color: 0xffffff, icon: "š" }, { label: "Hungry", color: 0xffff00, icon: "š" }, { label: "Sick", color: 0xffa500, icon: "š¤" }, { label: "Cold", color: 0x00bfff, icon: "š„¶" }, { label: "Dead", color: 0xff0000, icon: "ā ļø" }]; for (var li = 0; li < legendItems.length; li++) { var l = legendItems[li]; var dot = new Text2(l.icon, { size: 60, fill: l.color }); dot.anchor.set(0, 0.5); dot.x = legendX + li * 220; dot.y = legendY; familyScreen.addChild(dot); var label = new Text2(l.label, { size: 44, fill: 0xffffff }); label.anchor.set(0, 0.5); label.x = dot.x + 50; label.y = legendY; familyScreen.addChild(label); } // Continue button for next day var famContinueBtn = LK.getAsset('stamp_next', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2 + 800 }); famContinueBtn.width = 340; famContinueBtn.height = 170; familyScreen.addChild(famContinueBtn); var famContinueTxt = new Text2("Continue ā¶", { size: 90, fill: 0xffffff }); famContinueTxt.anchor.set(0.5, 0.5); famContinueTxt.x = famContinueBtn.width / 2; famContinueTxt.y = famContinueBtn.height / 2; famContinueBtn.addChild(famContinueTxt); familyScreen.alpha = 0; game.addChild(familyScreen); tween(familyScreen, { alpha: 1 }, { duration: 400, easing: tween.cubicOut }); // Play a soft popup sound if available if (typeof LK.getSound === "function") { var sfx = LK.getSound('next_btn'); if (sfx && typeof sfx.play === "function") sfx.play(); } // If any family member is dead, show game over var anyDead = false; for (var i = 0; i < familyMembers.length; i++) { if (familyMembers[i].dead) anyDead = true; } if (anyDead) { var overTxt = new Text2("A family member has died!\nGame Over.", { size: 100, fill: 0xff0000 }); overTxt.anchor.set(0.5, 0.5); overTxt.x = 2048 / 2; overTxt.y = 2732 / 2 - 700; familyScreen.addChild(overTxt); } // Handler for family screen continue var famHandler = function famHandler(x, y, obj) { var left = famContinueBtn.x - famContinueBtn.width / 2, right = famContinueBtn.x + famContinueBtn.width / 2; var top = famContinueBtn.y - famContinueBtn.height / 2, bottom = famContinueBtn.y + famContinueBtn.height / 2; if (x >= left && x <= right && y >= top && y <= bottom) { if (familyScreen && familyScreen.parent) familyScreen.parent.removeChild(familyScreen); // If any dead, end game if (anyDead) { LK.setTimeout(function () { LK.showGameOver(); }, 500); return; } // --- Apply slot spending --- var totalSpent = 0; for (var i = 0; typeof slotBtns !== "undefined" && i < slotBtns.length; i++) { var slot = slotBtns[i]; if (typeof selectedSlots !== "undefined" && selectedSlots[slot.key]) { // Deduct money for each selected need, if enough money if (money >= slot.cost) { money -= slot.cost; totalSpent += slot.cost; // Apply effects if (slot.key === "food") { for (var j = 0; j < familyMembers.length; j++) familyMembers[j].hunger = 0; } if (slot.key === "heating") { for (var j = 0; j < familyMembers.length; j++) familyMembers[j].cold = false; } if (slot.key === "medicine") { for (var j = 0; j < familyMembers.length; j++) familyMembers[j].sick = false; } // Rent: no effect, but must be paid } // If not enough money, do not apply effect or deduct } } // No need to deduct totalSpent again, as we deduct per-need above // 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 (clockBg) clockBg.visible = true; if (dayTxt) dayTxt.visible = true; // Advance day and reset state day++; travelersProcessed = 0; correctDecisions = 0; wrongDecisions = 0; LK.setScore(0); // Reset score for each episode 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 family screen game.down = famHandler; return; } }; // Override game.down to only handle popup game.down = popupHandler; return; } // On day 5 and multiples of 5, generate blacklist names and update button if (day % 5 === 0 && day > 0) { blacklistNames = generateBlacklistNames(); updateBlacklistBtn(); } else { if (blacklistBtn && blacklistBtn.parent) blacklistBtn.parent.removeChild(blacklistBtn); blacklistBtn = null; blacklistNames = []; } // 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 with a gentle bounce for polish currentTraveler.scaleX = 0.92; currentTraveler.scaleY = 0.92; tween(currentTraveler, { x: 2048 / 2, scaleX: 1.04, scaleY: 1.04 }, { duration: 420, easing: tween.cubicOut, onFinish: function onFinish() { // Subtle bounce back to normal scale tween(currentTraveler, { scaleX: 1, scaleY: 1 }, { duration: 180, easing: tween.cubicIn, 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(typeof t === "function" ? t("Day") + " " + day : "Day " + day); if (ruleTxt) { var rulesToday = getRulesForDay(day); var ruleStr = ""; for (var i = 0; i < rulesToday.length; i++) { // Localize rule description if available var desc = rulesToday[i].desc; if (typeof t === "function" && LOCALIZATION && LOCALIZATION[desc] && Array.isArray(LOCALIZATION[desc])) { desc = t(desc); } // Special handling for Day 2: add extra instructions for entry permit and document toggling if (day === 2 && i === 1) { // Add extra translated info for entry permit and document toggling var extraInfo = ""; if (selectedLanguageIdx === 0) { // English extraInfo = "\n⢠Non-German citizens must present an entry permit.\n⢠The entry permit contains information not in the passport (purpose, duration, occupation).\n⢠Tap the person to match their spoken answers with the entry permit.\n⢠If the info matches, allow entry. If not, deny entry.\n⢠Tap the passport to show/hide the entry permit."; } else if (selectedLanguageIdx === 1) { // Turkish extraInfo = "\n⢠Alman vatandaÅı olmayanların giriÅ izni gƶstermesi gerekir.\n⢠GiriÅ izninde pasaportta olmayan bilgiler bulunur (geliÅ amacı, kalıŠsüresi, meslek).\n⢠KiÅiye tıklayarak konuÅma kaydını giriÅ izniyle karÅılaÅtırın.\n⢠Bilgiler doÄruysa geƧiÅe izin verin, yanlıÅsa reddedin.\n⢠GiriÅ iznini aƧmak iƧin pasaporta tıklayın, tekrar pasaporta dƶnmek iƧin giriÅ iznine tekrar tıklayın."; } else if (selectedLanguageIdx === 2) { // German extraInfo = "\n⢠Nicht-deutsche Staatsbürger benƶtigen eine Einreisegenehmigung.\n⢠Die Einreisegenehmigung enthƤlt Informationen, die nicht im Pass stehen (Zweck, Aufenthaltsdauer, Beruf).\n⢠Tippen Sie auf die Person, um die gesprochenen Antworten mit der Einreisegenehmigung abzugleichen.\n⢠Stimmen die Angaben überein, Einlass gewƤhren. Andernfalls verweigern.\n⢠Zum Anzeigen des Einreisedokuments auf den Pass tippen, zurück zum Pass durch erneutes Tippen auf das Dokument."; } else if (selectedLanguageIdx === 3) { // French extraInfo = "\n⢠Les citoyens non allemands doivent prĆ©senter un permis d'entrĆ©e.\n⢠Le permis d'entrĆ©e contient des informations non inscrites sur le passeport (motif, durĆ©e, profession).\n⢠Touchez la personne pour comparer ses rĆ©ponses orales avec le permis d'entrĆ©e.\n⢠Si les informations correspondent, laissez passer. Sinon, refusez l'entrĆ©e.\n⢠Touchez le passeport pour afficher/masquer le permis d'entrĆ©e."; } else if (selectedLanguageIdx === 4) { // Russian extraInfo = "\n⢠ŠŃажГане, не ŃŠ²Š»ŃŃŃŠøŠµŃŃ Š½ŠµŠ¼ŃŠ°Š¼Šø, Š“Š¾Š»Š¶Š½Ń ŠæŃŠµŠ“ŃŃŠ²ŠøŃŃ Š²ŃŠµŠ·Š“ное ŃŠ°Š·ŃŠµŃŠµŠ½ŠøŠµ.\n⢠ŠŃезГное ŃŠ°Š·ŃŠµŃŠµŠ½ŠøŠµ ŃŠ¾Š“ŠµŃŠ¶ŠøŃ ŠøŠ½ŃŠ¾ŃŠ¼Š°ŃŠøŃ, ŠŗŠ¾ŃŠ¾Ńой Š½ŠµŃ в ŠæŠ°ŃŠæŠ¾ŃŃŠµ (ŃŠµŠ»Ń, ŃŃŠ¾Šŗ, ŠæŃŠ¾ŃеŃŃŠøŃ).\nā¢ ŠŠ°Š¶Š¼ŠøŃе на ŃŠµŠ»Š¾Š²ŠµŠŗŠ°, ŃŃŠ¾Š±Ń ŃŠ¾ŠæŠ¾ŃŃŠ°Š²ŠøŃŃ ŠµŠ³Š¾ ŃŃŃŠ½Ńе Š¾ŃвеŃŃ Ń Š²ŃŠµŠ·Š“Š½ŃŠ¼ Š“Š¾ŠŗŃŠ¼ŠµŠ½Ńом.\n⢠ŠŃли ŠøŠ½ŃŠ¾ŃŠ¼Š°ŃŠøŃ ŃŠ¾Š²ŠæŠ°Š“Š°ŠµŃ ā ŠæŃопŃŃŃŠøŃе, ŠµŃŠ»Šø Š½ŠµŃ ā Š¾ŃŠŗŠ°Š¶ŠøŃŠµ.\nā¢ ŠŠ»Ń показа Š²ŃезГного Š“Š¾ŠŗŃŠ¼ŠµŠ½Ńа Š½Š°Š¶Š¼ŠøŃе на ŠæŠ°ŃŠæŠ¾ŃŃ, ŃŃŠ¾Š±Ń Š²ŠµŃŠ½ŃŃŃŃŃ ā ŃŠ½Š¾Š²Š° Š½Š°Š¶Š¼ŠøŃŠµ на Š“Š¾ŠŗŃŠ¼ŠµŠ½Ń."; } desc += extraInfo; } ruleStr += i + 1 + ". " + 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++; // On day 2, if traveler has a permit, check infoMatched flag for correctness var allowed = isTravelerAllowed(currentTraveler); var correct = approved === allowed; // Special handling for day 2: if traveler has a permit, use infoMatched for correctness if (day === 2) { var permit = null; for (var i = 0; i < currentTraveler.documents.length; i++) { if (currentTraveler.documents[i].docType === "permit") { permit = currentTraveler.documents[i]; break; } } // Only check infoMatched if traveler is non-German and has a permit var pass = getDoc(currentTraveler, 'passport'); if (permit && pass && pass.fields.nationality !== "German") { // infoMatched is set by the player using the Match/No Match buttons // If not set, treat as not matched (player must use the buttons) if (typeof currentTraveler.infoMatched !== "undefined") { correct = approved && currentTraveler.infoMatched || !approved && !currentTraveler.infoMatched; allowed = currentTraveler.infoMatched; // for feedback } } } 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); // --- Police blacklist paper for day 5 and multiples of 5 --- var blacklistPaper = null; var blacklistNames = []; var blacklistVisible = false; var blacklistBtn = null; // Helper to generate blacklist names (3 random names for the day) function generateBlacklistNames() { var names = []; // Use only unique names var used = {}; while (names.length < 3) { var idx = Math.floor(Math.random() * NAMES.length); var n = NAMES[idx]; if (!used[n]) { names.push(n); used[n] = true; } } return names; } // Helper to show/hide blacklist paper function showBlacklistPaper(show) { if (show) { if (blacklistPaper && blacklistPaper.parent) blacklistPaper.parent.removeChild(blacklistPaper); blacklistPaper = new Container(); // Background var bg = LK.getAsset('bg_info_popup', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2 }); bg.width = 700; bg.height = 600; blacklistPaper.addChild(bg); // Title var title = new Text2("Police Blacklist", { size: 70, fill: 0x222222 }); title.anchor.set(0.5, 0); title.x = 2048 / 2; title.y = 2732 / 2 - 250; blacklistPaper.addChild(title); // List names var y = 2732 / 2 - 120; for (var i = 0; i < blacklistNames.length; i++) { var txt = new Text2(i + 1 + ". " + blacklistNames[i], { size: 56, fill: 0x333333 }); txt.anchor.set(0.5, 0); txt.x = 2048 / 2; txt.y = y + i * 80; blacklistPaper.addChild(txt); } // Close button var closeBtn = LK.getAsset('stamp_next', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2 + 220 }); blacklistPaper.addChild(closeBtn); var closeTxt = new Text2("Close", { size: 60, fill: 0xffffff }); closeTxt.anchor.set(0.5, 0.5); closeTxt.x = closeBtn.width / 2; closeTxt.y = closeBtn.height / 2; closeBtn.addChild(closeTxt); // Handler blacklistPaper.interactive = true; blacklistPaper.down = function (x, y, obj) { var left = closeBtn.x - closeBtn.width / 2, right = closeBtn.x + closeBtn.width / 2; var top = closeBtn.y - closeBtn.height / 2, bottom = closeBtn.y + closeBtn.height / 2; if (x >= left && x <= right && y >= top && y <= bottom) { if (blacklistPaper && blacklistPaper.parent) blacklistPaper.parent.removeChild(blacklistPaper); blacklistVisible = false; } }; game.addChild(blacklistPaper); blacklistVisible = true; } else { if (blacklistPaper && blacklistPaper.parent) blacklistPaper.parent.removeChild(blacklistPaper); blacklistVisible = false; } } // Add a button on the table to access blacklist (only on day 5+ and multiples of 5) function updateBlacklistBtn() { if (blacklistBtn && blacklistBtn.parent) blacklistBtn.parent.removeChild(blacklistBtn); if (day % 5 === 0 && day > 0) { blacklistBtn = LK.getAsset('bg_info_popup', { anchorX: 0, anchorY: 0, x: table.x + 600, y: table.y + 80 }); blacklistBtn.width = 220; blacklistBtn.height = 100; blacklistBtn.tint = 0x222244; var txt = new Text2("Blacklist", { size: 44, fill: 0xffffff }); txt.anchor.set(0.5, 0.5); txt.x = blacklistBtn.width / 2; txt.y = blacklistBtn.height / 2; blacklistBtn.addChild(txt); blacklistBtn.interactive = true; blacklistBtn.down = function (x, y, obj) { if (!blacklistVisible) showBlacklistPaper(true); }; game.addChild(blacklistBtn); } } // Score display scoreTxt = new Text2("0", { size: 100, fill: 0x222222 }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // Clock background asset (changeable in assets section) // Move to right side of the screen var clockBg = LK.getAsset('bg_clock', { anchorX: 1, anchorY: 0, x: 2048 - 110, y: 10 }); LK.gui.top.addChild(clockBg); // Clock display (centered in clock background) var clockTxt = new Text2("09:00", { size: 90, fill: 0x333366 }); // Center the clock text in the clock background clockTxt.anchor.set(0.5, 0.5); clockTxt.x = clockBg.x - clockBg.width / 2; clockTxt.y = clockBg.y + clockBg.height / 2; 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); // Subtle animated highlight for approve button approveBtn._pulseTween = null; function pulseApproveBtn() { if (approveBtn._pulseTween) return; approveBtn._pulseTween = tween(approveBtn, { scaleX: 1.08, scaleY: 1.08 }, { duration: 400, yoyo: true, repeat: 1, easing: tween.cubicInOut, onFinish: function onFinish() { approveBtn.scaleX = 1; approveBtn.scaleY = 1; approveBtn._pulseTween = null; } }); } approveBtn.interactive = true; approveBtn.down = function () { pulseApproveBtn(); }; // 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); // Subtle animated highlight for deny button denyBtn._pulseTween = null; function pulseDenyBtn() { if (denyBtn._pulseTween) return; denyBtn._pulseTween = tween(denyBtn, { scaleX: 1.08, scaleY: 1.08 }, { duration: 400, yoyo: true, repeat: 1, easing: tween.cubicInOut, onFinish: function onFinish() { denyBtn.scaleX = 1; denyBtn.scaleY = 1; denyBtn._pulseTween = null; } }); } denyBtn.interactive = true; denyBtn.down = function () { pulseDenyBtn(); }; // 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); // Set all currently loaded sounds/music to the new volume if (typeof LK.getSound === "function") { var soundIds = ['next_btn', 'traveler_arrive']; for (var i = 0; i < soundIds.length; i++) { var s = LK.getSound(soundIds[i]); if (s && typeof s.setVolume === "function") s.setVolume(soundVolume); } } if (typeof LK.getMusic === "function") { var musicIds = ['main_menu_music']; for (var i = 0; i < musicIds.length; i++) { var m = LK.getMusic(musicIds[i]); if (m && typeof m.setVolume === "function") m.setVolume(soundVolume); } } if (soundVolume <= 0 && typeof LK.stopMusic === "function") LK.stopMusic(); } // 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/Exit button asset keys by language var playBtnAssets = ['btn_play_en', 'btn_play_tr', 'btn_play_de', 'btn_play_fr', 'btn_play_ru']; var exitBtnAssets = ['btn_exit_en', 'btn_exit_tr', 'btn_exit_de', 'btn_exit_fr', 'btn_exit_ru']; // Helper to get asset id for current language function getPlayBtnAsset() { // Defensive: ensure selectedLanguageIdx is a valid number and in range var idx = typeof selectedLanguageIdx === "number" && selectedLanguageIdx >= 0 && selectedLanguageIdx < playBtnAssets.length ? selectedLanguageIdx : 0; if (Array.isArray(playBtnAssets) && playBtnAssets.length > 0) { return typeof playBtnAssets[idx] !== "undefined" ? playBtnAssets[idx] : playBtnAssets[0]; } else { return undefined; } } function getExitBtnAsset() { // Defensive: ensure selectedLanguageIdx is a valid number and in range var idx = typeof selectedLanguageIdx === "number" && selectedLanguageIdx >= 0 && selectedLanguageIdx < exitBtnAssets.length ? selectedLanguageIdx : 0; if (Array.isArray(exitBtnAssets) && exitBtnAssets.length > 0) { return typeof exitBtnAssets[idx] !== "undefined" ? exitBtnAssets[idx] : exitBtnAssets[0]; } else { return undefined; } } // 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(getPlayBtnAsset(), { 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(getExitBtnAsset(), { anchorX: 0, anchorY: 0.5, x: leftMargin, y: topStart + playBtn.height + buttonSpacing + settingsBtn.height + buttonSpacing }); mainMenu.addChild(quitBtn); // Helper to update main menu button assets on language change function updateMenuButtonAssets() { var playAsset = getPlayBtnAsset(); var exitAsset = getExitBtnAsset(); // Remove old play/exit buttons if (playBtn && playBtn.parent) playBtn.parent.removeChild(playBtn); if (quitBtn && quitBtn.parent) quitBtn.parent.removeChild(quitBtn); // Recreate with new assets // Defensive: Only create playBtn if playAsset is defined and is a string if (typeof playAsset === "string" && playAsset.length > 0) { playBtn = LK.getAsset(playAsset, { anchorX: 0, anchorY: 0.5, x: leftMargin, y: topStart }); mainMenu.addChildAt(playBtn, 0); } else { playBtn = null; } // Defensive: Only create quitBtn if exitAsset is defined and is a string if (typeof exitAsset === "string" && exitAsset.length > 0) { quitBtn = LK.getAsset(exitAsset, { anchorX: 0, anchorY: 0.5, x: leftMargin, y: topStart + (playBtn && playBtn.height ? playBtn.height : 0) + buttonSpacing + settingsBtn.height + buttonSpacing }); mainMenu.addChild(quitBtn); } else { quitBtn = null; } } // 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"]; // Always default to English on first load, and always show "English" at first var selectedLanguageIdx = 0; if (typeof storage.selectedLanguageIdx === "number" && storage.selectedLanguageIdx >= 0 && storage.selectedLanguageIdx < languageOptions.length) { selectedLanguageIdx = storage.selectedLanguageIdx; } else { selectedLanguageIdx = 0; storage.selectedLanguageIdx = 0; } // Always update all UI/game text to selected language on language change 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", "ŠŠ°Š»ŠµŠµ"], // Passport field labels and static inscriptions "Passport": ["Passport", "Pasaport", "Reisepass", "Passeport", "ŠŠ°ŃпоŃŃ"], "Name:": ["Name:", "İsim:", "Name:", "NomĀ :", "ŠŠ¼Ń:"], "Nationality:": ["Nationality:", "Uyruk:", "NationalitƤt:", "Nationalité :", "ŠŠ°ŃŠøŠ¾Š½Š°Š»ŃŠ½Š¾ŃŃŃ:"], "City:": ["City:", "Åehir:", "Stadt:", "VilleĀ :", "ŠŠ¾ŃоГ:"], "Dob:": ["Date of Birth:", "DoÄum Tarihi:", "Geburtsdatum:", "Date de naissanceĀ :", "ŠŠ°Ńа ŃŠ¾Š¶Š“ениŃ:"], "Gender:": ["Gender:", "Cinsiyet:", "Geschlecht:", "SexeĀ :", "ŠŠ¾Š»:"], "Valid:": ["Valid:", "GeƧerli:", "Gültig:", "ValideĀ :", "ŠŠµŠ¹ŃŃŠ²ŠøŃелен:"], // Traveler info random strings "I am here to visit family.": ["I am here to visit family.", "Ailemi ziyaret etmeye geldim.", "Ich bin hier, um Familie zu besuchen.", "Je suis ici pour rendre visite Ć ma famille.", "ŠÆ ŠæŃŠøŠµŃ ал навеŃŃŠøŃŃ ŃŠµŠ¼ŃŃ."], "I am traveling for work.": ["I am traveling for work.", "İŠiƧin seyahat ediyorum.", "Ich reise geschƤftlich.", "Je voyage pour le travail.", "ŠÆ ŠæŃŃŠµŃеŃŃŠ²ŃŃ ŠæŠ¾ ŃŠ°Š±Š¾Ńе."], "I am just passing through.": ["I am just passing through.", "Sadece geƧiyorum.", "Ich bin nur auf der Durchreise.", "Je ne fais que passer.", "ŠÆ ŠæŃŠ¾ŃŃŠ¾ ŠæŃŠ¾ŠµŠ·Š“ом."], "I am here for business.": ["I am here for business.", "İŠiƧin buradayım.", "Ich bin geschƤftlich hier.", "Je suis ici pour affaires.", "ŠÆ зГеŃŃ ŠæŠ¾ Гелам."], "I am here for a holiday.": ["I am here for a holiday.", "Tatil iƧin buradayım.", "Ich bin im Urlaub hier.", "Je suis ici en vacances.", "ŠÆ зГеŃŃ Š² Š¾ŃŠæŃŃŠŗŠµ."], "I am here to see a doctor.": ["I am here to see a doctor.", "Doktora gƶrünmeye geldim.", "Ich bin hier, um einen Arzt zu sehen.", "Je suis ici pour voir un mĆ©decin.", "ŠÆ ŠæŃŠøŠµŃ ал Šŗ Š²ŃŠ°ŃŃ."], "I am here to study.": ["I am here to study.", "ĆalıÅmaya geldim.", "Ich bin hier zum Studieren.", "Je suis ici pour Ć©tudier.", "ŠÆ ŠæŃŠøŠµŃ ал ŃŃŠøŃŃŃŃ."], "I am here to help relatives.": ["I am here to help relatives.", "Akrabalarıma yardım etmeye geldim.", "Ich bin hier, um Verwandten zu helfen.", "Je suis ici pour aider des proches.", "ŠÆ ŠæŃŠøŠµŃ ал помоŃŃ ŃŠ¾Š“ŃŃŠ²ŠµŠ½Š½ŠøŠŗŠ°Š¼."], "I am here for a funeral.": ["I am here for a funeral.", "Cenaze iƧin buradayım.", "Ich bin zu einer Beerdigung hier.", "Je suis ici pour des funĆ©railles.", "ŠÆ ŠæŃŠøŠµŃ ал на ŠæŠ¾Ń Š¾ŃŠ¾Š½Ń."], "I am here for a wedding.": ["I am here for a wedding.", "DüÄün iƧin buradayım.", "Ich bin zu einer Hochzeit hier.", "Je suis ici pour un mariage.", "ŠÆ ŠæŃŠøŠµŃ ал на ŃŠ²Š°Š“ŃŠ±Ń."], "I am a farmer.": ["I am a farmer.", "Ben ƧiftƧiyim.", "Ich bin Bauer.", "Je suis agriculteur.", "ŠÆ ŃŠµŃмеŃ."], "I am a teacher.": ["I am a teacher.", "Ben ƶÄretmenim.", "Ich bin Lehrer.", "Je suis enseignant.", "ŠÆ ŃŃŠøŃелŃ."], "I am a merchant.": ["I am a merchant.", "Ben tüccarım.", "Ich bin HƤndler.", "Je suis marchand.", "ŠÆ ŃŠ¾ŃговеŃ."], "I am a soldier.": ["I am a soldier.", "Ben askerim.", "Ich bin Soldat.", "Je suis soldat.", "ŠÆ ŃŠ¾Š»Š“аŃ."], "I am a doctor.": ["I am a doctor.", "Ben doktorum.", "Ich bin Arzt.", "Je suis mĆ©decin.", "ŠÆ Š²ŃŠ°Ń."], "I am a student.": ["I am a student.", "Ben ƶÄrenciyim.", "Ich bin Student.", "Je suis Ć©tudiant.", "ŠÆ ŃŃŃŠ“енŃ."], "I am a tailor.": ["I am a tailor.", "Ben terziyim.", "Ich bin Schneider.", "Je suis tailleur.", "ŠÆ поŃŃŠ½Š¾Š¹."], "I am a baker.": ["I am a baker.", "Ben fırıncıyım.", "Ich bin BƤcker.", "Je suis boulanger.", "ŠÆ пекаŃŃ."], "I am a carpenter.": ["I am a carpenter.", "Ben marangozum.", "Ich bin Tischler.", "Je suis charpentier.", "ŠÆ ŠæŠ»Š¾ŃŠ½ŠøŠŗ."], "I am a musician.": ["I am a musician.", "Ben müzisyenim.", "Ich bin Musiker.", "Je suis musicien.", "ŠÆ Š¼ŃŠ·ŃканŃ."], "I will stay for 2 days.": ["I will stay for 2 days.", "2 gün kalacaÄım.", "Ich bleibe 2 Tage.", "Je resterai 2 jours.", "ŠÆ оŃŃŠ°Š½ŃŃŃ Š½Š° 2 ГнŃ."], "I am here for a week.": ["I am here for a week.", "Bir hafta buradayım.", "Ich bin eine Woche hier.", "Je suis ici pour une semaine.", "ŠÆ зГеŃŃ Š½Š° неГелŃ."], "Just one night.": ["Just one night.", "Sadece bir gece.", "Nur eine Nacht.", "Juste une nuit.", "Š¢Š¾Š»ŃŠŗŠ¾ Š¾Š“Š½Ń Š½Š¾ŃŃ."], "I will stay for 3 days.": ["I will stay for 3 days.", "3 gün kalacaÄım.", "Ich bleibe 3 Tage.", "Je resterai 3 jours.", "ŠÆ оŃŃŠ°Š½ŃŃŃ Š½Š° 3 ГнŃ."], "I am here for a month.": ["I am here for a month.", "Bir ay buradayım.", "Ich bin einen Monat hier.", "Je suis ici pour un mois.", "ŠÆ зГеŃŃ Š½Š° меŃŃŃ."], "Only a few hours.": ["Only a few hours.", "Sadece birkaƧ saat.", "Nur ein paar Stunden.", "Seulement quelques heures.", "Š¢Š¾Š»ŃŠŗŠ¾ Š½ŠµŃŠŗŠ¾Š»Ńко ŃŠ°Ńов."], "I will stay for 10 days.": ["I will stay for 10 days.", "10 gün kalacaÄım.", "Ich bleibe 10 Tage.", "Je resterai 10 jours.", "ŠÆ оŃŃŠ°Š½ŃŃŃ Š½Š° 10 Гней."], "I am here for the season.": ["I am here for the season.", "Sezon boyunca buradayım.", "Ich bin für die Saison hier.", "Je suis ici pour la saison.", "ŠÆ зГеŃŃ Š½Š° ŃŠµŠ·Š¾Š½."], "I will leave tomorrow.": ["I will leave tomorrow.", "Yarın ayrılacaÄım.", "Ich reise morgen ab.", "Je pars demain.", "ŠÆ ŃŠµŠ“Ń Š·Š°Š²ŃŃŠ°."], "I am not sure yet.": ["I am not sure yet.", "Henüz emin deÄilim.", "Ich bin mir noch nicht sicher.", "Je ne sais pas encore.", "ŠÆ ŠµŃŠµ не ŃŠ²ŠµŃен."] }; // Helper to get localized string function t(key, vars) { var idx = selectedLanguageIdx || 0; var arr = typeof LOCALIZATION !== "undefined" && LOCALIZATION && LOCALIZATION[key] ? LOCALIZATION[key] : null; 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")); // Update Play/Exit button assets for selected language if (typeof updateMenuButtonAssets === "function") updateMenuButtonAssets(); settingsBtn && settingsBtn.children && settingsBtn.children[0] && settingsBtn.children[0].setText && settingsBtn.children[0].setText(t("Settings")); // 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 if (dayTxt && typeof dayTxt.setText === "function") dayTxt.setText(t("Day") + " " + day); // Score text if (scoreTxt && typeof scoreTxt.setText === "function") scoreTxt.setText(LK.getScore()); // Rule panel text (handled in updateDayAndRules, but we need to localize rule descriptions) if (ruleTxt && typeof ruleTxt.setText === "function") { var rulesToday = getRulesForDay(day); var ruleStr = ""; for (var i = 0; i < rulesToday.length; i++) { // Try to localize the rule description if present in LOCALIZATION var desc = rulesToday[i].desc; if (typeof t === "function" && LOCALIZATION && LOCALIZATION[desc] && Array.isArray(LOCALIZATION[desc])) desc = t(desc); ruleStr += i + 1 + ". " + desc + "\n"; } ruleTxt.setText(ruleStr); } // Localize all visible document titles and fields if (currentTraveler && currentTraveler.documents) { for (var i = 0; i < currentTraveler.documents.length; i++) { var doc = currentTraveler.documents[i]; if (doc && typeof doc.setup === "function") { // Re-setup the document to update localization doc.setup(doc.docType, doc.fields, doc.isForged); } } } // Localize extra info popup if visible if (currentTraveler && currentTraveler.extraInfoTxt && currentTraveler.extraInfoTxt.visible) { // Remove and recreate the info popup to update localization if (typeof game !== "undefined" && typeof game.down === "function") { // Remove old info popup if (currentTraveler.extraInfoTxt.parent) currentTraveler.extraInfoTxt.parent.removeChild(currentTraveler.extraInfoTxt); currentTraveler.extraInfoTxt = null; // Simulate tap to re-show game.down(currentTraveler.x, currentTraveler.y - 10, {}); } } // Feedback text (if visible) is 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") { LK.setGlobalVolume(soundVolume); } // Set all currently loaded sounds/music to the new volume if (typeof LK.getSound === "function") { var soundIds = ['next_btn', 'traveler_arrive']; for (var i = 0; i < soundIds.length; i++) { var s = LK.getSound(soundIds[i]); if (s && typeof s.setVolume === "function") s.setVolume(soundVolume); } } if (typeof LK.getMusic === "function") { var musicIds = ['main_menu_music']; for (var i = 0; i < musicIds.length; i++) { var m = LK.getMusic(musicIds[i]); if (m && typeof m.setVolume === "function") m.setVolume(soundVolume); } } // Mute all music if volume is 0, otherwise restore music volume if (soundVolume <= 0) { if (typeof LK.stopMusic === "function") LK.stopMusic(); } else { // Optionally, resume music if needed (handled by game flow) } 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") { LK.setGlobalVolume(soundVolume); } // Set all currently loaded sounds/music to the new volume if (typeof LK.getSound === "function") { var soundIds = ['next_btn', 'traveler_arrive']; for (var i = 0; i < soundIds.length; i++) { var s = LK.getSound(soundIds[i]); if (s && typeof s.setVolume === "function") s.setVolume(soundVolume); } } if (typeof LK.getMusic === "function") { var musicIds = ['main_menu_music']; for (var i = 0; i < musicIds.length; i++) { var m = LK.getMusic(musicIds[i]); if (m && typeof m.setVolume === "function") m.setVolume(soundVolume); } } // If volume is now above 0, music can be played again by game flow if (soundVolume <= 0) { if (typeof LK.stopMusic === "function") LK.stopMusic(); } 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) { // If traveler has a permit, show the conversation and allow matching var permit = null; for (var i = 0; i < currentTraveler.documents.length; i++) { if (currentTraveler.documents[i].docType === "permit") { permit = currentTraveler.documents[i]; break; } } if (permit) { // Show conversation popup with info from the permit (in selected language) if (!currentTraveler.extraInfoTxt) { // Use the info from the permit fields (already localized) 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: ") + (permit.fields.purpose || "") + "\n\n" + (typeof t === "function" ? t("Q: What do you do?") : "Q: What do you do?") + "\n" + (typeof t === "function" ? t("A: ") : "A: ") + (permit.fields.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: ") + (permit.fields.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); // Add a "Match" button to allow the player to confirm the info matches var matchBtn = LK.getAsset('stamp_approve', { anchorX: 0.5, anchorY: 0.5, x: bgRect.width - 120, y: bgRect.height - 80 }); matchBtn.width = 180; matchBtn.height = 90; infoBg.addChild(matchBtn); var matchTxt = new Text2(t("Correct!"), { size: 48, fill: 0xffffff }); matchTxt.anchor.set(0.5, 0.5); matchTxt.x = matchBtn.width / 2; matchTxt.y = matchBtn.height / 2; matchBtn.addChild(matchTxt); // Add a "No Match" button to allow the player to say the info does not match var noMatchBtn = LK.getAsset('stamp_deny', { anchorX: 0.5, anchorY: 0.5, x: bgRect.width - 340, y: bgRect.height - 80 }); noMatchBtn.width = 180; noMatchBtn.height = 90; infoBg.addChild(noMatchBtn); var noMatchTxt = new Text2(t("Mistake!"), { size: 48, fill: 0xffffff }); noMatchTxt.anchor.set(0.5, 0.5); noMatchTxt.x = noMatchBtn.width / 2; noMatchTxt.y = noMatchBtn.height / 2; noMatchBtn.addChild(noMatchTxt); // Handler for match/no match infoBg.down = function (mx, my, obj) { // Convert to local coordinates var localX = mx - infoBg.x; var localY = my - infoBg.y; // Check if matchBtn was pressed if (localX >= matchBtn.x - matchBtn.width / 2 && localX <= matchBtn.x + matchBtn.width / 2 && localY >= matchBtn.y - matchBtn.height / 2 && localY <= matchBtn.y + matchBtn.height / 2) { // If info matches, allow entry (set a flag for later) // On day 2, sometimes even if info matches, mark as error (simulate system error) if (day === 2 && currentTraveler && currentTraveler.forceInfoError) { currentTraveler.infoMatched = false; showFeedback(t("Mistake!"), "#d32f2f"); } else { currentTraveler.infoMatched = true; showFeedback(t("Correct!"), "#4caf50"); } // Hide popup and show docs again if (currentTraveler.extraInfoTxt && currentTraveler.extraInfoTxt.parent) currentTraveler.extraInfoTxt.parent.removeChild(currentTraveler.extraInfoTxt); currentTraveler.extraInfoTxt = null; currentTraveler.showDocuments(true); } // Check if noMatchBtn was pressed if (localX >= noMatchBtn.x - noMatchBtn.width / 2 && localX <= noMatchBtn.x + noMatchBtn.width / 2 && localY >= noMatchBtn.y - noMatchBtn.height / 2 && localY <= noMatchBtn.y + noMatchBtn.height / 2) { // If info does not match, deny entry (set a flag for later) currentTraveler.infoMatched = false; showFeedback(t("Mistake!"), "#d32f2f"); // Hide popup and show docs again if (currentTraveler.extraInfoTxt && currentTraveler.extraInfoTxt.parent) currentTraveler.extraInfoTxt.parent.removeChild(currentTraveler.extraInfoTxt); currentTraveler.extraInfoTxt = null; currentTraveler.showDocuments(true); } }; // Attach the down handler to the infoBg infoBg.interactive = true; infoBg.down = infoBg.down; } 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); } else { // No permit: fallback to default behavior (show/hide docs) if (!currentTraveler.extraInfoTxt) { // No extra info for Germans or those without permit currentTraveler.extraInfoTxt = null; } else { currentTraveler.extraInfoTxt.visible = !currentTraveler.extraInfoTxt.visible; } 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
@@ -4,9 +4,8 @@
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
highScore: 0
});
-var facekit = LK.import("@upit/facekit.v1");
/****
* Classes
****/
@@ -193,20 +192,20 @@
/****
* Game Code
****/
-// New unique character assets (3 female, 3 male)
-// Sound for traveler arrival
-// Sound for next button
-// Add background images for changeable backgrounds
-// Game state variables
+// Example: new background for permit
// Document backgrounds
-// Female character assets
-// Toggleable main menu button assets
-// Play/Exit button assets for each language (English, Turkish, German, French, Russian)
// New family member avatars
+// Play/Exit button assets for each language (English, Turkish, German, French, Russian)
+// Toggleable main menu button assets
+// Female character assets
// Document backgrounds
-// Example: new background for permit
+// Game state variables
+// Add background images for changeable backgrounds
+// Sound for next button
+// Sound for traveler arrival
+// New unique character assets (3 female, 3 male)
function _typeof(o) {
"@babel/helpers - typeof";
return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) {
return typeof o;
@@ -631,67 +630,10 @@
}
}
// Hide docs initially
traveler.showDocuments(false);
- // --- Random event: Bribe attempt ---
- traveler.bribeAttempt = false;
- traveler.bribeAmount = 0;
- if (day >= 2 && Math.random() < 0.12) {
- // 12% chance from day 2+
- traveler.bribeAttempt = true;
- traveler.bribeAmount = 5 + Math.floor(Math.random() * 11); // 5-15 RM
- // Attach a bribe icon to the traveler (coin emoji)
- var bribeIcon = new Text2("š°", {
- size: 80,
- fill: 0xffd700
- });
- bribeIcon.anchor.set(0.5, 1);
- bribeIcon.x = 0;
- bribeIcon.y = -20;
- traveler.addChild(bribeIcon);
- traveler.bribeIcon = bribeIcon;
- }
+ // Localize all document fields and info popups for this traveler
if (typeof updateAllLocalizedText === "function") updateAllLocalizedText();
- // --- Random event: Wanted criminal (red alert) ---
- traveler.isWanted = false;
- if (day >= 3 && Math.random() < 0.08) {
- // 8% chance from day 3+
- traveler.isWanted = true;
- // Add a red "WANTED" label above the person (not visible to player, but for internal logic)
- var wantedLabel = new Text2("WANTED", {
- size: 60,
- fill: 0xff0000
- });
- wantedLabel.anchor.set(0.5, 1);
- wantedLabel.x = 0;
- wantedLabel.y = -person.height - 20;
- wantedLabel.alpha = 0.0; // Hidden from player
- traveler.addChild(wantedLabel);
- traveler.wantedLabel = wantedLabel;
- // Mark the passport as forged to simulate a fake identity
- var pass = traveler.documents[0];
- if (pass && pass.docType === "passport") {
- pass.isForged = true;
- pass.setup(pass.docType, pass.fields, true);
- }
- }
- // --- Random event: Secret police inspector (tests player honesty) ---
- traveler.isInspector = false;
- if (day >= 4 && Math.random() < 0.06) {
- // 6% chance from day 4+
- traveler.isInspector = true;
- // Add a subtle inspector badge (star emoji) above the person
- var inspectorBadge = new Text2("ā", {
- size: 60,
- fill: 0x4caf50
- });
- inspectorBadge.anchor.set(0.5, 1);
- inspectorBadge.x = 0;
- inspectorBadge.y = -person.height - 20;
- inspectorBadge.alpha = 0.0; // Hidden from player
- traveler.addChild(inspectorBadge);
- traveler.inspectorBadge = inspectorBadge;
- }
return traveler;
}
// Helper: get today's rules
function getRulesForDay(day) {
@@ -744,12 +686,28 @@
feedbackTxt.setStyle({
fill: color
});
}
+ // Subtle animated feedback flash for polish
+ feedbackTxt.alpha = 0.2;
feedbackTxt.visible = true;
+ tween(feedbackTxt, {
+ alpha: 1
+ }, {
+ duration: 180,
+ easing: tween.cubicOut
+ });
if (lastFeedbackTimeout) LK.clearTimeout(lastFeedbackTimeout);
lastFeedbackTimeout = LK.setTimeout(function () {
- feedbackTxt.visible = false;
+ tween(feedbackTxt, {
+ alpha: 0.2
+ }, {
+ duration: 200,
+ easing: tween.cubicIn,
+ onFinish: function onFinish() {
+ feedbackTxt.visible = false;
+ }
+ });
}, 1200);
}
}
// Helper: next traveler
@@ -1334,20 +1292,34 @@
// 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
+ // Animate in to center with a gentle bounce for polish
+ currentTraveler.scaleX = 0.92;
+ currentTraveler.scaleY = 0.92;
tween(currentTraveler, {
- x: 2048 / 2
+ x: 2048 / 2,
+ scaleX: 1.04,
+ scaleY: 1.04
}, {
- duration: 600,
+ duration: 420,
easing: tween.cubicOut,
onFinish: function onFinish() {
- // Show docs after short delay
- LK.setTimeout(function () {
- if (currentTraveler) currentTraveler.showDocuments(true);
- isDecisionActive = true;
- }, 400);
+ // Subtle bounce back to normal scale
+ tween(currentTraveler, {
+ scaleX: 1,
+ scaleY: 1
+ }, {
+ duration: 180,
+ easing: tween.cubicIn,
+ 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
@@ -1434,44 +1406,15 @@
allowed = currentTraveler.infoMatched; // for feedback
}
}
}
- // --- Special event consequences ---
- var eventMsg = null;
- if (currentTraveler.bribeAttempt && currentTraveler.bribeAmount > 0 && approved) {
- // If player accepted bribe, but traveler is caught (random chance)
- if (Math.random() < 0.25) {
- // 25% chance to get caught
- eventMsg = "You were caught taking a bribe! -10 RM";
- money = Math.max(0, money - 10);
- showFeedback(eventMsg, "#d32f2f");
- LK.effects.flashScreen(0xff0000, 1000);
- }
- }
- if (currentTraveler.isWanted && approved) {
- // Letting a wanted criminal through is a big mistake
- eventMsg = "You let a wanted criminal through! -15 RM";
- money = Math.max(0, money - 15);
- showFeedback(eventMsg, "#d32f2f");
- LK.effects.flashScreen(0xff0000, 1200);
- }
- if (currentTraveler.isInspector) {
- // If player accepted a bribe or let a forged doc through, inspector will punish
- var pass = getDoc(currentTraveler, 'passport');
- if (currentTraveler.bribeAttempt && approved || pass && pass.isForged && approved) {
- eventMsg = "Secret police inspector caught you! -20 RM";
- money = Math.max(0, money - 20);
- showFeedback(eventMsg, "#d32f2f");
- LK.effects.flashScreen(0xff0000, 1500);
- }
- }
- if (correct && !eventMsg) {
+ if (correct) {
correctDecisions++;
showFeedback("Correct!", "#4caf50");
LK.setScore(LK.getScore() + 1);
if (scoreTxt) scoreTxt.setText(LK.getScore());
money += 5; // +5 for correct
- } else if (!eventMsg) {
+ } else {
wrongDecisions = (typeof wrongDecisions === "number" ? wrongDecisions : 0) + 1;
showFeedback("Mistake!", "#d32f2f");
// Flash screen red for mistake
LK.effects.flashScreen(0xff0000, 500);
@@ -1784,8 +1727,31 @@
x: 2048 / 2 + 350,
y: 2450 //{2G} // moved lower
});
game.addChild(approveBtn);
+// Subtle animated highlight for approve button
+approveBtn._pulseTween = null;
+function pulseApproveBtn() {
+ if (approveBtn._pulseTween) return;
+ approveBtn._pulseTween = tween(approveBtn, {
+ scaleX: 1.08,
+ scaleY: 1.08
+ }, {
+ duration: 400,
+ yoyo: true,
+ repeat: 1,
+ easing: tween.cubicInOut,
+ onFinish: function onFinish() {
+ approveBtn.scaleX = 1;
+ approveBtn.scaleY = 1;
+ approveBtn._pulseTween = null;
+ }
+ });
+}
+approveBtn.interactive = true;
+approveBtn.down = function () {
+ pulseApproveBtn();
+};
// Remove old denyBtn if it exists
if (denyBtn && denyBtn.parent) {
denyBtn.parent.removeChild(denyBtn);
denyBtn = null;
@@ -1798,8 +1764,31 @@
y: 2450
});
denyBtn.visible = true;
game.addChild(denyBtn);
+// Subtle animated highlight for deny button
+denyBtn._pulseTween = null;
+function pulseDenyBtn() {
+ if (denyBtn._pulseTween) return;
+ denyBtn._pulseTween = tween(denyBtn, {
+ scaleX: 1.08,
+ scaleY: 1.08
+ }, {
+ duration: 400,
+ yoyo: true,
+ repeat: 1,
+ easing: tween.cubicInOut,
+ onFinish: function onFinish() {
+ denyBtn.scaleX = 1;
+ denyBtn.scaleY = 1;
+ denyBtn._pulseTween = null;
+ }
+ });
+}
+denyBtn.interactive = true;
+denyBtn.down = function () {
+ pulseDenyBtn();
+};
// Next Traveler button
var nextBtn = LK.getAsset('stamp_next', {
anchorX: 0.5,
anchorY: 0.5,
@@ -1945,24 +1934,8 @@
};
// --- Main Menu Implementation ---
// Hide all gameplay UI elements initially
if (scoreTxt) scoreTxt.visible = false;
-// --- Facekit: Secret event if player smiles at camera (Easter egg) ---
-if (typeof facekit !== "undefined" && facekit && typeof facekit.onFace === "function") {
- facekit.onFace(function (data) {
- // If a face is detected and smiling probability is high, show a secret message
- if (data && data.smile && data.smile > 0.7) {
- // Only show once per game session
- if (!game._smileEasterEggShown) {
- game._smileEasterEggShown = true;
- showFeedback("You smiled at the border guard! š", "#4caf50");
- // Optionally, give a small bonus
- money += 3;
- saveDayStats();
- }
- }
- });
-}
if (clockTxt) clockTxt.visible = false;
if (dayTxt) dayTxt.visible = false;
if (arrowBtn) arrowBtn.visible = false;
if (rulesPanel) rulesPanel.visible = false;
@@ -2613,107 +2586,8 @@
return;
}
// Show/hide docs and show extra info on traveler tap
if (currentTraveler) {
- // --- Bribe event: If traveler is offering a bribe and player taps the bribe icon ---
- if (currentTraveler.bribeAttempt && currentTraveler.bribeIcon) {
- // Calculate bribe icon bounds (relative to traveler)
- var bx = currentTraveler.x + (currentTraveler.bribeIcon.x || 0);
- var by = currentTraveler.y + (currentTraveler.bribeIcon.y || 0) - (currentTraveler.bribeIcon.height || 80);
- var bLeft = bx - 40,
- bRight = bx + 40,
- bTop = by - 40,
- bBottom = by + 40;
- if (x >= bLeft && x <= bRight && y >= bTop && y <= bBottom) {
- // Show bribe popup
- var bribePopup = new Container();
- var bg = LK.getAsset('bg_info_popup', {
- anchorX: 0.5,
- anchorY: 0.5,
- x: 2048 / 2,
- y: 2732 / 2
- });
- bg.width = 600;
- bg.height = 400;
- bribePopup.addChild(bg);
- var txt = new Text2("The traveler offers you a bribe of " + currentTraveler.bribeAmount + " RM to let them pass.\nDo you accept?", {
- size: 54,
- fill: 0x222222
- });
- txt.anchor.set(0.5, 0.5);
- txt.x = 2048 / 2;
- txt.y = 2732 / 2 - 60;
- bribePopup.addChild(txt);
- // Accept button
- var acceptBtn = LK.getAsset('stamp_approve', {
- anchorX: 0.5,
- anchorY: 0.5,
- x: 2048 / 2 - 120,
- y: 2732 / 2 + 80
- });
- acceptBtn.width = 140;
- acceptBtn.height = 70;
- bribePopup.addChild(acceptBtn);
- var acceptTxt = new Text2("Accept", {
- size: 40,
- fill: 0xffffff
- });
- acceptTxt.anchor.set(0.5, 0.5);
- acceptTxt.x = acceptBtn.width / 2;
- acceptTxt.y = acceptBtn.height / 2;
- acceptBtn.addChild(acceptTxt);
- // Reject button
- var rejectBtn = LK.getAsset('stamp_deny', {
- anchorX: 0.5,
- anchorY: 0.5,
- x: 2048 / 2 + 120,
- y: 2732 / 2 + 80
- });
- rejectBtn.width = 140;
- rejectBtn.height = 70;
- bribePopup.addChild(rejectBtn);
- var rejectTxt = new Text2("Reject", {
- size: 40,
- fill: 0xffffff
- });
- rejectTxt.anchor.set(0.5, 0.5);
- rejectTxt.x = rejectBtn.width / 2;
- rejectTxt.y = rejectBtn.height / 2;
- rejectBtn.addChild(rejectTxt);
- // Handler for bribe popup
- bribePopup.interactive = true;
- bribePopup.down = function (mx, my, obj) {
- // Accept
- var aLeft = acceptBtn.x - acceptBtn.width / 2,
- aRight = acceptBtn.x + acceptBtn.width / 2;
- var aTop = acceptBtn.y - acceptBtn.height / 2,
- aBottom = acceptBtn.y + acceptBtn.height / 2;
- if (mx >= aLeft && mx <= aRight && my >= aTop && my <= aBottom) {
- // Accept bribe: add money, mark as accepted
- money += currentTraveler.bribeAmount;
- saveDayStats();
- showFeedback("You accepted the bribe! +" + currentTraveler.bribeAmount + " RM", "#ffd700");
- // Remove bribe icon and popup
- if (currentTraveler.bribeIcon && currentTraveler.bribeIcon.parent) currentTraveler.bribeIcon.parent.removeChild(currentTraveler.bribeIcon);
- currentTraveler.bribeAttempt = false;
- if (bribePopup.parent) bribePopup.parent.removeChild(bribePopup);
- return;
- }
- // Reject
- var rLeft = rejectBtn.x - rejectBtn.width / 2,
- rRight = rejectBtn.x + rejectBtn.width / 2;
- var rTop = rejectBtn.y - rejectBtn.height / 2,
- rBottom = rejectBtn.y + rejectBtn.height / 2;
- if (mx >= rLeft && mx <= rRight && my >= rTop && my <= rBottom) {
- showFeedback("You rejected the bribe.", "#d32f2f");
- if (bribePopup.parent) bribePopup.parent.removeChild(bribePopup);
- return;
- }
- };
- game.addChild(bribePopup);
- return;
- }
- }
// 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;
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