User prompt
pause menüsünden ses özelliklerini açıp kapatabilelim.
User prompt
ses dosyalarını ayarlayalım güzel bir background sound oluşturalım, paket teslim alındığında "thanks, drive safe" gibi kelimeler kullanılsın. Teslimat yapıldığında ise "thank you, here a tip for you" ve paradüşme sesi ekleyelim. Eğer peşimize polis takılırsa süreğen bir siren sesi olsun. Ayrıca vandallar paketimizi çalarsa kaza sesi veya ürkütücü kahkaha sesi ekleyelim.
User prompt
binalara çarpınca araçlar yola kaysın. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
yön gösterici ok oyuncuya daha yakın ve büyük olsun. Ve heart beaten şeklinde dükkana veya müşteriye yakınlaştıkça daha hızlı uzakken daha yavaş bir animasyon olsun. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Please fix the bug: 'Cannot set properties of undefined (setting 'x')' in or related to this line: 'policeNPC.patrol.x = 200 + Math.random() * 1600;' Line Number: 1312
User prompt
npc ler haritada sürekli gezmek zorundalar. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Tüm npcleri sil ve baştan yarat. Bina görüntülerinin etrafında gezebilecekler fakat binalara temas edemezler. Polisler sürekli vandal ve insurance ı olmayan kuryeleri arayacaklar. Vandallar polisi gördüklerinde kaçacaklar. Tüm npc ler maksimum 20px etrafını algılayabilirler.
User prompt
npc kuryeler durmadan paket alıp taşırlar.
User prompt
npc kuryelere paket alma ve teslim etme konusunda biraz daha esnek ol paket teslim almalarını engelleyen birşey var.
User prompt
npc kuryeler görünmüyorlarherhangi bir teslimat gerçekleşmiyor kontrol et
User prompt
polislerin kuryelerin ve vandalların doğdukları noktaları tekrar gözden geçir ve polisleri merkezden, vandalları haritanın 4 köşesinden, npc kuryeleri de shop ların önünde başlamasını sağla. NPC kuryeler belirli rotalardan paket alıp taşısınlar.
User prompt
im here yazısını 10 px e küçült ve rengini beyaz yap 3px siyah stroke ekle
User prompt
Im here yazılarını küçült
User prompt
biraz daha yaklaşabiliriz mahalle ekrana sığacak kadar
User prompt
tanrı modu oluştur ve tüm haritayı görebileceği kadar zoom out çalışsın.
User prompt
bütün npc ler yollarda doğmak zorunda bina ile çakışamazlar. Hepsi hareket halinde olmak zorunda. Kuryeler sipariş alıp teslim etmek zorunda.
User prompt
bütün npc ler sadece çevresindeki binaları düşünsün. Genel olarak haritayı düşündükleri için hem işlemci yoruluyor hemde hatalar yapıyorlar hareket etmiyorlar.
User prompt
yazı renkleri beyaz olsun
User prompt
komple gri arkaplanı sil ve vandal butonu aynı şekilde kalsın
User prompt
genel olarak menünün transparan görüntüsü sankı yazıların üstündeymiş gibi duruyor
User prompt
vandal seçeneğinin arka planını beyaz yap
User prompt
Please fix the bug: 'FFFFFF is not defined' in or related to this line: 'var policeButton = new Text2('POLICE', {' Line Number: 968
Code edit (1 edits merged)
Please save this source code
User prompt
evet şimdi Choose your role menüsündeki her bir yazıya 5px lik beyaz stroke ekle
User prompt
şimdi okunabilir boyutlara küçült
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var ArrowIndicator = Container.expand(function (targetX, targetY, color, arrowType) {
var self = Container.call(this);
// Create arrow shape using dedicated arrow assets
var assetName = arrowType === 'pickup' ? 'pickupArrow' : 'deliveryArrow';
var arrowGraphics = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5
});
arrowGraphics.scaleX = 0.8;
arrowGraphics.scaleY = 1.2;
self.targetX = targetX;
self.targetY = targetY;
self.update = function () {
// Calculate direction to target
var dx = self.targetX - courier.x;
var dy = self.targetY - courier.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 150) {
// Position arrow at edge of screen pointing toward target
var angle = Math.atan2(dy, dx);
var edgeDistance = 300;
// Position relative to screen center (where courier appears)
self.x = 1024 + Math.cos(angle) * edgeDistance;
self.y = 1366 + Math.sin(angle) * edgeDistance;
// Rotate arrow to point toward target
arrowGraphics.rotation = angle;
self.visible = true;
} else {
self.visible = false;
}
};
return self;
});
var Courier = Container.expand(function () {
var self = Container.call(this);
var courierGraphics = self.attachAsset('courierAsset', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 8;
self.hasOrder = false;
self.targetRestaurant = null;
self.targetCustomer = null;
self.moveToward = function (targetX, targetY) {
var dx = targetX - self.x;
var dy = targetY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 5) {
// Smooth movement with momentum for agar.io feel
var moveSpeed = Math.min(self.speed, distance * 0.15);
var newX = self.x + dx / distance * moveSpeed;
var newY = self.y + dy / distance * moveSpeed;
// Check collision with buildings
var canMove = true;
for (var i = 0; i < buildings.length; i++) {
var tempCourier = {
x: newX,
y: newY,
width: 9,
height: 9
};
var building = buildings[i];
if (tempCourier.x < building.x + building.width / 2 && tempCourier.x + tempCourier.width / 2 > building.x - building.width / 2 && tempCourier.y < building.y + building.height / 2 && tempCourier.y + tempCourier.height / 2 > building.y - building.height / 2) {
canMove = false;
break;
}
}
// Check collision with restaurants/shops
for (var i = 0; i < restaurants.length; i++) {
var tempCourier = {
x: newX,
y: newY,
width: 9,
height: 9
};
var restaurant = restaurants[i];
if (tempCourier.x < restaurant.x + restaurant.width / 2 && tempCourier.x + tempCourier.width / 2 > restaurant.x - restaurant.width / 2 && tempCourier.y < restaurant.y + restaurant.height / 2 && tempCourier.y + tempCourier.height / 2 > restaurant.y - restaurant.height / 2) {
canMove = false;
break;
}
}
// Check collision with customers
for (var i = 0; i < customers.length; i++) {
var tempCourier = {
x: newX,
y: newY,
width: 9,
height: 9
};
var customer = customers[i];
if (tempCourier.x < customer.x + 1 / 2 && tempCourier.x + tempCourier.width / 2 > customer.x - 1 / 2 && tempCourier.y < customer.y + 1 / 2 && tempCourier.y + tempCourier.height / 2 > customer.y - 1 / 2) {
canMove = false;
break;
}
}
if (canMove) {
self.x = newX;
self.y = newY;
}
}
};
return self;
});
var Customer = Container.expand(function () {
var self = Container.call(this);
var customerGraphics = self.attachAsset('customer', {
anchorX: 0.5,
anchorY: 0.5
});
self.waitingForOrder = false;
self.orderTime = 0;
self.maxWaitTime = 10000; // 10 seconds
self.deliveryTarget = null;
self.orderColor = 0x2196f3; // Default blue
self.setOrderColor = function (color) {
self.orderColor = color;
customerGraphics.tint = color;
};
self.update = function () {
if (self.waitingForOrder) {
self.orderTime += 16.67; // Approximate ms per frame at 60fps
}
};
return self;
});
var NPCCourier = Container.expand(function () {
var self = Container.call(this);
var courierGraphics = self.attachAsset('courierAsset', {
anchorX: 0.5,
anchorY: 0.5
});
courierGraphics.tint = 0xff5722;
self.speed = 2;
self.detectionRadius = 20;
self.hasInsurance = false;
self.patrol = {
x: 1024,
y: 1366
};
self.lastX = self.x;
self.lastY = self.y;
self.lastSeenPolice = false;
self.lastSeenVandal = false;
self.thinkTimer = 0;
self.moveToward = function (targetX, targetY) {
var dx = targetX - self.x;
var dy = targetY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 5) {
var newX = self.x + dx / distance * self.speed;
var newY = self.y + dy / distance * self.speed;
var canMove = true;
// Check collision with buildings
for (var i = 0; i < buildings.length; i++) {
var building = buildings[i];
var tempCourier = {
x: newX,
y: newY,
width: 15,
height: 15
};
if (tempCourier.x - tempCourier.width / 2 < building.x + building.width / 2 && tempCourier.x + tempCourier.width / 2 > building.x - building.width / 2 && tempCourier.y - tempCourier.height / 2 < building.y + building.height / 2 && tempCourier.y + tempCourier.height / 2 > building.y - building.height / 2) {
canMove = false;
break;
}
}
if (canMove) {
self.x = newX;
self.y = newY;
}
}
};
self.update = function () {
self.thinkTimer += 16.67;
if (self.thinkTimer >= 100) {
self.thinkTimer = 0;
// Get new patrol point if reached current one
if (Math.abs(self.x - self.patrol.x) < 30 && Math.abs(self.y - self.patrol.y) < 30) {
self.patrol.x = 200 + Math.random() * 1600;
self.patrol.y = 200 + Math.random() * 2300;
}
// Move around buildings
self.moveToward(self.patrol.x, self.patrol.y);
}
self.lastX = self.x;
self.lastY = self.y;
};
return self;
});
var NPCPolice = Container.expand(function () {
var self = Container.call(this);
var policeGraphics = self.attachAsset('policeAsset', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 3;
self.detectionRadius = 20;
self.patrol = {
x: 1024,
y: 1366
};
self.target = null;
self.lastX = self.x;
self.lastY = self.y;
self.thinkTimer = 0;
self.moveToward = function (targetX, targetY) {
var dx = targetX - self.x;
var dy = targetY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 5) {
var newX = self.x + dx / distance * self.speed;
var newY = self.y + dy / distance * self.speed;
var canMove = true;
// Check collision with buildings
for (var i = 0; i < buildings.length; i++) {
var building = buildings[i];
var tempPolice = {
x: newX,
y: newY,
width: 15,
height: 15
};
if (tempPolice.x - tempPolice.width / 2 < building.x + building.width / 2 && tempPolice.x + tempPolice.width / 2 > building.x - building.width / 2 && tempPolice.y - tempPolice.height / 2 < building.y + building.height / 2 && tempPolice.y + tempPolice.height / 2 > building.y - building.height / 2) {
canMove = false;
break;
}
}
if (canMove) {
self.x = newX;
self.y = newY;
}
}
};
self.update = function () {
self.thinkTimer += 16.67;
if (self.thinkTimer >= 100) {
self.thinkTimer = 0;
self.target = null;
// Look for vandals
for (var i = 0; i < npcs.length; i++) {
var npc = npcs[i];
if (npc.npcType === 'vandal') {
var dx = npc.x - self.x;
var dy = npc.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.detectionRadius) {
self.target = npc;
break;
}
}
}
// Look for uninsured couriers
if (!self.target) {
for (var i = 0; i < npcs.length; i++) {
var npc = npcs[i];
if (npc.npcType === 'courier' && !npc.hasInsurance) {
var dx = npc.x - self.x;
var dy = npc.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.detectionRadius) {
self.target = npc;
break;
}
}
}
}
// Check player courier if no insurance
if (!self.target && playerRole === 'courier' && courier && !hasInsurance) {
var dx = courier.x - self.x;
var dy = courier.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.detectionRadius) {
self.target = courier;
}
}
}
// Move behavior
if (self.target) {
self.moveToward(self.target.x, self.target.y);
} else {
// Get new patrol point if reached current one
if (Math.abs(self.x - self.patrol.x) < 30 && Math.abs(self.y - self.patrol.y) < 30) {
self.patrol.x = 200 + Math.random() * 1600;
self.patrol.y = 200 + Math.random() * 2300;
}
self.moveToward(self.patrol.x, self.patrol.y);
}
self.lastX = self.x;
self.lastY = self.y;
};
return self;
});
var NPCVandal = Container.expand(function () {
var self = Container.call(this);
var vandalGraphics = self.attachAsset('vandalAsset', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 2;
self.detectionRadius = 20;
self.patrol = {
x: 1024,
y: 1366
};
self.fleeing = false;
self.lastX = self.x;
self.lastY = self.y;
self.thinkTimer = 0;
self.moveToward = function (targetX, targetY) {
var dx = targetX - self.x;
var dy = targetY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 5) {
var newX = self.x + dx / distance * self.speed;
var newY = self.y + dy / distance * self.speed;
var canMove = true;
// Check collision with buildings
for (var i = 0; i < buildings.length; i++) {
var building = buildings[i];
var tempVandal = {
x: newX,
y: newY,
width: 15,
height: 15
};
if (tempVandal.x - tempVandal.width / 2 < building.x + building.width / 2 && tempVandal.x + tempVandal.width / 2 > building.x - building.width / 2 && tempVandal.y - tempVandal.height / 2 < building.y + building.height / 2 && tempVandal.y + tempVandal.height / 2 > building.y - building.height / 2) {
canMove = false;
break;
}
}
if (canMove) {
self.x = newX;
self.y = newY;
}
}
};
self.update = function () {
self.thinkTimer += 16.67;
if (self.thinkTimer >= 100) {
self.thinkTimer = 0;
self.fleeing = false;
// Look for police
for (var i = 0; i < npcs.length; i++) {
var npc = npcs[i];
if (npc.npcType === 'police') {
var dx = npc.x - self.x;
var dy = npc.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.detectionRadius) {
self.fleeing = true;
// Run in opposite direction
self.patrol.x = self.x - dx * 10;
self.patrol.y = self.y - dy * 10;
// Keep within bounds
self.patrol.x = Math.max(100, Math.min(1900, self.patrol.x));
self.patrol.y = Math.max(200, Math.min(2500, self.patrol.y));
break;
}
}
}
// Check player police
if (!self.fleeing && playerRole === 'police' && courier) {
var dx = courier.x - self.x;
var dy = courier.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.detectionRadius) {
self.fleeing = true;
// Run in opposite direction
self.patrol.x = self.x - dx * 10;
self.patrol.y = self.y - dy * 10;
// Keep within bounds
self.patrol.x = Math.max(100, Math.min(1900, self.patrol.x));
self.patrol.y = Math.max(200, Math.min(2500, self.patrol.y));
}
}
// Get new patrol point if not fleeing and reached current one
if (!self.fleeing && Math.abs(self.x - self.patrol.x) < 30 && Math.abs(self.y - self.patrol.y) < 30) {
self.patrol.x = 200 + Math.random() * 1600;
self.patrol.y = 200 + Math.random() * 2300;
}
}
// Move behavior
self.moveToward(self.patrol.x, self.patrol.y);
self.lastX = self.x;
self.lastY = self.y;
};
return self;
});
var Order = Container.expand(function () {
var self = Container.call(this);
var orderGraphics = self.attachAsset('order', {
anchorX: 0.5,
anchorY: 0.5
});
self.restaurant = null;
self.customer = null;
self.baseTimeLimit = 25000; // 25 seconds minimum
self.timeLimit = self.baseTimeLimit;
self.timeRemaining = self.timeLimit;
self.isPickedUp = false;
self.courierSpeed = 8; // Default courier speed
self.setColor = function (color) {
orderGraphics.tint = color;
};
self.startDeliveryTimer = function (courierSpeed) {
self.isPickedUp = true;
self.courierSpeed = courierSpeed;
self.timeLimit = self.baseTimeLimit;
self.timeRemaining = self.timeLimit;
};
self.updateTimeLimit = function (courierX, courierY) {
if (!self.isPickedUp || !self.customer) {
return;
}
// Calculate distance to customer
var dx = self.customer.x - courierX;
var dy = self.customer.y - courierY;
var distance = Math.sqrt(dx * dx + dy * dy);
// Calculate time needed to reach customer at max speed + 2 seconds buffer
var timeToReach = distance / self.courierSpeed * 16.67 + 2000; // Convert to ms and add 2s buffer
// Use the greater of base time limit or calculated time
var newTimeLimit = Math.max(self.baseTimeLimit, timeToReach);
// Only extend time, never reduce it
if (newTimeLimit > self.timeLimit) {
var extension = newTimeLimit - self.timeLimit;
self.timeLimit = newTimeLimit;
self.timeRemaining += extension;
}
};
self.update = function () {
if (self.isPickedUp && self.timeRemaining > 0) {
self.timeRemaining -= 16.67;
}
};
return self;
});
var PowerUp = Container.expand(function (type) {
var self = Container.call(this);
var powerupGraphics = self.attachAsset(type === 'health' ? 'healthPowerup' : 'timePowerup', {
anchorX: 0.5,
anchorY: 0.5
});
self.type = type;
self.lifeTime = 0;
self.maxLifeTime = 8000; // 8 seconds
self.update = function () {
self.lifeTime += 16.67;
// Pulse animation
var pulse = 1 + 0.2 * Math.sin(self.lifeTime * 0.01);
powerupGraphics.scaleX = pulse;
powerupGraphics.scaleY = pulse;
};
return self;
});
var Restaurant = Container.expand(function () {
var self = Container.call(this);
var restaurantGraphics = self.attachAsset('restaurant', {
anchorX: 0.5,
anchorY: 0.5
});
self.hasOrder = false;
self.orderReady = false;
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x8BC34A
});
/****
* Game Code
****/
// Game variables
var courier;
var npcs = [];
var restaurants = [];
var customers = [];
var orders = [];
var aiOrders = [];
var powerUps = [];
var buildings = [];
var health = 100;
var money = 20;
var currentOrder = null;
var gameState = 'pickup'; // 'pickup', 'delivery'
var timeFreezeActive = false;
var timeFreezeRemaining = 0;
// Role system
var playerRole = storage.playerRole || null; // 'courier', 'police', 'vandal'
var hasInsurance = false;
var insuranceEndTime = 0;
var gameStarted = false;
// Camera system
var cameraX = 0;
var cameraY = 0;
var worldContainer;
var pickupArrow = null;
var deliveryArrow = null;
// God mode system
var godMode = false;
var godModeScale = 0.8; // Zoom out scale for full map view
var normalScale = 3; // Normal game scale
// Order colors for variety
var orderColors = [0xffff00, 0xff4444, 0x44ff44, 0x4444ff, 0xff44ff, 0x44ffff, 0xffa500, 0x800080];
// UI Elements
var healthBar = new Text2('Health: 100', {
size: 48
});
healthBar.anchor.set(0, 0);
LK.gui.topLeft.addChild(healthBar);
healthBar.x = 120;
healthBar.y = 20;
var moneyText = new Text2('$0', {
size: 48
});
moneyText.anchor.set(1, 0);
LK.gui.topRight.addChild(moneyText);
moneyText.x = -20;
moneyText.y = 20;
var orderTimer = new Text2('', {
size: 42
});
orderTimer.anchor.set(0.5, 0);
LK.gui.top.addChild(orderTimer);
orderTimer.y = 100;
// Role selection UI
var roleSelectionContainer = new Container();
LK.gui.center.addChild(roleSelectionContainer);
var roleTitle = new Text2('Choose Your Role', {
size: 72,
fill: 0xFFFFFF,
stroke: 0xFFFFFF,
strokeThickness: 8
});
roleTitle.anchor.set(0.5, 0.5);
roleTitle.y = -200;
roleSelectionContainer.addChild(roleTitle);
// Create green background for courier button
var courierButtonBg = LK.getAsset('road', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3,
scaleY: 1
});
courierButtonBg.tint = 0x4caf50;
courierButtonBg.y = -50;
roleSelectionContainer.addChild(courierButtonBg);
var courierButton = new Text2('COURIER', {
size: 48,
fill: 0xFFFFFF,
stroke: 0xFFFFFF,
strokeThickness: 8
});
courierButton.anchor.set(0.5, 0.5);
courierButton.y = -50;
roleSelectionContainer.addChild(courierButton);
// Create blue background for police button
var policeButtonBg = LK.getAsset('road', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3,
scaleY: 1
});
policeButtonBg.tint = 0x2196f3;
policeButtonBg.y = 50;
roleSelectionContainer.addChild(policeButtonBg);
var policeButton = new Text2('POLICE', {
size: 48,
fill: 0xFFFFFF,
stroke: 0xFFFFFF,
strokeThickness: 8
});
policeButton.anchor.set(0.5, 0.5);
policeButton.y = 50;
roleSelectionContainer.addChild(policeButton);
// Create white background for vandal button
var vandalButtonBg = LK.getAsset('road', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3,
scaleY: 1
});
vandalButtonBg.tint = 0xFFFFFF;
vandalButtonBg.y = 150;
roleSelectionContainer.addChild(vandalButtonBg);
var vandalButton = new Text2('VANDAL', {
size: 48,
fill: 0xFFFFFF,
stroke: 0xFFFFFF,
strokeThickness: 8
});
vandalButton.anchor.set(0.5, 0.5);
vandalButton.y = 150;
roleSelectionContainer.addChild(vandalButton);
// Insurance UI (for couriers)
var insuranceButton = new Text2('Buy Insurance ($5)', {
size: 36
});
insuranceButton.anchor.set(0.5, 0);
LK.gui.top.addChild(insuranceButton);
insuranceButton.y = 160;
insuranceButton.visible = false;
var insuranceStatus = new Text2('', {
size: 32
});
insuranceStatus.anchor.set(0.5, 0);
LK.gui.top.addChild(insuranceStatus);
insuranceStatus.y = 210;
// God mode toggle button
var godModeButton = new Text2('God Mode: OFF', {
size: 36,
fill: 0xFFFFFF,
stroke: 0x000000,
strokeThickness: 4
});
godModeButton.anchor.set(1, 0);
LK.gui.topRight.addChild(godModeButton);
godModeButton.x = -20;
godModeButton.y = 80;
// Create neighborhood layout
function createNeighborhood() {
// Fill entire screen with buildings and restaurants
var buildingRows = 20; // More rows to fill the map
var buildingCols = 15; // More columns to fill the map
var gridSpacingX = 135; // Tighter spacing
var gridSpacingY = 135; // Tighter spacing
var startX = 70; // Start position
var startY = 150; // Start from top, leaving space for UI
// Helper function to check if two objects overlap
function checkOverlap(obj1, obj2) {
var obj1Left = obj1.x - obj1.width / 2;
var obj1Right = obj1.x + obj1.width / 2;
var obj1Top = obj1.y - obj1.height / 2;
var obj1Bottom = obj1.y + obj1.height / 2;
var obj2Left = obj2.x - obj2.width / 2;
var obj2Right = obj2.x + obj2.width / 2;
var obj2Top = obj2.y - obj2.height / 2;
var obj2Bottom = obj2.y + obj2.height / 2;
return !(obj1Right < obj2Left || obj1Left > obj2Right || obj1Bottom < obj2Top || obj1Top > obj2Bottom);
}
// Create array of all positions
var allPositions = [];
for (var row = 0; row < buildingRows; row++) {
for (var col = 0; col < buildingCols; col++) {
var buildingX = startX + col * gridSpacingX;
var buildingY = startY + row * gridSpacingY;
// Ensure buildings stay within reasonable bounds
if (buildingX < 2000 && buildingY < 2700) {
allPositions.push({
x: buildingX,
y: buildingY
});
}
}
}
// Calculate shop count (1 shop per 15 buildings)
var totalPositions = allPositions.length;
var shopCount = Math.floor(totalPositions / 16); // 1 shop for every 15 buildings (16 total positions)
// Shuffle positions to randomly distribute shops
for (var i = allPositions.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = allPositions[i];
allPositions[i] = allPositions[j];
allPositions[j] = temp;
}
// Track all placed objects to prevent overlaps
var placedObjects = [];
// Place shops in first N positions after shuffle
for (var i = 0; i < shopCount; i++) {
var pos = allPositions[i];
var restaurant = worldContainer.addChild(new Restaurant());
restaurant.x = pos.x;
restaurant.y = pos.y;
restaurant.width = 60; // Restaurant asset width
restaurant.height = 60; // Restaurant asset height
// Check for overlaps with existing objects
var overlaps = false;
for (var j = 0; j < placedObjects.length; j++) {
if (checkOverlap(restaurant, placedObjects[j])) {
overlaps = true;
break;
}
}
if (!overlaps) {
restaurants.push(restaurant);
placedObjects.push(restaurant);
} else {
// Remove restaurant if it overlaps
restaurant.destroy();
}
}
// Place buildings in remaining positions
for (var i = shopCount; i < totalPositions; i++) {
var pos = allPositions[i];
var building = worldContainer.addChild(LK.getAsset('building', {
anchorX: 0.5,
anchorY: 0.5,
x: pos.x,
y: pos.y,
scaleX: 2.5,
scaleY: 2.5
}));
building.width = 40; // Building collision width (unchanged)
building.height = 40; // Building collision height (unchanged)
// Check for overlaps with existing objects
var overlaps = false;
for (var j = 0; j < placedObjects.length; j++) {
if (checkOverlap(building, placedObjects[j])) {
overlaps = true;
break;
}
}
if (!overlaps) {
buildings.push(building);
placedObjects.push(building);
} else {
// Remove building if it overlaps
building.destroy();
}
}
// Create customers - place them outside buildings (not restaurants)
var customerCount = Math.min(25, buildings.length);
for (var i = 0; i < customerCount; i++) {
var customer = worldContainer.addChild(new Customer());
// Place customer further outside building to account for 2.5x visual scale
// Buildings are visually 2.5x larger, so move customers proportionally further out
customer.x = buildings[i].x + buildings[i].width / 2 * 2.5 + 15;
customer.y = buildings[i].y + buildings[i].height / 2 * 2.5 + 15;
// Hide all customers initially - only show when they have an order
customer.visible = false;
customers.push(customer);
}
// Create boundary fences around the neighborhood
// Top fence
var topFence = worldContainer.addChild(LK.getAsset('fence', {
anchorX: 0.5,
anchorY: 0.5,
x: 1000,
y: 10,
scaleX: 20,
scaleY: 1
}));
topFence.width = 2000;
topFence.height = 20;
buildings.push(topFence);
// Bottom fence
var bottomFence = worldContainer.addChild(LK.getAsset('fence', {
anchorX: 0.5,
anchorY: 0.5,
x: 1000,
y: 2722,
scaleX: 20,
scaleY: 1
}));
bottomFence.width = 2000;
bottomFence.height = 20;
buildings.push(bottomFence);
// Left fence
var leftFence = worldContainer.addChild(LK.getAsset('fence', {
anchorX: 0.5,
anchorY: 0.5,
x: 10,
y: 1400,
scaleX: 1,
scaleY: 135
}));
leftFence.width = 20;
leftFence.height = 2700;
buildings.push(leftFence);
// Right fence
var rightFence = worldContainer.addChild(LK.getAsset('fence', {
anchorX: 0.5,
anchorY: 0.5,
x: 2038,
y: 1400,
scaleX: 1,
scaleY: 135
}));
rightFence.width = 20;
rightFence.height = 2700;
buildings.push(rightFence);
}
function createNewOrder() {
if (currentOrder) {
return;
}
var availableRestaurants = restaurants.filter(function (r) {
return !r.hasOrder;
});
var availableCustomers = customers.filter(function (c) {
return !c.waitingForOrder;
});
if (availableRestaurants.length === 0 || availableCustomers.length === 0) {
return;
}
var restaurant = availableRestaurants[Math.floor(Math.random() * availableRestaurants.length)];
var customer = availableCustomers[Math.floor(Math.random() * availableCustomers.length)];
var order = worldContainer.addChild(new Order());
order.x = restaurant.x;
order.y = restaurant.y - 60;
order.restaurant = restaurant;
order.customer = customer;
order.claimed = false; // Allow AI couriers to claim this order
// Set random order color and match customer color
var orderColor = orderColors[Math.floor(Math.random() * orderColors.length)];
order.setColor(orderColor);
customer.setOrderColor(orderColor);
restaurant.hasOrder = true;
restaurant.orderReady = true;
customer.waitingForOrder = true;
customer.orderTime = 0;
// Show only this customer for player orders
customer.visible = true;
// Hide all other customers that might be visible from AI orders
for (var c = 0; c < customers.length; c++) {
if (customers[c] !== customer) {
customers[c].visible = false;
}
}
// Add "I'm here" text to the building that has the customer
var building = null;
// Find the building this customer belongs to (customers are positioned relative to buildings)
for (var b = 0; b < buildings.length; b++) {
var buildingCenterX = buildings[b].x;
var buildingCenterY = buildings[b].y;
var expectedCustomerX = buildingCenterX + buildings[b].width / 2 * 2.5 + 15;
var expectedCustomerY = buildingCenterY + buildings[b].height / 2 * 2.5 + 15;
if (Math.abs(customer.x - expectedCustomerX) < 5 && Math.abs(customer.y - expectedCustomerY) < 5) {
building = buildings[b];
break;
}
}
if (building && !building.customerText) {
var customerText = new Text2("I'm here", {
size: 10,
fill: 0xffffff,
stroke: 0x000000,
strokeThickness: 3
});
customerText.anchor.set(0.5, 0.5);
customerText.x = 0;
customerText.y = -30;
building.addChild(customerText);
building.customerText = customerText;
}
customer.deliveryTarget = null;
currentOrder = order;
orders.push(order);
gameState = 'pickup';
// Create pickup arrow indicator
if (pickupArrow) {
pickupArrow.destroy();
}
pickupArrow = game.addChild(new ArrowIndicator(restaurant.x, restaurant.y, 0x00ff00, 'pickup'));
}
function createAIOrder() {
var availableRestaurants = restaurants.filter(function (r) {
return !r.hasOrder;
});
var availableCustomers = customers.filter(function (c) {
return !c.waitingForOrder;
});
if (availableRestaurants.length === 0 || availableCustomers.length === 0) {
return;
}
var restaurant = availableRestaurants[Math.floor(Math.random() * availableRestaurants.length)];
var customer = availableCustomers[Math.floor(Math.random() * availableCustomers.length)];
var order = worldContainer.addChild(new Order());
order.x = restaurant.x;
order.y = restaurant.y - 60;
order.restaurant = restaurant;
order.customer = customer;
order.claimed = false; // For AI to claim
// Set random order color and match customer color
var orderColor = orderColors[Math.floor(Math.random() * orderColors.length)];
order.setColor(orderColor);
customer.setOrderColor(orderColor);
restaurant.hasOrder = true;
restaurant.orderReady = true;
customer.waitingForOrder = true;
customer.orderTime = 0;
// Don't show AI order customers - only player's customer should be visible
customer.visible = false;
// Add "I'm here" text to the building that has the customer
var building = null;
// Find the building this customer belongs to (customers are positioned relative to buildings)
for (var b = 0; b < buildings.length; b++) {
var buildingCenterX = buildings[b].x;
var buildingCenterY = buildings[b].y;
var expectedCustomerX = buildingCenterX + buildings[b].width / 2 * 2.5 + 15;
var expectedCustomerY = buildingCenterY + buildings[b].height / 2 * 2.5 + 15;
if (Math.abs(customer.x - expectedCustomerX) < 5 && Math.abs(customer.y - expectedCustomerY) < 5) {
building = buildings[b];
break;
}
}
if (building && !building.customerText) {
var customerText = new Text2("I'm here", {
size: 10,
fill: 0xffffff,
stroke: 0x000000,
strokeThickness: 3
});
customerText.anchor.set(0.5, 0.5);
customerText.x = 0;
customerText.y = -30;
building.addChild(customerText);
building.customerText = customerText;
}
customer.deliveryTarget = null;
aiOrders.push(order);
}
function spawnPowerUp() {
if (powerUps.length >= 3) {
return;
}
var type = Math.random() < 0.6 ? 'health' : 'time';
var powerUp = worldContainer.addChild(new PowerUp(type));
// Spawn power-ups on road positions between buildings
var roadPositions = [{
x: 200,
y: 220
}, {
x: 335,
y: 220
}, {
x: 470,
y: 220
}, {
x: 605,
y: 220
}, {
x: 740,
y: 220
}, {
x: 200,
y: 355
}, {
x: 335,
y: 355
}, {
x: 470,
y: 355
}, {
x: 605,
y: 355
}, {
x: 740,
y: 355
}, {
x: 200,
y: 490
}, {
x: 335,
y: 490
}, {
x: 470,
y: 490
}, {
x: 605,
y: 490
}, {
x: 740,
y: 490
}, {
x: 200,
y: 625
}, {
x: 335,
y: 625
}, {
x: 470,
y: 625
}, {
x: 605,
y: 625
}, {
x: 740,
y: 625
}];
var roadPos = roadPositions[Math.floor(Math.random() * roadPositions.length)];
powerUp.x = roadPos.x;
powerUp.y = roadPos.y;
powerUps.push(powerUp);
}
function updateUI() {
if (playerRole === 'courier') {
healthBar.setText('Health: ' + Math.floor(health));
moneyText.setText('$' + money);
if (hasInsurance) {
var timeLeft = Math.max(0, (insuranceEndTime - Date.now()) / 1000);
insuranceStatus.setText('Insurance: ' + timeLeft.toFixed(0) + 's');
insuranceStatus.visible = true;
} else {
insuranceStatus.visible = false;
}
if (currentOrder && gameState === 'delivery') {
var timeLeft = Math.max(0, currentOrder.timeRemaining / 1000);
orderTimer.setText('Deliver in: ' + timeLeft.toFixed(1) + 's');
} else {
orderTimer.setText('');
}
} else if (playerRole === 'police') {
healthBar.setText('Police Money: $' + courier.money);
moneyText.setText('Player: $' + money);
orderTimer.setText('');
insuranceStatus.visible = false;
} else if (playerRole === 'vandal') {
healthBar.setText('Vandal Money: $' + courier.money);
moneyText.setText('Player: $' + money);
orderTimer.setText('');
insuranceStatus.visible = false;
}
}
function checkCollisions() {
// Check courier-restaurant collision for pickup
if (gameState === 'pickup' && currentOrder) {
if (courier.intersects(currentOrder.restaurant)) {
// Pick up order
LK.getSound('pickup').play();
courier.hasOrder = true;
gameState = 'delivery';
currentOrder.claimed = true; // Mark as claimed by player
currentOrder.x = courier.x;
currentOrder.y = courier.y - 40;
// Start delivery timer now that order is picked up
currentOrder.startDeliveryTimer(courier.speed);
// Remove pickup arrow and create delivery arrow
if (pickupArrow) {
pickupArrow.destroy();
pickupArrow = null;
}
if (deliveryArrow) {
deliveryArrow.destroy();
}
// Create delivery arrow pointing to customer location
deliveryArrow = game.addChild(new ArrowIndicator(currentOrder.customer.x, currentOrder.customer.y, 0xff0000, 'delivery'));
}
}
// Check courier-customer collision for delivery
if (gameState === 'delivery' && currentOrder && courier.hasOrder) {
// Check if courier intersects directly with the customer
if (courier.intersects(currentOrder.customer)) {
// Deliver order
LK.getSound('delivery').play();
completeDelivery();
}
}
// Check courier-powerup collisions
for (var i = powerUps.length - 1; i >= 0; i--) {
var powerUp = powerUps[i];
if (courier.intersects(powerUp)) {
LK.getSound('powerup').play();
if (powerUp.type === 'health') {
health = Math.min(100, health + 25);
} else if (powerUp.type === 'time') {
timeFreezeActive = true;
timeFreezeRemaining = 3000; // 3 seconds
}
powerUp.destroy();
powerUps.splice(i, 1);
}
}
}
function completeDelivery() {
if (!currentOrder) {
return;
}
var deliveryTime = currentOrder.timeLimit - currentOrder.timeRemaining;
var bonus = 0;
if (currentOrder.timeRemaining > 0) {
// Successful delivery - calculate price based on distance
var dx = currentOrder.restaurant.x - currentOrder.customer.x;
var dy = currentOrder.restaurant.y - currentOrder.customer.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var basePrice = Math.max(2, Math.min(10, Math.floor(distance / 100) + 2));
var timeBonus = Math.floor(currentOrder.timeRemaining / 1000);
bonus = basePrice + Math.min(timeBonus, 3); // Cap time bonus at $3
money += bonus;
// Flash green for success
LK.effects.flashObject(courier, 0x00ff00, 500);
} else {
// Late delivery - health penalty
health -= 20;
LK.effects.flashObject(courier, 0xff0000, 1000);
if (health <= 0) {
LK.showGameOver();
return;
}
}
// Clean up order
currentOrder.restaurant.hasOrder = false;
currentOrder.customer.waitingForOrder = false;
// Reset customer color to default
currentOrder.customer.setOrderColor(0x2196f3);
// Hide customer after delivery
currentOrder.customer.visible = false;
// Remove "I'm here" text from building
var customer = currentOrder.customer;
var building = null;
// Find the building this customer belongs to using the same logic as creation
for (var b = 0; b < buildings.length; b++) {
var buildingCenterX = buildings[b].x;
var buildingCenterY = buildings[b].y;
var expectedCustomerX = buildingCenterX + buildings[b].width / 2 * 2.5 + 15;
var expectedCustomerY = buildingCenterY + buildings[b].height / 2 * 2.5 + 15;
if (Math.abs(customer.x - expectedCustomerX) < 5 && Math.abs(customer.y - expectedCustomerY) < 5) {
building = buildings[b];
break;
}
}
if (building && building.customerText) {
building.customerText.destroy();
building.customerText = null;
}
currentOrder.destroy();
for (var i = orders.length - 1; i >= 0; i--) {
if (orders[i] === currentOrder) {
orders.splice(i, 1);
break;
}
}
currentOrder = null;
courier.hasOrder = false;
gameState = 'pickup';
// Clean up arrow indicators
if (deliveryArrow) {
deliveryArrow.destroy();
deliveryArrow = null;
}
// Create new order after short delay
LK.setTimeout(function () {
createNewOrder();
}, 1000);
}
// Create world container for scrolling
worldContainer = game.addChild(new Container());
// Scale up the world to zoom in more - this makes roads wider and couriers appear smaller
worldContainer.scaleX = 3;
worldContainer.scaleY = 3;
// Initialize game elements
createNeighborhood();
// Courier will be created after role selection
// Role selection handlers
courierButtonBg.down = function () {
selectRole('courier');
};
courierButton.down = function () {
selectRole('courier');
};
policeButtonBg.down = function () {
selectRole('police');
};
policeButton.down = function () {
selectRole('police');
};
vandalButtonBg.down = function () {
selectRole('vandal');
};
vandalButton.down = function () {
selectRole('vandal');
};
insuranceButton.down = function () {
buyInsurance();
};
godModeButton.down = function () {
toggleGodMode();
};
function selectRole(role) {
playerRole = role;
storage.playerRole = role;
roleSelectionContainer.visible = false;
if (role === 'courier') {
insuranceButton.visible = true;
courier = worldContainer.addChild(new Courier());
courier.x = 1000;
courier.y = 1400;
} else if (role === 'police') {
courier = worldContainer.addChild(new Police());
courier.x = 1000;
courier.y = 1400;
} else if (role === 'vandal') {
courier = worldContainer.addChild(new Vandal());
courier.x = 1000;
courier.y = 1400;
}
gameStarted = true;
if (role === 'courier') {
createNewOrder();
}
}
function buyInsurance() {
if (money >= 5 && !hasInsurance) {
money -= 5;
hasInsurance = true;
insuranceEndTime = Date.now() + 300000; // 5 minutes
insuranceButton.visible = false;
}
}
function toggleGodMode() {
godMode = !godMode;
godModeButton.setText('God Mode: ' + (godMode ? 'ON' : 'OFF'));
if (godMode) {
// Switch to god mode - zoom out to see full map
worldContainer.scaleX = godModeScale;
worldContainer.scaleY = godModeScale;
// Center camera on map center
cameraX = 1024 - 1024 / godModeScale;
cameraY = 1366 - 1366 / godModeScale;
} else {
// Switch back to normal mode
worldContainer.scaleX = normalScale;
worldContainer.scaleY = normalScale;
// Center camera back on courier if available
if (courier) {
cameraX = courier.x - 1024 / worldContainer.scaleX;
cameraY = courier.y - 1366 / worldContainer.scaleY;
}
}
}
// Touch controls
var targetX = 1000;
var targetY = 1400;
game.down = function (x, y, obj) {
if (godMode) {
// In god mode, don't set movement target, just allow camera control
return;
}
// Convert screen coordinates to world coordinates accounting for scale
targetX = x / worldContainer.scaleX + cameraX;
targetY = y / worldContainer.scaleY + cameraY;
};
game.move = function (x, y, obj) {
if (godMode) {
// In god mode, don't set movement target, just allow camera control
return;
}
// Convert screen coordinates to world coordinates accounting for scale
targetX = x / worldContainer.scaleX + cameraX;
targetY = y / worldContainer.scaleY + cameraY;
};
// Create NPCs with unified system
// Create 8 police - spawn from center of map
for (var i = 0; i < 8; i++) {
var policeNPC = worldContainer.addChild(new NPCPolice());
// Spawn police from center area of the map
var centerX = 1024;
var centerY = 1366;
var radius = 150;
var angle = i / 8 * Math.PI * 2;
policeNPC.x = centerX + Math.cos(angle) * radius;
policeNPC.y = centerY + Math.sin(angle) * radius;
policeNPC.npcType = 'police';
policeNPC.patrol.x = 200 + Math.random() * 1600;
policeNPC.patrol.y = 200 + Math.random() * 2300;
npcs.push(policeNPC);
}
// Create 6 vandals - spawn from 4 corners of map
var cornerPositions = [{
x: 100,
y: 200
},
// Top-left
{
x: 1900,
y: 200
},
// Top-right
{
x: 100,
y: 2500
},
// Bottom-left
{
x: 1900,
y: 2500
},
// Bottom-right
{
x: 100,
y: 1350
},
// Left middle
{
x: 1900,
y: 1350
} // Right middle
];
for (var i = 0; i < 6; i++) {
var vandalNPC = worldContainer.addChild(new NPCVandal());
var pos = cornerPositions[i];
vandalNPC.x = pos.x;
vandalNPC.y = pos.y;
vandalNPC.npcType = 'vandal';
vandalNPC.patrol.x = 200 + Math.random() * 1600;
vandalNPC.patrol.y = 200 + Math.random() * 2300;
npcs.push(vandalNPC);
}
// Create NPC couriers - spawn around buildings
for (var i = 0; i < 10; i++) {
var courierNPC = worldContainer.addChild(new NPCCourier());
// Spawn around buildings randomly
if (buildings.length > 0) {
var building = buildings[Math.floor(Math.random() * buildings.length)];
courierNPC.x = building.x + (Math.random() - 0.5) * 100;
courierNPC.y = building.y + (Math.random() - 0.5) * 100;
} else {
courierNPC.x = 200 + Math.random() * 1600;
courierNPC.y = 200 + Math.random() * 2300;
}
courierNPC.npcType = 'courier';
courierNPC.patrol.x = 200 + Math.random() * 1600;
courierNPC.patrol.y = 200 + Math.random() * 2300;
npcs.push(courierNPC);
}
// Create initial AI orders immediately and more frequently
LK.setTimeout(function () {
createAIOrder();
}, 100);
LK.setTimeout(function () {
createAIOrder();
}, 300);
LK.setTimeout(function () {
createAIOrder();
}, 600);
LK.setTimeout(function () {
createAIOrder();
}, 900);
// Spawn initial power-up
LK.setTimeout(function () {
spawnPowerUp();
}, 3000);
game.update = function () {
// Only run game logic if role is selected
if (!gameStarted || !courier) {
return;
}
// Update insurance status
if (hasInsurance && Date.now() > insuranceEndTime) {
hasInsurance = false;
if (playerRole === 'courier') {
insuranceButton.visible = true;
}
}
// Move courier toward target
courier.moveToward(targetX, targetY);
// Update camera system
if (godMode) {
// In god mode, allow free camera movement or keep centered on map
// Keep camera centered on the map center for full view
cameraX = 1024 - 1024 / worldContainer.scaleX;
cameraY = 1366 - 1366 / worldContainer.scaleY;
} else {
// Normal mode - follow courier
cameraX = courier.x - 1024 / worldContainer.scaleX; // Center courier horizontally accounting for scale
cameraY = courier.y - 1366 / worldContainer.scaleY; // Center courier vertically accounting for scale
}
// Apply camera offset to world container
worldContainer.x = -cameraX * worldContainer.scaleX;
worldContainer.y = -cameraY * worldContainer.scaleY;
// Update time freeze
if (timeFreezeActive) {
timeFreezeRemaining -= 16.67;
if (timeFreezeRemaining <= 0) {
timeFreezeActive = false;
}
}
// Update current order timer (if not frozen)
if (currentOrder && !timeFreezeActive) {
// Update time limit based on courier distance if picked up
if (courier.hasOrder && gameState === 'delivery') {
currentOrder.updateTimeLimit(courier.x, courier.y);
}
currentOrder.update();
// Move order with courier if picked up
if (courier.hasOrder && gameState === 'delivery') {
currentOrder.x = courier.x;
currentOrder.y = courier.y - 40;
}
// No delivery target flashing needed anymore - buildings show "I'm here" text instead
// Check for failed delivery
if (currentOrder.timeRemaining <= 0 && gameState === 'delivery') {
completeDelivery();
}
}
// Update customers
for (var i = 0; i < customers.length; i++) {
customers[i].update();
}
// Update all NPCs
for (var i = 0; i < npcs.length; i++) {
npcs[i].update();
}
// Create new AI orders periodically - more frequently and higher limit
if (LK.ticks % 90 === 0 && aiOrders.length < 12) {
// Every 1.5 seconds, maintain up to 12 AI orders
createAIOrder();
}
// Create additional AI orders periodically
if (LK.ticks % 90 === 0 && aiOrders.length < 8) {
createAIOrder();
}
// Update power-ups
for (var i = powerUps.length - 1; i >= 0; i--) {
var powerUp = powerUps[i];
powerUp.update();
if (powerUp.lifeTime >= powerUp.maxLifeTime) {
powerUp.destroy();
powerUps.splice(i, 1);
}
}
// Spawn new power-ups occasionally
if (LK.ticks % 600 === 0) {
// Every 10 seconds
spawnPowerUp();
}
// Check collisions
checkCollisions();
// Update arrow indicators
if (pickupArrow) {
pickupArrow.update();
}
if (deliveryArrow) {
deliveryArrow.update();
}
// Update UI
updateUI();
// Game is now endless - no win condition
}; ===================================================================
--- original.js
+++ change.js
@@ -6,211 +6,8 @@
/****
* Classes
****/
-var AICourier = Container.expand(function () {
- var self = Container.call(this);
- var courierGraphics = self.attachAsset('courierAsset', {
- anchorX: 0.5,
- anchorY: 0.5
- });
- courierGraphics.tint = 0xff5722; // Orange tint for AI courier
- self.speed = 5; // Faster for more efficient deliveries
- self.hasOrder = false;
- self.targetRestaurant = null;
- self.targetCustomer = null;
- self.currentOrder = null;
- self.state = 'searching'; // 'searching', 'pickup', 'delivery'
- self.thinkTimer = 0;
- self.moveToward = function (targetX, targetY) {
- var dx = targetX - self.x;
- var dy = targetY - self.y;
- var distance = Math.sqrt(dx * dx + dy * dy);
- if (distance > 10) {
- var newX = self.x + dx / distance * self.speed;
- var newY = self.y + dy / distance * self.speed;
- // Check collision with nearby buildings only (within 200 pixels)
- var canMove = true;
- for (var i = 0; i < buildings.length; i++) {
- var building = buildings[i];
- // Skip distant buildings to improve performance
- var dx = building.x - self.x;
- var dy = building.y - self.y;
- if (Math.abs(dx) > 200 || Math.abs(dy) > 200) continue;
- var tempCourier = {
- x: newX,
- y: newY,
- width: 9,
- height: 9
- };
- if (tempCourier.x < building.x + building.width / 2 && tempCourier.x + tempCourier.width / 2 > building.x - building.width / 2 && tempCourier.y < building.y + building.height / 2 && tempCourier.y + tempCourier.height / 2 > building.y - building.height / 2) {
- canMove = false;
- break;
- }
- }
- // Check collision with nearby restaurants/shops only (within 200 pixels)
- for (var i = 0; i < restaurants.length; i++) {
- var restaurant = restaurants[i];
- // Skip distant restaurants to improve performance
- var dx = restaurant.x - self.x;
- var dy = restaurant.y - self.y;
- if (Math.abs(dx) > 200 || Math.abs(dy) > 200) continue;
- var tempCourier = {
- x: newX,
- y: newY,
- width: 9,
- height: 9
- };
- if (tempCourier.x < restaurant.x + restaurant.width / 2 && tempCourier.x + tempCourier.width / 2 > restaurant.x - restaurant.width / 2 && tempCourier.y < restaurant.y + restaurant.height / 2 && tempCourier.y + tempCourier.height / 2 > restaurant.y - restaurant.height / 2) {
- canMove = false;
- break;
- }
- }
- // Check collision with nearby customers only (within 200 pixels)
- for (var i = 0; i < customers.length; i++) {
- var customer = customers[i];
- // Skip distant customers to improve performance
- var dx = customer.x - self.x;
- var dy = customer.y - self.y;
- if (Math.abs(dx) > 200 || Math.abs(dy) > 200) continue;
- var tempCourier = {
- x: newX,
- y: newY,
- width: 9,
- height: 9
- };
- if (tempCourier.x < customer.x + 1 / 2 && tempCourier.x + tempCourier.width / 2 > customer.x - 1 / 2 && tempCourier.y < customer.y + 1 / 2 && tempCourier.y + tempCourier.height / 2 > customer.y - 1 / 2) {
- canMove = false;
- break;
- }
- }
- if (canMove) {
- self.x = newX;
- self.y = newY;
- }
- }
- };
- self.update = function () {
- self.thinkTimer += 16.67;
- // Always move around when searching to find more orders
- if (self.state === 'searching' && !self.hasOrder) {
- // Update search target more frequently for better coverage
- if (!self.searchTarget || Math.abs(self.x - self.searchTarget.x) < 50 && Math.abs(self.y - self.searchTarget.y) < 50) {
- self.searchTarget = {
- x: 200 + Math.random() * 1600,
- y: 200 + Math.random() * 2300
- };
- }
- self.moveToward(self.searchTarget.x, self.searchTarget.y);
- }
- // AI decision making every 100ms - much more frequent thinking for faster order claiming
- if (self.thinkTimer >= 100) {
- self.thinkTimer = 0;
- if (self.state === 'searching' && !self.hasOrder) {
- // Always look for available orders - no distance restriction for AI orders
- var availableOrders = aiOrders.filter(function (order) {
- return !order.claimed;
- });
- // Also check ALL regular orders that aren't the current player order
- var availableRegularOrders = orders.filter(function (order) {
- return order !== currentOrder && !order.claimed;
- });
- // Combine both types of orders
- var allAvailableOrders = availableOrders.concat(availableRegularOrders);
- if (allAvailableOrders.length > 0) {
- // Find the closest order
- var closestOrder = allAvailableOrders[0];
- var closestDistance = Math.sqrt(Math.pow(closestOrder.restaurant.x - self.x, 2) + Math.pow(closestOrder.restaurant.y - self.y, 2));
- for (var j = 1; j < allAvailableOrders.length; j++) {
- var order = allAvailableOrders[j];
- var distance = Math.sqrt(Math.pow(order.restaurant.x - self.x, 2) + Math.pow(order.restaurant.y - self.y, 2));
- if (distance < closestDistance) {
- closestOrder = order;
- closestDistance = distance;
- }
- }
- self.currentOrder = closestOrder;
- self.currentOrder.claimed = true;
- self.state = 'pickup';
- }
- }
- }
- // Execute current behavior
- if (self.state === 'pickup' && self.currentOrder) {
- self.moveToward(self.currentOrder.restaurant.x, self.currentOrder.restaurant.y);
- // Check if reached restaurant - more generous pickup radius
- var dx = self.currentOrder.restaurant.x - self.x;
- var dy = self.currentOrder.restaurant.y - self.y;
- if (Math.sqrt(dx * dx + dy * dy) < 100) {
- self.hasOrder = true;
- self.state = 'delivery';
- // Start delivery timer for AI courier
- self.currentOrder.startDeliveryTimer(self.speed);
- // Move order visual to AI courier
- self.currentOrder.x = self.x;
- self.currentOrder.y = self.y - 40;
- }
- } else if (self.state === 'delivery' && self.currentOrder && self.hasOrder) {
- self.moveToward(self.currentOrder.customer.x, self.currentOrder.customer.y);
- // Update time limit based on AI courier distance
- self.currentOrder.updateTimeLimit(self.x, self.y);
- // Move order with AI courier
- self.currentOrder.x = self.x;
- self.currentOrder.y = self.y - 40;
- // Check if reached customer - more generous delivery radius
- var dx = self.currentOrder.customer.x - self.x;
- var dy = self.currentOrder.customer.y - self.y;
- if (Math.sqrt(dx * dx + dy * dy) < 100) {
- // Complete delivery
- self.completeDelivery();
- }
- }
- };
- self.completeDelivery = function () {
- if (!self.currentOrder) {
- return;
- }
- // Clean up order
- self.currentOrder.restaurant.hasOrder = false;
- self.currentOrder.customer.waitingForOrder = false;
- // Reset customer color to default
- self.currentOrder.customer.setOrderColor(0x2196f3);
- // Hide customer after delivery
- self.currentOrder.customer.visible = false;
- // Remove "I'm here" text from building
- var customer = self.currentOrder.customer;
- var building = null;
- for (var b = 0; b < buildings.length; b++) {
- if (buildings[b].x === customer.x && buildings[b].y === customer.y) {
- building = buildings[b];
- break;
- }
- }
- if (building && building.customerText) {
- building.customerText.destroy();
- building.customerText = null;
- }
- self.currentOrder.destroy();
- // Remove from aiOrders if it exists there
- for (var i = aiOrders.length - 1; i >= 0; i--) {
- if (aiOrders[i] === self.currentOrder) {
- aiOrders.splice(i, 1);
- break;
- }
- }
- // Remove from regular orders if it exists there
- for (var i = orders.length - 1; i >= 0; i--) {
- if (orders[i] === self.currentOrder) {
- orders.splice(i, 1);
- break;
- }
- }
- self.currentOrder = null;
- self.hasOrder = false;
- self.state = 'searching';
- };
- return self;
-});
var ArrowIndicator = Container.expand(function (targetX, targetY, color, arrowType) {
var self = Container.call(this);
// Create arrow shape using dedicated arrow assets
var assetName = arrowType === 'pickup' ? 'pickupArrow' : 'deliveryArrow';
@@ -334,126 +131,106 @@
}
};
return self;
});
-var Order = Container.expand(function () {
+var NPCCourier = Container.expand(function () {
var self = Container.call(this);
- var orderGraphics = self.attachAsset('order', {
+ var courierGraphics = self.attachAsset('courierAsset', {
anchorX: 0.5,
anchorY: 0.5
});
- self.restaurant = null;
- self.customer = null;
- self.baseTimeLimit = 25000; // 25 seconds minimum
- self.timeLimit = self.baseTimeLimit;
- self.timeRemaining = self.timeLimit;
- self.isPickedUp = false;
- self.courierSpeed = 8; // Default courier speed
- self.setColor = function (color) {
- orderGraphics.tint = color;
+ courierGraphics.tint = 0xff5722;
+ self.speed = 2;
+ self.detectionRadius = 20;
+ self.hasInsurance = false;
+ self.patrol = {
+ x: 1024,
+ y: 1366
};
- self.startDeliveryTimer = function (courierSpeed) {
- self.isPickedUp = true;
- self.courierSpeed = courierSpeed;
- self.timeLimit = self.baseTimeLimit;
- self.timeRemaining = self.timeLimit;
- };
- self.updateTimeLimit = function (courierX, courierY) {
- if (!self.isPickedUp || !self.customer) {
- return;
- }
- // Calculate distance to customer
- var dx = self.customer.x - courierX;
- var dy = self.customer.y - courierY;
+ self.lastX = self.x;
+ self.lastY = self.y;
+ self.lastSeenPolice = false;
+ self.lastSeenVandal = false;
+ self.thinkTimer = 0;
+ self.moveToward = function (targetX, targetY) {
+ var dx = targetX - self.x;
+ var dy = targetY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
- // Calculate time needed to reach customer at max speed + 2 seconds buffer
- var timeToReach = distance / self.courierSpeed * 16.67 + 2000; // Convert to ms and add 2s buffer
- // Use the greater of base time limit or calculated time
- var newTimeLimit = Math.max(self.baseTimeLimit, timeToReach);
- // Only extend time, never reduce it
- if (newTimeLimit > self.timeLimit) {
- var extension = newTimeLimit - self.timeLimit;
- self.timeLimit = newTimeLimit;
- self.timeRemaining += extension;
+ if (distance > 5) {
+ var newX = self.x + dx / distance * self.speed;
+ var newY = self.y + dy / distance * self.speed;
+ var canMove = true;
+ // Check collision with buildings
+ for (var i = 0; i < buildings.length; i++) {
+ var building = buildings[i];
+ var tempCourier = {
+ x: newX,
+ y: newY,
+ width: 15,
+ height: 15
+ };
+ if (tempCourier.x - tempCourier.width / 2 < building.x + building.width / 2 && tempCourier.x + tempCourier.width / 2 > building.x - building.width / 2 && tempCourier.y - tempCourier.height / 2 < building.y + building.height / 2 && tempCourier.y + tempCourier.height / 2 > building.y - building.height / 2) {
+ canMove = false;
+ break;
+ }
+ }
+ if (canMove) {
+ self.x = newX;
+ self.y = newY;
+ }
}
};
self.update = function () {
- if (self.isPickedUp && self.timeRemaining > 0) {
- self.timeRemaining -= 16.67;
+ self.thinkTimer += 16.67;
+ if (self.thinkTimer >= 100) {
+ self.thinkTimer = 0;
+ // Get new patrol point if reached current one
+ if (Math.abs(self.x - self.patrol.x) < 30 && Math.abs(self.y - self.patrol.y) < 30) {
+ self.patrol.x = 200 + Math.random() * 1600;
+ self.patrol.y = 200 + Math.random() * 2300;
+ }
+ // Move around buildings
+ self.moveToward(self.patrol.x, self.patrol.y);
}
+ self.lastX = self.x;
+ self.lastY = self.y;
};
return self;
});
-var Police = Container.expand(function () {
+var NPCPolice = Container.expand(function () {
var self = Container.call(this);
var policeGraphics = self.attachAsset('policeAsset', {
anchorX: 0.5,
anchorY: 0.5
});
- self.speed = 4;
- self.money = 0;
- self.targetVandal = null;
- self.targetCourier = null;
- self.state = 'patrolling'; // 'patrolling', 'chasing', 'checking'
- self.thinkTimer = 0;
- self.flashTimer = 0;
- self.isFlashing = false;
- // Start flashing animation with bright colors
- tween(policeGraphics, {
- tint: 0xff0000
- }, {
- duration: 300,
- onFinish: function onFinish() {
- tween(policeGraphics, {
- tint: 0x0080ff
- }, {
- duration: 300,
- onFinish: function onFinish() {
- // Continue flashing cycle
- self.startFlashing();
- }
- });
- }
- });
- self.startFlashing = function () {
- tween(policeGraphics, {
- tint: 0xff0000
- }, {
- duration: 300,
- onFinish: function onFinish() {
- tween(policeGraphics, {
- tint: 0x0080ff
- }, {
- duration: 300,
- onFinish: function onFinish() {
- self.startFlashing();
- }
- });
- }
- });
+ self.speed = 3;
+ self.detectionRadius = 20;
+ self.patrol = {
+ x: 1024,
+ y: 1366
};
+ self.target = null;
+ self.lastX = self.x;
+ self.lastY = self.y;
+ self.thinkTimer = 0;
self.moveToward = function (targetX, targetY) {
var dx = targetX - self.x;
var dy = targetY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
- if (distance > 10) {
+ if (distance > 5) {
var newX = self.x + dx / distance * self.speed;
var newY = self.y + dy / distance * self.speed;
- // Check collision with nearby buildings only (within 200 pixels)
var canMove = true;
+ // Check collision with buildings
for (var i = 0; i < buildings.length; i++) {
var building = buildings[i];
- // Skip distant buildings to improve performance
- var dx = building.x - self.x;
- var dy = building.y - self.y;
- if (Math.abs(dx) > 200 || Math.abs(dy) > 200) continue;
var tempPolice = {
x: newX,
y: newY,
- width: 9,
- height: 9
+ width: 15,
+ height: 15
};
- if (tempPolice.x < building.x + building.width / 2 && tempPolice.x + tempPolice.width / 2 > building.x - building.width / 2 && tempPolice.y < building.y + building.height / 2 && tempPolice.y + tempPolice.height / 2 > building.y - building.height / 2) {
+ if (tempPolice.x - tempPolice.width / 2 < building.x + building.width / 2 && tempPolice.x + tempPolice.width / 2 > building.x - building.width / 2 && tempPolice.y - tempPolice.height / 2 < building.y + building.height / 2 && tempPolice.y + tempPolice.height / 2 > building.y - building.height / 2) {
canMove = false;
break;
}
}
@@ -464,262 +241,99 @@
}
};
self.update = function () {
self.thinkTimer += 16.67;
- if (self.thinkTimer >= 150) {
+ if (self.thinkTimer >= 100) {
self.thinkTimer = 0;
- if (self.state === 'patrolling') {
- // Always move to random patrol points when not chasing
- if (!self.patrolTarget || Math.abs(self.x - self.patrolTarget.x) < 20 && Math.abs(self.y - self.patrolTarget.y) < 20) {
- self.patrolTarget = {
- x: 200 + Math.random() * 1600,
- y: 200 + Math.random() * 2300
- };
- }
- self.moveToward(self.patrolTarget.x, self.patrolTarget.y);
- // Look for nearby vandals only (within 300 pixels)
- var closestVandal = null;
- var closestVandalDistance = Infinity;
- for (var i = 0; i < vandals.length; i++) {
- var vandal = vandals[i];
- var dx = vandal.x - self.x;
- var dy = vandal.y - self.y;
- // Skip distant vandals to improve performance
- if (Math.abs(dx) > 300 || Math.abs(dy) > 300) continue;
+ self.target = null;
+ // Look for vandals
+ for (var i = 0; i < npcs.length; i++) {
+ var npc = npcs[i];
+ if (npc.npcType === 'vandal') {
+ var dx = npc.x - self.x;
+ var dy = npc.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
- if (distance < 300 && distance < closestVandalDistance) {
- closestVandal = vandal;
- closestVandalDistance = distance;
+ if (distance <= self.detectionRadius) {
+ self.target = npc;
+ break;
}
}
- if (closestVandal) {
- self.targetVandal = closestVandal;
- self.state = 'chasing';
- } else {
- // Check for couriers without insurance
- var targets = [];
- // Check player courier
- if (playerRole === 'courier' && courier && courier.hasOrder && !hasInsurance) {
- var dx = courier.x - self.x;
- var dy = courier.y - self.y;
+ }
+ // Look for uninsured couriers
+ if (!self.target) {
+ for (var i = 0; i < npcs.length; i++) {
+ var npc = npcs[i];
+ if (npc.npcType === 'courier' && !npc.hasInsurance) {
+ var dx = npc.x - self.x;
+ var dy = npc.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
- if (distance < 200) {
- targets.push({
- courier: courier,
- distance: distance
- });
+ if (distance <= self.detectionRadius) {
+ self.target = npc;
+ break;
}
}
- // Check nearby AI couriers only (they never have insurance)
- for (var i = 0; i < aiCouriers.length; i++) {
- var aiCourier = aiCouriers[i];
- if (aiCourier.hasOrder) {
- var dx = aiCourier.x - self.x;
- var dy = aiCourier.y - self.y;
- // Skip distant AI couriers to improve performance
- if (Math.abs(dx) > 200 || Math.abs(dy) > 200) continue;
- var distance = Math.sqrt(dx * dx + dy * dy);
- if (distance < 200) {
- targets.push({
- courier: aiCourier,
- distance: distance
- });
- }
- }
- }
- if (targets.length > 0) {
- targets.sort(function (a, b) {
- return a.distance - b.distance;
- });
- self.targetCourier = targets[0].courier;
- self.state = 'checking';
- }
}
}
+ // Check player courier if no insurance
+ if (!self.target && playerRole === 'courier' && courier && !hasInsurance) {
+ var dx = courier.x - self.x;
+ var dy = courier.y - self.y;
+ var distance = Math.sqrt(dx * dx + dy * dy);
+ if (distance <= self.detectionRadius) {
+ self.target = courier;
+ }
+ }
}
- if (self.state === 'chasing' && self.targetVandal) {
- self.moveToward(self.targetVandal.x, self.targetVandal.y);
- // Check if close enough to catch
- var dx = self.targetVandal.x - self.x;
- var dy = self.targetVandal.y - self.y;
- var distance = Math.sqrt(dx * dx + dy * dy);
- if (distance < 25) {
- self.catchVandal();
+ // Move behavior
+ if (self.target) {
+ self.moveToward(self.target.x, self.target.y);
+ } else {
+ // Get new patrol point if reached current one
+ if (Math.abs(self.x - self.patrol.x) < 30 && Math.abs(self.y - self.patrol.y) < 30) {
+ self.patrol.x = 200 + Math.random() * 1600;
+ self.patrol.y = 200 + Math.random() * 2300;
}
- } else if (self.state === 'checking' && self.targetCourier) {
- self.moveToward(self.targetCourier.x, self.targetCourier.y);
- // Check if close enough to fine
- var dx = self.targetCourier.x - self.x;
- var dy = self.targetCourier.y - self.y;
- var distance = Math.sqrt(dx * dx + dy * dy);
- if (distance < 30) {
- self.fineUninsuredCourier();
- }
+ self.moveToward(self.patrol.x, self.patrol.y);
}
+ self.lastX = self.x;
+ self.lastY = self.y;
};
- self.catchVandal = function () {
- if (!self.targetVandal) {
- return;
- }
- // Take 50% of vandal's money
- var stolenMoney = Math.floor(self.targetVandal.money * 0.5);
- self.money += stolenMoney;
- self.targetVandal.money -= stolenMoney;
- // Reset vandal to safe house
- self.targetVandal.x = self.targetVandal.safeHouseX;
- self.targetVandal.y = self.targetVandal.safeHouseY;
- self.targetVandal.state = 'searching';
- self.targetVandal.hasOrder = false;
- if (self.targetVandal.currentOrder) {
- self.targetVandal.currentOrder.destroy();
- self.targetVandal.currentOrder = null;
- }
- self.targetVandal = null;
- self.state = 'patrolling';
- };
- self.fineUninsuredCourier = function () {
- if (!self.targetCourier) {
- return;
- }
- var fineAmount = 0;
- if (self.targetCourier === courier && currentOrder) {
- // Fine player courier
- var dx = currentOrder.restaurant.x - currentOrder.customer.x;
- var dy = currentOrder.restaurant.y - currentOrder.customer.y;
- var distance = Math.sqrt(dx * dx + dy * dy);
- var orderValue = Math.max(2, Math.min(10, Math.floor(distance / 100) + 2));
- fineAmount = Math.floor(orderValue * 0.5);
- money = Math.max(0, money - fineAmount);
- self.money += fineAmount;
- } else if (self.targetCourier.currentOrder) {
- // Fine AI courier
- var dx = self.targetCourier.currentOrder.restaurant.x - self.targetCourier.currentOrder.customer.x;
- var dy = self.targetCourier.currentOrder.restaurant.y - self.targetCourier.currentOrder.customer.y;
- var distance = Math.sqrt(dx * dx + dy * dy);
- var orderValue = Math.max(2, Math.min(10, Math.floor(distance / 100) + 2));
- fineAmount = Math.floor(orderValue * 0.5);
- self.money += fineAmount;
- }
- self.targetCourier = null;
- self.state = 'patrolling';
- };
return self;
});
-var PowerUp = Container.expand(function (type) {
+var NPCVandal = Container.expand(function () {
var self = Container.call(this);
- var powerupGraphics = self.attachAsset(type === 'health' ? 'healthPowerup' : 'timePowerup', {
- anchorX: 0.5,
- anchorY: 0.5
- });
- self.type = type;
- self.lifeTime = 0;
- self.maxLifeTime = 8000; // 8 seconds
- self.update = function () {
- self.lifeTime += 16.67;
- // Pulse animation
- var pulse = 1 + 0.2 * Math.sin(self.lifeTime * 0.01);
- powerupGraphics.scaleX = pulse;
- powerupGraphics.scaleY = pulse;
- };
- return self;
-});
-var Restaurant = Container.expand(function () {
- var self = Container.call(this);
- var restaurantGraphics = self.attachAsset('restaurant', {
- anchorX: 0.5,
- anchorY: 0.5
- });
- self.hasOrder = false;
- self.orderReady = false;
- return self;
-});
-var Vandal = Container.expand(function () {
- var self = Container.call(this);
var vandalGraphics = self.attachAsset('vandalAsset', {
anchorX: 0.5,
anchorY: 0.5
});
- self.speed = Math.min(4.4, 1.5 + Math.random() * 1); // Limited to 0.55x courier speed max - 2x slower
- self.hasOrder = false;
- self.targetOrder = null;
- self.money = 0;
- self.state = 'searching'; // 'searching', 'stealing', 'fleeing'
- self.thinkTimer = 0;
- self.safeHouseX = 100;
- self.safeHouseY = 100;
- self.flashTimer = 0;
- self.isFlashing = false;
- // Start flashing animation
- tween(vandalGraphics, {
- tint: 0xffffff
- }, {
- duration: 500,
- onFinish: function onFinish() {
- tween(vandalGraphics, {
- tint: 0x000000
- }, {
- duration: 500,
- onFinish: function onFinish() {
- // Recursive flashing
- tween(vandalGraphics, {
- tint: 0xffffff
- }, {
- duration: 500,
- onFinish: function onFinish() {
- tween(vandalGraphics, {
- tint: 0x000000
- }, {
- duration: 500,
- onFinish: function onFinish() {
- // Continue flashing cycle
- self.startFlashing();
- }
- });
- }
- });
- }
- });
- }
- });
- self.startFlashing = function () {
- tween(vandalGraphics, {
- tint: 0xffffff
- }, {
- duration: 500,
- onFinish: function onFinish() {
- tween(vandalGraphics, {
- tint: 0x000000
- }, {
- duration: 500,
- onFinish: function onFinish() {
- self.startFlashing();
- }
- });
- }
- });
+ self.speed = 2;
+ self.detectionRadius = 20;
+ self.patrol = {
+ x: 1024,
+ y: 1366
};
+ self.fleeing = false;
+ self.lastX = self.x;
+ self.lastY = self.y;
+ self.thinkTimer = 0;
self.moveToward = function (targetX, targetY) {
var dx = targetX - self.x;
var dy = targetY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
- if (distance > 10) {
+ if (distance > 5) {
var newX = self.x + dx / distance * self.speed;
var newY = self.y + dy / distance * self.speed;
- // Check collision with nearby buildings only (within 200 pixels)
var canMove = true;
+ // Check collision with buildings
for (var i = 0; i < buildings.length; i++) {
var building = buildings[i];
- // Skip distant buildings to improve performance
- var dx = building.x - self.x;
- var dy = building.y - self.y;
- if (Math.abs(dx) > 200 || Math.abs(dy) > 200) continue;
var tempVandal = {
x: newX,
y: newY,
- width: 9,
- height: 9
+ width: 15,
+ height: 15
};
- if (tempVandal.x < building.x + building.width / 2 && tempVandal.x + tempVandal.width / 2 > building.x - building.width / 2 && tempVandal.y < building.y + building.height / 2 && tempVandal.y + tempVandal.height / 2 > building.y - building.height / 2) {
+ if (tempVandal.x - tempVandal.width / 2 < building.x + building.width / 2 && tempVandal.x + tempVandal.width / 2 > building.x - building.width / 2 && tempVandal.y - tempVandal.height / 2 < building.y + building.height / 2 && tempVandal.y + tempVandal.height / 2 > building.y - building.height / 2) {
canMove = false;
break;
}
}
@@ -730,131 +344,134 @@
}
};
self.update = function () {
self.thinkTimer += 16.67;
- if (self.thinkTimer >= 200) {
+ if (self.thinkTimer >= 100) {
self.thinkTimer = 0;
- if (self.state === 'searching' && !self.hasOrder) {
- // Always move around when searching
- if (!self.searchTarget || Math.abs(self.x - self.searchTarget.x) < 30 && Math.abs(self.y - self.searchTarget.y) < 30) {
- self.searchTarget = {
- x: 200 + Math.random() * 1600,
- y: 200 + Math.random() * 2300
- };
- }
- self.moveToward(self.searchTarget.x, self.searchTarget.y);
- // Look for couriers with orders to steal
- var targets = [];
- // Check player courier
- if (courier && courier.hasOrder && currentOrder) {
- var dx = courier.x - self.x;
- var dy = courier.y - self.y;
+ self.fleeing = false;
+ // Look for police
+ for (var i = 0; i < npcs.length; i++) {
+ var npc = npcs[i];
+ if (npc.npcType === 'police') {
+ var dx = npc.x - self.x;
+ var dy = npc.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
- if (distance < 200) {
- targets.push({
- courier: courier,
- order: currentOrder,
- distance: distance
- });
+ if (distance <= self.detectionRadius) {
+ self.fleeing = true;
+ // Run in opposite direction
+ self.patrol.x = self.x - dx * 10;
+ self.patrol.y = self.y - dy * 10;
+ // Keep within bounds
+ self.patrol.x = Math.max(100, Math.min(1900, self.patrol.x));
+ self.patrol.y = Math.max(200, Math.min(2500, self.patrol.y));
+ break;
}
}
- // Check nearby AI couriers only
- for (var i = 0; i < aiCouriers.length; i++) {
- var aiCourier = aiCouriers[i];
- if (aiCourier.hasOrder && aiCourier.currentOrder) {
- var dx = aiCourier.x - self.x;
- var dy = aiCourier.y - self.y;
- // Skip distant AI couriers to improve performance
- if (Math.abs(dx) > 200 || Math.abs(dy) > 200) continue;
- var distance = Math.sqrt(dx * dx + dy * dy);
- if (distance < 200) {
- targets.push({
- courier: aiCourier,
- order: aiCourier.currentOrder,
- distance: distance
- });
- }
- }
- }
- if (targets.length > 0) {
- // Target closest courier with order
- targets.sort(function (a, b) {
- return a.distance - b.distance;
- });
- self.targetOrder = targets[0];
- self.state = 'stealing';
- }
}
- }
- if (self.state === 'stealing' && self.targetOrder) {
- self.moveToward(self.targetOrder.courier.x, self.targetOrder.courier.y);
- // Check if close enough to steal
- var dx = self.targetOrder.courier.x - self.x;
- var dy = self.targetOrder.courier.y - self.y;
- var distance = Math.sqrt(dx * dx + dy * dy);
- if (distance < 30) {
- // Steal the order
- self.stealOrder();
- }
- } else if (self.state === 'fleeing') {
- self.moveToward(self.safeHouseX, self.safeHouseY);
- // Check if reached safe house
- var dx = self.safeHouseX - self.x;
- var dy = self.safeHouseY - self.y;
- var distance = Math.sqrt(dx * dx + dy * dy);
- if (distance < 50) {
- self.state = 'searching';
- self.hasOrder = false;
- if (self.currentOrder) {
- self.currentOrder.destroy();
- self.currentOrder = null;
+ // Check player police
+ if (!self.fleeing && playerRole === 'police' && courier) {
+ var dx = courier.x - self.x;
+ var dy = courier.y - self.y;
+ var distance = Math.sqrt(dx * dx + dy * dy);
+ if (distance <= self.detectionRadius) {
+ self.fleeing = true;
+ // Run in opposite direction
+ self.patrol.x = self.x - dx * 10;
+ self.patrol.y = self.y - dy * 10;
+ // Keep within bounds
+ self.patrol.x = Math.max(100, Math.min(1900, self.patrol.x));
+ self.patrol.y = Math.max(200, Math.min(2500, self.patrol.y));
}
}
+ // Get new patrol point if not fleeing and reached current one
+ if (!self.fleeing && Math.abs(self.x - self.patrol.x) < 30 && Math.abs(self.y - self.patrol.y) < 30) {
+ self.patrol.x = 200 + Math.random() * 1600;
+ self.patrol.y = 200 + Math.random() * 2300;
+ }
}
+ // Move behavior
+ self.moveToward(self.patrol.x, self.patrol.y);
+ self.lastX = self.x;
+ self.lastY = self.y;
};
- self.stealOrder = function () {
- if (!self.targetOrder) {
+ return self;
+});
+var Order = Container.expand(function () {
+ var self = Container.call(this);
+ var orderGraphics = self.attachAsset('order', {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ self.restaurant = null;
+ self.customer = null;
+ self.baseTimeLimit = 25000; // 25 seconds minimum
+ self.timeLimit = self.baseTimeLimit;
+ self.timeRemaining = self.timeLimit;
+ self.isPickedUp = false;
+ self.courierSpeed = 8; // Default courier speed
+ self.setColor = function (color) {
+ orderGraphics.tint = color;
+ };
+ self.startDeliveryTimer = function (courierSpeed) {
+ self.isPickedUp = true;
+ self.courierSpeed = courierSpeed;
+ self.timeLimit = self.baseTimeLimit;
+ self.timeRemaining = self.timeLimit;
+ };
+ self.updateTimeLimit = function (courierX, courierY) {
+ if (!self.isPickedUp || !self.customer) {
return;
}
- var targetCourier = self.targetOrder.courier;
- var stolenOrder = self.targetOrder.order;
- // Calculate order value based on distance
- var dx = stolenOrder.restaurant.x - stolenOrder.customer.x;
- var dy = stolenOrder.restaurant.y - stolenOrder.customer.y;
+ // Calculate distance to customer
+ var dx = self.customer.x - courierX;
+ var dy = self.customer.y - courierY;
var distance = Math.sqrt(dx * dx + dy * dy);
- var orderValue = Math.max(2, Math.min(10, Math.floor(distance / 100) + 2));
- self.money += orderValue;
- self.hasOrder = true;
- self.currentOrder = stolenOrder;
- self.state = 'fleeing';
- // Remove order from target courier
- if (targetCourier === courier && courier) {
- // Stolen from player
- courier.hasOrder = false;
- currentOrder = null;
- gameState = 'pickup';
- // Clean up arrows
- if (deliveryArrow) {
- deliveryArrow.destroy();
- deliveryArrow = null;
- }
- // Create new order for player
- LK.setTimeout(function () {
- createNewOrder();
- }, 1000);
- } else {
- // Stolen from AI courier
- targetCourier.hasOrder = false;
- targetCourier.currentOrder = null;
- targetCourier.state = 'searching';
+ // Calculate time needed to reach customer at max speed + 2 seconds buffer
+ var timeToReach = distance / self.courierSpeed * 16.67 + 2000; // Convert to ms and add 2s buffer
+ // Use the greater of base time limit or calculated time
+ var newTimeLimit = Math.max(self.baseTimeLimit, timeToReach);
+ // Only extend time, never reduce it
+ if (newTimeLimit > self.timeLimit) {
+ var extension = newTimeLimit - self.timeLimit;
+ self.timeLimit = newTimeLimit;
+ self.timeRemaining += extension;
}
- // Move order to vandal
- stolenOrder.x = self.x;
- stolenOrder.y = self.y - 40;
- self.targetOrder = null;
};
+ self.update = function () {
+ if (self.isPickedUp && self.timeRemaining > 0) {
+ self.timeRemaining -= 16.67;
+ }
+ };
return self;
});
+var PowerUp = Container.expand(function (type) {
+ var self = Container.call(this);
+ var powerupGraphics = self.attachAsset(type === 'health' ? 'healthPowerup' : 'timePowerup', {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ self.type = type;
+ self.lifeTime = 0;
+ self.maxLifeTime = 8000; // 8 seconds
+ self.update = function () {
+ self.lifeTime += 16.67;
+ // Pulse animation
+ var pulse = 1 + 0.2 * Math.sin(self.lifeTime * 0.01);
+ powerupGraphics.scaleX = pulse;
+ powerupGraphics.scaleY = pulse;
+ };
+ return self;
+});
+var Restaurant = Container.expand(function () {
+ var self = Container.call(this);
+ var restaurantGraphics = self.attachAsset('restaurant', {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ self.hasOrder = false;
+ self.orderReady = false;
+ return self;
+});
/****
* Initialize Game
****/
@@ -866,11 +483,9 @@
* Game Code
****/
// Game variables
var courier;
-var aiCouriers = [];
-var vandals = [];
-var police = [];
+var npcs = [];
var restaurants = [];
var customers = [];
var orders = [];
var aiOrders = [];
@@ -1635,113 +1250,95 @@
// Convert screen coordinates to world coordinates accounting for scale
targetX = x / worldContainer.scaleX + cameraX;
targetY = y / worldContainer.scaleY + cameraY;
};
-// Create AI couriers - spawn in front of shops with predefined routes
-for (var i = 0; i < Math.min(6, restaurants.length); i++) {
- var aiCourier = worldContainer.addChild(new AICourier());
- // Place AI couriers in front of restaurants/shops
- if (i < restaurants.length) {
- aiCourier.x = restaurants[i].x + 80; // Spawn next to restaurant with more spacing
- aiCourier.y = restaurants[i].y + 50; // Offset slightly to avoid overlap
- // Assign predefined route waypoints for courier to follow
- aiCourier.routeWaypoints = [{
- x: restaurants[i].x,
- y: restaurants[i].y
- }, {
- x: restaurants[i].x + 200,
- y: restaurants[i].y
- }, {
- x: restaurants[i].x + 200,
- y: restaurants[i].y + 300
- }, {
- x: restaurants[i].x - 200,
- y: restaurants[i].y + 300
- }, {
- x: restaurants[i].x - 200,
- y: restaurants[i].y
- }];
- aiCourier.currentWaypointIndex = 0;
- } else {
- // Fallback position if more couriers than restaurants
- aiCourier.x = 300 + i * 250;
- aiCourier.y = 400;
- aiCourier.routeWaypoints = [{
- x: aiCourier.x,
- y: aiCourier.y
- }, {
- x: aiCourier.x + 300,
- y: aiCourier.y
- }, {
- x: aiCourier.x + 300,
- y: aiCourier.y + 400
- }, {
- x: aiCourier.x,
- y: aiCourier.y + 400
- }];
- aiCourier.currentWaypointIndex = 0;
- }
- // Make sure AI courier is visible
- aiCourier.visible = true;
- aiCouriers.push(aiCourier);
+// Create NPCs with unified system
+// Create 8 police - spawn from center of map
+for (var i = 0; i < 8; i++) {
+ var policeNPC = worldContainer.addChild(new NPCPolice());
+ // Spawn police from center area of the map
+ var centerX = 1024;
+ var centerY = 1366;
+ var radius = 150;
+ var angle = i / 8 * Math.PI * 2;
+ policeNPC.x = centerX + Math.cos(angle) * radius;
+ policeNPC.y = centerY + Math.sin(angle) * radius;
+ policeNPC.npcType = 'police';
+ policeNPC.patrol.x = 200 + Math.random() * 1600;
+ policeNPC.patrol.y = 200 + Math.random() * 2300;
+ npcs.push(policeNPC);
}
// Create 6 vandals - spawn from 4 corners of map
+var cornerPositions = [{
+ x: 100,
+ y: 200
+},
+// Top-left
+{
+ x: 1900,
+ y: 200
+},
+// Top-right
+{
+ x: 100,
+ y: 2500
+},
+// Bottom-left
+{
+ x: 1900,
+ y: 2500
+},
+// Bottom-right
+{
+ x: 100,
+ y: 1350
+},
+// Left middle
+{
+ x: 1900,
+ y: 1350
+} // Right middle
+];
for (var i = 0; i < 6; i++) {
- var vandal = worldContainer.addChild(new Vandal());
- // Define 4 corner positions with some variation
- var cornerPositions = [{
- x: 100,
- y: 200
- },
- // Top-left corner
- {
- x: 1900,
- y: 200
- },
- // Top-right corner
- {
- x: 100,
- y: 2500
- },
- // Bottom-left corner
- {
- x: 1900,
- y: 2500
- },
- // Bottom-right corner
- {
- x: 100,
- y: 1350
- },
- // Left middle
- {
- x: 1900,
- y: 1350
- } // Right middle
- ];
+ var vandalNPC = worldContainer.addChild(new NPCVandal());
var pos = cornerPositions[i];
- vandal.x = pos.x;
- vandal.y = pos.y;
- vandals.push(vandal);
+ vandalNPC.x = pos.x;
+ vandalNPC.y = pos.y;
+ vandalNPC.npcType = 'vandal';
+ vandalNPC.patrol.x = 200 + Math.random() * 1600;
+ vandalNPC.patrol.y = 200 + Math.random() * 2300;
+ npcs.push(vandalNPC);
}
-// Create 8 police - spawn from center of map
-for (var i = 0; i < 8; i++) {
- var policeOfficer = worldContainer.addChild(new Police());
- // Spawn police from center area of the map
- var centerX = 1000;
- var centerY = 1400;
- var radius = 150; // Small radius around center
- var angle = i / 8 * Math.PI * 2; // Distribute evenly in circle
- policeOfficer.x = centerX + Math.cos(angle) * radius;
- policeOfficer.y = centerY + Math.sin(angle) * radius;
- police.push(policeOfficer);
+// Create NPC couriers - spawn around buildings
+for (var i = 0; i < 10; i++) {
+ var courierNPC = worldContainer.addChild(new NPCCourier());
+ // Spawn around buildings randomly
+ if (buildings.length > 0) {
+ var building = buildings[Math.floor(Math.random() * buildings.length)];
+ courierNPC.x = building.x + (Math.random() - 0.5) * 100;
+ courierNPC.y = building.y + (Math.random() - 0.5) * 100;
+ } else {
+ courierNPC.x = 200 + Math.random() * 1600;
+ courierNPC.y = 200 + Math.random() * 2300;
+ }
+ courierNPC.npcType = 'courier';
+ courierNPC.patrol.x = 200 + Math.random() * 1600;
+ courierNPC.patrol.y = 200 + Math.random() * 2300;
+ npcs.push(courierNPC);
}
-// Create many initial AI orders immediately to keep couriers busy
-for (var i = 0; i < 8; i++) {
- LK.setTimeout(function () {
- createAIOrder();
- }, i * 200);
-}
+// Create initial AI orders immediately and more frequently
+LK.setTimeout(function () {
+ createAIOrder();
+}, 100);
+LK.setTimeout(function () {
+ createAIOrder();
+}, 300);
+LK.setTimeout(function () {
+ createAIOrder();
+}, 600);
+LK.setTimeout(function () {
+ createAIOrder();
+}, 900);
// Spawn initial power-up
LK.setTimeout(function () {
spawnPowerUp();
}, 3000);
@@ -1801,38 +1398,21 @@
// Update customers
for (var i = 0; i < customers.length; i++) {
customers[i].update();
}
- // Update AI couriers
- for (var i = 0; i < aiCouriers.length; i++) {
- aiCouriers[i].update();
+ // Update all NPCs
+ for (var i = 0; i < npcs.length; i++) {
+ npcs[i].update();
}
- // Update vandals
- for (var i = 0; i < vandals.length; i++) {
- vandals[i].update();
+ // Create new AI orders periodically - more frequently and higher limit
+ if (LK.ticks % 90 === 0 && aiOrders.length < 12) {
+ // Every 1.5 seconds, maintain up to 12 AI orders
+ createAIOrder();
}
- // Update police
- for (var i = 0; i < police.length; i++) {
- police[i].update();
- }
- // Create new AI orders very frequently to ensure constant activity
- if (LK.ticks % 30 === 0 && aiOrders.length < 20) {
- // Every 0.5 seconds, maintain up to 20 AI orders
+ // Create additional AI orders periodically
+ if (LK.ticks % 90 === 0 && aiOrders.length < 8) {
createAIOrder();
}
- // Always ensure there are orders available for idle couriers
- if (LK.ticks % 45 === 0) {
- var idleCouriers = 0;
- for (var i = 0; i < aiCouriers.length; i++) {
- if (!aiCouriers[i].hasOrder && aiCouriers[i].state === 'searching') {
- idleCouriers++;
- }
- }
- // Create multiple orders for each idle courier to ensure they find work
- for (var i = 0; i < idleCouriers * 2 && aiOrders.length < 25; i++) {
- createAIOrder();
- }
- }
// Update power-ups
for (var i = powerUps.length - 1; i >= 0; i--) {
var powerUp = powerUps[i];
powerUp.update();
ıts a courier scooter its looking behind . No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
police car with flashing lights. In-Game asset. 2d. High contrast. No shadows
vandal riding dirt bike. In-Game asset. High contrast. No shadows
health powerup. In-Game asset. 2d. High contrast. No shadows
pizza boxes. In-Game asset. 2d. High contrast. No shadows
extra time powerup. In-Game asset. 2d. High contrast. No shadows
restaurant shop. In-Game asset. High contrast. No shadows
town house. In-Game asset. 2d. High contrast. No shadows
a man waiting outside. In-Game asset. 2d. High contrast. No shadows
arrow. In-Game asset. 2d. High contrast. No shadows
fence. In-Game asset. 2d. High contrast. No shadows