841 lines
23 KiB
JavaScript
841 lines
23 KiB
JavaScript
/**********************************************************************
|
|
* ANIMAL CONSTANTS
|
|
**********************************************************************/
|
|
const ANIMAL_HUNGER_MAX = 100;
|
|
const WOLF_HUNGER_INCREMENT = 0.01;
|
|
const WOLF_DAMAGE = 10;
|
|
const WOLF_ATTACK_RANGE = 15;
|
|
const WOLF_ATTACK_COOLDOWN = 100;
|
|
const WOLF_REPRO_CHANCE = 0.00008; // Much lower than rabbits
|
|
const WOLF_REPRO_COOLDOWN = 8000; // Longer cooldown than rabbits
|
|
|
|
/**********************************************************************
|
|
* CITIZEN AI
|
|
**********************************************************************/
|
|
function updateCitizen(cit) {
|
|
cit.hunger += HUNGER_INCREMENT;
|
|
if(cit.hunger > HUNGER_MAX) cit.hunger = HUNGER_MAX;
|
|
|
|
if(["chop", "gatherFruit", "build", "treatPatients", "teachStudents", "sellGoods", "huntWolf", "patrol"].includes(cit.task)) {
|
|
cit.energy -= ENERGY_DECREMENT_WORK;
|
|
} else if(cit.task === "restAtHouse") {
|
|
cit.energy += ENERGY_INCREMENT_REST;
|
|
} else {
|
|
cit.energy -= 0.0005;
|
|
}
|
|
|
|
if(cit.energy < 0) cit.energy = 0;
|
|
if(cit.energy > ENERGY_MAX) cit.energy = ENERGY_MAX;
|
|
|
|
// Health decreases if hunger is high
|
|
if(cit.hunger > 80) {
|
|
cit.health -= 0.01;
|
|
if(cit.health < 0) cit.health = 0;
|
|
}
|
|
|
|
if(!cit.task) assignNewTask(cit);
|
|
|
|
switch(cit.task) {
|
|
case 'chop': chopTask(cit); break;
|
|
case 'deliverWood': deliverWoodTask(cit); break;
|
|
case 'build': buildTask(cit); break;
|
|
case 'gatherFruit': gatherFruitTask(cit); break;
|
|
case 'deliverFruit': deliverFruitTask(cit); break;
|
|
case 'plantFruitTree': plantFruitTreeTask(cit); break;
|
|
case 'eatAtHouse': eatAtHouseTask(cit); break;
|
|
case 'restAtHouse': restAtHouseTask(cit); break;
|
|
case 'treatPatients': treatPatientsTask(cit); break;
|
|
case 'teachStudents': teachStudentsTask(cit); break;
|
|
case 'sellGoods': sellGoodsTask(cit); break;
|
|
case 'visitHospital': visitHospitalTask(cit); break;
|
|
case 'visitSchool': visitSchoolTask(cit); break;
|
|
case 'huntWolf': huntWolfTask(cit); break;
|
|
case 'patrol': patrolTask(cit); break;
|
|
default: randomWander(cit); break;
|
|
}
|
|
|
|
cit.x += cit.vx;
|
|
cit.y += cit.vy;
|
|
}
|
|
|
|
function updateWolf(w) {
|
|
w.hunger += WOLF_HUNGER_INCREMENT;
|
|
if(w.hunger >= ANIMAL_HUNGER_MAX) {
|
|
w.dead = true;
|
|
logAction("A wolf starved to death.");
|
|
return;
|
|
}
|
|
|
|
// Look for prey
|
|
if(w.hunger > 30) {
|
|
// First try to find rabbits
|
|
let rabbit = findNearestAnimalOfType(w, "Rabbit");
|
|
if(rabbit && distance(w.x, w.y, rabbit.x, rabbit.y) < 200) {
|
|
moveToward(w, rabbit.x, rabbit.y, WOLF_SPEED);
|
|
|
|
// Attack if close enough and cooldown is ready
|
|
if(distance(w.x, w.y, rabbit.x, rabbit.y) < WOLF_ATTACK_RANGE && w.attackCooldown <= 0) {
|
|
rabbit.dead = true;
|
|
w.hunger -= 30;
|
|
if(w.hunger < 0) w.hunger = 0;
|
|
w.attackCooldown = WOLF_ATTACK_COOLDOWN;
|
|
logAction("A wolf caught and ate a rabbit!");
|
|
}
|
|
return;
|
|
}
|
|
|
|
// If no rabbits nearby, look for citizens (but not if soldiers are nearby)
|
|
let nearbySoldier = citizens.find(c =>
|
|
c.profession === "Soldier" &&
|
|
distance(w.x, w.y, c.x, c.y) < 100
|
|
);
|
|
|
|
if(!nearbySoldier) {
|
|
let citizen = citizens.find(c => distance(w.x, w.y, c.x, c.y) < 150);
|
|
if(citizen) {
|
|
moveToward(w, citizen.x, citizen.y, WOLF_SPEED);
|
|
|
|
// Attack if close enough and cooldown is ready
|
|
if(distance(w.x, w.y, citizen.x, citizen.y) < WOLF_ATTACK_RANGE && w.attackCooldown <= 0) {
|
|
citizen.health -= WOLF_DAMAGE;
|
|
w.attackCooldown = WOLF_ATTACK_COOLDOWN;
|
|
|
|
// Knockback effect
|
|
knockback(citizen, w.x, w.y, 20);
|
|
|
|
logAction(`A wolf attacked ${citizen.name}! Health: ${Math.floor(citizen.health)}`);
|
|
|
|
if(citizen.health <= 0) {
|
|
// Remove citizen
|
|
citizens = citizens.filter(c => c !== citizen);
|
|
logAction(`${citizen.name} was killed by a wolf!`);
|
|
w.hunger -= 50;
|
|
if(w.hunger < 0) w.hunger = 0;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
} else {
|
|
// Run away from soldier
|
|
moveAway(w, nearbySoldier.x, nearbySoldier.y, WOLF_SPEED);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Reproduce - wolves need to be well-fed and have low hunger to reproduce
|
|
if(w.hunger < 20 && w.reproductionCooldown <= 0) {
|
|
// Find another wolf nearby for mating
|
|
let nearbyWolf = findNearestAnimalOfType(w, "Wolf");
|
|
if(nearbyWolf && nearbyWolf !== w && distance(w.x, w.y, nearbyWolf.x, nearbyWolf.y) < 50) {
|
|
if(Math.random() < WOLF_REPRO_CHANCE) {
|
|
spawnBabyAnimal("Wolf", w.x, w.y);
|
|
w.reproductionCooldown = WOLF_REPRO_COOLDOWN;
|
|
logAction("A wolf pack has grown - a new wolf pup was born!");
|
|
}
|
|
}
|
|
}
|
|
|
|
// Random movement
|
|
randomAnimalWander(w);
|
|
}
|
|
|
|
/**********************************************************************
|
|
* ANIMAL AI
|
|
**********************************************************************/
|
|
function updateAnimal(ani) {
|
|
if(ani.type === "Rabbit") {
|
|
updateRabbit(ani);
|
|
} else if(ani.type === "Wolf") {
|
|
updateWolf(ani);
|
|
}
|
|
|
|
ani.x += ani.vx;
|
|
ani.y += ani.vy;
|
|
if(ani.reproductionCooldown > 0) {
|
|
ani.reproductionCooldown--;
|
|
}
|
|
if(ani.attackCooldown > 0) {
|
|
ani.attackCooldown--;
|
|
}
|
|
}
|
|
|
|
function updateRabbit(r) {
|
|
r.hunger += RABBIT_HUNGER_INCREMENT;
|
|
if(r.hunger >= ANIMAL_HUNGER_MAX) {
|
|
r.dead = true;
|
|
logAction("A rabbit starved to death.");
|
|
return;
|
|
}
|
|
|
|
// Check for nearby wolves and run away
|
|
let nearestWolf = findNearestAnimalOfType(r, "Wolf");
|
|
if(nearestWolf && distance(r.x, r.y, nearestWolf.x, nearestWolf.y) < 100) {
|
|
// Run away from wolf
|
|
moveAway(r, nearestWolf.x, nearestWolf.y, 0.6);
|
|
return;
|
|
}
|
|
// Reproduce
|
|
if(r.hunger < 50 && r.reproductionCooldown <= 0) {
|
|
if(Math.random() < RABBIT_REPRO_CHANCE) {
|
|
spawnBabyAnimal("Rabbit", r.x, r.y);
|
|
r.reproductionCooldown = RABBIT_REPRO_COOLDOWN;
|
|
}
|
|
}
|
|
if(r.hunger > 50) {
|
|
let tree = findNearestResourceOfType(r, "FruitTree");
|
|
if(tree) {
|
|
moveToward(r, tree.x, tree.y, 0.4);
|
|
if(distance(r.x, r.y, tree.x, tree.y) < 10) {
|
|
if(tree.amount > 0) {
|
|
tree.amount--;
|
|
r.hunger -= 2;
|
|
if(r.hunger < 0) r.hunger = 0;
|
|
if(Math.random() < 0.02) logAction("A rabbit is eating fruit...");
|
|
}
|
|
}
|
|
} else {
|
|
randomAnimalWander(r);
|
|
}
|
|
} else {
|
|
randomAnimalWander(r);
|
|
}
|
|
}
|
|
|
|
/**********************************************************************
|
|
* TASKS & AI LOGIC
|
|
**********************************************************************/
|
|
function assignNewTask(cit) {
|
|
// Check for critical needs first
|
|
if(cit.hunger >= HUNGER_THRESHOLD) {
|
|
let houseWithFruit = findHouseWithFruit();
|
|
if(houseWithFruit) {
|
|
cit.task = "eatAtHouse";
|
|
cit.target = houseWithFruit;
|
|
return;
|
|
}
|
|
}
|
|
|
|
if(cit.energy <= ENERGY_THRESHOLD) {
|
|
let compHouse = findAnyCompletedHouse();
|
|
if(compHouse) {
|
|
cit.task = "restAtHouse";
|
|
cit.target = compHouse;
|
|
return;
|
|
}
|
|
}
|
|
|
|
if(cit.health <= HEALTH_THRESHOLD) {
|
|
let hospital = findCompletedHospital();
|
|
if(hospital) {
|
|
cit.task = "visitHospital";
|
|
cit.target = hospital;
|
|
return;
|
|
}
|
|
}
|
|
|
|
// For soldiers, check for wolves to hunt
|
|
if(cit.profession === "Soldier") {
|
|
let nearbyWolf = findNearestAnimalOfType(cit, "Wolf");
|
|
if(nearbyWolf && distance(cit.x, cit.y, nearbyWolf.x, nearbyWolf.y) < 300) {
|
|
cit.task = "huntWolf";
|
|
cit.target = nearbyWolf;
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Profession-specific tasks
|
|
switch(cit.profession) {
|
|
case "Builder":
|
|
builderTasks(cit);
|
|
break;
|
|
case "Farmer":
|
|
farmerTasks(cit);
|
|
break;
|
|
case "Merchant":
|
|
merchantTasks(cit);
|
|
break;
|
|
case "Doctor":
|
|
doctorTasks(cit);
|
|
break;
|
|
case "Teacher":
|
|
teacherTasks(cit);
|
|
break;
|
|
case "Soldier":
|
|
soldierTasks(cit);
|
|
break;
|
|
case "Planner":
|
|
plannerTasks(cit);
|
|
break;
|
|
default:
|
|
cit.task = null;
|
|
cit.target = null;
|
|
}
|
|
}
|
|
|
|
function builderTasks(cit) {
|
|
let buildingNeedingWood = buildings.find(b => !b.completed && b.deliveredWood < b.requiredWood);
|
|
if(buildingNeedingWood) {
|
|
if(cit.carryingWood > 0) {
|
|
cit.task = "deliverWood";
|
|
cit.target = buildingNeedingWood;
|
|
return;
|
|
}
|
|
let tree = findNearestResourceOfType(cit, "Tree");
|
|
if(tree) {
|
|
cit.task = "chop";
|
|
cit.target = tree;
|
|
return;
|
|
}
|
|
}
|
|
|
|
let buildingToConstruct = buildings.find(b => !b.completed && b.deliveredWood >= b.requiredWood);
|
|
if(buildingToConstruct) {
|
|
cit.task = "build";
|
|
cit.target = buildingToConstruct;
|
|
return;
|
|
}
|
|
|
|
let anyTree = findNearestResourceOfType(cit, "Tree");
|
|
if(anyTree) {
|
|
cit.task = "chop";
|
|
cit.target = anyTree;
|
|
return;
|
|
}
|
|
|
|
cit.task = null;
|
|
cit.target = null;
|
|
}
|
|
|
|
function farmerTasks(cit) {
|
|
if(cit.carryingFruit > 0) {
|
|
let houseNeedFruit = findHouseNeedingFruit();
|
|
if(houseNeedFruit) {
|
|
cit.task = "deliverFruit";
|
|
cit.target = houseNeedFruit;
|
|
return;
|
|
}
|
|
}
|
|
|
|
let fruitTree = findNearestResourceOfType(cit, "FruitTree");
|
|
if(fruitTree && fruitTree.amount > 0) {
|
|
cit.task = "gatherFruit";
|
|
cit.target = fruitTree;
|
|
return;
|
|
}
|
|
|
|
if(cit.carryingFruit >= FRUIT_PLANT_COST && Math.random() < 0.1) {
|
|
cit.task = "plantFruitTree";
|
|
cit.target = null;
|
|
return;
|
|
}
|
|
|
|
cit.task = null;
|
|
cit.target = null;
|
|
}
|
|
|
|
function merchantTasks(cit) {
|
|
let market = findCompletedMarket();
|
|
if(market) {
|
|
cit.task = "sellGoods";
|
|
cit.target = market;
|
|
return;
|
|
}
|
|
|
|
// If no market, help with gathering resources
|
|
if(Math.random() < 0.5) {
|
|
farmerTasks(cit);
|
|
} else {
|
|
builderTasks(cit);
|
|
}
|
|
}
|
|
|
|
function doctorTasks(cit) {
|
|
let hospital = findCompletedHospital();
|
|
if(hospital) {
|
|
cit.task = "treatPatients";
|
|
cit.target = hospital;
|
|
return;
|
|
}
|
|
|
|
// If no hospital, help with gathering resources
|
|
builderTasks(cit);
|
|
}
|
|
|
|
function teacherTasks(cit) {
|
|
let school = findCompletedSchool();
|
|
if(school) {
|
|
cit.task = "teachStudents";
|
|
cit.target = school;
|
|
return;
|
|
}
|
|
|
|
// If no school, help with gathering resources
|
|
builderTasks(cit);
|
|
}
|
|
|
|
function soldierTasks(cit) {
|
|
// Patrol around the city center
|
|
if(Math.random() < 0.1) {
|
|
cit.task = "patrol";
|
|
cit.target = {
|
|
x: randInt(-200, 200),
|
|
y: randInt(-200, 200)
|
|
};
|
|
return;
|
|
}
|
|
|
|
// Help with building when not patrolling
|
|
builderTasks(cit);
|
|
}
|
|
|
|
function plannerTasks(cit) {
|
|
// Randomly place buildings with no planning
|
|
if(Math.random() < 0.05) {
|
|
cit.task = "planBuilding";
|
|
cit.target = null;
|
|
return;
|
|
}
|
|
|
|
// Just wander around when not placing buildings
|
|
cit.task = null;
|
|
cit.target = null;
|
|
}
|
|
|
|
/**********************************************************************
|
|
* NEW TASK HANDLERS
|
|
**********************************************************************/
|
|
function planBuildingTask(cit) {
|
|
// Choose a completely random building type
|
|
const buildingTypes = ["House", "Road", "Market", "Hospital", "School"];
|
|
const buildingType = buildingTypes[Math.floor(Math.random() * buildingTypes.length)];
|
|
|
|
// Set cost based on building type
|
|
let buildingCost = 0;
|
|
switch (buildingType) {
|
|
case "House": buildingCost = COST_HOUSE; break;
|
|
case "Road": buildingCost = COST_ROAD; break;
|
|
case "Market": buildingCost = COST_MARKET; break;
|
|
case "Hospital": buildingCost = COST_HOSPITAL; break;
|
|
case "School": buildingCost = COST_SCHOOL; break;
|
|
}
|
|
|
|
// Check if we can afford it
|
|
if (money < buildingCost) {
|
|
cit.task = null;
|
|
return;
|
|
}
|
|
|
|
// Choose a completely random location
|
|
let randomX, randomY;
|
|
let attempts = 0;
|
|
|
|
// Just try random locations until we find one that's not water
|
|
do {
|
|
randomX = randInt(-1500, 1500);
|
|
randomY = randInt(-1500, 1500);
|
|
attempts++;
|
|
if (attempts > 20) {
|
|
// Give up after too many attempts
|
|
cit.task = null;
|
|
return;
|
|
}
|
|
} while (!isValidPlacement(randomX, randomY));
|
|
|
|
// Place the building at the random location
|
|
addMoney(-buildingCost, `Planner: ${buildingType}`);
|
|
|
|
let newBuilding;
|
|
switch (buildingType) {
|
|
case "House":
|
|
newBuilding = createHouseSite(randomX, randomY);
|
|
break;
|
|
case "Road":
|
|
// Just make a random road segment
|
|
newBuilding = createRoadSite(
|
|
randomX,
|
|
randomY,
|
|
randomX + randInt(-100, 100),
|
|
randomY + randInt(-100, 100)
|
|
);
|
|
break;
|
|
case "Market":
|
|
newBuilding = createMarketSite(randomX, randomY);
|
|
break;
|
|
case "Hospital":
|
|
newBuilding = createHospitalSite(randomX, randomY);
|
|
break;
|
|
case "School":
|
|
newBuilding = createSchoolSite(randomX, randomY);
|
|
break;
|
|
}
|
|
|
|
buildings.push(newBuilding);
|
|
logAction(`${cit.name} [Planner] randomly placed a ${buildingType} at (${Math.floor(randomX)}, ${Math.floor(randomY)})`);
|
|
|
|
cit.task = null;
|
|
}
|
|
|
|
|
|
function huntWolfTask(cit) {
|
|
let wolf = cit.target;
|
|
if(!wolf || wolf.dead) {
|
|
cit.task = null;
|
|
cit.target = null;
|
|
return;
|
|
}
|
|
|
|
moveToward(cit, wolf.x, wolf.y, 0.5);
|
|
|
|
// Attack if close enough
|
|
if(distance(cit.x, cit.y, wolf.x, wolf.y) < 20) {
|
|
wolf.dead = true;
|
|
logAction(`${cit.name} [Soldier] killed a wolf!`);
|
|
cit.task = null;
|
|
cit.target = null;
|
|
}
|
|
}
|
|
|
|
function patrolTask(cit) {
|
|
let target = cit.target;
|
|
if(!target) {
|
|
cit.task = null;
|
|
return;
|
|
}
|
|
|
|
moveToward(cit, target.x, target.y, 0.3);
|
|
|
|
// When reached patrol point, find a new task
|
|
if(distance(cit.x, cit.y, target.x, target.y) < 10) {
|
|
cit.task = null;
|
|
cit.target = null;
|
|
}
|
|
}
|
|
function treatPatientsTask(cit) {
|
|
let hospital = cit.target;
|
|
if(!hospital || !hospital.completed) {
|
|
cit.task = null;
|
|
cit.target = null;
|
|
return;
|
|
}
|
|
|
|
moveToward(cit, hospital.x, hospital.y, 0.3);
|
|
if(distance(cit.x, cit.y, hospital.x, hospital.y) < 20) {
|
|
// Generate medicine
|
|
if(frameCount % 100 === 0) {
|
|
if(hospital.medicine < hospital.maxMedicine) {
|
|
hospital.medicine++;
|
|
cityStorage.medicine++;
|
|
if(Math.random() < 0.1) {
|
|
logAction(`${cit.name} [Doctor] created medicine at the hospital.`);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function teachStudentsTask(cit) {
|
|
let school = cit.target;
|
|
if(!school || !school.completed) {
|
|
cit.task = null;
|
|
cit.target = null;
|
|
return;
|
|
}
|
|
|
|
moveToward(cit, school.x, school.y, 0.3);
|
|
if(distance(cit.x, cit.y, school.x, school.y) < 20) {
|
|
// Generate knowledge
|
|
if(frameCount % 100 === 0) {
|
|
if(school.knowledge < school.maxKnowledge) {
|
|
school.knowledge++;
|
|
cityStorage.knowledge++;
|
|
if(Math.random() < 0.1) {
|
|
logAction(`${cit.name} [Teacher] is teaching at the school.`);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function sellGoodsTask(cit) {
|
|
let market = cit.target;
|
|
if(!market || !market.completed) {
|
|
cit.task = null;
|
|
cit.target = null;
|
|
return;
|
|
}
|
|
|
|
moveToward(cit, market.x, market.y, 0.3);
|
|
if(distance(cit.x, cit.y, market.x, market.y) < 20) {
|
|
// Generate income occasionally
|
|
if(frameCount % 200 === 0 && Math.random() < 0.3) {
|
|
let income = randInt(5, 15);
|
|
addMoney(income, "Market sales");
|
|
if(Math.random() < 0.1) {
|
|
logAction(`${cit.name} [Merchant] made $${income} at the market.`);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function visitHospitalTask(cit) {
|
|
let hospital = cit.target;
|
|
if(!hospital || !hospital.completed) {
|
|
cit.task = null;
|
|
cit.target = null;
|
|
return;
|
|
}
|
|
|
|
moveToward(cit, hospital.x, hospital.y, 0.4);
|
|
if(distance(cit.x, cit.y, hospital.x, hospital.y) < 20) {
|
|
if(hospital.medicine > 0 && cit.health < HEALTH_MAX) {
|
|
hospital.medicine--;
|
|
cit.health += 20;
|
|
if(cit.health > HEALTH_MAX) cit.health = HEALTH_MAX;
|
|
logAction(`${cit.name} received medical treatment. Health: ${Math.floor(cit.health)}`);
|
|
}
|
|
cit.task = null;
|
|
cit.target = null;
|
|
}
|
|
}
|
|
|
|
function visitSchoolTask(cit) {
|
|
let school = cit.target;
|
|
if(!school || !school.completed) {
|
|
cit.task = null;
|
|
cit.target = null;
|
|
return;
|
|
}
|
|
|
|
moveToward(cit, school.x, school.y, 0.4);
|
|
if(distance(cit.x, cit.y, school.x, school.y) < 20) {
|
|
if(school.knowledge > 0 && cit.education < EDUCATION_MAX) {
|
|
school.knowledge--;
|
|
cit.education += 10;
|
|
if(cit.education > EDUCATION_MAX) cit.education = EDUCATION_MAX;
|
|
logAction(`${cit.name} learned at school. Education: ${Math.floor(cit.education)}`);
|
|
}
|
|
cit.task = null;
|
|
cit.target = null;
|
|
}
|
|
}
|
|
|
|
/**********************************************************************
|
|
* EXISTING TASK HANDLERS
|
|
**********************************************************************/
|
|
function chopTask(cit) {
|
|
let tree = cit.target;
|
|
if(!tree || tree.amount <= 0) {
|
|
cit.task = null;
|
|
cit.target = null;
|
|
return;
|
|
}
|
|
|
|
// Check for nearby wolves and run if not a soldier
|
|
if(cit.profession !== "Soldier") {
|
|
let nearbyWolf = findNearestAnimalOfType(cit, "Wolf");
|
|
if(nearbyWolf && distance(cit.x, cit.y, nearbyWolf.x, nearbyWolf.y) < 100) {
|
|
// Run away from wolf
|
|
moveAway(cit, nearbyWolf.x, nearbyWolf.y, 0.6);
|
|
if(Math.random() < 0.1) {
|
|
logAction(`${cit.name} is running away from a wolf!`);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
moveToward(cit, tree.x, tree.y, 0.4);
|
|
if(distance(cit.x, cit.y, tree.x, tree.y) < 10) {
|
|
let canGather = cit.carryingCapacity - cit.carryingWood;
|
|
let toGather = Math.min(1, tree.amount, canGather);
|
|
tree.amount -= toGather;
|
|
cit.carryingWood += toGather;
|
|
if(Math.random() < 0.01) {
|
|
logAction(`${cit.name}[${cit.profession}] chopping wood...`);
|
|
}
|
|
if(cit.carryingWood >= cit.carryingCapacity || tree.amount <= 0) {
|
|
cit.task = null;
|
|
cit.target = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
function deliverWoodTask(cit) {
|
|
let b = cit.target;
|
|
if(!b || b.completed) {
|
|
cit.task = null;
|
|
cit.target = null;
|
|
return;
|
|
}
|
|
moveToward(cit, b.x, b.y, 0.4);
|
|
if(distance(cit.x, cit.y, b.x, b.y) < 20) {
|
|
let needed = b.requiredWood - b.deliveredWood;
|
|
if(needed > 0 && cit.carryingWood > 0) {
|
|
let toDeliver = Math.min(cit.carryingWood, needed);
|
|
b.deliveredWood += toDeliver;
|
|
cit.carryingWood -= toDeliver;
|
|
logAction(`${cit.name} delivered ${toDeliver} wood to ${b.buildingType}.`);
|
|
addMoney(REWARD_DELIVER_WOOD, "deliver wood");
|
|
}
|
|
cit.task = null;
|
|
cit.target = null;
|
|
}
|
|
}
|
|
|
|
function buildTask(cit) {
|
|
let b = cit.target;
|
|
if(!b || b.completed) {
|
|
cit.task = null;
|
|
cit.target = null;
|
|
return;
|
|
}
|
|
moveToward(cit, b.x, b.y, 0.3);
|
|
}
|
|
|
|
function gatherFruitTask(cit) {
|
|
let tree = cit.target;
|
|
if(!tree || tree.amount <= 0) {
|
|
cit.task = null;
|
|
cit.target = null;
|
|
return;
|
|
}
|
|
moveToward(cit, tree.x, tree.y, 0.4);
|
|
if(distance(cit.x, cit.y, tree.x, tree.y) < 10) {
|
|
let canGather = cit.carryingCapacity - cit.carryingFruit;
|
|
let toGather = Math.min(FRUIT_GATHER_RATE, tree.amount, canGather);
|
|
tree.amount -= toGather;
|
|
cit.carryingFruit += toGather;
|
|
cityStorage.food += toGather / 2; // Half goes to city storage
|
|
if(Math.random() < 0.01) {
|
|
logAction(`${cit.name} [${cit.profession}] is gathering fruit...`);
|
|
}
|
|
if(cit.carryingFruit >= cit.carryingCapacity || tree.amount <= 0) {
|
|
cit.task = null;
|
|
cit.target = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
function deliverFruitTask(cit) {
|
|
let house = cit.target;
|
|
if(!house || !house.completed) {
|
|
cit.task = null;
|
|
cit.target = null;
|
|
return;
|
|
}
|
|
moveToward(cit, house.x, house.y, 0.4);
|
|
if(distance(cit.x, cit.y, house.x, house.y) < 20) {
|
|
let space = house.maxFruit - house.storedFruit;
|
|
if(space > 0 && cit.carryingFruit > 0) {
|
|
let toDeliver = Math.min(cit.carryingFruit, space);
|
|
house.storedFruit += toDeliver;
|
|
cit.carryingFruit -= toDeliver;
|
|
logAction(`${cit.name} delivered ${toDeliver} fruit to house.`);
|
|
}
|
|
cit.task = null;
|
|
cit.target = null;
|
|
}
|
|
}
|
|
|
|
function plantFruitTreeTask(cit) {
|
|
// Try to find a valid land position nearby
|
|
let px, py;
|
|
let attempts = 0;
|
|
do {
|
|
px = cit.x + randInt(-50, 50);
|
|
py = cit.y + randInt(-50, 50);
|
|
attempts++;
|
|
// Give up after too many attempts and find a new task
|
|
if (attempts > 20) {
|
|
cit.task = null;
|
|
cit.target = null;
|
|
return;
|
|
}
|
|
} while (!isValidPlacement(px, py));
|
|
|
|
moveToward(cit, px, py, 0.4);
|
|
if(distance(cit.x, cit.y, px, py) < 10) {
|
|
if(cit.carryingFruit >= FRUIT_PLANT_COST) {
|
|
cit.carryingFruit -= FRUIT_PLANT_COST;
|
|
resources.push(createResource("FruitTree", px, py, FRUIT_TREE_START_AMOUNT));
|
|
logAction(`${cit.name}[${cit.profession}] planted a new fruit tree!`);
|
|
}
|
|
cit.task = null;
|
|
cit.target = null;
|
|
}
|
|
}
|
|
|
|
function eatAtHouseTask(cit) {
|
|
let house = cit.target;
|
|
if(!house || !house.completed) {
|
|
cit.task = null;
|
|
cit.target = null;
|
|
return;
|
|
}
|
|
moveToward(cit, house.x, house.y, 0.4);
|
|
if(distance(cit.x, cit.y, house.x, house.y) < 20) {
|
|
if(house.storedFruit > 0 && cit.hunger > 0) {
|
|
let amtToEat = 10;
|
|
let eaten = Math.min(amtToEat, house.storedFruit);
|
|
house.storedFruit -= eaten;
|
|
cit.hunger -= eaten;
|
|
if(cit.hunger < 0) cit.hunger = 0;
|
|
logAction(`${cit.name} ate ${eaten} fruit. Hunger => ${Math.floor(cit.hunger)}.`);
|
|
}
|
|
cit.task = null;
|
|
cit.target = null;
|
|
}
|
|
}
|
|
|
|
function restAtHouseTask(cit) {
|
|
let house = cit.target;
|
|
if(!house || !house.completed) {
|
|
cit.task = null;
|
|
cit.target = null;
|
|
return;
|
|
}
|
|
moveToward(cit, house.x, house.y, 0.4);
|
|
if(distance(cit.x, cit.y, house.x, house.y) < 20) {
|
|
if(cit.energy >= ENERGY_MAX - 1) {
|
|
cit.energy = ENERGY_MAX;
|
|
cit.task = null;
|
|
cit.target = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**********************************************************************
|
|
* RANDOM WANDER
|
|
**********************************************************************/
|
|
function randomWander(cit) {
|
|
if(Math.random() < 0.01) {
|
|
cit.vx = (Math.random() - 0.5) * 0.3;
|
|
cit.vy = (Math.random() - 0.5) * 0.3;
|
|
}
|
|
}
|
|
|
|
function randomAnimalWander(a) {
|
|
if(Math.random() < 0.01) {
|
|
a.vx = (Math.random() - 0.5) * 0.4;
|
|
a.vy = (Math.random() - 0.5) * 0.4;
|
|
}
|
|
}
|
|
|
|
function moveAway(obj, tx, ty, speed=0.4) {
|
|
let dx = obj.x - tx;
|
|
let dy = obj.y - ty;
|
|
let dist = Math.sqrt(dx*dx + dy*dy);
|
|
|
|
// Apply water movement penalty if in water
|
|
let actualSpeed = speed;
|
|
if (isWater(obj.x, obj.y)) {
|
|
actualSpeed *= WATER_MOVEMENT_PENALTY;
|
|
}
|
|
|
|
if(dist > 1) {
|
|
obj.vx = (dx/dist) * actualSpeed;
|
|
obj.vy = (dy/dist) * actualSpeed;
|
|
} else {
|
|
obj.vx = (Math.random() - 0.5) * actualSpeed;
|
|
obj.vy = (Math.random() - 0.5) * actualSpeed;
|
|
}
|
|
}
|