starlit  Check-in [6deb9bedbc]

Overview
Comment:check in missing mod, add forest biome, racial powers, overlays (untested)
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 6deb9bedbcb3cc5a6bba5e548bd6f4997ef3270fd53193d0c457a345f82285f6
User & Date: lexi on 2024-05-02 20:27:05
Other Links: manifest | tags
Context
2024-05-03
00:10
add sprint, improve bio job, rebalance stamina regen, various fixes check-in: cade6683f7 user: lexi tags: trunk
2024-05-02
20:27
check in missing mod, add forest biome, racial powers, overlays (untested) check-in: 6deb9bedbc user: lexi tags: trunk
00:22
plant growth, edibles, fixes check-in: e829ca194a user: lexi tags: trunk
Changes

Added mods/starlit-eco/init.lua version [ffe9d8ff5f].

            1  +local world = starlit.world
            2  +local lib = starlit.mod.lib
            3  +
            4  +world.ecology.biomes.link('starlit:steppe', {
            5  +	nightTempDelta = -30;
            6  +	waterTempDelta = 0;
            7  +	--               W    Sp   Su    Au   W
            8  +	seasonalTemp = {-50, -10, 5, 5, -20, -50};
            9  +	def = {
           10  +		node_top      = 'starlit:greengraze', depth_top = 1;
           11  +		node_filler   = 'starlit:soil',    depth_filler = 4;
           12  +		node_riverbed = 'starlit:sand',  depth_riverbed = 4;
           13  +		y_min = 0;
           14  +		y_max = 512;
           15  +		heat_point = 10;
           16  +		humidity_point = 30;
           17  +	};
           18  +})
           19  +
           20  +world.ecology.biomes.link('starlit:forest', {
           21  +	nightTempDelta = -20;
           22  +	waterTempDelta = 0;
           23  +	--               W    Sp   Su    Au   W
           24  +	seasonalTemp = {-40, -8, 10, 10, -14, -40};
           25  +	def = {
           26  +		node_top      = 'starlit:greengraze', depth_top = 1;
           27  +		node_filler   = 'starlit:soil',    depth_filler = 4;
           28  +		node_riverbed = 'starlit:sand',  depth_riverbed = 4;
           29  +		y_min = -100;
           30  +		y_max = 256;
           31  +		heat_point = 13;
           32  +		humidity_point = 40;
           33  +	};
           34  +})
           35  +
           36  +world.ecology.biomes.link('starlit:desert', {
           37  +	nightTempDelta = -40;
           38  +	waterTempDelta = 0;
           39  +	--               W    Sp  Su    Au   W
           40  +	seasonalTemp = {-10, -5, 15, 15, -5, -10};
           41  +	def = {
           42  +		node_top      = 'starlit:sand',  depth_top = 1;
           43  +		node_filler   = 'starlit:sand',  depth_filler = 4;
           44  +		node_riverbed = 'starlit:sand',  depth_riverbed = 4;
           45  +		y_min = 0;
           46  +		y_max = 512;
           47  +		heat_point = 20;
           48  +		humidity_point = 10;
           49  +	};
           50  +})
           51  +
           52  +world.ecology.biomes.link('starlit:ocean', {
           53  +	nightTempDelta = -35;
           54  +	waterTempDelta = 5;
           55  +	seasonalTemp = {0}; -- no seasonal variance
           56  +	def = {
           57  +		y_max = 3;
           58  +		y_min = -512;
           59  +		heat_point = 15;
           60  +		humidity_point = 50;
           61  +		node_top    = 'starlit:sand', depth_top    = 1;
           62  +		node_filler = 'starlit:sand', depth_filler = 3;
           63  +	};
           64  +})
           65  +
           66  +minetest.register_craftitem('starlit_eco:fiber', {
           67  +	description = "Plant Fiber";
           68  +	groups = {fiber = 1};
           69  +	inventory_image = lib.image('starlit-eco-plant-fiber.png'):shift(lib.color(0,1,0)):render();
           70  +	_starlit = {
           71  +		recover_vary = function(rng, ctx)
           72  +			return starlit.type.fab {
           73  +				element = { carbon   = rng:int(0,1) };
           74  +			};
           75  +		end;
           76  +	};
           77  +})
           78  +
           79  +starlit.include 'plants'
           80  +starlit.include 'trees'

Added mods/starlit-eco/mod.conf version [148d1a5e9a].

            1  +name = starlit_eco
            2  +title = starlit ecosphere
            3  +description = plants and biomes
            4  +depends = starlit

