-- 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