starlit  Check-in [4b3aa092f8]

Overview
Comment:add storage crate & generic interface for, add LED for print completion, add program tooltips, disfuckulate some longstanding idiot bugs
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 4b3aa092f85aebb8f99a8b846efc0ce4bf40058cc784c2c5c718690771b19d0f
User & Date: lexi on 2024-05-06 20:58:38
Other Links: manifest | tags
Context
2024-05-06
21:29
fix image regression check-in: 108df84ed3 user: lexi tags: trunk
20:58
add storage crate & generic interface for, add LED for print completion, add program tooltips, disfuckulate some longstanding idiot bugs check-in: 4b3aa092f8 user: lexi tags: trunk
16:20
complete (-ish) matter compiler UI (power drain still missing), add printable chemical light check-in: 3df08bd5ac user: lexi tags: trunk
Changes

Modified mods/starlit-electronics/init.lua from [6d0114ddbf] to [6f6bfb54e1].

   233    233   					--[[if not how.gift then -- cheap hack to make starting batteries fully charged
   234    234   						E.battery.setCharge(st, 0)
   235    235   					end]]
   236    236   					E.battery.update(st)
   237    237   				end;
   238    238   			};
   239    239   			fab = def.fab;
          240  +			reverseEngineer = def.reverseEngineer;
   240    241   			dynamo = {
   241    242   				vtable = E.dynamo.kind.battery;
   242    243   			};
   243    244   			battery = def;
   244    245   		};
   245    246   	})
   246    247   end)
................................................................................
   484    485   			return fab.size and fab.size.print or 1
   485    486   		else
   486    487   			return bType[s] * (bTier[s] or 1) * (bSize[s] or 1)
   487    488   		end
   488    489   	end
   489    490   
   490    491   	local swID = 'starlit_electronics:schematic_'..baseID
   491         -	fab.reverseEngineer = {
   492         -		complexity = bTier.complexity * bSize.complexity * bType.complexity;
   493         -		sw = swID;
   494         -	}
   495    492   	fab.flag = {print=true}
   496    493   
   497    494   	starlit.item.battery.link(id, {
   498    495   		name = name;
   499    496   		desc = table.concat({
   500    497   			bType.desc or '';
   501    498   			bTier.desc or '';
   502    499   			bSize.desc or '';
   503    500   		}, ' ');
   504    501   
   505    502   		fab = fab;
          503  +		reverseEngineer = {
          504  +			complexity = bTier.complexity * bSize.complexity * bType.complexity;
          505  +			sw = swID;
          506  +		};
   506    507   
   507    508   		capacity = batStat 'capacity';
   508    509   		dischargeRate  = batStat 'dischargeRate';
   509    510   		leak     = batStat 'leak';
   510    511   		decay    = batStat 'decay';
   511    512   	})
   512    513   
................................................................................
   540    541   -- chips --
   541    542   -----------
   542    543   
   543    544   E.sw = {}
   544    545   function E.sw.findSchematicFor(item)
   545    546   	local id = ItemStack(item):get_name()
   546    547   	local fm = minetest.registered_items[id]._starlit
   547         -	if not (fm and fm.fab and fm.fab.reverseEngineer) then return nil end
   548         -	local id = fm.fab.reverseEngineer.sw
          548  +	if not (fm and fm.reverseEngineer) then return nil end
          549  +	local id = fm.reverseEngineer.sw
   549    550   	return id, starlit.item.sw.db[id]
   550    551   end
   551    552   
   552    553   E.chip = { file = {} }
   553    554   do local T,G = lib.marshal.t, lib.marshal.g
   554    555   	-- love too reinvent unions from first principles
   555    556   	E.chip.data = G.struct {
................................................................................
   612    613   		elseif file.kind == 'note' then
   613    614   			local sz = 0x10 + #file.body.author
   614    615   			for _, e in pairs(file.body.entries) do
   615    616   				sz = sz + #e.title + #e.body + 0x10 -- header overhead
   616    617   			end
   617    618   			return sz
   618    619   		elseif file.kind == 'research' then
   619         -			local re = assert(minetest.registered_items[file.body.itemId]._starlit.fab.reverseEngineer)
          620  +			local re = assert(minetest.registered_items[file.body.itemId]._starlit.reverseEngineer)
   620    621   			return starlit.item.sw.db[re.sw].size * file.body.progress
   621    622   		elseif file.kind == 'sw' then
   622    623   			return starlit.item.sw.db[file.body.pgmId].size
   623    624   		elseif file.kind == 'genome' then
   624    625   			return 0 -- TODO
   625    626   		end
   626    627   	end
................................................................................
   853    854   	}
   854    855   	local n = 0
   855    856   	for _, e in pairs(chips) do
   856    857   		n = n + 1
   857    858   		if not e:is_empty() then
   858    859   			local ch = e:get_definition()._starlit.chip
   859    860   			c.cycles = c.cycles + ch.clockRate
   860         -			c.ram = c.ram + ch.clockRate
          861  +			c.ram = c.ram + ch.ram
   861    862   			c.flashFree = c.flashFree + E.chip.freeSpace(e)
   862    863   			c.powerEfficiency = c.powerEfficiency + ch.powerEfficiency
   863    864   		end
   864    865   	end
   865    866   	if n > 0 then c.powerEfficiency = c.powerEfficiency / n end
   866    867   	return c
   867    868   end

