Update index.html
This commit is contained in:
parent
11165b76b6
commit
cababc87bc
572
index.html
572
index.html
@ -2,7 +2,7 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<title>Virtual World</title>
|
<title>Virtual World: More Rabbits, Separate Wolf Spawning</title>
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@ -10,33 +10,27 @@
|
|||||||
background: #e0f7fa;
|
background: #e0f7fa;
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
#app {
|
#app {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
margin: 5px;
|
margin: 5px;
|
||||||
color: #333;
|
color: #333;
|
||||||
}
|
}
|
||||||
|
|
||||||
#controls {
|
#controls {
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
canvas {
|
canvas {
|
||||||
background: #ffffff;
|
background: #ffffff;
|
||||||
border: 2px solid #444;
|
border: 2px solid #444;
|
||||||
cursor: grab;
|
cursor: grab;
|
||||||
}
|
}
|
||||||
|
|
||||||
canvas:active {
|
canvas:active {
|
||||||
cursor: grabbing;
|
cursor: grabbing;
|
||||||
}
|
}
|
||||||
|
|
||||||
#log {
|
#log {
|
||||||
width: 800px;
|
width: 800px;
|
||||||
height: 120px;
|
height: 120px;
|
||||||
@ -47,7 +41,6 @@
|
|||||||
padding: 5px;
|
padding: 5px;
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.log-entry {
|
.log-entry {
|
||||||
margin: 2px 0;
|
margin: 2px 0;
|
||||||
}
|
}
|
||||||
@ -56,23 +49,22 @@
|
|||||||
<body>
|
<body>
|
||||||
|
|
||||||
<div id="app">
|
<div id="app">
|
||||||
<h1>Virtual World</h1>
|
<h1>Virtual World: Rabbits & Wolves (Separated Spawn)</h1>
|
||||||
<p id="controls">Drag to move the map, scroll to zoom in/out.</p>
|
<p id="controls">Drag to move the map, scroll to zoom in/out.</p>
|
||||||
<canvas id="worldCanvas" width="800" height="600"></canvas>
|
<canvas id="worldCanvas" width="800" height="600"></canvas>
|
||||||
<div id="log"></div>
|
<div id="log"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
/*
|
/*
|
||||||
---------------------------------------------------------------------------------
|
---------------------------------------------------------------------------------
|
||||||
Virtual World Simulation with:
|
Key Differences:
|
||||||
- Builder & Farmer Professions
|
- STARTING_RABBITS = 30 (spawned randomly across [-1000, 1000])
|
||||||
- Hunger & Energy
|
- STARTING_WOLVES = 5 (spawned in a separate area [500..1000])
|
||||||
- Fruit Trees (gather fruit, plant new trees)
|
- Rest of the simulation remains the same:
|
||||||
- Houses (store fruit, citizens can eat & rest)
|
* Citizens with Professions, Houses, Roads, Fruit Trees
|
||||||
- Roads automatically built between houses once they finish construction!
|
* Rabbits & Wolves Reproduction
|
||||||
- Logging & Pan/Zoom
|
* Basic Predator/Prey & Combat
|
||||||
- Child Birth Events
|
|
||||||
---------------------------------------------------------------------------------
|
---------------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -94,24 +86,27 @@ let lastMouseY = 0;
|
|||||||
// Frame counter for timed events
|
// Frame counter for timed events
|
||||||
let frameCount = 0;
|
let frameCount = 0;
|
||||||
|
|
||||||
// Shared city storage for wood
|
// City storage for wood
|
||||||
const cityStorage = {
|
const cityStorage = {
|
||||||
wood: 0
|
wood: 0
|
||||||
};
|
};
|
||||||
|
|
||||||
// Array of resource nodes (trees & fruit trees)
|
// Resource nodes (trees & fruit trees)
|
||||||
let resources = [];
|
let resources = [];
|
||||||
|
|
||||||
// Buildings array (includes both House sites and Road sites)
|
// Buildings array (houses, roads)
|
||||||
let buildings = [];
|
let buildings = [];
|
||||||
|
|
||||||
// Citizens
|
// Citizens
|
||||||
let citizens = [];
|
let citizens = [];
|
||||||
|
|
||||||
|
// Animals (rabbits & wolves)
|
||||||
|
let animals = [];
|
||||||
|
|
||||||
/**********************************************************************
|
/**********************************************************************
|
||||||
* SIMULATION CONSTANTS
|
* SIMULATION CONSTANTS
|
||||||
**********************************************************************/
|
**********************************************************************/
|
||||||
// Hunger & Energy
|
// Hunger & energy for citizens
|
||||||
const HUNGER_INCREMENT = 0.005;
|
const HUNGER_INCREMENT = 0.005;
|
||||||
const ENERGY_DECREMENT_WORK = 0.02;
|
const ENERGY_DECREMENT_WORK = 0.02;
|
||||||
const ENERGY_INCREMENT_REST = 0.05;
|
const ENERGY_INCREMENT_REST = 0.05;
|
||||||
@ -120,12 +115,16 @@ const ENERGY_THRESHOLD = 30;
|
|||||||
const HUNGER_MAX = 100;
|
const HUNGER_MAX = 100;
|
||||||
const ENERGY_MAX = 100;
|
const ENERGY_MAX = 100;
|
||||||
|
|
||||||
// House building requirements
|
// House building
|
||||||
const HOUSE_WOOD_REQUIRED = 50;
|
const HOUSE_WOOD_REQUIRED = 50;
|
||||||
const HOUSE_BUILD_RATE = 0.2;
|
const HOUSE_BUILD_RATE = 0.2;
|
||||||
const HOUSE_MAX_FRUIT = 30;
|
const HOUSE_MAX_FRUIT = 30;
|
||||||
|
|
||||||
// Fruit tree resource
|
// Road building
|
||||||
|
const ROAD_WOOD_REQUIRED = 10;
|
||||||
|
const ROAD_BUILD_RATE = 0.3;
|
||||||
|
|
||||||
|
// Fruit trees
|
||||||
const FRUIT_TREE_START_AMOUNT = 20;
|
const FRUIT_TREE_START_AMOUNT = 20;
|
||||||
const FRUIT_GATHER_RATE = 1;
|
const FRUIT_GATHER_RATE = 1;
|
||||||
const FRUIT_PLANT_COST = 1;
|
const FRUIT_PLANT_COST = 1;
|
||||||
@ -133,10 +132,26 @@ const FRUIT_PLANT_COST = 1;
|
|||||||
// Professions
|
// Professions
|
||||||
const PROFESSIONS = ["Farmer", "Builder"];
|
const PROFESSIONS = ["Farmer", "Builder"];
|
||||||
|
|
||||||
// Road building requirements
|
// Weapons
|
||||||
// We'll make roads also be "buildings" with buildingType = "Road"
|
const WEAPON_WOOD_COST = 10; // cost for a citizen to craft a weapon
|
||||||
const ROAD_WOOD_REQUIRED = 10;
|
|
||||||
const ROAD_BUILD_RATE = 0.3;
|
// Animal logic
|
||||||
|
const STARTING_RABBITS = 30;
|
||||||
|
const STARTING_WOLVES = 5;
|
||||||
|
|
||||||
|
// Rabbit hunger
|
||||||
|
const RABBIT_HUNGER_INCREMENT = 0.003;
|
||||||
|
// Wolf hunger
|
||||||
|
const WOLF_HUNGER_INCREMENT = 0.006;
|
||||||
|
|
||||||
|
const ANIMAL_HUNGER_MAX = 100;
|
||||||
|
const KNOCKBACK_DISTANCE = 20;
|
||||||
|
|
||||||
|
// Reproduction
|
||||||
|
const RABBIT_REPRO_COOLDOWN = 3000; // frames (~50s if ~60fps)
|
||||||
|
const WOLF_REPRO_COOLDOWN = 5000; // ~80s
|
||||||
|
const RABBIT_REPRO_CHANCE = 0.0005;
|
||||||
|
const WOLF_REPRO_CHANCE = 0.0003;
|
||||||
|
|
||||||
/**********************************************************************
|
/**********************************************************************
|
||||||
* LOGGING
|
* LOGGING
|
||||||
@ -184,7 +199,8 @@ function createCitizen(name, x, y) {
|
|||||||
carryingFruit: 0,
|
carryingFruit: 0,
|
||||||
carryingCapacity: 10,
|
carryingCapacity: 10,
|
||||||
hunger: 0,
|
hunger: 0,
|
||||||
energy: ENERGY_MAX
|
energy: ENERGY_MAX,
|
||||||
|
hasWeapon: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -192,18 +208,6 @@ function createResource(type, x, y, amount) {
|
|||||||
return { type, x, y, amount };
|
return { type, x, y, amount };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Buildings can be:
|
|
||||||
* - House:
|
|
||||||
* x, y, buildingType="House"
|
|
||||||
* requiredWood, deliveredWood, buildProgress, completed
|
|
||||||
* storedFruit, maxFruit
|
|
||||||
* - Road:
|
|
||||||
* buildingType="Road"
|
|
||||||
* (x, y) as midpoint for drawing info
|
|
||||||
* x1, y1, x2, y2 for endpoints
|
|
||||||
* requiredWood, deliveredWood, buildProgress, completed
|
|
||||||
*/
|
|
||||||
function createHouseSite(x, y) {
|
function createHouseSite(x, y) {
|
||||||
return {
|
return {
|
||||||
buildingType: "House",
|
buildingType: "House",
|
||||||
@ -219,15 +223,13 @@ function createHouseSite(x, y) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function createRoadSite(x1, y1, x2, y2) {
|
function createRoadSite(x1, y1, x2, y2) {
|
||||||
// midpoint for label
|
|
||||||
const mx = (x1 + x2) / 2;
|
const mx = (x1 + x2) / 2;
|
||||||
const my = (y1 + y2) / 2;
|
const my = (y1 + y2) / 2;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
buildingType: "Road",
|
buildingType: "Road",
|
||||||
x: mx,
|
x: mx,
|
||||||
y: my,
|
y: my,
|
||||||
x1, y1,
|
x1, y1,
|
||||||
x2, y2,
|
x2, y2,
|
||||||
requiredWood: ROAD_WOOD_REQUIRED,
|
requiredWood: ROAD_WOOD_REQUIRED,
|
||||||
deliveredWood: 0,
|
deliveredWood: 0,
|
||||||
@ -236,18 +238,35 @@ function createRoadSite(x1, y1, x2, y2) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Animals: Rabbit & Wolf */
|
||||||
|
function createAnimal(type, x, y) {
|
||||||
|
const initialCooldown = (type === "Rabbit")
|
||||||
|
? randInt(0, RABBIT_REPRO_COOLDOWN)
|
||||||
|
: randInt(0, WOLF_REPRO_COOLDOWN);
|
||||||
|
return {
|
||||||
|
type,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
vx: (Math.random() - 0.5) * 0.4,
|
||||||
|
vy: (Math.random() - 0.5) * 0.4,
|
||||||
|
hunger: 0,
|
||||||
|
dead: false,
|
||||||
|
reproductionCooldown: initialCooldown
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**********************************************************************
|
/**********************************************************************
|
||||||
* WORLD INITIALIZATION
|
* WORLD INITIALIZATION
|
||||||
**********************************************************************/
|
**********************************************************************/
|
||||||
function initWorld() {
|
function initWorld() {
|
||||||
// Create some normal wood trees
|
// Create normal trees
|
||||||
for (let i = 0; i < 15; i++) {
|
for (let i = 0; i < 15; i++) {
|
||||||
const x = randInt(-1000, 1000);
|
const x = randInt(-1000, 1000);
|
||||||
const y = randInt(-1000, 1000);
|
const y = randInt(-1000, 1000);
|
||||||
resources.push(createResource("Tree", x, y, 100));
|
resources.push(createResource("Tree", x, y, 100));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create some fruit trees
|
// Create fruit trees
|
||||||
for (let i = 0; i < 10; i++) {
|
for (let i = 0; i < 10; i++) {
|
||||||
const x = randInt(-1000, 1000);
|
const x = randInt(-1000, 1000);
|
||||||
const y = randInt(-1000, 1000);
|
const y = randInt(-1000, 1000);
|
||||||
@ -261,22 +280,46 @@ function initWorld() {
|
|||||||
logAction(`Citizen joined: ${c.name} [${c.profession}]`);
|
logAction(`Citizen joined: ${c.name} [${c.profession}]`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Spawn more rabbits all across the map
|
||||||
|
for (let i = 0; i < STARTING_RABBITS; i++) {
|
||||||
|
const rx = randInt(-1000, 1000);
|
||||||
|
const ry = randInt(-1000, 1000);
|
||||||
|
animals.push(createAnimal("Rabbit", rx, ry));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spawn wolves in a distant region (e.g. x,y in [500..1000])
|
||||||
|
for (let i = 0; i < STARTING_WOLVES; i++) {
|
||||||
|
const wx = randInt(500, 1000);
|
||||||
|
const wy = randInt(500, 1000);
|
||||||
|
animals.push(createAnimal("Wolf", wx, wy));
|
||||||
|
}
|
||||||
|
|
||||||
// Start simulation
|
// Start simulation
|
||||||
requestAnimationFrame(update);
|
requestAnimationFrame(update);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**********************************************************************
|
/**********************************************************************
|
||||||
* UPDATE LOOP
|
* MAIN UPDATE LOOP
|
||||||
**********************************************************************/
|
**********************************************************************/
|
||||||
function update() {
|
function update() {
|
||||||
frameCount++;
|
frameCount++;
|
||||||
|
|
||||||
// Update each citizen
|
// Update citizens
|
||||||
citizens.forEach((cit) => {
|
citizens.forEach((cit) => {
|
||||||
updateCitizen(cit);
|
updateCitizen(cit);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Periodically add new citizens (child births)
|
// Update animals
|
||||||
|
animals.forEach((ani) => {
|
||||||
|
if (!ani.dead) {
|
||||||
|
updateAnimal(ani);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Remove any dead animals
|
||||||
|
animals = animals.filter(a => !a.dead);
|
||||||
|
|
||||||
|
// Periodic child births for citizens
|
||||||
if (frameCount % 600 === 0) {
|
if (frameCount % 600 === 0) {
|
||||||
const baby = createCitizen(randomName(), randInt(-200, 200), randInt(-200, 200));
|
const baby = createCitizen(randomName(), randInt(-200, 200), randInt(-200, 200));
|
||||||
citizens.push(baby);
|
citizens.push(baby);
|
||||||
@ -286,17 +329,12 @@ function update() {
|
|||||||
// Update buildings
|
// Update buildings
|
||||||
buildings.forEach((b) => {
|
buildings.forEach((b) => {
|
||||||
if (!b.completed && b.deliveredWood >= b.requiredWood) {
|
if (!b.completed && b.deliveredWood >= b.requiredWood) {
|
||||||
// House or Road? Different build rates
|
let buildRate = (b.buildingType === "Road") ? ROAD_BUILD_RATE : HOUSE_BUILD_RATE;
|
||||||
let buildRate = HOUSE_BUILD_RATE;
|
|
||||||
if (b.buildingType === "Road") {
|
|
||||||
buildRate = ROAD_BUILD_RATE;
|
|
||||||
}
|
|
||||||
b.buildProgress += buildRate;
|
b.buildProgress += buildRate;
|
||||||
if (b.buildProgress >= 100) {
|
if (b.buildProgress >= 100) {
|
||||||
b.completed = true;
|
b.completed = true;
|
||||||
if (b.buildingType === "House") {
|
if (b.buildingType === "House") {
|
||||||
logAction(`A new House is completed at (${b.x}, ${b.y})!`);
|
logAction(`A new House is completed at (${b.x}, ${b.y})!`);
|
||||||
// Once the house is completed, build a road from it to the nearest house
|
|
||||||
maybeBuildRoad(b);
|
maybeBuildRoad(b);
|
||||||
} else {
|
} else {
|
||||||
logAction(`A Road has been completed!`);
|
logAction(`A Road has been completed!`);
|
||||||
@ -310,30 +348,27 @@ function update() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**********************************************************************
|
/**********************************************************************
|
||||||
* CITIZEN UPDATE (AI + Movement)
|
* CITIZEN UPDATE
|
||||||
**********************************************************************/
|
**********************************************************************/
|
||||||
function updateCitizen(cit) {
|
function updateCitizen(cit) {
|
||||||
// Hunger & energy
|
// Basic hunger & energy
|
||||||
cit.hunger += HUNGER_INCREMENT;
|
cit.hunger += HUNGER_INCREMENT;
|
||||||
if (cit.hunger > HUNGER_MAX) cit.hunger = HUNGER_MAX;
|
if (cit.hunger > HUNGER_MAX) cit.hunger = HUNGER_MAX;
|
||||||
|
|
||||||
if (cit.task === 'chop' || cit.task === 'gatherFruit' || cit.task === 'build') {
|
if (["chop","gatherFruit","build"].includes(cit.task)) {
|
||||||
cit.energy -= ENERGY_DECREMENT_WORK;
|
cit.energy -= ENERGY_DECREMENT_WORK;
|
||||||
} else if (cit.task === 'restAtHouse') {
|
} else if (cit.task === "restAtHouse") {
|
||||||
cit.energy += ENERGY_INCREMENT_REST;
|
cit.energy += ENERGY_INCREMENT_REST;
|
||||||
} else {
|
} else {
|
||||||
cit.energy -= 0.0005; // slight passive drain
|
cit.energy -= 0.0005;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cit.energy < 0) cit.energy = 0;
|
if (cit.energy < 0) cit.energy = 0;
|
||||||
if (cit.energy > ENERGY_MAX) cit.energy = ENERGY_MAX;
|
if (cit.energy > ENERGY_MAX) cit.energy = ENERGY_MAX;
|
||||||
|
|
||||||
// Assign a new task if none
|
|
||||||
if (!cit.task) {
|
if (!cit.task) {
|
||||||
assignNewTask(cit);
|
assignNewTask(cit);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute current task logic
|
|
||||||
switch (cit.task) {
|
switch (cit.task) {
|
||||||
case 'chop': chopTask(cit); break;
|
case 'chop': chopTask(cit); break;
|
||||||
case 'deliverWood': deliverWoodTask(cit); break;
|
case 'deliverWood': deliverWoodTask(cit); break;
|
||||||
@ -346,16 +381,200 @@ function updateCitizen(cit) {
|
|||||||
default: randomWander(cit); break;
|
default: randomWander(cit); break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply velocity
|
|
||||||
cit.x += cit.vx;
|
cit.x += cit.vx;
|
||||||
cit.y += cit.vy;
|
cit.y += cit.vy;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**********************************************************************
|
/**********************************************************************
|
||||||
* TASK ASSIGNMENT
|
* ANIMAL UPDATE
|
||||||
|
**********************************************************************/
|
||||||
|
function updateAnimal(ani) {
|
||||||
|
if (ani.type === "Rabbit") {
|
||||||
|
updateRabbit(ani);
|
||||||
|
} else if (ani.type === "Wolf") {
|
||||||
|
updateWolf(ani);
|
||||||
|
}
|
||||||
|
|
||||||
|
ani.x += ani.vx;
|
||||||
|
ani.y += ani.vy;
|
||||||
|
|
||||||
|
// Decrement reproduction cooldown
|
||||||
|
if (ani.reproductionCooldown > 0) {
|
||||||
|
ani.reproductionCooldown -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateRabbit(rabbit) {
|
||||||
|
rabbit.hunger += RABBIT_HUNGER_INCREMENT;
|
||||||
|
if (rabbit.hunger >= ANIMAL_HUNGER_MAX) {
|
||||||
|
rabbit.dead = true;
|
||||||
|
logAction("A rabbit starved to death.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reproduction
|
||||||
|
if (rabbit.hunger < 50 && rabbit.reproductionCooldown <= 0) {
|
||||||
|
if (Math.random() < RABBIT_REPRO_CHANCE) {
|
||||||
|
spawnBabyAnimal("Rabbit", rabbit.x, rabbit.y);
|
||||||
|
rabbit.reproductionCooldown = RABBIT_REPRO_COOLDOWN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rabbit.hunger > 50) {
|
||||||
|
const tree = findNearestResourceOfType({x: rabbit.x, y: rabbit.y}, "FruitTree");
|
||||||
|
if (tree) {
|
||||||
|
moveToward(rabbit, tree.x, tree.y, 0.4);
|
||||||
|
if (distance(rabbit.x, rabbit.y, tree.x, tree.y) < 10) {
|
||||||
|
if (tree.amount > 0) {
|
||||||
|
const eatAmount = 1;
|
||||||
|
tree.amount -= eatAmount;
|
||||||
|
rabbit.hunger -= eatAmount * 2;
|
||||||
|
if (rabbit.hunger < 0) rabbit.hunger = 0;
|
||||||
|
if (Math.random() < 0.02) {
|
||||||
|
logAction("A rabbit is eating fruit...");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
randomAnimalWander(rabbit);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
randomAnimalWander(rabbit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateWolf(wolf) {
|
||||||
|
wolf.hunger += WOLF_HUNGER_INCREMENT;
|
||||||
|
if (wolf.hunger >= ANIMAL_HUNGER_MAX) {
|
||||||
|
wolf.dead = true;
|
||||||
|
logAction("A wolf starved to death.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reproduction
|
||||||
|
if (wolf.hunger < 50 && wolf.reproductionCooldown <= 0) {
|
||||||
|
if (Math.random() < WOLF_REPRO_CHANCE) {
|
||||||
|
spawnBabyAnimal("Wolf", wolf.x, wolf.y);
|
||||||
|
wolf.reproductionCooldown = WOLF_REPRO_COOLDOWN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1) hunt rabbits
|
||||||
|
let targetRabbit = findNearestAnimalOfType(wolf, "Rabbit");
|
||||||
|
if (targetRabbit) {
|
||||||
|
moveToward(wolf, targetRabbit.x, targetRabbit.y, 0.5);
|
||||||
|
if (distance(wolf.x, wolf.y, targetRabbit.x, targetRabbit.y) < 10) {
|
||||||
|
targetRabbit.dead = true;
|
||||||
|
wolf.hunger = Math.max(0, wolf.hunger - 30);
|
||||||
|
logAction("A wolf killed a rabbit!");
|
||||||
|
knockback(wolf, targetRabbit.x, targetRabbit.y, KNOCKBACK_DISTANCE);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) no rabbit => maybe attack a citizen if hunger > 60
|
||||||
|
if (wolf.hunger > 60) {
|
||||||
|
let targetHuman = findNearestCitizen(wolf);
|
||||||
|
if (targetHuman) {
|
||||||
|
moveToward(wolf, targetHuman.x, targetHuman.y, 0.5);
|
||||||
|
if (distance(wolf.x, wolf.y, targetHuman.x, targetHuman.y) < 10) {
|
||||||
|
if (targetHuman.hasWeapon) {
|
||||||
|
wolf.dead = true;
|
||||||
|
logAction(`${targetHuman.name} defended against a wolf with a weapon! Wolf died.`);
|
||||||
|
} else {
|
||||||
|
logAction(`A wolf killed ${targetHuman.name}!`);
|
||||||
|
citizens.splice(citizens.indexOf(targetHuman), 1);
|
||||||
|
}
|
||||||
|
knockback(wolf, targetHuman.x, targetHuman.y, KNOCKBACK_DISTANCE);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
randomAnimalWander(wolf);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
randomAnimalWander(wolf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function spawnBabyAnimal(type, x, y) {
|
||||||
|
const nx = x + randInt(-20, 20);
|
||||||
|
const ny = y + randInt(-20, 20);
|
||||||
|
const baby = createAnimal(type, nx, ny);
|
||||||
|
animals.push(baby);
|
||||||
|
logAction(`A new baby ${type} is born!`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function knockback(entity, tx, ty, dist) {
|
||||||
|
const dx = entity.x - tx;
|
||||||
|
const dy = entity.y - ty;
|
||||||
|
const length = Math.sqrt(dx*dx + dy*dy);
|
||||||
|
if (length > 0.1) {
|
||||||
|
entity.x += (dx / length) * dist;
|
||||||
|
entity.y += (dy / length) * dist;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**********************************************************************
|
||||||
|
* FINDERS
|
||||||
|
**********************************************************************/
|
||||||
|
function findNearestResourceOfType(ref, rtype) {
|
||||||
|
let nearest = null;
|
||||||
|
let minDist = Infinity;
|
||||||
|
for (const res of resources) {
|
||||||
|
if (res.type === rtype && res.amount > 0) {
|
||||||
|
const d = distance(ref.x, ref.y, res.x, res.y);
|
||||||
|
if (d < minDist) {
|
||||||
|
minDist = d;
|
||||||
|
nearest = res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nearest;
|
||||||
|
}
|
||||||
|
|
||||||
|
function findNearestAnimalOfType(wolf, targetType) {
|
||||||
|
let nearest = null;
|
||||||
|
let minDist = Infinity;
|
||||||
|
for (const a of animals) {
|
||||||
|
if (a !== wolf && !a.dead && a.type === targetType) {
|
||||||
|
const d = distance(wolf.x, wolf.y, a.x, a.y);
|
||||||
|
if (d < minDist) {
|
||||||
|
minDist = d;
|
||||||
|
nearest = a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nearest;
|
||||||
|
}
|
||||||
|
|
||||||
|
function findNearestCitizen(wolf) {
|
||||||
|
let nearest = null;
|
||||||
|
let minDist = Infinity;
|
||||||
|
for (const c of citizens) {
|
||||||
|
const d = distance(wolf.x, wolf.y, c.x, c.y);
|
||||||
|
if (d < minDist) {
|
||||||
|
minDist = d;
|
||||||
|
nearest = c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nearest;
|
||||||
|
}
|
||||||
|
|
||||||
|
function findHouseWithFruit() {
|
||||||
|
return buildings.find(b => b.buildingType === "House" && b.completed && b.storedFruit > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function findAnyCompletedHouse() {
|
||||||
|
return buildings.find(b => b.buildingType === "House" && b.completed);
|
||||||
|
}
|
||||||
|
|
||||||
|
function findHouseNeedingFruit() {
|
||||||
|
return buildings.find(b => b.buildingType === "House" && b.completed && b.storedFruit < b.maxFruit);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**********************************************************************
|
||||||
|
* CITIZEN TASKS
|
||||||
**********************************************************************/
|
**********************************************************************/
|
||||||
function assignNewTask(cit) {
|
function assignNewTask(cit) {
|
||||||
// If hunger too high, eat if possible
|
|
||||||
if (cit.hunger >= HUNGER_THRESHOLD) {
|
if (cit.hunger >= HUNGER_THRESHOLD) {
|
||||||
const houseWithFruit = findHouseWithFruit();
|
const houseWithFruit = findHouseWithFruit();
|
||||||
if (houseWithFruit) {
|
if (houseWithFruit) {
|
||||||
@ -364,8 +583,6 @@ function assignNewTask(cit) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If energy too low, rest at any completed house
|
|
||||||
if (cit.energy <= ENERGY_THRESHOLD) {
|
if (cit.energy <= ENERGY_THRESHOLD) {
|
||||||
const completedHouse = findAnyCompletedHouse();
|
const completedHouse = findAnyCompletedHouse();
|
||||||
if (completedHouse) {
|
if (completedHouse) {
|
||||||
@ -374,8 +591,18 @@ function assignNewTask(cit) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// If builder & no weapon => craft if enough wood
|
||||||
// Profession-based
|
if (cit.profession === "Builder" && !cit.hasWeapon) {
|
||||||
|
if (cityStorage.wood >= WEAPON_WOOD_COST) {
|
||||||
|
cityStorage.wood -= WEAPON_WOOD_COST;
|
||||||
|
cit.hasWeapon = true;
|
||||||
|
logAction(`${cit.name} crafted a wooden weapon for defense!`);
|
||||||
|
cit.task = null;
|
||||||
|
cit.target = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Profession tasks
|
||||||
if (cit.profession === "Builder") {
|
if (cit.profession === "Builder") {
|
||||||
builderTasks(cit);
|
builderTasks(cit);
|
||||||
} else {
|
} else {
|
||||||
@ -384,16 +611,13 @@ function assignNewTask(cit) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function builderTasks(cit) {
|
function builderTasks(cit) {
|
||||||
// Find building needing wood
|
|
||||||
const buildingNeedingWood = buildings.find(b => !b.completed && b.deliveredWood < b.requiredWood);
|
const buildingNeedingWood = buildings.find(b => !b.completed && b.deliveredWood < b.requiredWood);
|
||||||
if (buildingNeedingWood) {
|
if (buildingNeedingWood) {
|
||||||
// If carrying wood, deliver
|
|
||||||
if (cit.carryingWood > 0) {
|
if (cit.carryingWood > 0) {
|
||||||
cit.task = 'deliverWood';
|
cit.task = 'deliverWood';
|
||||||
cit.target = buildingNeedingWood;
|
cit.target = buildingNeedingWood;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Otherwise chop wood
|
|
||||||
const tree = findNearestResourceOfType(cit, "Tree");
|
const tree = findNearestResourceOfType(cit, "Tree");
|
||||||
if (tree) {
|
if (tree) {
|
||||||
cit.task = 'chop';
|
cit.task = 'chop';
|
||||||
@ -401,30 +625,23 @@ function builderTasks(cit) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there's a building with enough wood but not finished, build
|
|
||||||
const buildingToConstruct = buildings.find(b => !b.completed && b.deliveredWood >= b.requiredWood);
|
const buildingToConstruct = buildings.find(b => !b.completed && b.deliveredWood >= b.requiredWood);
|
||||||
if (buildingToConstruct) {
|
if (buildingToConstruct) {
|
||||||
cit.task = 'build';
|
cit.task = 'build';
|
||||||
cit.target = buildingToConstruct;
|
cit.target = buildingToConstruct;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise chop wood (to store)
|
|
||||||
const anyTree = findNearestResourceOfType(cit, "Tree");
|
const anyTree = findNearestResourceOfType(cit, "Tree");
|
||||||
if (anyTree) {
|
if (anyTree) {
|
||||||
cit.task = 'chop';
|
cit.task = 'chop';
|
||||||
cit.target = anyTree;
|
cit.target = anyTree;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Idle
|
|
||||||
cit.task = null;
|
cit.task = null;
|
||||||
cit.target = null;
|
cit.target = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function farmerTasks(cit) {
|
function farmerTasks(cit) {
|
||||||
// If carrying fruit, try delivering to a house
|
|
||||||
if (cit.carryingFruit > 0) {
|
if (cit.carryingFruit > 0) {
|
||||||
const houseNeedingFruit = findHouseNeedingFruit();
|
const houseNeedingFruit = findHouseNeedingFruit();
|
||||||
if (houseNeedingFruit) {
|
if (houseNeedingFruit) {
|
||||||
@ -433,30 +650,21 @@ function farmerTasks(cit) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If no fruit in hand, gather fruit
|
|
||||||
const fruitTree = findNearestResourceOfType(cit, "FruitTree");
|
const fruitTree = findNearestResourceOfType(cit, "FruitTree");
|
||||||
if (fruitTree && fruitTree.amount > 0) {
|
if (fruitTree && fruitTree.amount > 0) {
|
||||||
cit.task = 'gatherFruit';
|
cit.task = 'gatherFruit';
|
||||||
cit.target = fruitTree;
|
cit.target = fruitTree;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Occasionally plant a new fruit tree if carrying fruit
|
|
||||||
if (cit.carryingFruit >= FRUIT_PLANT_COST && Math.random() < 0.1) {
|
if (cit.carryingFruit >= FRUIT_PLANT_COST && Math.random() < 0.1) {
|
||||||
cit.task = 'plantFruitTree';
|
cit.task = 'plantFruitTree';
|
||||||
cit.target = null;
|
cit.target = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Idle
|
|
||||||
cit.task = null;
|
cit.task = null;
|
||||||
cit.target = null;
|
cit.target = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**********************************************************************
|
|
||||||
* TASK HANDLERS
|
|
||||||
**********************************************************************/
|
|
||||||
function chopTask(cit) {
|
function chopTask(cit) {
|
||||||
const tree = cit.target;
|
const tree = cit.target;
|
||||||
if (!tree || tree.amount <= 0) {
|
if (!tree || tree.amount <= 0) {
|
||||||
@ -509,7 +717,6 @@ function buildTask(cit) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
moveToward(cit, b.x, b.y, 0.3);
|
moveToward(cit, b.x, b.y, 0.3);
|
||||||
// Building progress handled globally
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function gatherFruitTask(cit) {
|
function gatherFruitTask(cit) {
|
||||||
@ -544,7 +751,6 @@ function deliverFruitTask(cit) {
|
|||||||
}
|
}
|
||||||
moveToward(cit, house.x, house.y, 0.4);
|
moveToward(cit, house.x, house.y, 0.4);
|
||||||
if (distance(cit.x, cit.y, house.x, house.y) < 20) {
|
if (distance(cit.x, cit.y, house.x, house.y) < 20) {
|
||||||
// Deliver fruit
|
|
||||||
const space = house.maxFruit - house.storedFruit;
|
const space = house.maxFruit - house.storedFruit;
|
||||||
if (space > 0 && cit.carryingFruit > 0) {
|
if (space > 0 && cit.carryingFruit > 0) {
|
||||||
const toDeliver = Math.min(cit.carryingFruit, space);
|
const toDeliver = Math.min(cit.carryingFruit, space);
|
||||||
@ -558,10 +764,8 @@ function deliverFruitTask(cit) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function plantFruitTreeTask(cit) {
|
function plantFruitTreeTask(cit) {
|
||||||
// Plant near the citizen
|
|
||||||
const px = cit.x + randInt(-50, 50);
|
const px = cit.x + randInt(-50, 50);
|
||||||
const py = cit.y + randInt(-50, 50);
|
const py = cit.y + randInt(-50, 50);
|
||||||
|
|
||||||
moveToward(cit, px, py, 0.4);
|
moveToward(cit, px, py, 0.4);
|
||||||
if (distance(cit.x, cit.y, px, py) < 10) {
|
if (distance(cit.x, cit.y, px, py) < 10) {
|
||||||
if (cit.carryingFruit >= FRUIT_PLANT_COST) {
|
if (cit.carryingFruit >= FRUIT_PLANT_COST) {
|
||||||
@ -583,7 +787,6 @@ function eatAtHouseTask(cit) {
|
|||||||
}
|
}
|
||||||
moveToward(cit, house.x, house.y, 0.4);
|
moveToward(cit, house.x, house.y, 0.4);
|
||||||
if (distance(cit.x, cit.y, house.x, house.y) < 20) {
|
if (distance(cit.x, cit.y, house.x, house.y) < 20) {
|
||||||
// Eat some fruit
|
|
||||||
if (house.storedFruit > 0 && cit.hunger > 0) {
|
if (house.storedFruit > 0 && cit.hunger > 0) {
|
||||||
const amountToEat = 10;
|
const amountToEat = 10;
|
||||||
const eaten = Math.min(amountToEat, house.storedFruit);
|
const eaten = Math.min(amountToEat, house.storedFruit);
|
||||||
@ -606,7 +809,6 @@ function restAtHouseTask(cit) {
|
|||||||
}
|
}
|
||||||
moveToward(cit, house.x, house.y, 0.4);
|
moveToward(cit, house.x, house.y, 0.4);
|
||||||
if (distance(cit.x, cit.y, house.x, house.y) < 20) {
|
if (distance(cit.x, cit.y, house.x, house.y) < 20) {
|
||||||
// Gains energy passively
|
|
||||||
if (cit.energy >= ENERGY_MAX - 1) {
|
if (cit.energy >= ENERGY_MAX - 1) {
|
||||||
cit.energy = ENERGY_MAX;
|
cit.energy = ENERGY_MAX;
|
||||||
cit.task = null;
|
cit.task = null;
|
||||||
@ -615,88 +817,36 @@ function restAtHouseTask(cit) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**********************************************************************
|
|
||||||
* ROAD-BUILDING LOGIC
|
|
||||||
**********************************************************************/
|
|
||||||
/**
|
|
||||||
* When a new house is completed, we build a road from it
|
|
||||||
* to the nearest existing house (if one exists).
|
|
||||||
*/
|
|
||||||
function maybeBuildRoad(newHouse) {
|
|
||||||
// Find the nearest completed house (other than the new one)
|
|
||||||
const otherHouses = buildings.filter(b => b.buildingType === "House" && b.completed && b !== newHouse);
|
|
||||||
if (otherHouses.length === 0) return; // no other house to connect
|
|
||||||
|
|
||||||
let nearest = null;
|
|
||||||
let minDist = Infinity;
|
|
||||||
otherHouses.forEach((oh) => {
|
|
||||||
const d = distance(newHouse.x, newHouse.y, oh.x, oh.y);
|
|
||||||
if (d < minDist) {
|
|
||||||
minDist = d;
|
|
||||||
nearest = oh;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!nearest) return;
|
|
||||||
// Place a road site
|
|
||||||
const roadSite = createRoadSite(newHouse.x, newHouse.y, nearest.x, nearest.y);
|
|
||||||
buildings.push(roadSite);
|
|
||||||
logAction(`A Road site created between houses at (${newHouse.x}, ${newHouse.y}) and (${nearest.x}, ${nearest.y}).`);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**********************************************************************
|
|
||||||
* FIND BUILDINGS OR RESOURCES
|
|
||||||
**********************************************************************/
|
|
||||||
function findNearestResourceOfType(cit, rtype) {
|
|
||||||
let nearest = null;
|
|
||||||
let nearestDist = Infinity;
|
|
||||||
for (const res of resources) {
|
|
||||||
if (res.type === rtype && res.amount > 0) {
|
|
||||||
const d = distance(cit.x, cit.y, res.x, res.y);
|
|
||||||
if (d < nearestDist) {
|
|
||||||
nearestDist = d;
|
|
||||||
nearest = res;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nearest;
|
|
||||||
}
|
|
||||||
|
|
||||||
function findHouseWithFruit() {
|
|
||||||
return buildings.find(b => b.buildingType === "House" && b.completed && b.storedFruit > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
function findAnyCompletedHouse() {
|
|
||||||
return buildings.find(b => b.buildingType === "House" && b.completed);
|
|
||||||
}
|
|
||||||
|
|
||||||
function findHouseNeedingFruit() {
|
|
||||||
return buildings.find(b => b.buildingType === "House" && b.completed && b.storedFruit < b.maxFruit);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**********************************************************************
|
/**********************************************************************
|
||||||
* RANDOM WANDER
|
* RANDOM WANDER
|
||||||
**********************************************************************/
|
**********************************************************************/
|
||||||
function randomWander(cit) {
|
function randomWander(cit) {
|
||||||
if (Math.random() < 0.01) {
|
if (Math.random() < 0.01) {
|
||||||
cit.vx = (Math.random() - 0.5) * 0.5;
|
cit.vx = (Math.random() - 0.5) * 0.3;
|
||||||
cit.vy = (Math.random() - 0.5) * 0.5;
|
cit.vy = (Math.random() - 0.5) * 0.3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function randomAnimalWander(ani) {
|
||||||
|
if (Math.random() < 0.01) {
|
||||||
|
ani.vx = (Math.random() - 0.5) * 0.4;
|
||||||
|
ani.vy = (Math.random() - 0.5) * 0.4;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**********************************************************************
|
/**********************************************************************
|
||||||
* MOVEMENT UTILS
|
* MOVEMENT UTILS
|
||||||
**********************************************************************/
|
**********************************************************************/
|
||||||
function moveToward(cit, tx, ty, speed = 0.4) {
|
function moveToward(obj, tx, ty, speed = 0.4) {
|
||||||
const dx = tx - cit.x;
|
const dx = tx - obj.x;
|
||||||
const dy = ty - cit.y;
|
const dy = ty - obj.y;
|
||||||
const dist = Math.sqrt(dx*dx + dy*dy);
|
const dist = Math.sqrt(dx*dx + dy*dy);
|
||||||
if (dist > 1) {
|
if (dist > 1) {
|
||||||
cit.vx = (dx / dist) * speed;
|
obj.vx = (dx / dist) * speed;
|
||||||
cit.vy = (dy / dist) * speed;
|
obj.vy = (dy / dist) * speed;
|
||||||
} else {
|
} else {
|
||||||
cit.vx = 0;
|
obj.vx = 0;
|
||||||
cit.vy = 0;
|
obj.vy = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -706,10 +856,29 @@ function distance(x1, y1, x2, y2) {
|
|||||||
return Math.sqrt(dx*dx + dy*dy);
|
return Math.sqrt(dx*dx + dy*dy);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**********************************************************************
|
||||||
|
* ROAD-BUILDING WHEN HOUSE IS COMPLETED
|
||||||
|
**********************************************************************/
|
||||||
|
function maybeBuildRoad(newHouse) {
|
||||||
|
const otherHouses = buildings.filter(b => b.buildingType === "House" && b.completed && b !== newHouse);
|
||||||
|
if (otherHouses.length === 0) return;
|
||||||
|
let nearest = null;
|
||||||
|
let minDist = Infinity;
|
||||||
|
for (const oh of otherHouses) {
|
||||||
|
const d = distance(newHouse.x, newHouse.y, oh.x, oh.y);
|
||||||
|
if (d < minDist) {
|
||||||
|
minDist = d;
|
||||||
|
nearest = oh;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!nearest) return;
|
||||||
|
const roadSite = createRoadSite(newHouse.x, newHouse.y, nearest.x, nearest.y);
|
||||||
|
buildings.push(roadSite);
|
||||||
|
logAction(`A Road site created between houses at (${newHouse.x}, ${newHouse.y}) and (${nearest.x}, ${nearest.y}).`);
|
||||||
|
}
|
||||||
|
|
||||||
/**********************************************************************
|
/**********************************************************************
|
||||||
* AUTO-CREATION OF NEW HOUSE SITES
|
* AUTO-CREATION OF NEW HOUSE SITES
|
||||||
* Similar logic to previous examples: if cityStorage has enough wood,
|
|
||||||
* we place a new House site occasionally.
|
|
||||||
**********************************************************************/
|
**********************************************************************/
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
const underConstruction = buildings.find(b => b.buildingType === "House" && !b.completed);
|
const underConstruction = buildings.find(b => b.buildingType === "House" && !b.completed);
|
||||||
@ -724,11 +893,10 @@ setInterval(() => {
|
|||||||
}, 5000);
|
}, 5000);
|
||||||
|
|
||||||
/**********************************************************************
|
/**********************************************************************
|
||||||
* DEPOSIT WOOD IN CITY STORAGE IF IDLE BUILDER
|
* DEPOSIT WOOD LOGIC
|
||||||
**********************************************************************/
|
**********************************************************************/
|
||||||
const originalAssignNewTask = assignNewTask;
|
const originalAssignNewTask = assignNewTask;
|
||||||
assignNewTask = function(cit) {
|
assignNewTask = function(cit) {
|
||||||
// If builder carrying wood, but no building needs it, deposit to city center
|
|
||||||
if (cit.profession === "Builder") {
|
if (cit.profession === "Builder") {
|
||||||
const buildingNeedingWood = buildings.find(b => !b.completed && b.deliveredWood < b.requiredWood);
|
const buildingNeedingWood = buildings.find(b => !b.completed && b.deliveredWood < b.requiredWood);
|
||||||
if (cit.carryingWood > 0 && !buildingNeedingWood) {
|
if (cit.carryingWood > 0 && !buildingNeedingWood) {
|
||||||
@ -738,7 +906,6 @@ assignNewTask = function(cit) {
|
|||||||
logAction(`${cit.name} deposited ${cit.carryingWood} wood into city storage.`);
|
logAction(`${cit.name} deposited ${cit.carryingWood} wood into city storage.`);
|
||||||
cit.carryingWood = 0;
|
cit.carryingWood = 0;
|
||||||
} else {
|
} else {
|
||||||
// Move to city center
|
|
||||||
moveToward(cit, 0, 0, 0.4);
|
moveToward(cit, 0, 0, 0.4);
|
||||||
cit.task = null;
|
cit.task = null;
|
||||||
cit.target = null;
|
cit.target = null;
|
||||||
@ -755,29 +922,30 @@ assignNewTask = function(cit) {
|
|||||||
function drawWorld() {
|
function drawWorld() {
|
||||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
|
|
||||||
// Draw grid
|
// Grid
|
||||||
drawGrid();
|
drawGrid();
|
||||||
|
|
||||||
// Draw resources
|
// Resources
|
||||||
resources.forEach((res) => drawResource(res));
|
resources.forEach((res) => drawResource(res));
|
||||||
|
|
||||||
// Draw roads first (both completed or under construction)
|
// Roads
|
||||||
buildings
|
buildings
|
||||||
.filter(b => b.buildingType === "Road")
|
.filter(b => b.buildingType === "Road")
|
||||||
.forEach(b => drawRoad(b));
|
.forEach(b => drawRoad(b));
|
||||||
|
|
||||||
// Draw houses
|
// Houses
|
||||||
buildings
|
buildings
|
||||||
.filter(b => b.buildingType === "House")
|
.filter(b => b.buildingType === "House")
|
||||||
.forEach(b => drawHouse(b));
|
.forEach(b => drawHouse(b));
|
||||||
|
|
||||||
// Draw city storage info
|
// City storage
|
||||||
drawCityStorage();
|
drawCityStorage();
|
||||||
|
|
||||||
// Draw citizens
|
// Citizens
|
||||||
citizens.forEach((cit) => {
|
citizens.forEach((cit) => drawCitizen(cit));
|
||||||
drawCitizen(cit);
|
|
||||||
});
|
// Animals
|
||||||
|
animals.forEach((ani) => drawAnimal(ani));
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawGrid() {
|
function drawGrid() {
|
||||||
@ -801,7 +969,6 @@ function drawGrid() {
|
|||||||
ctx.lineTo(range, y);
|
ctx.lineTo(range, y);
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -828,20 +995,15 @@ function drawResource(res) {
|
|||||||
ctx.font = "12px sans-serif";
|
ctx.font = "12px sans-serif";
|
||||||
ctx.fillText(`Fruit (${res.amount})`, res.x - 25, res.y - 12);
|
ctx.fillText(`Fruit (${res.amount})`, res.x - 25, res.y - 12);
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Draw a House building site or a completed House
|
|
||||||
*/
|
|
||||||
function drawHouse(b) {
|
function drawHouse(b) {
|
||||||
ctx.save();
|
ctx.save();
|
||||||
ctx.translate(canvas.width / 2 + offsetX, canvas.height / 2 + offsetY);
|
ctx.translate(canvas.width / 2 + offsetX, canvas.height / 2 + offsetY);
|
||||||
ctx.scale(scale, scale);
|
ctx.scale(scale, scale);
|
||||||
|
|
||||||
if (!b.completed) {
|
if (!b.completed) {
|
||||||
// Construction site
|
|
||||||
ctx.strokeStyle = "#FF8C00";
|
ctx.strokeStyle = "#FF8C00";
|
||||||
ctx.lineWidth = 2 / scale;
|
ctx.lineWidth = 2 / scale;
|
||||||
ctx.strokeRect(b.x - 15, b.y - 15, 30, 30);
|
ctx.strokeRect(b.x - 15, b.y - 15, 30, 30);
|
||||||
@ -852,7 +1014,6 @@ function drawHouse(b) {
|
|||||||
ctx.fillText(`Wood: ${b.deliveredWood}/${b.requiredWood}`, b.x - 30, b.y + 30);
|
ctx.fillText(`Wood: ${b.deliveredWood}/${b.requiredWood}`, b.x - 30, b.y + 30);
|
||||||
ctx.fillText(`Progress: ${Math.floor(b.buildProgress)}%`, b.x - 30, b.y + 42);
|
ctx.fillText(`Progress: ${Math.floor(b.buildProgress)}%`, b.x - 30, b.y + 42);
|
||||||
} else {
|
} else {
|
||||||
// Completed
|
|
||||||
ctx.fillStyle = "#DAA520";
|
ctx.fillStyle = "#DAA520";
|
||||||
ctx.fillRect(b.x - 15, b.y - 15, 30, 30);
|
ctx.fillRect(b.x - 15, b.y - 15, 30, 30);
|
||||||
|
|
||||||
@ -865,17 +1026,13 @@ function drawHouse(b) {
|
|||||||
ctx.restore();
|
ctx.restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Draw a Road building site or a completed Road
|
|
||||||
*/
|
|
||||||
function drawRoad(b) {
|
function drawRoad(b) {
|
||||||
ctx.save();
|
ctx.save();
|
||||||
ctx.translate(canvas.width / 2 + offsetX, canvas.height / 2 + offsetY);
|
ctx.translate(canvas.width / 2 + offsetX, canvas.height / 2 + offsetY);
|
||||||
ctx.scale(scale, scale);
|
ctx.scale(scale, scale);
|
||||||
|
|
||||||
// Draw line between (b.x1, b.y1) and (b.x2, b.y2)
|
|
||||||
if (!b.completed) {
|
if (!b.completed) {
|
||||||
ctx.setLineDash([5, 5]); // dashed if under construction
|
ctx.setLineDash([5, 5]);
|
||||||
ctx.strokeStyle = "#888";
|
ctx.strokeStyle = "#888";
|
||||||
} else {
|
} else {
|
||||||
ctx.setLineDash([]);
|
ctx.setLineDash([]);
|
||||||
@ -888,7 +1045,6 @@ function drawRoad(b) {
|
|||||||
ctx.lineTo(b.x2, b.y2);
|
ctx.lineTo(b.x2, b.y2);
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
|
|
||||||
// Draw small label near midpoint
|
|
||||||
ctx.fillStyle = "#000";
|
ctx.fillStyle = "#000";
|
||||||
ctx.font = "12px sans-serif";
|
ctx.font = "12px sans-serif";
|
||||||
if (!b.completed) {
|
if (!b.completed) {
|
||||||
@ -897,13 +1053,9 @@ function drawRoad(b) {
|
|||||||
} else {
|
} else {
|
||||||
ctx.fillText(`Road`, b.x - 10, b.y - 5);
|
ctx.fillText(`Road`, b.x - 10, b.y - 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Show city storage
|
|
||||||
*/
|
|
||||||
function drawCityStorage() {
|
function drawCityStorage() {
|
||||||
ctx.save();
|
ctx.save();
|
||||||
ctx.fillStyle = "#000";
|
ctx.fillStyle = "#000";
|
||||||
@ -917,17 +1069,39 @@ function drawCitizen(cit) {
|
|||||||
ctx.translate(canvas.width / 2 + offsetX, canvas.height / 2 + offsetY);
|
ctx.translate(canvas.width / 2 + offsetX, canvas.height / 2 + offsetY);
|
||||||
ctx.scale(scale, scale);
|
ctx.scale(scale, scale);
|
||||||
|
|
||||||
// Citizen circle
|
|
||||||
ctx.fillStyle = cit.color;
|
ctx.fillStyle = cit.color;
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.arc(cit.x, cit.y, 7, 0, Math.PI * 2);
|
ctx.arc(cit.x, cit.y, 7, 0, Math.PI * 2);
|
||||||
ctx.fill();
|
ctx.fill();
|
||||||
|
|
||||||
// Show name, profession, carrying, hunger, energy
|
|
||||||
ctx.fillStyle = "#000";
|
ctx.fillStyle = "#000";
|
||||||
ctx.font = "10px sans-serif";
|
ctx.font = "10px sans-serif";
|
||||||
ctx.fillText(`${cit.name} [${cit.profession}]`, cit.x + 10, cit.y - 2);
|
const wpn = cit.hasWeapon ? "ARMED" : "";
|
||||||
ctx.fillText(`W:${cit.carryingWood} F:${cit.carryingFruit} H:${Math.floor(cit.hunger)} E:${Math.floor(cit.energy)}`, cit.x + 10, cit.y + 10);
|
ctx.fillText(`${cit.name} [${cit.profession}] ${wpn}`, cit.x + 10, cit.y - 2);
|
||||||
|
ctx.fillText(`W:${cit.carryingWood} F:${cit.carryingFruit} H:${Math.floor(cit.hunger)} E:${Math.floor(cit.energy)}`,
|
||||||
|
cit.x + 10, cit.y + 10);
|
||||||
|
|
||||||
|
ctx.restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawAnimal(ani) {
|
||||||
|
if (ani.dead) return;
|
||||||
|
ctx.save();
|
||||||
|
ctx.translate(canvas.width / 2 + offsetX, canvas.height / 2 + offsetY);
|
||||||
|
ctx.scale(scale, scale);
|
||||||
|
|
||||||
|
if (ani.type === "Rabbit") {
|
||||||
|
ctx.fillStyle = "#999";
|
||||||
|
} else {
|
||||||
|
ctx.fillStyle = "#555";
|
||||||
|
}
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(ani.x, ani.y, 6, 0, Math.PI * 2);
|
||||||
|
ctx.fill();
|
||||||
|
|
||||||
|
ctx.fillStyle = "#000";
|
||||||
|
ctx.font = "10px sans-serif";
|
||||||
|
ctx.fillText(ani.type, ani.x + 8, ani.y + 3);
|
||||||
|
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user