Added mods/starlit-eco/plants.lua version [e9756bfa75].

            1  +local world = starlit.world
            2  +local lib = starlit.mod.lib
            3  +
            4  +local function dropCat(a,b)
            5  +	local function strdrop(s)
            6  +		if type(s) == 'string' then
            7  +			return {max_items = 1; items = {{items={s}}}}
            8  +		else return s end
            9  +	end
           10  +	a = strdrop(a)
           11  +	b = strdrop(b)
           12  +	return {
           13  +		max_items = a.max_items + b.max_items;
           14  +		items = lib.tbl.append(a.items, b.items);
           15  +	}
           16  +end
           17  +local function stalkPlant(def)
           18  +	local function stage(s, drops, swap)
           19  +		return {
           20  +			tex = lib.image(string.format('starlit-eco-plant-stalk%s.png',s)):shift(def.color);
           21  +			drop = drops;
           22  +			swap = swap;
           23  +		}
           24  +	end
           25  +	local function plantMatter(opts)
           26  +		local dps = {
           27  +			seed = def.seed;
           28  +			fiber = def.fiber;
           29  +			leaf = def.leaf and def.leaf.drop or nil;
           30  +			berry = def.berries and def.berries.drop or nil;
           31  +		}
           32  +		local t = {max_items=0, items={}}
           33  +		for k,v in pairs(opts) do
           34  +			if dps[v] then t = dropCat(dps[v], t) end
           35  +		end
           36  +		return t
           37  +	end
           38  +
           39  +	local fg
           40  +	local stages = {
           41  +		stage('-grow-1', '');
           42  +		stage('-grow-2', plantMatter{'seed'});
           43  +		stage('-grow-3', plantMatter{'seed','fiber'});
           44  +		stage('', plantMatter{'seed','seed','fiber'});
           45  +	};
           46  +	if def.leaf then
           47  +		local ps = stage('', plantMatter{'seed','seed','seed','fiber','leaf'})
           48  +		ps.tex = ps.tex .. lib.image('starlit-eco-plant-stalk-petals.png'):shift(def.leaf.color)
           49  +		table.insert(stages, ps)
           50  +	end
           51  +	if def.berries then
           52  +		local ps = lib.image.clone(stages[#stages])
           53  +		ps.tex = ps.tex:blit(lib.image('starlit-eco-plant-stalk-berries.png'):shift(def.berries.color))
           54  +		ps.drop = def.berries.drop;
           55  +		ps.swap = #stages
           56  +		table.insert(stages, ps)
           57  +	end
           58  +
           59  +	if def.biolum then for i,v in ipairs(stages) do
           60  +		v.biolum = math.floor(def.biolum * (i/#stages))
           61  +	end end
           62  +
           63  +	world.ecology.plants.link(def.id, {
           64  +		name = def.name;
           65  +		stages = stages;
           66  +		decoration = def.decoration;
           67  +		meshOpt = 3;
           68  +	})
           69  +end
           70  +
           71  +local function simpleDrop(rarity, what)
           72  +	return {
           73  +		max_items = 1;
           74  +		items = {
           75  +			{rarity = rarity, items = {what}};
           76  +		};
           77  +	};
           78  +end
           79  +
           80  +function stalkPlantAuto(def)
           81  +	local id = def.id
           82  +	local id_berries = def.id .. '_berry'
           83  +	local id_seed = def.id .. '_seed'
           84  +
           85  +	local p = lib.tbl.proto({}, def)
           86  +	if def.berries then
           87  +		local bdef = lib.tbl.defaults({
           88  +			name = def.name .. ' Berry';
           89  +			tex = lib.image('starlit-eco-plant-berry-bunch.png'):shift(def.berries.color or def.color):render();
           90  +			color = def.color;
           91  +		}, def.berries)
           92  +		bdef.name = bdef.name
           93  +		starlit.item.food.link(id_berries, bdef)
           94  +		p.berries = {
           95  +			color = def.berries.color;
           96  +			drop = id_berries;
           97  +		}
           98  +	end
           99  +
          100  +	if def.seed then
          101  +		local sdef = lib.tbl.defaults({
          102  +			name = def.name .. ' Seed';
          103  +			tex = lib.image('starlit-eco-plant-seeds.png'):shift(def.seed.color or def.color):render();
          104  +			color = def.color;
          105  +			grow = {kind = 'plant', id = id};
          106  +		}, def.seed)
          107  +
          108  +		starlit.item.seed.link(id_seed, sdef)
          109  +		p.seed = id_seed
          110  +	end
          111  +
          112  +	return stalkPlant(p)
          113  +end
          114  +
          115  +stalkPlantAuto {
          116  +	id = 'starlit_eco:moondrop';
          117  +	name = "Moondrop";
          118  +	fiber = simpleDrop(2, 'starlit_eco:fiber');
          119  +	seed = {};
          120  +	color = lib.color(.5, .7, 1);
          121  +	biolum = 5;
          122  +	leaf = {
          123  +		color = lib.color(.6, .8, .8);
          124  +		drop = simpleDrop(2, 'starlit_eco:moondrop_petal');
          125  +	};
          126  +	berries = {
          127  +		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";
          128  +		color = lib.color(1,0,.4);
          129  +		nourish = 10;
          130  +		hydrate = 0.05;
          131  +		taste = 1 * 60;
          132  +		mass = 1;
          133  +	};
          134  +	decoration = {
          135  +		place_on = 'starlit:greengraze';
          136  +		fill_ratio = 0.03;
          137  +		biomes = {'starlit:steppe', 'starlit:forest'};
          138  +		y_min = 10;
          139  +		y_max = 100;
          140  +	};
          141  +}
          142  +
          143  +stalkPlantAuto {
          144  +	id = 'starlit_eco:dustrose';
          145  +	name = "Dust Rose";
          146  +	fiber = simpleDrop(2, 'starlit_eco:fiber');
          147  +	seed = {};
          148  +	color = lib.color(.3, .1, .2);
          149  +	leaf = {
          150  +		color = lib.color(.7, .4, .8);
          151  +		drop = simpleDrop(2, 'starlit_eco:dustrose_petal');
          152  +	};
          153  +	decoration = {
          154  +		place_on = 'starlit:greengraze';
          155  +		fill_ratio = 0.03;
          156  +		biomes = {'starlit:forest'};
          157  +		y_min = -50;
          158  +		y_max = 50;
          159  +	};
          160  +}
          161  +
          162  +stalkPlantAuto {
          163  +	id = 'starlit_eco:harrowstalk';
          164  +	name = "Harrowstalk";
          165  +	fiber = simpleDrop(2, 'starlit_eco:fiber');
          166  +	seed = {};
          167  +	color = lib.color(.3, .2, .1);
          168  +	decoration = {
          169  +		place_on = 'starlit:sand';
          170  +		fill_ratio = 0.03;
          171  +		biomes = {'starlit:ocean', 'starlit:desert'};
          172  +		y_min = -30;
          173  +		y_max = 400;
          174  +	};
          175  +}

Added mods/starlit-eco/trees.lua version [49c5f4ea72].

            1  +local world = starlit.world
            2  +local lib = starlit.mod.lib
            3  +
            4  +local function woodProps(def) return {
            5  +	recover = starlit.type.fab {
            6  +		time = { shred = 2; };
            7  +		cost = { shredPower = 1.5; };
            8  +	};
            9  +	recover_vary = function(rng, ctx)
           10  +		return starlit.type.fab {
           11  +			element = {
           12  +				potassium = rng:int(0,1);
           13  +				carbon   = rng:int(0,2);
           14  +			}
           15  +		};
           16  +	end;
           17  +	mass = 1.5e3;
           18  +} end
           19  +
           20  +local function leafProps(def) return {
           21  +	recover = starlit.type.fab {
           22  +		time = { shred = .5; };
           23  +		cost = { shredPower = .3; };
           24  +	};
           25  +	recover_vary = function(rng, ctx)
           26  +		return starlit.type.fab {
           27  +			element = {
           28  +				potassium = rng:int(0,2);
           29  +				carbon   = rng:int(0,1);
           30  +			}
           31  +		};
           32  +	end;
           33  +	mass = 100;
           34  +} end
           35  +
           36  +local function regLog(id, def)
           37  +	local base = table.copy(def)
           38  +	base.groups = base.groups or {}
           39  +	base.groups.wood = 1
           40  +	base.groups.log = 1
           41  +	base.groups.falling_node = 1
           42  +
           43  +	local live = table.copy(base)
           44  +	live.drop = id
           45  +	live.groups.alive = 1
           46  +	minetest.register_node(id, base)
           47  +	minetest.register_node(id..'_live', live)
           48  +end
           49  +
           50  +regLog('starlit_eco:lambent_pine_log', {
           51  +	description = 'Lambent Pine Log';
           52  +	drawtype = 'normal';
           53  +	tiles = {
           54  +		'starlit-eco-tree-lambent-pine-trunk-top.png';
           55  +		'starlit-eco-tree-lambent-pine-trunk.png';
           56  +	};
           57  +	_starlit = woodProps{};
           58  +})
           59  +
           60  +
           61  +starlit.item.food.link('starlit_eco:lambent_pine_berry', {
           62  +	name = 'Lambent Pine Berry';
           63  +	desc = 'Though packed with human-compatible nutrients, these berries are almost painfully sour when eaten raw.';
           64  +	tex = lib.image('starlit-eco-plant-berry-bunch.png'):shift{hue=180,sat=-30,lum=30}:render();
           65  +	nourish = 150;
           66  +	taste = -2 * 60;
           67  +	mass = 2;
           68  +})
           69  +
           70  +starlit.item.seed.link('starlit_eco:lambent_pine_seed', {
           71  +	name = 'Lambent Pine Seed';
           72  +	tex = lib.image('starlit-eco-plant-seeds.png'):shift{hue=150, sat=-50, lum=80}:render();
           73  +	grow = {kind = 'tree', id = 'starlit_eco:lambent_pine'};
           74  +})
           75  +
           76  +minetest.register_node('starlit_eco:lambent_pine_bulb', {
           77  +	description = 'Lambent Pine Bulb';
           78  +	drawtype = 'nodebox';
           79  +	connects_to = {'starlit_eco:lambent_pine_needles'};
           80  +	node_box = {
           81  +		type = 'connected';
           82  +		connect_top = {
           83  +			{-.1,  .5, -.1,
           84  +			  .1, .3,  .1};
           85  +		};
           86  +		fixed = {
           87  +			{-.2, -.2, -.2,
           88  +			  .2,  .3,  .2};
           89  +		};
           90  +	};
           91  +	light_source = 6;
           92  +	drop = {
           93  +		max_items = 3;
           94  +		items = {
           95  +			{rarity = 4, items = {'starlit_eco:lambent_pine_seed'}};
           96  +			{rarity = 3, items = {'starlit_eco:lambent_pine_berry'}};
           97  +			{rarity = 2, items = {'starlit_eco:lambent_pine_berry'}};
           98  +			{items = {'starlit_eco:lambent_pine_berry'}};
           99  +		};
          100  +	};
          101  +	groups = {plant=1, attached_node = 4};
          102  +	tiles = {
          103  +		'starlit-eco-tree-lambent-pine-bulb.png';
          104  +	};
          105  +	_starlit = woodProps{};
          106  +})
          107  +
          108  +minetest.register_node('starlit_eco:lambent_pine_needles', {
          109  +	description = 'Lambent Pine Needles';
          110  +	groups = {plant = 1;};
          111  +	drop = '';
          112  +	tiles = {
          113  +		'starlit-eco-tree-lambent-pine-needles.png';
          114  +		'starlit-eco-tree-lambent-pine-needles.png';
          115  +	};
          116  +	_starlit = leafProps{};
          117  +});
          118  +
          119  +regLog('starlit_eco:starblossom_log', {
          120  +	description = 'Starblossom Log';
          121  +	drawtype = 'normal';
          122  +	tiles = {
          123  +		'starlit-eco-tree-starblossom-trunk-top.png';
          124  +		'starlit-eco-tree-starblossom-trunk.png';
          125  +	};
          126  +	_starlit = woodProps{};
          127  +})
          128  +
          129  +minetest.register_node('starlit_eco:starblossom_leaves', {
          130  +	description = 'Starblossom Leaves';
          131  +	groups = {plant = 1;};
          132  +	drop = '';
          133  +	tiles = {
          134  +		'starlit-eco-tree-starblossom-leaves.png';
          135  +		'starlit-eco-tree-starblossom-leaves.png';
          136  +	};
          137  +	_starlit = leafProps{};
          138  +});
          139  +minetest.register_node('starlit_eco:starblossom_leaves_shine', {
          140  +	description = 'Shining Starblossom Leaves';
          141  +	groups = {plant = 1;};
          142  +	drop = '';
          143  +	paramtype = 'light';
          144  +	light_source = 4;
          145  +	tiles = {
          146  +		'starlit-eco-tree-starblossom-leaves.png';
          147  +		'starlit-eco-tree-starblossom-leaves.png';
          148  +		'starlit-eco-tree-starblossom-leaves.png^starlit-eco-tree-starblossom-shine.png';
          149  +	};
          150  +	_starlit = leafProps{};
          151  +});
          152  +
          153  +starlit.world.ecology.trees.meld {
          154  +	['starlit_eco:lambent_pine'] = {
          155  +		name = 'Lambent Pine';
          156  +		def = {
          157  +			axiom = 'TTB';
          158  +			rules_a = '[-[&Tf]Tf][+[^Tf]Tf]f';
          159  +			rules_c = '[-[&Tff]Tff][+[^Tff]Tff]f';
          160  +			rules_b = 'TCA[f]B';
          161  +
          162  +			-- nodes
          163  +			trunk = 'starlit_eco:lambent_pine_log_live';
          164  +			leaves = 'starlit_eco:lambent_pine_needles';
          165  +
          166  +			angle = 90;
          167  +
          168  +			iterations = 7;
          169  +			random_level = 3;
          170  +			trunk_type = 'single';
          171  +			thin_branches = true;
          172  +
          173  +			fruit = 'starlit_eco:lambent_pine_bulb';
          174  +			fruit_chance = 0;
          175  +		};
          176  +		decorate = {
          177  +			{ biomes = {'starlit:forest'};
          178  +				place_on = 'starlit:greengraze';
          179  +				fill_ratio = 0.004;
          180  +				y_min = -30, y_max = 500;
          181  +				seed = 0xe8190e;
          182  +			};
          183  +			{ biomes = {'starlit:steppe'};
          184  +				place_on = 'starlit:greengraze';
          185  +				fill_ratio = 0.002;
          186  +				y_min = -30, y_max = 500;
          187  +				seed = 0xe8190e;
          188  +			};
          189  +		};
          190  +	};
          191  +
          192  +	['starlit_eco:starblossom'] = {
          193  +		def = {
          194  +			axiom = 'TTTTATTTT[&&B]' .. string.rep(string.rep('/', 4) .. '[&&B]', math.floor(360/40));
          195  +			rules_a = 'TTTa';
          196  +			rules_b = 'FF&B';
          197  +
          198  +			trunk_type = 'double';
          199  +			trunk = 'starlit_eco:starblossom_log_live';
          200  +			leaves = 'starlit_eco:starblossom_leaves';
          201  +			leaves2 = 'starlit_eco:starblossom_leaves_shine';
          202  +			leaves2_chance = 20;
          203  +
          204  +			angle = 10;
          205  +			iterations = 13;
          206  +			random_level = 5;
          207  +		};
          208  +		decorate = {
          209  +			{ biomes = {'starlit:forest'};
          210  +				place_on = 'starlit:greengraze';
          211  +				fill_ratio = 0.001;
          212  +				y_min = -20, y_max = 512;
          213  +			};
          214  +		};
          215  +	};
          216  +}
          217  +
          218  +minetest.register_abm {
          219  +	label = "lambent pine fruiting";
          220  +	nodenames = {'starlit_eco:lambent_pine_needles'};
          221  +	neighbors = {'starlit_eco:lambent_pine_log_live'};
          222  +	chance = 40;
          223  +	interval = 80;
          224  +	catch_up = true;
          225  +	action = function(pos, node)
          226  +		local po = pos:offset(0,-1,0)
          227  +		if minetest.get_node(po).name == "air" then
          228  +			minetest.add_node(po, {name='starlit_eco:lambent_pine_bulb'})
          229  +		end
          230  +	end;
          231  +}

Modified mods/starlit-electronics/init.lua from [67a7c4791a] to [c28c8fe2b6].

   518    518   -----------
   519    519   -- chips --
   520    520   -----------
   521    521   
   522    522   E.sw = {}
   523    523   function E.sw.findSchematicFor(item)
   524    524   	local id = ItemStack(item):get_name()
   525         -	print(id)
   526    525   	local fm = minetest.registered_items[id]._starlit
   527    526   	if not (fm and fm.fab and fm.fab.reverseEngineer) then return nil end
   528    527   	local id = fm.fab.reverseEngineer.sw
   529    528   	return id, starlit.item.sw.db[id]
   530    529   end
   531    530   
   532    531   E.chip = { file = {} }

Modified mods/starlit/init.lua from [e52f2bba90] to [435c654ad9].

   348    348   
   349    349   local function pointChanged(a,b)
   350    350   	return a.type ~= b.type
   351    351   		or a.type == 'node'   and vector.new(a.under) ~= vector.new(b.under)
   352    352   		or a.type == 'object' and a.ref ~= b.ref 
   353    353   end
   354    354   local function triggerPower(_, luser, point)
   355         -	for k,v in pairs(starlit.activeUsers) do
   356         -	print (k,v) end
   357         -	print("trigger", luser, luser:get_player_name())
          355  +-- 	print("trigger", luser, luser:get_player_name())
   358    356   	local user = starlit.activeUsers[luser:get_player_name()]
   359    357   	local oldTgt = user.action.tgt
   360    358   	user.action.tgt = point
   361    359   	if bit.band(user.action.bits, 0x100)==0 then
   362    360   		user.action.bits = bit.bor(user.action.bits, 0x100)
   363    361   		--return user:trigger('secondary', {state = 'prog', delta = 0})
   364    362   	elseif pointChanged(oldTgt, point) then
................................................................................
   388    386   minetest.register_item("starlit:_hand_dig", {
   389    387   	type = "none",
   390    388   	wield_image = "wieldhand.png",
   391    389   	wield_scale = {x=1,y=1,z=2.5},
   392    390   	tool_capabilities = {
   393    391   		groupcaps = {
   394    392   			plant = {maxlevel=1, times = {.50}};
   395         -			dirt = {maxlevel=1, times = {2.5}};
   396    393   
   397         -			log = {maxlevel=1, times = {1}};
          394  +			-- sand, dirt, gravel
          395  +			looseClump = {maxlevel=1, times = {1.5, 2.5}};
   398    396   		};
   399    397   	}
   400    398   })
   401    399   
   402    400   minetest.register_on_player_inventory_action(function(luser, act, inv, p)
   403    401   	local name = luser:get_player_name()
   404    402   	local user = starlit.activeUsers[name]
................................................................................
   429    427   		return vector.new(
   430    428   			r(pos.x),
   431    429   			r(pos.y),
   432    430   			r(pos.z)
   433    431   		)
   434    432   	end
   435    433   	for i, it in ipairs(drops) do
   436         -		local it = minetest.add_item(jitter(pos), it)
   437         -		local dp = vector.new(0,0,0)
   438         -		if digger then dp = digger:get_pos() end
   439         -		local delta = dp - it:get_pos()
   440         -		it:add_velocity(vector.new(delta.x,0,delta.z));
          434  +		if type(it) == 'string' then it = ItemStack(it) end
          435  +		if not it:is_empty() then
          436  +			local ent = minetest.add_item(jitter(pos), it)
          437  +			if ent ~= nil then -- avoid crash when dropping unknown item
          438  +				local dp = vector.new(0,0,0)
          439  +				if digger then dp = digger:get_pos() end
          440  +				local delta = dp - ent:get_pos()
          441  +				ent:add_velocity(vector.new(delta.x,0,delta.z));
          442  +			end
          443  +		end
   441    444   	end
   442    445   end
   443    446   
   444    447   
   445    448   -- TODO timer iterates live UI
   446    449   

Modified mods/starlit/interfaces.lua from [497adf50ee] to [db0300e682].

   126    126   		table.insert(p, wrapMenu(a.w, a.h, 1.2, 2, m.opts))
   127    127   	end
   128    128   	return p
   129    129   end
   130    130   
   131    131   local function pptrMatch(a,b)
   132    132   	if a == nil or b == nil then return false end
   133         -	return a.chipID == b.chipID and a.pgmIndex == b.pgmIndex
          133  +	return (a.chipID ~= nil and (a.chipID == b.chipID and a.pgmIndex == b.pgmIndex))
          134  +       or (a.ref ~= nil and a.ref == b.ref)
   134    135   end
   135    136   
   136    137   starlit.interface.install(starlit.type.ui {
   137    138   	id = 'starlit:user-menu';
   138    139   	pages = {
   139    140   		compiler = {
   140    141   			setupState = function(state, user)
................................................................................
   357    358   					local st = string.format('%s / %s', s.desc(amt, true), s.desc(max))
   358    359   					table.insert(tb, {kind = 'hztl', padding = 0.25;
   359    360   						{kind = 'label', w=2, h=barh, text = s.name};
   360    361   						{kind = 'hbar',  w=4, h=barh, fac = sv, text = st, color=s.color};
   361    362   					})
   362    363   				end
   363    364   				local abilities = {
   364         -					{id = 'abl_sprint', label = 'Sprint', img = 'starlit-ui-icon-ability-sprint.png'};
          365  +					maneuver = {};
          366  +					direct = {};
          367  +					passive = {};
   365    368   				}
   366         -				table.insert(tb, wrapMenu(6.25,4, 1,2, abilities))
          369  +				state.abilityMap = {}
          370  +				for i, a in pairs(user:species().abilities) do
          371  +					local id = 'abl_'..a.id;
          372  +					state.abilityMap[id] = a;
          373  +					table.insert(abilities[a.powerKind], {
          374  +						id = id;
          375  +						label = a.name;
          376  +						desc = a.desc;
          377  +						img = a.img;
          378  +
          379  +						-- HACK
          380  +						color = pptrMatch(user.power.maneuver, {ref=a}) and
          381  +							{hue = 150, sat = 0, lum = .3} or nil;
          382  +					});
          383  +				end
          384  +				for i, n in ipairs {'maneuver', 'direct', 'passive'} do
          385  +					if next(abilities[n]) then
          386  +						table.insert(tb, wrapMenu(6.25,4, 1,2, abilities[n]))
          387  +					end
          388  +				end
   367    389   				return starlit.ui.build(tb)
          390  +			end;
          391  +			handle = function(state, user, q)
          392  +				for k,a in pairs(state.abilityMap) do
          393  +					if q[k] then
          394  +						if a.powerKind == 'maneuver' then
          395  +							if pptrMatch(user.power.maneuver, {ref=a}) then
          396  +								user.power.maneuver = nil
          397  +							else
          398  +								user.power.maneuver = {ref=a}
          399  +							end
          400  +							user:suitSound 'starlit-configure'
          401  +							return true
          402  +						elseif a.powerKind == 'direct' then
          403  +						elseif a.powerKind == 'passive' then
          404  +						else error('bad ability kind ' .. a.powerKind) end
          405  +						break
          406  +					end
          407  +				end
   368    408   			end;
   369    409   		};
   370    410   		suit = {
   371    411   			render = function(state, user)
   372    412   				local suit = user:getSuit()
   373    413   				local suitDef = suit:def()
   374    414   				local chipW, chipH = listWrap(suitDef.slots.chips, 5)

Modified mods/starlit/species.lua from [b30e9eb59a] to [1469667980].

    10     10   		str = T.str;
    11     11   		num = T.decimal;
    12     12   	}
    13     13   end
    14     14   
    15     15   -- constants
    16     16   local animationFrameRate = 60
           17  +
           18  +local bioAbilities = {
           19  +	sprint = {
           20  +		id = 'sprint';
           21  +		name = 'Sprint';
           22  +		desc = 'Put on a short burst of speed at the cost of some stamina';
           23  +		img = 'starlit-ui-icon-ability-sprint.png';
           24  +		powerKind = 'maneuver';
           25  +		run = function(user, ctx)
           26  +		end;
           27  +	};
           28  +}
    17     29   
    18     30   local species = {
    19     31   	human = {
    20     32   		name = 'Human';
    21     33   		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.';
    22     34   		scale = 1.0;
    23     35   		params = {
................................................................................
    58     70   					health = 400;
    59     71   					lungCapacity = .6;
    60     72   					irradiation = 0.8; -- you are smaller, so it takes less rads to kill ya
    61     73   					sturdiness = 0; -- women are more fragile and thus susceptible to blunt force trauma
    62     74   					metabolism = .150; -- kCal/s
    63     75   					painTolerance = 0.4;
    64     76   					dehydration = 10e-4; -- L/s
           77  +					speed = 1.1;
    65     78   				};
    66     79   			};
    67     80   			male = {
    68     81   				name = 'Human Male';
    69     82   				eyeHeight = 1.6;
    70     83   				stats = {
    71     84   					psiRegen = 1.0;
................................................................................
    78     91   				traits = {
    79     92   					health = 500;
    80     93   					painTolerance = 1.0;
    81     94   					lungCapacity = 1.0;
    82     95   					sturdiness = 0.3;
    83     96   					metabolism = .150; -- kCal/s
    84     97   					dehydration = 15e-4; -- L/s
           98  +					speed = 1.0;
    85     99   				};
    86    100   			};
    87    101   		};
    88    102   		traits = {};
          103  +		abilities = {bioAbilities.sprint};
    89    104   	};
    90    105   }
    91    106   
    92    107   
    93    108   starlit.world.species = {
    94    109   	index = species;
    95    110   	paramTypes = paramTypes;

Modified mods/starlit/stats.lua from [c485cf1b8a] to [93739694fe].

    22     22   	return lib.color {hue = h, sat = s or 1, lum = l or .7}
    23     23   end
    24     24   starlit.world.stats = {
    25     25   	psi        = {min = 0, max = 500, base = 0, desc = U('ψ', 10), color = C(320), name = 'Numina'};
    26     26   	-- numina is measured in daψ
    27     27   	warmth     = {min = -1000, max = 1000, base = 0, desc = U('°C', 10, true), color = C(5), name = 'Warmth'};
    28     28   	-- warmth in measured in d°C
    29         -	fatigue    = {min = 0, max = 76 * 60, base = 0, desc = U('hr', 60, true), color = C(288,.3,.5), name = 'Fatigue'};
           29  +	fatigue    = {min = 0, max = 76 * 60, base = 0, desc = U('hr', 60, true), color = C(288,.3,.5), name = 'Fatigue', srzType = T.decimal};
    30     30   	-- fatigue is measured in minutes one needs to sleep to cure it
    31     31   	stamina    = {min = 0, max = 20 * 100, base = true, desc = U('m', 100), color = C(88), name = 'Stamina'};
    32     32   	-- stamina is measured in how many 10th-nodes (== cm) one can sprint
    33     33   	nutrition  = {min = 0, max = 8000, base = 0, desc = U('kCal', 1, true), color = C(43,.5,.4), name = 'Nutrition', srzType = T.decimal};
    34     34   	-- hunger is measured in kcalories one must consume to cure it. at 0, you start dying
    35     35   	hydration  = {min = 0, max = 4, base = 0, desc = U('L', 1), color = C(217, .25,.4), name = 'Hydration', srzType = T.decimal};
    36     36   	-- thirst is measured in L of H²O required to cure it
    37         -	morale     = {min = 0, max = 24 * 60 * 10, base = true, desc = U('hr', 60, true), color = C(0,0,.8), name = 'Morale'};
           37  +	morale     = {min = 0, max = 10 * 24 * 60, base = true, desc = U('hr', 60, true), color = C(0,0,.8), name = 'Morale', srzType = T.decimal};
    38     38   	-- morale is measured in minutes. e.g. at base rate morale degrades by
    39         -	-- 60 points every hour. morale can last up to 10 days
           39  +	-- 60 points every hour. morale can last up to 10 earthdays
    40     40   	irradiation = {min = 0, max = 10, base = 0, desc = U('Gy', 1), color = C(141,1,.5), name = 'Irradiation', srzType = T.decimal};
    41     41   	-- irrad is measured is milligreys
    42     42   	-- 1Gy counters natural healing
    43     43   	-- ~3Gy counters basic nanomedicine
    44     44   	-- 5Gy causes death within two weeks without nanomedicine
    45     45   	-- radiation speeds up psi regen
    46     46   	-- morale drain doubles with each 2Gy
    47         -	illness    = {min = 0, max = 1000, base = 0, desc = U('%', 10, true), color = C(71,.4,.25), name = 'Illness'};
           47  +	illness    = {min = 0, max = 1, base = 0, desc = U('%', .01, true), color = C(71,.4,.25), name = 'Illness', srzType = T.factor};
    48     48   	-- as illness increases, maximum stamina and health gain a corresponding limit
    49     49   	-- illness is increased by certain conditions, and decreases on its own as your
    50     50   	-- body heals when those conditions wear off. some drugs can lower accumulated illness
    51     51   	-- but illness-causing conditions require specific cures
    52     52   	-- illness also causes thirst and fatigue to increase proportionately
    53     53   }

Modified mods/starlit/terrain.lua from [033036f747] to [43f9137bd9].

     1      1   local T = starlit.translator
     2      2   local lib = starlit.mod.lib
     3      3   
     4      4   starlit.terrain = {}
     5      5   local soilSounds = {
     6      6   	footstep = 'default-dirt-footstep';
     7         -	dig = 'default-dig-crumbly';
     8      7   	dug = 'default-dug-node';
     9      8   }
    10      9   local sandSounds = {
    11     10   	footstep = {name='default-sand-footstep',gain=0.1};
    12         -	dig = 'default-dig-crumbly';
    13     11   	dug = 'default-dug-node';
    14     12   }
    15     13   local grassSounds = {
    16     14   	footstep = 'default-grass-footstep';
    17         -	dig = 'default-dig-crumbly';
           15  +	dug = 'default-dug-node';
           16  +}
           17  +local hardSounds = {
           18  +	footstep = 'default-hard-footstep';
    18     19   	dug = 'default-dug-node';
    19     20   }
    20     21   
           22  +local soilDrop = {
           23  +	max_items = 3;
           24  +	items = {
           25  +		{rarity = 2, items = {'starlit:soil_clump'}};
           26  +		{rarity = 3, items = {'starlit:soil_clump'}};
           27  +		{rarity = 4, items = {'starlit:soil_clump'}};
           28  +	};
           29  +}
    21     30   minetest.register_node('starlit:soil', {
    22     31   	description = T 'Soil';
    23         -	tiles = {'default_dirt.png'};
    24         -	groups = {dirt = 1};
    25         -	drop = '';
           32  +	tiles = {'starlit-terrain-soil.png'};
           33  +	groups = {looseClump = 2, soil = 1};
           34  +	drop = soilDrop;
    26     35   	sounds = soilSounds;
    27     36   	_starlit = {
    28     37   		kind = 'block';
    29     38   		elements = {};
    30     39   	};
    31     40   })
    32     41   
    33     42   
    34     43   minetest.register_node('starlit:sand', {
    35     44   	description = T 'Sand';
    36         -	tiles = {'default_sand.png'};
    37         -	groups = {dirt = 1};
           45  +	tiles = {'starlit-terrain-sand.png'};
           46  +	groups = {looseClump = 1, sand  = 1};
    38     47   	drop = '';
    39     48   	sounds = sandSounds;
    40     49   	_starlit = {
    41     50   		kind = 'block';
    42     51   		fab = starlit.type.fab { element = { silicon = 25 } };
    43     52   	};
    44     53   })
................................................................................
    46     55   	short_description = T 'Soil';
    47     56   	description = starlit.ui.tooltip {
    48     57   		title = T 'Soil';
    49     58   		desc = 'A handful of nutrient-packed soil, suitable for growing plants';
    50     59   		color = lib.color(0.3,0.2,0.1);
    51     60   	};
    52     61   	inventory_image = 'starlit-item-soil.png';
    53         -	groups = {soil = 1};
           62  +	groups = {looseClump = 2, soil = 1};
           63  +	on_place = function(me, luser, point)
           64  +		if me:get_count() < 3 then return end
           65  +		if minetest.place_node(point.above, {name = 'starlit:soil'}, luser) then
           66  +			me:take_item(3)
           67  +		end
           68  +		return me
           69  +	end;
    54     70   	_starlit = {
    55     71   		fab = starlit.type.fab { element = { carbon = 12 / 4 } };
    56     72   	};
    57     73   })
    58     74   
    59     75   function starlit.terrain.createGrass(def)
    60     76   	local drop = {
................................................................................
    66     82   			};
    67     83   		};
    68     84   	}
    69     85   	minetest.register_node(def.name, {
    70     86   		description = T 'Greengraze';
    71     87   		tiles = {
    72     88   			def.img .. '.png';
    73         -			'default_dirt.png';
           89  +			'starlit-terrain-soil.png';
    74     90   			{
    75         -				name = 'default_dirt.png^' .. def.img ..'_side.png';
           91  +				name = 'starlit-terrain-soil.png^' .. def.img ..'-overlay.png';
    76     92   				tileable_vertical = false;
    77     93   			};
    78     94   		};
    79         -		groups = {grass = 1, dirt = 1, sub_walk = 1};
    80         -		drop = '';
           95  +		groups = {looseClump = 2, grass = 1, soil = 1, sub_walk = 1};
           96  +		drop = soilDrop;
    81     97   		sounds = grassSounds;
    82     98   		_starlit = {
    83     99   			fab = def.fab;
    84    100   			recover = def.recover;
    85    101   			recover_vary = def.recover_vary;
    86    102   		};
    87    103   	})
    88    104   end
    89    105   
    90    106   
    91    107   starlit.terrain.createGrass {
    92    108   	name = 'starlit:greengraze';
    93    109   	desc = T 'Greengraze';
    94         -	img = 'default_grass';
          110  +	img = 'starlit-terrain-greengraze';
    95    111   	fab = starlit.type.fab {
    96    112   		element = {
    97    113   			carbon = 12;
    98    114   		};
    99    115   		time = {
   100    116   			shred = 2.5;
   101    117   		};
................................................................................
   156    172   		tiles = m.tiles or 
   157    173   				(m.tone and {
   158    174   					string.format('default_stone.png^[colorizehsl:%s:%s:%s',
   159    175   						m.tone.hue, m.tone.sat, m.tone.lum)
   160    176   				}) or {'default_stone.png'};
   161    177   		groups = grp;
   162    178   		drop = m.rocks or '';
          179  +		sounds = hardSounds;
   163    180   		_starlit = {
   164    181   			kind = 'block';
   165    182   			elements = m.elements;
   166    183   			fab = m.fab;
   167    184   			recover = m.recover;
   168    185   			recover_vary = m.recover_vary;
   169    186   		};

Modified mods/starlit/user.lua from [fe75c1df99] to [910641ec39].

    55     55   				weapon = {primary = nil, secondary = nil};
    56     56   				psi = {primary = nil, secondary = nil};
    57     57   				maneuver = nil;
    58     58   			};
    59     59   			pref = {
    60     60   				calendar = 'commune';
    61     61   			};
           62  +			overlays = {};
    62     63   		}
    63     64   	end;
    64     65   	__index = {
           66  +		--------------
           67  +		-- overlays --
           68  +		--------------
           69  +		updateOverlays = function(self)
           70  +			local phys = {
           71  +				speed = self.pheno:trait('speed',1);
           72  +				jump = self.pheno:trait('jump',1);
           73  +				gravity = 1;
           74  +				speed_climb = 1;
           75  +				speed_crouch = 1;
           76  +				speed_walk = 1;
           77  +				acceleration_default = 1;
           78  +				acceleration_air = 1;
           79  +			}
           80  +			for i, o in ipairs(self.overlays) do o(phys) end
           81  +			self.entity:set_physics_override(phys)
           82  +		end;
           83  +		overlay = function(self, o)
           84  +			local id = #self.overlays+1
           85  +			self.overlays[id] = o
           86  +			self:updateOverlays()
           87  +			return id
           88  +		end;
           89  +		deleteOverlay = function(self, id)
           90  +			table.remove(self.overlays, id)
           91  +			self:updateOverlays()
           92  +		end;
           93  +
           94  +		--------------
           95  +		-- personae --
           96  +		--------------
    65     97   		pullPersona = function(self)
    66     98   			-- if later records are added in public updates, extend this function to merge them
    67     99   			-- into one object
    68    100   			local s = userStore(self.entity)
    69    101   			self.persona = s.read 'persona'
    70    102   			self.pheno = starlit.world.species.pheno(self.persona.species, self.persona.speciesVariant)
    71    103   		end;
    72    104   		pushPersona = function(self)
    73    105   			local s = userStore(self.entity)
    74    106   			s.write('persona', self.persona)
    75    107   		end;
          108  +
    76    109   		uiColor = function(self) return lib.color {hue=238,sat=.5,lum=.5} end;
          110  +
          111  +		-----------
          112  +		-- stats --
          113  +		-----------
    77    114   		statDelta = function(self, stat, d, cause, abs)
    78    115   			local dt = self.persona.statDeltas
    79    116   			local min, max, base = self:statRange(stat)
    80    117   			if abs then
    81    118   				if     d == true  then d = max
    82    119   				elseif d == false then d = min end
    83    120   			end
................................................................................
    97    134   				self:pushPersona()
    98    135   			end
    99    136   
   100    137   
   101    138   			self:updateHUD()
   102    139   			-- TODO trigger relevant animations?
   103    140   		end;
   104         -		lookupSpecies = function(self)
   105         -			return starlit.world.species.lookup(self.persona.species, self.persona.speciesVariant)
   106         -		end;
   107         -		phenoTrait = function(self, trait, dflt)
   108         --- 			local s,v = self:lookupSpecies()
   109         --- 			return v.traits[trait] or s.traits[trait] or 0
   110         -			return self.pheno:trait(trait, dflt)
   111         -		end;
   112    141   		statRange = function(self, stat) --> min, max, base
   113    142   			return starlit.world.species.statRange(
   114    143   				self.persona.species, self.persona.speciesVariant, stat)
   115    144   		end;
   116    145   		effectiveStat = function(self, stat)
   117    146   			local val
   118    147   			local min, max, base = self:statRange(stat)
................................................................................
   124    153   			else
   125    154   				val = base + self.persona.statDeltas[stat] or 0
   126    155   			end
   127    156   
   128    157   			local d = max - min
   129    158   			return val, (val - min) / d
   130    159   		end;
          160  +
          161  +		---------------
          162  +		-- phenotype --
          163  +		---------------
          164  +		lookupSpecies = function(self)
          165  +			return starlit.world.species.lookup(self.persona.species, self.persona.speciesVariant)
          166  +		end;
          167  +		phenoTrait = function(self, trait, dflt)
          168  +-- 			local s,v = self:lookupSpecies()
          169  +-- 			return v.traits[trait] or s.traits[trait] or 0
          170  +			return self.pheno:trait(trait, dflt)
          171  +		end;
   131    172   		damageModifier = function(self, kind, amt)
   132    173   			if kind == 'bluntForceTrauma' then
   133    174   				local std = self:phenoTrait 'sturdiness'
   134    175   				if std < 0 then
   135    176   					amt = amt / 1+std
   136    177   				else
   137    178   					amt = amt * 1-std
   138    179   				end
   139    180   			end
   140    181   			return amt
   141    182   		end;
          183  +
          184  +		---------
          185  +		-- HUD --
          186  +		---------
   142    187   		attachImage = function(self, def)
   143    188   			local user = self.entity
   144    189   			local img = {}
   145    190   			img.id = user:hud_add {
   146    191   				type = 'image';
   147    192   				text = def.tex;
   148    193   				scale = def.scale;
................................................................................
   380    425   				align = {x=0, y=-1};
   381    426   				z = -1;
   382    427   				update = function(user, set)
   383    428   					set('text', hudAdjustBacklight(hudCenterBG):render())
   384    429   				end;
   385    430   			};
   386    431   		end;
   387         -		-- horrible horrible HACK
   388         -		setModeHand = function(self)
   389         -			local inv = self.entity:get_inventory()
   390         -			local hnd
   391         -			if self.actMode == 'off'
   392         -				then hnd = ItemStack('starlit:_hand_dig')
   393         -				else hnd = ItemStack()
          432  +		deleteHUD = function(self)
          433  +			for name, e in pairs(self.hud.elt) do
          434  +				self:hud_delete(e.id)
   394    435   			end
   395         -			inv:set_stack('hand', 1, hnd)
   396    436   		end;
          437  +		updateHUD = function(self)
          438  +			for name, e in pairs(self.hud.elt) do
          439  +				if e.update then e.update() end
          440  +			end
          441  +		end;
          442  +
          443  +		---------------------
          444  +		-- actions & modes --
          445  +		---------------------
   397    446   		onModeChange = function(self, oldMode, silent)
   398    447   			self.hud.elt.crosshair.update()
   399    448   			if not silent then
   400    449   				local sfxt = {
   401    450   					off = 'starlit-mode-off';
   402    451   					nano = 'starlit-mode-nano';
   403    452   					psi = 'starlit-mode-psi';
................................................................................
   413    462   			local oldMode = self.actMode
   414    463   			self.actMode = mode
   415    464   			self:onModeChange(oldMode, silent)
   416    465   			if mode ~= oldMode then
   417    466   				starlit.ui.setupForUser(self)
   418    467   			end
   419    468   		end;
   420         -		deleteHUD = function(self)
   421         -			for name, e in pairs(self.hud.elt) do
   422         -				self:hud_delete(e.id)
          469  +		setModeHand = function(self) -- horrible horrible HACK
          470  +			local inv = self.entity:get_inventory()
          471  +			local hnd
          472  +			if self.actMode == 'off'
          473  +				then hnd = ItemStack('starlit:_hand_dig')
          474  +				else hnd = ItemStack()
   423    475   			end
          476  +			inv:set_stack('hand', 1, hnd)
   424    477   		end;
   425         -		updateHUD = function(self)
   426         -			for name, e in pairs(self.hud.elt) do
   427         -				if e.update then e.update() end
   428         -			end
   429         -		end;
          478  +
          479  +		---------------------
          480  +		-- intel-gathering --
          481  +		---------------------
   430    482   		clientInfo = function(self)
   431    483   			return minetest.get_player_information(self.name)
   432    484   		end;
          485  +		species = function(self)
          486  +			return starlit.world.species.index[self.persona.species]
          487  +		end;
          488  +
          489  +		--------------------
          490  +		-- event handlers --
          491  +		--------------------
   433    492   		onSignup = function(self)
   434    493   			local meta = self.entity:get_meta()
   435    494   			local inv = self.entity:get_inventory()
   436    495   			-- 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
   437    496   			inv:set_size('main', 6) -- carried items and tools. main hotbar.
   438    497   			inv:set_size('hand', 1) -- horrible hack to allow both tools and intrinsics
   439    498   
................................................................................
   471    530   			giveGifts('starlit_suit_canisters', gifts.suitCans)
   472    531   
   473    532   			giveGifts('main', gifts.carry)
   474    533   
   475    534   			self:reconfigureSuit()
   476    535   
   477    536   			-- i feel like there has to be a better way
   478         -			local cx = math.random(-500,500)
          537  +			local posrng = starlit.world.seedbank[0x13f19] -- TODO player-specific seed
          538  +			local cx = posrng:int(-500,500) --math.random(-500,500)
   479    539   			local iter, startPoint = 1
   480    540   			repeat local temp = -100
   481         -				local cz = math.random(-500,500)
          541  +				local cz = posrng:int(-500,500)
   482    542   				local cy = minetest.get_spawn_level(cx, cz)
   483    543   				if cy then
   484    544   					startPoint = vector.new(cx,cy,cz)
   485    545   					temp = starlit.world.climate.eval(startPoint,.5,.5).surfaceTemp
   486    546   				end
   487    547   				iter = iter + 1
   488    548   				if iter > 100 then break end -- avoid infiniloop in pathological conditions
................................................................................
   597    657   			}
   598    658   			me:set_eye_offset(nil, vector.new(3,-.2,10))
   599    659   			-- TODO set_clouds speed in accordance with wind
   600    660   			starlit.world.species.setupEntity(me, self.persona)
   601    661   			starlit.ui.setupForUser(self)
   602    662   			self:createHUD()
   603    663   			self:updateSuit()
          664  +			self:updateOverlays()
   604    665   		end;
          666  +		onPart = function(self)
          667  +			starlit.liveUI     [self.name] = nil
          668  +			starlit.activeUI   [self.name] = nil
          669  +			starlit.activeUsers[self.name] = nil
          670  +		end;
          671  +
          672  +		-----------------------------
          673  +		-- environment suit & body --
          674  +		-----------------------------
   605    675   		suitStack = function(self)
   606    676   			return self.entity:get_inventory():get_stack('starlit_suit', 1)
   607    677   		end;
   608    678   		suitSound = function(self, sfx)
   609    679   			-- trigger a sound effect from the player's suit computer
   610    680   			minetest.sound_play(sfx, {object=self.entity, max_hear_distance=4}, true)
   611    681   		end;
................................................................................
   630    700   					sfx = 'starlit-power-up'
   631    701   				elseif state == 'powerSave' or os == 'powerSave' then
   632    702   					sfx = 'starlit-configure'
   633    703   				end
   634    704   				if sfx then self:suitSound(sfx) end
   635    705   			end
   636    706   		end;
   637         -		species = function(self)
   638         -			return starlit.world.species.index[self.persona.species]
   639         -		end;
   640    707   		updateBody = function(self)
   641    708   			local adornment = {}
   642    709   			local suitStack = self:suitStack()
   643    710   			if suitStack and not suitStack:is_empty() then
   644    711   				local suit = suitStack:get_definition()._starlit.suit
   645    712   				suit.adorn(adornment, suitStack, self.persona)
   646    713   			end
................................................................................
   726    793   				-- TODO display power use icon
   727    794   			end
   728    795   			return supply, wasteHeat
   729    796   		end;
   730    797   		naked = function(self)
   731    798   			return self:suitStack():is_empty()
   732    799   		end;
   733         -		onPart = function(self)
   734         -			starlit.liveUI     [self.name] = nil
   735         -			starlit.activeUI   [self.name] = nil
   736         -			starlit.activeUsers[self.name] = nil
   737         -		end;
          800  +
          801  +		--------
          802  +		-- ui --
          803  +		--------
   738    804   		openUI = function(self, id, page, ...)
   739    805   			local ui = assert(starlit.interface.db[id])
   740    806   			ui:open(self, page, ...)
   741    807   		end;
   742    808   		onRespond = function(self, ui, state, resp)
   743    809   			ui:action(self, state, resp)
   744    810   		end;
   745         -
   746         -		updateWeather = function(self)
   747         -		end;
   748         -
   749         -		canInteract = function(self, with)
   750         -			return true; -- TODO
   751         -		end;
   752         -
   753    811   		trigger = function(self, which, how)
   754    812   			local p
   755    813   			local wld = self.entity:get_wielded_item()
   756    814   			if which == 'maneuver' then
   757    815   				p = self.power.maneuver
   758    816   			elseif which == 'retarget' then
   759    817   				self.action.prog = {}
................................................................................
   783    841   							end
   784    842   							local sw = starlit.item.sw.db[pgm.body.pgmId]
   785    843   							run = assert(sw.run, 'missing run() for active software ability ' .. pgm.body.pgmId)
   786    844   							break
   787    845   						end
   788    846   					end
   789    847   				end
          848  +			elseif p.ref then
          849  +				run = p.ref.run
   790    850   			else
   791    851   				error('bad ability pointer ' .. dump(p))
   792    852   			end
   793    853   			if run then
   794    854   				run(self, ctx)
   795    855   				return true
   796    856   			end
   797    857   			return false
   798    858   		end;
          859  +
          860  +		-------------
          861  +		-- weather --
          862  +		-------------
          863  +		updateWeather = function(self)
          864  +		end;
          865  +
          866  +		canInteract = function(self, with)
          867  +			return true; -- TODO
          868  +		end;
          869  +
          870  +		---------------
          871  +		-- inventory --
          872  +		---------------
   799    873   		give = function(self, item)
   800    874   			local inv = self.entity:get_inventory()
   801    875   			local function is(grp)
   802    876   				return minetest.get_item_group(item:get_name(), grp) ~= 0
   803    877   			end
   804    878   			-- TODO notif popups
   805    879   			if is 'specialInventory' then
................................................................................
   847    921   		local dehydration = p:trait 'dehydration' * biointerval
   848    922   		-- you dehydrate faster in higher temp
   849    923   		dehydration = dehydration * math.max(1, starlit.world.climate.temp(u.entity:get_pos()) / 10)
   850    924   
   851    925   		u:statDelta('nutrition', -bmr)
   852    926   		u:statDelta('hydration', -dehydration)
   853    927   
   854         -		if u:effectiveStat 'nutrition' == 0 then
   855         -			-- starvation
   856         -		end
          928  +		local moralePenalty = -1 -- 1min/min
          929  +		local fatiguePenalty = 1 -- 1min/min
          930  +		local heatPenalty = 1 -- stamina regen is divided by this
   857    931   
   858         -		if u:effectiveStat 'hydration' == 0 then
   859         -			-- dying of thirst
          932  +		do local warmth = u:effectiveStat 'warmth'
          933  +			local tempRange = u:species().tempRange
          934  +			local tComfMin, tComfMax = tempRange.comfort[1], tempRange.comfort[2]
          935  +			local tempDiff = 0
          936  +			if warmth < tComfMin then
          937  +				tempDiff = math.abs(warmth-tComfMin)
          938  +			elseif warmth > tComfMax then
          939  +				tempDiff = math.abs(warmth-tComfMax)
          940  +			end
          941  +			moralePenalty = moralePenalty + tempDiff
          942  +			heatPenalty = heatPenalty + tempDiff
   860    943   		end
   861    944   
          945  +		-- penalize heavy phys. activity
          946  +		local stamina, sp = u:effectiveStat 'stamina'
          947  +		fatiguePenalty = fatiguePenalty * (1 + 9*(1-sp))
          948  +
          949  +		local food = u:effectiveStat 'nutrition'
          950  +		local water = u:effectiveStat 'hydration'
   862    951   		local rads = u:effectiveStat 'irradiation'
          952  +		if food < 1000 then moralePenalty = moralePenalty + (1 - (food/1000)) * 5 end
          953  +		if water < 1   then moralePenalty = moralePenalty + (1 - (water/1)) * 10 end
          954  +
   863    955   		if rads > 0 then
   864    956   			u:statDelta('irradiation', -0.0001 * biointerval)
          957  +			local moraleDrainFac = 2^(rads / 2)
          958  +			moralePenalty = moralePenalty * moraleDrainFac
          959  +		end
          960  +
          961  +		u:statDelta('morale', moralePenalty * biointerval)
          962  +		u:statDelta('fatigue', fatiguePenalty * biointerval)
          963  +
          964  +		if food == 0 then -- starvation
          965  +			u:statDelta('health', -5*biointerval)
          966  +		end
          967  +
          968  +		if water == 0 then -- dying of thirst
          969  +			u:statDelta('health', -20*biointerval)
   865    970   		end
   866    971   
          972  +		if sp < 1.0 then
          973  +			u:statDelta('stamina', u:effectiveStat 'staminaRegen' / heatPenalty)
          974  +		end
   867    975   	end
   868    976   end)
   869    977   
   870    978   local cbit = {
   871    979   	up   = 0x001;
   872    980   	down = 0x002;
   873    981   	left = 0x004;
................................................................................
   893   1001   				return mustHalt
   894   1002   			else return doNothing end
   895   1003   		end
   896   1004   		local skipBits = 0
   897   1005   		if user.action.bits ~= bits then
   898   1006   			local mPrimary = what(cbit.dig)
   899   1007   			local mSecondary = what(cbit.put)
         1008  +			local mManeuver = what(cbit.manv)
   900   1009   			if mPrimary == mustInit then -- ENGINE-BUG
   901   1010   				user.action.tgt = {type='nothing'}
   902   1011   				user.action.prog = {}
   903   1012   			elseif mPrimary == mustHalt then
   904   1013   				user:trigger('primary', {state='halt'})
   905   1014   			end
   906   1015   			if mSecondary == mustHalt then
   907   1016   				user:trigger('secondary', {state='halt'})
   908   1017   			end
         1018  +			if mManeuver == mustInit then
         1019  +				user:trigger('maneuver', {state='init'})
         1020  +			elseif mManeuver == mustHalt then
         1021  +				user:trigger('maneuver', {state='halt'})
         1022  +			end
   909   1023   		end
   910   1024   		--bits = bit.band(bits, bit.bnot(skipBits))
   911         -		if bit.band(bits, cbit.dig)~=0 then
   912         -			user:trigger('primary', {state='prog', delta=delta})
         1025  +		local function prog(what)
         1026  +			user:trigger(what, {state='prog', delta=delta})
   913   1027   		end
   914         -		if bit.band(bits, cbit.put)~=0 then
   915         -			user:trigger('secondary', {state='prog', delta=delta})
   916         -		end
         1028  +		if bit.band(bits, cbit.dig)~=0  then prog 'primary'   end
         1029  +		if bit.band(bits, cbit.put)~=0  then prog 'secondary' end
         1030  +		if bit.band(bits, cbit.manv)~=0 then prog 'maneuver'  end
   917   1031   		user.action.bits = bits
   918   1032   		-- ENGINE-BUG: dig and put are not handled equally in the
   919   1033   		-- engine. it is possible for the put bit to get stuck on
   920   1034   		-- if the key is hammered while the player is not moving.
   921   1035   		-- the bit will release as soon as the player looks or turns
   922   1036   		-- nonetheless this is obnoxious
   923   1037   	end
   924   1038   end)

Modified mods/starlit/world.lua from [4823845880] to [cdaabd4e00].

    96     96   world.ecology.plants.foreach('starlit:plant-gen', {}, function(id, b)
    97     97   	local stageCt = #b.stages
    98     98   	local function stageID(n)
    99     99   		if n == stageCt then return id end
   100    100   		return id .. string.format('_stage_%s', n)
   101    101   	end
   102    102   	b.stageNodes = {}
          103  +	b.req = b.req or {}
   103    104   	local function regStage(n, st)
   104    105   		local base = {
   105    106   			description = b.name;
   106    107   			drawtype = "plantlike";
   107    108   			tiles = { tostring(st.tex) };
   108    109   			paramtype = "light";
   109    110   			paramtype2 = "meshoptions";
   110    111   			place_param2 = b.meshOpt;
   111    112   			walkable = false;
   112    113   			buildable_to = true;
   113    114   			groups = {
   114    115   				plant = 1;
   115    116   				plant_grow = stageCt ~= n and 1 or 0;
          117  +				attached_node = 3;
   116    118   			};
   117    119   			drop = st.drop;
   118    120   			_starlit = {
   119    121   				plant = {
   120    122   					id = id, stage = n;
   121    123   				};
   122    124   				recover = starlit.type.fab {
................................................................................
   130    132   							potassium = rng:int(0,1);
   131    133   						}
   132    134   					};
   133    135   				end;
   134    136   			};
   135    137   		}
   136    138   		if st.swap then
   137         -			base.node_dig_prediction = stageID(st.swap)
          139  +			base.node_dig_prediction = ""
   138    140   			function base.after_dig_node(pos, node, digger)
   139    141   				node.name = stageID(st.swap)
   140    142   				minetest.swap_node(pos, node)
   141    143   				return true
   142    144   			end
   143    145   		end
   144    146   		if st.biolum then base.light_source = st.biolum; end
................................................................................
   232    234   			user:statDelta('health', -dmg)
   233    235   		end
   234    236   	end
   235    237   end)
   236    238   
   237    239   
   238    240   world.ecology.trees.foreach('starlit:tree-gen', {}, function(id, t)
   239         -	local dec = {
   240         -		deco_type = 'lsystem';
   241         -		treedef = t.def;
   242         -	}
   243         -	for k,v in pairs(t.decorate) do dec[k]=v end
   244         -	minetest.register_decoration(dec)
          241  +	for i,td in ipairs(t.decorate) do
          242  +		local dec = {
          243  +			deco_type = 'lsystem';
          244  +			treedef = t.def;
          245  +		}
          246  +		for k,v in pairs(td) do dec[k]=v end
          247  +		minetest.register_decoration(dec)
          248  +	end
   245    249   end)
   246    250   
   247    251   minetest.register_abm {
   248    252   	label = "plant growth";
   249    253   	nodenames = {'group:plant_grow'};
   250    254   	chance = 15;
   251    255   	interval = 20;
   252    256   	catch_up = true;
   253    257   	action = function(pos, node)
   254    258   		local def = minetest.registered_nodes[node.name]._starlit.plant
   255         -		local plant = starlit.world.ecology.plants.db[def.id]
   256         -		local nextStage = plant.stageNodes[def.stage + 1]
   257         -		minetest.swap_node(pos, {name=nextStage})
          259  +		-- 5 W: maximum power for UV lamps
          260  +		-- 7 W: maximum solar power
          261  +		local uv = (minetest.get_natural_light(pos) / 15) * 7
          262  +		-- TODO compute artificial contribution
          263  +		local req = lib.tbl.defaults({
          264  +			uv = 3;
          265  +			soil = 'soil';
          266  +			temp = -10;
          267  +			humid = nil;
          268  +		}, def.growReq);
          269  +
          270  +		-- TODO check other reqs
          271  +
          272  +		if uv > req.uv then
          273  +			local plant = starlit.world.ecology.plants.db[def.id]
          274  +			local nextStage = plant.stageNodes[def.stage + 1]
          275  +			minetest.swap_node(pos, {name=nextStage})
          276  +		end
   258    277   	end;
   259    278   }

Modified mods/vtlib/math.lua from [9f5dd95326] to [6a568676fe].

   129    129   		return fn.rng(PcgRandom(self.seed+n):next())
   130    130   	end;
   131    131   	__add = function(self, n)
   132    132   		return fn.seedbank(self.seed + n)
   133    133   	end;
   134    134   }
   135    135   -- function fn.vlerp
          136  +
          137  +function fn.timespec(n)
          138  +	if n == 0 then return '0s' end
          139  +	if n < 0 then return '-' .. fn.timespec(n*-1) end
          140  +
          141  +	local sec = n % 60
          142  +	local hr = math.floor(n / 60)
          143  +	local spec = {}
          144  +
          145  +	if sec ~= 0 then table.insert(spec, string.format("%ss",  sec)) end
          146  +	if hr  ~= 0 then table.insert(spec, string.format("%shr", hr))  end
          147  +	return table.concat(spec, ' ')
          148  +end
   136    149   return fn

Modified starlit.ct from [3827ce4fcb] to [990b095f3f].

    38     38   
    39     39   	p11143: https://github.com/minetest/minetest/pull/11143.diff
    40     40   
    41     41   ### shadows
    42     42   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.
    43     43   
    44     44   ## gameplay
           45  +the most important thing to understand about starlit is that is is [*mean], by design.
           46  +
           47  +* 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.
           48  +* 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. 
           49  +
    45     50   starlit is somewhat unusual in how it uses the minetest engine. it's a voxel game but not of the minecraft variety.
    46     51   
    47     52   ### controls
    48     53   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.
    49     54   
    50     55   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.
    51     56