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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
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 version [148d1a5e9a].









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

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































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
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 version [49c5f4ea72].















































































































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
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;
}

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

518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
-----------
-- chips --
-----------

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

E.chip = { file = {} }







<







518
519
520
521
522
523
524

525
526
527
528
529
530
531
-----------
-- chips --
-----------

E.sw = {}
function E.sw.findSchematicFor(item)
	local id = ItemStack(item):get_name()

	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

E.chip = { file = {} }

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

348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
...
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
...
429
430
431
432
433
434
435


436

437
438
439
440


441
442
443
444
445
446

local function pointChanged(a,b)
	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())
	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)
		--return user:trigger('secondary', {state = 'prog', delta = 0})
	elseif pointChanged(oldTgt, point) then
................................................................................
minetest.register_item("starlit:_hand_dig", {
	type = "none",
	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}};
		};
	}
})

minetest.register_on_player_inventory_action(function(luser, act, inv, p)
	local name = luser:get_player_name()
	local user = starlit.activeUsers[name]
................................................................................
		return vector.new(
			r(pos.x),
			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));


	end
end


-- TODO timer iterates live UI








<
<
|







 







|
|
|







 







>
>
|
>
|
|
|
|
>
>






348
349
350
351
352
353
354


355
356
357
358
359
360
361
362
...
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
...
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449

local function pointChanged(a,b)
	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)


-- 	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)
		--return user:trigger('secondary', {state = 'prog', delta = 0})
	elseif pointChanged(oldTgt, point) then
................................................................................
minetest.register_item("starlit:_hand_dig", {
	type = "none",
	wield_image = "wieldhand.png",
	wield_scale = {x=1,y=1,z=2.5},
	tool_capabilities = {
		groupcaps = {
			plant = {maxlevel=1, times = {.50}};

			-- sand, dirt, gravel
			looseClump = {maxlevel=1, times = {1.5, 2.5}};
		};
	}
})

