Index: mods/starlit/init.lua ================================================================== --- mods/starlit/init.lua +++ mods/starlit/init.lua @@ -30,10 +30,11 @@ heat = { -- celsius freezing = 0; safe = 4; overheat = 32; boiling = 100; + thermalConductivity = 0.05; -- κ }; rad = { }; }; Index: mods/starlit/user.lua ================================================================== --- mods/starlit/user.lua +++ mods/starlit/user.lua @@ -39,10 +39,11 @@ entity = luser; name = name; hud = { elt = {}; bar = {}; + alarm = {}; }; tree = {}; action = { bits = 0; -- for control deltas prog = {}; -- for recording action progress on a node; reset on refocus @@ -61,10 +62,11 @@ calendar = 'commune'; }; overlays = {}; cooldownTimes = { stamina = 0; + alarm = 0; }; } end; __index = { -------------- @@ -190,11 +192,11 @@ --------- -- HUD -- --------- attachImage = function(self, def) local user = self.entity - local img = {} + local img = {def = def} img.id = user:hud_add { type = 'image'; text = def.tex; scale = def.scale; alignment = def.align; @@ -211,11 +213,11 @@ end return img end; attachMeter = function(self, def) local luser = self.entity - local m = {} + local m = {def = def} local w = def.size or 80 local szf = w / 80 local h = szf * 260 m.meter = luser:hud_add { type = 'image'; @@ -265,11 +267,11 @@ end return m end; attachTextBox = function(self, def) local luser = self.entity - local box = {} + local box = {def = def} box.id = luser:hud_add { type = 'text'; text = ''; alignment = def.align; number = def.color and def.color:int24() or 0xFFffFF; @@ -288,11 +290,11 @@ end return box end; attachStatBar = function(self, def) local luser = self.entity - local bar = {} + local bar = {def = def} local img = lib.image 'starlit-ui-bar.png' local colorized = img if type(def.color) ~= 'function' then colorized = colorized:shift(def.color) end @@ -448,11 +450,11 @@ }; self:updateHUD() end; deleteHUD = function(self) for name, e in pairs(self.hud.elt) do - self:hud_delete(e.id) + self.entity:hud_remove(e.id) end end; updateHUD = function(self) for name, e in pairs(self.hud.elt) do if e.update then e.update() end @@ -501,10 +503,34 @@ clientInfo = function(self) return minetest.get_player_information(self.name) end; species = function(self) return starlit.world.species.index[self.persona.species] + end; + -- can the suit heater sustain its current internal temperature in an area of t°C + tempCanSustain = function(self, t) + if self:naked() then return false end + local s = self:getSuit() + if s:powerState() == 'off' then return false end + local sd = s:def() + local w = self:effectiveStat 'warmth' + local kappa = starlit.constant.heat.thermalConductivity + local insul = sd.temp.insulation + local dt = (kappa * (1-insul)) * (t - w) + print('dt', dt, dump(sd.temp)) + if (dt > 0 and dt > sd.temp.maxCool) + or (dt < 0 and math.abs(dt) > sd.temp.maxHeat) then return false end + return true + end; + -- will exposure to temperature t cause the player eventual harm + tempHazard = function(self, t) + local tr = self:species().tempRange.survivable + if t >= tr[1] and t <= tr[2] then return nil end + if self:tempCanSustain(t) then return nil end + + if t < tr[1] then return 'cold' end + return 'hot' end; -------------------- -- event handlers -- -------------------- @@ -874,10 +900,47 @@ run(self, ctx) return true end return false end; + + alarm = function(self, urgency, kind, freq, where) + freq = freq or 3 + local urgencies = { + [1] = {sound = 'starlit-alarm'}; + [2] = {sound = 'starlit-alarm-urgent'}; + } + local gt = minetest.get_gametime() + local urg = urgencies[urgency] or urgencies[#urgencies] + + if gt - self.cooldownTimes.alarm < freq then return end + + self.cooldownTimes.alarm = gt + self:suitSound(urg.sound) + + if where then + local elt = { + tex = where.tex or 'starlit-ui-alert.png'; + scale = {x=1, y=1}; + align = table.copy(where.elt.def.align); + pos = table.copy(where.elt.def.pos); + ofs = table.copy(where.elt.def.ofs); + } + elt.ofs.x = elt.ofs.x + where.ofs.x + elt.ofs.y = elt.ofs.y + where.ofs.y + local attached = self:attachImage(elt) + table.insert(self.hud.alarm, attached) + + -- HATE. HATE. HAAAAAAAAAAATE + minetest.after(freq/2, function() + for k,v in pairs(self.hud.alarm) do + self.entity:hud_remove(v.id) + end + self.hud.alarm={} + end) + end + end; ------------- -- weather -- ------------- updateWeather = function(self) @@ -935,10 +998,16 @@ starlit.startJob('starlit:clock', clockInterval, function(delta) for id, u in pairs(starlit.activeUsers) do u.hud.elt.time:update() end end) + +-- performs a general HUD refresh, mainly to update the HUD backlight brightness +local hudInterval = 10 +starlit.startJob('starlit:hud-refresh', hudInterval, function(delta) + for id, u in pairs(starlit.activeUsers) do u:updateHUD() end +end) local biointerval = 1.0 starlit.startJob('starlit:bio', biointerval, function(delta) for id, u in pairs(starlit.activeUsers) do if u:effectiveStat 'health' ~= 0 then Index: mods/starlit/world.lua ================================================================== --- mods/starlit/world.lua +++ mods/starlit/world.lua @@ -178,15 +178,39 @@ -- κ = .1°C/C/s (which is apparently 100mHz) -- Tₚ = 10°C -- Tₑ = -30°C -- d = Tₑ − Tₚ = -40°C -- ΔT = κ×d = -.4°C/s + -- too cold: + -- x = beginning of danger zone + -- κ × (x - Tₚ) = y where y < Tₚ -- our final change in temperature is computed as tΔC where t is time - local kappa = .05 + local kappa = starlit.constant.heat.thermalConductivity for name,user in pairs(starlit.activeUsers) do local tr = user:species().tempRange local t = starlit.world.climate.temp(user.entity:get_pos()) + + do -- this bit probably belongs in starlit:bio but we do it here in order + -- to spare ourselves another call into the dark swamp of climate.temp + local urg = 1 + local function alarm(kind) + user:alarm(urg, kind, nil, { + elt = user.hud.elt.temp, ofs = {x=100,y=0}; + tex = 'starlit-ui-alert-'..kind..'.png'; + }) + end + local hz = user:tempHazard(t) + local tr = user:species().tempRange.survivable + if hz == 'cold' then + if tr[1] - t > 7 then urg = 2 end + alarm 'temp-cold' + elseif hz == 'hot' then + if t - tr[2] > 7 then urg = 2 end + alarm 'temp-hot' + end + end + local insul = 0 local naked = user:naked() local suitDef if not naked then suitDef = user:suitStack():get_definition() @@ -225,10 +249,11 @@ user:statDelta('warmth', tgt - warm) -- dopey but w/e warm = tgt -- for the sake of readable code + -- does this belong in starlit:bio? unsure tbh if warm < tSafeMin or warm > tSafeMax then local dv if warm < tSafeMin then dv = math.abs(warm - tSafeMin) else Index: starlit.ct ================================================================== --- starlit.ct +++ starlit.ct @@ -1,10 +1,9 @@ # starlit [*starlit] is a sci-fi survival game. you play the survivor of a disaster in space, stranded on a just-barely-habitable world with nothing but your escape pod, your survival suit, and some handy new psionic powers that the accident seems to have unlocked in you. scavenge resources, search alien ruins, and build a base where you can survive indefinitely, but be careful: winter approaches, and your starting suit heater is already struggling to keep you alive in the comparatively warm days of "summer". -## story -### "Imperial Expat" background +##story story about a month ago, you woke up to unexpected good news. your application to join the new Commune colony at Thousand Petal, submitted in a moment of utter existential weariness and almost in jest, was actually accepted. your skillset was a "perfect match" for the budding colony's needs, claimed the Population Control Authority, and you'd earned yourself a free trip to your new home -- on a swanky state transport, no less. it took a few discreet threats and bribes from Commune diplomats, but after a week of wrangling with the surly Crown Service for Comings & Goings -- whose bureaucrats seemed outright [!offended] that you actually managed to find a way off that hateful rock -- you secured grudging clearance to depart. you celebrated by telling your slackjawed boss exactly what you thought of him in a meeting room packed with his fellow parasites -- and left House Taladran with a new appreciation for the value of your labor, as the nobs found themselves desperately scrabbling for a replacement on short notice. you almost couldn't believe it when the Commune ship -- a sleek, solid piece of engineering whose graceful descent onto the landing pad seemed to sneer at the lurching rattletraps arrayed all around it -- actually showed up. in a daze you handed over your worldly possessions -- all three of them -- to a valet with impeccable manners, and climbed up out of the wagie nightmare into high orbit around your homeworld. the mercenary psion aboard, a preening Usukwinti with her very own luxury suite, tore a bleeding hole in the spacetime metric, and five hundred hopeful souls dove through towards fancied salvation. "sure," you thought to yourself as you slipped into your sleek new nanotech environment suit, itself worth more than the sum total of your earnings on Flame of Unyielding Purification, "life won't be easy -- but damn it, it'll [!mean] something out there." @@ -40,16 +39,41 @@ ### shadows i was delighted to see dynamic shadows land in minetest, and i hope the implementation will eventually mature. however, as it stands, there are severe issues with shadows that make them essentially incompatible with complex meshes like the Starlit player character meshes. for the sake of those who don't mind these glitches, Starlit does enable shadows, but i unfortunately have to recommend that you disable them until the minetest devs get their act together on this feature. ## gameplay +starlit is somewhat unusual in how it uses the minetest engine. it's a voxel game but not of the minecraft variety. + the most important thing to understand about starlit is that is is [*mean], by design. * chance plays an important role. your escape pod might land in the midst of a lush, temperate forest with plenty of nearby shipwrecks to scavenge. or it might land in the exact geographic center of a vast, harsh desert that your suit's cooling systems can't protect you from, ten klicks from anything of value. "unfair", you say? tough. Farthest Shadow doesn't care about your feelings. * death is much worse than a slap on the wrist. when you die, you drop your possessions and your suit, and respawn naked at your spawn point. this is a serious danger, as you might be kilometers away from your spawn point -- and there's no guarantee someone else won't take your suit before you can find your way back to it. good luck crossing long distances without climate control! if you haven't carefully prepared for this eventuality by keeping a spare suit by your spawn point, death can be devastating, to the point of making the game unsurvivable without another player's help. -starlit is somewhat unusual in how it uses the minetest engine. it's a voxel game but not of the minecraft variety. +### scenarios +your starting character configuration depends on the scenario you select. (right now this is configured in minetest settings, which is intensely awkward, but i don't have a better solution). the scenario controls your species, sex, and starting inventory. [*neither species nor sex is cosmetic]; e.g. human females are physically weaker but psionically stronged than males. the current playable scenarios are: + +#### Imperial Expat +[*phenotype]: human female +[*starting gear]: Commune survival kit +> Hoping to escape a miserable life deep in the grinding gears of the capitalist machine for the bracing freedom of the frontier, you sought entry as a colonist to the new Commune world of Thousand Petal. Fate -- which is to say, terrorists -- intervened, and you wound up stranded on Farthest Shadow with little more than the nanosuit on your back, ship blown to tatters and your soul thoroughly mauled by the explosion of a twisted alien artifact -- which SOMEONE neglected to inform you your ride would be carrying. +> At least you got some handy psionic powers out of this whole clusterfuck. Hopefully they're safe to use. + +#### Gentleman Adventurer [!(unimplemented)] +[*phenotype]: human male +[*starting gear]: Imperial survival kit +> Tired of the same-old-same-old, sick of your idiot contemporaries, exasperated with the shallow soul-rotting luxury of life as landless lordling, and earnestly eager to enrage your father, you resolved to see the Reach in all her splendor. Deftly evading the usual tourist traps, you finagled your way into the confidence of the Commune ambassador with a few modest infusions of Father's money -- now [!that] should pop his monocle -- and secured yourself a seat on a ride to their brand-new colony at Thousand Petal. How exciting -- a genuine frontier outing! + +#### Terrorist Tagalong +[*phenotype]: human female +[*starting gear]: star merc combat kit +> It turns out there's a *reason* Crown jobs pay so well. + +#### Tradebird Bodyguard [!(unimplemented)] +[*phenotype]: male Usukwinti +[*starting gear]: Usuk survival kit +> You've never understood why astropaths of all people [!insist] on bodyguards. This one could probably make hash of a good-sized human batallion, if her feathers were sufficiently ruffled. Perhaps it's a status thing. Whatever the case, it's easy money. +> At least, it was supposed to be. ### controls summon your Suit Interface by pressing the [*E] / [*Inventory] key. this will allow you to move items around in your inventory, but more importantly, it also allows you select or configure your Interaction Mode. the top three buttons can be used to select (or deactivate) an Interaction Mode. an Interaction Mode can be configured by pressing the button immediately below. the active Interaction Mode controls the behavior of the mouse buttons when no item is selected in the hotbar. @@ -69,10 +93,46 @@ hold [*Aux1] to activate your selected Maneuver. by default this is Sprint, which will consume stamina to allow you to run much faster. certain suits offer the Flight ability, which allows slow, mid-range flight. you can also unlock the psionic ability Lift, which allows very rapid flight but consumes psi at a prodigious rate. you can only have one Maneuver active at a time, whether this is a Psi Maneuver (consuming psi), a Suit Maneuver (consuming battery), or a Body Maneuver (consuming stamina). Maneuvers are activated in their respective panel. +### stats +to survive and thrive on Farthest Shadow, you need to manage your stats carefully. stats impact gameplay and other stats in various ways. +* [*health]: self-explanatory. it hits zero, you die. measured in hit points (hp) + ** regenerates very slowly, though nanomedical suites improve this considerably +* [*stamina]: how long you can sustain intense physical activity. measured in sprinting meters (m) +** regenerates quickly, depending on [*fatigue] +** consumed by biological abilities such as sprinting or Usuk flight. +** affects [*fatigue]: the lower your stamina bar, the faster your fatigue builds +* [*energy]: the amount of electrical power available to your environment suit. measured in joules (J) +** does not regenerate, but empty batteries can be swapped out to replenish energy in the field +** consumed by all suit abilities, including nanotech and weapons systems +* [*numina]: a measure of your available psionic power. measured in psi (ψ) +** accumulates slowly, depending on [*morale] +* [*nutrition]: how much energy your body has stored. measured in kCal. basal metabolic rate depends on species and sex +** affects [*morale]: if you're starving, you bleed morale more quickly +** affects [*health]: you will die if you do not eat enough +* [*hydration]: stay hydrated uwu. measured in liters (L) +** affects [*morale]: if you're parched, you bleed morale much more quickly +** affects [*health]: you will die very quickly if you do not drink enough +* [*warmth]: maintaining a healthy body temperature is crucially important on Farthest Shadow, which does not have many places a human can be comfortable without a powered environment suit. if you're outside your species' comfort range, your stamina regen and morale expenditure will be impacted. if you're outside your species' [*survival] range, you will start dying of hypo/hyperthermia. most environment suits have built-in thermoelectrics that will try to keep you at a comfortable (or merely survivable, in power save mode) temperature, but this can drain your suit batteries very quickly at extreme temperatures. +** affects [*morale], [*stamina], [*health] +* [*fatigue]: how long you've gone without sleep. +** affects [*stamina] regen: the higher your [*fatigue], the more slowly you regnerate stamina. if you are fully fatigued (exhausted), you do not regenerate stamina +* [*morale]: how happy you are. measured in the number of hours you can continue functioning emotionally +** diminishes over time. can be replenished by various activities, such as eating tasty food +** affects [*numina] regen: the lower your morale, the more slowly you accumulate numina. if you are completely out of morale (depressed), you do not accumulate any numina +* [*irradation]: how much ionizing radiation you've soaked up. measured in grays (Gy) +** your environment suit provides some protection against environmental radiation. how much depends on your model of suit. +** affects [*health]: slows natural healing. at values over 2Gy, you will start to hemorrhage health, though a capable nanomedical system can compensate up to a point +** affects [*numina]: you accumulate numina more quickly in high-rad areas +** naturally diminishes, but very slowly +* [*illness]: not everything on Farthest Shadow's ecosystem is, shall we say, biocompatible. measured in percent +** affects [*morale]: the higher your illness, the more quickly you bleed morale + +your primary stats are shown on your HUD. ancillary stats can be viewed in the "body" panel. + ### psionics there are four types of psionic abilities: Manual, Maneuver, Ritual, and Contextual. you can assign two Manual abilities at any given time and access them with the mouse buttons in Psionics mode.