local lib = starsoul.mod.lib
local world = starsoul.world
function world.date()
local days = minetest.get_day_count()
local year = math.floor(days / world.planet.orbit);
local day = days % world.planet.orbit;
return {
year = year, day = day;
season = day / world.planet.orbit;
}
end
local lerp = lib.math.lerp
local function gradient(grad, pos)
local n = #grad
if n == 1 then return grad[1] end
local op = pos*(n-1)
local idx = math.floor(op)
local t = op-idx
return lerp(t, grad[1 + idx], grad[2 + idx])
end
local altitudeCooling = 10 / 100
-- this function provides the basis for temperature calculation,
-- which is performed by adding this value to the ambient temperature,
-- determined by querying nearby group:heatSource items in accordance
-- with the inverse-square law
function world.climate.eval(pos, tod, season)
local data = minetest.get_biome_data(pos)
local biome = world.ecology.biomes.db[minetest.get_biome_name(data.biome)]
local heat, humid = data.heat, data.humidity
tod = tod or minetest.get_timeofday()
heat = lerp(math.abs(tod - 0.5)*2, heat, heat + biome.nightTempDelta)
local td = world.date()
heat = heat + gradient(biome.seasonalTemp, season or td.season)
if pos.y > 0 then
heat = heat - pos.y*altitudeCooling
end
return {
surfaceTemp = heat;
waterTemp = heat + biome.waterTempDelta;
surfaceHumid = humid;
}
end
local vdsq = lib.math.vdsq
function world.climate.temp(pos) --> irradiance at pos in W
local cl = world.climate.eval(pos)
local radCenters = starsoul.region.radiator.store:get_areas_for_pos(pos, false, true)
local irradiance = 0
for _,e in pairs(radCenters) do
local rpos = minetest.string_to_pos(e.data)
local rdef = assert(minetest.registered_nodes[assert(minetest.get_node(rpos)).name])
local rc = rdef._starsoul.radiator
local r_max = rc.radius(rpos)
local dist_sq = vdsq(rpos,pos)
if dist_sq <= r_max^2 then
-- cheap bad way
-- if minetest.line_of_sight(rpos,pos) then
--
-- expensive way
local obstruct = 0
local ray = Raycast(rpos, pos, true, true)
for p in ray do
if p.type == 'node' then obstruct = obstruct + 1 end
end
if obstruct < 4 then
local power, customFalloff = rc.radiate(rpos, pos)
-- okay this isn't the real inverse square law but i
-- couldn't figure out a better way to simplify the
-- model without checking an ENORMOUS number of nodes
-- maybe someone else who isn't completely
-- mathtarded can do better.
if not customFalloff then
power = power * (1 - (dist_sq / ((r_max+1)^2)))
end
power = power * (1 - (obstruct/5))
irradiance = irradiance + power
end
end
end
return irradiance + cl.surfaceTemp
end
world.ecology.biomes.foreach('starsoul:biome-gen', {}, function(id, b)
b.def.name = id
minetest.register_biome(b.def)
end)
world.ecology.biomes.link('starsoul:steppe', {
nightTempDelta = -30;
waterTempDelta = 0;
-- W Sp Su Au W
seasonalTemp = {-50, -10, 5, 5, -20, -50};
def = {
node_top = 'starsoul:greengraze', depth_top = 1;
node_filler = 'starsoul:soil', depth_filler = 4;
node_riverbed = 'starsoul:sand', depth_riverbed = 4;
y_min = 0;
y_max = 512;
heat_point = 10;
humidity_point = 30;
};
})
world.ecology.biomes.link('starsoul:ocean', {
nightTempDelta = -35;
waterTempDelta = 5;
seasonalTemp = {0}; -- no seasonal variance
def = {
y_max = 3;
y_min = -512;
heat_point = 15;
humidity_point = 50;
node_top = 'starsoul:sand', depth_top = 1;
node_filler = 'starsoul:sand', depth_filler = 3;
};
})
local toward = lib.math.toward
local hfinterval = 1.5
starsoul.startJob('starsoul:heatflow', hfinterval, function(delta)
-- our base thermal conductivity (κ) is measured in °C/°C/s. say the
-- player is in -30°C weather, and has an internal temperature of
-- 10°C. then:
-- κ = .1°C/C/s (which is apparently 100mHz)
-- Tₚ = 10°C
-- Tₑ = -30°C
-- d = Tₑ − Tₚ = -40°C
-- ΔT = κ×d = -.4°C/s
-- our final change in temperature is computed as tΔC where t is time
local kappa = .05
for name,user in pairs(starsoul.activeUsers) do
local tr = user:species().tempRange
local t = starsoul.world.climate.temp(user.entity:get_pos())
local insul = 0
local naked = user:naked()
local suitDef
if not naked then
suitDef = user:suitStack():get_definition()
insul = suitDef._starsoul.suit.temp.insulation
end
local warm = user:effectiveStat 'warmth'
local tSafeMin, tSafeMax = tr.survivable[1], tr.survivable[2]
local tComfMin, tComfMax = tr.comfort[1], tr.comfort[2]
local tDelta = (kappa * (1-insul)) * (t - warm) * hfinterval
local tgt = warm + tDelta
-- old logic: we move the user towards the exterior temperature, modulated
-- by her suit insulation.
--local tgt = toward(warm, t, hfinterval * thermalConductivity * (1 - insul))
if not naked then
local suit = user:getSuit()
local suitPower = suit:powerState()
local suitPowerLeft = suit:powerLeft()
if suitPower ~= 'off' then
local coilPower = 1.0
local st = suitDef._starsoul.suit.temp
if suitPower == 'powerSave' and (tgt >= tSafeMin and tgt <= tSafeMax) then coilPower = 0.5 end
if tgt < tComfMin and st.maxHeat > 0 then
local availPower = user:suitDrawCurrent(st.heatPower*coilPower, hfinterval)
tgt = tgt + (availPower / st.heatPower) * st.maxHeat * coilPower * hfinterval
end
if tgt > tComfMax and st.maxCool > 0 then
local availPower = user:suitDrawCurrent(st.coolPower*coilPower, hfinterval)
tgt = tgt - (availPower / st.coolPower) * st.maxCool * coilPower * hfinterval
end
end
end
user:statDelta('warmth', tgt - warm) -- dopey but w/e
warm = tgt -- for the sake of readable code
if warm < tSafeMin or warm > tSafeMax then
local dv
if warm < tSafeMin then
dv = math.abs(warm - tSafeMin)
else
dv = math.abs(warm - tSafeMax)
end
-- for every degree of difference you suffer 2 points of damage/s
local dmg = math.ceil(dv * 2)
user:statDelta('health', -dmg)
end
end
end)