minetest.register_on_player_inventory_action(function(luser, act, inv, p)
	local name = luser:get_player_name()
	local user = starlit.activeUsers[name]
................................................................................
		return vector.new(
			r(pos.x),
			r(pos.y),
			r(pos.z)
		)
	end
	for i, it in ipairs(drops) do
		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

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

126
127
128
129
130
131
132
133

134
135
136
137
138
139
140
...
357
358
359
360
361
362
363
364


365

















366


367


















368
369
370
371
372
373
374
		table.insert(p, wrapMenu(a.w, a.h, 1.2, 2, m.opts))
	end
	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

end

starlit.interface.install(starlit.type.ui {
	id = 'starlit:user-menu';
	pages = {
		compiler = {
			setupState = function(state, user)
................................................................................
					local st = string.format('%s / %s', s.desc(amt, true), s.desc(max))
					table.insert(tb, {kind = 'hztl', padding = 0.25;
						{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'};


				}

















				table.insert(tb, wrapMenu(6.25,4, 1,2, abilities))


				return starlit.ui.build(tb)


















			end;
		};
		suit = {
			render = function(state, user)
				local suit = user:getSuit()
				local suitDef = suit:def()
				local chipW, chipH = listWrap(suitDef.slots.chips, 5)







|
>







 







|
>
>

>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
>
>

>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
...
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
		table.insert(p, wrapMenu(a.w, a.h, 1.2, 2, m.opts))
	end
	return p
end

local function pptrMatch(a,b)
	if a == nil or b == nil then return false end
	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 = {
		compiler = {
			setupState = function(state, user)
................................................................................
					local st = string.format('%s / %s', s.desc(amt, true), s.desc(max))
					table.insert(tb, {kind = 'hztl', padding = 0.25;
						{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 = {
					maneuver = {};
					direct = {};
					passive = {};
				}
				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()
				local suitDef = suit:def()
				local chipW, chipH = listWrap(suitDef.slots.chips, 5)

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

10
11
12
13
14
15
16












17
18
19
20
21
22
23
..
58
59
60
61
62
63
64

65
66
67
68
69
70
71
..
78
79
80
81
82
83
84

85
86
87
88

89
90
91
92
93
94
95
		str = T.str;
		num = T.decimal;
	}
end

-- constants
local animationFrameRate = 60













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.';
		scale = 1.0;
		params = {
................................................................................
					health = 400;
					lungCapacity = .6;
					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

				};
			};
			male = {
				name = 'Human Male';
				eyeHeight = 1.6;
				stats = {
					psiRegen = 1.0;
................................................................................
				traits = {
					health = 500;
					painTolerance = 1.0;
					lungCapacity = 1.0;
					sturdiness = 0.3;
					metabolism = .150; -- kCal/s
					dehydration = 15e-4; -- L/s

				};
			};
		};
		traits = {};

	};
}


starlit.world.species = {
	index = species;
	paramTypes = paramTypes;







>
>
>
>
>
>
>
>
>
>
>
>







 







>







 







>




>







10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
..
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
..
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
		str = T.str;
		num = T.decimal;
	}
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.';
		scale = 1.0;
		params = {
................................................................................
					health = 400;
					lungCapacity = .6;
					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;
				stats = {
					psiRegen = 1.0;
................................................................................
				traits = {
					health = 500;
					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 = species;
	paramTypes = paramTypes;

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

22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
	return lib.color {hue = h, sat = s or 1, lum = l or .7}
end
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 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 is measured in minutes. e.g. at base rate morale degrades by
	-- 60 points every hour. morale can last up to 10 days
	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'};
	-- 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
}







|







|

|







|






22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
	return lib.color {hue = h, sat = s or 1, lum = l or .7}
end
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', 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 = 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 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 = 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
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17



18
19
20








21
22
23
24


25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
..
46
47
48
49
50
51
52
53







54
55
56
57
58
59
60
..
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
...
156
157
158
159
160
161
162

163
164
165
166
167
168
169
local T = starlit.translator
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';
}









minetest.register_node('starlit:soil', {
	description = T 'Soil';
	tiles = {'default_dirt.png'};
	groups = {dirt = 1};


	drop = '';
	sounds = soilSounds;
	_starlit = {
		kind = 'block';
		elements = {};
	};
})


minetest.register_node('starlit:sand', {
	description = T 'Sand';
	tiles = {'default_sand.png'};
	groups = {dirt = 1};
	drop = '';
	sounds = sandSounds;
	_starlit = {
		kind = 'block';
		fab = starlit.type.fab { element = { silicon = 25 } };
	};
})
................................................................................
	short_description = T 'Soil';
	description = starlit.ui.tooltip {
		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};







	_starlit = {
		fab = starlit.type.fab { element = { carbon = 12 / 4 } };
	};
})

function starlit.terrain.createGrass(def)
	local drop = {
................................................................................
			};
		};
	}
	minetest.register_node(def.name, {
		description = T 'Greengraze';
		tiles = {
			def.img .. '.png';
			'default_dirt.png';
			{
				name = 'default_dirt.png^' .. def.img ..'_side.png';
				tileable_vertical = false;
			};
		};
		groups = {grass = 1, dirt = 1, sub_walk = 1};
		drop = '';
		sounds = grassSounds;
		_starlit = {
			fab = def.fab;
			recover = def.recover;
			recover_vary = def.recover_vary;
		};
	})
end


starlit.terrain.createGrass {
	name = 'starlit:greengraze';
	desc = T 'Greengraze';
	img = 'default_grass';
	fab = starlit.type.fab {
		element = {
			carbon = 12;
		};
		time = {
			shred = 2.5;
		};
................................................................................
		tiles = m.tiles or 
				(m.tone and {
					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 '';

		_starlit = {
			kind = 'block';
			elements = m.elements;
			fab = m.fab;
			recover = m.recover;
			recover_vary = m.recover_vary;
		};






<




<




|
>
>
>



>
>
>
>
>
>
>
>


<
<
>
>
|










|
|







 







|
>
>
>
>
>
>
>







 







|

|



|
|













|







 







>







1
2
3
4
5
6

7
8
9
10

11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31


32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
..
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
..
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
...
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
local T = starlit.translator
local lib = starlit.mod.lib

starlit.terrain = {}
local soilSounds = {
	footstep = 'default-dirt-footstep';

	dug = 'default-dug-node';
}
local sandSounds = {
	footstep = {name='default-sand-footstep',gain=0.1};

	dug = 'default-dug-node';
}
local grassSounds = {
	footstep = 'default-grass-footstep';
	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 = {'starlit-terrain-soil.png'};
	groups = {looseClump = 2, soil = 1};
	drop = soilDrop;
	sounds = soilSounds;
	_starlit = {
		kind = 'block';
		elements = {};
	};
})


minetest.register_node('starlit:sand', {
	description = T 'Sand';
	tiles = {'starlit-terrain-sand.png'};
	groups = {looseClump = 1, sand  = 1};
	drop = '';
	sounds = sandSounds;
	_starlit = {
		kind = 'block';
		fab = starlit.type.fab { element = { silicon = 25 } };
	};
})
................................................................................
	short_description = T 'Soil';
	description = starlit.ui.tooltip {
		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 = {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 } };
	};
})

function starlit.terrain.createGrass(def)
	local drop = {
................................................................................
			};
		};
	}
	minetest.register_node(def.name, {
		description = T 'Greengraze';
		tiles = {
			def.img .. '.png';
			'starlit-terrain-soil.png';
			{
				name = 'starlit-terrain-soil.png^' .. def.img ..'-overlay.png';
				tileable_vertical = false;
			};
		};
		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;
		};
	})
end


starlit.terrain.createGrass {
	name = 'starlit:greengraze';
	desc = T 'Greengraze';
	img = 'starlit-terrain-greengraze';
	fab = starlit.type.fab {
		element = {
			carbon = 12;
		};
		time = {
			shred = 2.5;
		};
................................................................................
		tiles = m.tiles or 
				(m.tone and {
					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;
			recover_vary = m.recover_vary;
		};

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

55
56
57
58
59
60
61

62
63
64































65
66
67
68
69
70
71
72
73
74
75

76




77
78
79
80
81
82
83
..
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
...
124
125
126
127
128
129
130












131
132
133
134
135
136
137
138
139
140
141




142
143
144
145
146
147
148
...
380
381
382
383
384
385
386
387
388

389
390
391
392
393

394
395
396




397
398
399
400
401
402
403
...
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428

429




430
431
432







433
434
435
436
437
438
439
...
471
472
473
474
475
476
477

478
479
480
481

482
483
484
485
486
487
488
...
597
598
599
600
601
602
603

604









605
606
607
608
609
610
611
...
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
...
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
...
783
784
785
786
787
788
789


790
791
792
793
794
795
796
797
798














799
800
801
802
803
804
805
...
847
848
849
850
851
852
853




854
855







856



857

858
859
860

861


862



863
864


865
866














867
868
869
870
871
872
873
...
893
894
895
896
897
898
899

900
901
902
903
904
905
906
907
908




909

910
911

912
913

914
915
916

917
918
919
920
921
922
923
924
				weapon = {primary = nil, secondary = nil};
				psi = {primary = nil, secondary = nil};
				maneuver = nil;
			};
			pref = {
				calendar = 'commune';
			};

		}
	end;
	__index = {































		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'
			self.pheno = starlit.world.species.pheno(self.persona.species, self.persona.speciesVariant)
		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;




		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
				elseif d == false then d = min end
			end
................................................................................
				self:pushPersona()
			end


			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)
			local val
			local min, max, base = self:statRange(stat)
................................................................................
			else
				val = base + self.persona.statDeltas[stat] or 0
			end

			local d = max - min
			return val, (val - min) / d
		end;












		damageModifier = function(self, kind, amt)
			if kind == 'bluntForceTrauma' then
				local std = self:phenoTrait 'sturdiness'
				if std < 0 then
					amt = amt / 1+std
				else
					amt = amt * 1-std
				end
			end
			return amt
		end;




		attachImage = function(self, def)
			local user = self.entity
			local img = {}
			img.id = user:hud_add {
				type = 'image';
				text = def.tex;
				scale = def.scale;
................................................................................
				align = {x=0, y=-1};
				z = -1;
				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()

			end
			inv:set_stack('hand', 1, hnd)
		end;




		onModeChange = function(self, oldMode, silent)
			self.hud.elt.crosshair.update()
			if not silent then
				local sfxt = {
					off = 'starlit-mode-off';
					nano = 'starlit-mode-nano';
					psi = 'starlit-mode-psi';
................................................................................
			local oldMode = self.actMode
			self.actMode = mode
			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)
			end
		end;
		updateHUD = function(self)
			for name, e in pairs(self.hud.elt) do
				if e.update then e.update() end
			end

		end;




		clientInfo = function(self)
			return minetest.get_player_information(self.name)
		end;







		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.
			inv:set_size('hand', 1) -- horrible hack to allow both tools and intrinsics

................................................................................
			giveGifts('starlit_suit_canisters', gifts.suitCans)

			giveGifts('main', gifts.carry)

			self:reconfigureSuit()

			-- i feel like there has to be a better way

			local cx = math.random(-500,500)
			local iter, startPoint = 1
			repeat local temp = -100
				local cz = math.random(-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
				iter = iter + 1
				if iter > 100 then break end -- avoid infiniloop in pathological conditions
................................................................................
			}
			me:set_eye_offset(nil, vector.new(3,-.2,10))
			-- TODO set_clouds speed in accordance with wind
			starlit.world.species.setupEntity(me, self.persona)
			starlit.ui.setupForUser(self)
			self:createHUD()
			self:updateSuit()

		end;









		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
			minetest.sound_play(sfx, {object=self.entity, max_hear_distance=4}, true)
		end;
................................................................................
					sfx = 'starlit-power-up'
				elseif state == 'powerSave' or os == 'powerSave' then
					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
				suit.adorn(adornment, suitStack, self.persona)
			end
................................................................................
				-- TODO display power use icon
			end
			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;
		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
			elseif which == 'retarget' then
				self.action.prog = {}
................................................................................
							end
							local sw = starlit.item.sw.db[pgm.body.pgmId]
							run = assert(sw.run, 'missing run() for active software ability ' .. pgm.body.pgmId)
							break
						end
					end
				end


			else
				error('bad ability pointer ' .. dump(p))
			end
			if run then
				run(self, ctx)
				return true
			end
			return false
		end;














		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
			-- TODO notif popups
			if is 'specialInventory' then
................................................................................
		local dehydration = p:trait 'dehydration' * biointerval
		-- you dehydrate faster in higher temp
		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





		if u:effectiveStat 'hydration' == 0 then
			-- dying of thirst
		end




		local rads = u:effectiveStat 'irradiation'



		if rads > 0 then
			u:statDelta('irradiation', -0.0001 * biointerval)


		end















	end
end)

local cbit = {
	up   = 0x001;
	down = 0x002;
	left = 0x004;
................................................................................
				return mustHalt
			else return doNothing end
		end
		local skipBits = 0
		if user.action.bits ~= bits then
			local mPrimary = what(cbit.dig)
			local mSecondary = what(cbit.put)

			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




		end

		--bits = bit.band(bits, bit.bnot(skipBits))
		if bit.band(bits, cbit.dig)~=0 then

			user:trigger('primary', {state='prog', delta=delta})
		end

		if bit.band(bits, cbit.put)~=0 then
			user:trigger('secondary', {state='prog', delta=delta})
		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)







>



>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>











>

>
>
>
>







 







<
<
<
<
<
<
<
<







 







>
>
>
>
>
>
>
>
>
>
>
>











>
>
>
>







 







<
|
>
|
|
|
|
|
>

<

>
>
>
>







 







|
|
|
|
|
|
<
<

>

>
>
>
>



>
>
>
>
>
>
>







 







>
|


<
>







 







>

>
>
>
>
>
>
>
>
>







 







<
<
<







 







|
|
|
|
<







<
<
<
<
<
<
<
<







 







>
>









>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







>
>
>
>
|
<
>
>
>
>
>
>
>
|
>
>
>

>
|
<
<
>

>
>

>
>
>


>
>


>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







>









>
>
>
>
|
>

<
>
|

>
|
<
<
>








55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
...
134
135
136
137
138
139
140








141
142
143
144
145
146
147
...
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
...
425
426
427
428
429
430
431

432
433
434
435
436
437
438
439
440

441
442
443
444
445
446
447
448
449
450
451
452
...
462
463
464
465
466
467
468
469
470
471
472
473
474


475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
...
530
531
532
533
534
535
536
537
538
539
540

541
542
543
544
545
546
547
548
...
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
...
700
701
702
703
704
705
706



707
708
709
710
711
712
713
...
793
794
795
796
797
798
799
800
801
802
803

804
805
806
807
808
809
810








811
812
813
814
815
816
817
...
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
...
921
922
923
924
925
926
927
928
929
930
931
932

933
934
935
936
937
938
939
940
941
942
943
944
945
946


947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
....
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024

1025
1026
1027
1028
1029


1030
1031
1032
1033
1034
1035
1036
1037
1038
				weapon = {primary = nil, secondary = nil};
				psi = {primary = nil, secondary = nil};
				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'
			self.pheno = starlit.world.species.pheno(self.persona.species, self.persona.speciesVariant)
		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
				elseif d == false then d = min end
			end
................................................................................
				self:pushPersona()
			end


			self:updateHUD()
			-- TODO trigger relevant animations?
		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)
			local val
			local min, max, base = self:statRange(stat)
................................................................................
			else
				val = base + self.persona.statDeltas[stat] or 0
			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
				else
					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';
				text = def.tex;
				scale = def.scale;
................................................................................
				align = {x=0, y=-1};
				z = -1;
				update = function(user, set)
					set('text', hudAdjustBacklight(hudCenterBG):render())
				end;
			};
		end;

		deleteHUD = function(self)
			for name, e in pairs(self.hud.elt) do
				self:hud_delete(e.id)
			end
		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';
					nano = 'starlit-mode-nano';
					psi = 'starlit-mode-psi';
................................................................................
			local oldMode = self.actMode
			self.actMode = mode
			self:onModeChange(oldMode, silent)
			if mode ~= oldMode then
				starlit.ui.setupForUser(self)
			end
		end;
		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;

		---------------------
		-- 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.
			inv:set_size('hand', 1) -- horrible hack to allow both tools and intrinsics

................................................................................
			giveGifts('starlit_suit_canisters', gifts.suitCans)

			giveGifts('main', gifts.carry)

			self:reconfigureSuit()

			-- i feel like there has to be a better way
			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 = 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
				iter = iter + 1
				if iter > 100 then break end -- avoid infiniloop in pathological conditions
................................................................................
			}
			me:set_eye_offset(nil, vector.new(3,-.2,10))
			-- 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
			minetest.sound_play(sfx, {object=self.entity, max_hear_distance=4}, true)
		end;
