280 lines
9.3 KiB
JavaScript
280 lines
9.3 KiB
JavaScript
// Game Simulation / Logic
|
|
const Simulation = {
|
|
gameTime: 0,
|
|
gameSpeed: 1,
|
|
|
|
// Manual mining state
|
|
mining: {
|
|
active: false,
|
|
x: 0,
|
|
y: 0,
|
|
progress: 0
|
|
},
|
|
|
|
// Initialize
|
|
init() {
|
|
this.gameTime = 0;
|
|
this.gameSpeed = 1;
|
|
this.mining = { active: false, x: 0, y: 0, progress: 0 };
|
|
},
|
|
|
|
// Main update function
|
|
update(dt) {
|
|
dt *= this.gameSpeed;
|
|
this.gameTime += dt;
|
|
|
|
this.updateManualMining(dt);
|
|
this.updateMiners(dt);
|
|
this.updateFurnaces(dt);
|
|
this.updateAssemblers(dt);
|
|
this.updateInserters(dt);
|
|
this.updateBelts(dt);
|
|
this.updateChests(dt);
|
|
},
|
|
|
|
// Manual mining update
|
|
updateManualMining(dt) {
|
|
if (!this.mining.active) return;
|
|
|
|
const tile = Terrain.getTile(this.mining.x, this.mining.y);
|
|
if (!tile || !tile.resource || tile.amount <= 0) {
|
|
this.mining.active = false;
|
|
this.mining.progress = 0;
|
|
return;
|
|
}
|
|
|
|
this.mining.progress += dt / CONFIG.MANUAL_MINE_RATE;
|
|
|
|
// Play mine sound periodically
|
|
this.mining.soundTimer = (this.mining.soundTimer || 0) + dt;
|
|
if (this.mining.soundTimer > 0.3) {
|
|
this.mining.soundTimer = 0;
|
|
Audio.playMine();
|
|
}
|
|
|
|
if (this.mining.progress >= 1) {
|
|
this.mining.progress = 0;
|
|
const result = Terrain.mine(this.mining.x, this.mining.y, CONFIG.MANUAL_MINE_AMOUNT);
|
|
if (result) {
|
|
Resources.add(result.type, result.amount);
|
|
Audio.playMineComplete();
|
|
}
|
|
|
|
// Check if resource depleted
|
|
if (!Terrain.hasResource(this.mining.x, this.mining.y)) {
|
|
this.mining.active = false;
|
|
}
|
|
}
|
|
},
|
|
|
|
// Start manual mining at position
|
|
startMining(x, y) {
|
|
if (Terrain.hasResource(x, y) && !Buildings.getAt(x, y)) {
|
|
this.mining.active = true;
|
|
this.mining.x = x;
|
|
this.mining.y = y;
|
|
this.mining.progress = 0;
|
|
return true;
|
|
}
|
|
return false;
|
|
},
|
|
|
|
// Stop manual mining
|
|
stopMining() {
|
|
this.mining.active = false;
|
|
this.mining.progress = 0;
|
|
},
|
|
|
|
// Update miners
|
|
updateMiners(dt) {
|
|
Buildings.getByType('miner').forEach(miner => {
|
|
const size = Utils.getBuildingSize('miner');
|
|
let resourceType = null;
|
|
let hasResource = false;
|
|
|
|
// Find resource under miner
|
|
for (let dy = 0; dy < size.h && !hasResource; dy++) {
|
|
for (let dx = 0; dx < size.w && !hasResource; dx++) {
|
|
const tile = Terrain.getTile(miner.x + dx, miner.y + dy);
|
|
if (tile?.resource && tile.amount > 0) {
|
|
resourceType = tile.resource;
|
|
hasResource = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (hasResource && resourceType) {
|
|
miner.progress += dt * CONFIG.SPEEDS.miner;
|
|
if (miner.progress >= 1) {
|
|
miner.progress = 0;
|
|
Buildings.addToOutput(miner, resourceType);
|
|
|
|
// Deplete resource
|
|
for (let dy = 0; dy < size.h; dy++) {
|
|
for (let dx = 0; dx < size.w; dx++) {
|
|
const result = Terrain.mine(miner.x + dx, miner.y + dy, 1);
|
|
if (result) return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
},
|
|
|
|
// Update furnaces
|
|
updateFurnaces(dt) {
|
|
Buildings.getByType('furnace').forEach(furnace => {
|
|
const hasCoal = Buildings.getInventoryCount(furnace, 'coal') > 0;
|
|
const hasIron = Buildings.getInventoryCount(furnace, 'iron') > 0;
|
|
const hasCopper = Buildings.getInventoryCount(furnace, 'copper') > 0;
|
|
|
|
if (hasCoal && (hasIron || hasCopper)) {
|
|
furnace.progress += dt * CONFIG.SPEEDS.furnace;
|
|
if (furnace.progress >= 1) {
|
|
furnace.progress = 0;
|
|
Buildings.removeFromInventory(furnace, 'coal');
|
|
|
|
if (hasIron) {
|
|
Buildings.removeFromInventory(furnace, 'iron');
|
|
Buildings.addToOutput(furnace, 'iron-plate');
|
|
} else if (hasCopper) {
|
|
Buildings.removeFromInventory(furnace, 'copper');
|
|
Buildings.addToOutput(furnace, 'copper-plate');
|
|
}
|
|
}
|
|
}
|
|
});
|
|
},
|
|
|
|
// Update assemblers
|
|
updateAssemblers(dt) {
|
|
Buildings.getByType('assembler').forEach(asm => {
|
|
if (!asm.recipe) return;
|
|
|
|
const recipe = CONFIG.RECIPES[asm.recipe];
|
|
if (!recipe) return;
|
|
|
|
// Check if can craft
|
|
let canCraft = true;
|
|
for (const [item, amount] of Object.entries(recipe.inputs)) {
|
|
if (Buildings.getInventoryCount(asm, item) < amount) {
|
|
canCraft = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (canCraft) {
|
|
asm.progress += dt * CONFIG.SPEEDS.assembler;
|
|
if (asm.progress >= 1) {
|
|
asm.progress = 0;
|
|
|
|
// Consume inputs
|
|
for (const [item, amount] of Object.entries(recipe.inputs)) {
|
|
Buildings.removeFromInventory(asm, item, amount);
|
|
}
|
|
|
|
// Produce output
|
|
Buildings.addToOutput(asm, recipe.output, recipe.outputCount);
|
|
}
|
|
}
|
|
});
|
|
},
|
|
|
|
// Update inserters
|
|
updateInserters(dt) {
|
|
Buildings.getByType('inserter').forEach(ins => {
|
|
const dir = CONFIG.DIR[ins.rotation];
|
|
const pickupX = ins.x - dir.x;
|
|
const pickupY = ins.y - dir.y;
|
|
const dropX = ins.x + dir.x;
|
|
const dropY = ins.y + dir.y;
|
|
|
|
const source = Buildings.getAt(pickupX, pickupY);
|
|
const dest = Buildings.getAt(dropX, dropY);
|
|
|
|
if (!source) return;
|
|
|
|
// Find item to transfer from source output
|
|
for (const [item, count] of Object.entries(source.output || {})) {
|
|
if (count > 0) {
|
|
ins.progress += dt * CONFIG.SPEEDS.inserter;
|
|
if (ins.progress >= 1) {
|
|
ins.progress = 0;
|
|
Buildings.removeFromOutput(source, item);
|
|
|
|
if (dest) {
|
|
// Drop into building
|
|
Buildings.addToInventory(dest, item);
|
|
} else if (source.type === 'chest') {
|
|
// Only chests can output to player inventory
|
|
Resources.add(item, 1);
|
|
}
|
|
// If no dest and not chest, item is lost
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
});
|
|
},
|
|
|
|
// Update belts
|
|
updateBelts(dt) {
|
|
Buildings.getByType('belt').forEach(belt => {
|
|
const dir = CONFIG.DIR[belt.rotation];
|
|
const nextX = belt.x + dir.x;
|
|
const nextY = belt.y + dir.y;
|
|
const nextBuilding = Buildings.getAt(nextX, nextY);
|
|
|
|
// Move items from output to next building
|
|
for (const [item, count] of Object.entries(belt.output || {})) {
|
|
if (count > 0 && nextBuilding) {
|
|
Buildings.removeFromOutput(belt, item);
|
|
Buildings.addToInventory(nextBuilding, item);
|
|
}
|
|
}
|
|
|
|
// Move items through belt (inventory -> output)
|
|
belt.progress = (belt.progress || 0) + dt * CONFIG.SPEEDS.belt;
|
|
if (belt.progress >= 1) {
|
|
belt.progress = 0;
|
|
for (const [item, count] of Object.entries(belt.inventory || {})) {
|
|
if (count > 0) {
|
|
Buildings.removeFromInventory(belt, item);
|
|
Buildings.addToOutput(belt, item);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
},
|
|
|
|
// Update chests (move inventory to output for inserters)
|
|
updateChests(dt) {
|
|
Buildings.getByType('chest').forEach(chest => {
|
|
for (const [item, count] of Object.entries(chest.inventory)) {
|
|
if (count > 0) {
|
|
chest.output[item] = (chest.output[item] || 0) + count;
|
|
chest.inventory[item] = 0;
|
|
}
|
|
}
|
|
});
|
|
},
|
|
|
|
// Set game speed
|
|
setSpeed(speed) {
|
|
this.gameSpeed = speed;
|
|
},
|
|
|
|
// Get state for saving
|
|
getData() {
|
|
return {
|
|
gameTime: this.gameTime
|
|
};
|
|
},
|
|
|
|
// Load state
|
|
setData(data) {
|
|
this.gameTime = data.gameTime || 0;
|
|
}
|
|
};
|