Index: mods/starlit-electronics/init.lua ================================================================== --- mods/starlit-electronics/init.lua +++ mods/starlit-electronics/init.lua @@ -566,10 +566,11 @@ }; drm = T.u8; -- inhibit copying name = T.str; body = T.text; }, function(file) -- enc + assert(E.chip.file[file.kind], string.format('invalid file kind "%s"', file.kind)) local b = E.chip.file[file.kind].enc(file.body) return { kind = file.kind; drm = file.drm; name = file.name; @@ -874,11 +875,10 @@ read = function(self) local dat = E.chip.read(self.chip) return dat.files[self.inode] end; write = function(self,data) - -- print('writing', self.chip, self.inode) return E.chip.fileWrite(self.chip, self.inode, data) end; erase = function(self) local dat = E.chip.read(self.chip) table.remove(dat.files, self.inode) @@ -911,10 +911,11 @@ if fl.kind == 'sw' then local s = starlit.item.sw.db[fl.body.pgmId] table.insert(sw, { sw = s, chip = e, chipSlot = i; file = fl, inode = inode; + id = fl.body.pgmId; }) end end end end @@ -921,10 +922,11 @@ end for _, s in pairs(sw) do if s.sw.cost.ram <= comp.ram and pred(s) then table.insert(r, { + id = s.id; sw = s.sw; chip = s.chip, chipSlot = s.chipSlot; file = s.file; fd = E.chip.fileHandle(s.chip, s.inode); speed = s.sw.cost.cycles / comp.cycles; Index: mods/starlit-electronics/sw.lua ================================================================== --- mods/starlit-electronics/sw.lua +++ mods/starlit-electronics/sw.lua @@ -135,22 +135,64 @@ ram = 500e6; }; run = shredder{range=3, powerDraw=200}; }) -starlit.item.sw.link('starlit_electronics:compile_commune', { +local function matterCompiler(desc) + return { + name = desc.name; + kind = 'suitPower', powerKind = 'direct'; + desc = desc.desc; + size = desc.size; + cost = desc.cost; + ui = 'starlit:compile-matter-component'; + bgProc = function(user, ctx, interval, runState) + if runState.flags.compiled == true then return false end + -- only so many nanides to go around + runState.flags.compiled = true + + local conf = ctx.file.body.conf + local job_t = starlit.store.compilerJob + local job, jobSlot + for i, v in ipairs(conf) do + if v.key == 'job' then + job = job_t.dec(v.value) + jobSlot = i + goto found + end + end + + ::notfound:: do + return + end + + ::found:: + local scm = starlit.item.sw.db[job.schematic] + job.cyclesLeft = math.max(0, job.cyclesLeft - ctx.comp.cycles) + if job.cyclesLeft == 0 then + job.timeLeft = math.max(0, job.timeLeft - interval) + end + if job.timeLeft == 0 and job.cyclesLeft == 0 then + table.remove(conf, jobSlot) + user:give(scm.output) + else + conf[jobSlot].value = job_t.enc(job) + end + + ctx.saveConf() + end; + } +end + +starlit.item.sw.link('starlit_electronics:compile_commune', matterCompiler { name = 'Compile Matter'; - kind = 'suitPower', powerKind = 'direct'; desc = "A basic suit matter compiler program. It's rather slow, but it's been ruthlessly optimized for size- and memory-efficiency by some of the Commune's most fanatic coders, to the point where every Commune nanosuit can come with the program preinstalled."; size = 700e3; cost = { cycles = 4e9; ram = .3e9; }; - ui = 'starlit:compile-matter-component'; - run = function(user, ctx) - end; }) starlit.item.sw.link('starlit_electronics:compile_block_commune', { name = 'Compile Block'; kind = 'suitPower', powerKind = 'active'; @@ -163,22 +205,18 @@ ui = 'starlit:compile-matter-block'; run = function(user, ctx) end; }) -starlit.item.sw.link('starlit_electronics:compile_imperial', { +starlit.item.sw.link('starlit_electronics:compile_imperial', matterCompiler { name = 'Genesis Deluxe'; - kind = 'suitPower', powerKind = 'direct'; desc = "House Bascundir has long dominated the matter compiler market in the Crystal Sea. Their firmware is excessively complex due to mountains of specialized edge-case handling, but the end result is certainly speedier than the competitors'."; size = 2e4; cost = { cycles = 100e6; ram = 1.5e9; }; - ui = 'starlit:compile-matter-component'; - run = function(user, ctx) - end; }) do local J = starlit.store.compilerJob starlit.item.sw.link('starlit_electronics:driver_compiler_commune', { name = 'Matter Compiler'; @@ -190,10 +228,11 @@ ram = .2e9; }; ui = 'starlit:device-compile-matter-component'; run = function(user, ctx) end; + --[[ bgProc = function(user, ctx, interval, runState) if runState.flags.compiled == true then return false end -- only so many nanides to go around runState.flags.compiled = true local time = minetest.get_gametime() @@ -234,11 +273,11 @@ end if not cyclesLeft > 0 then break end end end ctx.saveConf() - end; + end;]] }) end local function pasv_heal(effect, energy, lvl, pgmId) return function(user, ctx, interval, runState) Index: mods/starlit-material/elements.lua ================================================================== --- mods/starlit-material/elements.lua +++ mods/starlit-material/elements.lua @@ -42,14 +42,19 @@ calcium = { name = 'calcium', sym = 'Ca', n = 20; density = 1.55; metal = true; color = lib.color(1,1,0.7); }; + magnesium = { + name = 'magnesium', sym = 'Mg', n = 12, density = 1.738; + metal = true; + color = lib.color(0.7, 0.7, 0.7); + }; aluminum = { name = 'aluminum', sym = 'Al', n = 13; density = 2.7; metal = true; - color = lib.color(0.9,.95,1); + color = lib.color(0.5,.55,.6); }; iron = { name = 'iron', sym = 'Fe', n = 26; density = 7.874; metal = true; color = lib.color(.3,.3,.3); Index: mods/starlit-scenario/init.lua ================================================================== --- mods/starlit-scenario/init.lua +++ mods/starlit-scenario/init.lua @@ -44,10 +44,11 @@ {'starlit_electronics:autodoc_deluxe', 1}; --{'starlit_electronics:driver_compiler_empire', 0}; }); survivalware = makeChip('Emergency Survivalware', { {'starlit_electronics:battery_chemical_commune_small', 0}; + {'starlit_tech:chem_lamp', 0}; }, { {'starlit_electronics:shred', 0}; {'starlit_electronics:compile_commune', 0}; {'starlit_electronics:nanomed', 0}; {'starlit_electronics:driver_compiler_commune', 0}; ADDED mods/starlit-tech/init.lua Index: mods/starlit-tech/init.lua ================================================================== --- mods/starlit-tech/init.lua +++ mods/starlit-tech/init.lua @@ -0,0 +1,95 @@ +local lib = starlit.mod.lib + + +do -- chemlamp + local burnTime = 60*60 + local maxBright = 12 + local stages = maxBright + local stageTimeout = burnTime / stages + local function chemLampID(n) + if n == stages then return 'starlit_tech:chem_lamp' end + return string.format('starlit_tech:chem_lamp_%s',n) + end + local fab = starlit.type.fab { + element = { carbon = 8, magnesium = 2 }; + cost = { power = 100 }; + flag = { print = true }; + time = { print = 5 }; + reverseEngineer = { + complexity = 1; + sw = 'starlit_tech:schematic_chem_lamp'; + }; + }; + for i = stages, 0, -1 do + minetest.register_node(chemLampID(i), { + short_description = 'Chem Lamp'; + description = starlit.ui.tooltip { + title = 'Chem Lamp'; + 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."; + color = lib.color(1,.4,.1); + props = { + {title = 'Burn Remaining', desc=lib.math.timespec(stageTimeout * i), affinity=i > 4 and 'good' or 'bad'}; + {title = 'Mass', desc='10g', affinity='info'}; + }; + }; + drawtype = 'nodebox'; + groups = { + object = 2; + attached_node = 1; + }; + node_box = { + type = 'fixed'; + fixed = { + -.4, -.5, -.20; + .4, -.3, .20; + }; + }; + tiles = { + lib.image 'starlit-tech-lamp-glow.png' + :fade(1 - i/stages) + :blit(lib.image 'starlit-tech-lamp.png') + :render(); + }; + paramtype = 'light'; + paramtype2 = 'wallmounted'; + wallmounted_rotate_vertical = true; + light_source = math.floor(lib.math.lerp(i/stages, 0, maxBright)); + on_construct = i ~= 0 and function(pos) + local t = minetest.get_node_timer(pos) + t:start(stageTimeout) + end or nil; + on_timer = i ~= 0 and function(pos) + local me = minetest.get_node(pos) + minetest.swap_node(pos, {name=chemLampID(i-1), param2=me.param2}) + return i > 1 + end or nil; + _starlit = { + mass = 10; + fab = fab; + recover = starlit.type.fab { + element = { + carbon = 8; + magnesium = math.floor(lib.math.lerp(i/stages, 0, 2)); + }; + time = { + shred = .5; + shredPower = 2; + }; + }; + + }; + }) + end + starlit.item.sw.link('starlit_tech:schematic_chem_lamp', { + name = 'Chem Lamp Schematic'; + kind = 'schematic'; + input = fab; + output = chemLampID(stages); + size = 32e6; + cost = { + cycles = 8e9; + ram = 16e6; + }; + rarity = 1; + }) +end ADDED mods/starlit-tech/mod.conf Index: mods/starlit-tech/mod.conf ================================================================== --- mods/starlit-tech/mod.conf +++ mods/starlit-tech/mod.conf @@ -0,0 +1,3 @@ +name = starlit_tech +title = starlit tech +depends = vtlib, starlit, starlit_material, starlit_electronics ADDED mods/starlit/compile.lua Index: mods/starlit/compile.lua ================================================================== --- mods/starlit/compile.lua +++ mods/starlit/compile.lua @@ -0,0 +1,494 @@ +local lib = starlit.mod.lib + +local recentJobs do + local T,G = lib.marshal.t, lib.marshal.g + recentJobs = G.array(8, T.str) +end +local function getRecentJobs(state) + for i,v in ipairs(state.pgm.file.body.conf) do + if v.key == 'recent' then + return recentJobs.dec(v.value), i + end + end + return {} +end + +local function buyPrintJob(state, invs, cost, scm) + -- consume consumables + -- FIXME handle cost.leftovers from partially-consumed items + for _, v in pairs(cost.consume) do +-- assert(v.inv == 1, 'impossible condition (wrong inventory ID)') + local i = invs[v.inv] + i.hnd:set_stack(i.list, v.slot, v.remain) + end + local output = ItemStack(scm.sw.output):get_definition() + local fab = scm.sw.input + + local job_t = starlit.store.compilerJob + local conf = state.pgm.file.body.conf + table.insert(conf, { + key='job', value=job_t.enc { + schematic = scm.id; + cyclesLeft = scm.sw.cost and scm.sw.cost.cycles or 0; + timeLeft = (fab.time and fab.time.print or 0); + } + }) +end + +local function compilerCanPrint(user, cpl, scm) + local E = starlit.mod.electronics + local output = ItemStack(scm.sw.output):get_definition() + local fab = scm.sw.input + local sw = scm.sw + local ok, consume, unsat, leftover, itemSpec = fab:seek { + user.entity:get_inventory():get_list 'main'; + } + + local cost = { + fab = fab, output = output; + consume = consume, unsat = unsat, leftover = leftover, itemSpec = itemSpec; + runtimeEstimate = scm.speed + cpl.speed + (fab.time and fab.time.print or 0); + power = cpl.powerCost + scm.powerCost; + ram = (cpl.cost and cpl.cost.ram or 0) + + (scm.cost and scm.cost.ram or 0); + cycles = (cpl.cost and cpl.cost.cycles or 0) + + (scm.cost and scm.cost.cycles or 0); + } + + local userComp = E.chip.sumCompute( + user.entity:get_inventory():get_list 'starlit_suit_chips' + ) + + if ok and cost.power <= user:suitCharge() and cost.ram <= userComp.ram then + return true, cost + else return false, cost end +end + +starlit.alg.compilerCanPrint = compilerCanPrint + +-- TODO destroy suit interfaces when power runs out or suit/chip is otherwise disabled +starlit.interface.install(starlit.type.ui { + id = 'starlit:compile-matter-component'; + sub = { + suit = function(state, user, evt) + if evt.kind == 'disrobe' then state:close() + elseif evt.kind == 'power' and evt.mode == 'off' then state:close() end + end; + playerInventory = function(state,user) + -- refresh + end; + }; + pages = { + index = { + setupState = function(state, user, ctx) + local E = starlit.mod.electronics + state.pgm = ctx.program + state.ctx = ctx + state.select = {} + if ctx.context == 'suit' then + state.fetch = function() + local cst = user.entity:get_inventory():get_list 'starlit_suit_chips' + local cl = {order={}, map={}, slot={}} + for i, c in ipairs(cst) do + if not c:is_empty() then + local d = E.chip.read(c) + local co = { + stack = c; + data = d; + } + table.insert(cl.order, co) + cl.map[d.uuid] = co + cl.slot[i] = co + end + end + + -- kill me fam + if ( state.select.chip + and state.select.chip ~= true + and not cl.map[state.select.chip]) + or (state.select.scm + and not state.select.scms[state.select.scm]) + then + -- chip or pgm no longer available + user:suitSound 'starlit-error' + state.select = {} + end + state.select.chips = cl + + state.select.scms = {} + if state.select.chip then + state.select.scms = E.chip.usableSoftware(cst,nil, function(s) + if state.select.chip ~= true then + if cl.slot[s.chipSlot].data.uuid ~= state.select.chip then + return false + end + end + return s.sw.kind == 'schematic' + end) + end + + end + end + end; + + onClose = function(state, user) + user:suitSound 'starlit-quit' + end; + refresh = true; + handle = function(state, user, q) + local E = starlit.mod.electronics + if not state.ctx.verify() then state.close() end + local inv = user.entity:get_inventory() + + local sel = state.select + state.fetch() + local chips = state.select.chips + local function chirp() + user:suitSound 'starlit-nav' + end + + -- are we signalling a reprint? + for k in next, q do + local idx = k:match'^recent_(%d+)$' + if idx then + local jobs = getRecentJobs(state) + local scmid = jobs[tonumber(idx)] + if not scmid then return false end + -- get list of available schematics and make sure this is on it + local cst = inv:get_list 'starlit_suit_chips' + local all = E.chip.usableSoftware(cst, nil, function(s) + return s.sw.kind == 'schematic' + end) + local scm + for i, s in ipairs(all) do + if s.id == scmid then scm = s goto found end + end + + ::fail:: do + user:suitSound 'starlit-error' + return false + end + + ::found:: + local ok,cost = compilerCanPrint(user, state.pgm, scm) + if not ok then goto fail end + user:suitSound 'starlit-configure' + buyPrintJob(state, {{hnd=inv,list='main'}}, cost, scm) + state.ctx.saveConf() + return true + end + end + + local function trySelection(id) + if sel[id] == nil then + for k in next, q do + local pat = "^"..id.."_(%d+)$" -- ew + local idx = k:match(pat) + if idx then + local cm = tonumber(idx) + if cm then + chirp() + sel[id] = cm + return true + end + end + end + end + end + + if sel.chip == nil then + if q.showAll then + chirp() + sel.chip = true + return true + elseif q.find then + chirp() + -- TODO + return true + end + end + + if trySelection('chip') then + return true + elseif trySelection('scm') then + return true + else + if q.back then + chirp() + if sel.input then + sel.input = nil + elseif sel.scm then + sel.scm = nil + elseif sel.chip then + sel.chip = nil + end + return true + elseif q.commit then + if not sel.input then + chirp() + sel.input = true + return true + else + local scm = sel.scms[sel.scm] + local ok, cost = compilerCanPrint(user, state.pgm, scm) + if ok then + user:suitSound 'starlit-configure' + + buyPrintJob(state, {{hnd=inv,list='main'}}, cost, scm) + + -- add recent print + local rctList, pairIdx = getRecentJobs(state) + if #rctList >= 30 then + table.remove(rctList) + end + + if not lib.tbl.has(rctList, scm.id) then + table.insert(rctList,1,scm.id) + end + + local conf = state.pgm.file.body.conf + local encoded = recentJobs.enc(rctList) + if pairIdx then + conf[pairIdx].value = encoded + else + table.insert(conf, {key='recent', value=encoded}) + end + + -- write chip + state.ctx.saveConf() + -- clear selection + state.select = {} + return true + else + user:suitSound 'starlit-error' + end + end + end + end + + end; + + render = function(state, user) + local sel, pgmSelector = state.select, {} + state.fetch() + state.ctx.pullConf() + + local function pushSelector(id, item, label, desc, req) + local rh = .5 + local label = {kind = 'text', w = 10-1.5, h=1.5; + text = ''..lib.str.htsan(label) } + if req then + label.h = label.h - rh - .2 + + local imgs = {} + for ci,c in ipairs(req) do + for ei, e in ipairs(c.list) do + table.insert(imgs, {kind = 'img', w=rh, h=rh, img=e.img}) + end + end + label = {kind = 'vert', w = 10-1.5, h=1.5; + label; + {kind ='hztl', w=10-1.5, h=rh; unpack(imgs); } + } + end + table.insert(pgmSelector, {kind = 'hztl', w=10,h=1.5; + {kind = 'contact', id=id, w=1.5, h=1.5; + item = item; + color = {hue=220, sat=0, lum=0}; + desc = desc; + }; + label; + }) + end + + local back = {kind = 'button', id='back', label = '<- Back', w=10,h=1.2} + if sel.chips == nil then + table.insert(pgmSelector, {kind = 'img', img = 'starlit-ui-alert.png', w=2, h=2}) + elseif sel.chip == nil then + for i, c in ipairs(sel.chips.order) do + -- TODO filter out chips without schematics? + pushSelector('chip_' .. c.data.uuid, c.stack, c.data.label) + end + if next(sel.chips.order) then + table.insert(pgmSelector, {kind = 'hztl', w=10,h=1.5; + {kind = 'button', w=5,h=1.5; id='showAll', label='Show All'}; + {kind = 'button', w=5,h=1.5; id='find', label='Find'}; + }) + end + else + if sel.scm == nil then + for idx, ent in ipairs(sel.scms) do + local fab = ItemStack(ent.sw.output):get_definition()._starlit.fab + if fab.flag and fab.flag.print then + local req = fab:visualize() + pushSelector('scm_' .. idx, ent.sw.output, ent.sw.name, nil, req) + end + end + table.insert(pgmSelector, back) + else + local scm = sel.scms[sel.scm] + local output = ItemStack(scm.sw.output):get_definition() + local fab = scm.sw.input + local sw = scm.sw + local function unmet(str) + return lib.color(1,.3,.3):fmt(str) + end + table.insert(pgmSelector, {kind = 'hztl', w=10, h=1.2; + {kind = 'img', item = sw.output, w=1.2, h=1.2, desc=output.description}; + {kind = 'text', text = string.format('%s', lib.str.htsan(sw.name)), w=10-1.2,h=1.2}; + }) + local inputTbl = {kind = 'vert', w=5,h=0; + {kind = 'hbar', w=5, h=.5, text=sel.input and 'Input Plan' or 'Input'}}; + local costTbl = {kind = 'vert', w=5,h=0; + {kind = 'hbar', w=5, h=.5, text=sel.input and 'Process Plan' or 'Process'}}; + local reqPane = {kind = 'pane', id='reqPane', w=10, h=7; + {kind = 'hztl', w=10,h=0; inputTbl, costTbl} + } + local function pushCost(x, t, val) + table.insert(costTbl, {kind='label', w=4.5,h=.5,x=x; + text=string.format('%s: %s',t,val); + }) + end + local function pushComputeCosts(header, p) + if p then + table.insert(costTbl, {kind = 'label', w=5, h=.5, x=0; text=header}); + if p.cycles then + pushCost(.5, 'Compute', lib.math.siUI({'cycle','cycles'}, p.cycles, true)) + end + if p.power then + local str = lib.math.siUI('J', p.power) + if p.power > user:suitCharge() then str = unmet(str) end + pushCost(.5, 'Power', str) + end + if p.ram then + local str = string.format("%s / %s", + lib.math.siUI('B', p.ram), + lib.math.siUI('B', state.pgm.comp.ram)) + if p.ram > state.pgm.comp.ram then str = unmet(str) end + pushCost(.5, 'Memory', str) + end + end + end + + local function fabToUI(x, inputTbl, req) + for ci,c in ipairs(req) do + table.insert(inputTbl, {kind = 'label', w=5-x, h=.5, x=x; + text=lib.str.capitalize(c.header)}); + for ei,e in ipairs(c.list) do + table.insert(inputTbl, {kind = 'hztl', w=4.5-x, h=.5, x=x+.5; + {kind='img', w=.5,h=.5, img=e.img}; + {kind='label', w=3.3,h=.5,x=.2, text=lib.str.capitalize(e.label)}; + }); + end + end + end + + local commitHue, commitLabel = 120 + if not sel.input then + commitLabel = 'Plan' + fabToUI(0, inputTbl, fab:visualize()) + local function pushComputeCostsSw(header, p) + if p.sw.cost then + pushComputeCosts(header, { + cycles = p.sw.cost.cycles; + power = p.powerCost; + ram = p.sw.cost.ram; + }) + end + end + pushComputeCostsSw('Schematic', scm) + pushComputeCostsSw('Compiler', state.pgm) + else + commitLabel = 'Commit' + pushComputeCosts('Total', { + cycles = (scm.sw.cost and scm.sw.cost.cycles or 0) + + (state.pgm.sw.cost and state.pgm.sw.cost.cycles or 0); + power = (scm.powerCost or 0) + + (state.pgm.powerCost or 0) + + (fab.cost and fab.cost.power or 0); + ram = (scm.sw.cost and scm.sw.cost.ram or 0) + + (state.pgm.sw.cost and state.pgm.sw.cost.ram or 0); + }) + if fab.time and fab.time.print then + pushCost(0, 'Job Runtime', lib.math.timespec(fab.time.print + scm.speed)) + pushCost(.5, 'Print Time', lib.math.timespec(fab.time.print)) + pushCost(.5, 'CPU Time', lib.math.timespec(scm.speed + state.pgm.speed)) + end + local ok, compileCost = compilerCanPrint(user, state.pgm, scm) + fabToUI(0, inputTbl, compileCost.itemSpec:visualize()) + + if next(compileCost.unsat) then + local vis = compileCost.unsat:visualize() + for si, s in ipairs(vis) do + s.header = 'Missing ' .. s.header + for ei, e in ipairs(s.list) do + e.label = lib.color(1,.2,.2):fmt(e.label) + end + end + fabToUI(0, inputTbl, vis) + end + if not ok then commitHue = 0 end + end + table.insert(pgmSelector, reqPane) + table.insert(pgmSelector, {kind = 'hztl', w=10,h=1.2; + {kind = 'button', id='back', label = '← Back', w=5,h=1.2}; + {kind = 'button', id='commit', label = commitLabel .. ' →', w=5,h=1.2, color={hue=commitHue,sat=0,lum=0}}; + }) + end + end + + local rec = getRecentJobs(state) + local recentList = {kind = 'hwrap', cols=3} + if rec then + for i,id in ipairs(rec) do + local out = ItemStack(starlit.item.sw.db[id].output) + table.insert(recentList, {kind='contact',w=1.5,h=1.5,id=string.format('recent_%s',i), item = out}) + end + end + local queue = {kind='pane', w=5, h=9.5} + for i,v in ipairs(state.pgm.file.body.conf) do + if v.key == 'job' then + local job = starlit.store.compilerJob.dec(v.value) + local scm = starlit.item.sw.db[job.schematic] + local cycTotal = (scm.cost and scm.cost.cycles or 0) + local secTotal = (scm.input.time and scm.input.time.print or 0) + local cycPct = cycTotal == 0 and 1 or (1 - job.cyclesLeft/cycTotal) + local secPct = secTotal == 0 and 1 or (1 - job.timeLeft/secTotal) + local out = ItemStack(scm.output) + local cycBar = {kind = 'hbar', fac = cycPct, w=5-1.5, h = .5; + color={hue=150,sat=0,lum=0}; text=string.format("Compute %s%%", math.floor(cycPct*100)); + } + local secBar = {kind = 'hbar', fac = secPct, w=5-1.5, h = .5; + color={hue=50,sat=0,lum=0}; text=string.format("Assemble %s%%", math.floor(secPct*100)); + } + table.insert(queue, {kind='hztl'; w=5, h=1.5; + {kind = 'contact', w=1.5,h=1.5, id=string.format('cancel_%s', i), item = out}; + {kind = 'vert', w=5-1.5, h=1.5; + {kind = 'label', text=out:get_definition().short_description, w=5-1.5, h=.75}; + (cycPct < 1 and cycBar or secBar); + } + }) + end + end + + return starlit.ui.build { + kind = 'hztl', padding = 0.5; w = 20, h = 10, mode = 'sw'; + {kind = 'vert', w = 5, h = 5; + {kind = 'hbar', fac=0, w = 5, h = .5, text = 'Recent Prints'}; + recentList; + }; + {kind = 'vert', w = 10, h = 10; + {kind = 'hbar', fac=0, w = 10, h = .5, text = 'Program Select'}; + {kind = 'pane', w = 10, h = 9.5, id='pgmSelect'; + unpack(pgmSelector) + }; + }; + {kind = 'vert', w = 5, h = 10; + {kind = 'hbar', fac=0, w = 5, h = .5, text = 'Print Queue'}; + queue; + }; + } + end; + }; + }; +}) Index: mods/starlit/fab.lua ================================================================== --- mods/starlit/fab.lua +++ mods/starlit/fab.lua @@ -104,11 +104,11 @@ return string.format('starlit-element-%s.png', x) end; inventory = fRawMat 'element'; op = fQuant; }; - metal ={ + metal = { name = {"metal", "metals"}; string = function(x, n) local met = starlit.world.material.metal.db[x] return lib.math.siUI('g', n) .. ' ' .. met.name end; @@ -335,10 +335,11 @@ inv=ii, slot=oi; consume=deduct, remain=st; satisfy=fab{[cat]={[substance]=avail}} } table.insert(stacks, sv) + table.insert(consumed, sv) end if amtFound >= amt then goto suffice end end end end Index: mods/starlit/init.lua ================================================================== --- mods/starlit/init.lua +++ mods/starlit/init.lua @@ -56,10 +56,13 @@ interface = lib.registry.mk 'starlit:interface'; item = { food = lib.registry.mk 'starlit:food'; seed = lib.registry.mk 'starlit:seed'; }; + + -- complex algorithms that cut across namespaces or don't belong anywhere else + alg = {}; region = { radiator = { store = AreaStore(); emitters = {}; @@ -277,10 +280,11 @@ starlit.include 'element' starlit.include 'terrain' starlit.include 'interfaces' +starlit.include 'compile' starlit.include 'suit' -- minetest.settings:set('movement_gravity', starlit.world.planet.gravity) -- ??? seriously??? -- THIS OVERRIDES THE GLOBAL SETTING *AND PERSISTS IT* WHAT IN THE SATANIC FUCK @@ -323,11 +327,14 @@ local ui = starlit.interface.db[formid] local state = starlit.activeUI[name] or {} if formid == '__builtin:help_cmds' or formid == '__builtin:help_privs' then return false end - assert(state.form == formid) -- sanity check + if not (state.form == formid) then -- sanity check + log.warn('user %s attempted to send form %s while %s was active', name, formid, state.form) + return false + end user:onRespond(ui, state, fields) if fields.quit then starlit.activeUI[name] = nil end return true @@ -394,10 +401,11 @@ type = "none", wield_image = "wieldhand.png", wield_scale = {x=1,y=1,z=2.5}, tool_capabilities = { groupcaps = { + object = {maxlevel=1, times = {.20,.10}}; plant = {maxlevel=1, times = {.50}}; -- sand, dirt, gravel looseClump = {maxlevel=1, times = {1.5, 2.5}}; }; Index: mods/starlit/interfaces.lua ================================================================== --- mods/starlit/interfaces.lua +++ mods/starlit/interfaces.lua @@ -14,11 +14,11 @@ img='starlit-ui-icon-weapon.png', close=true, color = cmode'weapon'}; {kind = 'contact', w=1.5,h=1.5, id = 'mode_psi', img='starlit-ui-icon-psi.png', close=true, color = cmode'psi'}; }; {kind = 'hztl'; - {kind = 'contact', w=1.5,h=1.5, id = 'open_elements', + {kind = 'contact', w=1.5,h=1.5, id = 'open_nano', img='starlit-ui-icon-element.png'}; {kind = 'contact', w=1.5,h=1.5, id = 'open_suit', img='starlit-item-suit.png^[hsl:200:-.7:0'}; {kind = 'contact', w=1.5,h=1.5, id = 'open_psi', img='starlit-ui-icon-psi-cfg.png'}; @@ -51,12 +51,12 @@ end return true end end - if fields.open_elements then - user:openUI('starlit:user-menu', 'compiler') + if fields.open_nano then + user:openUI('starlit:user-menu', 'nano') return true elseif fields.open_psi then user:openUI('starlit:user-menu', 'psi') return true elseif fields.open_suit then @@ -120,11 +120,11 @@ local function abilityMenu(a) -- select primary/secondary abilities or activate ritual abilities local p = {kind = 'vert'} for _, o in ipairs(a.order) do local m = a.menu[o] - table.insert(p, {kind='hbar', fac=0.5, text=string.format("%s",m.label), w=a.w, h = .5}) + table.insert(p, {kind='hbar', fac=0, text=string.format("%s",m.label), w=a.w, h = .5}) table.insert(p, wrapMenu(a.w, a.h, 1.2, 2, m.opts)) end return p end @@ -135,11 +135,11 @@ end starlit.interface.install(starlit.type.ui { id = 'starlit:user-menu'; pages = { - compiler = { + nano = { setupState = function(state, user) -- nanotech/suit software menu local chips = user.entity:get_inventory():get_list 'starlit_suit_chips' -- FIXME need better subinv api local sw = starlit.mod.electronics.chip.usableSoftware(chips) state.suitSW = {} @@ -170,16 +170,32 @@ if not pgm then return false end -- HAX -- kind=active programs must be assigned to a command slot -- kind=direct programs must open their UI -- kind=passive programs must toggle on and off + local inv = user.entity:get_inventory() local function suitCtx(pgm) - local chips = user.entity:get_inventory():get_list 'starlit_suit_chips' - local pgmctx = starlit.mod.electronics.chip.usableSoftware(chips, {pgm})[1] + local uuid = starlit.mod.electronics.chip.read(pgm.chip).uuid return { context = 'suit'; - program = pgmctx; + program = pgm; + verify = function() -- ew!! + local chipInSlot = inv:get_stack('starlit_suit_chips', pgm.chipSlot) + local csd = starlit.mod.electronics.chip.read(chipInSlot) + return csd.uuid == uuid + end; + saveConf = function(cfg) cfg = cfg or pgm.file.body.conf + pgm.file.body.conf = cfg + pgm.fd:write(pgm.file) + inv:set_stack('starlit_suit_chips', pgm.chipSlot, pgm.fd.chip) + user:reconfigureSuit() + end; + pullConf = function() + local stack = inv:get_stack('starlit_suit_chips', pgm.chipSlot) + pgm.fd.chip=stack + pgm.file = pgm.fd:read() + end; } end if pgm.sw.powerKind == 'active' then if cfg then @@ -497,366 +513,9 @@ elseif q.powerMode_on then suitMode = 'on' end if suitMode then user:suitPowerStateSet(suitMode) return true end - end; - }; - }; -}) - -local function compilerCanPrint(user, cpl, scm) - local output = ItemStack(scm.sw.output):get_definition() - local fab = output._starlit.fab - local sw = scm.sw - local ok, consume, unsat, leftover, itemSpec = fab:seek { - user.entity:get_inventory():get_list 'main'; - } - - local cost = { - consume = consume, unsat = unsat, leftover = leftover, itemSpec = itemSpec; - runtimeEstimate = scm.speed + cpl.speed + (fab.time and fab.time.print or 0); - power = cpl.powerCost + scm.powerCost; - ram = (cpl.cost and cpl.cost.ram or 0) - + (scm.cost and scm.cost.ram or 0); - cycles = (cpl.cost and cpl.cost.cycles or 0) - + (scm.cost and scm.cost.cycles or 0); - } - - local userComp = starlit.mod.electronics.chip.sumCompute( - user.entity:get_inventory():get_list 'starlit_suit_chips' - ) - - if ok and cost.power <= user:suitCharge() and cost.ram <= userComp.ram then - return true, cost - else return false, cost end -end - --- TODO destroy suit interfaces when power runs out or suit/chip is otherwise disabled -starlit.interface.install(starlit.type.ui { - id = 'starlit:compile-matter-component'; - sub = { - suit = function(state, user, evt) - if evt.kind == 'disrobe' then state:close() - elseif evt.kind == 'power' and evt.mode == 'off' then state:close() end - end; - playerInventory = function(state,user) - -- refresh - end; - }; - pages = { - index = { - setupState = function(state, user, ctx) - state.pgm = ctx.program - state.select = {} - local E = starlit.mod.electronics - if ctx.context == 'suit' then - state.fetch = function() - local cst = user.entity:get_inventory():get_list 'starlit_suit_chips' - local cl = {order={}, map={}, slot={}} - for i, c in ipairs(cst) do - if not c:is_empty() then - local d = E.chip.read(c) - local co = { - stack = c; - data = d; - } - table.insert(cl.order, co) - cl.map[d.uuid] = co - cl.slot[i] = co - end - end - - -- kill me fam - if ( state.select.chip - and state.select.chip ~= true - and not cl.map[state.select.chip]) - or (state.select.scm - and not state.select.scms[state.select.scm]) - then - -- chip or pgm no longer available - user:suitSound 'starlit-error' - state.select = {} - end - state.select.chips = cl - - state.select.scms = {} - if state.select.chip then - state.select.scms = E.chip.usableSoftware(cst,nil, function(s) - if state.select.chip ~= true then - if cl.slot[s.chipSlot].data.uuid ~= state.select.chip then - return false - end - end - return s.sw.kind == 'schematic' - end) - end - - end - end - end; - - onClose = function(state, user) - user:suitSound 'starlit-quit' - end; - handle = function(state, user, q) - local sel = state.select - state.fetch() - local chips = state.select.chips - local function chirp() - user:suitSound 'starlit-nav' - end - - local function trySelection(id) - if sel[id] == nil then - for k in next, q do - local pat = "^"..id.."_(%d+)$" -- ew - local idx = k:match(pat) - if idx then - local cm = tonumber(idx) - if cm then - chirp() - sel[id] = cm - return true - end - end - end - end - end - - if sel.chip == nil then - if q.showAll then - chirp() - sel.chip = true - return true - elseif q.find then - chirp() - -- TODO - return true - end - end - - if trySelection('chip') then - return true - elseif trySelection('scm') then - return true - else - if q.back then - chirp() - if sel.input then - sel.input = nil - elseif sel.scm then - sel.scm = nil - elseif sel.chip then - sel.chip = nil - end - return true - elseif q.commit then - if not sel.input then - chirp() - sel.input = true - return true - else - local scm = sel.scms[sel.scm] - local ok, cost = compilerCanPrint(user, state.pgm, scm) - if ok then - user:suitSound 'starlit-configure' - -- consume consumables - -- add print job - state.select = {} - return true - else - user:suitSound 'starlit-error' - end - end - end - end - - end; - - render = function(state, user) - local sel, pgmSelector = state.select, {} - state.fetch() - - local function pushSelector(id, item, label, desc, req) - local rh = .5 - local label = {kind = 'text', w = 10-1.5, h=1.5; - text = ''..lib.str.htsan(label) } - if req then - label.h = label.h - rh - .2 - - local imgs = {} - for ci,c in ipairs(req) do - for ei, e in ipairs(c.list) do - table.insert(imgs, {kind = 'img', w=rh, h=rh, img=e.img}) - end - end - label = {kind = 'vert', w = 10-1.5, h=1.5; - label; - {kind ='hztl', w=10-1.5, h=rh; unpack(imgs); } - } - end - table.insert(pgmSelector, {kind = 'hztl', w=10,h=1.5; - {kind = 'contact', id=id, w=1.5, h=1.5; - item = item; - color = {hue=220, sat=0, lum=0}; - desc = desc; - }; - label; - }) - end - - local back = {kind = 'button', id='back', label = '<- Back', w=10,h=1.2} - if sel.chips == nil then - table.insert(pgmSelector, {kind = 'img', img = 'starlit-ui-alert.png', w=2, h=2}) - elseif sel.chip == nil then - for i, c in ipairs(sel.chips.order) do - -- TODO filter out chips without schematics? - pushSelector('chip_' .. c.data.uuid, c.stack, c.data.label) - end - if next(sel.chips.order) then - table.insert(pgmSelector, {kind = 'hztl', w=10,h=1.5; - {kind = 'button', w=5,h=1.5; id='showAll', label='Show All'}; - {kind = 'button', w=5,h=1.5; id='find', label='Find'}; - }) - end - else - if sel.scm == nil then - for idx, ent in ipairs(sel.scms) do - local fab = ItemStack(ent.sw.output):get_definition()._starlit.fab - if fab.flag.print then - local req = fab:visualize() - pushSelector('scm_' .. idx, ent.sw.output, ent.sw.name, nil, req) - end - end - table.insert(pgmSelector, back) - else - local scm = sel.scms[sel.scm] - local output = ItemStack(scm.sw.output):get_definition() - local fab = output._starlit.fab - local sw = scm.sw - local function unmet(str) - return lib.color(1,.3,.3):fmt(str) - end - table.insert(pgmSelector, {kind = 'hztl', w=10, h=1.2; - {kind = 'img', item = sw.output, w=1.2, h=1.2, desc=output.description}; - {kind = 'text', text = string.format('%s', lib.str.htsan(sw.name)), w=10-1.2,h=1.2}; - }) - local inputTbl = {kind = 'vert', w=5,h=0; - {kind = 'hbar', w=5, h=.5, text=sel.input and 'Input Plan' or 'Input'}}; - local costTbl = {kind = 'vert', w=5,h=0; - {kind = 'hbar', w=5, h=.5, text=sel.input and 'Process Plan' or 'Process'}}; - local reqPane = {kind = 'pane', id='reqPane', w=10, h=7; - {kind = 'hztl', w=10,h=0; inputTbl, costTbl} - } - local function pushCost(x, t, val) - table.insert(costTbl, {kind='label', w=4.5,h=.5,x=x; - text=string.format('%s: %s',t,val); - }) - end - local function pushComputeCosts(header, p) - if p then - table.insert(costTbl, {kind = 'label', w=5, h=.5, x=0; text=header}); - if p.cycles then - pushCost(.5, 'Compute', lib.math.siUI({'cycle','cycles'}, p.cycles, true)) - end - if p.power then - local str = lib.math.siUI('J', p.power) - if p.power > user:suitCharge() then str = unmet(str) end - pushCost(.5, 'Power', str) - end - if p.ram then - local str = string.format("%s / %s", - lib.math.siUI('B', p.ram), - lib.math.siUI('B', state.pgm.comp.ram)) - if p.ram > state.pgm.comp.ram then str = unmet(str) end - pushCost(.5, 'Memory', str) - end - end - end - - local function fabToUI(x, inputTbl, req) - for ci,c in ipairs(req) do - table.insert(inputTbl, {kind = 'label', w=5-x, h=.5, x=x; - text=lib.str.capitalize(c.header)}); - for ei,e in ipairs(c.list) do - table.insert(inputTbl, {kind = 'hztl', w=4.5-x, h=.5, x=x+.5; - {kind='img', w=.5,h=.5, img=e.img}; - {kind='label', w=3.3,h=.5,x=.2, text=lib.str.capitalize(e.label)}; - }); - end - end - end - - local commitHue=120, commitLabel - if not sel.input then - commitLabel = 'Plan' - fabToUI(0, inputTbl, fab:visualize()) - local function pushComputeCostsSw(header, p) - if p.sw.cost then - pushComputeCosts(header, { - cycles = p.sw.cost.cycles; - power = p.powerCost; - ram = p.sw.cost.ram; - }) - end - end - pushComputeCostsSw('Schematic', scm) - pushComputeCostsSw('Compiler', state.pgm) - else - commitLabel = 'Commit' - pushComputeCosts('Total', { - cycles = (scm.sw.cost and scm.sw.cost.cycles or 0) - + (state.pgm.sw.cost and state.pgm.sw.cost.cycles or 0); - power = (scm.powerCost or 0) - + (state.pgm.powerCost or 0) - + (fab.cost and fab.cost.power or 0); - ram = (scm.sw.cost and scm.sw.cost.ram or 0) - + (state.pgm.sw.cost and state.pgm.sw.cost.ram or 0); - }) - if fab.time and fab.time.print then - pushCost(0, 'Job Runtime', lib.math.timespec(fab.time.print + scm.speed)) - pushCost(.5, 'Print Time', lib.math.timespec(fab.time.print)) - pushCost(.5, 'CPU Time', lib.math.timespec(scm.speed + state.pgm.speed)) - end - local ok, compileCost = compilerCanPrint(user, state.pgm, scm) - fabToUI(0, inputTbl, compileCost.itemSpec:visualize()) - - if next(compileCost.unsat) then - local vis = compileCost.unsat:visualize() - for si, s in ipairs(vis) do - s.header = 'Missing ' .. s.header - for ei, e in ipairs(s.list) do - e.label = lib.color(1,.2,.2):fmt(e.label) - end - end - fabToUI(0, inputTbl, vis) - end - if not ok then commitHue = 0 end - end - table.insert(pgmSelector, reqPane) - table.insert(pgmSelector, {kind = 'hztl', w=10,h=1.2; - {kind = 'button', id='back', label = '← Back', w=5,h=1.2}; - {kind = 'button', id='commit', label = commitLabel .. ' →', w=5,h=1.2, color={hue=commitHue,sat=0,lum=0}}; - }) - end - end - - return starlit.ui.build { - kind = 'hztl', padding = 0.5; w = 20, h = 10, mode = 'sw'; - {kind = 'vert', w = 5, h = 5; - {kind = 'hbar', fac=0, w = 5, h = .5, text = 'Recent Prints'}; - }; - {kind = 'vert', w = 10, h = 10; - {kind = 'hbar', fac=0, w = 10, h = .5, text = 'Program Select'}; - {kind = 'pane', w = 10, h = 9.5, id='pgmSelect'; - unpack(pgmSelector) - }; - }; - {kind = 'vert', w = 5, h = 10; - {kind = 'hbar', fac=0, w = 5, h = .5, text = 'Print Queue'}; - }; - } end; }; }; }) Index: mods/starlit/store.lua ================================================================== --- mods/starlit/store.lua +++ mods/starlit/store.lua @@ -22,12 +22,13 @@ (v.base == true or v.base > 0) and T.s16 or T.u16 ) end starlit.store.compilerJob = G.struct { - schematic = T.str; - progress = T.clamp; + schematic = T.str; + cyclesLeft = T.u64; + timeLeft = T.decimal; } starlit.store.persona = G.struct { name = T.str; species = T.str; Index: mods/starlit/suit.lua ================================================================== --- mods/starlit/suit.lua +++ mods/starlit/suit.lua @@ -429,15 +429,20 @@ end local fn if s.powerKind == 'passive' then fn = s.run else fn = s.bgProc end - function prop.saveConf(cfg) cfg = cfg or conf + function prop.saveConf(cfg) cfg = cfg or prop.file prop.fd:write(cfg) inv:set_stack('starlit_suit_chips', prop.chipSlot, prop.fd.chip) reconfSuit = true end + function prop.pullConf() + local stack = inv:get_stack('starlit_suit_chips', suitprog.chipSlot) + prop.fd.chip=stack + prop.file = prop.fd:read() + end function prop.giveItem(st) u:thrustUpon(st) end if enabled and fn(u, prop, suitInterval, runState) then Index: mods/starlit/terrain.lua ================================================================== --- mods/starlit/terrain.lua +++ mods/starlit/terrain.lua @@ -242,11 +242,10 @@ recover = starlit.type.fab { time = { shred = 3; }; cost = { shredPower = 3; }; }; recover_vary = function(rng, ctx) - -- print('vary!', rng:int(), rng:int(0,10)) return starlit.type.fab { element = { aluminum = rng:int(0,4); potassium = rng:int(0,2); calcium = rng:int(0,2); Index: mods/starlit/ui.lua ================================================================== --- mods/starlit/ui.lua +++ mods/starlit/ui.lua @@ -152,10 +152,33 @@ state.x=state.x + state.spacing + st.w state.h = math.max(state.h, st.h) end state.h = state.h + state.padding state.w = state.x + state.padding/2 + elseif def.kind == 'hwrap' then + local idx, rowH, totalH = 0,0,0 + local cols = def.cols or math.huge + local startX = state.x + local maxX = startX + for _, w in ipairs(def) do idx = idx + 1 + local src, st = starlit.ui.build(w, state) + -- TODO alignments + rowH = math.max(rowH, st.h) + if idx > cols or def.w and (state.x + state.spacing + st.w > def.w) then + totalH = totalH + rowH + state.x = startX + rowH,idx = 0,0 + state.y = state.y + state.spacing + st.h + end + widget('container[%s,%s]%scontainer_end[]', state.x, state.y, src) + state.x=state.x + state.spacing + st.w + maxX = math.max(state.x, maxX) + end + totalH = totalH + rowH + state.h = math.max(state.h, totalH) + state.padding + state.w = state.x + state.padding/2 + state.x = maxX elseif def.kind == 'pane' then widget('scroll_container[%s,%s;%s,%s;%s;vertical]', state.x, state.y, state.w, state.h, def.id) local y = 0 Index: mods/starlit/user.lua ================================================================== --- mods/starlit/user.lua +++ mods/starlit/user.lua @@ -1012,12 +1012,14 @@ if time - ul.origin > minFreq then ul.origin = time else return end end - if urgency > 0 then + if urgency ~= 0 then local urgencies = { + [-2] = {sound = 'starlit-success'}; + [-1] = {sound = 'starlit-nav'}; [1] = {sound = 'starlit-alarm'}; [2] = {sound = 'starlit-alarm-urgent'}; } local urg = urgencies[urgency] or urgencies[#urgencies] @@ -1087,10 +1089,11 @@ --------------- -- inventory -- --------------- give = function(self, item) + item = ItemStack(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 @@ -1132,10 +1135,14 @@ local clockInterval = 1.0 starlit.startJob('starlit:clock', clockInterval, function(delta) for id, u in pairs(starlit.activeUsers) do u.hud.elt.time:update() u:updateLEDs() + local ui = starlit.activeUI[u.name] + if ui and (ui.self.refresh or ui.self.pages[ui.page].refresh) then + ui.self:show(u) + end end end) -- performs a general HUD refresh, mainly to update the HUD backlight brightness local hudInterval = 10 Index: mods/starlit/world.lua ================================================================== --- mods/starlit/world.lua +++ mods/starlit/world.lua @@ -117,19 +117,20 @@ drop = st.drop; _starlit = { plant = { id = id, stage = n; }; - recover = starlit.type.fab { + recover = b.recover or starlit.type.fab { time = { shred = .3; }; cost = { shredPower = 1; }; }; - recover_vary = function(rng, ctx) + recover_vary = b.recover_vary or function(rng, ctx) return starlit.type.fab { element = { - carbon = rng:int(0,1); + carbon = rng:int(0,2); potassium = rng:int(0,1); + magnesium = rng:int(0,b.biolum and 2 or 1); } }; end; }; } Index: mods/vtlib/math.lua ================================================================== --- mods/vtlib/math.lua +++ mods/vtlib/math.lua @@ -160,13 +160,14 @@ if n < 0 then return '-' .. fn.timespec(n*-1) end local sec = math.floor(n % 60) local min = math.floor(n / 60) local hr = math.floor(min / 60) + min = min % 60 local spec = {} if hr ~= 0 then table.insert(spec, string.format("%shr", hr)) end if min ~= 0 then table.insert(spec, string.format("%sm", min)) end if sec ~= 0 then table.insert(spec, string.format("%ss", sec)) end return table.concat(spec, ' ') end return fn