................................................................................
					sfx = 'starlit-power-up'
				elseif state == 'powerSave' or os == 'powerSave' then
					sfx = 'starlit-configure'
				end
				if sfx then self:suitSound(sfx) end
			end
		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
				suit.adorn(adornment, suitStack, self.persona)
			end
................................................................................
				-- TODO display power use icon
			end
			return supply, wasteHeat
		end;
		naked = function(self)
			return self:suitStack():is_empty()
		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;








		trigger = function(self, which, how)
			local p
			local wld = self.entity:get_wielded_item()
			if which == 'maneuver' then
				p = self.power.maneuver
			elseif which == 'retarget' then
				self.action.prog = {}
................................................................................
							end
							local sw = starlit.item.sw.db[pgm.body.pgmId]
							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
			-- TODO notif popups
			if is 'specialInventory' then
................................................................................
		local dehydration = p:trait 'dehydration' * biointerval
		-- you dehydrate faster in higher temp
		dehydration = dehydration * math.max(1, starlit.world.climate.temp(u.entity:get_pos()) / 10)

		u:statDelta('nutrition', -bmr)
		u:statDelta('hydration', -dehydration)

		local moralePenalty = -1 -- 1min/min
		local fatiguePenalty = 1 -- 1min/min
		local heatPenalty = 1 -- stamina regen is divided by this

		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;
	down = 0x002;
	left = 0x004;