Modified mods/starlit-electronics/sw.lua from [1f9bdf2a9e] to [f5ad2a2c21].

   170    170   			job.cyclesLeft = math.max(0, job.cyclesLeft - ctx.comp.cycles)
   171    171   			if job.cyclesLeft == 0 then
   172    172   				job.timeLeft = math.max(0, job.timeLeft - interval)
   173    173   			end
   174    174   			if job.timeLeft == 0 and job.cyclesLeft == 0 then
   175    175   				table.remove(conf, jobSlot)
   176    176   				user:give(scm.output)
          177  +				user:alarm(-2, 'item')
   177    178   			else
   178    179   				conf[jobSlot].value = job_t.enc(job)
   179    180   			end
   180    181   
   181    182   			ctx.saveConf()
   182    183   		end;
   183    184   	}
................................................................................
   325    326   	size = 1e9;
   326    327   	cost = {
   327    328   		cycles = 700e6;
   328    329   		ram = 1e9;
   329    330   	};
   330    331   	run = pasv_heal(4, 50, .7);
   331    332   })
          333  +
          334  +starlit.item.sw.link('starlit_electronics:battle_buddy_extreme', {
          335  +	name = 'BattleBuddy XTREME';
          336  +	kind = 'suitPower', powerKind = 'passive';
          337  +	desc = "Who needs a unit medic when you've got BattleBuddy XTREME Edition! BattleBuddy XTREME Edition is fully loaded with emergency response protocols for wounds of every caliber, and is GUARANTEED* to keep you alive as long as you can still crawl to safety. BattleBuddy XTREME is not intended for civilian use. By using BattleBuddy XTREME, you commit to unbind House Vacsatar, its subcontractors, and cadet houses from all liability for product failure, intracellular mutilation, transcription drift, runaway prion cascades, or military defeat.\n*Guarantees not legally binding.";
          338  +	size = 4e9;
          339  +	cost = {
          340  +		cycles = 2000e6;
          341  +		ram = 8e9;
          342  +	};
          343  +	run = pasv_heal(4, 50, .7);
          344  +})
          345  +

