Files
kimi-game/src/buildings.lua
T
2026-07-21 16:28:15 +02:00

178 lines
6.0 KiB
Lua

-- Définitions, placement et simulation des bâtiments. Logique pure.
local C = require("src.config")
local Buildings = {}
Buildings.__index = Buildings
function Buildings.new()
return setmetatable({ list = {}, nextId = 1 }, Buildings)
end
local function defOf(b) return C.BUILDINGS[b.kind] end
function Buildings:at(x, y)
for _, b in ipairs(self.list) do
if b.x == x and b.y == y then return b end
end
return nil
end
function Buildings:get(id)
for _, b in ipairs(self.list) do
if b.id == id then return b end
end
return nil
end
function Buildings:countBuilt(kind)
local n = 0
for _, b in ipairs(self.list) do
if b.kind == kind and b.built then n = n + 1 end
end
return n
end
-- Vérifie qu'un plan peut être posé. Retourne ok, raison.
function Buildings:canPlace(kind, x, y, world, res)
local def = C.BUILDINGS[kind]
if not def then return false, "Type inconnu." end
if not world:isExplored(x, y) then return false, "Case non explorée." end
if def.unique and self:countBuilt(kind) + self:countPlanned(kind) > 0 then
return false, "Bâtiment unique déjà existant ou planifié."
end
local t = world:getTile(x, y)
if t == C.T.ROCK then return false, "Rochers : infranchissable." end
if def.needsOre and t ~= C.T.ORE then return false, "Nécessite un gisement de minerai." end
if not def.needsOre and kind ~= "station" and t == C.T.ORE and kind ~= "extractor" then
-- on autorise, pas de blocage
end
if self:at(x, y) then return false, "Case déjà occupée." end
if def.minRelayDist then
for _, b in ipairs(self.list) do
if b.kind == "relay" then
local d = math.abs(b.x - x) + math.abs(b.y - y)
if d < def.minRelayDist then
return false, "Trop proche d'un autre relais (min 6 cases)."
end
end
end
end
if res then
if (def.cost.ore or 0) > res.ore then return false, "Minerai insuffisant." end
if (def.cost.data or 0) > res.data then return false, "Données insuffisantes." end
end
return true
end
function Buildings:countPlanned(kind)
local n = 0
for _, b in ipairs(self.list) do
if b.kind == kind and not b.built then n = n + 1 end
end
return n
end
-- Pose un plan de construction (paie le coût si res fourni).
function Buildings:place(kind, x, y, res)
local def = C.BUILDINGS[kind]
if res then
res.ore = res.ore - (def.cost.ore or 0)
res.data = res.data - (def.cost.data or 0)
end
local b = {
id = self.nextId, kind = kind, x = x, y = y,
built = false, progress = 0,
integrity = def.integrityMax,
}
self.nextId = self.nextId + 1
self.list[#self.list + 1] = b
return b
end
-- Capacité de stockage énergétique totale.
function Buildings:energyStorage()
local cap = C.BASE_ENERGY_STORAGE
for _, b in ipairs(self.list) do
if b.built and b.kind == "battery" and b.integrity > 0 then
cap = cap + C.BUILDINGS.battery.storageBonus
end
end
return cap
end
-- Efficacité d'un relais (1 si isolé, 0.4 sinon — le placement empêche déjà la proximité,
-- mais les relais posés avant la règle ou chargés restent gérés).
function Buildings:relayEfficiency(b)
for _, o in ipairs(self.list) do
if o ~= b and o.kind == "relay" and o.built then
if math.abs(o.x - b.x) + math.abs(o.y - b.y) < C.BUILDINGS.relay.minRelayDist then
return 0.4
end
end
end
return 1
end
-- Simulation des bâtiments pour dt secondes. Mutate res {energy, ore, data}.
-- weather : C.WEATHER.* ; dayPhase : fraction de la journée (0..1).
function Buildings:simulate(dt, res, weather, game)
for _, b in ipairs(self.list) do
if b.built and b.integrity > 0 then
local def = defOf(b)
local eff = b.integrity / def.integrityMax -- bâtiment usé = moins efficace
if def.energyPerSec then
local f = 1
if b.kind == "solar" and weather == C.WEATHER.STORM then f = def.stormFactor end
res.energy = res.energy + def.energyPerSec * f * eff * dt
end
if def.orePerSec then
if res.energy > 0.1 then
local mined = math.min(def.orePerSec * eff * dt, game:oreRemaining(b))
res.ore = res.ore + mined
game:depleteOre(b, mined)
end
end
if def.dataPerSec then
if res.energy > 0.1 then
res.data = res.data + def.dataPerSec * self:relayEfficiency(b) * eff * dt
end
end
-- usure lente des bâtiments en tempête
if weather == C.WEATHER.STORM and b.kind ~= "station" then
b.integrity = math.max(0, b.integrity - 0.05 * dt)
end
end
end
-- bornes
if res.energy < 0 then res.energy = 0 end
local cap = self:energyStorage()
if res.energy > cap then res.energy = cap end
end
-- Sérialisation
function Buildings:serialize()
local parts = {}
for _, b in ipairs(self.list) do
parts[#parts + 1] = table.concat({b.id, b.kind, b.x, b.y, b.built and 1 or 0,
math.floor(b.progress * 100) / 100, math.floor(b.integrity * 100) / 100}, ",")
end
return table.concat(parts, ";")
end
function Buildings:deserialize(str)
self.list, self.nextId = {}, 1
if not str or str == "" then return end
for rec in str:gmatch("[^;]+") do
local f = {}
for v in rec:gmatch("[^,]+") do f[#f + 1] = v end
local b = {
id = tonumber(f[1]), kind = f[2], x = tonumber(f[3]), y = tonumber(f[4]),
built = f[5] == "1", progress = tonumber(f[6]), integrity = tonumber(f[7]),
}
self.list[#self.list + 1] = b
if b.id >= self.nextId then self.nextId = b.id + 1 end
end
end
return Buildings