................................................................................
				return mustHalt
			else return doNothing end
		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))

		local function prog(what)
			user:trigger(what, {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)

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

96
97
98
99
100
101
102

103
104
105
106
107
108
109
110
111
112
113
114
115

116
117
118
119
120
121
122
...
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
...
232
233
234
235
236
237
238

239
240
241
242
243
244

245
246
247
248
249
250
251
252
253
254














255
256
257
258

259
world.ecology.plants.foreach('starlit:plant-gen', {}, function(id, b)
	local stageCt = #b.stages
	local function stageID(n)
		if n == stageCt then return id end
		return id .. string.format('_stage_%s', n)
	end
	b.stageNodes = {}

	local function regStage(n, st)
		local base = {
			description = b.name;
			drawtype = "plantlike";
			tiles = { tostring(st.tex) };
			paramtype = "light";
			paramtype2 = "meshoptions";
			place_param2 = b.meshOpt;
			walkable = false;
			buildable_to = true;
			groups = {
				plant = 1;
				plant_grow = stageCt ~= n and 1 or 0;

			};
			drop = st.drop;
			_starlit = {
				plant = {
					id = id, stage = n;
				};
				recover = starlit.type.fab {
................................................................................
							potassium = rng:int(0,1);
						}
					};
				end;
			};
		}
		if st.swap then
			base.node_dig_prediction = stageID(st.swap)
			function base.after_dig_node(pos, node, digger)
				node.name = stageID(st.swap)
				minetest.swap_node(pos, node)
				return true
			end
		end
		if st.biolum then base.light_source = st.biolum; end
................................................................................
			user:statDelta('health', -dmg)
		end
	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)

