/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Bus (player) class var Bus = Container.expand(function () { var self = Container.call(this); var busAsset = self.attachAsset('bus', { anchorX: 0.5, anchorY: 1 }); // For touch drag self.dragging = false; self.dragOffsetY = 0; // Called every tick self.update = function () { // Clamp bus to road (vertical bounds) if (self.y < 400) self.y = 400; if (self.y > 2200) self.y = 2200; }; // Touch down on bus self.down = function (x, y, obj) { self.dragging = true; self.dragOffsetY = y - self.y; }; // Touch up on bus self.up = function (x, y, obj) { self.dragging = false; }; return self; }); // Bus Station class var BusStation = Container.expand(function () { var self = Container.call(this); // Use dropStation asset if isPickup is false, otherwise use station var assetId = typeof self.isPickup !== "undefined" && self.isPickup === false ? 'dropStation' : 'station'; var stationAsset = self.attachAsset(assetId, { anchorX: 0.5, anchorY: 1 }); // List of travelers at this station self.travelers = []; // Travelers are now spawned in spawnStation, not here // Remove traveler from this station self.removeTraveler = function (traveler) { for (var i = 0; i < self.travelers.length; i++) { if (self.travelers[i] === traveler) { self.travelers.splice(i, 1); break; } } }; // Called every tick self.update = function () { // No longer remove station when bus passes; stations persist }; return self; }); // Traveler class var Traveler = Container.expand(function () { var self = Container.call(this); // Attach correct asset based on gender (default to man if not set yet) var assetId = typeof self.gender !== "undefined" && self.gender === "woman" ? "travelerWoman" : "traveler"; var travelerAsset = self.attachAsset(assetId, { anchorX: 0.5, anchorY: 1 }); // Reference to the station this traveler belongs to self.station = null; // Whether this traveler is still waiting at the station self.waiting = true; // Track if this traveler was dropped (for possible future logic) self.dropped = false; // Time (in ticks) until this traveler leaves self.leaveTick = 0; // Called every tick self.update = function () { // If not waiting, do nothing if (!self.waiting) return; // Travelers no longer leave by themselves // Remove the leave-tick logic entirely }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x222222 }); /**** * Game Code ****/ // green // orange // visually distinguish drop station (flipX as example) // Play baba music at game start LK.playMusic('baba', { loop: true, fade: { start: 0, end: 0.01, duration: 1000 } }); var streetBgWidth = 900; var streetBgHeight = 400; var streetBgY = 1500; var streetBgs = []; for (var i = 0; i < 3; i++) { var streetBg = LK.getAsset('streetBg', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2 + (i - 1) * streetBgWidth, y: streetBgY }); game.addChild(streetBg); streetBgs.push(streetBg); } // Traveler // Bus station // Bus (player vehicle) // Game world scroll variables var scrollX = 0; // How far the world has scrolled right var scrollSpeed = 10; // Pixels per tick // Bus (player) var bus = new Bus(); game.addChild(bus); bus.x = 400; bus.y = 1500; // List of stations in the world var stations = []; // Spawn fewer stations, with much greater spacing, and ensure max 2 stations visible on screen var lastStationX = 1200; var NUM_STATIONS = 6; // fewer stations for less crowding var minStationSpacing = 1200; // minimum distance between stations (at least 1 screen width) var maxStationSpacing = 1600; // maximum distance between stations var stationWidth = 400; for (var i = 0; i < NUM_STATIONS; i++) { var isPickup = i % 2 === 0; var tryCount = 0; var valid = false; var tryX = 0; while (!valid && tryCount < 100) { var spacing = minStationSpacing + Math.floor(Math.random() * (maxStationSpacing - minStationSpacing + 1)); if (i === 0) { tryX = 1200; } else { tryX = lastStationX + spacing; } // Check for overlap with all previous stations (ensure at least minStationSpacing between centers) valid = true; for (var j = 0; j < stations.length; j++) { var prev = stations[j]; if (Math.abs(tryX - prev.x) < minStationSpacing) { valid = false; break; } } tryCount++; } lastStationX = tryX; spawnStation(lastStationX, isPickup); } // GUI: Score (number of travelers picked up) var score = 0; var scoreTxt = new Text2('0', { size: 120, fill: 0xFFFFFF, font: "'GillSans-Bold',Impact,'Arial Black',Tahoma" }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // --- Meter Bar UI --- var meterBarBg = LK.getAsset('meterBarBg', { anchorX: 0, anchorY: 0.5, x: 200, y: 120 }); var meterBarFill = LK.getAsset('meterBarBg', { anchorX: 0, anchorY: 0.5, x: 200, y: 120, width: 600, height: 40 }); meterBarFill.width = 0; LK.gui.top.addChild(meterBarBg); LK.gui.top.addChild(meterBarFill); // Add a text label to the meter bar showing the remaining distance in meters var meterBarText = new Text2('0m', { size: 60, fill: 0xffffff, font: "'GillSans-Bold',Impact,'Arial Black',Tahoma" }); meterBarText.anchor.set(0.5, 0.5); meterBarText.x = 200 + 600 / 2; meterBarText.y = 120; LK.gui.top.addChild(meterBarText); // --- Bus Capacity Bar UI --- var busCapacityBarBg = LK.getAsset('capacityBarBg', { anchorX: 0, anchorY: 0.5, x: 200, y: 200 }); // Plus capacity bar fill (dark green, for new travelers picked up this round) var plusCapacityBarFill = LK.getAsset('plusCapacityBarBg', { anchorX: 0, anchorY: 0.5, x: 200, y: 200, width: 600, height: 32 }); plusCapacityBarFill.width = 0; var busCapacityBarFill = LK.getAsset('plusCapacityBarBg', { anchorX: 0, anchorY: 0.5, x: 200, y: 200, width: 600, height: 32 }); busCapacityBarFill.width = 0; LK.gui.top.addChild(busCapacityBarBg); LK.gui.top.addChild(plusCapacityBarFill); LK.gui.top.addChild(busCapacityBarFill); // Add a bus capacity text label to the bus capacity bar in the main UI var busCapacityBarText = new Text2('0/10', { size: 48, fill: 0xffffff, font: "'GillSans-Bold',Impact,'Arial Black',Tahoma" }); busCapacityBarText.anchor.set(0.5, 0.5); busCapacityBarText.x = 200 + 600 / 2; busCapacityBarText.y = 200; LK.gui.top.addChild(busCapacityBarText); // For dragging bus var dragBus = false; // --- Take Travelers Button --- var takeBtnBg = LK.getAsset('takeBtnBg', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2 - 320, y: 2732 - 200 }); var takeBtnTxt = new Text2('Take Travelers', { size: 60, fill: 0xffffff }); takeBtnTxt.anchor.set(0.5, 0.5); takeBtnBg.addChild(takeBtnTxt); game.addChild(takeBtnBg); // --- Drop Travelers Button --- var dropBtnBg = LK.getAsset('takeBtnBg', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2 + 320, y: 2732 - 200 }); var dropBtnTxt = new Text2('Drop Travelers', { size: 60, fill: 0xffffff }); dropBtnTxt.anchor.set(0.5, 0.5); dropBtnBg.addChild(dropBtnTxt); game.addChild(dropBtnBg); // --- Shop Button --- var shopBtnBg = LK.getAsset('takeBtnBg', { anchorX: 0.5, anchorY: 0.5, x: 320, // Move to left side of the screen, away from top left 100x100 area y: 2732 - 200 }); var shopBtnTxt = new Text2('Shop', { size: 60, fill: 0xffffff }); shopBtnTxt.anchor.set(0.5, 0.5); shopBtnBg.addChild(shopBtnTxt); game.addChild(shopBtnBg); // --- Settings Button --- var settingsBtnBg = LK.getAsset('takeBtnBg', { anchorX: 0.5, anchorY: 0.5, x: 2048 - 320, y: 2732 - 200 }); var settingsBtnTxt = new Text2('Settings', { size: 60, fill: 0xffffff }); settingsBtnTxt.anchor.set(0.5, 0.5); settingsBtnBg.addChild(settingsBtnTxt); game.addChild(settingsBtnBg); // --- Settings Panel --- var settingsPanelBg = LK.getAsset('whitePanel', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2 }); settingsPanelBg.visible = false; game.addChild(settingsPanelBg); // Settings title var settingsTitle = new Text2('Settings', { size: 90, fill: 0x222222 }); settingsTitle.anchor.set(0.5, 0); settingsTitle.x = 0; settingsTitle.y = -350; settingsPanelBg.addChild(settingsTitle); // Music toggle button var musicToggleBtn = LK.getAsset('takeBtnBg', { anchorX: 0.5, anchorY: 0.5, x: 0, y: -100, width: 400, height: 120 }); var musicToggleTxt = new Text2('Music: On', { size: 56, fill: 0xffffff }); musicToggleTxt.anchor.set(0.5, 0.5); musicToggleBtn.addChild(musicToggleTxt); settingsPanelBg.addChild(musicToggleBtn); // Sound toggle button var soundToggleBtn = LK.getAsset('takeBtnBg', { anchorX: 0.5, anchorY: 0.5, x: 0, y: 80, width: 400, height: 120 }); var soundToggleTxt = new Text2('Sound: On', { size: 56, fill: 0xffffff }); soundToggleTxt.anchor.set(0.5, 0.5); soundToggleBtn.addChild(soundToggleTxt); settingsPanelBg.addChild(soundToggleBtn); // Set initial text for music and sound toggles musicToggleTxt.setText('Music: On'); soundToggleTxt.setText('Sound: On'); // Settings close button var settingsCloseBtn = LK.getAsset('takeBtnBg', { anchorX: 0.5, anchorY: 0.5, x: 0, y: 260, width: 300, height: 90 }); var settingsCloseTxt = new Text2('Close', { size: 48, fill: 0x222222 }); settingsCloseTxt.anchor.set(0.5, 0.5); settingsCloseBtn.addChild(settingsCloseTxt); settingsPanelBg.addChild(settingsCloseBtn); // Track settings state var settingsBtnPressed = false; var settingsClosePressed = false; var musicTogglePressed = false; var soundTogglePressed = false; var musicEnabled = true; var soundEnabled = true; // Shop UI elements (hidden by default) var shopPanelBg = LK.getAsset('whitePanel', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2 }); shopPanelBg.visible = false; game.addChild(shopPanelBg); // Shop title, centered near the top of the panel var shopTitle = new Text2('Shop', { size: 90, fill: 0x222222 }); shopTitle.anchor.set(0.5, 0); shopTitle.x = 0; // Move the shop title higher, since capacity text is removed shopTitle.y = -380; shopPanelBg.addChild(shopTitle); // Shop upgrade buttons and texts var shopUpgradeBtns = []; var shopUpgradeTxts = []; var shopUpgradeCosts = [30, 30, 30]; // initial costs var shopUpgradeLevels = [0, 0, 0]; // [capacity, speed, money] var shopUpgradeMax = [10, 10, 10]; var shopUpgradeNames = ['Capacity', 'Speed', 'Money']; var shopUpgradeDesc = ['Increase bus capacity by 2', 'Increase speed by 2', 'Increase money per traveler by 1']; for (var i = 0; i < 3; i++) { // Create a container for each row var row = new Container(); // Vertically space rows within the white panel, below the title, with even spacing var rowHeight = 180; // Move the upgrade rows higher as well var panelContentTop = shopTitle.y + shopTitle.height + 16; row.y = panelContentTop + i * rowHeight; // Center row horizontally in the panel row.x = 0; // --- Lay out texts from left to right, all inside the white panel --- // Name var nameTxt = new Text2(shopUpgradeNames[i], { size: 56, fill: 0x222222 }); nameTxt.anchor.set(0, 0.5); nameTxt.x = -900 / 2 + 40; nameTxt.y = 0; // Level var levelTxt = new Text2('(Lv.0)', { size: 56, fill: 0x222222 }); levelTxt.anchor.set(0, 0.5); levelTxt.x = nameTxt.x + nameTxt.width + 24; levelTxt.y = 0; // Cost (now below desc, but desc removed) var costTxt = new Text2('Cost: 30', { size: 44, fill: 0x888888 }); costTxt.anchor.set(0, 0.5); // Place cost where desc would have started costTxt.x = levelTxt.x + levelTxt.width + 24; costTxt.y = 0; // Upgrade button (right side, inside panel, with margin) var btn = LK.getAsset('takeBtnBg', { anchorX: 0.5, anchorY: 0.5, x: 900 / 2 - 120, y: 0, width: 180, height: 70 }); btn.visible = true; // Button label var btnLabel = new Text2('Upgrade', { size: 32, fill: 0xffffff }); btnLabel.anchor.set(0.5, 0.5); btn.addChild(btnLabel); // Add all texts and button to row row.addChild(nameTxt); row.addChild(levelTxt); row.addChild(costTxt); row.addChild(btn); // Add row to shop panel shopPanelBg.addChild(row); // Track for logic shopUpgradeBtns.push(btn); // Store all text objects for update logic (desc removed) shopUpgradeTxts.push({ name: nameTxt, level: levelTxt, cost: costTxt }); } shopPanelBg.visible = false; // Shop close button var shopCloseBtn = LK.getAsset('takeBtnBg', { anchorX: 0.5, anchorY: 0.5, x: 900 / 2, y: 820 }); var shopCloseTxt = new Text2('Close', { size: 48, fill: 0x222222 }); shopCloseTxt.anchor.set(0.5, 0.5); shopCloseBtn.addChild(shopCloseTxt); shopPanelBg.addChild(shopCloseBtn); // Track if take or drop or shop button is pressed this frame var takeBtnPressed = false; var dropBtnPressed = false; var shopBtnPressed = false; var shopUpgradePressed = [false, false, false]; var shopClosePressed = false; // Button press handlers takeBtnBg.down = function (x, y, obj) { if (takeBtnBg.interactive !== false && !shopPanelBg.visible) { takeBtnPressed = true; } }; dropBtnBg.down = function (x, y, obj) { if (dropBtnBg.interactive !== false && !shopPanelBg.visible) { dropBtnPressed = true; } }; // Shop button handler shopBtnBg.down = function (x, y, obj) { if (!shopPanelBg.visible && !settingsPanelBg.visible) { shopBtnPressed = true; } }; // Settings button handler settingsBtnBg.down = function (x, y, obj) { if (!settingsPanelBg.visible && !shopPanelBg.visible) { settingsBtnPressed = true; } }; // Settings close button handler settingsCloseBtn.down = function (x, y, obj) { if (settingsPanelBg.visible) { settingsClosePressed = true; } }; // Music toggle button handler musicToggleBtn.down = function (x, y, obj) { if (settingsPanelBg.visible) { musicTogglePressed = true; } }; // Sound toggle button handler soundToggleBtn.down = function (x, y, obj) { if (settingsPanelBg.visible) { soundTogglePressed = true; } }; // Shop upgrade button handlers for (var i = 0; i < 3; i++) { (function (idx) { shopUpgradeBtns[idx].down = function (x, y, obj) { if (shopPanelBg.visible) { shopUpgradePressed[idx] = true; } }; })(i); } // Shop close button handler shopCloseBtn.down = function (x, y, obj) { if (shopPanelBg.visible) { shopClosePressed = true; } }; // Generate a station at a given x, alternating pickup/drop and up/down function spawnStation(x, isPickup) { var station = new BusStation(); station.isPickup = !!isPickup; // Re-attach correct asset if drop station (for existing BusStation instances) if (!station.isPickup) { if (station.children.length > 0) { station.removeChild(station.children[0]); } station.attachAsset('dropStation', { anchorX: 0.5, anchorY: 1 }); } // Alternate up/down for each station, placing them exactly outside the street (not inside) var streetCenterY = streetBgY; var stationAssetHeight = 160; // matches asset height for station/dropStation if (stations.length % 2 === 0) { // Place station so its bottom edge is flush with the top edge of the street (just above street) station.y = streetCenterY - streetBgHeight / 2; } else { // Place station so its top edge is flush with the bottom edge of the street (just below street) station.y = streetCenterY + streetBgHeight / 2 + stationAssetHeight; } // Place at given x station.x = x; // Spawn travelers immediately if pickup station if (station.isPickup) { var numTravelers = 1 + Math.floor(Math.random() * 10); var travelerYOffset = station.y < streetCenterY ? -140 : 140; for (var i = 0; i < numTravelers; i++) { // Randomly choose gender: 0 = man, 1 = woman var gender = Math.random() < 0.5 ? "man" : "woman"; var traveler = new Traveler(); traveler.gender = gender; // Re-attach correct asset based on gender if (traveler.children.length > 0) { traveler.removeChild(traveler.children[0]); } var assetId = gender === "woman" ? "travelerWoman" : "traveler"; traveler.attachAsset(assetId, { anchorX: 0.5, anchorY: 1 }); traveler.station = station; traveler.x = 0; traveler.y = travelerYOffset + (station.y < streetCenterY ? -i * 130 : i * 130); station.addChild(traveler); station.travelers.push(traveler); } } // Add station above the shop panel so stations are always on top of shop UI var shopPanelIndex = game.children.indexOf(shopPanelBg); if (shopPanelIndex !== -1) { game.addChildAt(station, shopPanelIndex); } else { game.addChild(station); } stations.push(station); } // (removed duplicate old station spawn code) // Move handler for dragging bus function handleMove(x, y, obj) { // Only drag if started on bus if (bus.dragging) { bus.y = y - bus.dragOffsetY; } } game.move = handleMove; // Down handler: start drag if on bus, or start holding for rightward movement var holdingRight = false; game.down = function (x, y, obj) { // Convert to bus local coordinates var local = bus.toLocal(game.toGlobal({ x: x, y: y })); // If within bus bounds, start drag if (local.x > -bus.width / 2 && local.x < bus.width / 2 && local.y > -bus.height && local.y < 0) { bus.dragging = true; bus.dragOffsetY = y - bus.y; } else { // Start holding for rightward movement holdingRight = true; } }; // Up handler: stop drag and stop holding rightward movement game.up = function (x, y, obj) { bus.dragging = false; holdingRight = false; }; // Main game update loop game.update = function () { // --- SETTINGS LOGIC --- // Show/hide settings panel if (settingsBtnPressed) { settingsPanelBg.visible = true; takeBtnBg.visible = false; dropBtnBg.visible = false; shopBtnBg.visible = false; settingsBtnBg.visible = false; settingsBtnPressed = false; } // Settings close if (settingsClosePressed) { settingsPanelBg.visible = false; takeBtnBg.visible = true; dropBtnBg.visible = true; shopBtnBg.visible = true; settingsBtnBg.visible = true; settingsClosePressed = false; } // Music toggle if (musicTogglePressed) { musicEnabled = !musicEnabled; musicToggleTxt.setText('Music: ' + (musicEnabled ? 'On' : 'Off')); if (musicEnabled) { LK.playMusic('baba', { loop: true, fade: { start: 0, end: 0.01, duration: 1000 } }); } else { LK.stopMusic(); } musicTogglePressed = false; } // Sound toggle if (soundTogglePressed) { soundEnabled = !soundEnabled; soundToggleTxt.setText('Sound: ' + (soundEnabled ? 'On' : 'Off')); soundTogglePressed = false; } // --- SHOP LOGIC --- // Show/hide shop panel if (shopBtnPressed) { shopPanelBg.visible = true; takeBtnBg.visible = false; dropBtnBg.visible = false; shopBtnBg.visible = false; settingsBtnBg.visible = false; shopBtnPressed = false; } // Shop close if (shopClosePressed) { shopPanelBg.visible = false; takeBtnBg.visible = true; dropBtnBg.visible = true; shopBtnBg.visible = true; settingsBtnBg.visible = true; shopClosePressed = false; } // Update shop upgrade button texts for (var i = 0; i < 3; i++) { var level = shopUpgradeLevels[i]; var cost = shopUpgradeCosts[i] + level * 20; var max = shopUpgradeMax[i]; var name = shopUpgradeNames[i]; var desc = shopUpgradeDesc[i]; // Update each text field shopUpgradeTxts[i].name.setText(name); shopUpgradeTxts[i].level.setText('(Lv.' + level + (level >= max ? ' MAX' : '') + ')'); shopUpgradeTxts[i].cost.setText('Cost: ' + (level >= max ? '-' : cost)); // Re-layout: update x positions in case text width changed shopUpgradeTxts[i].level.x = shopUpgradeTxts[i].name.x + shopUpgradeTxts[i].name.width + 32; shopUpgradeTxts[i].cost.x = shopUpgradeTxts[i].level.x + shopUpgradeTxts[i].level.width + 32; shopUpgradeTxts[i].cost.y = 0; shopUpgradeBtns[i].alpha = level < max ? 1 : 0.5; shopUpgradeBtns[i].interactive = level < max; } // Update bus capacity text in main UI var travelersOnBoardCount = bus.travelersOnBoard ? bus.travelersOnBoard.length : 0; if (typeof maxCapacity === "undefined") maxCapacity = 10; busCapacityBarText.setText(travelersOnBoardCount + "/" + maxCapacity); // (shopBusCapacityText removed, no update needed) // Handle shop upgrades for (var i = 0; i < 3; i++) { if (shopUpgradePressed[i]) { var level = shopUpgradeLevels[i]; var cost = shopUpgradeCosts[i] + level * 20; var max = shopUpgradeMax[i]; if (level < max && score >= cost) { score -= cost; scoreTxt.setText(score); shopUpgradeLevels[i]++; // Apply upgrade effect if (i === 0) { // Capacity maxCapacity += 2; } else if (i === 1) { // Speed scrollSpeed += 2; } else if (i === 2) { // Money if (typeof moneyPerTraveler === "undefined") moneyPerTraveler = 1; moneyPerTraveler += 1; } } shopUpgradePressed[i] = false; } } if (shopPanelBg.visible) { // Pause game logic when shop is open return; } // Move bus/world right if holding if (typeof holdingRight !== "undefined" && holdingRight) { var moveAmount = scrollSpeed; // Instead of moving the bus, move the world and keep bus centered scrollX += moveAmount; for (var i = 0; i < stations.length; i++) { stations[i].x -= moveAmount; } // Move and repeat street backgrounds for (var i = 0; i < streetBgs.length; i++) { streetBgs[i].x -= moveAmount; // If streetBg is fully off the left, move it to the right end if (streetBgs[i].x < -streetBgWidth / 2) { // Find the rightmost streetBg var maxX = streetBgs[0].x; for (var j = 1; j < streetBgs.length; j++) { if (streetBgs[j].x > maxX) maxX = streetBgs[j].x; } streetBgs[i].x = maxX + streetBgWidth; } } } // Always keep bus in the center of the screen horizontally bus.x = 2048 / 2; // Move all stations and their travelers (no auto scroll) for (var i = 0; i < stations.length; i++) { var station = stations[i]; // No station.x -= scrollSpeed; station.update(); for (var j = 0; j < station.travelers.length; j++) { station.travelers[j].update(); } } // Update bus bus.update(); // --- Meter Bar Update: Show distance to nearest upcoming station --- var nearestStation = null; var nearestDist = null; for (var i = 0; i < stations.length; i++) { var station = stations[i]; // Only consider stations ahead of the bus (to the right) var dx = station.x - bus.x; if (dx >= 0) { if (nearestStation === null || dx < nearestDist) { nearestStation = station; nearestDist = dx; } } } if (nearestStation) { var dist = Math.max(0, Math.floor(nearestDist)); var maxDist = 1200; // Max distance for full bar var barWidth = Math.max(0, Math.min(600, 600 * (1 - dist / maxDist))); meterBarFill.width = barWidth; meterBarText.setText(dist + "m"); } else { meterBarFill.width = 0; meterBarText.setText("0m"); } // Endless station spawning logic // Find the rightmost station's x var rightmostStationX = 0; for (var i = 0; i < stations.length; i++) { if (stations[i].x > rightmostStationX) rightmostStationX = stations[i].x; } // If the rightmost station is less than 2.5 screens ahead, spawn a new one if (rightmostStationX < scrollX + 2048 * 2.5) { // Alternate pickup/drop var isPickup = stations.length % 2 === 0; // Place new station at least minStationSpacing after the last one, with some randomization var spacing = minStationSpacing + Math.floor(Math.random() * (maxStationSpacing - minStationSpacing + 1)); var newX = rightmostStationX + spacing; spawnStation(newX, isPickup); } // --- Manual pickup and drop-off logic with Take Travelers button --- // --- Manual pickup and drop-off logic with Take Travelers button --- if (typeof bus.travelersOnBoard === "undefined") { bus.travelersOnBoard = []; bus.lastPickupStationIndex = -1; bus.lastDropStationIndex = -1; } // --- Enable/disable Take Travelers button based on action possibility --- var canTake = false; var canDrop = false; var dropStationIndex = -1; // Check if we can take travelers at a nearby station for (var i = 0; i < stations.length; i++) { var station = stations[i]; // Take: must be pickup station, has travelers, close, and not already picked up here if (station.isPickup && station.travelers.length > 0 && Math.abs(station.x - bus.x) < 180 && bus.lastPickupStationIndex !== i) { canTake = true; } // Drop: must be drop station, bus has travelers, close, and not already dropped here if (!station.isPickup && bus.travelersOnBoard && bus.travelersOnBoard.length > 0 && Math.abs(station.x - bus.x) < 180 && bus.lastDropStationIndex !== i) { canDrop = true; dropStationIndex = i; } } // --- Bus Capacity Bar Update --- if (typeof maxCapacity === "undefined") maxCapacity = 10; var travelersOnBoardCount = bus.travelersOnBoard ? bus.travelersOnBoard.length : 0; var capBarWidth = Math.max(0, Math.min(600, 600 * (travelersOnBoardCount / maxCapacity))); busCapacityBarFill.width = capBarWidth; // Show plusCapacityBarFill for new travelers picked up this round if (typeof bus.lastTravelersOnBoardCount === "undefined") bus.lastTravelersOnBoardCount = 0; var plusCount = travelersOnBoardCount - bus.lastTravelersOnBoardCount; if (plusCount > 0) { var plusBarWidth = Math.max(0, Math.min(600, 600 * (plusCount / maxCapacity))); plusCapacityBarFill.width = plusBarWidth; plusCapacityBarFill.visible = true; } else { plusCapacityBarFill.width = 0; plusCapacityBarFill.visible = false; } // busCapacityBarText removed from main UI; only update shopBusCapacityText in shop panel // Visually enable/disable the take and drop buttons takeBtnBg.alpha = canTake ? 1 : 0.4; takeBtnBg.interactive = canTake; dropBtnBg.alpha = canDrop ? 1 : 0.4; dropBtnBg.interactive = canDrop; // --- Manual Pickup logic: only when button pressed --- if (typeof maxCapacity === "undefined") maxCapacity = 10; if (typeof moneyPerTraveler === "undefined") moneyPerTraveler = 1; if (takeBtnPressed) { for (var i = 0; i < stations.length; i++) { var station = stations[i]; // Only pick up if bus is close to pickup station, this is a new station, and this station has travelers if (station.isPickup && station.travelers.length > 0 && Math.abs(station.x - bus.x) < 180 && bus.lastPickupStationIndex !== i) { // Pick up all waiting travelers at this station var waitingTravelers = []; for (var j = station.travelers.length - 1; j >= 0; j--) { var traveler = station.travelers[j]; if (traveler.waiting) { waitingTravelers.push(traveler); } } // Actually pick up the travelers, but only up to available capacity var travelersOnBoardCount = bus.travelersOnBoard ? bus.travelersOnBoard.length : 0; var availableCapacity = maxCapacity - travelersOnBoardCount; var numToPick = Math.min(availableCapacity, waitingTravelers.length); for (var w = 0; w < numToPick; w++) { var traveler = waitingTravelers[w]; traveler.waiting = false; // Animate traveler to bus, then destroy tween(traveler, { x: bus.x - station.x, y: bus.y - station.y - 100, alpha: 0 }, { duration: 400, easing: tween.easeIn, onFinish: function (trav) { return function () { trav.destroy(); }; }(traveler) }); // Add to bus's onboard list if (!bus.travelersOnBoard) bus.travelersOnBoard = []; bus.travelersOnBoard.push({ traveler: traveler, pickedUpAt: i }); // Remove from station station.removeTraveler(traveler); // No score for pickup; award score on drop-off instead } // Mark this station as the last pickup bus.lastPickupStationIndex = i; // Update lastTravelersOnBoardCount for plus bar bus.lastTravelersOnBoardCount = bus.travelersOnBoard ? bus.travelersOnBoard.length : 0; // Play cheer sound when travelers are picked up if (soundEnabled) { LK.getSound('cheer').play(); } break; // Only pick up at one station per press } } takeBtnPressed = false; // Reset button press } // --- Manual Drop-off logic: only when drop button pressed --- if (dropBtnPressed && canDrop && dropStationIndex !== -1) { var station = stations[dropStationIndex]; // Drop all travelers currently on the bus if (bus.travelersOnBoard && bus.travelersOnBoard.length > 0) { // Calculate base Y for stacking var baseY = 0; var stackDir = 1; // If station is above street, stack upwards; if below, stack downwards var streetCenterY = streetBgY; if (station.y < streetCenterY) { baseY = -140; stackDir = -1; } else { baseY = 140; stackDir = 1; } // Count how many travelers are already at this drop station (for stacking) var alreadyDropped = 0; for (var tt = 0; tt < station.travelers.length; tt++) { if (station.travelers[tt].dropped) alreadyDropped++; } // Count how many men and women are on board var menToDrop = 0; var womenToDrop = 0; for (var t = 0; t < bus.travelersOnBoard.length; t++) { var travelerObj = bus.travelersOnBoard[t]; var traveler = travelerObj.traveler; if (traveler.gender === "woman") { womenToDrop++; } else { menToDrop++; } } // Drop correct number of men and women, stacking them var dropIndex = 0; for (var t = 0; t < menToDrop; t++, dropIndex++) { var newTraveler = new Traveler(); newTraveler.gender = "man"; // Remove default asset and attach correct one if (newTraveler.children.length > 0) { newTraveler.removeChild(newTraveler.children[0]); } newTraveler.attachAsset('traveler', { anchorX: 0.5, anchorY: 1 }); newTraveler.x = bus.x - station.x; newTraveler.y = bus.y - station.y - 100; newTraveler.alpha = 0; newTraveler.waiting = false; newTraveler.dropped = true; newTraveler.station = station; // Stack vertically at the station var stackIndex = alreadyDropped + dropIndex; newTraveler.x = 0; newTraveler.y = baseY + stackDir * stackIndex * 130; newTraveler.alpha = 0; station.addChild(newTraveler); station.travelers.push(newTraveler); tween(newTraveler, { x: 0, y: baseY + stackDir * stackIndex * 130, alpha: 1 }, { duration: 400, easing: tween.easeOut }); // Award score for each traveler dropped score += moneyPerTraveler; scoreTxt.setText(score); } for (var t = 0; t < womenToDrop; t++, dropIndex++) { var newTraveler = new Traveler(); newTraveler.gender = "woman"; // Remove default asset and attach correct one if (newTraveler.children.length > 0) { newTraveler.removeChild(newTraveler.children[0]); } newTraveler.attachAsset('travelerWoman', { anchorX: 0.5, anchorY: 1 }); newTraveler.x = bus.x - station.x; newTraveler.y = bus.y - station.y - 100; newTraveler.alpha = 0; newTraveler.waiting = false; newTraveler.dropped = true; newTraveler.station = station; // Stack vertically at the station var stackIndex = alreadyDropped + dropIndex; newTraveler.x = 0; newTraveler.y = baseY + stackDir * stackIndex * 130; newTraveler.alpha = 0; station.addChild(newTraveler); station.travelers.push(newTraveler); tween(newTraveler, { x: 0, y: baseY + stackDir * stackIndex * 130, alpha: 1 }, { duration: 400, easing: tween.easeOut }); // Award score for each traveler dropped score += moneyPerTraveler; scoreTxt.setText(score); } // Clear bus bus.travelersOnBoard = []; // Reset lastTravelersOnBoardCount for plus bar bus.lastTravelersOnBoardCount = 0; // Mark this station as the last drop bus.lastDropStationIndex = dropStationIndex; // Play cheer sound when travelers are dropped off if (soundEnabled) { LK.getSound('cheer').play(); } } dropBtnPressed = false; // Reset button press } };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Bus (player) class
var Bus = Container.expand(function () {
var self = Container.call(this);
var busAsset = self.attachAsset('bus', {
anchorX: 0.5,
anchorY: 1
});
// For touch drag
self.dragging = false;
self.dragOffsetY = 0;
// Called every tick
self.update = function () {
// Clamp bus to road (vertical bounds)
if (self.y < 400) self.y = 400;
if (self.y > 2200) self.y = 2200;
};
// Touch down on bus
self.down = function (x, y, obj) {
self.dragging = true;
self.dragOffsetY = y - self.y;
};
// Touch up on bus
self.up = function (x, y, obj) {
self.dragging = false;
};
return self;
});
// Bus Station class
var BusStation = Container.expand(function () {
var self = Container.call(this);
// Use dropStation asset if isPickup is false, otherwise use station
var assetId = typeof self.isPickup !== "undefined" && self.isPickup === false ? 'dropStation' : 'station';
var stationAsset = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 1
});
// List of travelers at this station
self.travelers = [];
// Travelers are now spawned in spawnStation, not here
// Remove traveler from this station
self.removeTraveler = function (traveler) {
for (var i = 0; i < self.travelers.length; i++) {
if (self.travelers[i] === traveler) {
self.travelers.splice(i, 1);
break;
}
}
};
// Called every tick
self.update = function () {
// No longer remove station when bus passes; stations persist
};
return self;
});
// Traveler class
var Traveler = Container.expand(function () {
var self = Container.call(this);
// Attach correct asset based on gender (default to man if not set yet)
var assetId = typeof self.gender !== "undefined" && self.gender === "woman" ? "travelerWoman" : "traveler";
var travelerAsset = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 1
});
// Reference to the station this traveler belongs to
self.station = null;
// Whether this traveler is still waiting at the station
self.waiting = true;
// Track if this traveler was dropped (for possible future logic)
self.dropped = false;
// Time (in ticks) until this traveler leaves
self.leaveTick = 0;
// Called every tick
self.update = function () {
// If not waiting, do nothing
if (!self.waiting) return;
// Travelers no longer leave by themselves
// Remove the leave-tick logic entirely
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x222222
});
/****
* Game Code
****/
// green
// orange
// visually distinguish drop station (flipX as example)
// Play baba music at game start
LK.playMusic('baba', {
loop: true,
fade: {
start: 0,
end: 0.01,
duration: 1000
}
});
var streetBgWidth = 900;
var streetBgHeight = 400;
var streetBgY = 1500;
var streetBgs = [];
for (var i = 0; i < 3; i++) {
var streetBg = LK.getAsset('streetBg', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2 + (i - 1) * streetBgWidth,
y: streetBgY
});
game.addChild(streetBg);
streetBgs.push(streetBg);
}
// Traveler
// Bus station
// Bus (player vehicle)
// Game world scroll variables
var scrollX = 0; // How far the world has scrolled right
var scrollSpeed = 10; // Pixels per tick
// Bus (player)
var bus = new Bus();
game.addChild(bus);
bus.x = 400;
bus.y = 1500;
// List of stations in the world
var stations = [];
// Spawn fewer stations, with much greater spacing, and ensure max 2 stations visible on screen
var lastStationX = 1200;
var NUM_STATIONS = 6; // fewer stations for less crowding
var minStationSpacing = 1200; // minimum distance between stations (at least 1 screen width)
var maxStationSpacing = 1600; // maximum distance between stations
var stationWidth = 400;
for (var i = 0; i < NUM_STATIONS; i++) {
var isPickup = i % 2 === 0;
var tryCount = 0;
var valid = false;
var tryX = 0;
while (!valid && tryCount < 100) {
var spacing = minStationSpacing + Math.floor(Math.random() * (maxStationSpacing - minStationSpacing + 1));
if (i === 0) {
tryX = 1200;
} else {
tryX = lastStationX + spacing;
}
// Check for overlap with all previous stations (ensure at least minStationSpacing between centers)
valid = true;
for (var j = 0; j < stations.length; j++) {
var prev = stations[j];
if (Math.abs(tryX - prev.x) < minStationSpacing) {
valid = false;
break;
}
}
tryCount++;
}
lastStationX = tryX;
spawnStation(lastStationX, isPickup);
}
// GUI: Score (number of travelers picked up)
var score = 0;
var scoreTxt = new Text2('0', {
size: 120,
fill: 0xFFFFFF,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// --- Meter Bar UI ---
var meterBarBg = LK.getAsset('meterBarBg', {
anchorX: 0,
anchorY: 0.5,
x: 200,
y: 120
});
var meterBarFill = LK.getAsset('meterBarBg', {
anchorX: 0,
anchorY: 0.5,
x: 200,
y: 120,
width: 600,
height: 40
});
meterBarFill.width = 0;
LK.gui.top.addChild(meterBarBg);
LK.gui.top.addChild(meterBarFill);
// Add a text label to the meter bar showing the remaining distance in meters
var meterBarText = new Text2('0m', {
size: 60,
fill: 0xffffff,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
meterBarText.anchor.set(0.5, 0.5);
meterBarText.x = 200 + 600 / 2;
meterBarText.y = 120;
LK.gui.top.addChild(meterBarText);
// --- Bus Capacity Bar UI ---
var busCapacityBarBg = LK.getAsset('capacityBarBg', {
anchorX: 0,
anchorY: 0.5,
x: 200,
y: 200
});
// Plus capacity bar fill (dark green, for new travelers picked up this round)
var plusCapacityBarFill = LK.getAsset('plusCapacityBarBg', {
anchorX: 0,
anchorY: 0.5,
x: 200,
y: 200,
width: 600,
height: 32
});
plusCapacityBarFill.width = 0;
var busCapacityBarFill = LK.getAsset('plusCapacityBarBg', {
anchorX: 0,
anchorY: 0.5,
x: 200,
y: 200,
width: 600,
height: 32
});
busCapacityBarFill.width = 0;
LK.gui.top.addChild(busCapacityBarBg);
LK.gui.top.addChild(plusCapacityBarFill);
LK.gui.top.addChild(busCapacityBarFill);
// Add a bus capacity text label to the bus capacity bar in the main UI
var busCapacityBarText = new Text2('0/10', {
size: 48,
fill: 0xffffff,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
busCapacityBarText.anchor.set(0.5, 0.5);
busCapacityBarText.x = 200 + 600 / 2;
busCapacityBarText.y = 200;
LK.gui.top.addChild(busCapacityBarText);
// For dragging bus
var dragBus = false;
// --- Take Travelers Button ---
var takeBtnBg = LK.getAsset('takeBtnBg', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2 - 320,
y: 2732 - 200
});
var takeBtnTxt = new Text2('Take Travelers', {
size: 60,
fill: 0xffffff
});
takeBtnTxt.anchor.set(0.5, 0.5);
takeBtnBg.addChild(takeBtnTxt);
game.addChild(takeBtnBg);
// --- Drop Travelers Button ---
var dropBtnBg = LK.getAsset('takeBtnBg', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2 + 320,
y: 2732 - 200
});
var dropBtnTxt = new Text2('Drop Travelers', {
size: 60,
fill: 0xffffff
});
dropBtnTxt.anchor.set(0.5, 0.5);
dropBtnBg.addChild(dropBtnTxt);
game.addChild(dropBtnBg);
// --- Shop Button ---
var shopBtnBg = LK.getAsset('takeBtnBg', {
anchorX: 0.5,
anchorY: 0.5,
x: 320,
// Move to left side of the screen, away from top left 100x100 area
y: 2732 - 200
});
var shopBtnTxt = new Text2('Shop', {
size: 60,
fill: 0xffffff
});
shopBtnTxt.anchor.set(0.5, 0.5);
shopBtnBg.addChild(shopBtnTxt);
game.addChild(shopBtnBg);
// --- Settings Button ---
var settingsBtnBg = LK.getAsset('takeBtnBg', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 - 320,
y: 2732 - 200
});
var settingsBtnTxt = new Text2('Settings', {
size: 60,
fill: 0xffffff
});
settingsBtnTxt.anchor.set(0.5, 0.5);
settingsBtnBg.addChild(settingsBtnTxt);
game.addChild(settingsBtnBg);
// --- Settings Panel ---
var settingsPanelBg = LK.getAsset('whitePanel', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2
});
settingsPanelBg.visible = false;
game.addChild(settingsPanelBg);
// Settings title
var settingsTitle = new Text2('Settings', {
size: 90,
fill: 0x222222
});
settingsTitle.anchor.set(0.5, 0);
settingsTitle.x = 0;
settingsTitle.y = -350;
settingsPanelBg.addChild(settingsTitle);
// Music toggle button
var musicToggleBtn = LK.getAsset('takeBtnBg', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: -100,
width: 400,
height: 120
});
var musicToggleTxt = new Text2('Music: On', {
size: 56,
fill: 0xffffff
});
musicToggleTxt.anchor.set(0.5, 0.5);
musicToggleBtn.addChild(musicToggleTxt);
settingsPanelBg.addChild(musicToggleBtn);
// Sound toggle button
var soundToggleBtn = LK.getAsset('takeBtnBg', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 80,
width: 400,
height: 120
});
var soundToggleTxt = new Text2('Sound: On', {
size: 56,
fill: 0xffffff
});
soundToggleTxt.anchor.set(0.5, 0.5);
soundToggleBtn.addChild(soundToggleTxt);
settingsPanelBg.addChild(soundToggleBtn);
// Set initial text for music and sound toggles
musicToggleTxt.setText('Music: On');
soundToggleTxt.setText('Sound: On');
// Settings close button
var settingsCloseBtn = LK.getAsset('takeBtnBg', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 260,
width: 300,
height: 90
});
var settingsCloseTxt = new Text2('Close', {
size: 48,
fill: 0x222222
});
settingsCloseTxt.anchor.set(0.5, 0.5);
settingsCloseBtn.addChild(settingsCloseTxt);
settingsPanelBg.addChild(settingsCloseBtn);
// Track settings state
var settingsBtnPressed = false;
var settingsClosePressed = false;
var musicTogglePressed = false;
var soundTogglePressed = false;
var musicEnabled = true;
var soundEnabled = true;
// Shop UI elements (hidden by default)
var shopPanelBg = LK.getAsset('whitePanel', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2
});
shopPanelBg.visible = false;
game.addChild(shopPanelBg);
// Shop title, centered near the top of the panel
var shopTitle = new Text2('Shop', {
size: 90,
fill: 0x222222
});
shopTitle.anchor.set(0.5, 0);
shopTitle.x = 0;
// Move the shop title higher, since capacity text is removed
shopTitle.y = -380;
shopPanelBg.addChild(shopTitle);
// Shop upgrade buttons and texts
var shopUpgradeBtns = [];
var shopUpgradeTxts = [];
var shopUpgradeCosts = [30, 30, 30]; // initial costs
var shopUpgradeLevels = [0, 0, 0]; // [capacity, speed, money]
var shopUpgradeMax = [10, 10, 10];
var shopUpgradeNames = ['Capacity', 'Speed', 'Money'];
var shopUpgradeDesc = ['Increase bus capacity by 2', 'Increase speed by 2', 'Increase money per traveler by 1'];
for (var i = 0; i < 3; i++) {
// Create a container for each row
var row = new Container();
// Vertically space rows within the white panel, below the title, with even spacing
var rowHeight = 180;
// Move the upgrade rows higher as well
var panelContentTop = shopTitle.y + shopTitle.height + 16;
row.y = panelContentTop + i * rowHeight;
// Center row horizontally in the panel
row.x = 0;
// --- Lay out texts from left to right, all inside the white panel ---
// Name
var nameTxt = new Text2(shopUpgradeNames[i], {
size: 56,
fill: 0x222222
});
nameTxt.anchor.set(0, 0.5);
nameTxt.x = -900 / 2 + 40;
nameTxt.y = 0;
// Level
var levelTxt = new Text2('(Lv.0)', {
size: 56,
fill: 0x222222
});
levelTxt.anchor.set(0, 0.5);
levelTxt.x = nameTxt.x + nameTxt.width + 24;
levelTxt.y = 0;
// Cost (now below desc, but desc removed)
var costTxt = new Text2('Cost: 30', {
size: 44,
fill: 0x888888
});
costTxt.anchor.set(0, 0.5);
// Place cost where desc would have started
costTxt.x = levelTxt.x + levelTxt.width + 24;
costTxt.y = 0;
// Upgrade button (right side, inside panel, with margin)
var btn = LK.getAsset('takeBtnBg', {
anchorX: 0.5,
anchorY: 0.5,
x: 900 / 2 - 120,
y: 0,
width: 180,
height: 70
});
btn.visible = true;
// Button label
var btnLabel = new Text2('Upgrade', {
size: 32,
fill: 0xffffff
});
btnLabel.anchor.set(0.5, 0.5);
btn.addChild(btnLabel);
// Add all texts and button to row
row.addChild(nameTxt);
row.addChild(levelTxt);
row.addChild(costTxt);
row.addChild(btn);
// Add row to shop panel
shopPanelBg.addChild(row);
// Track for logic
shopUpgradeBtns.push(btn);
// Store all text objects for update logic (desc removed)
shopUpgradeTxts.push({
name: nameTxt,
level: levelTxt,
cost: costTxt
});
}
shopPanelBg.visible = false;
// Shop close button
var shopCloseBtn = LK.getAsset('takeBtnBg', {
anchorX: 0.5,
anchorY: 0.5,
x: 900 / 2,
y: 820
});
var shopCloseTxt = new Text2('Close', {
size: 48,
fill: 0x222222
});
shopCloseTxt.anchor.set(0.5, 0.5);
shopCloseBtn.addChild(shopCloseTxt);
shopPanelBg.addChild(shopCloseBtn);
// Track if take or drop or shop button is pressed this frame
var takeBtnPressed = false;
var dropBtnPressed = false;
var shopBtnPressed = false;
var shopUpgradePressed = [false, false, false];
var shopClosePressed = false;
// Button press handlers
takeBtnBg.down = function (x, y, obj) {
if (takeBtnBg.interactive !== false && !shopPanelBg.visible) {
takeBtnPressed = true;
}
};
dropBtnBg.down = function (x, y, obj) {
if (dropBtnBg.interactive !== false && !shopPanelBg.visible) {
dropBtnPressed = true;
}
};
// Shop button handler
shopBtnBg.down = function (x, y, obj) {
if (!shopPanelBg.visible && !settingsPanelBg.visible) {
shopBtnPressed = true;
}
};
// Settings button handler
settingsBtnBg.down = function (x, y, obj) {
if (!settingsPanelBg.visible && !shopPanelBg.visible) {
settingsBtnPressed = true;
}
};
// Settings close button handler
settingsCloseBtn.down = function (x, y, obj) {
if (settingsPanelBg.visible) {
settingsClosePressed = true;
}
};
// Music toggle button handler
musicToggleBtn.down = function (x, y, obj) {
if (settingsPanelBg.visible) {
musicTogglePressed = true;
}
};
// Sound toggle button handler
soundToggleBtn.down = function (x, y, obj) {
if (settingsPanelBg.visible) {
soundTogglePressed = true;
}
};
// Shop upgrade button handlers
for (var i = 0; i < 3; i++) {
(function (idx) {
shopUpgradeBtns[idx].down = function (x, y, obj) {
if (shopPanelBg.visible) {
shopUpgradePressed[idx] = true;
}
};
})(i);
}
// Shop close button handler
shopCloseBtn.down = function (x, y, obj) {
if (shopPanelBg.visible) {
shopClosePressed = true;
}
};
// Generate a station at a given x, alternating pickup/drop and up/down
function spawnStation(x, isPickup) {
var station = new BusStation();
station.isPickup = !!isPickup;
// Re-attach correct asset if drop station (for existing BusStation instances)
if (!station.isPickup) {
if (station.children.length > 0) {
station.removeChild(station.children[0]);
}
station.attachAsset('dropStation', {
anchorX: 0.5,
anchorY: 1
});
}
// Alternate up/down for each station, placing them exactly outside the street (not inside)
var streetCenterY = streetBgY;
var stationAssetHeight = 160; // matches asset height for station/dropStation
if (stations.length % 2 === 0) {
// Place station so its bottom edge is flush with the top edge of the street (just above street)
station.y = streetCenterY - streetBgHeight / 2;
} else {
// Place station so its top edge is flush with the bottom edge of the street (just below street)
station.y = streetCenterY + streetBgHeight / 2 + stationAssetHeight;
}
// Place at given x
station.x = x;
// Spawn travelers immediately if pickup station
if (station.isPickup) {
var numTravelers = 1 + Math.floor(Math.random() * 10);
var travelerYOffset = station.y < streetCenterY ? -140 : 140;
for (var i = 0; i < numTravelers; i++) {
// Randomly choose gender: 0 = man, 1 = woman
var gender = Math.random() < 0.5 ? "man" : "woman";
var traveler = new Traveler();
traveler.gender = gender;
// Re-attach correct asset based on gender
if (traveler.children.length > 0) {
traveler.removeChild(traveler.children[0]);
}
var assetId = gender === "woman" ? "travelerWoman" : "traveler";
traveler.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 1
});
traveler.station = station;
traveler.x = 0;
traveler.y = travelerYOffset + (station.y < streetCenterY ? -i * 130 : i * 130);
station.addChild(traveler);
station.travelers.push(traveler);
}
}
// Add station above the shop panel so stations are always on top of shop UI
var shopPanelIndex = game.children.indexOf(shopPanelBg);
if (shopPanelIndex !== -1) {
game.addChildAt(station, shopPanelIndex);
} else {
game.addChild(station);
}
stations.push(station);
}
// (removed duplicate old station spawn code)
// Move handler for dragging bus
function handleMove(x, y, obj) {
// Only drag if started on bus
if (bus.dragging) {
bus.y = y - bus.dragOffsetY;
}
}
game.move = handleMove;
// Down handler: start drag if on bus, or start holding for rightward movement
var holdingRight = false;
game.down = function (x, y, obj) {
// Convert to bus local coordinates
var local = bus.toLocal(game.toGlobal({
x: x,
y: y
}));
// If within bus bounds, start drag
if (local.x > -bus.width / 2 && local.x < bus.width / 2 && local.y > -bus.height && local.y < 0) {
bus.dragging = true;
bus.dragOffsetY = y - bus.y;
} else {
// Start holding for rightward movement
holdingRight = true;
}
};
// Up handler: stop drag and stop holding rightward movement
game.up = function (x, y, obj) {
bus.dragging = false;
holdingRight = false;
};
// Main game update loop
game.update = function () {
// --- SETTINGS LOGIC ---
// Show/hide settings panel
if (settingsBtnPressed) {
settingsPanelBg.visible = true;
takeBtnBg.visible = false;
dropBtnBg.visible = false;
shopBtnBg.visible = false;
settingsBtnBg.visible = false;
settingsBtnPressed = false;
}
// Settings close
if (settingsClosePressed) {
settingsPanelBg.visible = false;
takeBtnBg.visible = true;
dropBtnBg.visible = true;
shopBtnBg.visible = true;
settingsBtnBg.visible = true;
settingsClosePressed = false;
}
// Music toggle
if (musicTogglePressed) {
musicEnabled = !musicEnabled;
musicToggleTxt.setText('Music: ' + (musicEnabled ? 'On' : 'Off'));
if (musicEnabled) {
LK.playMusic('baba', {
loop: true,
fade: {
start: 0,
end: 0.01,
duration: 1000
}
});
} else {
LK.stopMusic();
}
musicTogglePressed = false;
}
// Sound toggle
if (soundTogglePressed) {
soundEnabled = !soundEnabled;
soundToggleTxt.setText('Sound: ' + (soundEnabled ? 'On' : 'Off'));
soundTogglePressed = false;
}
// --- SHOP LOGIC ---
// Show/hide shop panel
if (shopBtnPressed) {
shopPanelBg.visible = true;
takeBtnBg.visible = false;
dropBtnBg.visible = false;
shopBtnBg.visible = false;
settingsBtnBg.visible = false;
shopBtnPressed = false;
}
// Shop close
if (shopClosePressed) {
shopPanelBg.visible = false;
takeBtnBg.visible = true;
dropBtnBg.visible = true;
shopBtnBg.visible = true;
settingsBtnBg.visible = true;
shopClosePressed = false;
}
// Update shop upgrade button texts
for (var i = 0; i < 3; i++) {
var level = shopUpgradeLevels[i];
var cost = shopUpgradeCosts[i] + level * 20;
var max = shopUpgradeMax[i];
var name = shopUpgradeNames[i];
var desc = shopUpgradeDesc[i];
// Update each text field
shopUpgradeTxts[i].name.setText(name);
shopUpgradeTxts[i].level.setText('(Lv.' + level + (level >= max ? ' MAX' : '') + ')');
shopUpgradeTxts[i].cost.setText('Cost: ' + (level >= max ? '-' : cost));
// Re-layout: update x positions in case text width changed
shopUpgradeTxts[i].level.x = shopUpgradeTxts[i].name.x + shopUpgradeTxts[i].name.width + 32;
shopUpgradeTxts[i].cost.x = shopUpgradeTxts[i].level.x + shopUpgradeTxts[i].level.width + 32;
shopUpgradeTxts[i].cost.y = 0;
shopUpgradeBtns[i].alpha = level < max ? 1 : 0.5;
shopUpgradeBtns[i].interactive = level < max;
}
// Update bus capacity text in main UI
var travelersOnBoardCount = bus.travelersOnBoard ? bus.travelersOnBoard.length : 0;
if (typeof maxCapacity === "undefined") maxCapacity = 10;
busCapacityBarText.setText(travelersOnBoardCount + "/" + maxCapacity);
// (shopBusCapacityText removed, no update needed)
// Handle shop upgrades
for (var i = 0; i < 3; i++) {
if (shopUpgradePressed[i]) {
var level = shopUpgradeLevels[i];
var cost = shopUpgradeCosts[i] + level * 20;
var max = shopUpgradeMax[i];
if (level < max && score >= cost) {
score -= cost;
scoreTxt.setText(score);
shopUpgradeLevels[i]++;
// Apply upgrade effect
if (i === 0) {
// Capacity
maxCapacity += 2;
} else if (i === 1) {
// Speed
scrollSpeed += 2;
} else if (i === 2) {
// Money
if (typeof moneyPerTraveler === "undefined") moneyPerTraveler = 1;
moneyPerTraveler += 1;
}
}
shopUpgradePressed[i] = false;
}
}
if (shopPanelBg.visible) {
// Pause game logic when shop is open
return;
}
// Move bus/world right if holding
if (typeof holdingRight !== "undefined" && holdingRight) {
var moveAmount = scrollSpeed;
// Instead of moving the bus, move the world and keep bus centered
scrollX += moveAmount;
for (var i = 0; i < stations.length; i++) {
stations[i].x -= moveAmount;
}
// Move and repeat street backgrounds
for (var i = 0; i < streetBgs.length; i++) {
streetBgs[i].x -= moveAmount;
// If streetBg is fully off the left, move it to the right end
if (streetBgs[i].x < -streetBgWidth / 2) {
// Find the rightmost streetBg
var maxX = streetBgs[0].x;
for (var j = 1; j < streetBgs.length; j++) {
if (streetBgs[j].x > maxX) maxX = streetBgs[j].x;
}
streetBgs[i].x = maxX + streetBgWidth;
}
}
}
// Always keep bus in the center of the screen horizontally
bus.x = 2048 / 2;
// Move all stations and their travelers (no auto scroll)
for (var i = 0; i < stations.length; i++) {
var station = stations[i];
// No station.x -= scrollSpeed;
station.update();
for (var j = 0; j < station.travelers.length; j++) {
station.travelers[j].update();
}
}
// Update bus
bus.update();
// --- Meter Bar Update: Show distance to nearest upcoming station ---
var nearestStation = null;
var nearestDist = null;
for (var i = 0; i < stations.length; i++) {
var station = stations[i];
// Only consider stations ahead of the bus (to the right)
var dx = station.x - bus.x;
if (dx >= 0) {
if (nearestStation === null || dx < nearestDist) {
nearestStation = station;
nearestDist = dx;
}
}
}
if (nearestStation) {
var dist = Math.max(0, Math.floor(nearestDist));
var maxDist = 1200; // Max distance for full bar
var barWidth = Math.max(0, Math.min(600, 600 * (1 - dist / maxDist)));
meterBarFill.width = barWidth;
meterBarText.setText(dist + "m");
} else {
meterBarFill.width = 0;
meterBarText.setText("0m");
}
// Endless station spawning logic
// Find the rightmost station's x
var rightmostStationX = 0;
for (var i = 0; i < stations.length; i++) {
if (stations[i].x > rightmostStationX) rightmostStationX = stations[i].x;
}
// If the rightmost station is less than 2.5 screens ahead, spawn a new one
if (rightmostStationX < scrollX + 2048 * 2.5) {
// Alternate pickup/drop
var isPickup = stations.length % 2 === 0;
// Place new station at least minStationSpacing after the last one, with some randomization
var spacing = minStationSpacing + Math.floor(Math.random() * (maxStationSpacing - minStationSpacing + 1));
var newX = rightmostStationX + spacing;
spawnStation(newX, isPickup);
}
// --- Manual pickup and drop-off logic with Take Travelers button ---
// --- Manual pickup and drop-off logic with Take Travelers button ---
if (typeof bus.travelersOnBoard === "undefined") {
bus.travelersOnBoard = [];
bus.lastPickupStationIndex = -1;
bus.lastDropStationIndex = -1;
}
// --- Enable/disable Take Travelers button based on action possibility ---
var canTake = false;
var canDrop = false;
var dropStationIndex = -1;
// Check if we can take travelers at a nearby station
for (var i = 0; i < stations.length; i++) {
var station = stations[i];
// Take: must be pickup station, has travelers, close, and not already picked up here
if (station.isPickup && station.travelers.length > 0 && Math.abs(station.x - bus.x) < 180 && bus.lastPickupStationIndex !== i) {
canTake = true;
}
// Drop: must be drop station, bus has travelers, close, and not already dropped here
if (!station.isPickup && bus.travelersOnBoard && bus.travelersOnBoard.length > 0 && Math.abs(station.x - bus.x) < 180 && bus.lastDropStationIndex !== i) {
canDrop = true;
dropStationIndex = i;
}
}
// --- Bus Capacity Bar Update ---
if (typeof maxCapacity === "undefined") maxCapacity = 10;
var travelersOnBoardCount = bus.travelersOnBoard ? bus.travelersOnBoard.length : 0;
var capBarWidth = Math.max(0, Math.min(600, 600 * (travelersOnBoardCount / maxCapacity)));
busCapacityBarFill.width = capBarWidth;
// Show plusCapacityBarFill for new travelers picked up this round
if (typeof bus.lastTravelersOnBoardCount === "undefined") bus.lastTravelersOnBoardCount = 0;
var plusCount = travelersOnBoardCount - bus.lastTravelersOnBoardCount;
if (plusCount > 0) {
var plusBarWidth = Math.max(0, Math.min(600, 600 * (plusCount / maxCapacity)));
plusCapacityBarFill.width = plusBarWidth;
plusCapacityBarFill.visible = true;
} else {
plusCapacityBarFill.width = 0;
plusCapacityBarFill.visible = false;
}
// busCapacityBarText removed from main UI; only update shopBusCapacityText in shop panel
// Visually enable/disable the take and drop buttons
takeBtnBg.alpha = canTake ? 1 : 0.4;
takeBtnBg.interactive = canTake;
dropBtnBg.alpha = canDrop ? 1 : 0.4;
dropBtnBg.interactive = canDrop;
// --- Manual Pickup logic: only when button pressed ---
if (typeof maxCapacity === "undefined") maxCapacity = 10;
if (typeof moneyPerTraveler === "undefined") moneyPerTraveler = 1;
if (takeBtnPressed) {
for (var i = 0; i < stations.length; i++) {
var station = stations[i];
// Only pick up if bus is close to pickup station, this is a new station, and this station has travelers
if (station.isPickup && station.travelers.length > 0 && Math.abs(station.x - bus.x) < 180 && bus.lastPickupStationIndex !== i) {
// Pick up all waiting travelers at this station
var waitingTravelers = [];
for (var j = station.travelers.length - 1; j >= 0; j--) {
var traveler = station.travelers[j];
if (traveler.waiting) {
waitingTravelers.push(traveler);
}
}
// Actually pick up the travelers, but only up to available capacity
var travelersOnBoardCount = bus.travelersOnBoard ? bus.travelersOnBoard.length : 0;
var availableCapacity = maxCapacity - travelersOnBoardCount;
var numToPick = Math.min(availableCapacity, waitingTravelers.length);
for (var w = 0; w < numToPick; w++) {
var traveler = waitingTravelers[w];
traveler.waiting = false;
// Animate traveler to bus, then destroy
tween(traveler, {
x: bus.x - station.x,
y: bus.y - station.y - 100,
alpha: 0
}, {
duration: 400,
easing: tween.easeIn,
onFinish: function (trav) {
return function () {
trav.destroy();
};
}(traveler)
});
// Add to bus's onboard list
if (!bus.travelersOnBoard) bus.travelersOnBoard = [];
bus.travelersOnBoard.push({
traveler: traveler,
pickedUpAt: i
});
// Remove from station
station.removeTraveler(traveler);
// No score for pickup; award score on drop-off instead
}
// Mark this station as the last pickup
bus.lastPickupStationIndex = i;
// Update lastTravelersOnBoardCount for plus bar
bus.lastTravelersOnBoardCount = bus.travelersOnBoard ? bus.travelersOnBoard.length : 0;
// Play cheer sound when travelers are picked up
if (soundEnabled) {
LK.getSound('cheer').play();
}
break; // Only pick up at one station per press
}
}
takeBtnPressed = false; // Reset button press
}
// --- Manual Drop-off logic: only when drop button pressed ---
if (dropBtnPressed && canDrop && dropStationIndex !== -1) {
var station = stations[dropStationIndex];
// Drop all travelers currently on the bus
if (bus.travelersOnBoard && bus.travelersOnBoard.length > 0) {
// Calculate base Y for stacking
var baseY = 0;
var stackDir = 1;
// If station is above street, stack upwards; if below, stack downwards
var streetCenterY = streetBgY;
if (station.y < streetCenterY) {
baseY = -140;
stackDir = -1;
} else {
baseY = 140;
stackDir = 1;
}
// Count how many travelers are already at this drop station (for stacking)
var alreadyDropped = 0;
for (var tt = 0; tt < station.travelers.length; tt++) {
if (station.travelers[tt].dropped) alreadyDropped++;
}
// Count how many men and women are on board
var menToDrop = 0;
var womenToDrop = 0;
for (var t = 0; t < bus.travelersOnBoard.length; t++) {
var travelerObj = bus.travelersOnBoard[t];
var traveler = travelerObj.traveler;
if (traveler.gender === "woman") {
womenToDrop++;
} else {
menToDrop++;
}
}
// Drop correct number of men and women, stacking them
var dropIndex = 0;
for (var t = 0; t < menToDrop; t++, dropIndex++) {
var newTraveler = new Traveler();
newTraveler.gender = "man";
// Remove default asset and attach correct one
if (newTraveler.children.length > 0) {
newTraveler.removeChild(newTraveler.children[0]);
}
newTraveler.attachAsset('traveler', {
anchorX: 0.5,
anchorY: 1
});
newTraveler.x = bus.x - station.x;
newTraveler.y = bus.y - station.y - 100;
newTraveler.alpha = 0;
newTraveler.waiting = false;
newTraveler.dropped = true;
newTraveler.station = station;
// Stack vertically at the station
var stackIndex = alreadyDropped + dropIndex;
newTraveler.x = 0;
newTraveler.y = baseY + stackDir * stackIndex * 130;
newTraveler.alpha = 0;
station.addChild(newTraveler);
station.travelers.push(newTraveler);
tween(newTraveler, {
x: 0,
y: baseY + stackDir * stackIndex * 130,
alpha: 1
}, {
duration: 400,
easing: tween.easeOut
});
// Award score for each traveler dropped
score += moneyPerTraveler;
scoreTxt.setText(score);
}
for (var t = 0; t < womenToDrop; t++, dropIndex++) {
var newTraveler = new Traveler();
newTraveler.gender = "woman";
// Remove default asset and attach correct one
if (newTraveler.children.length > 0) {
newTraveler.removeChild(newTraveler.children[0]);
}
newTraveler.attachAsset('travelerWoman', {
anchorX: 0.5,
anchorY: 1
});
newTraveler.x = bus.x - station.x;
newTraveler.y = bus.y - station.y - 100;
newTraveler.alpha = 0;
newTraveler.waiting = false;
newTraveler.dropped = true;
newTraveler.station = station;
// Stack vertically at the station
var stackIndex = alreadyDropped + dropIndex;
newTraveler.x = 0;
newTraveler.y = baseY + stackDir * stackIndex * 130;
newTraveler.alpha = 0;
station.addChild(newTraveler);
station.travelers.push(newTraveler);
tween(newTraveler, {
x: 0,
y: baseY + stackDir * stackIndex * 130,
alpha: 1
}, {
duration: 400,
easing: tween.easeOut
});
// Award score for each traveler dropped
score += moneyPerTraveler;
scoreTxt.setText(score);
}
// Clear bus
bus.travelersOnBoard = [];
// Reset lastTravelersOnBoardCount for plus bar
bus.lastTravelersOnBoardCount = 0;
// Mark this station as the last drop
bus.lastDropStationIndex = dropStationIndex;
// Play cheer sound when travelers are dropped off
if (soundEnabled) {
LK.getSound('cheer').play();
}
}
dropBtnPressed = false; // Reset button press
}
};