Modified mods/starlit-scenario/init.lua from [98f93d94ac] to [739d68f851].

    30     30   	end)))
    31     31   	local r = E.chip.read(chip)
    32     32   	r.label = label
    33     33   	r.files = files
    34     34   	E.chip.write(chip, r)
    35     35   	return chip
    36     36   end
           37  +
           38  +local survivalBasics = {
           39  +		{'starlit_tech:chem_lamp', 0};
           40  +		{'starlit_tech:crate', 0};
           41  +}
    37     42   
    38     43   local chipLibrary = {
    39         -	compendium = makeChip('The Gentleman Adventurer\'s Compleat Wilderness Compendium', {
           44  +	compendium = makeChip('The Gentleman Adventurer\'s Compleat Wilderness Compendium', lib.tbl.append(survivalBasics, {
    40     45   		{'starlit_electronics:battery_chemical_imperial_small', 0};
    41         -	}, {
           46  +	}), {
    42     47   		{'starlit_electronics:shred', 0};
    43         -		--{'starlit_electronics:compile_empire', 0};
           48  +		{'starlit_electronics:compile_imperial', 0};
    44     49   		{'starlit_electronics:autodoc_deluxe', 1};
    45     50   		--{'starlit_electronics:driver_compiler_empire', 0};
    46     51   	});
    47         -	survivalware = makeChip('Emergency Survivalware', {
           52  +	survivalware = makeChip('Emergency Survivalware', lib.tbl.append(survivalBasics, {
    48     53   		{'starlit_electronics:battery_chemical_commune_small', 0};
    49         -		{'starlit_tech:chem_lamp', 0};
    50         -	}, {
           54  +	}), {
    51     55   		{'starlit_electronics:shred', 0};
    52     56   		{'starlit_electronics:compile_commune', 0};
    53     57   		{'starlit_electronics:nanomed', 0};
    54     58   		{'starlit_electronics:driver_compiler_commune', 0};
    55     59   	});
    56     60   	misfortune = makeChip("Sold1er0fMisf0rtune TOP Schematic Crackz REPACK", {
           61  +		{'starlit_tech:chem_lamp', 0};
    57     62   		{'starlit_electronics:battery_chemical_usukwinya_mid', 0};
    58     63   		{'starlit_electronics:battery_hybrid_imperial_small', 0};
    59     64   		-- ammunition
    60         -	}, {});
           65  +	}, {
           66  +		{'starlit_electronics:battle_buddy_extreme', 1}; --
           67  +	});
    61     68   }
    62     69   
    63     70   local battery = function(name)
    64     71   	local s = ItemStack(name)
    65     72   	starlit.mod.electronics.battery.setChargeF(s, 1.0)
    66     73   	return s
    67     74   end

Modified mods/starlit-scenario/mod.conf from [8a1ae19f59] to [6a41220601].

     1      1   name = starlit_scenario
     2      2   title = starlit scenarios
     3      3   description = built-in scenarios for Starsoul
     4         -depends = starlit, starlit_suit, starlit_electronics, starlit_building, starlit_material
            4  +depends = vtlib, starlit, starlit_suit, starlit_electronics, starlit_building, starlit_material, starlit_tech
     5      5   # be sure to add any mods from which you list new starting items!

Modified mods/starlit-tech/init.lua from [2a09a8ef6e] to [c5d4cbc341].

    11     11   		return string.format('starlit_tech:chem_lamp_%s',n)
    12     12   	end
    13     13   	local fab = starlit.type.fab {
    14     14   		element = { carbon = 8, magnesium = 2 };
    15     15   		cost = { power = 100 };
    16     16   		flag = { print = true };
    17     17   		time = { print = 5 };
    18         -		reverseEngineer = {
    19         -			complexity = 1;
    20         -			sw = 'starlit_tech:schematic_chem_lamp';
    21         -		};
    22     18   	};
    23     19   	for i = stages, 0, -1 do
    24     20   		minetest.register_node(chemLampID(i), {
    25     21   			short_description = 'Chem Lamp';
    26     22   			description = starlit.ui.tooltip {
    27     23   				title = 'Chem Lamp';
    28     24   				desc = "A simple carbon-frame chemical light source powered by ambient oxygen. Cheap, quick to print, and biodedragable, without any need for an electric grid or complex power storage mechanism. However, the light only lasts a few days, after which the lamp must be recycled or discarded.";
................................................................................
    30     26   				props = {
    31     27   					{title = 'Burn Remaining', desc=lib.math.timespec(stageTimeout * i), affinity=i > 4 and 'good' or 'bad'};
    32     28   					{title = 'Mass', desc='10g', affinity='info'};
    33     29   				};
    34     30   			};
    35     31   			drawtype = 'nodebox';
    36     32   			groups = {
    37         -				object = 2;
           33  +				object = 1;
    38     34   				attached_node = 1;
    39     35   			};
    40     36   			node_box = {
    41     37   				type = 'fixed';
    42     38   				fixed = {
    43     39   					-.4, -.5, -.20;
    44     40   					 .4, -.3,  .20;
................................................................................
    61     57   			on_timer = i ~= 0 and function(pos)
    62     58   				local me = minetest.get_node(pos)
    63     59   				minetest.swap_node(pos, {name=chemLampID(i-1), param2=me.param2})
    64     60   				return i > 1
    65     61   			end or nil;
    66     62   			_starlit = {
    67     63   				mass = 10;
    68         -				fab = fab;
           64  +				reverseEngineer = {
           65  +					complexity = 1;
           66  +					sw = 'starlit_tech:schematic_chem_lamp';
           67  +				};
    69     68   				recover = starlit.type.fab {
    70     69   					element = {
    71     70   						carbon = 8;
    72     71   						magnesium = math.floor(lib.math.lerp(i/stages, 0, 2));
    73     72   					};
    74     73   					time = {
    75     74   						shred = .5;
................................................................................
    89     88   		cost = {
    90     89   			cycles = 8e9;
    91     90   			ram = 16e6;
    92     91   		};
    93     92   		rarity = 1;
    94     93   	})
    95     94   end
           95  +
           96  +
           97  +minetest.register_node('starlit_tech:crate', {
           98  +	short_description = 'Crate';
           99  +	description = starlit.ui.tooltip {
          100  +		title = 'Crate';
          101  +		desc = 'A sturdy but lightweight storage crate made from solid carbon polymer.';
          102  +		props = { {title='Mass', affinity='info', desc='100g'} };
          103  +	};
          104  +	drawtype = 'nodebox';
          105  +	node_box = {
          106  +		type = 'fixed';
          107  +		fixed = {
          108  +			 .4,  .2,  .4;
          109  +			-.4, -.5, -.2;
          110  +		};
          111  +	};
          112  +	groups = {
          113  +		object = 3;
          114  +		attached_node = 3;
          115  +	};
          116  +	paramtype2 = 'facedir';
          117  +	tiles = {
          118  +		'starlit-tech-crate-top.png';
          119  +		'starlit-tech-crate-bottom.png';
          120  +
          121  +		'starlit-tech-crate-side.png^[transformFX';
          122  +		'starlit-tech-crate-side.png';
          123  +
          124  +		'starlit-tech-crate-back.png';
          125  +		'starlit-tech-crate-front.png';
          126  +	};
          127  +	_starlit = {
          128  +		mass = 100;
          129  +		reverseEngineer = {
          130  +			complexity = 1;
          131  +			sw = 'starlit_tech:schematic_crate';
          132  +		};
          133  +		recover = starlit.type.fab {
          134  +			element = { carbon = 100; };
          135  +			time = {
          136  +				shred = 1;
          137  +				shredPower = 3;
          138  +			};
          139  +		};
          140  +	};
          141  +	on_construct = function(pos)
          142  +		local m = minetest.get_meta(pos)
          143  +		local inv = m:get_inventory()
          144  +		inv:set_size('starlit:contents', 12)
          145  +	end;
          146  +	on_rightclick = function(pos, node, luser)
          147  +		if not luser then return end
          148  +		local user = starlit.activeUsers[luser:get_player_name()]
          149  +		user:openUI('starlit:box', 'index', {
          150  +			inv={
          151  +				{id = 'starlit:contents', pos=pos};
          152  +			};
          153  +		})
          154  +	end;
          155  +})
          156  +
          157  +starlit.item.sw.link('starlit_tech:schematic_crate', {
          158  +	name = 'Crate Schematic';
          159  +	kind = 'schematic';
          160  +	input = starlit.type.fab {
          161  +		element = { carbon = 100; };
          162  +		flag = {print = true};
          163  +		time = {print = 25};
          164  +		cost = {power = 250};
          165  +	};
          166  +	output = 'starlit_tech:crate';
          167  +	size = 48e6;
          168  +	cost = {
          169  +		cycles = 12e9;
          170  +		ram = 16e6;
          171  +	};
          172  +	rarity = 1;
          173  +})

Modified mods/starlit/compile.lua from [8a9b22510e] to [5db02bfe42].

   314    314   							{kind = 'button', w=5,h=1.5; id='showAll', label='Show All'};
   315    315   							{kind = 'button', w=5,h=1.5; id='find', label='Find'};
   316    316   						})
   317    317   					end
   318    318   				else
   319    319   					if sel.scm == nil then
   320    320   						for idx, ent in ipairs(sel.scms) do
   321         -							local fab = ItemStack(ent.sw.output):get_definition()._starlit.fab
          321  +							local fab = ent.sw.input
   322    322   							if fab.flag and fab.flag.print then
   323    323   								local req = fab:visualize()
   324    324   								pushSelector('scm_' .. idx, ent.sw.output, ent.sw.name, nil, req)
   325    325   							end
   326    326   						end
   327    327   						table.insert(pgmSelector, back)
   328    328   					else

Modified mods/starlit/init.lua from [a5bde19f07] to [df4ee2ddd8].

   399    399   })
   400    400   minetest.register_item("starlit:_hand_dig", {
   401    401   	type = "none",
   402    402   	wield_image = "wieldhand.png",
   403    403   	wield_scale = {x=1,y=1,z=2.5},
   404    404   	tool_capabilities = {
   405    405   		groupcaps = {
   406         -			object = {maxlevel=1, times = {.20,.10}};
          406  +			object = {maxlevel=1, times = {.10,.20,.40}};
   407    407   			plant = {maxlevel=1, times = {.50}};
   408    408   
   409    409   			-- sand, dirt, gravel
   410    410   			looseClump = {maxlevel=1, times = {1.5, 2.5}};
   411    411   		};
   412    412   	}
   413    413   })

Modified mods/starlit/interfaces.lua from [ddaa1e9478] to [39c7d68ac6].

    93     93   			kind = i.img and 'contact' or 'button', close = i.close;
    94     94   			color = i.color;
    95     95   			fg = i.fg;
    96     96   			label = i.label;
    97     97   			img = i.img;
    98     98   			id = i.id;
    99     99   			w = bw, h = rh;
          100  +			desc = i.desc;
   100    101   		})
   101    102   		if i.cfg then 
   102    103   			table.insert(bar, {
   103    104   				kind = 'button';
   104    105   				color = i.color;
   105    106   				fg = i.fg;
   106    107   				label = "CFG";
................................................................................
   306    307   								if e.key == 'disable' and e.value == 'yes' then
   307    308   									color.lum = -.2
   308    309   									fg = lib.color {hue=color.hue,sat=0.7,lum=0.7}
   309    310   									break
   310    311   								end
   311    312   							end
   312    313   						end
   313         -						if tbl then table.insert(tbl, {
   314         -							color = color, fg = fg;
   315         -							label = r.sw.label or r.sw.name;
   316         -							id = string.format('suit_pgm_%s_', id);
   317         -							cfg = cfg, close = close;
   318         -						}) end
          314  +						if tbl then
          315  +							local props = {
          316  +								{title = "Size", desc=lib.math.siUI('B', r.sw.size), affinity='info'};
          317  +							}
          318  +							if r.sw.cost and r.sw.cost.ram then
          319  +								table.insert(props, {title = "Memory Usage", desc=lib.math.siUI('B', r.sw.cost.ram), affinity='info'})
          320  +							end
          321  +							if r.sw.cost and r.sw.cost.cycles then
          322  +								table.insert(props, {title = "Compute Usage", desc=lib.math.siUI('cycles',r.sw.cost.cycles,true), affinity='info'})
          323  +							end
          324  +							if r.powerCost then
          325  +								table.insert(props, {title = "Power Draw", desc=lib.math.siUI('W', r.powerCost), affinity='info'})
          326  +							end
          327  +							if r.speed then
          328  +								table.insert(props, {title = "Minimum Runtime", desc=lib.math.timespec(r.speed), affinity='info'})
          329  +							end
          330  +							table.insert(tbl, {
          331  +								color = color, fg = fg;
          332  +								label = r.sw.label or r.sw.name;
          333  +								id = string.format('suit_pgm_%s_', id);
          334  +								desc = starlit.ui.tooltip {
          335  +									title = r.sw.name;
          336  +									desc = r.sw.desc;
          337  +									color = lib.color(1,0,.8);
          338  +									props = props;
          339  +								};
          340  +								cfg = cfg, close = close;
          341  +							})
          342  +						end
   319    343   					end
   320    344   				end
   321    345   				local menu = { kind = 'vert', mode = 'sw', padding = 0.5 }
   322    346   				if swm then table.insert(menu, abilityMenu(swm)) end
   323    347   
   324    348   				local inv = user.entity:get_inventory()
   325    349   				--[[
................................................................................
   515    539   					user:suitPowerStateSet(suitMode)
   516    540   					return true
   517    541   				end
   518    542   			end;
   519    543   		};
   520    544   	};
   521    545   })
          546  +
          547  +starlit.interface.install(starlit.type.ui {
          548  +	id = 'starlit:box';
          549  +	pages = {
          550  +		index = {
          551  +			setupState = function(state, user, ctx)
          552  +				state.ctx = ctx
          553  +			end;
          554  +			handle = function(state, user, q)
          555  +				if q.quit then
          556  +					user:suitSound 'starlit-quit' -- TODO better sound
          557  +				end
          558  +			end;
          559  +			render = function(state, user)
          560  +				local body = {kind='vert', w=6; mode='hw', spacing=.5, padding=1 }
          561  +				for i, l in ipairs(state.ctx.inv) do
          562  +					local inv = minetest.get_meta(l.pos):get_inventory()
          563  +					local w = l.w or 6
          564  +					if l.label then
          565  +						table.insert(body, {kind = 'hbar'; text = l.label, w=w+.5, h = .5})
          566  +					end
          567  +					table.insert(body, {kind = 'list';
          568  +						w = w, h = inv:get_size(l.id)/w;
          569  +						node = l.pos, inv = l.id;
          570  +						spacing = .1;
          571  +					})
          572  +				end
          573  +				table.insert(body, {kind = 'list';
          574  +					target = 'current_player', inv = 'main';
          575  +					w = 6, h = 1, spacing = 0.1;
          576  +				})
          577  +				return starlit.ui.build(body)
          578  +			end;
          579  +		}
          580  +	}
          581  +})

Modified mods/starlit/ui.lua from [16bf595c8c] to [5149fd5b74].

   131    131   		return climg('',         'starlit-ui-button-sw.png')       ..
   132    132   		       climg(':hovered', 'starlit-ui-button-sw-hover.png') ..
   133    133   		       climg(':pressed', 'starlit-ui-button-sw-press.png')
   134    134   	end
   135    135   	local function widget(...)
   136    136   		table.insert(lines, string.format(...))
   137    137   	end
          138  +	local specializedTooltip = false
   138    139   	if def.kind == 'vert' then
   139    140   		for _, w in ipairs(def) do
   140    141   			local src, st = starlit.ui.build(w, state)
   141    142   			widget('container[%s,%s]%scontainer_end[]', state.x, state.y, src)
   142    143   			state.y=state.y + state.spacing + st.h
   143    144   			state.w = math.max(state.w, st.w)
   144    145   		end
          146  +		state.y = state.y - state.spacing
   145    147   		state.w = state.w + state.padding
   146    148   		state.h = state.y + state.padding/2
   147    149   	elseif def.kind == 'hztl' then
   148    150   		for _, w in ipairs(def) do
   149    151   			local src, st = starlit.ui.build(w, state)
   150    152   			widget('container[%s,%s]%scontainer_end[]', state.x, state.y, src)
   151    153   			-- TODO alignments
................................................................................
   195    197   				def.id)
   196    198   		end
   197    199   		state.w = state.w + state.padding
   198    200   		state.h = state.h + state.padding/2
   199    201   	elseif def.kind == 'list' then
   200    202   		local slotTypes = {
   201    203   			plain = {hue = 200, sat = -.1, lum = 0};
   202         -			element = {hue = 20, sat = -.3, lum = 0};
          204  +-- 			element = {hue = 20, sat = -.3, lum = 0};
   203    205   			chip = {hue = 0, sat = -1, lum = 0};
   204         -			psi = {hue = 300, sat = 0, lum = 0};
          206  +-- 			psi = {hue = 300, sat = 0, lum = 0};
   205    207   			power = {hue = 50, sat = 0, lum = .2};
   206    208   		}
   207    209   		local img
   208    210   		if state.mode == 'hw' then
   209    211   			img = lib.image('starlit-ui-slot-physical.png');
   210    212   		else
   211    213   			img = lib.image('starlit-ui-slot.png'):shift(slotTypes[def.listContent or 'plain']);
   212    214   		end
   213    215   		local spac = state.spacing
          216  +		local target = def.target
          217  +		if not target and def.node then
          218  +			target=string.format('nodemeta:%s,%s,%s', def.node.x,def.node.y,def.node.z)
          219  +		end
   214    220   		widget('style_type[list;spacing=%s,%s]',spac,spac)
   215    221   		assert(def.w and def.h, 'ui-lists require a fixed size')
   216    222   		for lx = 0, def.w-1 do
   217    223   		for ly = 0, def.h-1 do
   218    224   			local ox, oy = state.x + lx*(1+spac), state.y + ly*(1+spac)
   219    225   			table.insert(lines, string.format('image[%s,%s;1.1,1.1;%s]', ox-0.05,oy-0.05, img:render()))
   220    226   		end end
   221    227   		table.insert(lines, string.format('listcolors[#00000000;#ffffff10]')) -- FIXME
   222    228   		table.insert(lines, string.format('list[%s;%s;%s,%s;%s,%s;%s]',
   223         -			E(def.target), E(def.inv),
          229  +			E(target), E(def.inv),
   224    230   			state.x, state.y,
   225    231   			def.w,   def.h,
   226    232   			def.idx))
   227    233   		local sm = 1
   228    234   		state.w = def.w * sm + (spac * (def.w - 1))
   229    235   		state.h = def.h * sm + (spac * (def.h - 1))
   230    236   	elseif def.kind == 'contact' then
