Index: dev.ct ================================================================== --- dev.ct +++ dev.ct @@ -1,19 +1,19 @@ # starlit development -this file contains information meant for those who wish to develop for Starsoul or build the game from trunk. do NOT add any story information, particularly spoilers; those go in src/lore.ct. +this file contains information meant for those who wish to develop for Starlit or build the game from trunk. do NOT add any story information, particularly spoilers; those go in src/lore.ct. ## tooling starlit uses the following software in the development process: * [*csound] to generate sound effects * [*GNU make] to automate build tasks * [*lua] to automate configure tasks ## building -to run a trunk version of Starsoul, you'll need to install the above tools and run `make` from the base directory. this will: +to run a trunk version of Starlit, you'll need to install the above tools and run `make` from the base directory. this will: * run lua scripts to generate necessary makefiles * generate the game sound effects and install them in mods/starlit/sounds ## policy * copyright of all submitted code must be reassigned to the maintainer. * all code is to be indented with tabs and aligned with spaces; formatting is otherwise up to whoever is responsible for maintaining that code * use [`camelCase], not [`snake_case] and CERTAINLY not [`SCREAMING_SNAKE_CASE] * sounds effects should be contributed in the form of csound files; avoid adding audio files to the repository except for foley effects Index: mods/starlit-eco/init.lua ================================================================== --- mods/starlit-eco/init.lua +++ mods/starlit-eco/init.lua @@ -13,10 +13,19 @@ y_min = 0; y_max = 56; heat_point = 50; humidity_point = 40; }; + weather = { + {-0.900, 'starlit:meteorShower'}; + {-0.700, 'starlit:sstorm'}; + {-0.100, 'starlit:clear'}; + {0.300, 'starlit:cloudy'}; + {0.400, 'starlit:precip'}; + {0.450, 'starlit:storm'}; + {0.500, 'starlit:tstorm'}; + }; }) world.ecology.biomes.link('starlit:forest', { nightTempDelta = -20; waterTempDelta = 0; @@ -29,10 +38,19 @@ y_min = 0; y_max = 256; heat_point = 60; humidity_point = 45; }; + weather = { + {-0.900, 'starlit:meteorShower'}; + {-0.700, 'starlit:sstorm'}; + {-0.100, 'starlit:clear'}; + {0.200, 'starlit:cloudy'}; + {0.400, 'starlit:precip'}; + {0.650, 'starlit:storm'}; + {0.800, 'starlit:tstorm'}; + }; }) world.ecology.biomes.link('starlit:desert', { nightTempDelta = -40; waterTempDelta = 0; @@ -45,10 +63,17 @@ y_min = 0; y_max = 512; heat_point = 70; humidity_point = 10; }; + weather = { + {-0.900, 'starlit:meteorShower'}; + {-0.700, 'starlit:sstorm'}; + {-0.100, 'starlit:clear'}; + {0.400, 'starlit:cloudy'}; + {0.850, 'starlit:tstorm'}; + }; }) world.ecology.biomes.link('starlit:ocean', { nightTempDelta = -35; waterTempDelta = 5; @@ -59,10 +84,19 @@ heat_point = 60; humidity_point = 70; node_top = 'starlit:sand', depth_top = 1; node_filler = 'starlit:sand', depth_filler = 3; }; + weather = { + {-0.900, 'starlit:meteorShower'}; + {-0.700, 'starlit:sstorm'}; + {-0.100, 'starlit:clear'}; + {0.300, 'starlit:cloudy'}; + {0.500, 'starlit:precip'}; + {0.650, 'starlit:storm'}; + {0.800, 'starlit:tstorm'}; + }; }) world.ecology.biomes.link('starlit:shiverdeep', { nightTempDelta = -25; waterTempDelta = 5; @@ -75,10 +109,19 @@ humidity_point = 30; node_water_top = 'starlit:ice', depth_water_top = 1; node_top = 'starlit:undergloam', depth_top = 1; node_filler = 'starlit:soil', depth_filler = 2; }; + weather = { + {-0.900, 'starlit:meteorShower'}; + {-0.700, 'starlit:sstorm'}; + {-0.100, 'starlit:clear'}; + {0.200, 'starlit:cloudy'}; + {0.400, 'starlit:precip'}; + {0.650, 'starlit:storm'}; + {0.900, 'starlit:tstorm'}; + }; }) world.ecology.biomes.link('starlit:silthaven', { nightTempDelta = -5; waterTempDelta = 5; @@ -90,10 +133,19 @@ heat_point = 30; humidity_point = 30; -- node_top = 'starlit:undergloam', depth_top = 1; node_filler = 'starlit:lifesilt', depth_filler = 5; }; + weather = { + {-0.900, 'starlit:meteorShower'}; + {-0.700, 'starlit:sstorm'}; + {-0.100, 'starlit:clear'}; + {0.400, 'starlit:cloudy'}; + {0.600, 'starlit:precip'}; + {0.750, 'starlit:storm'}; + {0.900, 'starlit:tstorm'}; + }; }) world.ecology.biomes.link('starlit:barrens', { nightTempDelta = -20; waterTempDelta = 5; @@ -103,10 +155,19 @@ y_max = 512; y_min = -512; heat_point = 0; humidity_point = 0; }; + weather = { + {-0.900, 'starlit:meteorShower'}; + {-0.600, 'starlit:sstorm'}; + {-0.100, 'starlit:clear'}; + { 0.300, 'starlit:cloudy'}; + { 0.600, 'starlit:precip'}; + { 0.850, 'starlit:storm'}; + { 0.900, 'starlit:tstorm'}; + }; }) minetest.register_craftitem('starlit_eco:fiber', { description = "Plant Fiber"; groups = {fiber = 1}; inventory_image = lib.image('starlit-eco-plant-fiber.png'):shift(lib.color(0,1,0)):render(); Index: mods/starlit-tech/init.lua ================================================================== --- mods/starlit-tech/init.lua +++ mods/starlit-tech/init.lua @@ -160,11 +160,11 @@ minetest.register_node('starlit_tech:crate', { short_description = 'Crate'; description = starlit.ui.tooltip { title = 'Crate'; - desc = 'A sturdy but lightweight storage crate woven from graphene.'; + desc = 'A sturdy but lightweight aluminum storage crate.'; props = { {title='Mass', affinity='info', desc='100g'} }; }; drawtype = 'nodebox'; node_box = { type = 'fixed'; @@ -193,11 +193,11 @@ reverseEngineer = { complexity = 1; sw = 'starlit_tech:schematic_crate'; }; recover = starlit.type.fab { - element = { carbon = 100; }; + element = { aluminum = 100; }; time = { shred = 1; shredPower = 3; }; }; Index: mods/starlit/init.lua ================================================================== --- mods/starlit/init.lua +++ mods/starlit/init.lua @@ -90,11 +90,14 @@ ecology = { plants = lib.registry.mk 'starlit:plants'; trees = lib.registry.mk 'starlit:trees'; biomes = lib.registry.mk 'starlit:biome'; }; - climate = {}; + climate = { + weather = lib.registry.mk 'starlit:weather'; + weatherMap = {} + }; scenario = {}; planet = { gravity = 7.44; orbit = 189; -- 1 year is 189 days revolve = 20; -- 1 day is 20 irl minutes @@ -173,10 +176,12 @@ }; }; jobs = {}; } + +-- TODO deal with core.DEFAULT_PHYSICS once it hits master starlit.cfgDir = minetest.get_worldpath() .. '/' .. starlit.ident local logger = function(module) local function argjoin(arg, nxt, ...) Index: mods/starlit/user.lua ================================================================== --- mods/starlit/user.lua +++ mods/starlit/user.lua @@ -105,10 +105,13 @@ overlays = {}; cooldownTimes = { stamina = 0; alarm = 0; }; + env = { + weather = nil; + }; } end; __index = { -------------- -- overlays -- Index: mods/starlit/world.lua ================================================================== --- mods/starlit/world.lua +++ mods/starlit/world.lua @@ -44,11 +44,11 @@ surfaceHumid = humid; } end local vdsq = lib.math.vdsq -function world.climate.temp(pos) --> irradiance at pos in W +function world.climate.temp(pos, timeshift) --> irradiance at pos in W local cl = world.climate.eval(pos) local radCenters = starlit.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) @@ -81,12 +81,122 @@ power = power * (1 - (obstruct/5)) irradiance = irradiance + power end end end + local w = world.climate.weatherAt(pos, timeshift) + return irradiance + cl.surfaceTemp end + +function world.ecology.biomeAt(pos) + return world.ecology.biomes.db[minetest.get_biome_name(minetest.get_biome_data(pos).biome)] +end + + +minetest.after(0, function() + world.climate.weatherMap.kind = minetest.get_perlin { + seed = 0x925afe; + octaves = 2; + spread = vector.new(256,256,120); + }; + world.climate.weatherMap.severity = minetest.get_perlin { + seed = 0x39de1d; + octaves = 1; + spread = vector.new(256,256,60); + }; +end) + +function world.climate.weatherAt(pos, timeshift) + timeshift = timeshift or 0 + local wv = world.climate.weatherMap.kind:get_3d(vector.new(pos.x, pos.z, minetest.get_gametime() + timeshift)) + local sev = world.climate.weatherMap.severity:get_3d(vector.new(pos.x, pos.z, minetest.get_gametime() + timeshift)) + local b = world.ecology.biomeAt(pos) + local w = 'starlit:clear' + for i,v in ipairs(b.weather) do + if wv < v[1] then + w = v[2] + break + end + end + local mods = { + cloudCover = 0; + rain = 0; -- affects plant growth + snow = 0; -- spawns snow layer + fog = 0; + temp = 0; + hum = 0; + rad = 0; + } + return world.climate.weather.db[w], sev +end + + +-- weather manages particle systems, and provides modifiers for +-- temp, cloud cover, received precipitation, and fog + +world.climate.weather.link('starlit:clear', { + name = 'Clear'; +}) +world.climate.weather.link('starlit:cloudy', { + name = 'Cloudy'; + mod = function(m, temp, hum, sev) + m.cloudCover = math.max(m.cloudCover, sev) + end; +}) +world.climate.weather.link('starlit:precip', { + name = function(temp, hum, sev) + if temp < 0 then return 'Snow' else return 'Rain' end + end; + mod = function(m, temp, hum, sev) + m.cloudCover = math.max(m.cloudCover, sev) + if temp < 0 then + m.snow = math.max(m.snow, sev/2) + else + m.rain = math.max(m.rain, sev/2) + end + end; +}) +world.climate.weather.link('starlit:storm', { + name = function(temp, hum, sev) + if temp < 0 then + if sev > .5 + then return 'Blizzard' + else return 'Snowstorm' + end + else + if sev > .5 + then return 'Monsoon' + else return 'Rainstorm' + end + end + end; + mod = function(m, temp, hum, sev) + m.cloudCover = math.max(m.cloudCover, sev) + if temp < 0 then + m.snow = math.max(m.snow, sev/2 + .5) + else + m.rain = math.max(m.rain, sev/2 + .5) + end + end; +}) +world.climate.weather.link('starlit:tstorm', { + name = 'Thunderstorm'; + danger = 1; + mod = function(m, temp, hum, sev) + m.cloudCover = math.max(m.cloudCover, sev) + m.danger = (sev>.5) and 2 or 1 + end; +}) +world.climate.weather.link('starlit:sstorm', { + name = 'Solar Storm'; + danger = 2; +}) +world.climate.weather.link('starlit:meteorShower', { + name = 'Meteor Shower'; + danger = 2; +}) world.ecology.biomes.foreach('starlit:biome-gen', {}, function(id, b) b.def.name = id minetest.register_biome(b.def) end) @@ -162,11 +272,11 @@ b.decoration = minetest.register_decoration(dec) end) local toward = lib.math.toward local hfinterval = 1.5 -starlit.startJob('starlit:heatflow', hfinterval, function(delta) +starlit.startJob('starlit:temps', 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) @@ -177,13 +287,24 @@ -- 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 = starlit.constant.heat.thermalConductivity + local now = minetest.get_gametime() for name,user in pairs(starlit.activeUsers) do local tr = user:species().tempRange local t = starlit.world.climate.temp(user.entity:get_pos()) + + local weather,wsev = starlit.world.climate.weatherAt(user.entity:get_pos()) + local wfac + if user.env.weather == nil + then wfac = 1 + else wfac = (now - user.env.weather.when) / 10 + end + if user.env.weather == nil or now - user.env.weather.when >= 10 then + user.env.weather = {when = now, what = weather} + end 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 hz = user:tempHazard(t) Index: mods/vtlib/init.lua ================================================================== --- mods/vtlib/init.lua +++ mods/vtlib/init.lua @@ -14,13 +14,13 @@ end component 'dbg' -- primitive manip -component 'tbl' component 'class' component 'math' +component 'tbl' component 'str' -- reading and writing data formats component 'marshal' Index: mods/vtlib/math.lua ================================================================== --- mods/vtlib/math.lua +++ mods/vtlib/math.lua @@ -22,10 +22,12 @@ return dsq / (dist^2) -- [0,1) == less then -- 1 == equal -- >1 == greater than end + +local unpack = table.unpack or unpack -- produce an SI expression for a quantity fn.si = function(unit, val, full, uncommonScales, prec) if val == 0 then return '0 ' .. unit end local scales = { @@ -53,11 +55,11 @@ return unit end for i, s in ipairs(scales) do local amt, smaj, pmaj, cmaj, - smin, pmin, cmin = lib.tbl.unpack(s) + smin, pmin, cmin = unpack(s) if math.abs(val) > 1 then if uncommonScales or cmaj then local denom = 10^amt Index: mods/vtlib/tbl.lua ================================================================== --- mods/vtlib/tbl.lua +++ mods/vtlib/tbl.lua @@ -109,15 +109,15 @@ local keys = fn.keys(lst) local k = keys[math.random(#keys)] return k, lst[k] end -fn.unpack = table.unpack or unpack or function(tbl,i) +fn.unpack = table.unpack or unpack --[[or function(tbl,i) i = i or 1 if #tbl == i then return tbl[i] end return tbl[i], fn.unpack(tbl, i+1) -end +end]] fn.split = function(...) return fn.unpack(lib.str.explode(...)) end fn.each = function(tbl,f) local r = {} @@ -253,7 +253,14 @@ local s = {} fn.setOrD(s, ...) return s end +fn.lerp = function(t, a, b) + local r = {} + for k in next, a do + r[k] = lib.math.lerp(t, a[k], b[k]) + end + return r +end return fn Index: src/lore.ct ================================================================== --- src/lore.ct +++ src/lore.ct @@ -71,11 +71,11 @@ it is known that the Eluthrai are of great intelligence: a 200pt IQ makes you a laughable simpleton in their eyes. it is estimated that the average individual has an IQ of 290, close to the theoretical maximum where organized intelligence dissolves into a sea of blinding psychosis. consequently, they are very conservative and cautious of new ideas; their culture emphasises skepticism and avoiding rash action. early Eluthran history was extremely warlike, and they could have easily devastated the whole of the Reach in their fanatical pursuit of competing ideologies. however, a philosophical tradition emerged from the rubble of a particularly ruinous exchange that offered the correct tools for neutering the more dangerous aspects of their intelligence -- after the centuries proved its value, the Philosophers exterminated all the remaining Eluthrai who had not adopted their practices. it was a coldblooded but rational act of genocide: an individual Eluthra is intelligent enough to bootstrap an industrial civilization from first principles with a few years of effort. an entire civilization of them, devoid of self-control? that wasn't merely a threat to the Philosophers; it was a threat to the Galaxy entire. -the Eluthrai have a single common language, Eluthric, which they use in interstellar discourse and in the sciences. however, the different far-flung colonies have their own individual tongues as well. Eluthric has the largest vocabulary of any known language, with over twenty million words. an Eluthra who hasn't learned at least a million of them by adolescence is deemed slow. +the Eluthrai have a single common language, Iluthanna ("Eluthric" as the Crystal Sea calls it), which they use in interstellar discourse and in the sciences. however, the different far-flung colonies have their own individual tongues as well. Eluthric has the largest vocabulary of any known language, with over twenty million words. an Eluthra who hasn't learned at least a million of them by adolescence is deemed slow. they have developed very slowly since the Philosophers came to power, but were already so advanced that nobody is expected to exceed them any time soon. Eluthran civilization is united under the rule of the Philosopher-King, an enlightened despot with unrestricted power, in a complex web of fealty and patronage collectively named the Corcordance of the Eluthrai. while the First Philosopher died tens of thousands of years ago, he had the foresight to prepare a successor to take his place in case of his assassination or ill-fortune. in all those years, power has changed hands only three times. the current Philosopher-King has ruled for eight thousand years.