178 lines
6.0 KiB
Lua
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
|