................................................................................
   231    237   		if def.color then table.insert(lines, btnColorDef(def.id)) end
   232    238   		local img = def.img
   233    239   		local desc
   234    240   		if def.item then
   235    241   			img  = ItemStack(def.item):get_name()
   236    242   			desc = ItemStack(def.item):get_description()
   237    243   		end
          244  +		desc = def.desc or desc
   238    245   		widget('%simage_button%s[%s,%s;%s,%s;%s;%s;%s]',
   239    246   			def.item and 'item_' or '',
   240    247   			def.close and '_exit' or '',
   241    248   			state.x, state.y, def.w, def.h,
   242    249   			E(img), E(def.id), E(def.label or ''))
   243         -		if desc and not def.desc then
          250  +		if desc then
   244    251   			widget('tooltip[%s;%s]', E(def.id), E(desc))
          252  +			specializedTooltip = true
   245    253   		end
   246    254   	elseif def.kind == 'button' then
   247    255   		if def.color then table.insert(lines, btnColorDef(def.id)) end
   248    256   		local label = E(def.label or '')
   249    257   		if state.fg then label = lib.color(state.fg):fmt(label) end
   250    258   		widget('button%s[%s,%s;%s,%s;%s;%s]',
   251    259   			def.close and '_exit' or '',
   252    260   			state.x, state.y, def.w, def.h,
   253    261   			E(def.id), label)
          262  +		if def.desc then
          263  +			widget('tooltip[%s;%s]', E(def.id), E(def.desc))
          264  +			specializedTooltip = true
          265  +		end
   254    266   	elseif def.kind == 'img' then
   255    267   		widget('%s[%s,%s;%s,%s;%s]',
   256    268   			def.item and 'item_image' or 'image',
   257    269   			state.x, state.y, def.w, def.h, E(def.item or def.img))
   258    270   	elseif def.kind == 'label' then
   259    271   		local txt = E(def.text)
   260    272   		if state.fg then txt = lib.color(state.fg):fmt(txt) end
