ADDED mods/starlit-eco/init.lua Index: mods/starlit-eco/init.lua ================================================================== --- mods/starlit-eco/init.lua +++ mods/starlit-eco/init.lua @@ -0,0 +1,80 @@ +local world = starlit.world +local lib = starlit.mod.lib + +world.ecology.biomes.link('starlit:steppe', { + nightTempDelta = -30; + waterTempDelta = 0; + -- W Sp Su Au W + seasonalTemp = {-50, -10, 5, 5, -20, -50}; + def = { + node_top = 'starlit:greengraze', depth_top = 1; + node_filler = 'starlit:soil', depth_filler = 4; + node_riverbed = 'starlit:sand', depth_riverbed = 4; + y_min = 0; + y_max = 512; + heat_point = 10; + humidity_point = 30; + }; +}) + +world.ecology.biomes.link('starlit:forest', { + nightTempDelta = -20; + waterTempDelta = 0; + -- W Sp Su Au W + seasonalTemp = {-40, -8, 10, 10, -14, -40}; + def = { + node_top = 'starlit:greengraze', depth_top = 1; + node_filler = 'starlit:soil', depth_filler = 4; + node_riverbed = 'starlit:sand', depth_riverbed = 4; + y_min = -100; + y_max = 256; + heat_point = 13; + humidity_point = 40; + }; +}) + +world.ecology.biomes.link('starlit:desert', { + nightTempDelta = -40; + waterTempDelta = 0; + -- W Sp Su Au W + seasonalTemp = {-10, -5, 15, 15, -5, -10}; + def = { + node_top = 'starlit:sand', depth_top = 1; + node_filler = 'starlit:sand', depth_filler = 4; + node_riverbed = 'starlit:sand', depth_riverbed = 4; + y_min = 0; + y_max = 512; + heat_point = 20; + humidity_point = 10; + }; +}) + +world.ecology.biomes.link('starlit: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 = 'starlit:sand', depth_top = 1; + node_filler = 'starlit:sand', depth_filler = 3; + }; +}) + +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(); + _starlit = { + recover_vary = function(rng, ctx) + return starlit.type.fab { + element = { carbon = rng:int(0,1) }; + }; + end; + }; +}) + +starlit.include 'plants' +starlit.include 'trees' ADDED mods/starlit-eco/mod.conf Index: mods/starlit-eco/mod.conf ================================================================== --- mods/starlit-eco/mod.conf +++ mods/starlit-eco/mod.conf @@ -0,0 +1,4 @@ +name = starlit_eco +title = starlit ecosphere +description = plants and biomes +depends = starlit ADDED mods/starlit-eco/plants.lua Index: mods/starlit-eco/plants.lua ================================================================== --- mods/starlit-eco/plants.lua +++ mods/starlit-eco/plants.lua @@ -0,0 +1,175 @@ +local world = starlit.world +local lib = starlit.mod.lib + +local function dropCat(a,b) + local function strdrop(s) + if type(s) == 'string' then + return {max_items = 1; items = {{items={s}}}} + else return s end + end + a = strdrop(a) + b = strdrop(b) + return { + max_items = a.max_items + b.max_items; + items = lib.tbl.append(a.items, b.items); + } +end +local function stalkPlant(def) + local function stage(s, drops, swap) + return { + tex = lib.image(string.format('starlit-eco-plant-stalk%s.png',s)):shift(def.color); + drop = drops; + swap = swap; + } + end + local function plantMatter(opts) + local dps = { + seed = def.seed; + fiber = def.fiber; + leaf = def.leaf and def.leaf.drop or nil; + berry = def.berries and def.berries.drop or nil; + } + local t = {max_items=0, items={}} + for k,v in pairs(opts) do + if dps[v] then t = dropCat(dps[v], t) end + end + return t + end + + local fg + local stages = { + stage('-grow-1', ''); + stage('-grow-2', plantMatter{'seed'}); + stage('-grow-3', plantMatter{'seed','fiber'}); + stage('', plantMatter{'seed','seed','fiber'}); + }; + if def.leaf then + local ps = stage('', plantMatter{'seed','seed','seed','fiber','leaf'}) + ps.tex = ps.tex .. lib.image('starlit-eco-plant-stalk-petals.png'):shift(def.leaf.color) + table.insert(stages, ps) + end + if def.berries then + local ps = lib.image.clone(stages[#stages]) + ps.tex = ps.tex:blit(lib.image('starlit-eco-plant-stalk-berries.png'):shift(def.berries.color)) + ps.drop = def.berries.drop; + ps.swap = #stages + table.insert(stages, ps) + end + + if def.biolum then for i,v in ipairs(stages) do + v.biolum = math.floor(def.biolum * (i/#stages)) + end end + + world.ecology.plants.link(def.id, { + name = def.name; + stages = stages; + decoration = def.decoration; + meshOpt = 3; + }) +end + +local function simpleDrop(rarity, what) + return { + max_items = 1; + items = { + {rarity = rarity, items = {what}}; + }; + }; +end + +function stalkPlantAuto(def) + local id = def.id + local id_berries = def.id .. '_berry' + local id_seed = def.id .. '_seed' + + local p = lib.tbl.proto({}, def) + if def.berries then + local bdef = lib.tbl.defaults({ + name = def.name .. ' Berry'; + tex = lib.image('starlit-eco-plant-berry-bunch.png'):shift(def.berries.color or def.color):render(); + color = def.color; + }, def.berries) + bdef.name = bdef.name + starlit.item.food.link(id_berries, bdef) + p.berries = { + color = def.berries.color; + drop = id_berries; + } + end + + if def.seed then + local sdef = lib.tbl.defaults({ + name = def.name .. ' Seed'; + tex = lib.image('starlit-eco-plant-seeds.png'):shift(def.seed.color or def.color):render(); + color = def.color; + grow = {kind = 'plant', id = id}; + }, def.seed) + + starlit.item.seed.link(id_seed, sdef) + p.seed = id_seed + end + + return stalkPlant(p) +end + +stalkPlantAuto { + id = 'starlit_eco:moondrop'; + name = "Moondrop"; + fiber = simpleDrop(2, 'starlit_eco:fiber'); + seed = {}; + color = lib.color(.5, .7, 1); + biolum = 5; + leaf = { + color = lib.color(.6, .8, .8); + drop = simpleDrop(2, 'starlit_eco:moondrop_petal'); + }; + berries = { + desc = "The fruits of the moondrop are not very nutritious, but their peculiar sweet-sour flavor profile makes them one Thousand Petal's great delicacies"; + color = lib.color(1,0,.4); + nourish = 10; + hydrate = 0.05; + taste = 1 * 60; + mass = 1; + }; + decoration = { + place_on = 'starlit:greengraze'; + fill_ratio = 0.03; + biomes = {'starlit:steppe', 'starlit:forest'}; + y_min = 10; + y_max = 100; + }; +} + +stalkPlantAuto { + id = 'starlit_eco:dustrose'; + name = "Dust Rose"; + fiber = simpleDrop(2, 'starlit_eco:fiber'); + seed = {}; + color = lib.color(.3, .1, .2); + leaf = { + color = lib.color(.7, .4, .8); + drop = simpleDrop(2, 'starlit_eco:dustrose_petal'); + }; + decoration = { + place_on = 'starlit:greengraze'; + fill_ratio = 0.03; + biomes = {'starlit:forest'}; + y_min = -50; + y_max = 50; + }; +} + +stalkPlantAuto { + id = 'starlit_eco:harrowstalk'; + name = "Harrowstalk"; + fiber = simpleDrop(2, 'starlit_eco:fiber'); + seed = {}; + color = lib.color(.3, .2, .1); + decoration = { + place_on = 'starlit:sand'; + fill_ratio = 0.03; + biomes = {'starlit:ocean', 'starlit:desert'}; + y_min = -30; + y_max = 400; + }; +} ADDED mods/starlit-eco/trees.lua Index: mods/starlit-eco/trees.lua ================================================================== --- mods/starlit-eco/trees.lua +++ mods/starlit-eco/trees.lua @@ -0,0 +1,231 @@ +local world = starlit.world +local lib = starlit.mod.lib + +local function woodProps(def) return { + recover = starlit.type.fab { + time = { shred = 2; }; + cost = { shredPower = 1.5; }; + }; + recover_vary = function(rng, ctx) + return starlit.type.fab { + element = { + potassium = rng:int(0,1); + carbon = rng:int(0,2); + } + }; + end; + mass = 1.5e3; +} end + +local function leafProps(def) return { + recover = starlit.type.fab { + time = { shred = .5; }; + cost = { shredPower = .3; }; + }; + recover_vary = function(rng, ctx) + return starlit.type.fab { + element = { + potassium = rng:int(0,2); + carbon = rng:int(0,1); + } + }; + end; + mass = 100; +} end + +local function regLog(id, def) + local base = table.copy(def) + base.groups = base.groups or {} + base.groups.wood = 1 + base.groups.log = 1 + base.groups.falling_node = 1 + + local live = table.copy(base) + live.drop = id + live.groups.alive = 1 + minetest.register_node(id, base) + minetest.register_node(id..'_live', live) +end + +regLog('starlit_eco:lambent_pine_log', { + description = 'Lambent Pine Log'; + drawtype = 'normal'; + tiles = { + 'starlit-eco-tree-lambent-pine-trunk-top.png'; + 'starlit-eco-tree-lambent-pine-trunk.png'; + }; + _starlit = woodProps{}; +}) + + +starlit.item.food.link('starlit_eco:lambent_pine_berry', { + name = 'Lambent Pine Berry'; + desc = 'Though packed with human-compatible nutrients, these berries are almost painfully sour when eaten raw.'; + tex = lib.image('starlit-eco-plant-berry-bunch.png'):shift{hue=180,sat=-30,lum=30}:render(); + nourish = 150; + taste = -2 * 60; + mass = 2; +}) + +starlit.item.seed.link('starlit_eco:lambent_pine_seed', { + name = 'Lambent Pine Seed'; + tex = lib.image('starlit-eco-plant-seeds.png'):shift{hue=150, sat=-50, lum=80}:render(); + grow = {kind = 'tree', id = 'starlit_eco:lambent_pine'}; +}) + +minetest.register_node('starlit_eco:lambent_pine_bulb', { + description = 'Lambent Pine Bulb'; + drawtype = 'nodebox'; + connects_to = {'starlit_eco:lambent_pine_needles'}; + node_box = { + type = 'connected'; + connect_top = { + {-.1, .5, -.1, + .1, .3, .1}; + }; + fixed = { + {-.2, -.2, -.2, + .2, .3, .2}; + }; + }; + light_source = 6; + drop = { + max_items = 3; + items = { + {rarity = 4, items = {'starlit_eco:lambent_pine_seed'}}; + {rarity = 3, items = {'starlit_eco:lambent_pine_berry'}}; + {rarity = 2, items = {'starlit_eco:lambent_pine_berry'}}; + {items = {'starlit_eco:lambent_pine_berry'}}; + }; + }; + groups = {plant=1, attached_node = 4}; + tiles = { + 'starlit-eco-tree-lambent-pine-bulb.png'; + }; + _starlit = woodProps{}; +}) + +minetest.register_node('starlit_eco:lambent_pine_needles', { + description = 'Lambent Pine Needles'; + groups = {plant = 1;}; + drop = ''; + tiles = { + 'starlit-eco-tree-lambent-pine-needles.png'; + 'starlit-eco-tree-lambent-pine-needles.png'; + }; + _starlit = leafProps{}; +}); + +regLog('starlit_eco:starblossom_log', { + description = 'Starblossom Log'; + drawtype = 'normal'; + tiles = { + 'starlit-eco-tree-starblossom-trunk-top.png'; + 'starlit-eco-tree-starblossom-trunk.png'; + }; + _starlit = woodProps{}; +}) + +minetest.register_node('starlit_eco:starblossom_leaves', { + description = 'Starblossom Leaves'; + groups = {plant = 1;}; + drop = ''; + tiles = { + 'starlit-eco-tree-starblossom-leaves.png'; + 'starlit-eco-tree-starblossom-leaves.png'; + }; + _starlit = leafProps{}; +}); +minetest.register_node('starlit_eco:starblossom_leaves_shine', { + description = 'Shining Starblossom Leaves'; + groups = {plant = 1;}; + drop = ''; + paramtype = 'light'; + light_source = 4; + tiles = { + 'starlit-eco-tree-starblossom-leaves.png'; + 'starlit-eco-tree-starblossom-leaves.png'; + 'starlit-eco-tree-starblossom-leaves.png^starlit-eco-tree-starblossom-shine.png'; + }; + _starlit = leafProps{}; +}); + +starlit.world.ecology.trees.meld { + ['starlit_eco:lambent_pine'] = { + name = 'Lambent Pine'; + def = { + axiom = 'TTB'; + rules_a = '[-[&Tf]Tf][+[^Tf]Tf]f'; + rules_c = '[-[&Tff]Tff][+[^Tff]Tff]f'; + rules_b = 'TCA[f]B'; + + -- nodes + trunk = 'starlit_eco:lambent_pine_log_live'; + leaves = 'starlit_eco:lambent_pine_needles'; + + angle = 90; + + iterations = 7; + random_level = 3; + trunk_type = 'single'; + thin_branches = true; + + fruit = 'starlit_eco:lambent_pine_bulb'; + fruit_chance = 0; + }; + decorate = { + { biomes = {'starlit:forest'}; + place_on = 'starlit:greengraze'; + fill_ratio = 0.004; + y_min = -30, y_max = 500; + seed = 0xe8190e; + }; + { biomes = {'starlit:steppe'}; + place_on = 'starlit:greengraze'; + fill_ratio = 0.002; + y_min = -30, y_max = 500; + seed = 0xe8190e; + }; + }; + }; + + ['starlit_eco:starblossom'] = { + def = { + axiom = 'TTTTATTTT[&&B]' .. string.rep(string.rep('/', 4) .. '[&&B]', math.floor(360/40)); + rules_a = 'TTTa'; + rules_b = 'FF&B'; + + trunk_type = 'double'; + trunk = 'starlit_eco:starblossom_log_live'; + leaves = 'starlit_eco:starblossom_leaves'; + leaves2 = 'starlit_eco:starblossom_leaves_shine'; + leaves2_chance = 20; + + angle = 10; + iterations = 13; + random_level = 5; + }; + decorate = { + { biomes = {'starlit:forest'}; + place_on = 'starlit:greengraze'; + fill_ratio = 0.001; + y_min = -20, y_max = 512; + }; + }; + }; +} + +minetest.register_abm { + label = "lambent pine fruiting"; + nodenames = {'starlit_eco:lambent_pine_needles'}; + neighbors = {'starlit_eco:lambent_pine_log_live'}; + chance = 40; + interval = 80; + catch_up = true; + action = function(pos, node) + local po = pos:offset(0,-1,0) + if minetest.get_node(po).name == "air" then + minetest.add_node(po, {name='starlit_eco:lambent_pine_bulb'}) + end + end; +} Index: mods/starlit-electronics/init.lua ================================================================== --- mods/starlit-electronics/init.lua +++ mods/starlit-electronics/init.lua @@ -520,11 +520,10 @@ ----------- E.sw = {} function E.sw.findSchematicFor(item) local id = ItemStack(item):get_name() - print(id) local fm = minetest.registered_items[id]._starlit if not (fm and fm.fab and fm.fab.reverseEngineer) then return nil end local id = fm.fab.reverseEngineer.sw return id, starlit.item.sw.db[id] end Index: mods/starlit/init.lua ================================================================== --- mods/starlit/init.lua +++ mods/starlit/init.lua @@ -350,13 +350,11 @@ return a.type ~= b.type or a.type == 'node' and vector.new(a.under) ~= vector.new(b.under) or a.type == 'object' and a.ref ~= b.ref end local function triggerPower(_, luser, point) - for k,v in pairs(starlit.activeUsers) do - print (k,v) end - print("trigger", luser, luser:get_player_name()) +-- print("trigger", luser, luser:get_player_name()) local user = starlit.activeUsers[luser:get_player_name()] local oldTgt = user.action.tgt user.action.tgt = point if bit.band(user.action.bits, 0x100)==0 then user.action.bits = bit.bor(user.action.bits, 0x100) @@ -390,13 +388,13 @@ wield_image = "wieldhand.png", wield_scale = {x=1,y=1,z=2.5}, tool_capabilities = { groupcaps = { plant = {maxlevel=1, times = {.50}}; - dirt = {maxlevel=1, times = {2.5}}; - log = {maxlevel=1, times = {1}}; + -- sand, dirt, gravel + looseClump = {maxlevel=1, times = {1.5, 2.5}}; }; } }) minetest.register_on_player_inventory_action(function(luser, act, inv, p) @@ -431,16 +429,21 @@ r(pos.y), r(pos.z) ) end for i, it in ipairs(drops) do - local it = minetest.add_item(jitter(pos), it) - local dp = vector.new(0,0,0) - if digger then dp = digger:get_pos() end - local delta = dp - it:get_pos() - it:add_velocity(vector.new(delta.x,0,delta.z)); + if type(it) == 'string' then it = ItemStack(it) end + if not it:is_empty() then + local ent = minetest.add_item(jitter(pos), it) + if ent ~= nil then -- avoid crash when dropping unknown item + local dp = vector.new(0,0,0) + if digger then dp = digger:get_pos() end + local delta = dp - ent:get_pos() + ent:add_velocity(vector.new(delta.x,0,delta.z)); + end + end end end -- TODO timer iterates live UI Index: mods/starlit/interfaces.lua ================================================================== --- mods/starlit/interfaces.lua +++ mods/starlit/interfaces.lua @@ -128,11 +128,12 @@ return p end local function pptrMatch(a,b) if a == nil or b == nil then return false end - return a.chipID == b.chipID and a.pgmIndex == b.pgmIndex + return (a.chipID ~= nil and (a.chipID == b.chipID and a.pgmIndex == b.pgmIndex)) + or (a.ref ~= nil and a.ref == b.ref) end starlit.interface.install(starlit.type.ui { id = 'starlit:user-menu'; pages = { @@ -359,14 +360,53 @@ {kind = 'label', w=2, h=barh, text = s.name}; {kind = 'hbar', w=4, h=barh, fac = sv, text = st, color=s.color}; }) end local abilities = { - {id = 'abl_sprint', label = 'Sprint', img = 'starlit-ui-icon-ability-sprint.png'}; + maneuver = {}; + direct = {}; + passive = {}; } - table.insert(tb, wrapMenu(6.25,4, 1,2, abilities)) + state.abilityMap = {} + for i, a in pairs(user:species().abilities) do + local id = 'abl_'..a.id; + state.abilityMap[id] = a; + table.insert(abilities[a.powerKind], { + id = id; + label = a.name; + desc = a.desc; + img = a.img; + + -- HACK + color = pptrMatch(user.power.maneuver, {ref=a}) and + {hue = 150, sat = 0, lum = .3} or nil; + }); + end + for i, n in ipairs {'maneuver', 'direct', 'passive'} do + if next(abilities[n]) then + table.insert(tb, wrapMenu(6.25,4, 1,2, abilities[n])) + end + end return starlit.ui.build(tb) + end; + handle = function(state, user, q) + for k,a in pairs(state.abilityMap) do + if q[k] then + if a.powerKind == 'maneuver' then + if pptrMatch(user.power.maneuver, {ref=a}) then + user.power.maneuver = nil + else + user.power.maneuver = {ref=a} + end + user:suitSound 'starlit-configure' + return true + elseif a.powerKind == 'direct' then + elseif a.powerKind == 'passive' then + else error('bad ability kind ' .. a.powerKind) end + break + end + end end; }; suit = { render = function(state, user) local suit = user:getSuit() Index: mods/starlit/species.lua ================================================================== --- mods/starlit/species.lua +++ mods/starlit/species.lua @@ -12,10 +12,22 @@ } end -- constants local animationFrameRate = 60 + +local bioAbilities = { + sprint = { + id = 'sprint'; + name = 'Sprint'; + desc = 'Put on a short burst of speed at the cost of some stamina'; + img = 'starlit-ui-icon-ability-sprint.png'; + powerKind = 'maneuver'; + run = function(user, ctx) + end; + }; +} local species = { human = { name = 'Human'; desc = 'The weeds of the galactic flowerbed. Humans are one of the Lesser Races, excluded from the ranks of the Starlit by souls that lack, in normal circumstances, external psionic channels. Their mastery of the universe cut unexpectedly short, forever locked out of FTL travel, short-lived without augments, and alternately pitied or scorned by the lowest of the low, humans flourish nonetheless due to a capacity for adaptation unmatched among the Thinking Few, terrifyingly rapid reproductive cycles -- and a keen facility for bribery. While the lack of human psions remains a sensitive topic, humans (unlike the bitter and emotional Kruthandi) are practical enough to hire the talent they cannot possess, and have even built a small number of symbiotic civilizations with the more indulging of the Powers. In a galaxy where nearly all sophont life is specialized to a fault, humans have found the unique niche of occupying no particular niche.'; @@ -60,10 +72,11 @@ irradiation = 0.8; -- you are smaller, so it takes less rads to kill ya sturdiness = 0; -- women are more fragile and thus susceptible to blunt force trauma metabolism = .150; -- kCal/s painTolerance = 0.4; dehydration = 10e-4; -- L/s + speed = 1.1; }; }; male = { name = 'Human Male'; eyeHeight = 1.6; @@ -80,14 +93,16 @@ painTolerance = 1.0; lungCapacity = 1.0; sturdiness = 0.3; metabolism = .150; -- kCal/s dehydration = 15e-4; -- L/s + speed = 1.0; }; }; }; traits = {}; + abilities = {bioAbilities.sprint}; }; } starlit.world.species = { Index: mods/starlit/stats.lua ================================================================== --- mods/starlit/stats.lua +++ mods/starlit/stats.lua @@ -24,30 +24,30 @@ starlit.world.stats = { psi = {min = 0, max = 500, base = 0, desc = U('ψ', 10), color = C(320), name = 'Numina'}; -- numina is measured in daψ warmth = {min = -1000, max = 1000, base = 0, desc = U('°C', 10, true), color = C(5), name = 'Warmth'}; -- warmth in measured in d°C - fatigue = {min = 0, max = 76 * 60, base = 0, desc = U('hr', 60, true), color = C(288,.3,.5), name = 'Fatigue'}; + fatigue = {min = 0, max = 76 * 60, base = 0, desc = U('hr', 60, true), color = C(288,.3,.5), name = 'Fatigue', srzType = T.decimal}; -- fatigue is measured in minutes one needs to sleep to cure it stamina = {min = 0, max = 20 * 100, base = true, desc = U('m', 100), color = C(88), name = 'Stamina'}; -- stamina is measured in how many 10th-nodes (== cm) one can sprint nutrition = {min = 0, max = 8000, base = 0, desc = U('kCal', 1, true), color = C(43,.5,.4), name = 'Nutrition', srzType = T.decimal}; -- hunger is measured in kcalories one must consume to cure it. at 0, you start dying hydration = {min = 0, max = 4, base = 0, desc = U('L', 1), color = C(217, .25,.4), name = 'Hydration', srzType = T.decimal}; -- thirst is measured in L of H²O required to cure it - morale = {min = 0, max = 24 * 60 * 10, base = true, desc = U('hr', 60, true), color = C(0,0,.8), name = 'Morale'}; + morale = {min = 0, max = 10 * 24 * 60, base = true, desc = U('hr', 60, true), color = C(0,0,.8), name = 'Morale', srzType = T.decimal}; -- morale is measured in minutes. e.g. at base rate morale degrades by - -- 60 points every hour. morale can last up to 10 days + -- 60 points every hour. morale can last up to 10 earthdays irradiation = {min = 0, max = 10, base = 0, desc = U('Gy', 1), color = C(141,1,.5), name = 'Irradiation', srzType = T.decimal}; -- irrad is measured is milligreys -- 1Gy counters natural healing -- ~3Gy counters basic nanomedicine -- 5Gy causes death within two weeks without nanomedicine -- radiation speeds up psi regen -- morale drain doubles with each 2Gy - illness = {min = 0, max = 1000, base = 0, desc = U('%', 10, true), color = C(71,.4,.25), name = 'Illness'}; + illness = {min = 0, max = 1, base = 0, desc = U('%', .01, true), color = C(71,.4,.25), name = 'Illness', srzType = T.factor}; -- as illness increases, maximum stamina and health gain a corresponding limit -- illness is increased by certain conditions, and decreases on its own as your -- body heals when those conditions wear off. some drugs can lower accumulated illness -- but illness-causing conditions require specific cures -- illness also causes thirst and fatigue to increase proportionately } Index: mods/starlit/terrain.lua ================================================================== --- mods/starlit/terrain.lua +++ mods/starlit/terrain.lua @@ -2,29 +2,38 @@ local lib = starlit.mod.lib starlit.terrain = {} local soilSounds = { footstep = 'default-dirt-footstep'; - dig = 'default-dig-crumbly'; dug = 'default-dug-node'; } local sandSounds = { footstep = {name='default-sand-footstep',gain=0.1}; - dig = 'default-dig-crumbly'; dug = 'default-dug-node'; } local grassSounds = { footstep = 'default-grass-footstep'; - dig = 'default-dig-crumbly'; + dug = 'default-dug-node'; +} +local hardSounds = { + footstep = 'default-hard-footstep'; dug = 'default-dug-node'; } +local soilDrop = { + max_items = 3; + items = { + {rarity = 2, items = {'starlit:soil_clump'}}; + {rarity = 3, items = {'starlit:soil_clump'}}; + {rarity = 4, items = {'starlit:soil_clump'}}; + }; +} minetest.register_node('starlit:soil', { description = T 'Soil'; - tiles = {'default_dirt.png'}; - groups = {dirt = 1}; - drop = ''; + tiles = {'starlit-terrain-soil.png'}; + groups = {looseClump = 2, soil = 1}; + drop = soilDrop; sounds = soilSounds; _starlit = { kind = 'block'; elements = {}; }; @@ -31,12 +40,12 @@ }) minetest.register_node('starlit:sand', { description = T 'Sand'; - tiles = {'default_sand.png'}; - groups = {dirt = 1}; + tiles = {'starlit-terrain-sand.png'}; + groups = {looseClump = 1, sand = 1}; drop = ''; sounds = sandSounds; _starlit = { kind = 'block'; fab = starlit.type.fab { element = { silicon = 25 } }; @@ -48,11 +57,18 @@ title = T 'Soil'; desc = 'A handful of nutrient-packed soil, suitable for growing plants'; color = lib.color(0.3,0.2,0.1); }; inventory_image = 'starlit-item-soil.png'; - groups = {soil = 1}; + groups = {looseClump = 2, soil = 1}; + on_place = function(me, luser, point) + if me:get_count() < 3 then return end + if minetest.place_node(point.above, {name = 'starlit:soil'}, luser) then + me:take_item(3) + end + return me + end; _starlit = { fab = starlit.type.fab { element = { carbon = 12 / 4 } }; }; }) @@ -68,18 +84,18 @@ } minetest.register_node(def.name, { description = T 'Greengraze'; tiles = { def.img .. '.png'; - 'default_dirt.png'; + 'starlit-terrain-soil.png'; { - name = 'default_dirt.png^' .. def.img ..'_side.png'; + name = 'starlit-terrain-soil.png^' .. def.img ..'-overlay.png'; tileable_vertical = false; }; }; - groups = {grass = 1, dirt = 1, sub_walk = 1}; - drop = ''; + groups = {looseClump = 2, grass = 1, soil = 1, sub_walk = 1}; + drop = soilDrop; sounds = grassSounds; _starlit = { fab = def.fab; recover = def.recover; recover_vary = def.recover_vary; @@ -89,11 +105,11 @@ starlit.terrain.createGrass { name = 'starlit:greengraze'; desc = T 'Greengraze'; - img = 'default_grass'; + img = 'starlit-terrain-greengraze'; fab = starlit.type.fab { element = { carbon = 12; }; time = { @@ -158,10 +174,11 @@ string.format('default_stone.png^[colorizehsl:%s:%s:%s', m.tone.hue, m.tone.sat, m.tone.lum) }) or {'default_stone.png'}; groups = grp; drop = m.rocks or ''; + sounds = hardSounds; _starlit = { kind = 'block'; elements = m.elements; fab = m.fab; recover = m.recover; Index: mods/starlit/user.lua ================================================================== --- mods/starlit/user.lua +++ mods/starlit/user.lua @@ -57,13 +57,45 @@ maneuver = nil; }; pref = { calendar = 'commune'; }; + overlays = {}; } end; __index = { + -------------- + -- overlays -- + -------------- + updateOverlays = function(self) + local phys = { + speed = self.pheno:trait('speed',1); + jump = self.pheno:trait('jump',1); + gravity = 1; + speed_climb = 1; + speed_crouch = 1; + speed_walk = 1; + acceleration_default = 1; + acceleration_air = 1; + } + for i, o in ipairs(self.overlays) do o(phys) end + self.entity:set_physics_override(phys) + end; + overlay = function(self, o) + local id = #self.overlays+1 + self.overlays[id] = o + self:updateOverlays() + return id + end; + deleteOverlay = function(self, id) + table.remove(self.overlays, id) + self:updateOverlays() + end; + + -------------- + -- personae -- + -------------- pullPersona = function(self) -- if later records are added in public updates, extend this function to merge them -- into one object local s = userStore(self.entity) self.persona = s.read 'persona' @@ -71,11 +103,16 @@ end; pushPersona = function(self) local s = userStore(self.entity) s.write('persona', self.persona) end; + uiColor = function(self) return lib.color {hue=238,sat=.5,lum=.5} end; + + ----------- + -- stats -- + ----------- statDelta = function(self, stat, d, cause, abs) local dt = self.persona.statDeltas local min, max, base = self:statRange(stat) if abs then if d == true then d = max @@ -99,18 +136,10 @@ self:updateHUD() -- TODO trigger relevant animations? end; - lookupSpecies = function(self) - return starlit.world.species.lookup(self.persona.species, self.persona.speciesVariant) - end; - phenoTrait = function(self, trait, dflt) --- local s,v = self:lookupSpecies() --- return v.traits[trait] or s.traits[trait] or 0 - return self.pheno:trait(trait, dflt) - end; statRange = function(self, stat) --> min, max, base return starlit.world.species.statRange( self.persona.species, self.persona.speciesVariant, stat) end; effectiveStat = function(self, stat) @@ -126,10 +155,22 @@ end local d = max - min return val, (val - min) / d end; + + --------------- + -- phenotype -- + --------------- + lookupSpecies = function(self) + return starlit.world.species.lookup(self.persona.species, self.persona.speciesVariant) + end; + phenoTrait = function(self, trait, dflt) +-- local s,v = self:lookupSpecies() +-- return v.traits[trait] or s.traits[trait] or 0 + return self.pheno:trait(trait, dflt) + end; damageModifier = function(self, kind, amt) if kind == 'bluntForceTrauma' then local std = self:phenoTrait 'sturdiness' if std < 0 then amt = amt / 1+std @@ -137,10 +178,14 @@ amt = amt * 1-std end end return amt end; + + --------- + -- HUD -- + --------- attachImage = function(self, def) local user = self.entity local img = {} img.id = user:hud_add { type = 'image'; @@ -382,20 +427,24 @@ update = function(user, set) set('text', hudAdjustBacklight(hudCenterBG):render()) end; }; end; - -- horrible horrible HACK - setModeHand = function(self) - local inv = self.entity:get_inventory() - local hnd - if self.actMode == 'off' - then hnd = ItemStack('starlit:_hand_dig') - else hnd = ItemStack() + deleteHUD = function(self) + for name, e in pairs(self.hud.elt) do + self:hud_delete(e.id) end - inv:set_stack('hand', 1, hnd) end; + updateHUD = function(self) + for name, e in pairs(self.hud.elt) do + if e.update then e.update() end + end + end; + + --------------------- + -- actions & modes -- + --------------------- onModeChange = function(self, oldMode, silent) self.hud.elt.crosshair.update() if not silent then local sfxt = { off = 'starlit-mode-off'; @@ -415,23 +464,33 @@ self:onModeChange(oldMode, silent) if mode ~= oldMode then starlit.ui.setupForUser(self) end end; - deleteHUD = function(self) - for name, e in pairs(self.hud.elt) do - self:hud_delete(e.id) + setModeHand = function(self) -- horrible horrible HACK + local inv = self.entity:get_inventory() + local hnd + if self.actMode == 'off' + then hnd = ItemStack('starlit:_hand_dig') + else hnd = ItemStack() end + inv:set_stack('hand', 1, hnd) end; - updateHUD = function(self) - for name, e in pairs(self.hud.elt) do - if e.update then e.update() end - end - end; + + --------------------- + -- intel-gathering -- + --------------------- clientInfo = function(self) return minetest.get_player_information(self.name) end; + species = function(self) + return starlit.world.species.index[self.persona.species] + end; + + -------------------- + -- event handlers -- + -------------------- onSignup = function(self) local meta = self.entity:get_meta() local inv = self.entity:get_inventory() -- the sizes indicated here are MAXIMA. limitations on e.g. the number of elements that may be carried are defined by your suit and enforced through callbacks and UI generation code, not inventory size inv:set_size('main', 6) -- carried items and tools. main hotbar. @@ -473,14 +532,15 @@ giveGifts('main', gifts.carry) self:reconfigureSuit() -- i feel like there has to be a better way - local cx = math.random(-500,500) + local posrng = starlit.world.seedbank[0x13f19] -- TODO player-specific seed + local cx = posrng:int(-500,500) --math.random(-500,500) local iter, startPoint = 1 repeat local temp = -100 - local cz = math.random(-500,500) + local cz = posrng:int(-500,500) local cy = minetest.get_spawn_level(cx, cz) if cy then startPoint = vector.new(cx,cy,cz) temp = starlit.world.climate.eval(startPoint,.5,.5).surfaceTemp end @@ -599,11 +659,21 @@ -- TODO set_clouds speed in accordance with wind starlit.world.species.setupEntity(me, self.persona) starlit.ui.setupForUser(self) self:createHUD() self:updateSuit() + self:updateOverlays() end; + onPart = function(self) + starlit.liveUI [self.name] = nil + starlit.activeUI [self.name] = nil + starlit.activeUsers[self.name] = nil + end; + + ----------------------------- + -- environment suit & body -- + ----------------------------- suitStack = function(self) return self.entity:get_inventory():get_stack('starlit_suit', 1) end; suitSound = function(self, sfx) -- trigger a sound effect from the player's suit computer @@ -632,13 +702,10 @@ sfx = 'starlit-configure' end if sfx then self:suitSound(sfx) end end end; - species = function(self) - return starlit.world.species.index[self.persona.species] - end; updateBody = function(self) local adornment = {} local suitStack = self:suitStack() if suitStack and not suitStack:is_empty() then local suit = suitStack:get_definition()._starlit.suit @@ -728,30 +795,21 @@ return supply, wasteHeat end; naked = function(self) return self:suitStack():is_empty() end; - onPart = function(self) - starlit.liveUI [self.name] = nil - starlit.activeUI [self.name] = nil - starlit.activeUsers[self.name] = nil - end; + + -------- + -- ui -- + -------- openUI = function(self, id, page, ...) local ui = assert(starlit.interface.db[id]) ui:open(self, page, ...) end; onRespond = function(self, ui, state, resp) ui:action(self, state, resp) end; - - updateWeather = function(self) - end; - - canInteract = function(self, with) - return true; -- TODO - end; - trigger = function(self, which, how) local p local wld = self.entity:get_wielded_item() if which == 'maneuver' then p = self.power.maneuver @@ -785,19 +843,35 @@ run = assert(sw.run, 'missing run() for active software ability ' .. pgm.body.pgmId) break end end end + elseif p.ref then + run = p.ref.run else error('bad ability pointer ' .. dump(p)) end if run then run(self, ctx) return true end return false end; + + ------------- + -- weather -- + ------------- + updateWeather = function(self) + end; + + canInteract = function(self, with) + return true; -- TODO + end; + + --------------- + -- inventory -- + --------------- give = function(self, item) local inv = self.entity:get_inventory() local function is(grp) return minetest.get_item_group(item:get_name(), grp) ~= 0 end @@ -849,23 +923,57 @@ dehydration = dehydration * math.max(1, starlit.world.climate.temp(u.entity:get_pos()) / 10) u:statDelta('nutrition', -bmr) u:statDelta('hydration', -dehydration) - if u:effectiveStat 'nutrition' == 0 then - -- starvation - end + local moralePenalty = -1 -- 1min/min + local fatiguePenalty = 1 -- 1min/min + local heatPenalty = 1 -- stamina regen is divided by this - if u:effectiveStat 'hydration' == 0 then - -- dying of thirst + do local warmth = u:effectiveStat 'warmth' + local tempRange = u:species().tempRange + local tComfMin, tComfMax = tempRange.comfort[1], tempRange.comfort[2] + local tempDiff = 0 + if warmth < tComfMin then + tempDiff = math.abs(warmth-tComfMin) + elseif warmth > tComfMax then + tempDiff = math.abs(warmth-tComfMax) + end + moralePenalty = moralePenalty + tempDiff + heatPenalty = heatPenalty + tempDiff end + -- penalize heavy phys. activity + local stamina, sp = u:effectiveStat 'stamina' + fatiguePenalty = fatiguePenalty * (1 + 9*(1-sp)) + + local food = u:effectiveStat 'nutrition' + local water = u:effectiveStat 'hydration' local rads = u:effectiveStat 'irradiation' + if food < 1000 then moralePenalty = moralePenalty + (1 - (food/1000)) * 5 end + if water < 1 then moralePenalty = moralePenalty + (1 - (water/1)) * 10 end + if rads > 0 then u:statDelta('irradiation', -0.0001 * biointerval) + local moraleDrainFac = 2^(rads / 2) + moralePenalty = moralePenalty * moraleDrainFac + end + + u:statDelta('morale', moralePenalty * biointerval) + u:statDelta('fatigue', fatiguePenalty * biointerval) + + if food == 0 then -- starvation + u:statDelta('health', -5*biointerval) + end + + if water == 0 then -- dying of thirst + u:statDelta('health', -20*biointerval) end + if sp < 1.0 then + u:statDelta('stamina', u:effectiveStat 'staminaRegen' / heatPenalty) + end end end) local cbit = { up = 0x001; @@ -895,30 +1003,36 @@ end local skipBits = 0 if user.action.bits ~= bits then local mPrimary = what(cbit.dig) local mSecondary = what(cbit.put) + local mManeuver = what(cbit.manv) if mPrimary == mustInit then -- ENGINE-BUG user.action.tgt = {type='nothing'} user.action.prog = {} elseif mPrimary == mustHalt then user:trigger('primary', {state='halt'}) end if mSecondary == mustHalt then user:trigger('secondary', {state='halt'}) end + if mManeuver == mustInit then + user:trigger('maneuver', {state='init'}) + elseif mManeuver == mustHalt then + user:trigger('maneuver', {state='halt'}) + end end --bits = bit.band(bits, bit.bnot(skipBits)) - if bit.band(bits, cbit.dig)~=0 then - user:trigger('primary', {state='prog', delta=delta}) + local function prog(what) + user:trigger(what, {state='prog', delta=delta}) end - if bit.band(bits, cbit.put)~=0 then - user:trigger('secondary', {state='prog', delta=delta}) - end + if bit.band(bits, cbit.dig)~=0 then prog 'primary' end + if bit.band(bits, cbit.put)~=0 then prog 'secondary' end + if bit.band(bits, cbit.manv)~=0 then prog 'maneuver' end user.action.bits = bits -- ENGINE-BUG: dig and put are not handled equally in the -- engine. it is possible for the put bit to get stuck on -- if the key is hammered while the player is not moving. -- the bit will release as soon as the player looks or turns -- nonetheless this is obnoxious end end) Index: mods/starlit/world.lua ================================================================== --- mods/starlit/world.lua +++ mods/starlit/world.lua @@ -98,10 +98,11 @@ local function stageID(n) if n == stageCt then return id end return id .. string.format('_stage_%s', n) end b.stageNodes = {} + b.req = b.req or {} local function regStage(n, st) local base = { description = b.name; drawtype = "plantlike"; tiles = { tostring(st.tex) }; @@ -111,10 +112,11 @@ walkable = false; buildable_to = true; groups = { plant = 1; plant_grow = stageCt ~= n and 1 or 0; + attached_node = 3; }; drop = st.drop; _starlit = { plant = { id = id, stage = n; @@ -132,11 +134,11 @@ }; end; }; } if st.swap then - base.node_dig_prediction = stageID(st.swap) + base.node_dig_prediction = "" function base.after_dig_node(pos, node, digger) node.name = stageID(st.swap) minetest.swap_node(pos, node) return true end @@ -234,16 +236,18 @@ end end) world.ecology.trees.foreach('starlit:tree-gen', {}, function(id, t) - local dec = { - deco_type = 'lsystem'; - treedef = t.def; - } - for k,v in pairs(t.decorate) do dec[k]=v end - minetest.register_decoration(dec) + for i,td in ipairs(t.decorate) do + local dec = { + deco_type = 'lsystem'; + treedef = t.def; + } + for k,v in pairs(td) do dec[k]=v end + minetest.register_decoration(dec) + end end) minetest.register_abm { label = "plant growth"; nodenames = {'group:plant_grow'}; @@ -250,10 +254,25 @@ chance = 15; interval = 20; catch_up = true; action = function(pos, node) local def = minetest.registered_nodes[node.name]._starlit.plant - local plant = starlit.world.ecology.plants.db[def.id] - local nextStage = plant.stageNodes[def.stage + 1] - minetest.swap_node(pos, {name=nextStage}) + -- 5 W: maximum power for UV lamps + -- 7 W: maximum solar power + local uv = (minetest.get_natural_light(pos) / 15) * 7 + -- TODO compute artificial contribution + local req = lib.tbl.defaults({ + uv = 3; + soil = 'soil'; + temp = -10; + humid = nil; + }, def.growReq); + + -- TODO check other reqs + + if uv > req.uv then + local plant = starlit.world.ecology.plants.db[def.id] + local nextStage = plant.stageNodes[def.stage + 1] + minetest.swap_node(pos, {name=nextStage}) + end end; } Index: mods/vtlib/math.lua ================================================================== --- mods/vtlib/math.lua +++ mods/vtlib/math.lua @@ -131,6 +131,19 @@ __add = function(self, n) return fn.seedbank(self.seed + n) end; } -- function fn.vlerp + +function fn.timespec(n) + if n == 0 then return '0s' end + if n < 0 then return '-' .. fn.timespec(n*-1) end + + local sec = n % 60 + local hr = math.floor(n / 60) + local spec = {} + + if sec ~= 0 then table.insert(spec, string.format("%ss", sec)) end + if hr ~= 0 then table.insert(spec, string.format("%shr", hr)) end + return table.concat(spec, ' ') +end return fn Index: starlit.ct ================================================================== --- starlit.ct +++ starlit.ct @@ -40,10 +40,15 @@ ### 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 +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. Thousand Petal 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. ### 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.