fix: Add soldier button and implement wolf hunting mechanics
This commit is contained in:
188
ai.js
188
ai.js
@@ -2,6 +2,12 @@
|
||||
* 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.0002;
|
||||
const WOLF_REPRO_COOLDOWN = 5000;
|
||||
|
||||
/**********************************************************************
|
||||
* CITIZEN AI
|
||||
@@ -10,7 +16,7 @@ function updateCitizen(cit) {
|
||||
cit.hunger += HUNGER_INCREMENT;
|
||||
if(cit.hunger > HUNGER_MAX) cit.hunger = HUNGER_MAX;
|
||||
|
||||
if(["chop", "gatherFruit", "build", "treatPatients", "teachStudents", "sellGoods"].includes(cit.task)) {
|
||||
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;
|
||||
@@ -43,6 +49,8 @@ function updateCitizen(cit) {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -50,18 +58,100 @@ function updateCitizen(cit) {
|
||||
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, 0.5);
|
||||
|
||||
// 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, 0.45);
|
||||
|
||||
// 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, 0.5);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Reproduce
|
||||
if(w.hunger < 30 && w.reproductionCooldown <= 0) {
|
||||
if(Math.random() < WOLF_REPRO_CHANCE) {
|
||||
spawnBabyAnimal("Wolf", w.x, w.y);
|
||||
w.reproductionCooldown = WOLF_REPRO_COOLDOWN;
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
@@ -71,6 +161,14 @@ function updateRabbit(r) {
|
||||
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) {
|
||||
@@ -130,6 +228,16 @@ function assignNewTask(cit) {
|
||||
}
|
||||
}
|
||||
|
||||
// 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":
|
||||
@@ -147,6 +255,9 @@ function assignNewTask(cit) {
|
||||
case "Teacher":
|
||||
teacherTasks(cit);
|
||||
break;
|
||||
case "Soldier":
|
||||
soldierTasks(cit);
|
||||
break;
|
||||
default:
|
||||
cit.task = null;
|
||||
cit.target = null;
|
||||
@@ -254,9 +365,58 @@ function teacherTasks(cit) {
|
||||
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);
|
||||
}
|
||||
|
||||
/**********************************************************************
|
||||
* NEW TASK HANDLERS
|
||||
**********************************************************************/
|
||||
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) {
|
||||
@@ -376,6 +536,19 @@ function chopTask(cit) {
|
||||
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;
|
||||
@@ -539,3 +712,16 @@ function randomAnimalWander(a) {
|
||||
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);
|
||||
if(dist > 1) {
|
||||
obj.vx = (dx/dist) * speed;
|
||||
obj.vy = (dy/dist) * speed;
|
||||
} else {
|
||||
obj.vx = (Math.random() - 0.5) * speed;
|
||||
obj.vy = (Math.random() - 0.5) * speed;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user