end)

minetest.register_abm {
	label = "plant growth";
	nodenames = {'group:plant_grow'};
	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})
	end;

}







>













>







 







|







 







>
|
|
|
|
|
|
>










>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
|
|
|
>

96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
...
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
...
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
world.ecology.plants.foreach('starlit:plant-gen', {}, function(id, b)
	local stageCt = #b.stages
	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) };
			paramtype = "light";
			paramtype2 = "meshoptions";
			place_param2 = b.meshOpt;
			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;
				};
				recover = starlit.type.fab {
................................................................................
							potassium = rng:int(0,1);
						}
					};
				end;
			};
		}
		if st.swap then
			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
		end
		if st.biolum then base.light_source = st.biolum; end
................................................................................
			user:statDelta('health', -dmg)
		end
	end
end)


world.ecology.trees.foreach('starlit:tree-gen', {}, function(id, t)
	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'};
	chance = 15;
	interval = 20;
	catch_up = true;
	action = function(pos, node)
		local def = minetest.registered_nodes[node.name]._starlit.plant
		-- 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;
}

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

129
130
131
132
133
134
135













136
		return fn.rng(PcgRandom(self.seed+n):next())
	end;
	__add = function(self, n)
		return fn.seedbank(self.seed + n)
	end;
}
-- function fn.vlerp













