1318 lines
35 KiB
HTML
1318 lines
35 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<title>Virtual World</title>
|
|
<style>
|
|
/* Futuristic / fun styling */
|
|
|
|
/* A gentle gradient background with animation */
|
|
body {
|
|
margin: 0;
|
|
padding: 0;
|
|
font-family: 'Trebuchet MS', sans-serif;
|
|
background: linear-gradient(120deg, #0ff, #f0f);
|
|
background-size: 400% 400%;
|
|
animation: gradientShift 10s ease infinite alternate;
|
|
}
|
|
|
|
@keyframes gradientShift {
|
|
0% { background-position: 0% 50%; }
|
|
100% { background-position: 100% 50%; }
|
|
}
|
|
|
|
/* Container for everything */
|
|
#app {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
padding: 10px;
|
|
position: relative;
|
|
z-index: 1; /* so it stays above the gradient */
|
|
}
|
|
|
|
/* Info bar top-left */
|
|
#infoBar {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
margin: 8px;
|
|
padding: 5px 10px;
|
|
background: rgba(255, 255, 255, 0.7);
|
|
border: 1px solid #888;
|
|
border-radius: 6px;
|
|
font-size: 14px;
|
|
z-index: 10;
|
|
transition: box-shadow 0.3s;
|
|
}
|
|
#infoBar:hover {
|
|
box-shadow: 0 0 10px rgba(255,255,255,0.8);
|
|
}
|
|
|
|
#menu {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 10px;
|
|
margin: 8px 0;
|
|
justify-content: center;
|
|
}
|
|
|
|
.menu-button {
|
|
padding: 6px 12px;
|
|
background: #fff;
|
|
border: 2px solid #444;
|
|
cursor: pointer;
|
|
border-radius: 4px;
|
|
transition: transform 0.25s ease, background-color 0.25s ease;
|
|
}
|
|
.menu-button:hover {
|
|
transform: scale(1.08);
|
|
background-color: #c8fffa;
|
|
}
|
|
|
|
#toggleLogsBtn {
|
|
margin-left: 10px;
|
|
}
|
|
|
|
canvas {
|
|
background: #ffffff;
|
|
border: 2px solid #444;
|
|
cursor: grab;
|
|
transition: box-shadow 0.3s;
|
|
}
|
|
canvas:active {
|
|
cursor: grabbing;
|
|
box-shadow: 0 0 10px rgba(0,0,0,0.3);
|
|
}
|
|
|
|
#log {
|
|
width: 800px;
|
|
height: 120px;
|
|
margin-top: 10px;
|
|
border: 2px solid #444;
|
|
background: #f9f9f9;
|
|
padding: 5px;
|
|
font-size: 0.9rem;
|
|
overflow-y: auto;
|
|
border-radius: 6px;
|
|
transition: box-shadow 0.3s;
|
|
}
|
|
#log:hover {
|
|
box-shadow: 0 0 6px rgba(0,0,0,0.15);
|
|
}
|
|
.log-entry {
|
|
margin: 2px 0;
|
|
}
|
|
|
|
/* Responsive adjustments */
|
|
@media (max-width: 850px) {
|
|
#log {
|
|
width: 95%;
|
|
height: 100px;
|
|
}
|
|
canvas {
|
|
width: 95% !important;
|
|
height: 400px !important;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<!-- Info Bar with money/citizen count -->
|
|
<div id="infoBar">
|
|
<p id="moneyDisplay">Money: $0</p>
|
|
<p id="citizenCount">Citizens: 0</p>
|
|
</div>
|
|
|
|
<div id="app">
|
|
<!-- Store Menu -->
|
|
<div id="menu">
|
|
<button class="menu-button" id="buyHouseBtn">Buy House ($300)</button>
|
|
<button class="menu-button" id="buyRoadBtn">Buy Road ($150)</button>
|
|
<button class="menu-button" id="buyBuilderBtn">Buy Builder ($100)</button>
|
|
<button class="menu-button" id="buyFarmerBtn">Buy Farmer ($100)</button>
|
|
<button class="menu-button" id="buySpawnerBtn">Buy Spawner ($500)</button>
|
|
<button class="menu-button" id="buyTreeBtn">Buy Tree ($50)</button>
|
|
<button class="menu-button" id="toggleLogsBtn">Hide Logs</button>
|
|
</div>
|
|
|
|
<canvas id="worldCanvas" width="800" height="600"></canvas>
|
|
<div id="log"></div>
|
|
</div>
|
|
|
|
<script>
|
|
/*
|
|
Virtual World with advanced AI (Farmers/Builders), Wolves & Rabbits,
|
|
user can buy House, Road, Citizens, Spawner, Tree, logs are togglable,
|
|
user starts with 1 builder, plus a new citizen every 3000 frames.
|
|
Fix "spawnBabyAnimal is not defined" by ensuring we define it properly.
|
|
*/
|
|
|
|
/**********************************************************************
|
|
* GLOBALS & STRUCTURES
|
|
**********************************************************************/
|
|
const canvas = document.getElementById('worldCanvas');
|
|
const ctx = canvas.getContext('2d');
|
|
const logContainer = document.getElementById('log');
|
|
const moneyDisplay = document.getElementById('moneyDisplay');
|
|
const citizenCountDisplay = document.getElementById('citizenCount');
|
|
|
|
// Pan & Zoom
|
|
let offsetX = 0;
|
|
let offsetY = 0;
|
|
let scale = 1.0;
|
|
let isDragging = false;
|
|
let lastMouseX = 0;
|
|
let lastMouseY = 0;
|
|
|
|
// Basic money system
|
|
let money = 5000; // Start with 500
|
|
let purchaseMode = null; // "House" or "Road" or "Builder" or "Farmer", etc.
|
|
|
|
// Frame counter
|
|
let frameCount = 0;
|
|
|
|
// Shared city storage (wood, etc.)
|
|
const cityStorage = {
|
|
wood: 0
|
|
};
|
|
|
|
// Arrays
|
|
let resources = []; // Trees, FruitTrees
|
|
let buildings = []; // House, Road, Spawner
|
|
let citizens = [];
|
|
let animals = [];
|
|
|
|
/**********************************************************************
|
|
* COSTS & EARNINGS
|
|
**********************************************************************/
|
|
const COST_HOUSE = 300;
|
|
const COST_ROAD = 150;
|
|
const COST_BUILDER = 100;
|
|
const COST_FARMER = 100;
|
|
const COST_SPAWNER = 500;
|
|
const COST_TREE = 50;
|
|
|
|
// Earn money
|
|
const REWARD_DELIVER_WOOD = 5;
|
|
const REWARD_BUILD_COMPLETE = 20;
|
|
|
|
/**********************************************************************
|
|
* STATS & CONSTANTS
|
|
**********************************************************************/
|
|
// Hunger & energy
|
|
const HUNGER_MAX = 100;
|
|
const ENERGY_MAX = 100;
|
|
const HUNGER_INCREMENT = 0.005;
|
|
const ENERGY_DECREMENT_WORK = 0.02;
|
|
const ENERGY_INCREMENT_REST = 0.05;
|
|
const HUNGER_THRESHOLD = 50;
|
|
const ENERGY_THRESHOLD = 30;
|
|
|
|
// Trees
|
|
const FRUIT_TREE_START_AMOUNT = 20;
|
|
const FRUIT_GATHER_RATE = 1;
|
|
const FRUIT_PLANT_COST = 1;
|
|
|
|
// House & Road
|
|
const HOUSE_WOOD_REQUIRED = 50;
|
|
const HOUSE_BUILD_RATE = 0.2;
|
|
const HOUSE_MAX_FRUIT = 30;
|
|
|
|
const ROAD_WOOD_REQUIRED = 10;
|
|
const ROAD_BUILD_RATE = 0.3;
|
|
|
|
// Professions
|
|
const PROFESSIONS = ["Farmer","Builder"];
|
|
const WEAPON_WOOD_COST = 10; // forging cost
|
|
|
|
// Wolves & Rabbits
|
|
const STARTING_RABBITS = 5;
|
|
const STARTING_WOLVES = 3;
|
|
const ANIMAL_HUNGER_MAX = 100;
|
|
const RABBIT_HUNGER_INCREMENT = 0.003;
|
|
const WOLF_HUNGER_INCREMENT = 0.006;
|
|
const RABBIT_REPRO_COOLDOWN = 3000;
|
|
const WOLF_REPRO_COOLDOWN = 5000;
|
|
const RABBIT_REPRO_CHANCE = 0.0005;
|
|
const WOLF_REPRO_CHANCE = 0.0003;
|
|
const KNOCKBACK_DISTANCE = 20;
|
|
|
|
/**********************************************************************
|
|
* LOGGING & MONEY
|
|
**********************************************************************/
|
|
function logAction(text) {
|
|
const entry = document.createElement('div');
|
|
entry.className='log-entry';
|
|
entry.textContent=text;
|
|
logContainer.appendChild(entry);
|
|
logContainer.scrollTop=logContainer.scrollHeight;
|
|
}
|
|
function addMoney(amount, reason="") {
|
|
money += amount;
|
|
if(money<0) money=0;
|
|
updateMoneyDisplay();
|
|
if(reason){
|
|
logAction(`Money +$${amount} from ${reason}. Total=$${money}`);
|
|
}
|
|
}
|
|
function updateMoneyDisplay(){
|
|
moneyDisplay.textContent=`Money: $${money}`;
|
|
citizenCountDisplay.textContent=`Citizens: ${citizens.length}`;
|
|
}
|
|
|
|
/**********************************************************************
|
|
* RANDOM UTILS
|
|
**********************************************************************/
|
|
const firstNames = ["Al","Bea","Cal","Dee","Eve","Fay","Gil","Hal","Ian","Joy",
|
|
"Kay","Lee","Max","Ned","Oda","Pam","Ray","Sue","Tim","Ula",
|
|
"Vic","Wyn","Xan","Yel","Zed"];
|
|
const lastNames = ["Apple","Berry","Cherry","Delta","Echo","Flint","Gran","Hills",
|
|
"Iris","Jones","Knight","Lemon","Myer","Noble","Olson","Prime",
|
|
"Quartz","Row","Smith","Turn","Umbra","Vale","Wick","Xeno","Yolk","Zoom"];
|
|
|
|
function randomName() {
|
|
const f=firstNames[Math.floor(Math.random()*firstNames.length)];
|
|
const l=lastNames[Math.floor(Math.random()*lastNames.length)];
|
|
return `${f} ${l}`;
|
|
}
|
|
function randInt(min,max){
|
|
return Math.floor(Math.random()*(max-min))+min;
|
|
}
|
|
|
|
/**********************************************************************
|
|
* ENTITY DEFINITIONS
|
|
**********************************************************************/
|
|
function createCitizen(name,x,y, forcedProfession=null) {
|
|
// If forcedProfession is "Builder"/"Farmer", override random
|
|
let profession = forcedProfession
|
|
? forcedProfession
|
|
: PROFESSIONS[Math.floor(Math.random()*PROFESSIONS.length)];
|
|
|
|
return {
|
|
name,
|
|
profession,
|
|
x,y,
|
|
vx:(Math.random()-0.5)*0.3,
|
|
vy:(Math.random()-0.5)*0.3,
|
|
color:`hsl(${Math.random()*360},70%,50%)`,
|
|
task:null,
|
|
target:null,
|
|
carryingWood:0,
|
|
carryingFruit:0,
|
|
carryingCapacity:10,
|
|
hunger:0,
|
|
energy:ENERGY_MAX,
|
|
hasWeapon:false
|
|
};
|
|
}
|
|
|
|
function createResource(type,x,y,amount){
|
|
return { type, x, y, amount };
|
|
}
|
|
|
|
function createHouseSite(x,y){
|
|
return {
|
|
buildingType:"House",
|
|
x,y,
|
|
requiredWood:HOUSE_WOOD_REQUIRED,
|
|
deliveredWood:0,
|
|
buildProgress:0,
|
|
completed:false,
|
|
storedFruit:0,
|
|
maxFruit:HOUSE_MAX_FRUIT
|
|
};
|
|
}
|
|
|
|
function createRoadSite(x1,y1,x2,y2){
|
|
const mx=(x1+x2)/2;
|
|
const my=(y1+y2)/2;
|
|
return {
|
|
buildingType:"Road",
|
|
x:mx,
|
|
y:my,
|
|
x1,y1,x2,y2,
|
|
requiredWood:ROAD_WOOD_REQUIRED,
|
|
deliveredWood:0,
|
|
buildProgress:0,
|
|
completed:false
|
|
};
|
|
}
|
|
|
|
function createAnimal(type,x,y){
|
|
let cd=(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:cd
|
|
};
|
|
}
|
|
|
|
/**********************************************************************
|
|
* SPAWN BABY ANIMAL (to fix ReferenceError)
|
|
**********************************************************************/
|
|
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!`);
|
|
}
|
|
|
|
/**********************************************************************
|
|
* INIT WORLD
|
|
**********************************************************************/
|
|
function initWorld(){
|
|
// Normal trees
|
|
for(let i=0;i<15;i++){
|
|
resources.push(createResource("Tree", randInt(-1000,1000), randInt(-1000,1000),100));
|
|
}
|
|
// Fruit trees
|
|
for(let i=0;i<10;i++){
|
|
resources.push(createResource("FruitTree", randInt(-1000,1000), randInt(-1000,1000), FRUIT_TREE_START_AMOUNT));
|
|
}
|
|
|
|
// Start with 1 citizen => always a "Builder"
|
|
let c=createCitizen(randomName(), randInt(-200,200), randInt(-200,200), "Builder");
|
|
citizens.push(c);
|
|
logAction(`Initial Citizen joined: ${c.name} [Builder]`);
|
|
|
|
// Spawn some rabbits & wolves
|
|
for(let i=0;i<STARTING_RABBITS;i++){
|
|
animals.push(createAnimal("Rabbit", randInt(-1000,1000), randInt(-1000,1000)));
|
|
}
|
|
for(let i=0;i<STARTING_WOLVES;i++){
|
|
animals.push(createAnimal("Wolf", randInt(500,1000), randInt(500,1000)));
|
|
}
|
|
|
|
requestAnimationFrame(update);
|
|
}
|
|
|
|
/**********************************************************************
|
|
* UPDATE LOOP
|
|
**********************************************************************/
|
|
function update(){
|
|
frameCount++;
|
|
|
|
// Citizens
|
|
citizens.forEach((cit)=>updateCitizen(cit));
|
|
|
|
// Animals
|
|
animals.forEach((ani)=>{
|
|
if(!ani.dead) updateAnimal(ani);
|
|
});
|
|
animals=animals.filter(a=>!a.dead);
|
|
|
|
// Automatic new citizen every 3000 frames
|
|
if(frameCount%3000===0){
|
|
let baby=createCitizen(randomName(), randInt(-200,200), randInt(-200,200));
|
|
citizens.push(baby);
|
|
logAction(`A new citizen is born: ${baby.name} [${baby.profession}]`);
|
|
}
|
|
|
|
// Buildings
|
|
buildings.forEach((b)=>{
|
|
if(!b.completed && b.deliveredWood>=b.requiredWood){
|
|
let buildRate=(b.buildingType==="Road")?ROAD_BUILD_RATE:HOUSE_BUILD_RATE;
|
|
b.buildProgress+=buildRate;
|
|
if(b.buildProgress>=100){
|
|
b.completed=true;
|
|
addMoney(REWARD_BUILD_COMPLETE, `Complete ${b.buildingType}`);
|
|
if(b.buildingType==="House"){
|
|
logAction(`A new House completed @(${b.x},${b.y})!`);
|
|
maybeBuildRoad(b);
|
|
} else if(b.buildingType==="Road"){
|
|
logAction(`A Road has been completed!`);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
drawWorld();
|
|
requestAnimationFrame(update);
|
|
}
|
|
|
|
/**********************************************************************
|
|
* CITIZEN AI
|
|
**********************************************************************/
|
|
function updateCitizen(cit){
|
|
cit.hunger+=HUNGER_INCREMENT;
|
|
if(cit.hunger>HUNGER_MAX) cit.hunger=HUNGER_MAX;
|
|
|
|
if(["chop","gatherFruit","build"].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;
|
|
|
|
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;
|
|
default: randomWander(cit); break;
|
|
}
|
|
|
|
cit.x+=cit.vx;
|
|
cit.y+=cit.vy;
|
|
}
|
|
|
|
/**********************************************************************
|
|
* ANIMAL AI
|
|
**********************************************************************/
|
|
function updateAnimal(ani){
|
|
if(ani.type==="Rabbit"){
|
|
updateRabbit(ani);
|
|
} else {
|
|
updateWolf(ani);
|
|
}
|
|
ani.x+=ani.vx;
|
|
ani.y+=ani.vy;
|
|
if(ani.reproductionCooldown>0){
|
|
ani.reproductionCooldown--;
|
|
}
|
|
}
|
|
|
|
function updateRabbit(r){
|
|
r.hunger+=RABBIT_HUNGER_INCREMENT;
|
|
if(r.hunger>=ANIMAL_HUNGER_MAX){
|
|
r.dead=true;
|
|
logAction("A rabbit starved to death.");
|
|
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);
|
|
}
|
|
}
|
|
|
|
function updateWolf(w){
|
|
w.hunger+=WOLF_HUNGER_INCREMENT;
|
|
if(w.hunger>=ANIMAL_HUNGER_MAX){
|
|
w.dead=true;
|
|
logAction("A wolf starved to death.");
|
|
return;
|
|
}
|
|
// Reproduce
|
|
if(w.hunger<50&&w.reproductionCooldown<=0){
|
|
if(Math.random()<WOLF_REPRO_CHANCE){
|
|
spawnBabyAnimal("Wolf",w.x,w.y);
|
|
w.reproductionCooldown=WOLF_REPRO_COOLDOWN;
|
|
}
|
|
}
|
|
// hunt rabbit
|
|
let bunny=findNearestAnimalOfType(w,"Rabbit");
|
|
if(bunny){
|
|
moveToward(w,bunny.x,bunny.y,0.5);
|
|
if(distance(w.x,w.y,bunny.x,bunny.y)<10){
|
|
bunny.dead=true;
|
|
w.hunger=Math.max(0,w.hunger-30);
|
|
logAction("A wolf killed a rabbit!");
|
|
knockback(w,bunny.x,bunny.y,KNOCKBACK_DISTANCE);
|
|
}
|
|
return;
|
|
}
|
|
// if hunger>60 => try attack citizen
|
|
if(w.hunger>60){
|
|
let hum=findNearestCitizen(w);
|
|
if(hum){
|
|
moveToward(w,hum.x,hum.y,0.5);
|
|
if(distance(w.x,w.y,hum.x,hum.y)<10){
|
|
if(hum.hasWeapon){
|
|
w.dead=true;
|
|
logAction(`${hum.name} defended vs wolf using a weapon! Wolf died.`);
|
|
} else {
|
|
logAction(`A wolf killed ${hum.name}!`);
|
|
citizens.splice(citizens.indexOf(hum),1);
|
|
}
|
|
knockback(w,hum.x,hum.y,KNOCKBACK_DISTANCE);
|
|
}
|
|
} else {
|
|
randomAnimalWander(w);
|
|
}
|
|
} else {
|
|
randomAnimalWander(w);
|
|
}
|
|
}
|
|
|
|
/**********************************************************************
|
|
* TASKS & AI LOGIC
|
|
**********************************************************************/
|
|
function assignNewTask(cit){
|
|
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.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!`);
|
|
cit.task=null;
|
|
cit.target=null;
|
|
return;
|
|
}
|
|
}
|
|
if(cit.profession==="Builder"){
|
|
builderTasks(cit);
|
|
} else {
|
|
farmerTasks(cit);
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
/**********************************************************************
|
|
* TASK HANDLERS
|
|
**********************************************************************/
|
|
function chopTask(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.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;
|
|
if(Math.random()<0.01){
|
|
logAction(`${cit.name} [Farmer] 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){
|
|
let px=cit.x+randInt(-50,50);
|
|
let py=cit.y+randInt(-50,50);
|
|
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}[Farmer] 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;
|
|
}
|
|
}
|
|
|
|
/**********************************************************************
|
|
* MOVE & DISTANCE
|
|
**********************************************************************/
|
|
function moveToward(obj,tx,ty,speed=0.4){
|
|
let dx=tx-obj.x;
|
|
let dy=ty-obj.y;
|
|
let dist=Math.sqrt(dx*dx+dy*dy);
|
|
if(dist>1){
|
|
obj.vx=(dx/dist)*speed;
|
|
obj.vy=(dy/dist)*speed;
|
|
} else {
|
|
obj.vx=0;
|
|
obj.vy=0;
|
|
}
|
|
}
|
|
function distance(x1,y1,x2,y2){
|
|
let dx=x2-x1;
|
|
let dy=y2-y1;
|
|
return Math.sqrt(dx*dx+dy*dy);
|
|
}
|
|
function knockback(ent,tx,ty,dist){
|
|
let dx=ent.x-tx;
|
|
let dy=ent.y-ty;
|
|
let length=Math.sqrt(dx*dx+dy*dy);
|
|
if(length>0.1){
|
|
ent.x+=(dx/length)*dist;
|
|
ent.y+=(dy/length)*dist;
|
|
}
|
|
}
|
|
|
|
/**********************************************************************
|
|
* FINDERS
|
|
**********************************************************************/
|
|
function findNearestResourceOfType(ref, rtype){
|
|
let best=null;
|
|
let bestD=Infinity;
|
|
resources.forEach((res)=>{
|
|
if(res.type===rtype && res.amount>0){
|
|
let d=distance(ref.x,ref.y,res.x,res.y);
|
|
if(d<bestD){
|
|
bestD=d;
|
|
best=res;
|
|
}
|
|
}
|
|
});
|
|
return best;
|
|
}
|
|
function findNearestAnimalOfType(me,targetType){
|
|
let best=null;
|
|
let bestD=Infinity;
|
|
animals.forEach((a)=>{
|
|
if(a.type===targetType && !a.dead && a!==me){
|
|
let d=distance(me.x,me.y,a.x,a.y);
|
|
if(d<bestD){
|
|
bestD=d;
|
|
best=a;
|
|
}
|
|
}
|
|
});
|
|
return best;
|
|
}
|
|
function findNearestCitizen(wolf){
|
|
let best=null;
|
|
let bestD=Infinity;
|
|
citizens.forEach((c)=>{
|
|
let d=distance(wolf.x,wolf.y,c.x,c.y);
|
|
if(d<bestD){
|
|
bestD=d;
|
|
best=c;
|
|
}
|
|
});
|
|
return best;
|
|
}
|
|
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);
|
|
}
|
|
|
|
/**********************************************************************
|
|
* ROAD BUILDING WHEN HOUSE COMPLETES
|
|
**********************************************************************/
|
|
function maybeBuildRoad(newHouse){
|
|
let otherHouses=buildings.filter(b=>b.buildingType==="House" && b.completed && b!==newHouse);
|
|
if(otherHouses.length===0) return;
|
|
let nearest=null;
|
|
let minD=Infinity;
|
|
otherHouses.forEach((oh)=>{
|
|
let d=distance(newHouse.x,newHouse.y,oh.x,oh.y);
|
|
if(d<minD){
|
|
minD=d;
|
|
nearest=oh;
|
|
}
|
|
});
|
|
if(!nearest)return;
|
|
let road=createRoadSite(newHouse.x,newHouse.y, nearest.x, nearest.y);
|
|
buildings.push(road);
|
|
logAction(`A Road site created between House@(${newHouse.x},${newHouse.y}) & House@(${nearest.x},${nearest.y}).`);
|
|
}
|
|
|
|
/**********************************************************************
|
|
* DEPOSIT WOOD LOGIC
|
|
**********************************************************************/
|
|
const originalAssignNewTask=assignNewTask;
|
|
assignNewTask=function(cit){
|
|
if(cit.profession==="Builder"){
|
|
let buildingNeedingWood=buildings.find(b=>!b.completed&&b.deliveredWood<b.requiredWood);
|
|
if(cit.carryingWood>0 && !buildingNeedingWood){
|
|
// deposit wood to city center (0,0)
|
|
let d=distance(cit.x,cit.y,0,0);
|
|
if(d<20){
|
|
cityStorage.wood+=cit.carryingWood;
|
|
logAction(`${cit.name} deposited ${cit.carryingWood} wood into city storage.`);
|
|
cit.carryingWood=0;
|
|
} else {
|
|
moveToward(cit,0,0,0.4);
|
|
cit.task=null;
|
|
cit.target=null;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
originalAssignNewTask(cit);
|
|
};
|
|
|
|
/**********************************************************************
|
|
* RENDER
|
|
**********************************************************************/
|
|
function drawWorld(){
|
|
ctx.clearRect(0,0,canvas.width,canvas.height);
|
|
drawGrid();
|
|
|
|
// resources
|
|
resources.forEach((res)=>drawResource(res));
|
|
|
|
// roads
|
|
buildings.filter(b=>b.buildingType==="Road").forEach(b=>drawRoad(b));
|
|
// houses
|
|
buildings.filter(b=>b.buildingType==="House").forEach(b=>drawHouse(b));
|
|
|
|
// city storage
|
|
drawCityStorage();
|
|
|
|
// citizens
|
|
citizens.forEach((cit)=>drawCitizen(cit));
|
|
|
|
// animals
|
|
animals.forEach((ani)=>{
|
|
if(!ani.dead) drawAnimal(ani);
|
|
});
|
|
}
|
|
|
|
function drawGrid(){
|
|
ctx.save();
|
|
ctx.translate(canvas.width/2+offsetX, canvas.height/2+offsetY);
|
|
ctx.scale(scale, scale);
|
|
|
|
ctx.strokeStyle="#ccc";
|
|
ctx.lineWidth=1/scale;
|
|
let range=2000;
|
|
for(let x=-range;x<=range;x+=100){
|
|
ctx.beginPath();
|
|
ctx.moveTo(x,-range);
|
|
ctx.lineTo(x,range);
|
|
ctx.stroke();
|
|
}
|
|
for(let y=-range;y<=range;y+=100){
|
|
ctx.beginPath();
|
|
ctx.moveTo(-range,y);
|
|
ctx.lineTo(range,y);
|
|
ctx.stroke();
|
|
}
|
|
ctx.restore();
|
|
}
|
|
|
|
function drawResource(res){
|
|
if(res.amount<=0)return;
|
|
ctx.save();
|
|
ctx.translate(canvas.width/2+offsetX, canvas.height/2+offsetY);
|
|
ctx.scale(scale, scale);
|
|
|
|
if(res.type==="Tree"){
|
|
ctx.fillStyle="#228B22";
|
|
ctx.beginPath();
|
|
ctx.arc(res.x,res.y,10,0,Math.PI*2);
|
|
ctx.fill();
|
|
ctx.fillStyle="#000";
|
|
ctx.font="12px sans-serif";
|
|
ctx.fillText(`Tree(${res.amount})`, res.x-20, res.y-12);
|
|
} else {
|
|
ctx.fillStyle="#FF6347";
|
|
ctx.beginPath();
|
|
ctx.arc(res.x,res.y,10,0,Math.PI*2);
|
|
ctx.fill();
|
|
ctx.fillStyle="#000";
|
|
ctx.font="12px sans-serif";
|
|
ctx.fillText(`Fruit(${res.amount})`, res.x-25,res.y-12);
|
|
}
|
|
|
|
ctx.restore();
|
|
}
|
|
|
|
function drawHouse(b){
|
|
ctx.save();
|
|
ctx.translate(canvas.width/2+offsetX, canvas.height/2+offsetY);
|
|
ctx.scale(scale, scale);
|
|
|
|
if(!b.completed){
|
|
ctx.strokeStyle="#FF8C00";
|
|
ctx.lineWidth=2/scale;
|
|
ctx.strokeRect(b.x-15,b.y-15,30,30);
|
|
|
|
ctx.fillStyle="#000";
|
|
ctx.font="12px sans-serif";
|
|
ctx.fillText(`House(Bldg)`, b.x-30, b.y-25);
|
|
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);
|
|
} else {
|
|
ctx.fillStyle="#DAA520";
|
|
ctx.fillRect(b.x-15,b.y-15,30,30);
|
|
|
|
ctx.fillStyle="#000";
|
|
ctx.font="12px sans-serif";
|
|
ctx.fillText("House", b.x-15,b.y-20);
|
|
ctx.fillText(`Fruit:${b.storedFruit}/${b.maxFruit}`, b.x-25,b.y+32);
|
|
}
|
|
|
|
ctx.restore();
|
|
}
|
|
|
|
function drawRoad(b){
|
|
ctx.save();
|
|
ctx.translate(canvas.width/2+offsetX, canvas.height/2+offsetY);
|
|
ctx.scale(scale, scale);
|
|
|
|
if(!b.completed){
|
|
ctx.setLineDash([5,5]);
|
|
ctx.strokeStyle="#888";
|
|
} else {
|
|
ctx.setLineDash([]);
|
|
ctx.strokeStyle="#444";
|
|
}
|
|
ctx.lineWidth=2/scale;
|
|
|
|
ctx.beginPath();
|
|
ctx.moveTo(b.x1,b.y1);
|
|
ctx.lineTo(b.x2,b.y2);
|
|
ctx.stroke();
|
|
|
|
ctx.fillStyle="#000";
|
|
ctx.font="12px sans-serif";
|
|
if(!b.completed){
|
|
ctx.fillText(`Road(Bldg) ${Math.floor(b.buildProgress)}%`, b.x,b.y-15);
|
|
ctx.fillText(`Wood:${b.deliveredWood}/${b.requiredWood}`, b.x-20,b.y+15);
|
|
} else {
|
|
ctx.fillText("Road", b.x-10,b.y-5);
|
|
}
|
|
|
|
ctx.restore();
|
|
}
|
|
|
|
function drawCityStorage(){
|
|
ctx.save();
|
|
ctx.fillStyle="#000";
|
|
ctx.font="16px sans-serif";
|
|
ctx.fillText(`City Wood Storage: ${cityStorage.wood}`,10,20);
|
|
ctx.restore();
|
|
}
|
|
|
|
function drawCitizen(c){
|
|
ctx.save();
|
|
ctx.translate(canvas.width/2+offsetX, canvas.height/2+offsetY);
|
|
ctx.scale(scale, scale);
|
|
|
|
ctx.fillStyle=c.color;
|
|
ctx.beginPath();
|
|
ctx.arc(c.x,c.y,7,0,Math.PI*2);
|
|
ctx.fill();
|
|
|
|
ctx.fillStyle="#000";
|
|
ctx.font="10px sans-serif";
|
|
let wpn=c.hasWeapon?"ARMED":"";
|
|
ctx.fillText(`${c.name}[${c.profession}]${wpn}`, c.x+10,c.y-2);
|
|
ctx.fillText(`W:${c.carryingWood} F:${c.carryingFruit} H:${Math.floor(c.hunger)} E:${Math.floor(c.energy)}`,
|
|
c.x+10,c.y+10);
|
|
|
|
ctx.restore();
|
|
}
|
|
|
|
function drawAnimal(a){
|
|
if(a.dead)return;
|
|
ctx.save();
|
|
ctx.translate(canvas.width/2+offsetX, canvas.height/2+offsetY);
|
|
ctx.scale(scale, scale);
|
|
|
|
if(a.type==="Rabbit"){
|
|
ctx.fillStyle="#999";
|
|
} else {
|
|
ctx.fillStyle="#555";
|
|
}
|
|
ctx.beginPath();
|
|
ctx.arc(a.x,a.y,6,0,Math.PI*2);
|
|
ctx.fill();
|
|
|
|
ctx.fillStyle="#000";
|
|
ctx.font="10px sans-serif";
|
|
ctx.fillText(a.type, a.x+8,a.y+3);
|
|
|
|
ctx.restore();
|
|
}
|
|
|
|
/**********************************************************************
|
|
* PAN & ZOOM
|
|
**********************************************************************/
|
|
canvas.addEventListener('mousedown',(e)=>{
|
|
isDragging=true;
|
|
lastMouseX=e.clientX;
|
|
lastMouseY=e.clientY;
|
|
});
|
|
canvas.addEventListener('mouseup',()=>{isDragging=false;});
|
|
canvas.addEventListener('mouseleave',()=>{isDragging=false;});
|
|
canvas.addEventListener('mousemove',(e)=>{
|
|
if(isDragging){
|
|
let dx=e.clientX-lastMouseX;
|
|
let dy=e.clientY-lastMouseY;
|
|
offsetX+=dx;
|
|
offsetY+=dy;
|
|
lastMouseX=e.clientX;
|
|
lastMouseY=e.clientY;
|
|
}
|
|
});
|
|
canvas.addEventListener('wheel',(e)=>{
|
|
e.preventDefault();
|
|
let zoomSpeed=0.001;
|
|
let delta=e.deltaY*zoomSpeed;
|
|
let oldScale=scale;
|
|
scale-=delta;
|
|
if(scale<0.1) scale=0.1;
|
|
if(scale>5) scale=5;
|
|
|
|
let mouseX=e.clientX-(canvas.width/2+offsetX);
|
|
let mouseY=e.clientY-(canvas.height/2+offsetY);
|
|
offsetX-=mouseX*(scale-oldScale);
|
|
offsetY-=mouseY*(scale-oldScale);
|
|
},{passive:false});
|
|
|
|
/**********************************************************************
|
|
* BUY MENU LOGIC
|
|
**********************************************************************/
|
|
document.getElementById('buyHouseBtn').addEventListener('click',()=>{
|
|
purchaseMode="House";
|
|
logAction("Click on map to place a House site.");
|
|
});
|
|
document.getElementById('buyRoadBtn').addEventListener('click',()=>{
|
|
purchaseMode="Road";
|
|
logAction("Click on map to place a Road site.");
|
|
});
|
|
document.getElementById('buyBuilderBtn').addEventListener('click',()=>{
|
|
purchaseMode="Builder";
|
|
logAction("Click on map to place a new Builder citizen.");
|
|
});
|
|
document.getElementById('buyFarmerBtn').addEventListener('click',()=>{
|
|
purchaseMode="Farmer";
|
|
logAction("Click on map to place a new Farmer citizen.");
|
|
});
|
|
document.getElementById('buySpawnerBtn').addEventListener('click',()=>{
|
|
purchaseMode="Spawner";
|
|
logAction("Click on map to place a Spawner building (not implemented fully).");
|
|
});
|
|
document.getElementById('buyTreeBtn').addEventListener('click',()=>{
|
|
purchaseMode="Tree";
|
|
logAction("Click on map to place a new Tree.");
|
|
});
|
|
|
|
canvas.addEventListener('click',(e)=>{
|
|
if(!purchaseMode)return;
|
|
const rect=canvas.getBoundingClientRect();
|
|
let cx=e.clientX-rect.left;
|
|
let cy=e.clientY-rect.top;
|
|
let worldX=(cx-(canvas.width/2)-offsetX)/scale;
|
|
let worldY=(cy-(canvas.height/2)-offsetY)/scale;
|
|
|
|
if(purchaseMode==="House"){
|
|
if(money>=COST_HOUSE){
|
|
addMoney(-COST_HOUSE, "Buy House");
|
|
let site=createHouseSite(worldX,worldY);
|
|
buildings.push(site);
|
|
logAction(`Purchased House site @(${Math.floor(worldX)},${Math.floor(worldY)})`);
|
|
} else {
|
|
logAction("Not enough money to buy House!");
|
|
}
|
|
}
|
|
else if(purchaseMode==="Road"){
|
|
if(money>=COST_ROAD){
|
|
addMoney(-COST_ROAD, "Buy Road");
|
|
// For simplicity, we create a short horizontal road
|
|
let site=createRoadSite(worldX-50,worldY, worldX+50,worldY);
|
|
buildings.push(site);
|
|
logAction(`Purchased Road site @(${Math.floor(worldX)},${Math.floor(worldY)})`);
|
|
} else {
|
|
logAction("Not enough money to buy Road!");
|
|
}
|
|
}
|
|
else if(purchaseMode==="Builder"){
|
|
if(money>=COST_BUILDER){
|
|
addMoney(-COST_BUILDER, "Buy Builder");
|
|
let c=createCitizen(randomName(), worldX,worldY, "Builder");
|
|
citizens.push(c);
|
|
logAction(`Purchased new Builder @(${Math.floor(worldX)},${Math.floor(worldY)})`);
|
|
} else {
|
|
logAction("Not enough money to buy Builder!");
|
|
}
|
|
}
|
|
else if(purchaseMode==="Farmer"){
|
|
if(money>=COST_FARMER){
|
|
addMoney(-COST_FARMER, "Buy Farmer");
|
|
let c=createCitizen(randomName(), worldX,worldY, "Farmer");
|
|
citizens.push(c);
|
|
logAction(`Purchased new Farmer @(${Math.floor(worldX)},${Math.floor(worldY)})`);
|
|
} else {
|
|
logAction("Not enough money to buy Farmer!");
|
|
}
|
|
}
|
|
else if(purchaseMode==="Spawner"){
|
|
if(money>=COST_SPAWNER){
|
|
addMoney(-COST_SPAWNER, "Buy Spawner");
|
|
// We'll treat a Spawner as a completed building that spawns new Citizens automatically
|
|
// For demonstration, let's keep it simple or expand later
|
|
let spawnBuild={
|
|
buildingType:"Spawner",
|
|
x:worldX, y:worldY,
|
|
completed:true,
|
|
lastSpawnTime:Date.now() // can spawn 1 citizen/min
|
|
};
|
|
buildings.push(spawnBuild);
|
|
logAction(`Purchased Spawner @(${Math.floor(worldX)},${Math.floor(worldY)})
|
|
(not fully implemented, but can expand).`);
|
|
} else {
|
|
logAction("Not enough money to buy Spawner!");
|
|
}
|
|
}
|
|
else if(purchaseMode==="Tree"){
|
|
if(money>=COST_TREE){
|
|
addMoney(-COST_TREE, "Buy Tree");
|
|
let t=createResource("Tree", worldX,worldY, 100);
|
|
resources.push(t);
|
|
logAction(`Purchased a new Tree @(${Math.floor(worldX)},${Math.floor(worldY)})`);
|
|
} else {
|
|
logAction("Not enough money to buy Tree!");
|
|
}
|
|
}
|
|
|
|
purchaseMode=null;
|
|
});
|
|
|
|
/**********************************************************************
|
|
* TOGGLE LOGS
|
|
**********************************************************************/
|
|
document.getElementById('toggleLogsBtn').addEventListener('click',(e)=>{
|
|
if(logContainer.style.display==="none"){
|
|
logContainer.style.display="block";
|
|
e.target.textContent="Hide Logs";
|
|
} else {
|
|
logContainer.style.display="none";
|
|
e.target.textContent="Show Logs";
|
|
}
|
|
});
|
|
|
|
/**********************************************************************
|
|
* START
|
|
**********************************************************************/
|
|
initWorld();
|
|
updateMoneyDisplay();
|
|
</script>
|
|
</body>
|
|
</html>
|