................................................................................
   283    295   		if def.text then
   284    296   			widget('hypertext[%s,%s;%s,%s;;%s]',
   285    297   				state.x, state.y, def.w, def.h,
   286    298   				string.format('<global halign=center valign=middle color=%s>%s', fg:hex(), E(def.text)))
   287    299   		end
   288    300   	end
   289    301   
   290         -	if def.desc then
          302  +	if def.desc and not specializedTooltip then
   291    303   		local coord
   292    304   		if def.id then
   293    305   			coord = E(def.id)
   294    306   		else
   295    307   			coord = string.format("%s,%s;%s,%s", state.x, state.y, def.w, def.h)
   296    308   		end
   297    309   		widget('tooltip[%s;%s;#000000;#ffffff]', coord, E(def.desc))

Modified mods/starlit/user.lua from [aa3a95c242] to [d58ed76b59].

    52     52   		side = 'right';
    53     53   	};
    54     54   	fatigue = {
    55     55   		icon = lib.image('starlit-ui-alert-fatigue.png');
    56     56   		bg = lib.image('starlit-ui-alert-bg-fatigue.png');
    57     57   		side = 'right';
    58     58   	};
           59  +	item = {
           60  +		icon = lib.image('starlit-ui-alert-item.png');
           61  +		bg = lib.image('starlit-ui-alert-bg-success.png');
           62  +		side = 'right';
           63  +	};
    59     64   }
    60     65   
    61     66   starlit.type.user = lib.class {
    62     67   	name = 'starlit:user';
    63     68   	leds = leds;
    64     69   	construct = function(ident)
    65     70   		local name, luser
................................................................................
   289    294   				local v,txt,color,txtcolor,hl,hlcolor = def.measure(luser,def)
   290    295   				v = math.max(0, math.min(1, v))
   291    296   				local n = math.floor(v*16) + 1
   292    297   				local function adjust(img)
   293    298   					return hudAdjustBacklight(lib.image(img)):shift(color or def.color)
   294    299   				end
   295    300   				local img = adjust 'starlit-ui-meter.png'
   296         -				if def.flipX then
   297         -					img = img:transform 'FX'
   298         -				end
   299    301   				img = img:render()
   300    302   				img = img .. '^[verticalframe:17:' .. tostring(17 - n)
   301    303   				if hl then
   302    304   					hl = math.floor(hl*16) + 1
   303    305   					local hi = hudAdjustBacklight(lib.image 'starlit-ui-meter-hl.png')
   304    306   						:shift(hlcolor or def.color)
   305    307   						:render()
   306    308   					hi = hi .. '^[verticalframe:17:' .. tostring(17 - hl)
   307    309   					img = string.format('%s^(%s)', img, hi)
   308    310   				end
   309    311   				img = string.format('%s^(%s)', img, adjust 'starlit-ui-meter-readout.png':render())
          312  +				if def.flipX then
          313  +					img = img .. '^[transformFX'
          314  +				end
   310    315   				luser:hud_change(m.meter, 'text', img)
   311    316   				if txt then
   312    317   					luser:hud_change(m.readout, 'text', txt)
   313    318   				end
   314    319   				if txtcolor then
   315    320   					luser:hud_change(m.readout, 'number', txtcolor:hex())
   316    321   				end

Modified mods/vtlib/image.lua from [33182947e9] to [a9a0d17ab5].

    11     11   			local bracket = false
    12     12   			if self.combine then
    13     13   				str = string.format('[combine:%sx%s', self.w, self.h)
    14     14   				for _,i in pairs(self.atop) do
    15     15   					str = str .. string.format(':%s,%s=(%s)', i.at.x, i.at.y, i.img:render())
    16     16   				end
    17     17   			else
    18         -				for _,i in pairs(self.atop) do
    19         -					str = '(' .. i.img:render() .. ')^' .. str
    20         -				end
    21     18   				if str ~= '' then
    22     19   					str = str .. '('
    23     20   					bracket = true
    24     21   				end
           22  +				for _,i in pairs(self.atop) do
           23  +					str = '(' .. i.img:render() .. ')^' .. str
           24  +				end
    25     25   				str = str .. self.string
    26     26   			end
    27     27   			for _,e in pairs(self.fx) do
    28     28   				str = str .. '^[' .. e
    29     29   				-- be sure to escape ones that take arguments
    30     30   				-- correctly!
    31     31   			end

Modified mods/vtlib/math.lua from [557fe13815] to [276f7a5d6e].

    77     77   					return string.format("%s%s%s",
    78     78   						vd, (full and (' ' .. pmin) or smin), unitForAmt(vd))
    79     79   				end
    80     80   			end
    81     81   		end
    82     82   	end
    83     83   
           84  +	if prec then val = lib.math.trim(val,prec) end
    84     85   	return string.format("%s%s", val, unitForAmt(val))
    85     86   end
    86     87   function fn.siUI(u,v,f,us,...) return fn.si(u,v,f,us,2,...) end
    87     88   
    88     89   function fn.lerp(t, a, b) return (1-t)*a + t*b end
    89     90   function fn.gradient(grad, pos)
    90     91   	local n = #grad
................................................................................
   154    155   	end;
   155    156   }
   156    157   -- function fn.vlerp
   157    158   
   158    159   function fn.timespec(n)
   159    160   	if n == 0 then return '0s' end
   160    161   	if n < 0 then return '-' .. fn.timespec(n*-1) end
          162  +	if n < 1 then return fn.siUI('s', n) end
   161    163   
   162    164   	local sec = math.floor(n % 60)
   163    165   	local min = math.floor(n / 60)
   164    166   	local hr = math.floor(min / 60)
   165    167   	min = min % 60
   166    168   	local spec = {}
   167    169   
   168    170   	if hr  ~= 0 then table.insert(spec, string.format("%shr", hr))  end
   169    171   	if min  ~= 0 then table.insert(spec, string.format("%sm", min))  end
   170    172   	if sec ~= 0 then table.insert(spec, string.format("%ss",  sec)) end
   171    173   	return table.concat(spec, ' ')
   172    174   end
   173    175   return fn