return fn







>
>
>
>
>
>
>
>
>
>
>
>
>

129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
		return fn.rng(PcgRandom(self.seed+n):next())
	end;
	__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

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

38
39
40
41
42
43
44





45
46
47
48
49
50
51

	p11143: https://github.com/minetest/minetest/pull/11143.diff

### shadows
i was delighted to see dynamic shadows land in minetest, and i hope the implementation will eventually mature. however, as it stands, there are severe issues with shadows that make them essentially incompatible with complex meshes like the Starlit player character meshes. for the sake of those who don't mind these glitches, Starlit does enable shadows, but i unfortunately have to recommend that you disable them until the minetest devs get their act together on this feature.

## gameplay





starlit is somewhat unusual in how it uses the minetest engine. it's a voxel game but not of the minecraft variety.

### controls
summon your Suit Interface by pressing the [*E] / [*Inventory] key. this will allow you to move items around in your inventory, but more importantly, it also allows you select or configure your Interaction Mode.

the top three buttons can be used to select (or deactivate) an Interaction Mode. an Interaction Mode can be configured by pressing the button immediately below. the active Interaction Mode controls the behavior of the mouse buttons when no item is selected in the hotbar.








>
>
>
>
>







38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56

	p11143: https://github.com/minetest/minetest/pull/11143.diff

### 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.

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.