Comment: | rename again |
---|---|
Downloads: | Tarball | ZIP archive | SQL archive |
Timelines: | family | ancestors | descendants | both | trunk |
Files: | files | file ages | folders |
SHA3-256: |
187bf04c875d238d14e4ccedc13983bf |
User & Date: | lexi on 2024-04-29 22:49:02 |
Other Links: | manifest | tags |
2024-05-01
| ||
13:46 | cleanups, fixes, begin canister rework, begin ecology check-in: a810a756ce user: lexi tags: trunk | |
2024-04-29
| ||
22:49 | rename again check-in: 187bf04c87 user: lexi tags: trunk | |
2024-03-29
| ||
22:43 | fix batteries, silence debug noise check-in: 6469428393 user: lexi tags: trunk | |
Modified dev.ct from [3b1604bc1d] to [912982b94d].
1 -# starsoul development 1 +# starlit development 2 2 this file contains information meant for those who wish to develop for Starsoul or build the game from trunk. do NOT add any story information, particularly spoilers; those go in src/lore.ct. 3 3 4 4 ## tooling 5 -starsoul uses the following software in the development process: 5 +starlit uses the following software in the development process: 6 6 * [*csound] to generate sound effects 7 7 * [*GNU make] to automate build tasks 8 8 * [*lua] to automate configure tasks 9 9 10 10 ## building 11 11 to run a trunk version of Starsoul, you'll need to install the above tools and run `make` from the base directory. this will: 12 12 * run lua scripts to generate necessary makefiles 13 -* generate the game sound effects and install them in mods/starsoul/sounds 13 +* generate the game sound effects and install them in mods/starlit/sounds 14 14 15 15 ## policy 16 16 * copyright of all submitted code must be reassigned to the maintainer. 17 17 * all code is to be indented with tabs and aligned with spaces; formatting is otherwise up to whoever is responsible for maintaining that code 18 18 * use [`camelCase], not [`snake_case] and CERTAINLY not [`SCREAMING_SNAKE_CASE] 19 19 * sounds effects should be contributed in the form of csound files; avoid adding audio files to the repository except for foley effects
Modified game.conf from [d6b8d1d1d1] to [b1638bce61].
1 -title = Starsoul 1 +title = Starlit 2 2 author = velartrill 3 3 description = High-tech survival on a hostile alien world 4 4 allowed_mapgens = v7 5 5 disabled_settings = !enable_damage, creative_mode
Added mods/starlit-building/init.lua version [7b434e8bee].
1 +local lib = starlit.mod.lib 2 +local B = {} 3 +starlit.mod.building = B 4 + 5 +B.path = {} 6 +-- this maps stage IDs to tables of the following form 7 +--[[ { 8 + part = { 9 + ['starlit_building:pipe'] = 'myMod:stage3'; 10 + }; 11 + tool = { 12 + ['starlit:scredriver'] = 'myMod:otherThing_stage1'; 13 + ['starlit:saw'] = function(node, tool) 14 + minetest.replace_node(node, {name='myMod:stage1'}) 15 + minetest.drop_item(node, 'starlit_building:pipe') 16 + end; 17 + ['myMod:laserWrench'] = { 18 + allow = function(node, tool) ... end; 19 + handle = function(node, tool) ... end; 20 + }; 21 + }; 22 +} ]] 23 +-- it should only be written by special accessor functions! 24 + 25 +B.stage = lib.registry.mk 'starlit_building:stage' 26 +-- a stage consists of a list of pieces and maps from possible materials 27 +-- / tool usages to succeeding stages in the build tree. note that all 28 +-- used pieces must be defined before a stage is defined currently, due 29 +-- to a lack of cross-registry dependency mechanisms. i will hopefully 30 +-- improve vtlib to handle this condition eventually. 31 +--[[ 32 + starlit.mod.building.stage.link(id, { 33 + pieces = { 34 + 'starlit_building:foundation'; 35 + 'starlit_building:insulation'; -- offset ofsFac 36 + {'starlit_building:pipe', vector.new(-.5, 0, 0), 0};-- 37 + {'starlit_building:pipe', vector.new(-.5, 0, 0), 1}; 38 + 'starlit_building:insulation'; 39 + 'starlit_building:panel'; 40 + }; 41 + }) 42 +]] 43 + 44 +B.piece = lib.registry.mk 'starlit_building:piece' 45 +-- a piece is used to produce stage definitions, by means of appending 46 +-- nodeboxes with appropriate offsets. it also lists the recoverable 47 +-- materials which can be obtained by destroying a stage containing 48 +-- this piece using nano. part IDs should correspond with piece IDs 49 +-- where possible 50 +--[[ 51 + starlit.mod.building.piece.link(id, { 52 + tex = 'myMod_part.png'; 53 + height = 0.1; -- used for auto-offset 54 + fab = { 55 + element = {iron=10}; 56 + }; 57 + shape = { 58 + type = "fixed"; 59 + fixed = { ... }; 60 + }; 61 + }) 62 +]] 63 + 64 +B.part = lib.registry.mk 'starlit_building:part' 65 +-- a part is implemented as a special craftitem with the proper callbacks 66 +-- to index the registries and place/replace noes by reference to the 67 +-- build tree. 68 +--[[ 69 + starlit.mod.building.part.link(id, { 70 + name = ''; -- display name 71 + desc = ''; -- display desc 72 + img = ''; -- display image 73 + }) 74 +]] 75 + 76 +B.stage.foreach('starlit:stageGen', {}, function(id, e) 77 + local box = {type = 'fixed', fixed = {}} 78 + local tex = {} 79 + local ofs = vector.new(0,0,0) 80 + for idx, p in ipairs(e.pieces) do 81 + local ho, pieceID, pos 82 + if type(p) == 'string' then 83 + pieceID, pos, ho = p, vector.zero(), 1.0 84 + else 85 + pieceID, pos, ho = pc[1],pc[2],pc[3] 86 + end 87 + local pc = B.piece.db[pieceID] 88 + pos = pos + ofs 89 + if ho ~= 0.0 then 90 + ofs = vector.offset(ofs, 0, pc.height) 91 + end 92 + local sh = lib.node.boxwarped(pc.shape, function(b) 93 + -- { -x, -y, -z; 94 + -- +x, +y, +z } 95 + b[1] = b[1] + ofs.x b[4] = b[4] + ofs.x 96 + b[2] = b[2] + ofs.y b[5] = b[5] + ofs.y 97 + b[3] = b[3] + ofs.z b[6] = b[6] + ofs.z 98 + end) 99 + table.insert(box, sh) 100 + if type(pc.tex) == 'string' then 101 + table.insert(tex, pc.tex) 102 + else 103 + for i,t in ipairs(pc.tex) do 104 + table.insert(tex, t or '') 105 + end 106 + end 107 + end 108 + minetest.register_node(id, { 109 + description = 'Construction'; 110 + drawtype = 'nodebox'; 111 + paramtype = 'light'; 112 + paramtype2 = e.stateful or 'none'; 113 + textures = tex; 114 + node_box = box; 115 + group = { stage = 1 }; 116 + _starlit = { 117 + stage = id; 118 + }; 119 + }) 120 +end) 121 + 122 +function B.pathLink(from, kind, what, to) 123 + if not B.path[from] then 124 + B.path[from] = {part={}, tool={}} 125 + end 126 + local k = B.path[from][kind] 127 + assert(k[what] == nil) 128 + k[what] = to 129 +end 130 + 131 +function B.pathFind(from, kind, what) 132 + if not B.path[from] then return nil end 133 + return B.path[from][kind][what] 134 +end 135 +
Added mods/starlit-building/mod.conf version [dd26a16b6d].
1 +name = starlit_building 2 +title = starlit building 3 +depends = starlit_electronics, starlit 4 +description = implements construction elements
Added mods/starlit-electronics/init.lua version [67a7c4791a].
1 +local lib = starlit.mod.lib 2 + 3 +local E = {} 4 +starlit.mod.electronics = E 5 + 6 +--------------------- 7 +-- item registries -- 8 +--------------------- 9 + 10 +-- a dynamo is any item that produces power and can be slotted into a power 11 +-- source slot. this includes batteries, but also things like radiothermal 12 +-- dynamos. 13 +starlit.item.dynamo = lib.registry.mk 'starlit_electronics:dynamo' 14 + 15 +-- batteries hold a charge of power (measured in kJ). how much they can hold 16 +-- (and how much power they can discharge?) depends on their quality 17 +starlit.item.battery = lib.registry.mk 'starlit_electronics:battery' 18 + 19 +-- a battery has the properties: 20 +-- class 21 +-- |- capacity (J ): amount of energy the battery can hold 22 +-- |- dischargeRate (W ): rate at which battery can supply power/be charged 23 +-- |- decay (J/J): rate at which the battery capacity degrades while 24 +-- discharging. decay=0 batteries require no maintenance; 25 +-- decay=1 batteries are effectively disposable 26 +-- |- leak (fac): charging inefficiency. depends on the energy storage 27 +-- technology. when N J are drawn from a power source, 28 +-- only (N*leak) J actually make it into the battery. 29 +-- leak=0 is a supercapacitor, leak=1 is /dev/null 30 +-- |- size (m): each suit has a limit to how big of a battery it can take 31 +-- instance 32 +-- |- degrade (mJ): how much the battery has degraded. instance max charge is 33 +-- | determined by $capacity - @degrade 34 +-- |- %wear ÷ 2¹⁶ : used as a factor to determine battery charge 35 + 36 +-- chips are standardized data storage hardware that can contain a certain amount 37 +-- of software. in addition to their flash storage, they also provide a given amount 38 +-- of working memory and processor power. processor power speeds up operations like 39 +-- crafting, while programs require a certain amount of memory. 40 +-- chips have a variable number of program slots and a single bootloader slot 41 +-- 42 +starlit.item.chip = lib.registry.mk 'starlit_electronics:chip' 43 + 44 +-- software is of one of the following types: 45 +-- schematic: program for your matter compiler that enables crafting a given item. 46 +-- output: the result 47 +-- driver: inserted into a Core to control attached hardware 48 +-- suitPower: provides suit functionality like nanoshredding or healing 49 +-- passive powers are iterated on suit application/configuration and upon fst-tick 50 +-- cost: what the software needs to run. some fields are fab-specific 51 +-- energy: for fab, total energy cost of process in joules 52 +-- for suitPassive, added suit power consumption in watts 53 +starlit.item.sw = lib.registry.mk 'starlit_electronics:sw' 54 +-- chip = lib.color(0, 0, .3); 55 + 56 +E.schematicGroups = lib.registry.mk 'starlit_electronics:schematicGroups' 57 +E.schematicGroupMembers = {} 58 +E.schematicGroups.foreach('starlit_electronics:ensure-memlist', {}, function(id,g) 59 + E.schematicGroupMembers[id] = {} 60 +end) 61 +function E.schematicGroupLink(group, item) 62 + table.insert(E.schematicGroupMembers[group], item) 63 +end 64 + 65 +E.schematicGroups.link('starlit_electronics:chip', { 66 + title = 'Chip', icon = 'starlit-item-chip.png'; 67 + description = 'Standardized data storage and compute modules'; 68 +}) 69 + 70 +E.schematicGroups.link('starlit_electronics:battery', { 71 + title = 'Battery', icon = 'starlit-item-battery.png'; 72 + description = 'Portable power storage cells are essential to all aspects of survival'; 73 +}) 74 + 75 +E.schematicGroups.link('starlit_electronics:decayCell', { 76 + title = 'Decay Cell', icon = 'starlit-item-decaycell.png'; 77 + description = "Radioisotope generators can pack much more power into a smaller amount of space than conventional batteries, but they can't be recharged, dump power and heat whether they're in use or not, and their power yield drops towards zero over their usable lifetime."; 78 +}) 79 + 80 + 81 +------------------------- 82 +-- batteries & dynamos -- 83 +------------------------- 84 + 85 +E.battery = {} 86 +local function accessor(ty, fn) 87 + return function(stack, ...) 88 + local function fail() 89 + error(string.format('object %q is not a %s', stack:get_name(), ty)) 90 + end 91 + 92 + if not stack or stack:is_empty() then fail() end 93 + 94 + if minetest.get_item_group(stack:get_name(), ty) == 0 then fail() end 95 + 96 + return fn(stack, 97 + stack:get_definition()._starlit[ty], 98 + stack:get_meta(), ...) 99 + end 100 +end 101 + 102 +-- return a wear level that won't destroy the item 103 +-- local function safeWear(fac) return math.min(math.max(fac,0),1) * 0xFFFE end 104 +-- local function safeWearToFac(w) return w/0xFFFE end 105 + 106 +E.battery.update = accessor('battery', function(stack, batClass, meta) 107 + -- local cap = E.battery.capacity(stack) 108 + local charge = meta:get_float 'starlit_electronics:battery_charge' 109 + meta:set_string('count_meta', string.format('%s%%', math.floor(charge * 100))) 110 + meta:set_int('count_alignment', 14) 111 +end) 112 + 113 +-- E.battery.capacity(bat) --> charge (J) 114 +E.battery.capacity = accessor('battery', function(stack, batClass, meta) 115 + local dmg = meta:get_int 'starlit_electronics:battery_degrade' -- µJ/μW 116 + local dmg_J = dmg / 1000 117 + return (batClass.capacity - dmg_J) 118 +end) 119 + 120 +-- E.battery.charge(bat) --> charge (J) 121 +E.battery.charge = accessor('battery', function(stack, batClass, meta) 122 + local fac = meta:get_float 'starlit_electronics:battery_charge' 123 + -- local fac = 1 - safeWearToFac(stack:get_wear()) 124 + return E.battery.capacity(stack) * fac 125 +end) 126 + 127 +-- E.battery.dischargeRate(bat) --> dischargeRate (W) 128 +E.battery.dischargeRate = accessor('battery', function(stack, batClass, meta) 129 + local dmg = meta:get_int 'starlit_electronics:battery_degrade' -- µJ/μW 130 + local dmg_W = dmg / 1000 131 + return batClass.dischargeRate - dmg_W 132 +end); 133 + 134 + 135 +-- E.battery.drawCurrent(bat, power, time, test) --> supply (J), wasteHeat (J) 136 +-- bat = battery stack 137 +-- power J = joules of energy user wishes to consume 138 +-- time s = the amount of time available for this transaction 139 +-- supply J = how much power was actually provided in $time seconds 140 +-- wasteHeat J = how heat is generated in the process 141 +-- test = if true, the battery is not actually modified 142 +E.battery.drawCurrent = accessor('battery', function(s, bc, m, power, time, test) 143 + local ch = E.battery.charge(s) 144 + local maxPower = math.min(E.battery.dischargeRate(s)*time, power, ch) 145 + ch = ch - maxPower 146 + 147 + if not test then 148 + local degrade = m:get_int 'starlit_electronics:battery_degrade' or 0 149 + degrade = degrade + maxPower * bc.decay 150 + -- for each joule of power drawn, capacity degrades by `decay` J 151 + -- this should ordinarily be on the order of mJ or smaller 152 + m:set_int('starlit_electronics:battery_degrade', degrade) 153 + -- s:set_wear(safeWear(1 - (ch / E.battery.capacity(s)))) 154 + m:set_float('starlit_electronics:battery_charge', ch / E.battery.capacity(s)) 155 + E.battery.update(s) 156 + end 157 + 158 + return maxPower, 0 -- FIXME specify waste heat 159 +end) 160 + 161 +-- E.battery.recharge(bat, power, time) --> draw (J) 162 +-- bat = battery stack 163 +-- power J = joules of energy user wishes to charge the battery with 164 +-- time s = the amount of time available for this transaction 165 +-- draw J = how much power was actually drawn in $time seconds 166 +E.battery.recharge = accessor('battery', function(s, bc, m, power, time) 167 + local ch = E.battery.charge(s) 168 + local cap = E.battery.capacity(s) 169 + local maxPower = math.min(E.battery.dischargeRate(s)*time, power) 170 + local total = math.min(ch + maxPower, cap) 171 + -- s:set_wear(safeWear(1 - (total/cap))) 172 + m:set_float('starlit_electronics:battery_charge', total/cap) 173 + E.battery.update(s) 174 + return maxPower, 0 -- FIXME 175 +end) 176 + 177 +E.battery.setCharge = accessor('battery', function(s, bc, m, newPower) 178 + local cap = E.battery.capacity(s) 179 + local power = math.min(cap, newPower) 180 + -- s:set_wear(safeWear(1 - (power/cap))) 181 + m:set_float('starlit_electronics:battery_charge', power/cap) 182 + E.battery.update(s) 183 +end) 184 +E.battery.setChargeF = accessor('battery', function(s, bc, m, newPowerF) 185 + local power = math.min(1.0, newPowerF) 186 + m:set_float('starlit_electronics:battery_charge', power) 187 + E.battery.update(s) 188 +end) 189 + 190 +E.dynamo = { kind = {} } 191 + 192 +E.dynamo.drawCurrent = accessor('dynamo', function(s,c,m, power, time, test) 193 + return c.vtable.drawCurrent(s, power, time, test) 194 +end) 195 +E.dynamo.totalPower = accessor('dynamo', function(s,c,m) return c.vtable.totalPower(s) end) 196 +E.dynamo.dischargeRate = accessor('dynamo', function(s,c,m) return c.vtable.dischargeRate (s) end) 197 +E.dynamo.initialPower = accessor('dynamo', function(s,c,m) return c.vtable.initialPower(s) end) 198 +E.dynamo.wasteHeat = accessor('dynamo', function(s,c,m) return c.vtable.wasteHeat(s) end) 199 +-- baseline waste heat, produced whether or not power is being drawn. for batteries this is 0, but for 200 +-- radiothermal generators it may be high 201 + 202 +E.dynamo.kind.battery = { 203 + drawCurrent = E.battery.drawCurrent; 204 + totalPower = E.battery.charge; 205 + initialPower = E.battery.capacity; 206 + dischargeRate = E.battery.dischargeRate; 207 + wasteHeat = function() return 0 end; 208 +}; 209 + 210 +starlit.item.battery.foreach('starlit_electronics:battery-gen', {}, function(id, def) 211 + minetest.register_tool(id, { 212 + short_description = def.name; 213 + groups = { battery = 1; dynamo = 1; electronic = 1; }; 214 + inventory_image = def.img or 'starlit-item-battery.png'; 215 + description = starlit.ui.tooltip { 216 + title = def.name; 217 + desc = def.desc; 218 + color = lib.color(0,.2,1); 219 + props = { 220 + { title = 'Optimal Capacity', affinity = 'info'; 221 + desc = lib.math.si('J', def.capacity) }; 222 + { title = 'Discharge Rate', affinity = 'info'; 223 + desc = lib.math.si('W', def.dischargeRate) }; 224 + { title = 'Charge Efficiency', affinity = 'info'; 225 + desc = string.format('%s%%', (1-def.leak) * 100) }; 226 + { title = 'Size', affinity = 'info'; 227 + desc = lib.math.si('m', def.fab.size.print) }; 228 + }; 229 + }; 230 + _starlit = { 231 + event = { 232 + create = function(st, how) 233 + --[[if not how.gift then -- cheap hack to make starting batteries fully charged 234 + E.battery.setCharge(st, 0) 235 + end]] 236 + E.battery.update(st) 237 + end; 238 + }; 239 + fab = def.fab; 240 + dynamo = { 241 + vtable = E.dynamo.kind.battery; 242 + }; 243 + battery = def; 244 + }; 245 + }) 246 +end) 247 + 248 + 249 +-- to use the power functions, consider the following situation. you have 250 +-- a high-tier battery charger that can draw 100kW. (for simplicity, assume 251 +-- it supports only one battery). if you install a low-tier battery, and 252 +-- the charging callback is called every five seconds, you might use 253 +-- a `recharge` call that looks like 254 +-- 255 +-- starlit.mod.electronics.battery.recharge(bat, 5 * 100*1e4, 5) 256 +-- 257 +-- this would offer the battery 500kJ over five seconds. the battery will 258 +-- determine how much power it can actually make use of in 5 five seconds, 259 +-- and then return that amount. 260 +-- 261 +-- always remember to save the battery back to its inventory slot after 262 +-- modifying its ItemStack with one of these functions! 263 + 264 + 265 +-- battery types 266 +-- supercapacitor: low capacity, no degrade, high dischargeRate, no leak 267 +-- chemical: high capacity, high degrade, mid dischargeRate, low leak 268 + 269 +-- battery tiers 270 +-- makeshift: cheap, weak, low quality 271 +-- imperial ("da red wunz go fasta"): powerful, low quality 272 +-- commune ("snooty sophisticates"): limited power, high quality, expensive 273 +-- usukwinya ("value engineering"): high power, mid quality, affordable 274 +-- eluthrai ("uncompromising"): high power, high quality, wildly expensive 275 +-- firstborn ("god-tier"): exceptional 276 + 277 +local batteryTiers = { 278 + makeshift = { 279 + name = 'Makeshift'; capacity = .5, decay = 3, leak = 2, dischargeRate = 1, 280 + fab = starlit.type.fab { 281 + metal = {copper=10}; 282 + }; 283 + desc = "Every attosecond this electrical abomination doesn't explode in your face is but the unearned grace of the Wild Gods."; 284 + complexity = 1; 285 + sw = {rarity = 1}; 286 + }; 287 + imperial = { 288 + name = 'Imperial'; capacity = 2, decay = 2, leak = 2, dischargeRate = 2; 289 + fab = starlit.type.fab { 290 + metal = {copper=15, iron = 20}; 291 + size = { print = 0.1 }; 292 + }; 293 + desc = "The Empire's native technology is a lumbering titan: bulky, inefficient, unreliable, ugly, and awesomely powerful. Their batteries are no exception, with raw capacity and throughput that exceed even Usukinwya designs."; 294 + drm = 1; 295 + complexity = 2; 296 + sw = {rarity = 2}; 297 + }; 298 + commune = { 299 + name = 'Commune'; capacity = 1, decay = .5, leak = .2, dischargeRate = 1; 300 + fab = starlit.type.fab { 301 + metal = {vanadium=50, steel=10}; 302 + size = { print = 0.05 }; 303 + }; 304 + desc = "The Commune's proprietary battery designs prioritize reliability, compactness, and maintenance concerns above raw throughput, with an elegance of engineering and design that would make a Su'ikuri cry."; 305 + complexity = 5; 306 + sw = {rarity = 3}; 307 + }; 308 + usukwinya = { 309 + name = 'Usukwinya'; capacity = 2, decay = 1, leak = 1, dischargeRate = 1.5, 310 + fab = starlit.type.fab { 311 + metal = {vanadium=30, argon=10}; 312 + size = { print = 0.07 }; 313 + }; 314 + desc = "A race of consummate value engineers, the Usukwinya have spent thousands of years refining their tech to be as cheap to build as possible, without compromising much on quality. The Tradebirds drive an infamously hard bargain, but their batteries are more than worth their meagre cost."; 315 + drm = 2; 316 + sw = {rarity = 10}; 317 + complexity = 15; 318 + }; 319 + eluthrai = { 320 + name = 'Eluthrai'; capacity = 3, decay = .4, leak = .1, dischargeRate = 1.5, 321 + fab = starlit.type.fab { 322 + metal = {beryllium=20, platinum=20, technetium = 1, cinderstone = 10 }; 323 + size = { print = 0.03 }; 324 + }; 325 + desc = "The uncompromising Eluthrai are never satisfied until every quantifiable characteristic of their tech is maximally optimised down to the picoscale. Their batteries are some of the best in the Reach, and unquestionably the most expensive -- especially for those lesser races trying to copy the designs without the benefit of the sublime autofabricator ecosystem of the Eluthrai themselves."; 326 + complexity = 200; 327 + sw = {rarity = 0}; -- you think you're gonna buy eluthran schematics on SuperDiscountNanoWare.space?? 328 + }; 329 + firstborn = { 330 + name = 'Firstborn'; capacity = 5, decay = 0.1, leak = 0, dischargeRate = 3; 331 + fab = starlit.type.fab { 332 + metal = {neodymium=20, xenon=150, technetium=5, sunsteel = 10 }; 333 + crystal = {astrite = 1}; 334 + size = { print = 0.05 }; 335 + }; 336 + desc = "Firstborn engineering seamlessly merges psionic effects with a mastery of the physical universe unattained by even the greatest of the living Starsouls. Their batteries reach levels of performance that strongly imply Quantum Gravity Theory -- and several major holy books -- need to be rewritten. From the ground up."; 337 + complexity = 1000; 338 + sw = {rarity = 0}; -- lol no 339 + }; 340 +} 341 + 342 +local batterySizes = { 343 + small = {name = 'Small', capacity = .5, dischargeRate = .5, complexity = 1, matMult = .5, fab = starlit.type.fab {size={print=0.1}}}; 344 + mid = { capacity = 1, dischargeRate = 1, complexity = 1, matMult = 1, fab = starlit.type.fab {size={print=0.3}}}; 345 + large = {name = 'Large', capacity = 2, dischargeRate = 1.5, complexity = 1, matMult = 1.5, fab = starlit.type.fab {size={print=0.5}}}; 346 + huge = {name = 'Huge', capacity = 3, dischargeRate = 2, complexity = 1, matMult = 2, fab = starlit.type.fab {size={print=0.8}}}; 347 +} 348 + 349 +local batteryTypes = { 350 + supercapacitor = { 351 + name = 'Supercapacitor'; 352 + desc = 'Room-temperature superconductors make for very reliable, high-dischargeRate, but low-capacity batteries.'; 353 + fab = starlit.type.fab { 354 + metal = { enodium = 5 }; 355 + size = {print=0.8}; 356 + }; 357 + sw = { 358 + cost = { 359 + cycles = 5e9; -- 5 bil cycles 360 + ram = 10e9; -- 10GB 361 + }; 362 + pgmSize = 2e9; -- 2GB 363 + rarity = 5; 364 + }; 365 + capacity = 50e3, dischargeRate = 1000; 366 + leak = 0, decay = 1e-6; 367 + 368 + complexity = 3; 369 + }; 370 + chemical = { 371 + name = 'Chemical'; 372 + desc = ''; 373 + fab = starlit.type.fab { 374 + element = { lithium = 3}; 375 + metal = {iron = 5}; 376 + size = {print=1.0}; 377 + }; 378 + sw = { 379 + cost = { 380 + cycles = 1e9; -- 1 bil cycles 381 + ram = 2e9; -- 2GB 382 + }; 383 + pgmSize = 512e6; -- 512MB 384 + rarity = 2; 385 + }; 386 + capacity = 200e3, dischargeRate = 200; 387 + leak = 0.2, decay = 1e-2; 388 + complexity = 1; 389 + }; 390 + carbon = { 391 + name = 'Carbon'; 392 + desc = 'Carbon nanotubes form the basis of many important metamaterials, chief among them power-polymer.'; 393 + capacity = 1; 394 + fab = starlit.type.fab { 395 + element = { carbon = 40 }; 396 + size = {print=0.5}; 397 + }; 398 + sw = { 399 + cost = { 400 + cycles = 50e9; -- 50 bil cycles 401 + ram = 64e9; -- 64GB 402 + }; 403 + pgmSize = 1e9; -- 1GB 404 + rarity = 10; 405 + }; 406 + capacity = 100e3, dischargeRate = 500; 407 + leak = 0.1, decay = 1e-3; 408 + complexity = 10; 409 + }; 410 + hybrid = { 411 + name = 'Hybrid'; 412 + desc = ''; 413 + capacity = 1; 414 + fab = starlit.type.fab { 415 + element = { 416 + lithium = 3; 417 + }; 418 + metal = { 419 + iron = 5; 420 + }; 421 + size = {print=1.5}; 422 + }; 423 + sw = { 424 + cost = { 425 + cycles = 65e9; -- 65 bil cycles 426 + ram = 96e9; -- 96GB 427 + }; 428 + pgmSize = 5e9; -- 5GB 429 + rarity = 15; 430 + }; 431 + capacity = 300e3, dischargeRate = 350; 432 + leak = 0.3, decay = 1e-5; 433 + complexity = 30; 434 + }; 435 +} 436 + 437 +local function elemath(dest, src, mult) 438 + dest = dest or {} 439 + for k,v in pairs(src) do 440 + if not dest[k] then dest[k] = 0 end 441 + dest[k] = dest[k] + v*mult 442 + end 443 + return dest 444 +end 445 + 446 +for bTypeName, bType in pairs(batteryTypes) do 447 +for bTierName, bTier in pairs(batteryTiers) do 448 +for bSizeName, bSize in pairs(batterySizes) do 449 + -- elemath(elementCost, bType.fab.element or {}, bSize.matMult) 450 + -- elemath(elementCost, bTier.fab.element or {}, bSize.matMult) 451 + -- elemath(metalCost, bType.fab.metal or {}, bSize.matMult) 452 + -- elemath(metalCost, bTier.fab.metal or {}, bSize.matMult) 453 + local fab = bType.fab + bTier.fab + bSize.fab + starlit.type.fab { 454 + element = {copper = 10, silicon = 5}; 455 + } 456 + local baseID = string.format('battery_%s_%s_%s', 457 + bTypeName, bTierName, bSizeName) 458 + local id = 'starlit_electronics:'..baseID 459 + local name = string.format('%s %s Battery', bTier.name, bType.name) 460 + if bSize.name then name = bSize.name .. ' ' .. name end 461 + local function batStat(s) 462 + if s == 'size' then 463 + --return bType.fab[s] * (bTier.fab[s] or 1) * (bSize.fab[s] or 1) 464 + return fab.size and fab.size.print or 1 465 + else 466 + return bType[s] * (bTier[s] or 1) * (bSize[s] or 1) 467 + end 468 + end 469 + 470 + local swID = 'starlit_electronics:schematic_'..baseID 471 + fab.reverseEngineer = { 472 + complexity = bTier.complexity * bSize.complexity * bType.complexity; 473 + sw = swID; 474 + } 475 + fab.flag = {print=true} 476 + 477 + starlit.item.battery.link(id, { 478 + name = name; 479 + desc = table.concat({ 480 + bType.desc or ''; 481 + bTier.desc or ''; 482 + bSize.desc or ''; 483 + }, ' '); 484 + 485 + fab = fab; 486 + 487 + capacity = batStat 'capacity'; 488 + dischargeRate = batStat 'dischargeRate'; 489 + leak = batStat 'leak'; 490 + decay = batStat 'decay'; 491 + }) 492 + 493 + local rare 494 + if bType.sw.rarity == 0 or bTier.sw.rarity == 0 then 495 + -- rarity is measured such that the player has a 1/r 496 + -- chance of finding a given item, or if r=0, no chance 497 + -- whatsoever (the sw must be obtained e.g. by reverse- 498 + -- engineering alien tech) 499 + rare = 0 500 + else 501 + rare = bType.sw.rarity + bTier.sw.rarity 502 + end 503 + 504 + starlit.item.sw.link(swID, { 505 + kind = 'schematic'; 506 + name = name .. ' Schematic'; 507 + output = id; 508 + size = bType.sw.pgmSize; 509 + cost = bType.sw.cost; 510 + rarity = rare; 511 + }) 512 + 513 + E.schematicGroupLink('starlit_electronics:battery', swID) 514 + 515 +end end end 516 + 517 + 518 +----------- 519 +-- chips -- 520 +----------- 521 + 522 +E.sw = {} 523 +function E.sw.findSchematicFor(item) 524 + local id = ItemStack(item):get_name() 525 + print(id) 526 + local fm = minetest.registered_items[id]._starlit 527 + if not (fm and fm.fab and fm.fab.reverseEngineer) then return nil end 528 + local id = fm.fab.reverseEngineer.sw 529 + return id, starlit.item.sw.db[id] 530 +end 531 + 532 +E.chip = { file = {} } 533 +do local T,G = lib.marshal.t, lib.marshal.g 534 + -- love too reinvent unions from first principles 535 + E.chip.data = G.struct { 536 + label = T.str; 537 + uuid = T.u64; 538 + files = G.array(16, G.class(G.struct { 539 + kind = G.enum { 540 + 'sw'; -- a piece of installed software 541 + 'note'; -- a user-readable text file 542 + 'research'; -- saved RE progress 543 + 'genome'; -- for use with plant biosequencer? 544 + 'blob'; -- opaque binary blob, so 3d-pty mods can use the 545 + -- file mechanism to store arbirary data. 546 + }; 547 + drm = T.u8; -- inhibit copying 548 + name = T.str; 549 + body = T.text; 550 + }, function(file) -- enc 551 + local b = E.chip.file[file.kind].enc(file.body) 552 + return { 553 + kind = file.kind; 554 + drm = file.drm; 555 + name = file.name; 556 + body = b; 557 + } 558 + end, function(file) -- dec 559 + local f, ns = E.chip.file[file.kind].dec(file.body) 560 + file.body = f 561 + return file, ns 562 + end)); 563 + bootSlot = T.u8; -- indexes into files; 0 = no bootloader 564 + } 565 + E.chip.file.sw = G.struct { 566 + pgmId = T.str; 567 + conf = G.array(16, G.struct { 568 + key = T.str, value = T.str; 569 + }); 570 + } 571 + E.chip.file.note = G.struct { 572 + author = T.str; 573 + entries = G.array(16, G.struct { 574 + title = T.str; 575 + body = T.str; 576 + }); 577 + } 578 + E.chip.file.research = G.struct { 579 + itemId = T.str; 580 + progress = T.clamp; 581 + } 582 + E.chip.file.blob = G.struct { 583 + kind = T.str; -- MT ID that identifies a blob file type belonging to an external mod 584 + size = T.u8; -- this must be manually reported since we don't know how to evaluate it 585 + data = T.text; 586 + } 587 + function E.chip.fileSize(file) 588 + -- boy howdy 589 + if file.kind == 'blob' then 590 + return file.body.size 591 + elseif file.kind == 'note' then 592 + local sz = 0x10 + #file.body.author 593 + for _, e in pairs(file.body.entries) do 594 + sz = sz + #e.title + #e.body + 0x10 -- header overhead 595 + end 596 + return sz 597 + elseif file.kind == 'research' then 598 + local re = assert(minetest.registered_items[file.body.itemId]._starlit.fab.reverseEngineer) 599 + return starlit.item.sw.db[re.sw].size * file.body.progress 600 + elseif file.kind == 'sw' then 601 + return starlit.item.sw.db[file.body.pgmId].size 602 + elseif file.kind == 'genome' then 603 + return 0 -- TODO 604 + end 605 + end 606 + local metaKey = 'starlit_electronics:chip' 607 + function E.chip.read(chip) 608 + local m = chip:get_meta() 609 + local blob = m:get_string(metaKey) 610 + if blob and blob ~= '' then 611 + return E.chip.data.dec(lib.str.meta_dearmor(blob)) 612 + else -- prepare to format the chip 613 + return { 614 + label = ''; 615 + bootSlot = 0; 616 + uuid = math.floor(math.random(0,2^32)); 617 + files = {}; 618 + } 619 + end 620 + end 621 + function E.chip.write(chip, data) 622 + local m = chip:get_meta() 623 + m:set_string(metaKey, lib.str.meta_armor(E.chip.data.enc(data))) 624 + E.chip.update(chip) 625 + end 626 + function E.chip.fileOpen(chip, inode, fn) 627 + local c = E.chip.read(chip) 628 + if fn(c.files[inode]) then 629 + E.chip.write(chip, c) 630 + return true 631 + end 632 + return false 633 + end 634 + function E.chip.fileWrite(chip, inode, file) 635 + local c = E.chip.read(chip) 636 + c.files[inode] = file 637 + E.chip.write(chip, c) 638 + end 639 + function E.chip.usedSpace(chip, d) 640 + d = d or E.chip.read(chip) 641 + local sz = 0 642 + for _, f in pairs(d.files) do 643 + sz = sz + E.chip.fileSize(f) 644 + end 645 + return sz 646 + end 647 + function E.chip.freeSpace(chip, d) 648 + local used = E.chip.usedSpace(chip,d) 649 + local max = assert(chip:get_definition()._starlit.chip.flash) 650 + return max - used 651 + end 652 + function E.chip.install(chip, file) 653 + -- remember to write out the itemstack after using this function! 654 + local d = E.chip.read(chip) 655 + if E.chip.freeSpace(chip, d) - E.chip.fileSize(file) >= 0 then 656 + table.insert(d.files, file) 657 + E.chip.write(chip, d) 658 + return true 659 + else 660 + return false 661 + end 662 + end 663 +end 664 + 665 +function E.chip.files(ch) 666 + local m = ch:get_meta() 667 + if not m:contains 'starlit_electronics:chip' then 668 + return nil 669 + end 670 + local data = E.chip.read(ch) 671 + local f = 0 672 + return function() 673 + f = f + 1 674 + return data.files[f], f 675 + end 676 +end 677 + 678 +function E.chip.describe(ch, defOnly) 679 + local def, data if defOnly then 680 + def, data = ch, {} 681 + else 682 + def = ch:get_definition() 683 + local m = ch:get_meta() 684 + if m:contains 'starlit_electronics:chip' then 685 + data = E.chip.read(ch) 686 + else 687 + data = {} 688 + defOnly = true 689 + end 690 + def = assert(def._starlit.chip) 691 + end 692 + local props = { 693 + {title = 'Clock Rate', affinity = 'info'; 694 + desc = lib.math.si('Hz', def.clockRate)}; 695 + {title = 'RAM', affinity = 'info'; 696 + desc = lib.math.si('B', def.ram)}; 697 + } 698 + if not defOnly then 699 + table.insert(props, { 700 + title = 'Free Storage', affinity = 'info'; 701 + desc = lib.math.si('B', E.chip.freeSpace(ch, data)) .. ' / ' 702 + .. lib.math.si('B', def.flash); 703 + }) 704 + local swAffMap = { 705 + schematic = 'schematic'; 706 + suitPower = 'ability'; 707 + driver = 'driver'; 708 + } 709 + for i, e in ipairs(data.files) do 710 + local aff = 'neutral' 711 + local name = e.name 712 + local disabled = false 713 + if e.kind == 'sw' then 714 + for _,cf in pairs(e.body.conf) do 715 + if cf.key == 'disable' and cf.value == 'yes' then 716 + disabled = true 717 + break 718 + end 719 + end 720 + local sw = starlit.item.sw.db[e.body.pgmId] 721 + aff = swAffMap[sw.kind] or 'good' 722 + if name == '' then name = sw.name end 723 + end 724 + name = name or '<???>' 725 + table.insert(props, disabled and { 726 + title = name; 727 + affinity = aff; 728 + desc = '<off>'; 729 + } or { 730 + --title = name; 731 + affinity = aff; 732 + desc = name; 733 + }) 734 + end 735 + else 736 + table.insert(props, { 737 + title = 'Flash Storage', affinity = 'info'; 738 + desc = lib.math.si('B', def.flash); 739 + }) 740 + end 741 + return starlit.ui.tooltip { 742 + title = data.label and data.label~='' and string.format('<%s>', data.label) or def.name; 743 + color = lib.color(.6,.6,.6); 744 + desc = def.desc; 745 + props = props; 746 + }; 747 +end 748 + 749 +function E.chip.update(chip) 750 + chip:get_meta():set_string('description', E.chip.describe(chip)) 751 +end 752 + 753 +starlit.item.chip.foreach('starlit_electronics:chip-gen', {}, function(id, def) 754 + minetest.register_craftitem(id, { 755 + short_description = def.name; 756 + description = E.chip.describe(def, true); 757 + inventory_image = def.img or 'starlit-item-chip.png'; 758 + groups = {chip = 1}; 759 + _starlit = { 760 + fab = def.fab; 761 + chip = def; 762 + }; 763 + }) 764 +end) 765 + 766 +-- in case other mods want to define their own tiers 767 +E.chip.tiers = lib.registry.mk 'starlit_electronics:chipTiers' 768 +E.chip.tiers.meld { 769 + -- GP chips 770 + tiny = {name = 'Tiny Chip', clockRate = 512e3, flash = 4096, ram = 1024, powerEfficiency = 1e9, size = 1}; 771 + small = {name = 'Small Chip', clockRate = 128e6, flash = 512e6, ram = 512e6, powerEfficiency = 1e8, size = 3}; 772 + med = {name = 'Chip', clockRate = 1e9, flash = 4e9, ram = 4e9, powerEfficiency = 1e7, size = 6}; 773 + large = {name = 'Large Chip', clockRate = 2e9, flash = 8e9, ram = 8e9, powerEfficiency = 1e6, size = 8}; 774 + -- specialized chips 775 + compute = {name = 'Compute Chip', clockRate = 4e9, flash = 24e6, ram = 64e9, powerEfficiency = 1e8, size = 4}; 776 + data = {name = 'Data Chip', clockRate = 128e3, flash = 2e12, ram = 32e3, powerEfficiency = 1e5, size = 4}; 777 + lp = {name = 'Low-Power Chip', clockRate = 128e6, flash = 64e6, ram = 1e9, powerEfficiency = 1e10, size = 4}; 778 + carbon = {name = 'Carbon Chip', clockRate = 64e6, flash = 32e6, ram = 2e6, powerEfficiency = 2e9, size = 2, circ='carbon'}; 779 +} 780 + 781 +E.chip.tiers.foreach('starlit_electronics:genChips', {}, function(id, t) 782 + id = t.id or string.format('%s:chip_%s', minetest.get_current_modname(), id) 783 + local circMat = t.circ or 'silicon'; 784 + starlit.item.chip.link(id, { 785 + name = t.name; 786 + clockRate = t.clockRate; 787 + flash = t.flash; 788 + ram = t.ram; 789 + powerEfficiency = t.powerEfficiency; -- cycles per joule 790 + fab = { 791 + flag = { 792 + silicompile = true; 793 + }; 794 + time = { 795 + silicompile = t.size * 24*60; 796 + }; 797 + cost = { 798 + energy = 50e3 + t.size * 15e2; 799 + }; 800 + element = { 801 + [circMat] = 50 * t.size; 802 + copper = 30; 803 + gold = 15; 804 + }; 805 + }; 806 + }) 807 +end) 808 + 809 +function E.chip.findBest(test, ...) 810 + local chip, bestFitness 811 + for id, c in pairs(starlit.item.chip.db) do 812 + local fit, fitness = test(c, ...) 813 + if fit and (bestFitness == nil or fitness > bestFitness) then 814 + chip, bestFitness = id, fitness 815 + end 816 + end 817 + return chip, starlit.item.chip.db[chip], bestFitness 818 +end 819 + 820 +function E.chip.findForStorage(sz) 821 + return E.chip.findBest(function(c) 822 + return c.flash >= sz, -math.abs(c.flash - sz) 823 + end) 824 +end 825 + 826 +function E.chip.sumCompute(chips) 827 + local c = { 828 + cycles = 0; 829 + ram = 0; 830 + flashFree = 0; 831 + powerEfficiency = 0; 832 + } 833 + local n = 0 834 + for _, e in pairs(chips) do 835 + n = n + 1 836 + if not e:is_empty() then 837 + local ch = e:get_definition()._starlit.chip 838 + c.cycles = c.cycles + ch.clockRate 839 + c.ram = c.ram + ch.clockRate 840 + c.flashFree = c.flashFree + E.chip.freeSpace(e) 841 + c.powerEfficiency = c.powerEfficiency + ch.powerEfficiency 842 + end 843 + end 844 + if n > 0 then c.powerEfficiency = c.powerEfficiency / n end 845 + return c 846 +end 847 + 848 +E.chip.fileHandle = lib.class { 849 + __name = 'starlit_electronics:chip.fileHandle'; 850 + construct = function(chip, inode) -- stack, int --> fd 851 + return { chip = chip, inode = inode } 852 + end; 853 + __index = { 854 + read = function(self) 855 + local dat = E.chip.read(self.chip) 856 + return dat.files[self.inode] 857 + end; 858 + write = function(self,data) 859 + -- print('writing', self.chip, self.inode) 860 + return E.chip.fileWrite(self.chip, self.inode, data) 861 + end; 862 + erase = function(self) 863 + local dat = E.chip.read(self.chip) 864 + table.remove(dat.files, self.inode) 865 + E.chip.write(self.chip, dat) 866 + self.inode = nil 867 + end; 868 + open = function(self,fn) 869 + return E.chip.fileOpen(self.chip, self.inode, fn) 870 + end; 871 + }; 872 +} 873 + 874 +function E.chip.usableSoftware(chips,pgm) 875 + local comp = E.chip.sumCompute(chips) 876 + local r = {} 877 + local unusable = {} 878 + local sw if pgm then 879 + if type(pgm) == 'string' then 880 + pgm = {starlit.item.sw.db[pgm]} 881 + end 882 + sw = pgm 883 + else 884 + sw = {} 885 + for i, e in ipairs(chips) do 886 + if (not e:is_empty()) 887 + and minetest.get_item_group(e:get_name(), 'chip') ~= 0 888 + then 889 + for fl, inode in E.chip.files(e) do 890 + if fl.kind == 'sw' then 891 + local s = starlit.item.sw.db[fl.body.pgmId] 892 + table.insert(sw, { 893 + sw = s, chip = e, chipSlot = i; 894 + file = fl, inode = inode; 895 + }) 896 + end 897 + end 898 + end 899 + end 900 + end 901 + 902 + for _, s in pairs(sw) do 903 + if s.sw.cost.ram <= comp.ram then 904 + table.insert(r, { 905 + sw = s.sw; 906 + chip = s.chip, chipSlot = s.chipSlot; 907 + file = s.file; 908 + fd = E.chip.fileHandle(s.chip, s.inode); 909 + speed = s.sw.cost.cycles / comp.cycles; 910 + powerCost = s.sw.cost.cycles / comp.powerEfficiency; 911 + comp = comp; 912 + }) 913 + else 914 + table.insert(unusable, { 915 + sw = s.sw; 916 + chip = s.chip; 917 + ramNeeded = s.sw.cost.ram - comp.ram; 918 + }) 919 + end 920 + end 921 + return r, unusable 922 +end 923 + 924 +starlit.include 'sw'
Added mods/starlit-electronics/mod.conf version [c7d10b71e3].
1 +name = starlit_electronics 2 +title = starlit electronics 3 +description = basic electronic components and logic 4 +depends = starlit
Added mods/starlit-electronics/sw.lua version [288b4b5859].
1 +-- [ʞ] sw.lua 2 +-- ~ lexi hale <lexi@hale.su> 3 +-- 🄯 EUPL v1.2 4 +-- ? 5 + 6 +------------------------------- 7 +-- basic suit nano abilities -- 8 +------------------------------- 9 +local function shredder(prop) 10 + local function getItemsForFab(fab) 11 + local elt 12 + if fab then 13 + elt = fab:elementalize() 14 + else 15 + elt = {} 16 + end 17 + local items = {} 18 + if elt.element then 19 + for k,v in pairs(elt.element) do 20 + local st = ItemStack { 21 + name = starlit.world.material.element.db[k].form.element; 22 + count = v; 23 + } 24 + table.insert(items, st) 25 + end 26 + end 27 + return items 28 + end 29 + 30 + return function(user, ctx) 31 + local function cleanup() 32 + user.action.prog.shred = nil 33 + if user.action.sfx.shred then 34 + minetest.sound_fade(user.action.sfx.shred, 1, 0) 35 + user.action.sfx.shred = nil 36 + end 37 + if user.action.fx.shred then 38 + user.action.fx.shred.abort() 39 + end 40 + end 41 + 42 + if user.action.tgt.type ~= 'node' then return end 43 + local what = user.action.tgt.under 44 + if what == nil or user.entity:get_pos():distance(what) > prop.range then 45 + cleanup() 46 + return false 47 + end 48 + local shredTime = 1.0 49 + local soundPitch = 1.0 -- TODO 50 + local pdraw = prop.powerDraw or 0 51 + 52 + local node = minetest.get_node(what) 53 + local nd = minetest.registered_nodes[node.name] 54 + local elt, fab, vary 55 + if nd._starlit then 56 + fab = nd._starlit.recover or nd._starlit.fab 57 + vary = nd._starlit.recover_vary 58 + end 59 + if fab then 60 + if fab.flag then 61 + if fab.flag.unshreddable then 62 + cleanup() 63 + return false 64 + -- TODO error beep 65 + end 66 + end 67 + shredTime = fab.time and fab.time.shred or shredTime -- FIXME 68 + if fab.cost and fab.cost.shredPower then 69 + pdraw = pdraw * fab.cost.shredPower 70 + end 71 + end 72 + local maxW = user:getSuit():maxPowerUse() 73 + if maxW < pdraw then 74 + shredTime = shredTime * (pdraw/maxW) 75 + pdraw = maxW 76 + end 77 + if ctx.how.state == 'prog' then 78 + local pdx = pdraw * ctx.how.delta 79 + local p = user:suitDrawCurrent(pdx, ctx.how.delta, {kind='nano',label='Shredder'}, pdx) 80 + if p < pdx then 81 + cleanup() 82 + return false 83 + elseif not user.action.prog.shred then 84 + cleanup() -- kill danglers 85 + -- begin 86 + user.action.prog.shred = 0 87 + user.action.sfx.shred = minetest.sound_play('starlit-nano-shred', { 88 + object = user.entity; 89 + max_hear_distance = prop.range*2; 90 + loop = true; 91 + pitch = soundPitch; 92 + }) 93 + user.action.fx.shred = starlit.fx.nano.shred(user, what, prop, shredTime, node) 94 + else 95 + user.action.prog.shred = user.action.prog.shred + ctx.how.delta or 0 96 + end 97 + --print('shred progress: ', user.action.prog.shred) 98 + if user.action.prog.shred >= shredTime then 99 + if minetest.dig_node(what) then 100 + --print('shred complete') 101 + user:suitSound 'starlit-success' 102 + if fab then 103 + local vf = fab 104 + if vary then 105 + local rng = (starlit.world.seedbank+0xa891f62)[minetest.hash_node_position(what)] 106 + vf = vf + vary(rng, {}) 107 + end 108 + local items = getItemsForFab(vf) 109 + for i, it in ipairs(items) do user:give(it) end 110 + end 111 + else 112 + user:suitSound 'starlit-error' 113 + end 114 + cleanup() 115 + end 116 + elseif ctx.how.state == 'halt' then 117 + cleanup() 118 + end 119 + return true 120 + end 121 +end 122 + 123 +starlit.item.sw.link('starlit_electronics:shred', { 124 + name = 'NanoShred'; 125 + kind = 'suitPower', powerKind = 'active'; 126 + desc = 'An open-source program used in its various forks and iterations all across human-inhabited space and beyond. Rumored to contain fragments of code stolen from the nanoware of the Greater Races by an elusive infoterrorist.'; 127 + size = 500e3; 128 + cost = { 129 + cycles = 100e6; 130 + ram = 500e6; 131 + }; 132 + run = shredder{range=2, powerDraw=200}; 133 +}) 134 + 135 +starlit.item.sw.link('starlit_electronics:compile_commune', { 136 + name = 'Compile Matter'; 137 + kind = 'suitPower', powerKind = 'direct'; 138 + desc = "A basic suit matter compiler program, rather slow but ruthlessly optimized for power- and memory-efficiency by some of the Commune's most fanatic coders."; 139 + size = 700e3; 140 + cost = { 141 + cycles = 300e6; 142 + ram = 2e9; 143 + }; 144 + ui = 'starlit:compile-matter-component'; 145 + run = function(user, ctx) 146 + end; 147 +}) 148 + 149 +starlit.item.sw.link('starlit_electronics:compile_block_commune', { 150 + name = 'Compile Block'; 151 + kind = 'suitPower', powerKind = 'active'; 152 + desc = "An advanced suit matter compiler program, capable of printing complete devices and structure parts directly into the world."; 153 + size = 5e6; 154 + cost = { 155 + cycles = 700e6; 156 + ram = 4e9; 157 + }; 158 + ui = 'starlit:compile-matter-block'; 159 + run = function(user, ctx) 160 + end; 161 +}) 162 + 163 +do local J = starlit.store.compilerJob 164 + starlit.item.sw.link('starlit_electronics:driver_compiler_commune', { 165 + name = 'Matter Compiler'; 166 + kind = 'driver'; 167 + desc = "A driver for a standalone matter compiler, suitable for building larger components than your suit alone can handle."; 168 + size = 850e3; 169 + cost = { 170 + cycles = 400e6; 171 + ram = 2e9; 172 + }; 173 + ui = 'starlit:device-compile-matter-component'; 174 + run = function(user, ctx) 175 + end; 176 + bgProc = function(user, ctx, interval, runState) 177 + if runState.flags.compiled == true then return false end 178 + -- only so many nanides to go around 179 + runState.flags.compiled = true 180 + local time = minetest.get_gametime() 181 + local cyclesLeft = ctx.comp.cycles * interval 182 + 183 + for id, e in ipairs(ctx.file.body.conf) do 184 + if e.key == 'job' then 185 + local t = J.dec(e.value) 186 + local remove = false 187 + local r = starlit.item.sw.db[t.schematic] 188 + if not r then -- bad schematic 189 + remove = true 190 + else 191 + local ccost = ctx.sw.cost.cycles + r.cost.cycles 192 + local tcost = ccost / cyclesLeft 193 + t.progress = t.progress + (1/tcost)*interval 194 + cyclesLeft = cyclesLeft - ccost*interval 195 + if t.progress >= 1 then 196 + -- complete 197 + remove = true 198 + local i = starlit.item.mk(r.output, { 199 + how = 'print'; 200 + user = user; -- for suit 201 + compiler = { 202 + node = ctx.compiler; -- for device 203 + sw = ctx.sw; 204 + install = ctx.fd; 205 + }; 206 + schematic = r; 207 + }) 208 + ctx.giveItem(i) 209 + end 210 + end 211 + if remove then 212 + table.remove(ctx.file.body.conf, id) 213 + else 214 + e.value = J.enc(t) 215 + end 216 + if not cyclesLeft > 0 then break end 217 + end 218 + end 219 + ctx.saveConf() 220 + end; 221 + }) 222 +end 223 + 224 +local function pasv_heal(effect, energy, lvl, pgmId) 225 + return function(user, ctx, interval, runState) 226 + if runState.flags.healed == true then return false end 227 + -- competing nanosurgical programs?? VERY bad idea 228 + runState.flags.healed = true 229 + 230 + local amt, f = user:effectiveStat 'health' 231 + local st = user:getSuit():powerState() 232 + if (st == 'on' and f < lvl) or (st == 'powerSave' and f < math.min(lvl,0.25)) then 233 + local maxPower = energy*interval 234 + local p = user:suitDrawCurrent(maxPower, interval, { 235 + id = 'heal'; 236 + src = 'suitPower'; 237 + pgmId = pgmId; 238 + healAmount = effect; 239 + }) 240 + if p > 0 then 241 + local heal = (p/maxPower) * ctx.speed * effect*interval 242 + --user:statDelta('health', math.max(1, heal)) 243 + starlit.fx.nano.heal(user, {{player=user.entity}}, heal, 1) 244 + return true 245 + end 246 + end 247 + return false -- program did not run 248 + end; 249 +end 250 + 251 +starlit.item.sw.link('starlit_electronics:nanomed', { 252 + name = 'NanoMed'; 253 + kind = 'suitPower', powerKind = 'passive'; 254 + desc = 'Repair of the body is a Commune specialty, and their environment suits all come equipped with highly sophisticated nanomedicine suites, able to repair even the most grievous of wounds given sufficient energy input and time.'; 255 + size = 2e9; 256 + cost = { 257 + cycles = 400e6; 258 + ram = 3e9; 259 + }; 260 + run = pasv_heal(2, 20, 1); 261 +}) 262 + 263 +starlit.item.sw.link('starlit_electronics:autodoc_deluxe', { 264 + name = 'AutoDoc Deluxe'; 265 + kind = 'suitPower', powerKind = 'passive'; 266 + desc = "A flagship offering of the Excellence Unyielding nanoware division, AutoDoc Deluxe has been the top-rated nanocare package in the Celestial Shores Province for six centuries and counting. Every chip includes our comprehensive database of illnesses, prosyn schematics, and organ repair techniques, with free over-the-ether updates guaranteed for ten solariads from date of purchase! When professional medical care just isn't an option, 9/10 doctors recommend Excellence Unyielding AutoDoc Deluxe! The remaining doctor was bribed by our competitors."; 267 + size = 1e9; 268 + cost = { 269 + cycles = 700e6; 270 + ram = 1e9; 271 + }; 272 + run = pasv_heal(4, 50, .7); 273 +})
Added mods/starlit-material/elements.lua version [5740080b38].
1 +local lib = starlit.mod.lib 2 +local W = starlit.world 3 +local M = W.material 4 + 5 +M.element.meld { 6 + hydrogen = { 7 + name = 'hydrogen', sym = 'H', n = 1; 8 + gas = true; 9 + color = lib.color(1,0.8,.3); 10 + }; 11 + beryllium = { 12 + name = 'Beryllium', sym = 'Be', n = 4; 13 + metal = true; -- rare emerald-stuff 14 + color = lib.color(0.2,1,0.2); 15 + }; 16 + oxygen = { 17 + name = 'oxygen', sym = 'O', n = 8; 18 + gas = true; 19 + color = lib.color(.2,1,.2); 20 + }; 21 + carbon = { 22 + name = 'carbon', sym = 'C', n = 6; 23 + color = lib.color(.7,.2,.1); 24 + }; 25 + silicon = { 26 + name = 'silicon', sym = 'Si', n = 14; 27 + metal = true; -- can be forged into an ingot 28 + color = lib.color(.6,.6,.4); 29 + }; 30 + potassium = { 31 + name = 'potassium', sym = 'K', n = 19; 32 + -- potassium is technically a metal but it's so soft 33 + -- it can be easily nanoworked without high temps, so 34 + -- ingots make no sense 35 + color = lib.color(1,.8,0.1); 36 + }; 37 + calcium = { 38 + name = 'calcium', sym = 'Ca', n = 20; 39 + metal = true; 40 + color = lib.color(1,1,0.7); 41 + }; 42 + aluminum = { 43 + name = 'aluminum', sym = 'Al', n = 13; 44 + metal = true; 45 + color = lib.color(0.9,.95,1); 46 + }; 47 + iron = { 48 + name = 'iron', sym = 'Fe', n = 26; 49 + metal = true; 50 + color = lib.color(.3,.3,.3); 51 + }; 52 + copper = { 53 + name = 'copper', sym = 'Cu', n = 29; 54 + metal = true; 55 + color = lib.color(.8,.4,.1); 56 + }; 57 + lithium = { 58 + name = 'lithium', sym = 'Li', n = 3; 59 + -- i think lithium is considered a metal but we don't mark it as 60 + -- one here because making a 'lithium ingot' is insane (even possible?) 61 + color = lib.color(1,0.8,.3); 62 + }; 63 + titanium = { 64 + name = 'titanium', sym = 'Ti', n = 22; 65 + metal = true; 66 + color = lib.color(.7,.7,.7); 67 + }; 68 + vanadium = { 69 + name = 'vanadium', sym = 'V', n = 23; 70 + metal = true; 71 + color = lib.color(.3,0.5,.3); 72 + }; 73 + xenon = { 74 + name = 'xenon', sym = 'Xe', n = 54; 75 + gas = true; 76 + color = lib.color(.5,.1,1); 77 + }; 78 + argon = { 79 + name = 'argon', sym = 'Ar', n = 18; 80 + gas = true; 81 + color = lib.color(0,0.1,.9); 82 + }; 83 + osmium = { 84 + name = 'osmium', sym = 'Os', n = 76; 85 + metal = true; 86 + color = lib.color(.8,.1,1); 87 + }; 88 + iridium = { 89 + name = 'iridium', sym = 'Ir', n = 77; 90 + metal = true; 91 + color = lib.color(.8,0,.5); 92 + }; 93 + technetium = { 94 + name = 'technetium', sym = 'Tc', n = 43; 95 + desc = 'Prized by the higher Powers for subtle interactions that elude mere human scholars, technetium is of particular use in nuclear nanobatteries.'; 96 + metal = true; 97 + color = lib.color(.2,0.2,1); 98 + }; 99 + uranium = { 100 + name = 'uranium', sym = 'U', n = 92; 101 + desc = 'A weak but relatively plentiful nuclear fuel.'; 102 + metal = true; 103 + color = lib.color(.2,.7,0); 104 + }; 105 + thorium = { 106 + name = 'thorium', sym = 'Th', n = 90; 107 + desc = 'A frighteningly powerful nuclear fuel.'; 108 + metal = true; 109 + color = lib.color(.7,.3,.1); 110 + }; 111 + silver = { 112 + name = 'silver', sym = 'Ag', n = 47; 113 + metal = true; 114 + color = lib.color(.7,.7,.8); 115 + }; 116 + gold = { 117 + name = 'gold', sym = 'Au', n = 79; 118 + metal = true; 119 + color = lib.color(1,.8,0); 120 + }; 121 +}
Added mods/starlit-material/init.lua version [fffa847df5].
1 +local lib = starlit.mod.lib 2 +local M = { 3 + canisterSizes = lib.registry.mk 'starlit_material:canister-size'; 4 +} 5 +M.canisterSizes.foreach('starlit_material:canister_link', {}, function(id, sz) 6 + starlit.item.canister.link(minetest.get_current_modname() .. ':canister_' .. id, { 7 + name = sz.name; 8 + slots = sz.slots; 9 + vol = 0.1; -- too big for suit? 10 + desc = sz.desc; 11 + }) 12 +end) 13 +M.canisterSizes.meld { 14 + tiny = {name = 'Tiny Canister', slots = 1, vol = 0.05}; 15 + small = {name = 'Small Canister', slots = 3, vol = 0.2}; 16 + mid = {name = 'Canister', slots = 5, vol = 0.5}; 17 + large = {name = 'Large Canister', slots = 10, vol = 1.0}; 18 + storage = {name = 'Storage Canister', slots = 50, vol = 5.0}; 19 +} 20 + 21 +starlit.include 'elements' 22 +
Added mods/starlit-material/mod.conf version [e33ae98ab9].
1 +name = starlit_material 2 +title = starlit materials 3 +description = defines the raw materials and alloys used in printing 4 +depends = starlit
Added mods/starlit-scenario/init.lua version [bf764e9a62].
1 +local lib = starlit.mod.lib 2 +local scenario = starlit.world.scenario 3 + 4 +local function makeChip(label, schem, sw) 5 + local E = starlit.mod.electronics 6 + local files = {} 7 + local sz = 0 8 + for _, e in ipairs(schem) do 9 + local p = E.sw.findSchematicFor(e[1]) 10 + if p then 11 + local file = { 12 + kind = 'sw', name = '', drm = e[2]; 13 + body = {pgmId = p}; 14 + } 15 + table.insert(files, file) 16 + sz = sz + E.chip.fileSize(file) 17 + end 18 + end 19 + for _, e in ipairs(sw) do 20 + local file = { 21 + kind = 'sw', name = '', drm = e[2]; 22 + body = {pgmId = e[1]}; 23 + } 24 + table.insert(files, file) 25 + sz = sz + E.chip.fileSize(file) 26 + end 27 + local chip = ItemStack(assert(E.chip.findBest(function(c) 28 + return c.flash >= sz, c.ram + c.clockRate 29 + end))) 30 + local r = E.chip.read(chip) 31 + r.label = label 32 + r.files = files 33 + E.chip.write(chip, r) 34 + return chip 35 +end 36 + 37 +local chipLibrary = { 38 + compendium = makeChip('The Gentleman Adventurer\'s Compleat Wilderness Compendium', { 39 + {'starlit_electronics:battery_chemical_imperial_small', 0}; 40 + }, { 41 + {'starlit_electronics:shred', 0}; 42 + --{'starlit_electronics:compile_empire', 0}; 43 + {'starlit_electronics:autodoc_deluxe', 1}; 44 + --{'starlit_electronics:driver_compiler_empire', 0}; 45 + }); 46 + survivalware = makeChip('Emergency Survivalware', { 47 + {'starlit_electronics:battery_chemical_commune_small', 0}; 48 + }, { 49 + {'starlit_electronics:shred', 0}; 50 + {'starlit_electronics:compile_commune', 0}; 51 + {'starlit_electronics:nanomed', 0}; 52 + {'starlit_electronics:driver_compiler_commune', 0}; 53 + }); 54 + misfortune = makeChip("Sold1er0fMisf0rtune TOP Schematic Crackz REPACK", { 55 + {'starlit_electronics:battery_chemical_usukwinya_mid', 0}; 56 + {'starlit_electronics:battery_hybrid_imperial_small', 0}; 57 + -- ammunition 58 + }, {}); 59 +} 60 + 61 +local battery = function(name) 62 + local s = ItemStack(name) 63 + starlit.mod.electronics.battery.setChargeF(s, 1.0) 64 + return s 65 +end 66 + 67 + 68 +table.insert(scenario, { 69 + id = 'starlit_scenario:imperialExpat'; 70 + name = 'Imperial Expat'; 71 + desc = "Hoping to escape a miserable life deep in the grinding gears of the capitalist machine for the bracing freedom of the frontier, you sought entry as a colonist to the new Commune world of Thousand Petal. Fate -- which is to say, terrorists -- intervened, and you wound up stranded on Farthest Shadow with little more than the nanosuit on your back, ship blown to tatters and your soul thoroughly mauled by the explosion of a twisted alien artifact -- which SOMEONE neglected to inform you your ride would be carrying.\nAt least you got some nifty psionic powers out of this whole clusterfuck. Hopefully they're safe to use."; 72 + 73 + species = 'human'; 74 + speciesVariant = 'female'; 75 + soul = { 76 + externalChannel = true; -- able to touch other souls in the spiritual realm 77 + physicalChannel = true; -- able to extend influence into physical realm 78 + damage = 1; 79 + }; 80 + social = { 81 + empire = 'workingClass'; 82 + commune = 'metic'; 83 + }; 84 + 85 + startingItems = { 86 + suit = ItemStack('starlit_suit:suit_survival_commune'); 87 + suitBatteries = {battery 'starlit_electronics:battery_carbon_commune_small'}; 88 + suitChips = { 89 + chipLibrary.survivalware; 90 + -- you didn't notice it earlier, but your Commune environment suit 91 + -- came with this chip already plugged in. it's apparently true 92 + -- what they say: the Commune is always prepared for everything. 93 + -- E V E R Y T H I N G. 94 + }; 95 + suitGuns = {}; 96 + suitAmmo = {}; 97 + suitCans = { 98 + ItemStack('starlit_material:canister_small'); 99 + }; 100 + carry = { 101 + chipLibrary.compendium; 102 + -- you bought this on a whim before you left the Empire, and 103 + -- just happened to still have it on your person when everything 104 + -- went straight to the Wild Gods' privy 105 + }; 106 + }; 107 +}) 108 + 109 +table.insert(scenario, { 110 + id = 'starlit_scenario:gentlemanAdventurer'; 111 + -- Othar Tryggvasson, 112 + name = 'Gentleman Adventurer'; 113 + desc = "Tired of the same-old-same-old, sick of your idiot contemporaries, exasperated with the shallow soul-rotting luxury of life as landless lordling, and earnestly eager to enrage your father, you resolved to see the Reach in all her splendor. Deftly evading the usual tourist traps, you finagled your way into the confidence of the Commune ambassador with a few modest infusions of Father's money -- now *that* should pop his monocle -- and secured yourself a seat on a ride to their brand-new colony at Thousand Petal. How exciting -- a genuine frontier outing!"; 114 + 115 + species = 'human'; 116 + speciesVariant = 'male'; 117 + soul = { 118 + externalChannel = true; 119 + physicalChannel = true; 120 + damage = 1; 121 + }; 122 + social = { 123 + empire = 'aristo'; 124 + }; 125 + 126 + startingItems = { 127 + suit = 'starlit_suit:suit_survival_imperial'; 128 + suitBatteries = {battery 'starlit_electronics:battery_supercapacitor_imperial_mid'}; 129 + suitChips = { 130 + chipLibrary.compendium; 131 + -- Mother, bless her soul, simply insisted on buying you this as a parting 132 + -- gift. "it's dangerous out there for a young man," she proclaimed as 133 + -- if she had profound firsthand experience of the matter. mindful of the 134 + -- husband she endures, you suffered to humor her, and made a big show of 135 + -- installing it your brand-new nanosuit before you fled the family seat. 136 + }; 137 + suitGuns = {}; 138 + suitAmmo = {}; 139 + suitCans = { 140 + ItemStack('starlit_material:canister_mid'); 141 + }; 142 + carry = {}; 143 + }; 144 +}) 145 + 146 +table.insert(scenario, { 147 + -- you start out with strong combat abilities but weak engineering, 148 + -- and will have to scavenge wrecks to find basic crafting gear 149 + id = 'starlit_scenario:terroristTagalong'; 150 + name = 'Terrorist Tagalong'; 151 + desc = "It turns out there's a *reason* Crown jobs pay so well."; 152 + species = 'human'; 153 + speciesVariant = 'female'; 154 + social = { 155 + empire = 'lowlife'; 156 + commune = 'mostWanted'; 157 + underworldConnections = true; 158 + }; 159 + soul = { 160 + externalChannel = true; 161 + physicalChannel = true; 162 + damage = 2; -- closer to the blast 163 + }; 164 + startingItems = { 165 + suit = 'starlit_suit:suit_combat_imperial'; 166 + suitBatteries = { 167 + ItemStack('starlit_electronics:battery_supercapacitor_imperial_small'); 168 + ItemStack('starlit_electronics:battery_chemical_imperial_large'); 169 + }; 170 + suitGuns = {}; 171 + suitAmmo = {}; 172 + carry = {}; 173 + }; 174 + suitChips = {chipLibrary.misfortune}; 175 +}) 176 + 177 +table.insert(scenario, { 178 + id = 'starlit_scenario:tradebirdBodyguard'; 179 + name = 'Tradebird Bodyguard'; 180 + desc = "You've never understood why astropaths of all people *insist* on bodyguards. This one could probably make hash of a good-sized human batallion, if her feathers were sufficiently ruffled. Perhaps it's a status thing. Whatever the case, it's easy money.\nAt least, it was supposed to be.'"; 181 + species = 'usukwinya'; 182 + speciesVariant = 'male'; 183 + soul = { 184 + damage = 0; -- Inyukiriku and her entourage fled the ship when she sensed something serious was about to go down. lucky: the humans only survived because their souls were closed off to the Physical. less luckily, the explosion knocked your escape pod off course and the damn astropath was too busy saving her own skin to come after you 185 + externalChannel = true; -- usukwinya are already psionic 186 + physicalChannel = true; -- usukwinya are Starlit 187 + }; 188 + startingItems = { 189 + suit = 'starlit_suit:suit_combat_usukwinya'; 190 + suitBatteries = { 191 + ItemStack('starlit_electronics:battery_hybrid_usukwinya_mid'); 192 + }; 193 + suitGuns = {}; 194 + suitChips = {}; 195 + suitAmmo = {}; 196 + carry = {}; 197 + }; 198 +})
Added mods/starlit-scenario/mod.conf version [8a1ae19f59].
1 +name = starlit_scenario 2 +title = starlit scenarios 3 +description = built-in scenarios for Starsoul 4 +depends = starlit, starlit_suit, starlit_electronics, starlit_building, starlit_material 5 +# be sure to add any mods from which you list new starting items!
Added mods/starlit-secrets/init.lua version [0a228a51be].
1 +---------------------------------------------------- 2 +------------- CONTROLLED INFORMATION -------------- 3 +---------------------------------------------------- 4 +-- THE INFORMATION CONTAINED IN THIS DOCUMENT IS -- 5 +-- SUBJECT TO RESTRAINT OF TRANSMISSION PER THE -- 6 +-- TERMS OF THE COMMUNE CHARTER INFOSECURITY -- 7 +-- PROVISION. IF YOU ARE NOT AUTHORIZED UNDER THE -- 8 +-- AEGIS OF THE APPROPRITE CONTROLLING AUTHORITY, -- 9 +-- CLOSE THIS DOCUMENT IMMEDIATELY AND REPORT THE -- 10 +-- SECURITY BREACH TO YOUR DESIGNATED INFORMATION -- 11 +-- HYGIENE OVERSEER OR FACE CORRECTIVE DISCIPLINE -- 12 +---------------------------------------------------- 13 + 14 +local lib = starlit.mod.lib 15 +local sec = {} 16 +starlit.mod.secrets = sec 17 + 18 +sec.index = lib.registry.mk 'starlit_secrets:secret' 19 + 20 +--[==[ 21 + 22 +a secret is a piece of information that is made available 23 +for review once certain conditions are met. despite the name, 24 +it doesn't necessarily have to be secret -- it could include 25 +e.g. journal entries about a character's background. a secret 26 +is defined in the following manner: 27 + 28 +{ 29 + title = the string that appears in the UI 30 + stages = { 31 + { 32 + prereqs = { 33 + {kind = 'fact', id = 'starlit:terroristEmployer'} 34 + {kind = 'item', id = 'starlit_electronic:firstbornDoomBong'} 35 + {kind = 'background', id = 'starlit:terroristTagalong'} 36 + } 37 + body = { 38 + 'the firstborn smonked hella weed'; 39 + }; 40 + -- body can also be a function(user,secret) 41 + } 42 + } 43 +} 44 + 45 +TODO would it be useful to impl horn clauses and a general fact database? 46 + is that level of flexibility meaningful? or are simply flags better 47 + 48 +a secret can be a single piece of information predicated 49 +on a fact, in which case the secret and fact should share 50 +the same ID. the ID should be as non-indicative as possible 51 +to avoid spoilers for devs of unrelated code. 52 + 53 +a secret can also be manually unlocked e.g. by using an item 54 + 55 +]==]-- 56 + 57 +function sec.prereqCheck(user, pr) 58 +end
Added mods/starlit-secrets/mod.conf version [74f79378c7].
1 +name = starlit_secrets 2 +title = starlit secrets 3 +description = TS//NOFORN 4 +depends = starlit
Added mods/starlit-suit/init.lua version [078f484c1f].
1 +local lib = starlit.mod.lib 2 +local fab = starlit.type.fab 3 + 4 +local facDescs = { 5 + commune = { 6 + survival = { 7 + suit = 'A light, simple, bare-bones environment suit that will provide heating, cooling, and nanide support to a stranded cosmonaut'; 8 + cc = 'The Survival Suit uses compact thermoelectrics to keep the wearer perfectly comfortable in extremes of heat or cold. It makes up for its heavy power usage with effective insulation that substantially reduces any need for climate control.'; 9 + }; 10 + engineer = { 11 + suit = 'A lightweight environment suit designed for indoor work, the Commune\'s Engineer Suit boasts advanced nanotech capable of constructing objects in place.'; 12 + cc = 'The Engineer Suit is designed for indoor work. Consequently, it features only a low-power thermoelectric cooler meant to keep its wearer comfortable during strenuous work.'; 13 + }; 14 + combat = { 15 + suit = 'A military-grade suit with the latest Commune technology. Designed for maximum force multiplication, the suit has dual weapon hardpoints and supports a gargantuan power reserve. Its nanotech systems are specialized for tearing through obstacles, but can also be used to manufacturer ammunition in a pinch.'; 16 + cc = 'This Combat Suit uses electrothermal cooling to keep an active soldier comfortable and effective, as well as conventional heating coils to enable operation in hostile atmospheres.'; 17 + }; 18 + }; 19 + 20 +} 21 + 22 + 23 +starlit.world.tier.foreach('starlit:suit-gen', {}, function(tid, t) 24 + local function hasTech(tech) 25 + return starlit.world.tier.tech(tid, tech) 26 + end 27 + if not hasTech 'suit' then return end 28 + -- TODO tier customization 29 + -- 30 + local function fabsum(f) 31 + return starlit.world.tier.fabsum(tid, 'suit') 32 + end 33 + local function fabReq(sz, days) 34 + local tierMatBase = ( 35 + (fabsum 'electric' * 4) + 36 + (fabsum 'basis' + fabsum 'suit') * sz 37 + ) 38 + local b = tierMatBase + fab { 39 + -- universal suit requirements 40 + time = { print = 60*60*24 * days }; 41 + size = { printBay = sz }; 42 + } 43 + b.flag = lib.tbl.set('print'); 44 + return b 45 + end 46 + local function facDesc(s, t) 47 + local default = 'A protective nanosuit' -- FIXME 48 + if not facDescs[tid] then return default end 49 + if not facDescs[tid][s] then return default end 50 + if not facDescs[tid][s][t] then return default end 51 + return facDescs[tid][s][t] 52 + end 53 + starlit.item.suit.link('starlit_suit:suit_survival_' .. tid, { 54 + name = t.name .. ' Survival Suit'; 55 + desc = facDesc('survival','suit'); 56 + fab = fabReq(1, 2.2) + fab { }; 57 + tex = { 58 + plate = { 59 + id = 'starlit-suit-survival-plate'; 60 + tint = lib.color {hue = 210, sat = .5, lum = .5}; 61 + }; 62 + lining = { 63 + id = 'starlit-suit-survival-lining'; 64 + tint = lib.color {hue = 180, sat = .2, lum = .7}; 65 + }; 66 + }; 67 + tints = {'suit_plate', 'suit_lining'}; 68 + temp = { 69 + desc = facDesc('survival','cc'); 70 + maxHeat = 0.7; -- can produce a half-degree Δ per second 71 + maxCool = 0.5; 72 + heatPower = 50; -- 50W 73 + coolPower = 50/t.efficiency; 74 + insulation = 0.5; -- prevent half of heat loss 75 + }; 76 + protection = { 77 + rad = 0.7; -- blocks 70% of ionizing radiation 78 + }; 79 + slots = { 80 + canisters = 1; 81 + batteries = math.ceil(math.max(1, t.power/2)); 82 + chips = 3; 83 + guns = 0; 84 + ammo = 0; 85 + }; 86 + nano = { 87 + compileSpeed = 0.1 * t.efficiency; 88 + shredSpeed = 0.1 * t.power; 89 + fabSizeLimit = 0.6; -- 60cm 90 + }; 91 + }) 92 + 93 + starlit.item.suit.link('starlit_suit:suit_engineer_' .. tid, { 94 + name = t.name .. ' Engineer Suit'; 95 + desc = facDesc('engineer','suit'); 96 + tex = { 97 + plate = { 98 + id = 'starlit-suit-survival-plate'; 99 + tint = lib.color {hue = 0, sat = .5, lum = .7}; 100 + }; 101 + }; 102 + tints = {'suit_plate', 'suit_lining'}; 103 + fab = fabReq(.8, 7) + fab { }; 104 + temp = { 105 + desc = facDesc('engineer','cc'); 106 + maxHeat = 0; 107 + maxCool = 0.2; 108 + heatPower = 0; 109 + coolPower = 10 / t.efficiency; 110 + insulation = 0.1; -- no lining 111 + }; 112 + slots = { 113 + canisters = 2; 114 + batteries = 2; 115 + chips = 6; 116 + guns = 0; 117 + ammo = 0; 118 + }; 119 + compat = { 120 + maxBatterySize = 0.10 * t.power; -- 10cm 121 + }; 122 + protection = { 123 + rad = 0.1; -- blocks 10% of ionizing radiation 124 + }; 125 + nano = { 126 + compileSpeed = 1 * t.efficiency; 127 + shredSpeed = 0.7 * t.power; 128 + fabSizeLimit = 1.5; -- 1.5m (enables node compilation) 129 + }; 130 + }) 131 + 132 + if hasTech 'suitCombat' then 133 + starlit.item.suit.link('starlit_suit:suit_combat_' .. tid, { 134 + name = t.name .. ' Combat Suit'; 135 + desc = facDesc('combat','suit'); 136 + fab = fabReq(1.5, 14) + fab { 137 + metal = {iridium = 1e3}; 138 + }; 139 + tex = { 140 + plate = { 141 + id = 'starlit-suit-survival-plate'; 142 + tint = lib.color {hue = 0, sat = 0, lum = 0}; 143 + }; 144 + lining = { 145 + id = 'starlit-suit-survival-lining'; 146 + tint = lib.color {hue = 180, sat = .5, lum = .3}; 147 + }; 148 + }; 149 + tints = {'suit_plate', 'suit_lining'}; 150 + slots = { 151 + canisters = 1; 152 + batteries = math.ceil(math.max(3, 8*(t.power/2))); 153 + chips = 5; 154 + guns = 2; 155 + ammo = 1; 156 + }; 157 + compat = { 158 + maxBatterySize = 0.10 * t.power; -- 10cm 159 + }; 160 + temp = { 161 + desc = facDesc('combat','cc'); 162 + maxHeat = 0.3; 163 + maxCool = 0.6; 164 + heatPower = 20 / t.efficiency; 165 + coolPower = 40 / t.efficiency; 166 + insulation = 0.2; 167 + }; 168 + protection = { 169 + rad = 0.9; -- blocks 90% of ionizing radiation 170 + }; 171 + nano = { 172 + compileSpeed = 0.05; 173 + shredSpeed = 2 * t.power; 174 + fabSizeLimit = 0.3; -- 30cm 175 + }; 176 + }) 177 + end 178 +end) 179 + 180 +
Added mods/starlit-suit/mod.conf version [785651d288].
1 +name = starlit_suit 2 +title = starlit environment suit 3 +description = defines the environment suits available in starlit 4 +depends = starlit, starlit_electronics
Added mods/starlit/container.lua version [38768ee087].
1 +-- a container item defines a 'container' structure listing its 2 +-- inventories and their properties. a container object is created 3 +-- in order to interact with a container 4 +local lib = starlit.mod.lib 5 +starlit.item.container = lib.class { 6 + __name = 'starlit:container'; 7 + construct = function(stack, inv, def) 8 + local T,G = lib.marshal.t, lib.marshal.g 9 + local cdef = stack:get_definition()._starlit.container; 10 + local sd = {} 11 + for k,v in pairs(cdef.list) do 12 + sd[k] = { 13 + key = v.key; 14 + type = T.inventoryList; 15 + } 16 + end 17 + return { 18 + stack = stack, inv = inv, pdef = def, cdef = cdef; 19 + store = lib.marshal.metaStore(sd)(stack); 20 + } 21 + end; 22 + __index = { 23 + slot = function(self, id) 24 + return string.format("%s_%s", self.pdef.pfx, id) 25 + end; 26 + clear = function(self) -- initialize or empty the metadata 27 + self:update(function() 28 + for k,v in pairs(self.cdef.list) do 29 + if v.sz > 0 then 30 + self.store.write(k, {}) 31 + end 32 + end 33 + end) 34 + end; 35 + list = function(self, k) return self.store.read(k) end; 36 + read = function(self) 37 + local lst = {} 38 + for k,v in pairs(self.cdef.list) do 39 + if v.sz > 0 then lst[k] = self:list(k) end 40 + end 41 + return lst 42 + end; 43 + pull = function(self) -- align the inventories with the metadata 44 + for k,v in pairs(self.cdef.list) do 45 + if v.sz > 0 then 46 + local stacks = self:list(k) 47 + local sid = self:slot(k) 48 + self.inv:set_size(sid, v.sz) 49 + self.inv:set_list(sid, stacks) 50 + end 51 + end 52 + end; 53 + update = function(self, fn) 54 + local old = ItemStack(self.stack) 55 + if fn then fn() end 56 + if self.cdef.handle then 57 + self.cdef.handle(self.stack, old) 58 + end 59 + end; 60 + push = function(self) -- align the metadata with the inventories 61 + self:update(function() 62 + for k,v in pairs(self.cdef.list) do 63 + if v.sz > 0 then 64 + local sid = self:slot(k) 65 + local lst = self.inv:get_list(sid) 66 + self.store.write(k, lst) 67 + end 68 + end 69 + end) 70 + end; 71 + drop = function(self) -- remove the inventories from the node/entity 72 + for k,v in pairs(self.cdef.list) do 73 + local sid = self:slot(k) 74 + self.inv:set_size(sid, 0) 75 + end 76 + end; 77 + slotAccepts = function(self, lst, slot, stack) 78 + end; 79 + }; 80 +} 81 + 82 +function starlit.item.container.dropPrefix(inv, pfx) 83 + local lists = inv:get_lists() 84 + for k,v in pairs(lists) do 85 + if #k > #pfx then 86 + if string.sub(k, 1, #pfx + 1) == pfx .. '_' then 87 + inv:set_size(k, 0) 88 + end 89 + end 90 + end 91 +end
Added mods/starlit/effect.lua version [eed7790b6a].
1 +-- ported from sorcery/spell.lua, hence the lingering refs to "magic" 2 +-- 3 +-- this file is used to track active effects, for the purposes of metamagic 4 +-- like disjunction. a "effect" is a table consisting of several properties: 5 +-- a "disjoin" function that, if present, is called when the effect is 6 +-- abnormally interrupted, a "terminate" function that calls when the effect 7 +-- completes, a "duration" property specifying how long the effect lasts in 8 +-- seconds, and a "timeline" table that maps floats to functions called at 9 +-- specific points during the function's activity. it can also have a 10 +-- 'delay' property that specifies how long to wait until the effect sequence 11 +-- starts; the effect is however still vulnerable to disjunction during this 12 +-- period. there can also be a sounds table that maps timepoints to sounds 13 +-- the same way timeline does. each value should be a table of form {sound, 14 +-- where}. the `where` field may contain one of 'pos', 'caster', 'subjects', or 15 +-- a vector specifying a position in the world, and indicate where the sound 16 +-- should be played. by default 'caster' and 'subjects' sounds will be attached 17 +-- to the objects they reference; 'attach=false' can be added to prevent this. 18 +-- by default sounds will be faded out quickly when disjunction occurs; this 19 +-- can be controlled by the fade parameter. 20 +-- 21 +-- effects can have various other properties, for instance 'disjunction', which 22 +-- when true prevents other effects from being cast in its radius while it is 23 +-- still in effect. disjunction is absolute; there is no way to overwhelm it. 24 +-- 25 +-- the effect also needs at least one of "anchor", "subjects", or "caster". 26 +-- * an anchor is a position that, in combination with 'range', specifies the area 27 +-- where a effect is in effect; this is used for determining whether it 28 +-- is affected by a disjunction that incorporates part of that position 29 +-- * subjects is an array of individuals affected by the effect. when 30 +-- disjunction is cast on one of them, they will be removed from the 31 +-- table. each entry should have at least a 'player' field; they can 32 +-- also contain any other data useful to the effect. if a subject has 33 +-- a 'disjoin' field it must be a function called when they are removed 34 +-- from the list of effect targets. 35 +-- * caster is the individual who cast the effect, if any. a disjunction 36 +-- against their person will totally disrupt the effect. 37 +local log = starlit.logger 'effect' 38 +local lib = starlit.mod.lib 39 + 40 +-- FIXME saving object refs is iffy, find a better alternative 41 +starlit.effect = { 42 + active = {} 43 +} 44 + 45 +local get_effect_positions = function(effect) 46 + local effectpos 47 + if effect.anchor then 48 + effectpos = {effect.anchor} 49 + elseif effect.attach then 50 + if effect.attach == 'caster' then 51 + effectpos = {effect.caster:get_pos()} 52 + elseif effect.attach == 'subjects' or effect.attach == 'both' then 53 + if effect.attach == 'both' then 54 + effectpos = {effect.caster:get_pos()} 55 + else effectpos = {} end 56 + for _,s in pairs(effect.subjects) do 57 + effectpos[#effectpos+1] = s.player:get_pos() 58 + end 59 + else effectpos = {effect.attach:get_pos()} end 60 + else assert(false) end 61 + return effectpos 62 +end 63 + 64 +local ineffectrange = function(effect,pos,range) 65 + local effectpos = get_effect_positions(effect) 66 + 67 + for _,p in pairs(effectpos) do 68 + if vector.equals(pos,p) or 69 + (range and lib.math.vdcomp(range, pos,p)<=1) or 70 + (effect.range and lib.math.vdcomp(effect.range,p,pos)<=1) then 71 + return true 72 + end 73 + end 74 + return false 75 +end 76 + 77 +starlit.effect.probe = function(pos,range) 78 + -- this should be called before any effects are performed. 79 + -- other mods can overlay their own functions to e.g. protect areas 80 + -- from effects 81 + local result = {} 82 + 83 + -- first we need to check if any active injunctions are in effect 84 + -- injunctions are registered as effects with a 'disjunction = true' 85 + -- property 86 + for id,effect in pairs(starlit.effect.active) do 87 + if not (effect.disjunction and (effect.anchor or effect.attach)) then goto skip end 88 + if ineffectrange(effect,pos,range) then 89 + result.disjunction = true 90 + break 91 + end 92 + ::skip::end 93 + 94 + -- at some point we might also check to see if certain anti-effect 95 + -- blocks are nearby or suchlike. there could also be regions where 96 + -- perhaps certain kinds of effect are unusually empowered or weak 97 + return result 98 +end 99 +starlit.effect.disjoin = function(d) 100 + local effects,targets = {},{} 101 + if d.effect then effects = {{v=d.effect}} 102 + elseif d.target then targets = {d.target} 103 + elseif d.pos then -- find effects anchored here and people in range 104 + for id,effect in pairs(starlit.effect.active) do 105 + if not effect.anchor then goto skip end -- this intentionally excludes attached effects 106 + if ineffectrange(effect,d.pos,d.range) then 107 + effects[#effects+1] = {v=effect,i=id} 108 + end 109 + ::skip::end 110 + local ppl = minetest.get_objects_inside_radius(d.pos,d.range) 111 + if #targets == 0 then targets = ppl else 112 + for _,p in pairs(ppl) do targets[#targets+1] = p end 113 + end 114 + end 115 + 116 + -- iterate over targets to remove from any effect's influence 117 + for _,t in pairs(targets) do 118 + for id,effect in pairs(starlit.effect.active) do 119 + if effect.caster == t then effects[#effects+1] = {v=effect,i=id} else 120 + for si, sub in pairs(effect.subjects) do 121 + if sub.player == t then 122 + if sub.disjoin then sub:disjoin(effect) end 123 + effect.release_subject(si) 124 + break 125 + end 126 + end 127 + end 128 + end 129 + end 130 + 131 + -- effects to disjoin entirely 132 + for _,s in pairs(effects) do local effect = s.v 133 + if effect.disjoin then effect:disjoin() end 134 + effect.abort() 135 + if s.i then starlit.effect.active[s.i] = nil else 136 + for k,v in pairs(starlit.effect.active) do 137 + if v == effect then starlit.effect.active[k] = nil break end 138 + end 139 + end 140 + end 141 +end 142 + 143 +starlit.effect.ensorcelled = function(player,effect) 144 + if type(player) == 'string' then player = minetest.get_player_by_name(player) end 145 + for _,s in pairs(starlit.effect.active) do 146 + if effect and (s.name ~= effect) then goto skip end 147 + for _,sub in pairs(s.subjects) do 148 + if sub.player == player then return s end 149 + end 150 + ::skip::end 151 + return false 152 +end 153 + 154 +starlit.effect.each = function(player,effect) 155 + local idx = 0 156 + return function() 157 + repeat idx = idx + 1 158 + local sp = starlit.effect.active[idx] 159 + if sp == nil then return nil end 160 + if effect == nil or sp.name == effect then 161 + for _,sub in pairs(sp.subjects) do 162 + if sub.player == player then return sp end 163 + end 164 + end 165 + until idx >= #starlit.effect.active 166 + end 167 +end 168 + 169 +-- when a new effect is created, we analyze it and make the appropriate calls 170 +-- to minetest.after to queue up the events. each job returned needs to be 171 +-- saved in 'jobs' so they can be canceled if the effect is disjoined. no polling 172 +-- necessary :D 173 + 174 +starlit.effect.cast = function(proto) 175 + local s = table.copy(proto) 176 + s.jobs = s.jobs or {} s.vfx = s.vfx or {} s.sfx = s.sfx or {} 177 + s.impacts = s.impacts or {} s.subjects = s.subjects or {} 178 + s.delay = s.delay or 0 179 + s.visual = function(subj, def) 180 + s.vfx[#s.vfx + 1] = { 181 + handle = minetest.add_particlespawner(def); 182 + subject = subj; 183 + } 184 + end 185 + s.visual_caster = function(def) -- convenience function 186 + local d = table.copy(def) 187 + d.attached = s.caster 188 + s.visual(nil, d) 189 + end 190 + s.visual_subjects = function(def) 191 + for _,sub in pairs(s.subjects) do 192 + local d = table.copy(def) 193 + d.attached = sub.player 194 + s.visual(sub, d) 195 + end 196 + end 197 + s.affect = function(i) 198 + local etbl = {} 199 + for _,sub in pairs(s.subjects) do 200 + -- local eff = late.new_effect(sub.player, i) 201 + -- starlit will not be using late 202 + local rec = { 203 + effect = eff; 204 + subject = sub; 205 + } 206 + s.impacts[#s.impacts+1] = rec 207 + etbl[#etbl+1] = rec 208 + end 209 + return etbl 210 + end 211 + s.abort = function() 212 + for _,j in ipairs(s.jobs) do j:cancel() end 213 + for _,v in ipairs(s.vfx) do minetest.delete_particlespawner(v.handle) end 214 + for _,i in ipairs(s.sfx) do s.silence(i) end 215 + for _,i in ipairs(s.impacts) do i.effect:stop() end 216 + end 217 + s.release_subject = function(si) 218 + local t = s.subjects[si] 219 + for _,f in pairs(s.sfx) do if f.subject == t then s.silence(f) end end 220 + for _,f in pairs(s.impacts) do if f.subject == t then f.effect:stop() end end 221 + for _,f in pairs(s.vfx) do 222 + if f.subject == t then minetest.delete_particlespawner(f.handle) end 223 + end 224 + s.subjects[si] = nil 225 + end 226 + local interpret_timespec = function(when) 227 + if when == nil then return 0 end 228 + local t if type(when) == 'number' then 229 + t = s.duration * when 230 + else 231 + t = (s.duration * (when.whence or 0)) + (when.secs or 0) 232 + end 233 + if t then return math.min(s.duration,math.max(0,t)) end 234 + 235 + log.err('invalid timespec ' .. dump(when)) 236 + return 0 237 + end 238 + s.queue = function(when,fn) 239 + local elapsed = s.starttime and minetest.get_server_uptime() - s.starttime or 0 240 + local timepast = interpret_timespec(when) 241 + if not timepast then timepast = 0 end 242 + local timeleft = s.duration - timepast 243 + local howlong = (s.delay + timepast) - elapsed 244 + if howlong < 0 then 245 + log.err('cannot time-travel! queue() called with `when` specifying timepoint that has already passed') 246 + howlong = 0 247 + end 248 + s.jobs[#s.jobs+1] = minetest.after(howlong, function() 249 + -- this is somewhat awkward. since we're using a non-polling approach, we 250 + -- need to find a way to account for a caster or subject walking into an 251 + -- existing antimagic field, or someone with an existing antimagic aura 252 + -- walking into range of the anchor. so every time a effect effect would 253 + -- take place, we first check to see if it's in range of something nasty 254 + if not s.disjunction and -- avoid self-disjunction 255 + ((s.caster and starlit.effect.probe(s.caster:get_pos()).disjunction) or 256 + (s.anchor and starlit.effect.probe(s.anchor,s.range).disjunction)) then 257 + starlit.effect.disjoin{effect=s} 258 + else 259 + if not s.disjunction then for _,sub in pairs(s.subjects) do 260 + local sp = sub.player:get_pos() 261 + if starlit.effect.probe(sp).disjunction then 262 + starlit.effect.disjoin{pos=sp} 263 + end 264 + end end 265 + -- effect still exists and we've removed any subjects who have been 266 + -- affected by a disjunction effect, it's now time to actually perform 267 + -- the queued-up action 268 + fn(s,timepast,timeleft) 269 + end 270 + end) 271 + end 272 + s.play_now = function(spec) 273 + local specs, stbl = {}, {} 274 + local addobj = function(obj,sub) 275 + if spec.attach == false then specs[#specs+1] = { 276 + spec = { pos = obj:get_pos() }; 277 + obj = obj, subject = sub; 278 + } else specs[#specs+1] = { 279 + spec = { object = obj }; 280 + obj = obj, subject = sub; 281 + } end 282 + end 283 + 284 + if spec.where == 'caster' then addobj(s.caster) 285 + elseif spec.where == 'subjects' then 286 + for _,sub in pairs(s.subjects) do addobj(sub.player,sub) end 287 + elseif spec.where == 'pos' then specs[#specs+1] = { spec = {pos = s.anchor} } 288 + else specs[#specs+1] = { spec = {pos = spec.where} } end 289 + 290 + for _,sp in pairs(specs) do 291 + sp.spec.gain = sp.spec.gain or spec.gain 292 + local so = { 293 + handle = minetest.sound_play(spec.sound, sp.spec, spec.ephemeral); 294 + ctl = spec; 295 + -- object = sp.obj; 296 + subject = sp.subject; 297 + } 298 + stbl[#stbl+1] = so 299 + s.sfx[#s.sfx+1] = so 300 + end 301 + return stbl 302 + end 303 + s.play = function(when,spec) 304 + s.queue(when, function() 305 + local snds = s.play_now(spec) 306 + if spec.stop then 307 + s.queue(spec.stop, function() 308 + for _,snd in pairs(snds) do s.silence(snd) end 309 + end) 310 + end 311 + end) 312 + end 313 + s.silence = function(sound) 314 + if sound.ctl.fade == 0 then minetest.sound_stop(sound.handle) 315 + else minetest.sound_fade(sound.handle,sound.ctl.fade or 1,0) end 316 + end 317 + local startqueued, termqueued = false, false 318 + local myid = #starlit.effect.active+1 319 + s.cancel = function() 320 + s.abort() 321 + starlit.effect.active[myid] = nil 322 + end 323 + local perform_disjunction_calls = function() 324 + local positions = get_effect_positions(s) 325 + for _,p in pairs(positions) do 326 + starlit.effect.disjoin{pos = p, range = s.range} 327 + end 328 + end 329 + if s.timeline then 330 + for when_raw,what in pairs(s.timeline) do 331 + local when = interpret_timespec(when_raw) 332 + if s.delay == 0 and when == 0 then 333 + startqueued = true 334 + if s.disjunction then perform_disjunction_calls() end 335 + what(s,0,s.duration) 336 + elseif when_raw == 1 or when >= s.duration then -- avoid race conditions 337 + if not termqueued then 338 + termqueued = true 339 + s.queue(1,function(s,...) 340 + what(s,...) 341 + if s.terminate then s:terminate() end 342 + starlit.effect.active[myid] = nil 343 + end) 344 + else 345 + log.warn('multiple final timeline events not possible, ignoring') 346 + end 347 + elseif when == 0 and s.disjunction then 348 + startqueued = true 349 + s.queue(when_raw,function(...) 350 + perform_disjunction_calls() 351 + what(...) 352 + end) 353 + else s.queue(when_raw,what) end 354 + end 355 + end 356 + if s.intervals then 357 + for _,int in pairs(s.intervals) do 358 + local timeleft = s.duration - interpret_timespec(int.after) 359 + local iteration, itercount = 0, timeleft / int.period 360 + local function iterate(lastreturn) 361 + iteration = iteration + 1 362 + local nr = int.fn { 363 + effect = s; 364 + iteration = iteration; 365 + iterationcount = itercount; 366 + timeleft = timeleft; 367 + timeelapsed = s.duration - timeleft; 368 + lastreturn = lastreturn; 369 + } 370 + if nr ~= false and iteration < itercount then 371 + s.jobs[#s.jobs+1] = minetest.after(int.period, 372 + function() iterate(nr) end) 373 + end 374 + end 375 + if int.after 376 + then s.queue(int.after, iterate) 377 + else s.queue({whence=0, secs=s.period}, iterate) 378 + end 379 + end 380 + end 381 + if s.disjunction and not startqueued then 382 + if s.delay == 0 then perform_disjunction_calls() else 383 + s.queue(0, function() perform_disjunction_calls() end) 384 + end 385 + end 386 + if s.sounds then 387 + for when,what in pairs(s.sounds) do s.play(when,what) end 388 + end 389 + starlit.effect.active[myid] = s 390 + if not termqueued then 391 + s.jobs[#s.jobs+1] = minetest.after(s.delay + s.duration, function() 392 + if s.terminate then s:terminate() end 393 + starlit.effect.active[myid] = nil 394 + end) 395 + end 396 + s.starttime = minetest.get_server_uptime() 397 + return s 398 +end 399 + 400 +minetest.register_on_dieplayer(function(player) 401 + starlit.effect.disjoin{target=player} 402 +end)
Added mods/starlit/element.lua version [db8d1e2ad1].
1 +local lib = starlit.mod.lib 2 +local W = starlit.world 3 +local M = W.material 4 + 5 +M.element.foreach('starlit:sort', {}, function(id, m) 6 + if m.metal then 7 + M.metal.link(id, { 8 + name = m.name; 9 + composition = starlit.type.fab{element = {[id] = 1}}; 10 + color = m.color; 11 + -- n.b. this is a RATIO: it will be appropriately multiplied 12 + -- for the object in question; e.g a normal chunk will be 13 + -- 100 $element, an ingot will be 1000 $element 14 + }) 15 + elseif m.gas then 16 + M.gas.link(id, { 17 + name = m.name; 18 + composition = starlit.type.fab{element = {[id] = 1}}; 19 + }) 20 + elseif m.liquid then 21 + M.liquid.link(id, { 22 + name = m.name; 23 + composition = starlit.type.fab{element = {[id] = 1}}; 24 + }) 25 + end 26 +end) 27 + 28 +local F = string.format 29 + 30 +local function mkEltIndicator(composition) 31 + local indicator = '' 32 + local idx = 0 33 + local ccount = 0 34 + for _ in pairs(composition) do 35 + ccount = ccount + 1 36 + end 37 + local indsz,indpad = 28,4 38 + local ofs = math.min(11, (indsz-indpad)/ccount) 39 + for id, amt in pairs(composition) do 40 + idx = idx + 1 41 + indicator = indicator .. F( 42 + ':%s,3=starlit-element-%s.png', 43 + (indsz-indpad) - (idx*ofs), id 44 + ) 45 + end 46 + indicator = lib.image(indicator) 47 + return function(s) 48 + return string.format('(%s^[resize:%sx%s)^[combine:%sx%s%s', 49 + s, 50 + indsz, indsz, 51 + indsz, indsz, 52 + indicator); 53 + end 54 +end 55 + 56 +M.element.foreach('starlit:gen-forms', {}, function(id, m) 57 + local eltID = F('%s:element_%s', minetest.get_current_modname(), id) 58 + local eltName = F('Elemental %s', lib.str.capitalize(m.name)) 59 + local tt = function(t, d, g) 60 + return starlit.ui.tooltip { 61 + title = t, desc = d; 62 + color = lib.color(0.1,0.2,0.1); 63 + props = { 64 + {title = 'Mass', desc = lib.math.si('g', g), affinity='info'} 65 + } 66 + } 67 + end 68 + local comp = {[id] = 1} 69 + local iblit = mkEltIndicator(comp) 70 + m.form = m.form or {} 71 + m.form.element = eltID 72 + 73 + local powder = F('starlit-element-%s-powder.png', id); 74 + minetest.register_craftitem(eltID, { 75 + short_description = eltName; 76 + description = tt(eltName, F('Elemental %s kept in suspension by a nanide storage system, ready to be worked by a cold matter compiler', m.name), 1); 77 + inventory_image = iblit(powder); 78 + wield_image = powder; 79 + stack_max = 1000; -- 1kg 80 + groups = {element = 1, powder = 1, specialInventory = 1}; 81 + _starlit = { 82 + mass = 1; 83 + material = { 84 + kind = 'element'; 85 + element = id; 86 + }; 87 + fab = starlit.type.fab { 88 + element = comp; 89 + }; 90 + }; 91 + }); 92 +end) 93 + 94 + 95 +M.metal.foreach('starlit:gen-forms', {}, function(id, m) 96 + local baseID = F('%s:metal_%s_', minetest.get_current_modname(), id) 97 + local brickID, ingotID = baseID .. 'brick', baseID .. 'ingot' 98 + local brickName, ingotName = 99 + F('%s Brick', lib.str.capitalize(m.name)), 100 + F('%s Ingot', lib.str.capitalize(m.name)) 101 + m.form = m.form or {} 102 + m.form.brick = brickID 103 + m.form.ingot = ingotID 104 + local tt = function(t, d, g) 105 + return starlit.ui.tooltip { 106 + title = t, desc = d; 107 + color = lib.color(0.1,0.1,0.1); 108 + props = { 109 + {title = 'Mass', desc = lib.math.si('g', g), affinity='info'} 110 + } 111 + } 112 + end 113 + local mcomp = m.composition:elementalize().element 114 + local function comp(n) 115 + local t = {} 116 + for id, amt in pairs(mcomp) do 117 + t[id] = amt * n 118 + end 119 + return t 120 + end 121 + local iblit = mkEltIndicator(mcomp) 122 + local function img(s) 123 + return iblit(s:colorize(m.color):render()) 124 + end 125 + 126 + minetest.register_craftitem(brickID, { 127 + short_description = brickName; 128 + description = tt(brickName, F('A solid brick of %s, ready to be worked by a matter compiler', m.name), 100); 129 + inventory_image = img(lib.image 'starlit-item-brick.png'); 130 + wield_image = lib.image 'starlit-item-brick.png':colorize(m.color):render(); 131 + stack_max = 10; 132 + groups = {metal = 1, ingot = 1}; 133 + _starlit = { 134 + mass = 100; 135 + material = { 136 + kind = 'metal'; 137 + metal = id; 138 + }; 139 + fab = starlit.type.fab { 140 + flag = {smelt= true}; 141 + element = comp(1e2); 142 + }; 143 + }; 144 + }); 145 + 146 + minetest.register_craftitem(ingotID, { 147 + short_description = ingotName; 148 + description = tt(ingotName, F('A solid ingot of %s, ready to be worked by a large matter compiler', m.name), 1e3); 149 + inventory_image = img(lib.image('starlit-item-ingot.png')); 150 + wield_image = lib.image 'starlit-item-ingot.png':colorize(m.color):render(); 151 + groups = {metal = 1, ingot = 1}; 152 + stack_max = 5; 153 + _starlit = { 154 + mass = 1e3; 155 + material = { 156 + kind = 'metal'; 157 + metal = id; 158 + }; 159 + fab = starlit.type.fab { 160 + flag = {smelt= true}; 161 + element = comp(1e3); 162 + }; 163 + }; 164 + }); 165 + 166 + 167 +end) 168 + 169 +local function canisterDesc(stack, def) 170 + def = def or stack:get_definition()._starlit.canister 171 + local props = { 172 + {title = 'Charge Slots', affinity = 'info', desc = tostring(def.slots)}; 173 + }; 174 + if stack then 175 + local inv = starlit.item.container(stack) 176 + for i,e in ipairs(inv:list 'elem') do 177 + local comp = e:get_definition()._starlit.fab 178 + table.insert(props, { 179 + title = comp:formula(); 180 + desc = lib.math.si('g', e:get_count()); 181 + affinity = 'good'; 182 + }) 183 + end 184 + -- TODO list masses 185 + end 186 + return starlit.ui.tooltip { 187 + title = def.name, desc = def.desc or 'A canister that can store a charge of elemental powder, gas, or liquid'; 188 + color = lib.color(0.2,0.1,0.1); 189 + props = props; 190 + }; 191 +end 192 + 193 +starlit.item.canister = lib.registry.mk 'starlit:canister'; 194 +starlit.item.canister.foreach('starlit:item-gen', {}, function(id, c) 195 + minetest.register_craftitem(id, { 196 + short_description = c.name; 197 + description = canisterDesc(nil, c); 198 + inventory_image = c.image or 'starlit-item-element-canister.png'; 199 + groups = {canister = 1}; 200 + stack_max = 1; 201 + _starlit = { 202 + canister = c; 203 + container = { 204 + handle = function(stack, oldstack) 205 + stack:get_meta():set_string('description', canisterDesc(stack)) 206 + return stack 207 + end; 208 + list = { 209 + elem = { 210 + key = 'starlit:canister_elem'; 211 + accept = 'powder'; 212 + sz = c.slots; 213 + }; 214 + }; 215 + }; 216 + }; 217 + }) 218 +end)
Added mods/starlit/fab.lua version [9968a2e2d9].
1 +-- [ʞ] fab.lua 2 +-- ~ lexi hale <lexi@hale.su> 3 +-- 🄯 EUPL1.2 4 +-- ? fabrication spec class 5 +-- a type.fab supports two operators: 6 +-- 7 +-- + used for compounding recipes. that is, 8 +-- a+b = compose a new spec from the spec parts a and b. 9 +-- this is used e.g. for creating tier-based 10 +-- fabspecs. 11 +-- 12 +-- * used for determining quantities. that is, 13 +-- f*x = spec to make x instances of f 14 +-- 15 +-- new fab fields must be defined in starlit.type.fab.opClass. 16 +-- this maps a name to fn(a,b,n) -> quant, where a is the first 17 +-- argument, b is a compounding amount, and n is a quantity of 18 +-- items to produce. fields that are unnamed will be underwritten 19 + 20 +local function fQuant(a,b,n) return ((a or 0)+(b or 0))*n end 21 +local function fFac (a,b,n) 22 + if a == nil and b == nil then return nil end 23 + local f if a == nil or b == nil then 24 + f = a or b 25 + else 26 + f = (a or 1)*(b or 1) 27 + end 28 + return f*n 29 +end 30 +local function fReq (a,b,n) return a or b end 31 +local function fFlag (a,b,n) return a and b end 32 +local function fSize (a,b,n) return math.max(a,b) end 33 +local opClass = { 34 + -- fabrication eligibility will be determined by which kinds 35 + -- of input a particular fabricator can introduce. e.g. a 36 + -- printer with a but no cache can only print items whose 37 + -- recipe only names elements as ingredients 38 + 39 + -- ingredients 40 + element = fQuant; -- (g) 41 + gas = fQuant; -- () 42 + liquid = fQuant; -- (l) 43 + crystal = fQuant; -- (g) 44 + item = fQuant; -- n 45 + metal = fQuant; -- (g) 46 + metalIngot = fQuant; -- (g) 47 + -- factors 48 + cost = fFac; -- units vary 49 + time = fFac; -- (s) 50 + -- print: base printing time 51 + size = fSize; 52 + -- printBay: size of the printer bay necessary to produce the item 53 + req = fReq; 54 + flag = fFlag; -- means that can be used to produce the item & misc flags 55 + -- print: allow production with a printer 56 + -- smelt: allow production with a smelter 57 + -- all else defaults to underwrite 58 +} 59 + 60 +local F = string.format 61 +local strClass = { 62 + element = function(x, n) 63 + local el = starlit.world.material.element[x] 64 + return lib.math.si('g', n) .. ' ' .. (el.sym or el.name) 65 + end; 66 + metal = function(x, n) 67 + local met = starlit.world.material.metal[x] 68 + return lib.math.si('g', n) .. ' ' .. met.name 69 + end; 70 + liquid = function(x, n) 71 + local liq = starlit.world.material.liquid[x] 72 + return lib.math.si('L', n) .. ' ' .. liq.name 73 + end; 74 + gas = function(x, n) 75 + local gas = starlit.world.material.gas[x] 76 + return lib.math.si('g', n) .. ' ' .. gas.name 77 + end; 78 + item = function(x, n) 79 + local i = minetest.registered_items[x] 80 + return tostring(n) .. 'x ' .. i.short_description 81 + end; 82 +} 83 + 84 +local order = { 85 + 'element', 'metal', 'liquid', 'gas', 'item' 86 +} 87 + 88 +local lib = starlit.mod.lib 89 +local fab fab = lib.class { 90 + __name = 'starlit:fab'; 91 + 92 + opClass = opClass; 93 + strClass = strClass; 94 + order = order; 95 + construct = function(q) return q end; 96 + __index = { 97 + elementalize = function(self) 98 + local e = fab {element = self.element or {}} 99 + for _, kind in pairs {'metal', 'gas', 'liquid'} do 100 + for m,mass in pairs(self[kind] or {}) do 101 + local mc = starlit.world.material[kind][m].composition 102 + e = e + mc:elementalize()*mass 103 + end 104 + end 105 + return e 106 + end; 107 + 108 + elementSeq = function(self) 109 + local el = {} 110 + local em = self.element 111 + local s = 0 112 + local eldb = starlit.world.material.element.db 113 + for k in pairs(em) do table.insert(el, k) s=s+eldb[k].n end 114 + table.sort(el, function(a,b) 115 + return eldb[a].n > eldb[b].n 116 + end) 117 + return el, em, s 118 + end; 119 + 120 + formula = function(self) 121 + print('make formula', dump(self)) 122 + local ts,f=0 123 + if self.element then 124 + f = {} 125 + local el, em, s = self:elementSeq() 126 + local eldb = starlit.world.material.element.db 127 + for i, e in ipairs(el) do 128 + local sym, n = eldb[e].sym, em[e] 129 + if n > 0 then 130 + table.insert(f, string.format("%s%s", 131 + sym, n>1 and lib.str.nIdx(n) or '')) 132 + end 133 + end 134 + f = table.concat(f) 135 + ts = ts + s 136 + end 137 + 138 + local sub = {} 139 + for _, w in pairs {'metal', 'gas', 'liquid'} do 140 + if self[w] then 141 + local mdb = starlit.world.material[w].db 142 + for k, amt in pairs(self[w]) do 143 + local mf, s = mdb[k].composition:formula() 144 + if amt > 0 then table.insert(sub, { 145 + f = string.format("(%s)%s",mf, 146 + lib.str.nIdx(amt)); 147 + s = s; 148 + }) end 149 + ts = ts + s*amt 150 + end 151 + end 152 + end 153 + table.sort(sub, function(a,b) return a.s > b.s end) 154 + local fml = {} 155 + for i, v in ipairs(sub) do fml[i] = v.f end 156 + if f then table.insert(fml, f) end 157 + fml = table.concat(fml, ' + ') 158 + 159 + return fml, ts 160 + end; 161 + }; 162 + 163 + __tostring = function(self) 164 + local t = {} 165 + for i,o in ipairs(order) do 166 + if self[o] then 167 + for mat,amt in pairs(self[o]) do 168 + if amt > 0 then 169 + table.insert(t, strClass[o](mat, amt)) 170 + end 171 + end 172 + end 173 + end 174 + return table.concat(t, ", ") 175 + end; 176 + 177 + 178 + __add = function(a,b) 179 + local new = fab {} 180 + for cat, vals in pairs(a) do 181 + new[cat] = lib.tbl.copy(vals) 182 + end 183 + for cat, vals in pairs(b) do 184 + if not new[cat] then 185 + new[cat] = lib.tbl.copy(vals) 186 + else 187 + local f = opClass[cat] 188 + for k,v in pairs(vals) do 189 + local n = f(new[cat][k], v, 1) 190 + new[cat][k] = n > 0 and n or nil 191 + end 192 + end 193 + end 194 + return new 195 + end; 196 + 197 + __mul = function(x,n) 198 + local new = fab {} 199 + for cat, vals in pairs(x) do 200 + new[cat] = {} 201 + local f = opClass[cat] 202 + for k,v in pairs(vals) do 203 + local num = f(v,nil,n) 204 + new[cat][k] = num > 0 and num or nil 205 + end 206 + end 207 + return new 208 + end; 209 + 210 + __div = function(x,n) 211 + return x * (1/n) 212 + end; 213 +} 214 + 215 +starlit.type.fab = fab
Added mods/starlit/fx/nano.lua version [85f63bf07b].
1 +local lib = starlit.mod.lib 2 +local E = starlit.effect 3 +local N = {} 4 +starlit.fx.nano = N 5 +local nanopool= { 6 + { 7 + name = 'starlit-fx-nano-spark-small.png'; 8 + scale_tween = {0,.5, style = 'pulse', rep = 3}; 9 + }; 10 + { 11 + name = 'starlit-fx-nano-spark-small.png'; 12 + scale_tween = {0,1, style = 'pulse', rep = 2}; 13 + }; 14 + { 15 + name = 'starlit-fx-nano-spark-big.png'; 16 + scale_tween = {0,1, style = 'pulse'}; 17 + }; 18 +} 19 + 20 +function N.heal(user, targets, amt, dur) 21 + local amthealed = {} 22 + local f = E.cast { 23 + caster = user.entity; 24 + subjects = targets; 25 + duration = dur; 26 + intervals = { 27 + { 28 + after = 0; 29 + period = 4; 30 + fn = function(c) 31 + for i,v in pairs(c.effect.subjects) do 32 + local u = starlit.activeUsers[v.player:get_player_name()] 33 + if u then 34 + local heal = math.max(amt/4, 1) 35 + amthealed[u] = amthealed[u] or 0 36 + if amthealed[u] < amt then 37 + amthealed[u] = amthealed[u] + heal 38 + u:statDelta('health', heal) 39 + end 40 + end 41 + end 42 + end; 43 + } 44 + } 45 + } 46 + 47 + local casterIsTarget = false 48 + for _, sub in pairs(f.subjects) do 49 + if sub.player == user.entity then 50 + casterIsTarget = true 51 + end 52 + f.visual(sub, { 53 + amount = 50; 54 + time = dur; 55 + glow = 14; 56 + jitter = 0.01; 57 + attached = user.entity; 58 + vel = { min = -0.1, max = 0.1; }; 59 + pos = { 60 + min = vector.new(0,0.2,0); 61 + max = vector.new(0,1.2,0); 62 + }; 63 + radius = { min = 0.2; max = 0.6; bias = -1; }; 64 + exptime = {min=0.5,max=2}; 65 + attract = { 66 + kind = 'line'; 67 + strength = {min = 0.5, max = 2}; 68 + origin = 0; 69 + direction = vector.new(0,1,0); 70 + origin_attached = sub.player; 71 + direction_attached = sub.player; 72 + }; 73 + 74 + texpool = nanopool; 75 + }) 76 + end 77 + if not casterIsTarget then 78 + -- f.visual_caster { } 79 + end 80 + f.play(0.3, { 81 + where = 'subjects'; 82 + sound = 'starlit-nano-heal'; 83 + ephemeral = true; 84 + spec = {gain = 0.3}; 85 + }) 86 + 87 + return f 88 +end 89 + 90 +function N.shred(user, pos, prop, time, node) 91 + local f = E.cast { 92 + caster = user.entity; 93 + subjects = {}; 94 + duration = time; 95 + } 96 + local sp,sv = user:lookupSpecies() 97 + local eh = sv.eyeHeight or sp.eyeHeight 98 + f.visual_caster { 99 + amount = 200 * time; 100 + pos = vector.new(0.12,eh - 0.1,0); 101 + radius = 0.2; 102 + time = time - (time/3); 103 + glow = 14; 104 + jitter = 0.1; 105 + size = {min = 0.2, max = 0.5}; 106 + exptime = {min=0.5,max=1}; 107 + vel_tween = { 108 + 0; 109 + { min = -0.4, max = 0.4; }; 110 + style = 'pulse', rep = time * 2; 111 + }; 112 + attract = { 113 + kind = 'point'; 114 + origin = pos; 115 + radius = 0.5; 116 + strength = {min=.3,max=2}; 117 + }; 118 + texpool = nanopool; 119 + }; 120 + f.queue(0.05, function(s, timepast, timeleft) 121 + f.visual(nil, { 122 + amount = timeleft * 40; 123 + time = timeleft; 124 + pos = pos; 125 + size_tween = { 126 + 0, {min = 0.5, max = 2}; 127 + }; 128 + vel = { 129 + min = vector.new(-1.2,0.5,-1.2); 130 + max = vector.new(1.2,3.5,1.2); 131 + }; 132 + acc = vector.new(0,-starlit.world.planet.gravity,0); 133 + node = node; 134 + }) 135 + end); 136 + f.queue(0.9, function(s, timepast, timeleft) 137 + f.visual(nil, { 138 + amount = 200; 139 + time = timeleft; 140 + pos = pos; 141 + size = {min = 0.1, max = 0.3}; 142 + vel = { 143 + min = vector.new(-2,0.5,-2); 144 + max = vector.new(2,4,2); 145 + }; 146 + acc = vector.new(0,-starlit.world.planet.gravity,0); 147 + node = node; 148 + }) 149 + end); 150 + f.queue(0.3, function(s, timepast, timeleft) 151 + local function v(fn) 152 + local def = { 153 + amount = timeleft * 100; 154 + pos = pos; 155 + time = timeleft; 156 + radius = 0.5; 157 + jitter = {min = 0.0, max = 0.2}; 158 + size = {min = 0.2, max = 0.5}; 159 + exptime = {min = 0.5, max = 1}; 160 + attract = { 161 + kind = 'point'; 162 + strength = {min=0.3, max = 1}; 163 + origin = vector.new(0,eh-0.1,0); 164 + radius = 0.5; 165 + origin_attached = user.entity; 166 + }; 167 + } 168 + fn(def) 169 + f.visual(nil, def) 170 + end 171 + v(function(t) t.texpool = nanopool t.glow = 14 end) 172 + v(function(t) 173 + t.node = node 174 + t.amount = timeleft * 20 175 + t.size = {min = 0.1, max = 0.3}; 176 + end) 177 + end) 178 + return f 179 + 180 +end
Added mods/starlit/init.lua version [1f09b99871].
1 +-- [ʞ] starlit/init.lua 2 +-- ~ lexi hale <lexi@hale.su> 3 +-- ? basic setup, game rules, terrain 4 +-- © EUPL v1.2 5 + 6 +local T = minetest.get_translator 'starlit' 7 + 8 +-- TODO enforce latest engine version 9 + 10 +local mod = { 11 + -- subordinate mods register here 12 + lib = vtlib; 13 + -- vtlib should be accessed as starlit.mod.lib by starlit modules for the sake of proper encapsulation. vtlib should simply be a provider, not a hardcoded dependency 14 +} 15 +local lib = mod.lib 16 + 17 + 18 +starlit = { 19 + ident = minetest.get_current_modname(); 20 + mod = mod; 21 + translator = T; 22 + 23 + constant = { 24 + light = { --minetest units 25 + dim = 3; 26 + lamp = 7; 27 + bright = 10; 28 + brightest = 14; -- only sun and growlights 29 + }; 30 + heat = { -- celsius 31 + freezing = 0; 32 + safe = 4; 33 + overheat = 32; 34 + boiling = 100; 35 + }; 36 + rad = { 37 + }; 38 + }; 39 + 40 + activeUsers = { 41 + -- map of username -> user object 42 + }; 43 + activeUI = { 44 + -- map of username -> UI context 45 + }; 46 + liveUI = { 47 + -- cached subset of activeUI containing those UIs needing live updates 48 + }; 49 + 50 + interface = lib.registry.mk 'starlit:interface'; 51 + item = { 52 + }; 53 + 54 + region = { 55 + radiator = { 56 + store = AreaStore(); 57 + emitters = {} 58 + }; 59 + }; 60 + 61 + -- standardized effects 62 + fx = {}; 63 + 64 + type = {}; 65 + world = { 66 + defaultScenario = 'starlit_scenario:imperialExpat'; 67 + seedbank = lib.math.seedbank(minetest.get_mapgen_setting 'seed'); 68 + mineral = lib.registry.mk 'starlit:mineral'; 69 + material = { -- raw materials 70 + element = lib.registry.mk 'starlit:element'; 71 + -- elements are automatically sorted into the following categories 72 + -- if they match. however, it's possible to have a metal/gas/liquid 73 + -- that *isn't* a pure element, so these need separate registries 74 + -- for alloys and mixtures like steel and water 75 + metal = lib.registry.mk 'starlit:metal'; 76 + gas = lib.registry.mk 'starlit:gas'; 77 + liquid = lib.registry.mk 'starlit:liquid'; 78 + }; 79 + ecology = { 80 + plants = lib.registry.mk 'starlit:plants'; 81 + trees = lib.registry.mk 'starlit:trees'; 82 + biomes = lib.registry.mk 'starlit:biome'; 83 + }; 84 + climate = {}; 85 + scenario = {}; 86 + planet = { 87 + gravity = 7.44; 88 + orbit = 189; -- 1 year is 189 days 89 + revolve = 20; -- 1 day is 20 irl minutes 90 + }; 91 + fact = lib.registry.mk 'starlit:fact'; 92 + time = { 93 + calendar = { 94 + empire = { 95 + name = 'Imperial Regnal Calendar'; 96 + year = function(t, long) 97 + local reigns = { 98 + -- if anyone actually makes it to his Honor & Glory Unfailing Persigan I i will be 99 + -- exceptionally flattered 100 + {4, 'Emperor', 'Atavarka', 'the Bold'}; -- died at war 101 + {9, 'Emperor', 'Vatikserka', 'the Unconquered'}; -- died at war 102 + {22, 'Emperor', 'Rusifend', 'the Wise'}; -- poisoned at diplomacy 103 + {61, 'Empress', 'Tafseshendi', 'the Great'}; -- died of an 'insurrection of the innards' after a celebrated reign 104 + {291, 'Emperor', 'Treptebaska', 'the Unwise'}; -- murdered by his wife in short order 105 + {292, 'Empress', 'Vilintalti', 'the Impious'}; -- removed by the praetorian elite 106 + {298, 'Emperor', 'Radavan', 'the Reckless'}; -- died at war 107 + {316, 'Emperor', 'Suldibrand', 'the Forsaken of Men'}; -- fucked around. found out. 108 + {320, 'Emperor', 'Persigan', 'the Deathless'}; 109 + } 110 + local year, r = math.floor(t / 414) 111 + for i=1, #reigns do if reigns[i+1][1] < year then r = reigns[i+1] end end 112 + local reignBegin, title, name, epithet = lib.tbl.unpack(r) 113 + local ry = 1 + (year - reignBegin) 114 + return long and string.format('Year %s of the Reign of HH&GU %s %s %s', 115 + ry, title, name, epithet) or string.format('Y. %s %s', name, ry) 116 + end; 117 + time = function(t, long) 118 + local bellsInDay, candleSpansInBell = 5, 7 119 + local bell = bellsInDay*t 120 + local cspan = (bellsInDay*candleSpansInBell*t) % candleSpansInBell 121 + return string.format(long and 'Bell %s, Candlespan %s' or '%sb %sc', math.floor(bell), math.floor(cspan)) 122 + end; 123 + }; 124 + commune = { 125 + name = 'People\'s Calendar'; 126 + date = function(t, long) 127 + local year = math.floor(t / 256) + 314 128 + return string.format(long and 'Foundation %s' or 'F:%s', year) 129 + end; 130 + time = function(t, long) 131 + local hoursInDay, minutesInHour = 16, 16 132 + local hour = hoursInDay*t 133 + local min = (hoursInDay*minutesInHour*t) % minutesInHour 134 + 135 + local dawn = 0.24*hoursInDay 136 + local noon = 0.5*hoursInDay 137 + local dusk = 0.76*hoursInDay 138 + local midnight = 1.0*hoursInDay 139 + 140 + local tl, str 141 + if hour < dawn then 142 + tl = dawn - hour 143 + str = long and 'dawn' or 'D' 144 + elseif hour < noon then 145 + tl = noon - hour 146 + str = long and 'noon' or 'N' 147 + elseif hour < dusk then 148 + tl = dusk - hour 149 + str = long and 'dusk' or 'd' 150 + elseif hour < midnight then 151 + tl = midnight - hour 152 + str = long and 'midnight' or 'M' 153 + end 154 + return long 155 + and string.format('%s hours, %s minutes to %s', 156 + math.floor(tl), math.floor(minutesInHour - min), str) 157 + or string.format('%s.%sH.%sM', str, math.floor(tl), 158 + math.floor(minutesInHour - min)) 159 + end; 160 + }; 161 + }; 162 + }; 163 + }; 164 + 165 + jobs = {}; 166 +} 167 + 168 +starlit.cfgDir = minetest.get_worldpath() .. '/' .. starlit.ident 169 + 170 +local logger = function(module) 171 + local function argjoin(arg, nxt, ...) 172 + if arg and not nxt then return tostring(arg) end 173 + if not arg then return "(nil)" end 174 + return tostring(arg) .. ' ' .. argjoin(nxt, ...) 175 + end 176 + local lg = {} 177 + local setup = function(fn, lvl) 178 + lvl = lvl or fn 179 + local function emit(...) 180 + local call = (fn == 'fatal') and error 181 + or function(str) minetest.log(lvl, str) end 182 + if module 183 + then call(string.format('[%s :: %s] %s',starlit.ident,module,argjoin(...))) 184 + else call(string.format('[%s] %s',starlit.ident,argjoin(...))) 185 + end 186 + end 187 + lg[fn ] = function(...) emit(...) end 188 + lg[fn .. 'f'] = function(...) emit(string.format(...)) end -- convenience fn 189 + end 190 + setup('info') 191 + setup('warn','warning') 192 + setup('err','error') 193 + setup('act','action') 194 + setup('fatal') 195 + return lg 196 +end 197 + 198 +starlit.logger = logger 199 + 200 +local log = logger() 201 + 202 +function starlit.evaluate(name, ...) 203 + local path = minetest.get_modpath(minetest.get_current_modname()) 204 + local filename = string.format('%s/%s', path, name) 205 + log.info('loading', filename) 206 + local chunk, err = loadfile(filename, filename) 207 + if not chunk then error(err) end 208 + return chunk(...) 209 +end 210 + 211 +function starlit.include(name, ...) -- semantic variant used for loading modules 212 + return starlit.evaluate(name..'.lua', ...) 213 +end 214 + 215 +minetest.register_lbm { 216 + label = 'build radiator index'; 217 + name = 'starlit:loadradiatorboxes'; 218 + nodenames = {'group:radiator'}; 219 + run_at_every_load = true; 220 + action = function(pos, node, dt) 221 + local R = starlit.region 222 + local phash = minetest.hash_node_position(pos) 223 + if R.radiator.sources[phash] then return end -- already loaded 224 + 225 + local def = minetest.registered_nodes[node.name] 226 + local cl = def._starlit.radiator 227 + local min,max = cl.maxEffectArea(pos) 228 + local id = R.radiator.store:insert_area(min,max, minetest.pos_to_string(pos)) 229 + R.radiator.sources[phash] = id 230 + end; 231 + -- NOTE: temp emitter nodes are responsible for decaching themselves in their on_destruct cb 232 +} 233 + 234 +function starlit.startJob(id, interval, job) 235 + local lastRun 236 + local function start() 237 + starlit.jobs[id] = minetest.after(interval, function() 238 + local t = minetest.get_gametime() 239 + local d = lastRun and t - lastRun or nil 240 + lastRun = t 241 + local continue = job(d, interval) 242 + if continue == true or continue == nil then 243 + start() 244 + elseif continue ~= false then 245 + interval = continue 246 + start() 247 + end 248 + end) 249 + end 250 + start() 251 +end 252 + 253 +starlit.include 'stats' 254 +starlit.include 'world' 255 +starlit.include 'fab' 256 +starlit.include 'tiers' 257 +starlit.include 'species' 258 + 259 +starlit.include 'store' 260 + 261 +starlit.include 'ui' 262 +starlit.include 'item' 263 +starlit.include 'container' 264 +starlit.include 'user' 265 +starlit.include 'effect' 266 + 267 +starlit.include 'fx/nano' 268 + 269 +starlit.include 'element' 270 + 271 +starlit.include 'terrain' 272 +starlit.include 'interfaces' 273 +starlit.include 'suit' 274 + 275 +minetest.settings:set('movement_gravity', starlit.world.planet.gravity) -- ??? seriously??? 276 + 277 +--------------- 278 +-- callbacks -- 279 +--------------- 280 +-- here we connect our types up to the minetest API 281 + 282 +local function userCB(fn) 283 + return function(luser, ...) 284 + local name = luser:get_player_name() 285 + local user = starlit.activeUsers[name] 286 + return fn(user, ...) 287 + end 288 +end 289 + 290 +minetest.register_on_joinplayer(function(luser, lastLogin) 291 + -- TODO check that necessary CSMs are installed 292 + local user = starlit.type.user(luser) 293 + 294 + if lastLogin == nil then 295 + user:onSignup() 296 + end 297 + user:onJoin() 298 + 299 + starlit.activeUsers[user.name] = user 300 +end) 301 + 302 +minetest.register_on_leaveplayer(function(luser) 303 + starlit.activeUsers[luser:get_player_name()]:onPart() 304 +end) 305 + 306 +minetest.register_on_player_receive_fields(function(luser, formid, fields) 307 + local name = luser:get_player_name() 308 + local user = starlit.activeUsers[name] 309 + if not user then return false end 310 + if formid == '' then -- main menu 311 + return starlit.ui.userMenuDispatch(user,fields) 312 + end 313 + local ui = starlit.interface.db[formid] 314 + local state = starlit.activeUI[name] or {} 315 + if formid == '__builtin:help_cmds' 316 + or formid == '__builtin:help_privs' 317 + then return false end 318 + assert(state.form == formid) -- sanity check 319 + user:onRespond(ui, state, fields) 320 + if fields.quit then 321 + starlit.activeUI[name] = nil 322 + end 323 + return true 324 +end) 325 + 326 +minetest.register_on_respawnplayer(userCB(function(user) 327 + return user:onRespawn() 328 +end)) 329 + 330 +minetest.register_on_dieplayer(userCB(function(user, reason) 331 + return user:onDie(reason) 332 +end)) 333 + 334 +minetest.register_on_punchnode(function(pos,node,puncher,point) 335 + local user = starlit.activeUsers[puncher:get_player_name()] 336 + local oldTgt = user.action.tgt 337 + user.action.tgt = point 338 + if bit.band(user.action.bits, 0x80)==0 then 339 + user.action.bits = bit.bor(user.action.bits, 0x80) 340 + --user:trigger('primary', {state = 'init'}) 341 + else 342 + user:trigger('retarget', {oldTgt = oldTgt}) 343 + end 344 +end) 345 + 346 +local function pointChanged(a,b) 347 + return a.type ~= b.type 348 + or a.type == 'node' and vector.new(a.under) ~= vector.new(b.under) 349 + or a.type == 'object' and a.ref ~= b.ref 350 +end 351 +local function triggerPower(_, luser, point) 352 + local user = starlit.activeUsers[luser:get_player_name()] 353 + local oldTgt = user.action.tgt 354 + user.action.tgt = point 355 + if bit.band(user.action.bits, 0x100)==0 then 356 + user.action.bits = bit.bor(user.action.bits, 0x100) 357 + --return user:trigger('secondary', {state = 'prog', delta = 0}) 358 + elseif pointChanged(oldTgt, point) then 359 + user:trigger('retarget', {oldTgt = oldTgt}) 360 + end 361 +end 362 +-- sigh 363 +core.noneitemdef_default.on_place = function(...) 364 + if not triggerPower(...) then 365 + minetest.item_place(...) 366 + end 367 +end 368 +core.noneitemdef_default.on_use = function(...) triggerPower(...) end 369 +core.noneitemdef_default.on_secondary_use = function(...) triggerPower(...) end 370 + 371 +minetest.register_on_player_inventory_action(function(luser, act, inv, p) 372 + local name = luser:get_player_name() 373 + local user = starlit.activeUsers[name] 374 + -- allow UIs to update on UI changes 375 + local state = starlit.activeUI[name] 376 + if state then 377 + local ui = starlit.interface.db[state.form] 378 + ui:cb('onMoveItem', user, act, inv, p) 379 + end 380 +end) 381 + 382 +minetest.register_on_player_hpchange(function(luser, delta, cause) 383 + local user = starlit.activeUsers[luser:get_player_name()] 384 + if cause.type == 'fall' then 385 + delta = user:damageModifier('bluntForceTrauma', (delta * 50)) 386 + -- justification: a short fall can do around 387 + -- five points of damage, which is nearly 50% 388 + -- of the default hp_max. since we crank up 389 + -- hp by a factor of 50~40, damage should be 390 + -- cranked by similarly 391 + end 392 + return delta 393 +end, true) 394 + 395 +function minetest.handle_node_drops(pos, drops, digger) 396 + local function jitter(pos) 397 + local function r(x) return x+math.random(-0.2, 0.2) end 398 + return vector.new( 399 + r(pos.x), 400 + r(pos.y), 401 + r(pos.z) 402 + ) 403 + end 404 + for i, it in ipairs(drops) do 405 + minetest.add_item(jitter(pos), it) 406 + end 407 +end 408 + 409 + 410 +-- TODO timer iterates live UI 411 +
Added mods/starlit/interfaces.lua version [1cb802f20f].
1 +local lib = starlit.mod.lib 2 + 3 +function starlit.ui.setupForUser(user) 4 + local function cmode(mode) 5 + if user.actMode == mode then return {hue = 150, sat = 0, lum = .3} end 6 + end 7 + user.entity:set_inventory_formspec(starlit.ui.build { 8 + kind = 'vert', mode = 'sw'; 9 + padding = .5, spacing = 0.1; 10 + {kind = 'hztl'; 11 + {kind = 'contact', w=1.5,h=1.5, id = 'mode_nano', 12 + img='starlit-ui-icon-nano.png', close=true, color = cmode'nano'}; 13 + {kind = 'contact', w=1.5,h=1.5, id = 'mode_weapon', 14 + img='starlit-ui-icon-weapon.png', close=true, color = cmode'weapon'}; 15 + {kind = 'contact', w=1.5,h=1.5, id = 'mode_psi', 16 + img='starlit-ui-icon-psi.png', close=true, color = cmode'psi'}; 17 + }; 18 + {kind = 'hztl'; 19 + {kind = 'contact', w=1.5,h=1.5, id = 'open_elements', 20 + img='starlit-ui-icon-element.png'}; 21 + {kind = 'contact', w=1.5,h=1.5, id = 'open_suit', 22 + img='starlit-item-suit.png^[hsl:200:-.7:0'}; 23 + {kind = 'contact', w=1.5,h=1.5, id = 'open_psi', 24 + img='starlit-ui-icon-psi-cfg.png'}; 25 + {kind = 'contact', w=1.5,h=1.5, id = 'open_body', 26 + img='starlit-ui-icon-self.png'}; 27 + }; 28 + {kind = 'list'; 29 + target = 'current_player', inv = 'main'; 30 + w = 6, h = 1, spacing = 0.1; 31 + }; 32 + }) 33 +end 34 + 35 +function starlit.ui.userMenuDispatch(user, fields) 36 + local function setSuitMode(mode) 37 + if user.actMode == mode then 38 + user:actModeSet 'off' 39 + else 40 + user:actModeSet(mode) 41 + end 42 + end 43 + 44 + local modes = { nano = true, psi = false, weapon = true } 45 + for e,s in pairs(modes) do 46 + if fields['mode_' .. e] then 47 + if s and (user:naked() or user:getSuit():powerState() == 'off') then 48 + user:suitSound 'starlit-error' 49 + else 50 + setSuitMode(e) 51 + end 52 + return true 53 + end 54 + end 55 + 56 + if fields.open_elements then 57 + user:openUI('starlit:user-menu', 'compiler') 58 + return true 59 + elseif fields.open_psi then 60 + user:openUI('starlit:user-menu', 'psi') 61 + return true 62 + elseif fields.open_suit then 63 + if not user:naked() then 64 + user:openUI('starlit:user-menu', 'suit') 65 + end 66 + return true 67 + elseif fields.open_body then 68 + user:openUI('starlit:user-menu', 'body') 69 + end 70 + return false 71 +end 72 + 73 +local function listWrap(n, max) 74 + local h = math.ceil(n / max) 75 + local w = math.min(max, n) 76 + return w, h 77 +end 78 + 79 +local function wrapMenu(w, h, rh, max, l) 80 + local root = {kind = 'vert', w=w, h=h} 81 + local bar 82 + local function flush() 83 + if bar and bar[1] then table.insert(root, bar) end 84 + bar = {kind = 'hztl'} 85 + end 86 + flush() 87 + 88 + for _, i in ipairs(l) do 89 + local bw = w/max 90 + if i.cfg then w = w - rh end 91 + 92 + table.insert(bar, { 93 + kind = 'button', close = i.close; 94 + color = i.color; 95 + fg = i.fg; 96 + label = i.label; 97 + icon = i.img; 98 + id = i.id; 99 + w = bw, h = rh; 100 + }) 101 + if i.cfg then 102 + table.insert(bar, { 103 + kind = 'button'; 104 + color = i.color; 105 + fg = i.fg; 106 + label = "CFG"; 107 + icon = i.img; 108 + id = i.id .. '_cfg'; 109 + w = rh, h = rh; 110 + }) 111 + end 112 + 113 + if bar[max] then flush() end 114 + end 115 + flush() 116 + 117 + return root 118 +end 119 + 120 +local function abilityMenu(a) 121 + -- select primary/secondary abilities or activate ritual abilities 122 + local p = {kind = 'vert'} 123 + for _, o in ipairs(a.order) do 124 + local m = a.menu[o] 125 + table.insert(p, {kind='label', text=m.label, w=a.w, h = .5}) 126 + table.insert(p, wrapMenu(a.w, a.h, 1.2, 2, m.opts)) 127 + end 128 + return p 129 +end 130 + 131 +local function pptrMatch(a,b) 132 + if a == nil or b == nil then return false end 133 + return a.chipID == b.chipID and a.pgmIndex == b.pgmIndex 134 +end 135 + 136 +starlit.interface.install(starlit.type.ui { 137 + id = 'starlit:user-menu'; 138 + pages = { 139 + compiler = { 140 + setupState = function(state, user) 141 + -- nanotech/suit software menu 142 + local chips = user.entity:get_inventory():get_list 'starlit_suit_chips' -- FIXME need better subinv api 143 + local sw = starlit.mod.electronics.chip.usableSoftware(chips) 144 + state.suitSW = {} 145 + local dedup = {} 146 + for i, r in ipairs(sw) do if 147 + r.sw.kind == 'suitPower' 148 + then 149 + if not dedup[r.sw] then 150 + dedup[r.sw] = true 151 + table.insert(state.suitSW, r) 152 + end 153 + end end 154 + end; 155 + handle = function(state, user, act) 156 + if user:getSuit():powerState() == 'off' then return false end 157 + local pgm, cfg 158 + for k in next, act do 159 + local id, mode = k:match('^suit_pgm_([0-9]+)_(.*)$') 160 + if id then 161 + id = tonumber(id) 162 + if state.suitSW[id] then 163 + pgm = state.suitSW[id] 164 + cfg = mode == '_cfg' 165 + break 166 + end 167 + end 168 + end 169 + if not pgm then return false end -- HAX 170 + 171 + -- kind=active programs must be assigned to a command slot 172 + -- kind=direct programs must open their UI 173 + -- kind=passive programs must toggle on and off 174 + if pgm.sw.powerKind == 'active' then 175 + if cfg then 176 + user:openUI(pgm.sw.ui, 'index', { 177 + context = 'suit'; 178 + program = pgm; 179 + }) 180 + return false 181 + end 182 + local ptr = {chipID = starlit.mod.electronics.chip.read(pgm.chip).uuid, pgmIndex = pgm.fd.inode} 183 + local pnan = user.power.nano 184 + if pnan.primary == nil then 185 + pnan.primary = ptr 186 + elseif pptrMatch(ptr, pnan.primary) then 187 + pnan.primary = nil 188 + elseif pptrMatch(ptr, pnan.secondary) then 189 + pnan.secondary = nil 190 + else 191 + pnan.secondary = ptr 192 + end 193 + user:suitSound 'starlit-configure' 194 + elseif pgm.sw.powerKind == 'direct' then 195 + local ctx = { 196 + context = 'suit'; 197 + program = pgm; 198 + } 199 + if pgm.sw.ui then 200 + user:openUI(pgm.sw.ui, 'index', ctx) 201 + return false 202 + else 203 + pgm.sw.run(user, ctx) 204 + end 205 + elseif pgm.sw.powerKind == 'passive' then 206 + if cfg then 207 + user:openUI(pgm.sw.ui, 'index', { 208 + context = 'suit'; 209 + program = pgm; 210 + }) 211 + return false 212 + end 213 + 214 + local addDisableRec = true 215 + for i, e in ipairs(pgm.file.body.conf) do 216 + if e.key == 'disable' and e.value == 'yes' then 217 + addDisableRec = false 218 + table.remove(pgm.file.body.conf, i) 219 + break 220 + elseif e.key == 'disable' and e.value == 'no' then 221 + e.value = 'yes' 222 + addDisableRec = false 223 + break 224 + end 225 + end 226 + if addDisableRec then 227 + table.insert(pgm.file.body.conf, {key='disable',value='yes'}) 228 + end 229 + -- update the chip *wince* 230 + pgm.fd:write(pgm.file) 231 + user.entity:get_inventory():set_stack('starlit_suit_chips', 232 + pgm.chipSlot, pgm.chip) 233 + user:reconfigureSuit() 234 + user:suitSound('starlit-configure') 235 + 236 + end 237 + return true, true 238 + end; 239 + render = function(state, user) 240 + local suit = user:getSuit() 241 + local swm 242 + if user:getSuit():powerState() ~= 'off' then 243 + swm = { 244 + w = 8, h = 3; 245 + order = {'active','ritual','pasv'}; 246 + menu = { 247 + active = { 248 + label = 'Nanoware'; 249 + opts = {}; 250 + }; 251 + ritual = { 252 + label = 'Programs'; 253 + opts = {}; 254 + }; 255 + pasv = { 256 + label = 'Passive'; 257 + opts = {}; 258 + }; 259 + }; 260 + } 261 + for id, r in pairs(state.suitSW) do 262 + local color = {hue=300,sat=0,lum=0} 263 + local fg = nil 264 + local close = nil 265 + local tbl, cfg if r.sw.powerKind == 'active' then 266 + tbl = swm.menu.active.opts 267 + if r.sw.ui then cfg = true end 268 + local pnan = user.power.nano 269 + if pnan then 270 + local ptr = {chipID = starlit.mod.electronics.chip.read(r.chip).uuid, pgmIndex = r.fd.inode} 271 + if pptrMatch(ptr, pnan.primary) then 272 + color.lum = 1 273 + elseif pptrMatch(ptr, pnan.secondary) then 274 + color.lum = 0.8 275 + end 276 + end 277 + elseif r.sw.powerKind == 'direct' then 278 + tbl = swm.menu.ritual.opts 279 + if not r.sw.ui then 280 + close = true 281 + end 282 + elseif r.sw.powerKind == 'passive' then 283 + tbl = swm.menu.pasv.opts 284 + if r.sw.ui then cfg = true end 285 + for i, e in ipairs(r.file.body.conf) do 286 + if e.key == 'disable' and e.value == 'yes' then 287 + color.lum = -.2 288 + fg = lib.color {hue=color.hue,sat=0.7,lum=0.7} 289 + break 290 + end 291 + end 292 + end 293 + if tbl then table.insert(tbl, { 294 + color = color, fg = fg; 295 + label = r.sw.label or r.sw.name; 296 + id = string.format('suit_pgm_%s_', id); 297 + cfg = cfg, close = close; 298 + }) end 299 + end 300 + end 301 + local menu = { kind = 'vert', mode = 'sw', padding = 0.5 } 302 + if swm then table.insert(menu, abilityMenu(swm)) end 303 + 304 + local inv = user.entity:get_inventory() 305 + local cans = inv:get_list 'starlit_suit_canisters' 306 + if cans and next(cans) then for i, st in ipairs(cans) do 307 + local id = string.format('starlit_canister_%u_elem', i) 308 + local esz = inv:get_size(id) 309 + if esz > 0 then 310 + local eltW, eltH = listWrap(esz, 5) 311 + table.insert(menu, {kind = 'hztl', 312 + {kind = 'img', desc='Elements', img = 'starlit-ui-icon-element.png', w=1,h=1}; 313 + {kind = 'list', target = 'current_player', inv = id, 314 + listContent = 'element', w = eltW, h = eltH, spacing = 0.1}; 315 + }) 316 + end 317 + end end 318 + 319 + if #menu == 0 then 320 + table.insert(menu, { 321 + kind = 'img'; 322 + img = 'starlit-ui-alert.png'; 323 + w=2, h=2; 324 + }) 325 + menu.padding = 1; 326 + end 327 + return starlit.ui.build(menu) 328 + end; 329 + }; 330 + compilerListRecipes = { 331 + }; 332 + psi = { 333 + render = function(state, user) 334 + return starlit.ui.build { 335 + kind = 'vert', mode = 'sw'; 336 + padding = 0.5; 337 + } 338 + end; 339 + }; 340 + body = { 341 + render = function(state, user) 342 + local barh = .75 343 + local tb = { 344 + kind = 'vert', mode = 'sw'; 345 + padding = 0.5, 346 + {kind = 'hztl', padding = 0.25; 347 + {kind = 'label', text = 'Name', w = 2, h = barh}; 348 + {kind = 'label', text = user.persona.name, w = 4, h = barh}}; 349 + } 350 + local statBars = {'hunger', 'thirst', 'fatigue', 'morale'} 351 + for idx, id in ipairs(statBars) do 352 + local s = starlit.world.stats[id] 353 + local amt, sv = user:effectiveStat(id) 354 + local min, max = starlit.world.species.statRange(user.persona.species, user.persona.speciesVariant, id) 355 + local st = string.format('%s / %s', s.desc(amt, true), s.desc(max)) 356 + table.insert(tb, {kind = 'hztl', padding = 0.25; 357 + {kind = 'label', w=2, h=barh, text = s.name}; 358 + {kind = 'hbar', w=4, h=barh, fac = sv, text = st, color=s.color}; 359 + }) 360 + end 361 + local abilities = { 362 + {id = 'abl_sprint', label = 'Sprint', img = 'starlit-ui-icon-ability-sprint.png'}; 363 + } 364 + table.insert(tb, wrapMenu(6.25,4, 1,2, abilities)) 365 + return starlit.ui.build(tb) 366 + end; 367 + }; 368 + suit = { 369 + render = function(state, user) 370 + local suit = user:getSuit() 371 + local suitDef = suit:def() 372 + local chipW, chipH = listWrap(suitDef.slots.chips, 5) 373 + local batW, batH = listWrap(suitDef.slots.batteries, 5) 374 + local canW, canH = listWrap(suitDef.slots.canisters, 5) 375 + local suitMode = suit:powerState() 376 + local function modeColor(mode) 377 + if mode == suitMode then return {hue = 180, sat = 0, lum = .5} end 378 + end 379 + return starlit.ui.build { 380 + kind = 'vert', mode = 'sw'; 381 + padding = 0.5, spacing = 0.1; 382 + {kind = 'hztl', 383 + {kind = 'img', desc='Batteries', img = 'starlit-item-battery.png', w=1,h=1}; 384 + {kind = 'list', target = 'current_player', inv = 'starlit_suit_bat', 385 + listContent = 'power', w = batW, h = batH, spacing = 0.1}; 386 + }; 387 + {kind = 'hztl', 388 + {kind = 'img', desc='Chips', img = 'starlit-item-chip.png', w=1,h=1}; 389 + {kind = 'list', target = 'current_player', inv = 'starlit_suit_chips', 390 + listContent = 'chip', w = chipW, h = chipH, spacing = 0.1}; 391 + }; 392 + {kind = 'hztl', 393 + {kind = 'img', desc='Canisters', img = 'starlit-item-element-canister.png', w=1,h=1}; 394 + {kind = 'list', target = 'current_player', inv = 'starlit_suit_canisters', 395 + listContent = nil, w = canW, h = canH, spacing = 0.1}; 396 + }; 397 + {kind = 'hztl'; 398 + {kind = 'img', w=1,h=1, item = suit.item:get_name(), 399 + desc = suit.item:get_definition().short_description}; 400 + {kind = 'button', w=1.5,h=1, id = 'powerMode_off', label = 'Off'; 401 + color=modeColor'off'}; 402 + {kind = 'button', w=2.5,h=1, id = 'powerMode_save', label = 'Power Save'; 403 + color=modeColor'powerSave'}; 404 + {kind = 'button', w=1.5,h=1, id = 'powerMode_on', label = 'On'; 405 + color=modeColor'on'}; 406 + }; 407 + {kind = 'list', target = 'current_player', inv = 'main', w = 6, h = 1, spacing = 0.1}; 408 + } 409 + end; 410 + handle = function(state, user, q) 411 + local suitMode 412 + if q.powerMode_off then suitMode = 'off' 413 + elseif q.powerMode_save then suitMode = 'powerSave' 414 + elseif q.powerMode_on then suitMode = 'on' end 415 + if suitMode then 416 + user:suitPowerStateSet(suitMode) 417 + return true 418 + end 419 + end; 420 + }; 421 + }; 422 +}) 423 + 424 +starlit.interface.install(starlit.type.ui { 425 + id = 'starlit:compile-matter-component'; 426 + pages = { 427 + index = { 428 + setupState = function(state, user, ctx) 429 + if ctx.context == 'suit' then 430 + end 431 + state.pgm = ctx.program 432 + end; 433 + render = function(state, user) 434 + return starlit.ui.build { 435 + kind = 'vert', padding = 0.5; w = 5, h = 5, mode = 'sw'; 436 + {kind = 'label', w = 4, h = 1, text = 'hello'}; 437 + } 438 + end; 439 + }; 440 + }; 441 +})
Added mods/starlit/item.lua version [aa837e16cf].
1 +local lib = starlit.mod.lib 2 +local I = starlit.item 3 + 4 +function I.mk(item, context) 5 + local st = ItemStack(item) 6 + local md = st:get_definition()._starlit 7 + local ctx = context or {} 8 + if md and md.event then 9 + md.event.create(st, ctx) 10 + end 11 + if context.how == 'print' then 12 + if context.schematic and context.schematic.setup then 13 + context.schematic.setup(st, ctx) 14 + end 15 + end 16 + return st 17 +end
Added mods/starlit/mod.conf version [e7817791d1].
1 +name = starlit 2 +author = velartrill 3 +description = world logic and UI 4 +depends = vtlib
Added mods/starlit/species.lua version [3944fdb227].
1 +local lib = starlit.mod.lib 2 + 3 +local paramTypes do local T,G = lib.marshal.t, lib.marshal.g 4 + paramTypes = { 5 + tone = G.struct { 6 + hue = T.angle; 7 + sat = T.clamp; 8 + lum = T.clamp; 9 + }; 10 + str = T.str; 11 + num = T.decimal; 12 + } 13 +end 14 + 15 +-- constants 16 +local animationFrameRate = 60 17 + 18 +local species = { 19 + human = { 20 + name = 'Human'; 21 + desc = 'The weeds of the galactic flowerbed. Humans are one of the Lesser Races, excluded from the ranks of the Greatest Races by souls that lack, in normal circumstances, external psionic channels. Their mastery of the universe cut unexpectedly short, forever locked out of FTL travel, short-lived without augments, and alternately pitied or scorned by the lowest of the low, humans flourish nonetheless due to a capacity for adaptation unmatched among the Thinking Few, terrifyingly rapid reproductive cycles -- and a keen facility for bribery. While the lack of human psions remains a sensitive topic, humans (unlike the bitter and emotional Kruthandi) are practical enough to hire the talent they cannot possess, and have even built a small number of symbiotic civilizations with the more indulging of the Powers. In a galaxy where nearly all sophont life is specialized to a fault, humans have found the unique niche of occupying no particular niche.'; 22 + scale = 1.0; 23 + params = { 24 + {'eyeColor', 'Eye Color', 'tone', {hue=327, sat=0, lum=0}}; 25 + {'hairColor', 'Hair Color', 'tone', {hue=100, sat=0, lum=0}}; 26 + {'skinTone', 'Skin Tone', 'tone', {hue= 0, sat=0, lum=0}}; 27 + }; 28 + tempRange = { 29 + comfort = {18.3, 23.8}; -- needed for full stamina regen 30 + survivable = {5, 33}; -- anything below/above will cause progressively more damage 31 + }; 32 + variants = { 33 + female = { 34 + name = 'Human Female'; 35 + mesh = 'starlit-body-female.x'; 36 + eyeHeight = 1.4; 37 + texture = function(t, adorn) 38 + local skin = lib.image 'starlit-body-skin.png' : shift(t.skinTone) 39 + local eye = lib.image 'starlit-body-eye.png' : shift(t.eyeColor) 40 + local hair = lib.image 'starlit-body-hair.png' : shift(t.hairColor) 41 + 42 + local invis = lib.image '[fill:1x1:0,0:#00000000' 43 + local plate = adorn.suit and adorn.suit.plate or invis 44 + local lining = adorn.suit and adorn.suit.lining or invis 45 + 46 + return {lining, plate, skin, skin, eye, hair} 47 + end; 48 + stats = { 49 + psiRegen = 1.3; 50 + psiPower = 1.2; 51 + psi = 1.2; 52 + hunger = .8; -- women have smaller stomachs 53 + thirst = .8; 54 + staminaRegen = 1.0; 55 + morale = 0.8; -- you are not She-Bear Grylls 56 + }; 57 + traits = { 58 + health = 400; 59 + lungCapacity = .6; 60 + irradiation = 0.8; -- you are smaller, so it takes less rads to kill ya 61 + sturdiness = 0; -- women are more fragile and thus susceptible to blunt force trauma 62 + metabolism = 1800; --Cal 63 + painTolerance = 0.4; 64 + }; 65 + }; 66 + male = { 67 + name = 'Human Male'; 68 + eyeHeight = 1.6; 69 + stats = { 70 + psiRegen = 1.0; 71 + psiPower = 1.0; 72 + psi = 1.0; 73 + hunger = 1.0; 74 + staminaRegen = .7; -- men are strong but have inferior endurance 75 + }; 76 + traits = { 77 + health = 500; 78 + painTolerance = 1.0; 79 + lungCapacity = 1.0; 80 + sturdiness = 0.3; 81 + metabolism = 2200; --Cal 82 + }; 83 + }; 84 + }; 85 + traits = {}; 86 + }; 87 +} 88 + 89 +starlit.world.species = { 90 + index = species; 91 + paramTypes = paramTypes; 92 +} 93 + 94 +function starlit.world.species.mkDefaultParamsTable(pSpecies, pVariant) 95 + local sp = species[pSpecies] 96 + local var = sp.variants[pVariant] 97 + local vpd = var.defaults or {} 98 + local tbl = {} 99 + for _, p in pairs(sp.params) do 100 + local name, desc, ty, dflt = lib.tbl.unpack(p) 101 + tbl[name] = vpd[name] or dflt 102 + end 103 + return tbl 104 +end 105 + 106 + 107 +function starlit.world.species.mkPersonaFor(pSpecies, pVariant) 108 + return { 109 + species = pSpecies; 110 + speciesVariant = pVariant; 111 + bodyParams = starlit.world.species.paramsFromTable(pSpecies, 112 + starlit.world.species.mkDefaultParamsTable(pSpecies, pVariant) 113 + ); 114 + statDeltas = {}; 115 + } 116 +end 117 + 118 +local function spLookup(pSpecies, pVariant) 119 + local sp = species[pSpecies] 120 + local var = sp.variants[pVariant or next(sp.variants)] 121 + return sp, var 122 +end 123 +starlit.world.species.lookup = spLookup 124 + 125 +function starlit.world.species.statRange(pSpecies, pVariant, pStat) 126 + local sp,spv = spLookup(pSpecies, pVariant) 127 + local min, max, base 128 + if pStat == 'health' then 129 + min,max = 0, spv.traits.health 130 + elseif pStat == 'breath' then 131 + min,max = 0, 65535 132 + else 133 + local spfac = spv.stats[pStat] 134 + local basis = starlit.world.stats[pStat] 135 + min,max = basis.min, basis.max 136 + 137 + if spfac then 138 + min = min * spfac 139 + max = max * spfac 140 + end 141 + 142 + base = basis.base 143 + if base == true then 144 + base = max 145 + elseif base == false then 146 + base = min 147 + end 148 + 149 + end 150 + return min, max, base 151 +end 152 + 153 +-- set the necessary properties and create a persona for a newspawned entity 154 +function starlit.world.species.birth(pSpecies, pVariant, entity, circumstances) 155 + circumstances = circumstances or {} 156 + local sp,var = spLookup(pSpecies, pVariant) 157 + 158 + local function pct(st, p) 159 + local min, max = starlit.world.species.statRange(pSpecies, pVariant, st) 160 + local delta = max - min 161 + return min + delta*p 162 + end 163 + local ps = starlit.world.species.mkPersonaFor(pSpecies,pVariant) 164 + local startingHP = pct('health', 1.0) 165 + if circumstances.injured then startingHP = pct('health', circumstances.injured) end 166 + if circumstances.psiCharged then ps.statDeltas.psi = pct('psi', circumstances.psiCharged) end 167 + ps.statDeltas.warmth = 20 -- don't instantly start dying of frostbite 168 + 169 + entity:set_properties{hp_max = var.traits.health or sp.traits.health} 170 + entity:set_hp(startingHP, 'initial hp') 171 + return ps 172 +end 173 + 174 +function starlit.world.species.paramsFromTable(pSpecies, tbl) 175 + local lst = {} 176 + local sp = species[pSpecies] 177 + for i, par in pairs(sp.params) do 178 + local name,desc,ty,dflt = lib.tbl.unpack(par) 179 + if tbl[name] then 180 + table.insert(lst, {id=name, value=paramTypes[ty].enc(tbl[name])}) 181 + end 182 + end 183 + return lst 184 +end 185 +function starlit.world.species.paramsToTable(pSpecies, lst) 186 + local tymap = {} 187 + local sp = species[pSpecies] 188 + for i, par in pairs(sp.params) do 189 + local name,desc,ty,dflt = lib.tbl.unpack(par) 190 + tymap[name] = paramTypes[ty] 191 + end 192 + 193 + local tbl = {} 194 + for _, e in pairs(lst) do 195 + tbl[e.id] = tymap[e.id].dec(e.value) 196 + end 197 + return tbl 198 +end 199 + 200 +for speciesName, sp in pairs(species) do 201 + for varName, var in pairs(sp.variants) do 202 + if var.mesh then 203 + var.animations = starlit.evaluate(string.format('models/%s.nla', var.mesh)).skel.action 204 + end 205 + end 206 +end 207 + 208 + 209 +function starlit.world.species.updateTextures(ent, persona, adornment) 210 + local s,v = spLookup(persona.species, persona.speciesVariant) 211 + local paramTable = starlit.world.species.paramsToTable(persona.species, persona.bodyParams) 212 + local texs = {} 213 + for i, t in ipairs(v.texture(paramTable, adornment)) do 214 + texs[i] = t:render() 215 + end 216 + ent:set_properties { textures = texs } 217 +end 218 + 219 +function starlit.world.species.setupEntity(ent, persona) 220 + local s,v = spLookup(persona.species, persona.speciesVariant) 221 + local _, maxHealth = starlit.world.species.statRange(persona.species, persona.speciesVariant, 'health') 222 + ent:set_properties { 223 + visual = 'mesh'; 224 + mesh = v.mesh; 225 + stepheight = .51; 226 + eye_height = v.eyeHeight; 227 + collisionbox = { -- FIXME 228 + -0.3, 0.0, -0.3; 229 + 0.3, 1.5, 0.3; 230 + }; 231 + visual_size = vector.new(10,10,10) * s.scale; 232 + 233 + hp_max = maxHealth; 234 + } 235 + local function P(v) 236 + if v then return {x=v[1],y=v[2]} end 237 + return {x=0,y=0} 238 + end 239 + ent:set_local_animation( 240 + P(v.animations.idle), 241 + P(v.animations.run), 242 + P(v.animations.work), 243 + P(v.animations.runWork), 244 + animationFrameRate) 245 + 246 +end
Added mods/starlit/stats.lua version [c766e87490].
1 +local lib = starlit.mod.lib 2 + 3 +local function U(unit, prec, fixed) 4 + if fixed then 5 + return function(amt, excludeUnit) 6 + if excludeUnit then return tostring(amt/prec) end 7 + return string.format("%s %s", amt/prec, unit) 8 + end 9 + else 10 + return function(amt, excludeUnit) 11 + if excludeUnit then return tostring(amt/prec) end 12 + return lib.math.si(unit, amt/prec) 13 + end 14 + end 15 +end 16 + 17 +local function C(h, s, l) 18 + return lib.color {hue = h, sat = s or 1, lum = l or .7} 19 +end 20 +starlit.world.stats = { 21 + psi = {min = 0, max = 500, base = 0, desc = U('ψ', 10), color = C(320), name = 'Numina'}; 22 + -- numina is measured in daψ 23 + warmth = {min = -1000, max = 1000, base = 0, desc = U('°C', 10, true), color = C(5), name = 'Warmth'}; 24 + -- warmth in measured in °C×10 25 + fatigue = {min = 0, max = 76 * 60, base = 0, desc = U('hr', 60, true), color = C(288,.3,.5), name = 'Fatigue'}; 26 + -- fatigue is measured in minutes one needs to sleep to cure it 27 + stamina = {min = 0, max = 20 * 100, base = true, desc = U('m', 100), color = C(88), name = 'Stamina'}; 28 + -- stamina is measured in how many 10th-nodes (== cm) one can sprint 29 + hunger = {min = 0, max = 20000, base = 0, desc = U('Cal', 1), color = C(43,.5,.4), name = 'Hunger'}; 30 + -- hunger is measured in calories one must consume to cure it 31 + thirst = {min = 0, max = 1600, base = 0, desc = U('l', 100), color = C(217, .25,.4), name = 'Thirst'}; 32 + -- thirst is measured in centiliters of H²O required to cure it 33 + morale = {min = 0, max = 24 * 60 * 10, base = true, desc = U('hr', 60, true), color = C(0,0,.8), name = 'Morale'}; 34 + -- morale is measured in minutes. e.g. at base rate morale degrades by 35 + -- 60 points every hour. morale can last up to 10 days 36 + irradiation = {min = 0, max = 20000, base = 0, desc = U('Gy', 1000), color = C(141,1,.5), name = 'Irradiation'}; 37 + -- irrad is measured is milligreys 38 + -- 1Gy counters natural healing 39 + -- ~3Gy counters basic nanomedicine 40 + -- 5Gy causes death within two weeks without nanomedicine 41 + -- radiation speeds up psi regen 42 + -- morale drain doubles with each 2Gy 43 + illness = {min = 0, max = 1000, base = 0, desc = U('%', 10, true), color = C(71,.4,.25), name = 'Illness'}; 44 + -- as illness increases, maximum stamina and health gain a corresponding limit 45 + -- illness is increased by certain conditions, and decreases on its own as your 46 + -- body heals when those conditions wear off. some drugs can lower accumulated illness 47 + -- but illness-causing conditions require specific cures 48 + -- illness also causes thirst and fatigue to increase proportionately 49 +}
Added mods/starlit/store.lua version [73c6f814ce].
1 +-- [ʞ] store.lua 2 +-- ~ lexi hale <lexi@hale.su> 3 +-- © EUPLv1.2 4 +-- ? defines serialization datatypes that don't belong to 5 +-- any individual class 6 + 7 +local lib = starlit.mod.lib 8 +local T,G = lib.marshal.t, lib.marshal.g 9 +starlit.store = {} -- the serialization equivalent of .type 10 + 11 +------------- 12 +-- persona -- 13 +------------- ----------------------------------------------- 14 +-- a Persona is a structure that defines the nature of -- 15 +-- an (N)PC and how it interacts with the Starsoul-managed -- 16 +-- portion of the game world -- things like name, species, -- 17 +-- stat values, physical characteristics, and so forth -- 18 + 19 +local statStructFields = {} 20 +for k,v in pairs(starlit.world.stats) do 21 + statStructFields[k] = v.srzType or ( 22 + (v.base == true or v.base > 0) and T.s16 or T.u16 23 + ) 24 +end 25 + 26 +starlit.store.compilerJob = G.struct { 27 + schematic = T.str; 28 + progress = T.clamp; 29 +} 30 + 31 +starlit.store.persona = G.struct { 32 + name = T.str; 33 + species = T.str; 34 + speciesVariant = T.str; 35 + background = T.str; 36 + bodyParams = G.array(8, G.struct {id = T.str, value = T.str}); --variant 37 + 38 + statDeltas = G.struct(statStructFields); 39 + 40 + facts = G.array(32, G.array(8, T.str)); 41 + -- facts stores information the player has discovered and narrative choices 42 + -- she has made. 43 + -- parametric facts are encoded as horn clauses 44 + -- non-parametric facts are encoded as {'fact-mod:fact-id'} 45 +} 46 + 47 +starlit.store.suitMeta = lib.marshal.metaStore { 48 + batteries = {key = 'starlit:suit_slots_bat', type = T.inventoryList}; 49 + chips = {key = 'starlit:suit_slots_chips', type = T.inventoryList}; 50 + elements = {key = 'starlit:suit_slots_elem', type = T.inventoryList}; 51 + guns = {key = 'starlit:suit_slots_gun', type = T.inventoryList}; 52 + ammo = {key = 'starlit:suit_slots_ammo', type = T.inventoryList}; 53 +}
Added mods/starlit/suit.lua version [7112e6c94b].
1 +local lib = starlit.mod.lib 2 + 3 +local suitStore = starlit.store.suitMeta 4 +starlit.item.suit = lib.registry.mk 'starlit:suits'; 5 + 6 +-- note that this cannot be persisted as a reference to a particular suit in the world 7 +local function suitContainer(stack, inv) 8 + return starlit.item.container(stack, inv, { 9 + pfx = 'starlit_suit' 10 + }) 11 +end 12 +starlit.type.suit = lib.class { 13 + name = 'starlit:suit'; 14 + construct = function(stack) 15 + return { 16 + item = stack; 17 + inv = suitStore(stack); 18 + } 19 + end; 20 + __index = { 21 + powerState = function(self) 22 + local s = self.item 23 + if not s then return nil end 24 + local m = s:get_meta():get_int('starlit:power_mode') 25 + if m == 1 then return 'on' 26 + elseif m == 2 then return 'powerSave' 27 + else return 'off' end 28 + end; 29 + powerStateSet = function(self, state) 30 + local s = self.item 31 + if not s then return nil end 32 + local m 33 + if state == 'on' then m = 1 -- TODO check power level 34 + elseif state == 'powerSave' then m = 2 35 + else m = 0 end 36 + if self:powerLeft() <= 0 then m = 0 end 37 + s:get_meta():set_int('starlit:power_mode', m) 38 + end; 39 + powerLeft = function(self) 40 + local batteries = self.inv.read 'batteries' 41 + local power = 0 42 + for idx, slot in pairs(batteries) do 43 + power = power + starlit.mod.electronics.dynamo.totalPower(slot) 44 + end 45 + return power 46 + end; 47 + powerCapacity = function(self) 48 + local batteries = self.inv.read 'batteries' 49 + local power = 0 50 + for idx, slot in pairs(batteries) do 51 + power = power + starlit.mod.electronics.dynamo.initialPower(slot) 52 + end 53 + return power 54 + end; 55 + maxPowerUse = function(self) 56 + local batteries = self.inv.read 'batteries' 57 + local w = 0 58 + for idx, slot in pairs(batteries) do 59 + w = w + starlit.mod.electronics.dynamo.dischargeRate(slot) 60 + end 61 + return w 62 + end; 63 + onReconfigure = function(self, inv) 64 + -- apply any changes to item metadata and export any subinventories 65 + -- to the provided invref, as they may have changed 66 + local sc = starlit.item.container(self.item, inv, {pfx = 'starlit_suit'}) 67 + sc:push() 68 + self:pullCanisters(inv) 69 + end; 70 + onItemMove = function(self, user, list, act, what) 71 + -- called when the suit inventory is changed 72 + if act == 'put' then 73 + if list == 'starlit_suit_bat' then 74 + user:suitSound('starlit-suit-battery-in') 75 + elseif list == 'starlit_suit_chips' then 76 + user:suitSound('starlit-suit-chip-in') 77 + elseif list == 'starlit_suit_canisters' then 78 + user:suitSound('starlit-insert-snap') 79 + end 80 + elseif act == 'take' then 81 + if list == 'starlit_suit_bat' then 82 + user:suitSound('starlit-insert-snap') 83 + elseif list == 'starlit_suit_chips' then 84 + --user:suitSound('starlit-suit-chip-out') 85 + elseif list == 'starlit_suit_canisters' then 86 + user:suitSound('starlit-insert-snap') 87 + end 88 + end 89 + end; 90 + def = function(self) 91 + return self.item:get_definition()._starlit.suit 92 + end; 93 + pullCanisters = function(self, inv) 94 + starlit.item.container.dropPrefix(inv, 'starlit_canister') 95 + self:forCanisters(inv, function(sc) sc:pull() end) 96 + end; 97 + pushCanisters = function(self, inv, st, i) 98 + self:forCanisters(inv, function(sc) 99 + sc:push() 100 + return true 101 + end) 102 + end; 103 + forCanisters = function(self, inv, fn) 104 + local cans = inv:get_list 'starlit_suit_canisters' 105 + if cans and next(cans) then for i, st in ipairs(cans) do 106 + if not st:is_empty() then 107 + local pfx = 'starlit_canister_' .. tostring(i) 108 + local sc = starlit.item.container(st, inv, {pfx = pfx}) 109 + if fn(sc, st, i, pfx) then 110 + inv:set_stack('starlit_suit_canisters', i, st) 111 + end 112 + end 113 + end end 114 + end; 115 + establishInventories = function(self, obj) 116 + local inv = obj:get_inventory() 117 + local ct = suitContainer(self.item, inv) 118 + ct:pull() 119 + self:pullCanisters(inv) 120 + 121 + --[[ 122 + local def = self:def() 123 + local sst = suitStore(self.item) 124 + local function readList(listName, prop) 125 + inv:set_size(listName, def.slots[prop]) 126 + if def.slots[prop] > 0 then 127 + local lst = sst.read(prop) 128 + inv:set_list(listName, lst) 129 + end 130 + end 131 + readList('starlit_suit_chips', 'chips') 132 + readList('starlit_suit_bat', 'batteries') 133 + readList('starlit_suit_guns', 'guns') 134 + readList('starlit_suit_elem', 'elements') 135 + readList('starlit_suit_ammo', 'ammo') 136 + ]] 137 + end; 138 + }; 139 +} 140 + 141 +-- TODO find a better place for this! 142 +starlit.type.suit.purgeInventories = function(obj) 143 + local inv = obj:get_inventory() 144 + starlit.item.container.dropPrefix(inv, 'starlit_suit') 145 + starlit.item.container.dropPrefix(inv, 'starlit_canister') 146 + --[[inv:set_size('starlit_suit_bat', 0) 147 + inv:set_size('starlit_suit_guns', 0) 148 + inv:set_size('starlit_suit_chips', 0) 149 + inv:set_size('starlit_suit_ammo', 0) 150 + inv:set_size('starlit_suit_elem', 0) 151 + ]] 152 +end 153 + 154 +starlit.item.suit.foreach('starlit:suit-gen', {}, function(id, def) 155 + local icon = lib.image(def.img or 'starlit-item-suit.png') 156 + 157 + local iconColor = def.iconColor 158 + if not iconColor then 159 + iconColor = (def.tex and def.tex.plate and def.tex.plate.tint) 160 + or def.defaultColor 161 + iconColor = iconColor:to_hsl() 162 + iconColor.lum = 0 163 + end 164 + 165 + if iconColor then icon = icon:shift(iconColor) end 166 + 167 + if not def.adorn then 168 + function def.adorn(a, item, persona) 169 + local function imageFor(pfx) 170 + return lib.image(string.format("%s-%s-%s.png", pfx, persona.species, persona.speciesVariant)) 171 + end 172 + if not def.tex then return end 173 + a.suit = {} 174 + for name, t in pairs(def.tex) do 175 + local img = imageFor(t.id) 176 + local color 177 + 178 + local cstr = item:get_meta():get_string('starlit:tint_suit_' .. name) 179 + if cstr and cstr ~= '' then 180 + color = lib.color.unmarshal(cstr) 181 + elseif t.tint then 182 + color = t.tint or def.defaultColor 183 + end 184 + 185 + if color then 186 + local hsl = color:to_hsl() 187 + local adjusted = { 188 + hue = hsl.hue; 189 + sat = hsl.sat * 2 - 1; 190 + lum = hsl.lum * 2 - 1; 191 + } 192 + img = img:shift(adjusted) 193 + end 194 + 195 + a.suit[name] = img 196 + end 197 + end 198 + end 199 + 200 + minetest.register_tool(id, { 201 + short_description = def.name; 202 + description = starlit.ui.tooltip { 203 + title = def.name; 204 + desc = def.desc; 205 + color = lib.color(.1, .7, 1); 206 + }; 207 + groups = { 208 + suit = 1; 209 + inv = 1; -- has inventories 210 + batteryPowered = 1; -- has a battery inv 211 + programmable = 1; -- has a chip inv 212 + }; 213 + on_use = function(st, luser, pointed) 214 + local user = starlit.activeUsers[luser:get_player_name()] 215 + if not user then return end 216 + -- have mercy on users who've lost their suits and wound 217 + -- up naked and dying of exposure 218 + if user:naked() then 219 + local ss = st:take_item(1) 220 + user:setSuit(starlit.type.suit(ss)) 221 + user:suitSound('starlit-suit-don') 222 + return st 223 + end 224 + end; 225 + inventory_image = icon:render(); 226 + _starlit = { 227 + container = { 228 + workbench = { 229 + order = {'batteries','chips','guns','ammo'} 230 + }; 231 + list = { 232 + bat = { 233 + key = 'starlit:suit_slots_bat'; 234 + accept = 'dynamo'; 235 + sz = def.slots.batteries; 236 + }; 237 + chips = { 238 + key = 'starlit:suit_slots_chips'; 239 + accept = 'chip'; 240 + sz = def.slots.chips; 241 + }; 242 + canisters = { 243 + key = 'starlit:suit_slots_canisters'; 244 + accept = 'canister'; 245 + sz = def.slots.canisters; 246 + }; 247 + guns = { 248 + key = 'starlit:suit_slots_gun'; 249 + accept = 'weapon'; 250 + workbench = { 251 + label = 'Weapon'; 252 + icon = 'starlit-ui-icon-gun'; 253 + color = lib.color(1,0,0); 254 + }; 255 + sz = def.slots.guns; 256 + }; 257 + ammo = { 258 + key = 'starlit:suit_slots_ammo'; 259 + accept = 'ammo'; 260 + workbench = { 261 + label = 'Ammunition'; 262 + color = lib.color(1,.5,0); 263 + easySlots = true; -- all slots accessible on the go 264 + }; 265 + sz = def.slots.ammo; 266 + }; 267 + }; 268 + }; 269 + event = { 270 + create = function(st,how) 271 + local s = suitStore(st) 272 + -- make sure there's a defined powerstate 273 + starlit.type.suit(st):powerStateSet 'off' 274 + suitContainer(st):clear() 275 + --[[ populate meta tables 276 + s.write('batteries', {}) 277 + s.write('guns', {}) 278 + s.write('ammo', {}) 279 + s.write('elements', {}) 280 + s.write('chips', {})]] 281 + end; 282 + }; 283 + suit = def; 284 + }; 285 + }); 286 +end) 287 + 288 +local slotProps = { 289 + starlit_cfg = { 290 + itemClass = 'inv'; 291 + }; 292 + starlit_suit_bat = { 293 + suitSlot = true; 294 + powerLock = true; 295 + itemClass = 'dynamo'; 296 + }; 297 + starlit_suit_chips = { 298 + suitSlot = true; 299 + powerLock = true; 300 + itemClass = 'chip'; 301 + }; 302 + starlit_suit_guns = { 303 + suitSlot = true; 304 + maintenanceNode = ''; 305 + itemClass = 'suitWeapon'; 306 + }; 307 + starlit_suit_ammo = { 308 + suitSlot = true; 309 + maintenanceNode = ''; 310 + itemClass = 'suitAmmo'; 311 + }; 312 + starlit_suit_canisters = { 313 + suitSlot = true; 314 + itemClass = 'canister'; 315 + }; 316 +} 317 + 318 +minetest.register_allow_player_inventory_action(function(luser, act, inv, p) 319 + local user = starlit.activeUsers[luser:get_player_name()] 320 + local function grp(i,g) 321 + return minetest.get_item_group(i:get_name(), g) ~= 0 322 + end 323 + local function checkBaseRestrictions(list) 324 + local restrictions = slotProps[list] 325 + if not restrictions then return nil, true end 326 + if restrictions.suitSlot then 327 + if user:naked() then return restrictions, false end 328 + end 329 + if restrictions.powerLock then 330 + if user:getSuit():powerState() ~= 'off' then return restrictions, false end 331 + end 332 + return restrictions, true 333 + end 334 + local function itemFits(item, list) 335 + local rst, ok = checkBaseRestrictions(list) 336 + if not ok then return false end 337 + if rst == nil then return true end 338 + 339 + if rst.itemClass and not grp(item, rst.itemClass) then 340 + return false 341 + end 342 + if rst.maintenanceNode then return false end 343 + -- FIXME figure out best way to identify when the player is using a maintenance node 344 + 345 + if grp(item, 'specialInventory') then 346 + if grp(item, 'powder') and list ~= 'starlit_suit_elem' then return false end 347 + -- FIXME handle containers 348 + if grp(item, 'psi') and list ~= 'starlit_psi' then return false end 349 + end 350 + 351 + return true 352 + end 353 + local function itemCanLeave(item, list) 354 + local rst, ok = checkBaseRestrictions(list) 355 + if not ok then return false end 356 + if rst == nil then return true end 357 + 358 + if minetest.get_item_group(item:get_name(), 'specialInventory') then 359 + 360 + end 361 + 362 + if rst.maintenanceNode then return false end 363 + return true 364 + end 365 + 366 + if act == 'move' then 367 + local item = inv:get_stack(p.from_list, p.from_index) 368 + if not (itemFits(item, p.to_list) and itemCanLeave(item, p.from_list)) then 369 + return 0 370 + end 371 + elseif act == 'put' then 372 + if not itemFits(p.stack, p.listname) then return 0 end 373 + elseif act == 'take' then 374 + if not itemCanLeave(p.stack, p.listname) then return 0 end 375 + end 376 + return true 377 +end) 378 + 379 +minetest.register_on_player_inventory_action(function(luser, act, inv, p) 380 + local user = starlit.activeUsers[luser:get_player_name()] 381 + local function slotChange(slot,a,item) 382 + local s = slotProps[slot] 383 + if slot == 'starlit_suit' then 384 + user:updateSuit() 385 + if user:naked() then 386 + starlit.type.suit.purgeInventories(user.entity) 387 + user.power.nano = {} 388 + end 389 + elseif s and s.suitSlot then 390 + local s = user:getSuit() 391 + s:onItemMove(user, slot, a, item) 392 + s:onReconfigure(user.entity:get_inventory()) 393 + user:setSuit(s) 394 + else return end 395 + user:updateHUD() 396 + end 397 + 398 + if act == 'put' or act == 'take' then 399 + local item = p.stack 400 + slotChange(p.listname, act, item) 401 + elseif act == 'move' then 402 + local item = inv:get_stack(p.to_list, p.to_index) 403 + slotChange(p.from_list, 'take', item) 404 + slotChange(p.to_list, 'put', item) 405 + end 406 +end) 407 + 408 +local suitInterval = 2.0 409 +starlit.startJob('starlit:suit-software', suitInterval, function(delta) 410 + local runState = { 411 + pgmsRun = {}; 412 + flags = {}; 413 + } 414 + for id, u in pairs(starlit.activeUsers) do 415 + if not u:naked() then 416 + local reconfSuit = false 417 + local inv = u.entity:get_inventory() 418 + local chips = inv:get_list('starlit_suit_chips') 419 + local suitprog = starlit.mod.electronics.chip.usableSoftware(chips) 420 + for _, prop in pairs(suitprog) do 421 + local s = prop.sw 422 + if s.kind == 'suitPower' and (s.powerKind == 'passive' or s.bgProc) and (not runState.pgmsRun[s]) then 423 + local conf = prop.file.body.conf 424 + local enabled = true 425 + for _, e in ipairs(conf) do 426 + if e.key == 'disable' and e.value == 'yes' then 427 + enabled = false 428 + break 429 + end 430 + end 431 + local fn if s.powerKind == 'passive' 432 + then fn = s.run 433 + else fn = s.bgProc 434 + end 435 + function prop.saveConf(cfg) cfg = cfg or conf 436 + prop.fd:write(cfg) 437 + inv:set_stack('starlit_suit_chips', prop.chipSlot, prop.fd.chip) 438 + reconfSuit = true 439 + end 440 + function prop.giveItem(st) 441 + u:thrustUpon(st) 442 + end 443 + 444 + if enabled and fn(u, prop, suitInterval, runState) then 445 + runState.pgmsRun[s] = true 446 + end 447 + end 448 + end 449 + if reconfSuit then 450 + u:reconfigureSuit() 451 + end 452 + end 453 + end 454 +end) 455 +
Added mods/starlit/terrain.lua version [5a8b3b76d0].
1 +local T = starlit.translator 2 +local lib = starlit.mod.lib 3 + 4 +starlit.terrain = {} 5 +local soilSounds = {} 6 +local grassSounds = {} 7 + 8 +minetest.register_node('starlit:soil', { 9 + description = T 'Soil'; 10 + tiles = {'default_dirt.png'}; 11 + groups = {dirt = 1}; 12 + drop = ''; 13 + sounds = soilSounds; 14 + _starlit = { 15 + onDestroy = function() end; 16 + kind = 'block'; 17 + elements = {}; 18 + }; 19 +}) 20 + 21 + 22 +minetest.register_node('starlit:sand', { 23 + description = T 'Sand'; 24 + tiles = {'default_sand.png'}; 25 + groups = {dirt = 1}; 26 + drop = ''; 27 + sounds = soilSounds; 28 + _starlit = { 29 + kind = 'block'; 30 + fab = starlit.type.fab { element = { silicon = 25 } }; 31 + }; 32 +}) 33 +minetest.register_craftitem('starlit:soil_clump', { 34 + short_description = T 'Soil'; 35 + description = starlit.ui.tooltip { 36 + title = T 'Soil'; 37 + desc = 'A handful of nutrient-packed soil, suitable for growing plants'; 38 + color = lib.color(0.3,0.2,0.1); 39 + }; 40 + inventory_image = 'starlit-item-soil.png'; 41 + groups = {soil = 1}; 42 + _starlit = { 43 + fab = starlit.type.fab { element = { carbon = 12 / 4 } }; 44 + }; 45 +}) 46 + 47 +function starlit.terrain.createGrass(def) 48 + local function grassfst(i) 49 + local nextNode = def.name 50 + if i >= 0 then 51 + nextNode = nextNode .. '_walk_' .. tostring(i) 52 + end 53 + return { 54 + onWalk = function(pos) 55 + minetest.set_node_at(pos, def.name .. '_walk_2'); 56 + end; 57 + onDecay = function(pos,delta) 58 + minetest.set_node_at(pos, nextNode); 59 + end; 60 + onDestroy = function(pos) end; 61 + fab = def.fab; 62 + recover = def.recover; 63 + recover_vary = def.recover_vary; 64 + }; 65 + end 66 + local drop = { 67 + max_items = 4; 68 + items = { 69 + { 70 + items = {'starlit:soil'}, rarity = 2; 71 + tool_groups = { 'shovel', 'trowel' }; 72 + }; 73 + }; 74 + } 75 + minetest.register_node(def.name, { 76 + description = T 'Greengraze'; 77 + tiles = { 78 + def.img .. '.png'; 79 + 'default_dirt.png'; 80 + { 81 + name = 'default_dirt.png^' .. def.img ..'_side.png'; 82 + tileable_vertical = false; 83 + }; 84 + }; 85 + groups = {grass = 1, sub_walk = 1}; 86 + drop = ''; 87 + sounds = grassSounds; 88 + _starlit = grassfst(2); 89 + }) 90 + for i=2,0,-1 do 91 + local opacity = tostring((i/2.0) * 255) 92 + 93 + minetest.register_node(def.name, { 94 + description = def.desc; 95 + tiles = { 96 + def.img .. '.png^(default_footprint.png^[opacity:'..opacity..')'; 97 + 'default_dirt.png'; 98 + { 99 + name = 'default_dirt.png^' .. def.img ..'_side.png'; 100 + tileable_vertical = false; 101 + }; 102 + }; 103 + groups = {grass = 1, sub_walk = 1, sub_decay = 5}; 104 + drop = ''; 105 + _starlit = grassfst(i-1); 106 + sounds = grassSounds; 107 + }) 108 + end 109 +end 110 + 111 + 112 +starlit.terrain.createGrass { 113 + name = 'starlit:greengraze'; 114 + desc = T 'Greengraze'; 115 + img = 'default_grass'; 116 + fab = starlit.type.fab { 117 + element = { 118 + carbon = 12; 119 + }; 120 + time = { 121 + shred = 2.5; 122 + }; 123 + }; 124 +} 125 + 126 +for _, w in pairs {false,true} do 127 + minetest.register_node('starlit:liquid_water' .. (w and '_flowing' or ''), { 128 + description = T 'Water'; 129 + drawtype = 'liquid'; 130 + waving = 3; 131 + tiles = { 132 + { 133 + name = "default_water_source_animated.png"; 134 + backface_culling = false; 135 + animation = { 136 + type = "vertical_frames"; 137 + aspect_w = 16; 138 + aspect_h = 16; 139 + length = 2.0; 140 + }; 141 + }; 142 + { 143 + name = "default_water_source_animated.png"; 144 + backface_culling = true; 145 + animation = { 146 + type = "vertical_frames"; 147 + aspect_w = 16; 148 + aspect_h = 16; 149 + length = 2.0; 150 + }; 151 + }; 152 + }; 153 + use_texture_alpha = 'blend'; 154 + paramtype = 'light'; 155 + walkable = false, pointable = "blocking", diggable = false, buildable_to = true; 156 + is_ground_content = false; 157 + drop = ''; 158 + drowning = 1; 159 + liquidtype = w and 'flowing' or 'source'; 160 + liquid_alternative_flowing = 'starlit:liquid_water_flowing'; 161 + liquid_alternative_source = 'starlit:liquid_water'; 162 + liquid_viscosity = 1; 163 + liquid_renewable = true; 164 + liquid_range = 2; 165 + drowning = 40; 166 + post_effect_color = {a=103, r=10, g=40, b=70}; 167 + groups = {water = 3, liquid = 3}; 168 + }); 169 +end 170 + 171 + 172 +starlit.world.mineral.foreach('starlit:mineral_generate', {}, function(name,m) 173 + local node = string.format('starlit:mineral_%s', name) 174 + local grp = {mineral = 1} 175 + minetest.register_node(node, { 176 + description = m.desc; 177 + tiles = m.tiles or 178 + (m.tone and { 179 + string.format('default_stone.png^[colorizehsl:%s:%s:%s', 180 + m.tone.hue, m.tone.sat, m.tone.lum) 181 + }) or {'default_stone.png'}; 182 + groups = grp; 183 + drop = m.rocks or ''; 184 + _starlit = { 185 + kind = 'block'; 186 + elements = m.elements; 187 + fab = m.fab; 188 + recover = m.recover; 189 + recover_vary = m.recover_vary; 190 + }; 191 + }) 192 + if not m.excludeOre then 193 + local seed = 0 194 + grp.ore = 1 195 + for i = 1, #m.name do 196 + seed = seed*50 + string.byte(name, i) 197 + end 198 + minetest.register_ore { 199 + ore = node; 200 + ore_type = m.dist.kind; 201 + wherein = {m.dist.among}; 202 + clust_scarcity = m.dist.rare; 203 + y_max = m.dist.height[1], y_min = m.dist.height[2]; 204 + noise_params = m.dist.noise or { 205 + offset = 28; 206 + scale = 16; 207 + spread = vector.new(128,128,128); 208 + seed = seed; 209 + octaves = 1; 210 + }; 211 + } 212 + end 213 +end) 214 + 215 +starlit.world.mineral.link('feldspar', { 216 + desc = T 'Feldspar'; 217 + excludeOre = true; 218 + recover = starlit.type.fab { 219 + time = { 220 + shred = 3; 221 + }; 222 + cost = { 223 + shredPower = 3; 224 + }; 225 + }; 226 + recover_vary = function(rng, ctx) 227 + -- print('vary!', rng:int(), rng:int(0,10)) 228 + return starlit.type.fab { 229 + element = { 230 + aluminum = rng:int(0,4); 231 + potassium = rng:int(0,2); 232 + calcium = rng:int(0,2); 233 + } 234 + }; 235 + end; 236 +}) 237 + 238 +-- map generation 239 + 240 +minetest.register_alias('mapgen_stone', 'starlit:mineral_feldspar') 241 +minetest.register_alias('mapgen_water_source', 'starlit:liquid_water') 242 +minetest.register_alias('mapgen_river_water_source', 'starlit:liquid_water') 243 +
Added mods/starlit/tiers.lua version [513c94b946].
1 +local lib = starlit.mod.lib 2 + 3 +starlit.world.tier = lib.registry.mk 'starlit:tier' 4 +local T = starlit.world.tier 5 +local fab = starlit.type.fab 6 + 7 +function starlit.world.tier.fabsum(name, ty) 8 + local dest = fab {} 9 + local t = starlit.world.tier.db[name] 10 + assert(t, 'reference to nonexisting tier '..name) 11 + if t.super then 12 + dest = dest+starlit.world.tier.fabsum(t.super, ty)*(t.cost or 1) 13 + end 14 + if t.fabclasses and t.fabclasses[ty] then 15 + dest = dest + t.fabclasses[ty] 16 + end 17 + return dest 18 +end 19 + 20 +function starlit.world.tier.tech(name, tech) 21 + local t = starlit.world.tier.db[name] 22 + if t.techs and t.techs[tech] ~= nil then return t.techs[tech] end 23 + if t.super then return starlit.world.tier.tech(t.super, tech) end 24 + return false 25 +end 26 + 27 +T.meld { 28 + base = { 29 + fabclass = { 30 + electric = fab {metal={copper = 10}}; 31 + suit = fab {element={carbon = 1e3}}; 32 + psi = fab {metal={numinium = 1}}; 33 + bio = fab {element={carbon = 1}}; 34 + }; 35 + 36 + }; -- properties that apply to all tiers 37 + ------------------ 38 + -- tier classes -- 39 + ------------------ 40 + 41 + lesser = { 42 + name = 'Lesser', adj = 'Lesser'; 43 + super = 'base'; 44 + fabclasses = { 45 + basis = fab { 46 + metal = {aluminum=4}; 47 + }; 48 + }; 49 + }; 50 + greater = { 51 + name = 'Greater', adj = 'Greater'; 52 + super = 'base'; 53 + fabclasses = { 54 + basis = fab { 55 + metal = {vanadium=2}; 56 + }; 57 + }; 58 + }; 59 + starlit = { 60 + name = 'Starsoul', adj = 'Starsoul'; 61 + super = 'base'; 62 + fabclasses = { 63 + basis = fab { 64 + metal = {osmiridium=1}; 65 + }; 66 + }; 67 + }; 68 + forevanished = { 69 + name = 'Forevanished One', adj = 'Forevanished'; 70 + super = 'base'; 71 + fabclasses = { 72 + basis = fab { 73 + metal = {elusium=1}; 74 + }; 75 + }; 76 + }; 77 + 78 + ------------------ 79 + -- Lesser Races -- 80 + ------------------ 81 + 82 + makeshift = { -- regular trash 83 + name = 'Makeshift', adj = 'Makeshift'; 84 + super = 'lesser'; 85 + techs = {tool = true, prim = true, electric = true}; 86 + power = 0.5; 87 + efficiency = 0.3; 88 + reliability = 0.2; 89 + cost = 0.3; 90 + fabclasses = { -- characteristic materials 91 + basis = fab { -- fallback 92 + metal = {iron=3}; 93 + }; 94 + }; 95 + }; 96 + 97 + imperial = { --powerful trash 98 + name = 'Imperial', adj = 'Imperial'; 99 + super = 'lesser'; 100 + techs = {tool = true, electric = true, electronic = true, suit = true, combatSuit = true, weapon = true, hover='ion'}; 101 + power = 2.0; 102 + efficiency = 0.5; 103 + reliability = 0.5; 104 + cost = 1.0; 105 + fabclasses = { 106 + basis = fab { 107 + metal = {steel=2}; 108 + }; 109 + }; 110 + }; 111 + 112 + commune = { --reliability 113 + name = 'Commune', adj = 'Commune'; 114 + super = 'lesser'; 115 + techs = {tool = true, electric = true, electronic = true, suit = true, combatSuit = true, weapon = true, gravitic = true, hover='grav'}; 116 + power = 1.0; 117 + efficiency = 2.0; 118 + reliability = 3.0; 119 + cost = 1.5; 120 + fabclasses = { 121 + basis = fab { 122 + metal = {titanium=1}; 123 + time = {print = 1.2}; -- commune stuff is intricate 124 + }; 125 + }; 126 + }; 127 + 128 + ------------------- 129 + -- Greater Races -- 130 + ------------------- 131 + 132 + 133 + ---------------- 134 + -- Starsouled -- 135 + ---------------- 136 + 137 + suIkuri = { --super-tier 138 + name = 'Su\'ikuri', adj = "Su'ikuruk"; 139 + super = 'starlit'; 140 + techs = {psi = true, prim = true, bioSuit = true, psiSuit = true}; 141 + power = 1.5; 142 + efficiency = 1.0; 143 + reliability = 3.0; 144 + cost = 2.0; 145 + fabclasses = { 146 + psi = fab { 147 + metal = {numinium = 2.0}; 148 + crystal = {beryllium = 1.0}; 149 + }; 150 + bio = fab { 151 + crystal = {beryllium = 1.0}; 152 + }; 153 + }; 154 + }; 155 + 156 + usukwinya = { --value for 'money'; no weapons; no hovertech (they are birds) 157 + -- NOTA BENE: the ususkwinya *do* have weapons of their own; however, 158 + -- they are extremely restricted and never made available except to a 159 + -- very select number of that species. consequently, usuk players 160 + -- of a certain scenario may have usuk starting weapons, but these must 161 + -- be manually encoded to avoid injecting them into the overall crafting 162 + -- /loot system. because there are so few of these weapons in existence, 163 + -- all so tightly controlled, the odds of the weapons or plans winding 164 + -- up on Farthest Shadow are basically zero unless you bring them yourself 165 + name = 'Usukwinya', adj = 'Usuk'; 166 + super = 'starlit'; 167 + techs = lib.tbl.set('tool', 'electric', 'electronic', 'suit', 'gravitic'); 168 + power = 2.0; 169 + efficiency = 2.0; 170 + reliability = 2.0; 171 + cost = 0.5; 172 + fabclasses = { 173 + basis = fab { 174 + crystal = {aluminum = 5}; -- ruby 175 + }; 176 + }; 177 + }; 178 + 179 + eluthrai = { --super-tier 180 + name = 'Eluthrai', adj = 'Eluthran'; 181 + super = 'starlit'; 182 + techs = {tool = true, electric = true, electronic = true, weapon = true, gravitic = true, gravweapon = true, suit = true, combatSuit = true, hover = 'grav'}; 183 + power = 4.0; 184 + efficiency = 4.0; 185 + reliability = 4.0; 186 + cost = 4.0; 187 + fabclasses = { 188 + basis = fab { 189 + crystal = {carbon = 5}; -- diamond 190 + }; 191 + special = fab { 192 + metal = {technetium=1, cinderstone=1} 193 + }; 194 + }; 195 + }; 196 + 197 + ----------------------- 198 + -- Forevanished Ones -- 199 + ----------------------- 200 + 201 + firstborn = { --god-tier 202 + name = 'Firstborn', adj = 'Firstborn'; 203 + super = 'forevanished'; 204 + techs = {tool = true, electric = true, electronic = true, suit = true, psi = true, combatSuit = true, weapon = true, gravitic = true, gravweapon = true}; 205 + power = 10.0; 206 + efficiency = 5.0; 207 + reliability = 3.0; 208 + cost = 10.0; 209 + fabclasses = { 210 + basis = fab { 211 + metal = {technetium=2, neodymium=3, sunsteel=1}; 212 + crystal = {astrite=1}; 213 + }; 214 + }; 215 + }; 216 + 217 + forevanisher = { --godslayer-tier 218 + name = 'Forevanisher', adj = 'Forevanisher'; 219 + super = 'forevanished'; 220 + techs = {tool = true, electric = true, electronic = true, suit = true, psi = true, combatSuit = true, weapon = true, gravitic = true, gravweapon = true}; 221 + power = 20.0; 222 + efficiency = 1.0; 223 + reliability = 2.0; 224 + cost = 100.0; 225 + fabclasses = { 226 + basis = fab { 227 + metal = {}; 228 + crystal = {}; 229 + }; 230 + }; 231 + }; 232 + 233 +}
Added mods/starlit/ui.lua version [7085b387cd].
1 +local lib = starlit.mod.lib 2 + 3 +starlit.ui = {} 4 + 5 +starlit.type.ui = lib.class { 6 + name = 'starlit:ui'; 7 + __index = { 8 + action = function(self, user, state, fields) 9 + local pg = self.pages[state.page or 'index'] 10 + if not pg then return end 11 + if pg.handle then 12 + local redraw, reset = pg.handle(state, user, fields) 13 + if reset then pg.setupState(state,user) end 14 + if redraw then self:show(user) end 15 + end 16 + if fields.quit then self:cb('onClose', user) end 17 + end; 18 + cb = function(self, name, user, ...) 19 + local state = self:begin(user) 20 + if self[name] then self[name](state, user, ...) end 21 + local pcb = self.pages[state.page][name] 22 + if pcb then pcb(state, user, ...) end 23 + end; 24 + begin = function(self, user, page, ...) 25 + local state = starlit.activeUI[user.name] 26 + if state and state.form ~= self.id then 27 + state = nil 28 + starlit.activeUI[user.name] = nil 29 + end 30 + local created = state == nil 31 + 32 + if not state then 33 + state = { 34 + page = page or 'index'; 35 + form = self.id; 36 + } 37 + starlit.activeUI[user.name] = state 38 + self:cb('setupState', user, ...) 39 + elseif page ~= nil and state.page ~= page then 40 + state.page = page 41 + local psetup = self.pages[state.page].setupState 42 + if psetup then psetup(state,user, ...) end 43 + end 44 + return state, created 45 + end; 46 + render = function(self, state, user) 47 + return self.pages[state.page].render(state, user) 48 + end; 49 + show = function(self, user) 50 + local state = self:begin(user) 51 + minetest.show_formspec(user.name, self.id,self:render(state, user)) 52 + end; 53 + open = function(self, user, page, ...) 54 + user:suitSound 'starlit-nav' 55 + self:begin(user, page, ...) 56 + self:show(user) 57 + end; 58 + close = function(self, user) 59 + local state = starlit.activeUI[user.name] 60 + if state and state.form == self.id then 61 + self:cb('onClose', user) 62 + starlit.activeUI[user.name] = nil 63 + minetest.close_formspec(user.name, self.id) 64 + end 65 + end; 66 + }; 67 + construct = function(p) 68 + if not p.id then error('UI missing id') end 69 + p.pages = p.pages or {} 70 + return p 71 + end; 72 +} 73 + 74 +function starlit.interface.install(ui) 75 + starlit.interface.link(ui.id, ui) 76 +end 77 + 78 +function starlit.ui.build(def, parent) 79 + local clr = def.color 80 + if clr and lib.color.id(clr) then 81 + clr = clr:to_hsl_o() 82 + end 83 + local state = { 84 + x = (def.x or 0); 85 + y = (def.y or 0); 86 + w = def.w or 0, h = def.h or 0; 87 + fixed = def.fixed or false; 88 + spacing = def.spacing or 0; 89 + padding = def.padding or 0; 90 + align = def.align or (parent and parent.align) or 'left'; 91 + lines = {}; 92 + mode = def.mode or (parent and parent.mode or nil); -- hw or sw 93 + gen = (parent and parent.gen or 0) + 1; 94 + fg = def.fg or (parent and parent.fg); 95 + color = clr or (parent and parent.color) or { 96 + hue = 260, sat = 0, lum = 0 97 + }; 98 + } 99 + local lines = state.lines 100 + local cmod = string.format('^[hsl:%s:%s:%s', 101 + state.color.hue, state.color.sat*0xff, state.color.lum*0xff) 102 + 103 + local E = minetest.formspec_escape 104 + if state.padding/2 > state.x then state.x = state.padding/2 end 105 + if state.padding/2 > state.y then state.y = state.padding/2 end 106 + 107 + local function btnColorDef(sel) 108 + local function climg(state,img) 109 + local selstr 110 + if sel == nil then 111 + selstr = string.format( 112 + 'button%s,' .. 113 + 'button_exit%s,' .. 114 + 'image_button%s,' .. 115 + 'item_image_button%s', 116 + state, state, state, state) 117 + else 118 + selstr = E(sel) .. state 119 + end 120 + 121 + return string.format('%s[%s;' .. 122 + 'bgimg=%s;' .. 123 + 'bgimg_middle=16;' .. 124 + 'content_offset=0,0' .. 125 + ']', sel and 'style' or 'style_type', 126 + selstr, E(img..'^[resize:48x48'..cmod)) 127 + end 128 + 129 + return climg('', 'starlit-ui-button-sw.png') .. 130 + climg(':hovered', 'starlit-ui-button-sw-hover.png') .. 131 + climg(':pressed', 'starlit-ui-button-sw-press.png') 132 + end 133 + local function widget(...) 134 + table.insert(lines, string.format(...)) 135 + end 136 + if def.kind == 'vert' then 137 + for _, w in ipairs(def) do 138 + local src, st = starlit.ui.build(w, state) 139 + widget('container[%s,%s]%scontainer_end[]', state.x, state.y, src) 140 + state.y=state.y + state.spacing + st.h 141 + state.w = math.max(state.w, st.w) 142 + end 143 + state.w = state.w + state.padding 144 + state.h = state.y + state.padding/2 145 + elseif def.kind == 'hztl' then 146 + for _, w in ipairs(def) do 147 + local src, st = starlit.ui.build(w, state) 148 + widget('container[%s,%s]%scontainer_end[]', state.x, state.y, src) 149 + -- TODO alignments 150 + state.x=state.x + state.spacing + st.w 151 + state.h = math.max(state.h, st.h) 152 + end 153 + state.h = state.h + state.padding 154 + state.w = state.x + state.padding/2 155 + elseif def.kind == 'list' then 156 + local slotTypes = { 157 + plain = {hue = 200, sat = -.1, lum = 0}; 158 + element = {hue = 20, sat = -.3, lum = 0}; 159 + chip = {hue = 0, sat = -1, lum = 0}; 160 + psi = {hue = 300, sat = 0, lum = 0}; 161 + power = {hue = 50, sat = 0, lum = .2}; 162 + } 163 + local img 164 + if state.mode == 'hw' then 165 + img = lib.image('starlit-ui-slot-physical.png'); 166 + else 167 + img = lib.image('starlit-ui-slot.png'):shift(slotTypes[def.listContent or 'plain']); 168 + end 169 + local spac = state.spacing 170 + widget('style_type[list;spacing=%s,%s]',spac,spac) 171 + assert(def.w and def.h, 'ui-lists require a fixed size') 172 + for lx = 0, def.w-1 do 173 + for ly = 0, def.h-1 do 174 + local ox, oy = state.x + lx*(1+spac), state.y + ly*(1+spac) 175 + table.insert(lines, string.format('image[%s,%s;1.1,1.1;%s]', ox-0.05,oy-0.05, img:render())) 176 + end end 177 + table.insert(lines, string.format('listcolors[#00000000;#ffffff10]')) -- FIXME 178 + table.insert(lines, string.format('list[%s;%s;%s,%s;%s,%s;%s]', 179 + E(def.target), E(def.inv), 180 + state.x, state.y, 181 + def.w, def.h, 182 + def.idx)) 183 + local sm = 1 184 + state.w = def.w * sm + (spac * (def.w - 1)) 185 + state.h = def.h * sm + (spac * (def.h - 1)) 186 + elseif def.kind == 'contact' then 187 + if def.color then table.insert(lines, btnColorDef(def.id)) end 188 + widget('image_button%s[%s,%s;%s,%s;%s;%s;%s]', 189 + def.close and '_exit' or '', 190 + state.x, state.y, def.w, def.h, 191 + E(def.img), E(def.id), E(def.label or '')) 192 + elseif def.kind == 'button' then 193 + if def.color then table.insert(lines, btnColorDef(def.id)) end 194 + local label = E(def.label or '') 195 + if state.fg then label = lib.color(state.fg):fmt(label) end 196 + widget('button%s[%s,%s;%s,%s;%s;%s]', 197 + def.close and '_exit' or '', 198 + state.x, state.y, def.w, def.h, 199 + E(def.id), label) 200 + elseif def.kind == 'img' then 201 + widget('%s[%s,%s;%s,%s;%s]', 202 + def.item and 'item_image' or 'image', 203 + state.x, state.y, def.w, def.h, E(def.item or def.img)) 204 + elseif def.kind == 'label' then 205 + local txt = E(def.text) 206 + if state.fg then txt = lib.color(state.fg):fmt(txt) end 207 + widget('label[%s,%s;%s]', 208 + state.x, state.y + def.h*.5, txt) 209 + elseif def.kind == 'text' then 210 + -- TODO paragraph formatter 211 + widget('hypertext[%s,%s;%s,%s;%s;%s]', 212 + state.x, state.y, def.w, def.h, E(def.id), E(def.text)) 213 + elseif def.kind == 'hbar' or def.kind == 'vbar' then -- TODO fancy image bars 214 + local cl = lib.color(state.color) 215 + local fg = state.fg or cl:readable(.8,1) 216 + local wfac, hfac = 1,1 217 + local clamp = math.min(math.max(def.fac, 0), 1) 218 + if def.kind == 'hbar' 219 + then wfac = wfac * clamp 220 + else hfac = hfac * clamp 221 + end 222 + local x,y, w,h = state.x, state.y, def.w, def.h 223 + widget('box[%s,%s;%s,%s;%s]', 224 + x,y, w,h, cl:brighten(0.2):hex()) 225 + widget('box[%s,%s;%s,%s;%s]', 226 + x, y + (h*(1-hfac)), w * wfac, h * hfac, cl:hex()) 227 + if def.text then 228 + widget('hypertext[%s,%s;%s,%s;;%s]', 229 + state.x, state.y, def.w, def.h, 230 + string.format('<global halign=center valign=middle color=%s>%s', fg:hex(), E(def.text))) 231 + end 232 + end 233 + 234 + if def.desc then 235 + widget('tooltip[%s,%s;%s,%s;%s]', 236 + state.x, state.y, def.w, def.h, E(def.desc)) 237 + end 238 + 239 + local originX = (parent and parent.x or 0) 240 + local originY = (parent and parent.y or 0) 241 + local l = table.concat(lines) 242 + -- if state.fixed and (state.w < state.x or state.h < state.y) then 243 + -- l = string.format('scroll_container[%s,%s;%s,%s;scroll_%s;%s]%sscroll_container_end[]', 244 + -- (parent and parent.x or 0), (parent and parent.y or 0), 245 + -- state.w, state.h, state.gen, 246 + -- (state.x > state.w) and 'horizontal' or 'vertical', l) 247 + -- end 248 + 249 + 250 + if def.mode or def.container then 251 + if def.mode then 252 + l = string.format('background9[%s,%s;%s,%s;%s;false;64]', 253 + originX, originY, state.w, state.h, 254 + E(string.format('starlit-ui-bg-%s.png%s^[resize:128x128', 255 + (def.mode == 'sw') and 'digital' 256 + or 'panel', cmod))) .. l 257 + end 258 + if parent == nil or state.color ~= parent.color then 259 + l = btnColorDef() .. l 260 + end 261 + end 262 + if not parent then 263 + return string.format('formspec_version[6]size[%s,%s]%s', state.w, state.h, l), state 264 + else 265 + return l, state 266 + end 267 +end 268 + 269 +starlit.ui.tooltip = lib.ui.tooltipper { 270 + colors = { 271 + -- generic notes 272 + neutral = lib.color(.5,.5,.5); 273 + good = lib.color(.2,1,.2); 274 + bad = lib.color(1,.2,.2); 275 + info = lib.color(.4,.4,1); 276 + -- chip notes 277 + schemaic = lib.color(.2,.7,1); 278 + ability = lib.color(.7,.2,1); 279 + driver = lib.color(1,.7,.2); 280 + }; 281 +}
Added mods/starlit/user.lua version [aee7410825].
1 +-- [ʞ] user.lua 2 +-- ~ lexi hale <lexi@hale.su> 3 +-- © EUPL v1.2 4 +-- ? defines the starlit.type.user class, which is 5 +-- the main interface between the game world and the 6 +-- client. it provides for initial signup and join, 7 +-- managing the HUD, skinning the player model, 8 +-- effecting weather changes, etc. 9 + 10 +local lib = starlit.mod.lib 11 + 12 +local function hudAdjustBacklight(img) 13 + local night = math.abs(minetest.get_timeofday() - .5) * 2 14 + local opacity = night*0.8 15 + return img:fade(opacity) 16 +end 17 + 18 +local userStore = lib.marshal.metaStore { 19 + persona = { 20 + key = 'starlit:persona'; 21 + type = starlit.store.persona; 22 + }; 23 +} 24 + 25 +local suitStore = starlit.store.suitMeta 26 + 27 +starlit.type.user = lib.class { 28 + name = 'starlit:user'; 29 + construct = function(ident) 30 + local name, luser 31 + if type(ident) == 'string' then 32 + name = ident 33 + luser = minetest.get_player_by_name(name) 34 + else 35 + luser = ident 36 + name = luser:get_player_name() 37 + end 38 + return { 39 + entity = luser; 40 + name = name; 41 + hud = { 42 + elt = {}; 43 + }; 44 + tree = {}; 45 + action = { 46 + bits = 0; -- for control deltas 47 + prog = {}; -- for recording action progress on a node; reset on refocus 48 + tgt = {type='nothing'}; 49 + sfx = {}; 50 + fx = {}; 51 + }; 52 + actMode = 'off'; 53 + power = { 54 + nano = {primary = nil, secondary = nil}; 55 + weapon = {primary = nil, secondary = nil}; 56 + psi = {primary = nil, secondary = nil}; 57 + maneuver = nil; 58 + }; 59 + pref = { 60 + calendar = 'commune'; 61 + }; 62 + } 63 + end; 64 + __index = { 65 + pullPersona = function(self) 66 + -- if later records are added in public updates, extend this function to merge them 67 + -- into one object 68 + local s = userStore(self.entity) 69 + self.persona = s.read 'persona' 70 + end; 71 + pushPersona = function(self) 72 + local s = userStore(self.entity) 73 + s.write('persona', self.persona) 74 + end; 75 + uiColor = function(self) return lib.color {hue=238,sat=.5,lum=.5} end; 76 + statDelta = function(self, stat, d, cause, abs) 77 + local dt = self.persona.statDeltas 78 + local base 79 + if abs then 80 + local min, max 81 + min, max, base = self:statRange(stat) 82 + if d == true then d = max 83 + elseif d == false then d = min end 84 + end 85 + if stat == 'health' then 86 + self.entity:set_hp(abs and d or (self.entity:get_hp() + d), cause) 87 + elseif stat == 'breath' then 88 + self.entity:set_breath(abs and d or (self.entity:get_breath() + d)) 89 + else 90 + if abs then 91 + dt[stat] = d - base 92 + else 93 + dt[stat] = dt[stat] + d 94 + end 95 + self:pushPersona() 96 + end 97 + self:updateHUD() 98 + -- TODO trigger relevant animations? 99 + end; 100 + lookupSpecies = function(self) 101 + return starlit.world.species.lookup(self.persona.species, self.persona.speciesVariant) 102 + end; 103 + phenoTrait = function(self, trait) 104 + local s,v = self:lookupSpecies() 105 + return v.traits[trait] or s.traits[trait] or 0 106 + end; 107 + statRange = function(self, stat) --> min, max, base 108 + return starlit.world.species.statRange( 109 + self.persona.species, self.persona.speciesVariant, stat) 110 + end; 111 + effectiveStat = function(self, stat) 112 + local val 113 + local min, max, base = self:statRange(stat) 114 + 115 + if stat == 'health' then 116 + val = self.entity:get_hp() 117 + elseif stat == 'breath' then 118 + val = self.entity:get_breath() 119 + else 120 + val = base + self.persona.statDeltas[stat] or 0 121 + end 122 + 123 + local d = max - min 124 + return val, (val - min) / d 125 + end; 126 + damageModifier = function(self, kind, amt) 127 + if kind == 'bluntForceTrauma' then 128 + local std = self:phenoTrait 'sturdiness' 129 + if std < 0 then 130 + amt = amt / 1+std 131 + else 132 + amt = amt * 1-std 133 + end 134 + end 135 + return amt 136 + end; 137 + attachImage = function(self, def) 138 + local user = self.entity 139 + local img = {} 140 + img.id = user:hud_add { 141 + type = 'image'; 142 + text = def.tex; 143 + scale = def.scale; 144 + alignment = def.align; 145 + position = def.pos; 146 + offset = def.ofs; 147 + z_index = def.z; 148 + } 149 + if def.update then 150 + img.update = function() 151 + def.update(user, function(prop, val) 152 + user:hud_change(img.id, prop, val) 153 + end, def) 154 + end 155 + end 156 + return img 157 + end; 158 + attachMeter = function(self, def) 159 + local luser = self.entity 160 + local m = {} 161 + local w = def.size or 80 162 + local szf = w / 80 163 + local h = szf * 260 164 + m.meter = luser:hud_add { 165 + type = 'image'; 166 + scale = {x = szf, y = szf}; 167 + alignment = def.align; 168 + position = def.pos; 169 + offset = def.ofs; 170 + z_index = def.z or 0; 171 + } 172 + local cx = def.ofs.x + (w/2)*def.align.x 173 + local cy = def.ofs.y + (h/2)*def.align.y 174 + local oy = cy + h/2 - 42 175 + -- this is so fucking fragile holy fuck 176 + m.readout = luser:hud_add { 177 + type = 'text'; 178 + scale = {x = w, y = h}; 179 + size = szf; 180 + style = 4; 181 + position = def.pos; 182 + alignment = {x=0,0}; 183 + offset = {x = cx, y = oy}; 184 + z_index = (def.z or 0)+1; 185 + number = 0xffffff; 186 + } 187 + m.destroy = function() 188 + luser:hud_remove(m.meter) 189 + luser:hud_remove(m.readout) 190 + end 191 + m.update = function() 192 + local v,txt,color,txtcolor = def.measure(luser,def) 193 + v = math.max(0, math.min(1, v)) 194 + local n = math.floor(v*16) + 1 195 + local img = hudAdjustBacklight(lib.image('starlit-ui-meter.png')) 196 + :colorize(color or def.color) 197 + if def.flipX then 198 + img = img:transform 'FX' 199 + end 200 + img = img:render() 201 + img = img .. '^[verticalframe:17:' .. tostring(17 - n) 202 + luser:hud_change(m.meter, 'text', img) 203 + if txt then 204 + luser:hud_change(m.readout, 'text', txt) 205 + end 206 + if txtcolor then 207 + luser:hud_change(m.readout, 'number', txtcolor:hex()) 208 + end 209 + end 210 + return m 211 + end; 212 + attachTextBox = function(self, def) 213 + local luser = self.entity 214 + local box = {} 215 + box.id = luser:hud_add { 216 + type = 'text'; 217 + text = ''; 218 + alignment = def.align; 219 + number = def.color and def.color:int24() or 0xFFffFF; 220 + scale = def.bound; 221 + size = {x = def.size, y=0}; 222 + style = def.style; 223 + position = def.pos; 224 + offset = def.ofs; 225 + } 226 + box.update = function() 227 + local text, color = def.text(self, box, def) 228 + luser:hud_change(box.id, 'text', text) 229 + if color then 230 + luser:hud_change(box.id, 'number', color:int24()) 231 + end 232 + end 233 + return box 234 + end; 235 + attachStatBar = function(self, def) 236 + local luser = self.entity 237 + local bar = {} 238 + local img = lib.image 'starlit-ui-bar.png' 239 + local colorized = img 240 + if type(def.color) ~= 'function' then 241 + colorized = colorized:shift(def.color) 242 + end 243 + 244 + bar.id = luser:hud_add { 245 + type = 'statbar'; 246 + position = def.pos; 247 + offset = def.ofs; 248 + name = def.name; 249 + text = colorized:render(); 250 + text2 = img:tint{hue=0, sat=-1, lum = -0.5}:fade(0.5):render(); 251 + number = def.size; 252 + item = def.size; 253 + direction = def.dir; 254 + alignment = def.align; 255 + size = {x=4,y=24}; 256 + } 257 + bar.update = function() 258 + local sv, sf = def.stat(self, bar, def) 259 + luser:hud_change(bar.id, 'number', def.size * sf) 260 + if type(def.color) == 'function' then 261 + local clr = def.color(sv, luser, sv, sf) 262 + luser:hud_change(bar.id, 'text', img:tint(clr):render()) 263 + end 264 + end 265 + return bar, {x=3 * def.size, y=16} -- x*2??? what 266 + end; 267 + createHUD = function(self) 268 + local function basicStat(statName) 269 + return function(user, bar) 270 + return self:effectiveStat(statName) 271 + end 272 + end 273 + local function batteryLookup(user) 274 + local max = user:suitPowerCapacity() 275 + if max == 0 then return 0, 0 end 276 + local ch = user:suitCharge() 277 + return (ch/max)*100, ch/max 278 + end 279 + local function C(h,s,l) return {hue=h,sat=s,lum=l} end 280 + local hbofs = (1+self.entity:hud_get_hotbar_itemcount()) * 25 281 + local bpad = 8 282 + self.hud.elt.health = self:attachStatBar { 283 + name = 'health', stat = basicStat 'health'; 284 + color = C(340,0,.3), size = 100; 285 + pos = {x=0.5, y=1}, ofs = {x = -hbofs, y=-48 - bpad}; 286 + dir = 1; 287 + align = {x=-1, y=-1}; 288 + } 289 + self.hud.elt.stamina = self:attachStatBar { 290 + name = 'stamina', stat = basicStat 'stamina'; 291 + color = C(60,0,.2), size = 100; 292 + pos = {x=0.5, y=1}, ofs = {x = -hbofs, y=-24 - bpad}; 293 + dir = 1; 294 + align = {x=-1, y=-1}; 295 + } 296 + self.hud.elt.bat = self:attachStatBar { 297 + name = 'battery', stat = batteryLookup; 298 + color = C(190,0,.2), size = 100; 299 + pos = {x=0.5, y=1}, ofs = {x = hbofs - 4, y=-48 - bpad}; 300 + dir = 0; 301 + align = {x=1, y=-1}; 302 + } 303 + self.hud.elt.psi = self:attachStatBar { 304 + name = 'psi', stat = basicStat 'psi'; 305 + color = C(320,0,.2), size = 100; 306 + pos = {x=0.5, y=1}, ofs = {x = hbofs - 4, y=-24 - bpad}; 307 + dir = 0; 308 + align = {x=1, y=-1}; 309 + } 310 + self.hud.elt.time = self:attachTextBox { 311 + name = 'time'; 312 + align = {x=0, y=1}; 313 + pos = {x=0.5, y=1}; 314 + ofs = {x=0,y=-95}; 315 + text = function(user) 316 + local cal = starlit.world.time.calendar[user.pref.calendar] 317 + return cal.time(minetest.get_timeofday()) 318 + end; 319 + } 320 + self.hud.elt.temp = self:attachMeter { 321 + name = 'temp'; 322 + align = {x=1, y=-1}; 323 + pos = {x=0, y=1}; 324 + ofs = {x=20, y=-20}; 325 + measure = function(user) 326 + local warm = self:effectiveStat 'warmth' 327 + local n, color if warm < 0 then 328 + n = math.min(100, -warm) 329 + color = lib.color(0.1,0.3,1):lerp(lib.color(0.7, 1, 1), math.min(1, n/50)) 330 + else 331 + n = math.min(100, warm) 332 + color = lib.color(0.1,0.3,1):lerp(lib.color(1, 0, 0), math.min(1, n/50)) 333 + end 334 + local txt = string.format("%s°", math.floor(warm)) 335 + return (n/50), txt, color 336 + end; 337 + } 338 + self.hud.elt.geiger = self:attachMeter { 339 + name = 'geiger'; 340 + align = {x=-1, y=-1}; 341 + pos = {x=1, y=1}; 342 + ofs = {x=-20, y=-20}; 343 + flipX = true; 344 + measure = function(user) 345 + local hot = self:effectiveStat 'irradiation' 346 + local color = self:uiColor():lerp(lib.color(0.3, 1, 0), math.min(1, hot/5)) 347 + local txt = string.format("%sGy", math.floor(hot)) 348 + return (hot/5), txt, color 349 + end; 350 + } 351 + self.hud.elt.crosshair = self:attachImage { 352 + name = 'crosshair '; 353 + tex = ''; 354 + pos = {x=.5, y=.5}; 355 + scale = {x=1,y=1}; 356 + ofs = {x=0, y=0}; 357 + align = {x=0, y=0}; 358 + update = function(user, set) 359 + local imgs = { 360 + off = ''; 361 + nano = 'starlit-ui-crosshair-nano.png'; 362 + psi = 'starlit-ui-crosshair-psi.png'; 363 + weapon = 'starlit-ui-crosshair-weapon.png'; 364 + } 365 + set('text', imgs[self.actMode] or imgs.off) 366 + end; 367 + }; 368 + local hudCenterBG = lib.image 'starlit-ui-hud-bg.png':colorize(self:uiColor()) 369 + self.hud.elt.bg = self:attachImage { 370 + name = 'hudBg'; 371 + tex = hudCenterBG:render(); 372 + pos = {x=.5, y=1}; 373 + scale = {x=1,y=1}; 374 + ofs = {x=0, y=0}; 375 + align = {x=0, y=-1}; 376 + z = -1; 377 + update = function(user, set) 378 + set('text', hudAdjustBacklight(hudCenterBG):render()) 379 + end; 380 + }; 381 + end; 382 + onModeChange = function(self, oldMode, silent) 383 + self.hud.elt.crosshair.update() 384 + if not silent then 385 + local sfxt = { 386 + off = 'starlit-mode-off'; 387 + nano = 'starlit-mode-nano'; 388 + psi = 'starlit-mode-psi'; 389 + weapon = 'starlit-mode-weapon'; 390 + } 391 + local sfx = self.actMode and sfxt[self.actMode] or sfxt.off 392 + self:suitSound(sfx) 393 + end 394 + end; 395 + actModeSet = function(self, mode, silent) 396 + if not mode then mode = 'off' end 397 + local oldMode = self.actMode 398 + self.actMode = mode 399 + self:onModeChange(oldMode, silent) 400 + if mode ~= oldMode then 401 + starlit.ui.setupForUser(self) 402 + end 403 + end; 404 + deleteHUD = function(self) 405 + for name, e in pairs(self.hud.elt) do 406 + self:hud_delete(e.id) 407 + end 408 + end; 409 + updateHUD = function(self) 410 + for name, e in pairs(self.hud.elt) do 411 + if e.update then e.update() end 412 + end 413 + end; 414 + clientInfo = function(self) 415 + return minetest.get_player_information(self.name) 416 + end; 417 + onSignup = function(self) 418 + local meta = self.entity:get_meta() 419 + local inv = self.entity:get_inventory() 420 + -- 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 421 + inv:set_size('main', 6) -- carried items and tools. main hotbar. 422 + 423 + inv:set_size('starlit_suit', 1) -- your environment suit (change at wardrobe) 424 + inv:set_size('starlit_cfg', 1) -- the item you're reconfiguring / container you're accessing 425 + 426 + local scenario 427 + for _, e in pairs(starlit.world.scenario) do 428 + if e.id == starlit.world.defaultScenario then 429 + scenario = e break 430 + end 431 + end assert(scenario) 432 + self.persona = starlit.world.species.birth(scenario.species, scenario.speciesVariant, self.entity) 433 + self.persona.name = self.entity:get_player_name() -- a reasonable default 434 + self.persona.background = starlit.world.defaultScenario 435 + self:pushPersona() 436 + 437 + local gifts = scenario.startingItems 438 + local inv = self.entity:get_inventory() 439 + inv:set_stack('starlit_suit', 1, starlit.item.mk(gifts.suit, self, {gift=true})) 440 + self:getSuit():establishInventories(self.entity) 441 + 442 + local function giveGifts(name, list) 443 + if inv:get_size(name) > 0 then 444 + for i, e in ipairs(list) do 445 + inv:add_item(name, starlit.item.mk(e, self, {gift=true})) 446 + end 447 + end 448 + end 449 + 450 + giveGifts('starlit_suit_bat', gifts.suitBatteries) 451 + giveGifts('starlit_suit_chips', gifts.suitChips) 452 + giveGifts('starlit_suit_guns', gifts.suitGuns) 453 + giveGifts('starlit_suit_ammo', gifts.suitAmmo) 454 + giveGifts('starlit_suit_canisters', gifts.suitCans) 455 + 456 + giveGifts('main', gifts.carry) 457 + 458 + self:reconfigureSuit() 459 + 460 + -- i feel like there has to be a better way 461 + local cx = math.random(-500,500) 462 + local startPoint 463 + repeat local temp = -100 464 + local cz = math.random(-500,500) 465 + local cy = minetest.get_spawn_level(cx, cz) 466 + if cy then 467 + startPoint = vector.new(cx,cy,cz) 468 + temp = starlit.world.climate.eval(startPoint,.5,.5).surfaceTemp 469 + end 470 + if cx > 10000 then break end -- avoid infiniloop in pathological conditions 471 + until temp > -2 472 + self.entity:set_pos(startPoint) 473 + meta:set_string('starlit_spawn', startPoint:to_string()) 474 + end; 475 + onDie = function(self, reason) 476 + local inv = self.entity:get_inventory() 477 + local where = self.entity:get_pos() 478 + local function dropInv(lst) 479 + local l = inv:get_list(lst) 480 + for i, o in ipairs(l) do 481 + if o and not o:is_empty() then 482 + minetest.item_drop(o, self.entity, where) 483 + end 484 + end 485 + inv:set_list(lst, {}) 486 + end 487 + dropInv 'main' 488 + dropInv 'starlit_suit' 489 + self:statDelta('psi', 0, 'death', true) 490 + self:statDelta('hunger', 0, 'death', true) 491 + self:statDelta('thirst', 0, 'death', true) 492 + self:statDelta('fatigue', 0, 'death', true) 493 + self:statDelta('stamina', 0, 'death', true) 494 + self:updateSuit() 495 + end; 496 + onRespawn = function(self) 497 + local meta = self.entity:get_meta() 498 + self.entity:set_pos(vector.from_string(meta:get_string'starlit_spawn')) 499 + self:updateSuit() 500 + return true 501 + end; 502 + onJoin = function(self) 503 + local me = self.entity 504 + local meta = me:get_meta() 505 + self:pullPersona() 506 + 507 + -- formspec_version and real_coordinates are apparently just 508 + -- completely ignored here 509 + me:set_formspec_prepend [[ 510 + bgcolor[#00000000;true] 511 + style_type[button,button_exit,image_button,item_image_button;border=false] 512 + style_type[button;bgimg=starlit-ui-button-hw.png;bgimg_middle=8;content_offset=0,-2] 513 + style_type[button:hovered;bgimg=starlit-ui-button-hw-hover.png;bgimg_middle=8] 514 + style_type[button:pressed;bgimg=starlit-ui-button-hw-press.png;bgimg_middle=8;content_offset=0,1] 515 + ]] 516 + local hotbarSlots = me:get_inventory():get_size 'main'; 517 +-- local slotTex = 'starlit-ui-slot.png' 518 +-- local hbimg = string.format('[combine:%sx128', 128 * hotbarSlots) 519 +-- for i = 0, hotbarSlots-1 do 520 +-- hbimg = hbimg .. string.format(':%s,0=%s', 128 * i, slotTex) 521 +-- end 522 + --me:hud_set_hotbar_image(lib.image(hbimg):colorize(self:uiColor()):fade(.36):render()) 523 +-- me:hud_set_hotbar_selected_image(lib.image(slotTex):colorize(self:uiColor()):render()) 524 + me:hud_set_hotbar_image('[fill:1x24:0,0:' .. self:uiColor():fade(.1):hex()) 525 + me:hud_set_hotbar_selected_image( 526 + '[fill:1x24,0,0:' .. self:uiColor():fade(.4):hex() .. '^[fill:1x1:0,23:#ffFFffff' 527 + ) 528 + me:hud_set_hotbar_itemcount(hotbarSlots) 529 + me:hud_set_flags { 530 + hotbar = true; 531 + healthbar = false; 532 + breathbar = false; 533 + basic_debug = false; 534 + crosshair = false; 535 + } 536 + -- disable builtin crafting 537 + local inv = me:get_inventory() 538 + inv:set_size('craftpreview', 0) 539 + inv:set_size('craftresult', 0) 540 + inv:set_size('craft', 0) 541 + 542 + me:set_stars { 543 + day_opacity = 0.7; 544 + } 545 + me:set_sky { 546 + sky_color = { 547 + day_sky = '#a7c2cd', day_horizon = '#ddeeff'; 548 + dawn_sky = '#003964', dawn_horizon = '#87ebff'; 549 + night_sky = '#000000', night_horizon = '#000E29'; 550 + fog_sun_tint = '#72e4ff'; 551 + fog_moon_tint = '#2983d0'; 552 + fog_tint_type = 'custom'; 553 + }; 554 + fog = { -- not respected?? 555 + -- TODO make this seasonal & vary with weather 556 + fog_distance = 40; 557 + fog_start = 0.3; 558 + }; 559 + } 560 + me:set_sun { 561 + texture = 'starlit-sun.png'; 562 + sunrise = 'sunrisebg.png^[hsl:180:1:.7'; 563 + tonemap = 'sun_tonemap.png^[hsl:180:1:.7'; 564 + scale = 0.8; 565 + } 566 + me:set_lighting { 567 + shadows = { 568 + intensity = .5; 569 + }; 570 + exposure = { 571 + luminance_max = 3.0; 572 + speed_dark_bright = 0.5; 573 + speed_bright_dark = 1.0; 574 + }; 575 + volumetric_light = { 576 + strength = 0.3; 577 + }; 578 + } 579 + me:set_eye_offset(nil, vector.new(3,-.2,10)) 580 + -- TODO set_clouds speed in accordance with wind 581 + starlit.world.species.setupEntity(me, self.persona) 582 + starlit.ui.setupForUser(self) 583 + self:createHUD() 584 + self:updateSuit() 585 + end; 586 + suitStack = function(self) 587 + return self.entity:get_inventory():get_stack('starlit_suit', 1) 588 + end; 589 + suitSound = function(self, sfx) 590 + -- trigger a sound effect from the player's suit computer 591 + minetest.sound_play(sfx, {object=self.entity, max_hear_distance=4}, true) 592 + end; 593 + suitPowerStateSet = function(self, state, silent) 594 + -- necessary to enable reacting to power state changes 595 + -- e.g. to play sound effects, display warnings 596 + local os 597 + self:forSuit(function(s) 598 + os=s:powerState() 599 + s:powerStateSet(state) 600 + end) 601 + if state == 'off' then 602 + if self.actMode == 'nano' or self.actMode == 'weapon' then 603 + self:actModeSet('off', silent) 604 + end 605 + end 606 + if not silent and os ~= state then 607 + local sfx 608 + if state == 'off' then 609 + sfx = 'starlit-power-down' 610 + elseif os == 'off' then 611 + sfx = 'starlit-power-up' 612 + elseif state == 'powerSave' or os == 'powerSave' then 613 + sfx = 'starlit-configure' 614 + end 615 + if sfx then self:suitSound(sfx) end 616 + end 617 + end; 618 + species = function(self) 619 + return starlit.world.species.index[self.persona.species] 620 + end; 621 + updateBody = function(self) 622 + local adornment = {} 623 + local suitStack = self:suitStack() 624 + if suitStack and not suitStack:is_empty() then 625 + local suit = suitStack:get_definition()._starlit.suit 626 + suit.adorn(adornment, suitStack, self.persona) 627 + end 628 + starlit.world.species.updateTextures(self.entity, self.persona, adornment) 629 + end; 630 + updateSuit = function(self) 631 + self:updateBody() 632 + local inv = self.entity:get_inventory() 633 + local sst = suitStore(self:suitStack()) 634 + if self:naked() then 635 + starlit.type.suit.purgeInventories(self.entity) 636 + if self.actMode == 'nano' or self.actMode == 'weapon' then 637 + self:actModeSet 'off' 638 + end 639 + else 640 + local suit = self:getSuit() 641 + suit:establishInventories(self.entity) 642 + 643 + if self:suitCharge() <= 0 then 644 + self:suitPowerStateSet 'off' 645 + end 646 + end 647 + self:updateHUD() 648 + end; 649 + reconfigureSuit = function(self) 650 + -- and here's where things get ugly 651 + -- you can't have an inventory inside another item. to hack around this, 652 + -- we use the player as the location of the suit inventories, and whenever 653 + -- there's a change in the content of these inventories, this function is 654 + -- called to serialize those inventories out to the suit stack 655 + if self:naked() then return end 656 + local suit = self:getSuit() 657 + suit:onReconfigure(self.entity:get_inventory()) 658 + self:setSuit(suit) 659 + 660 + -- reconfiguring the suit can affect player abilities: e.g. removing 661 + -- / inserting a chip with a minimap program 662 + end; 663 + getSuit = function(self) 664 + local st = self:suitStack() 665 + if st:is_empty() then return nil end 666 + return starlit.type.suit(st) 667 + end; 668 + setSuit = function(self, suit) 669 + self.entity:get_inventory():set_stack('starlit_suit', 1, suit.item) 670 + end; 671 + changeSuit = function(self, ...) 672 + self:setSuit(...) 673 + self:updateSuit() 674 + end; 675 + forSuit = function(self, fn) 676 + local s = self:getSuit() 677 + if fn(s) ~= false then 678 + self:setSuit(s) 679 + end 680 + end; 681 + suitPowerCapacity = function(self) -- TODO optimize 682 + if self:naked() then return 0 end 683 + return self:getSuit():powerCapacity() 684 + end; 685 + suitCharge = function(self) -- TODO optimize 686 + if self:naked() then return 0 end 687 + return self:getSuit():powerLeft() 688 + end; 689 + suitDrawCurrent = function(self, power, time, whatFor, min) 690 + if self:naked() then return 0,0 end 691 + local inv = self.entity:get_inventory() 692 + local bl = inv:get_list('starlit_suit_bat') 693 + local supply = 0 694 + local wasteHeat = 0 --TODO handle internally 695 + for slot, ps in ipairs(bl) do 696 + if not ps:is_empty() then 697 + local p, h = starlit.mod.electronics.dynamo.drawCurrent(ps, power - supply, time) 698 + supply = supply + p 699 + wasteHeat = wasteHeat + h 700 + if power-supply <= 0 then break end 701 + end 702 + end 703 + if min and supply < min then return 0,0 end 704 + inv:set_list('starlit_suit_bat', bl) 705 + self:reconfigureSuit() 706 + if whatFor then 707 + -- TODO display power use icon 708 + end 709 + return supply, wasteHeat 710 + end; 711 + naked = function(self) 712 + return self:suitStack():is_empty() 713 + end; 714 + onPart = function(self) 715 + starlit.liveUI [self.name] = nil 716 + starlit.activeUI [self.name] = nil 717 + starlit.activeUsers[self.name] = nil 718 + end; 719 + openUI = function(self, id, page, ...) 720 + local ui = assert(starlit.interface.db[id]) 721 + ui:open(self, page, ...) 722 + end; 723 + onRespond = function(self, ui, state, resp) 724 + ui:action(self, state, resp) 725 + end; 726 + 727 + updateWeather = function(self) 728 + end; 729 + 730 + canInteract = function(self, with) 731 + return true; -- TODO 732 + end; 733 + 734 + trigger = function(self, which, how) 735 + --print('trigger', which, dump(how)) 736 + local p 737 + local wld = self.entity:get_wielded_item() 738 + if which == 'maneuver' then 739 + p = self.power.maneuver 740 + elseif which == 'retarget' then 741 + self.action.prog = {} 742 + elseif wld and not wld:is_empty() then 743 + local wdef = wld:get_definition() 744 + if wdef._starlit and wdef._starlit.tool then 745 + p = {tool = wdef._starlit.tool} 746 + end 747 + elseif self.actMode ~= 'off' then 748 + p = self.power[self.actMode][which] 749 + end 750 + if p == nil then return false end 751 + local ctx, run = { 752 + how = how; 753 + } 754 + if p.chipID then 755 + local inv = self.entity:get_inventory() 756 + local chips = inv:get_list 'starlit_suit_chips' 757 + for chSlot, ch in pairs(chips) do 758 + if ch and not ch:is_empty() then 759 + local d = starlit.mod.electronics.chip.read(ch) 760 + if d.uuid == p.chipID then 761 + local pgm = assert(d.files[p.pgmIndex], 'file missing for ability') 762 + ctx.file = starlit.mod.electronics.chip.fileHandle(ch, p.pgmIndex) 763 + ctx.saveChip = function() 764 + inv:set_slot('starlit_suit_chips', chSlot, ch) 765 + end 766 + local sw = starlit.item.sw.db[pgm.body.pgmId] 767 + run = assert(sw.run, 'missing run() for active software ability ' .. pgm.body.pgmId) 768 + break 769 + end 770 + end 771 + end 772 + else 773 + error('bad ability pointer ' .. dump(p)) 774 + end 775 + if run then 776 + run(self, ctx) 777 + return true 778 + end 779 + return false 780 + end; 781 + give = function(self, item) 782 + local inv = self.entity:get_inventory() 783 + local function is(grp) 784 + return minetest.get_item_group(item:get_name(), grp) ~= 0 785 + end 786 + -- TODO notif popups 787 + if is 'specialInventory' then 788 + if is 'powder' then 789 + if self:naked() then return item end 790 + local cans = inv:get_list 'starlit_suit_canisters' 791 + if cans and next(cans) then for i, st in ipairs(cans) do 792 + local lst = string.format('starlit_canister_%u_elem', i) 793 + item = inv:add_item(lst, item) 794 + if item:is_empty() then break end 795 + end end 796 + self:forSuit(function(x) x:pushCanisters(inv) end) 797 + end 798 + return item 799 + else 800 + return inv:add_item('main', item) 801 + end 802 + end; 803 + thrustUpon = function(self, item) 804 + local r = self:give(st) 805 + if not r:is_empty() then 806 + return minetest.add_item(self.entity:get_pos(), r) 807 + end 808 + end; 809 + }; 810 +} 811 + 812 +local biointerval = 3.0 813 +starlit.startJob('starlit:bio', biointerval, function(delta) 814 + for id, u in pairs(starlit.activeUsers) do 815 + 816 + end 817 +end) 818 + 819 +local cbit = { 820 + up = 0x001; 821 + down = 0x002; 822 + left = 0x004; 823 + right= 0x008; 824 + jump = 0x010; 825 + manv = 0x020; 826 + snk = 0x040; 827 + dig = 0x080; 828 + put = 0x100; 829 + zoom = 0x200; 830 +} 831 +-- this is the painful part 832 +minetest.register_globalstep(function(delta) 833 + local doNothing,mustInit,mustHalt = 0,1,2 834 + for id, user in pairs(starlit.activeUsers) do 835 + local ent = user.entity 836 + local bits = ent:get_player_control_bits() 837 + 838 + local function what(b) 839 + if bit.band(bits, b) ~= 0 and bit.band(user.action.bits, b) == 0 then 840 + return mustInit 841 + elseif bit.band(bits, b) == 0 and bit.band(user.action.bits, b) ~= 0 then 842 + return mustHalt 843 + else return doNothing end 844 + end 845 + local skipBits = 0 846 + if user.action.bits ~= bits then 847 + local mPrimary = what(cbit.dig) 848 + local mSecondary = what(cbit.put) 849 + if mPrimary == mustInit then -- ENGINE-BUG 850 + user.action.tgt = {type='nothing'} 851 + user.action.prog = {} 852 + elseif mPrimary == mustHalt then 853 + user:trigger('primary', {state='halt'}) 854 + end 855 + if mSecondary == mustHalt then 856 + user:trigger('secondary', {state='halt'}) 857 + end 858 + end 859 + --bits = bit.band(bits, bit.bnot(skipBits)) 860 + if bit.band(bits, cbit.dig)~=0 then 861 + user:trigger('primary', {state='prog', delta=delta}) 862 + end 863 + if bit.band(bits, cbit.put)~=0 then 864 + user:trigger('secondary', {state='prog', delta=delta}) 865 + end 866 + user.action.bits = bits 867 + -- ENGINE-BUG: dig and put are not handled equally in the 868 + -- engine. it is possible for the put bit to get stuck on 869 + -- if the key is hammered while the player is not moving. 870 + -- the bit will release as soon as the player looks or turns 871 + -- nonetheless this is obnoxious 872 + end 873 +end)
Added mods/starlit/world.lua version [830720f731].
1 +local lib = starlit.mod.lib 2 +local world = starlit.world 3 + 4 +function world.date() 5 + local days = minetest.get_day_count() 6 + local year = math.floor(days / world.planet.orbit); 7 + local day = days % world.planet.orbit; 8 + return { 9 + year = year, day = day; 10 + season = day / world.planet.orbit; 11 + } 12 +end 13 +local lerp = lib.math.lerp 14 + 15 +local function gradient(grad, pos) 16 + local n = #grad 17 + if n == 1 then return grad[1] end 18 + local op = pos*(n-1) 19 + local idx = math.floor(op) 20 + local t = op-idx 21 + return lerp(t, grad[1 + idx], grad[2 + idx]) 22 +end 23 + 24 +local altitudeCooling = 10 / 100 25 + 26 +-- this function provides the basis for temperature calculation, 27 +-- which is performed by adding this value to the ambient temperature, 28 +-- determined by querying nearby group:heatSource items in accordance 29 +-- with the inverse-square law 30 +function world.climate.eval(pos, tod, season) 31 + local data = minetest.get_biome_data(pos) 32 + local biome = world.ecology.biomes.db[minetest.get_biome_name(data.biome)] 33 + local heat, humid = data.heat, data.humidity 34 + tod = tod or minetest.get_timeofday() 35 + heat = lerp(math.abs(tod - 0.5)*2, heat, heat + biome.nightTempDelta) 36 + 37 + local td = world.date() 38 + heat = heat + gradient(biome.seasonalTemp, season or td.season) 39 + if pos.y > 0 then 40 + heat = heat - pos.y*altitudeCooling 41 + end 42 + 43 + return { 44 + surfaceTemp = heat; 45 + waterTemp = heat + biome.waterTempDelta; 46 + surfaceHumid = humid; 47 + } 48 +end 49 + 50 +local vdsq = lib.math.vdsq 51 +function world.climate.temp(pos) --> irradiance at pos in W 52 + local cl = world.climate.eval(pos) 53 + local radCenters = starlit.region.radiator.store:get_areas_for_pos(pos, false, true) 54 + local irradiance = 0 55 + for _,e in pairs(radCenters) do 56 + local rpos = minetest.string_to_pos(e.data) 57 + local rdef = assert(minetest.registered_nodes[assert(minetest.get_node(rpos)).name]) 58 + local rc = rdef._starlit.radiator 59 + local r_max = rc.radius(rpos) 60 + 61 + local dist_sq = vdsq(rpos,pos) 62 + if dist_sq <= r_max^2 then 63 + -- cheap bad way 64 + -- if minetest.line_of_sight(rpos,pos) then 65 + -- 66 + -- expensive way 67 + local obstruct = 0 68 + local ray = Raycast(rpos, pos, true, true) 69 + for p in ray do 70 + if p.type == 'node' then obstruct = obstruct + 1 end 71 + end 72 + 73 + if obstruct < 4 then 74 + local power, customFalloff = rc.radiate(rpos, pos) 75 + -- okay this isn't the real inverse square law but i 76 + -- couldn't figure out a better way to simplify the 77 + -- model without checking an ENORMOUS number of nodes 78 + -- maybe someone else who isn't completely 79 + -- mathtarded can do better. 80 + if not customFalloff then 81 + power = power * (1 - (dist_sq / ((r_max+1)^2))) 82 + end 83 + power = power * (1 - (obstruct/5)) 84 + irradiance = irradiance + power 85 + end 86 + end 87 + end 88 + return irradiance + cl.surfaceTemp 89 +end 90 + 91 +world.ecology.biomes.foreach('starlit:biome-gen', {}, function(id, b) 92 + b.def.name = id 93 + minetest.register_biome(b.def) 94 +end) 95 + 96 +world.ecology.biomes.link('starlit:steppe', { 97 + nightTempDelta = -30; 98 + waterTempDelta = 0; 99 + -- W Sp Su Au W 100 + seasonalTemp = {-50, -10, 5, 5, -20, -50}; 101 + def = { 102 + node_top = 'starlit:greengraze', depth_top = 1; 103 + node_filler = 'starlit:soil', depth_filler = 4; 104 + node_riverbed = 'starlit:sand', depth_riverbed = 4; 105 + y_min = 0; 106 + y_max = 512; 107 + heat_point = 10; 108 + humidity_point = 30; 109 + }; 110 +}) 111 + 112 +world.ecology.biomes.link('starlit:ocean', { 113 + nightTempDelta = -35; 114 + waterTempDelta = 5; 115 + seasonalTemp = {0}; -- no seasonal variance 116 + def = { 117 + y_max = 3; 118 + y_min = -512; 119 + heat_point = 15; 120 + humidity_point = 50; 121 + node_top = 'starlit:sand', depth_top = 1; 122 + node_filler = 'starlit:sand', depth_filler = 3; 123 + }; 124 +}) 125 + 126 +local toward = lib.math.toward 127 +local hfinterval = 1.5 128 +starlit.startJob('starlit:heatflow', hfinterval, function(delta) 129 + 130 + -- our base thermal conductivity (κ) is measured in °C/°C/s. say the 131 + -- player is in -30°C weather, and has an internal temperature of 132 + -- 10°C. then: 133 + -- κ = .1°C/C/s (which is apparently 100mHz) 134 + -- Tₚ = 10°C 135 + -- Tₑ = -30°C 136 + -- d = Tₑ − Tₚ = -40°C 137 + -- ΔT = κ×d = -.4°C/s 138 + -- our final change in temperature is computed as tΔC where t is time 139 + local kappa = .05 140 + for name,user in pairs(starlit.activeUsers) do 141 + local tr = user:species().tempRange 142 + local t = starlit.world.climate.temp(user.entity:get_pos()) 143 + local insul = 0 144 + local naked = user:naked() 145 + local suitDef 146 + if not naked then 147 + suitDef = user:suitStack():get_definition() 148 + insul = suitDef._starlit.suit.temp.insulation 149 + end 150 + 151 + local warm = user:effectiveStat 'warmth' 152 + local tSafeMin, tSafeMax = tr.survivable[1], tr.survivable[2] 153 + local tComfMin, tComfMax = tr.comfort[1], tr.comfort[2] 154 + 155 + local tDelta = (kappa * (1-insul)) * (t - warm) * hfinterval 156 + local tgt = warm + tDelta 157 + 158 + -- old logic: we move the user towards the exterior temperature, modulated 159 + -- by her suit insulation. 160 + --local tgt = toward(warm, t, hfinterval * thermalConductivity * (1 - insul)) 161 + 162 + if not naked then 163 + local suit = user:getSuit() 164 + local suitPower = suit:powerState() 165 + local suitPowerLeft = suit:powerLeft() 166 + if suitPower ~= 'off' then 167 + local coilPower = 1.0 168 + local st = suitDef._starlit.suit.temp 169 + if suitPower == 'powerSave' and (tgt >= tSafeMin and tgt <= tSafeMax) then coilPower = 0.5 end 170 + if tgt < tComfMin and st.maxHeat > 0 then 171 + local availPower = user:suitDrawCurrent(st.heatPower*coilPower, hfinterval) 172 + tgt = tgt + (availPower / st.heatPower) * st.maxHeat * coilPower * hfinterval 173 + end 174 + if tgt > tComfMax and st.maxCool > 0 then 175 + local availPower = user:suitDrawCurrent(st.coolPower*coilPower, hfinterval) 176 + tgt = tgt - (availPower / st.coolPower) * st.maxCool * coilPower * hfinterval 177 + end 178 + end 179 + end 180 + 181 + user:statDelta('warmth', tgt - warm) -- dopey but w/e 182 + 183 + warm = tgt -- for the sake of readable code 184 + 185 + if warm < tSafeMin or warm > tSafeMax then 186 + local dv 187 + if warm < tSafeMin then 188 + dv = math.abs(warm - tSafeMin) 189 + else 190 + dv = math.abs(warm - tSafeMax) 191 + end 192 + -- for every degree of difference you suffer 2 points of damage/s 193 + local dmg = math.ceil(dv * 2) 194 + user:statDelta('health', -dmg) 195 + end 196 + end 197 +end)
Deleted mods/starsoul-building/init.lua version [a42549c83d].
1 -local lib = starsoul.mod.lib 2 -local B = {} 3 -starsoul.mod.building = B 4 - 5 -B.path = {} 6 --- this maps stage IDs to tables of the following form 7 ---[[ { 8 - part = { 9 - ['starsoul_building:pipe'] = 'myMod:stage3'; 10 - }; 11 - tool = { 12 - ['starsoul:scredriver'] = 'myMod:otherThing_stage1'; 13 - ['starsoul:saw'] = function(node, tool) 14 - minetest.replace_node(node, {name='myMod:stage1'}) 15 - minetest.drop_item(node, 'starsoul_building:pipe') 16 - end; 17 - ['myMod:laserWrench'] = { 18 - allow = function(node, tool) ... end; 19 - handle = function(node, tool) ... end; 20 - }; 21 - }; 22 -} ]] 23 --- it should only be written by special accessor functions! 24 - 25 -B.stage = lib.registry.mk 'starsoul_building:stage' 26 --- a stage consists of a list of pieces and maps from possible materials 27 --- / tool usages to succeeding stages in the build tree. note that all 28 --- used pieces must be defined before a stage is defined currently, due 29 --- to a lack of cross-registry dependency mechanisms. i will hopefully 30 --- improve vtlib to handle this condition eventually. 31 ---[[ 32 - starsoul.mod.building.stage.link(id, { 33 - pieces = { 34 - 'starsoul_building:foundation'; 35 - 'starsoul_building:insulation'; -- offset ofsFac 36 - {'starsoul_building:pipe', vector.new(-.5, 0, 0), 0};-- 37 - {'starsoul_building:pipe', vector.new(-.5, 0, 0), 1}; 38 - 'starsoul_building:insulation'; 39 - 'starsoul_building:panel'; 40 - }; 41 - }) 42 -]] 43 - 44 -B.piece = lib.registry.mk 'starsoul_building:piece' 45 --- a piece is used to produce stage definitions, by means of appending 46 --- nodeboxes with appropriate offsets. it also lists the recoverable 47 --- materials which can be obtained by destroying a stage containing 48 --- this piece using nano. part IDs should correspond with piece IDs 49 --- where possible 50 ---[[ 51 - starsoul.mod.building.piece.link(id, { 52 - tex = 'myMod_part.png'; 53 - height = 0.1; -- used for auto-offset 54 - fab = { 55 - element = {iron=10}; 56 - }; 57 - shape = { 58 - type = "fixed"; 59 - fixed = { ... }; 60 - }; 61 - }) 62 -]] 63 - 64 -B.part = lib.registry.mk 'starsoul_building:part' 65 --- a part is implemented as a special craftitem with the proper callbacks 66 --- to index the registries and place/replace noes by reference to the 67 --- build tree. 68 ---[[ 69 - starsoul.mod.building.part.link(id, { 70 - name = ''; -- display name 71 - desc = ''; -- display desc 72 - img = ''; -- display image 73 - }) 74 -]] 75 - 76 -B.stage.foreach('starsoul:stageGen', {}, function(id, e) 77 - local box = {type = 'fixed', fixed = {}} 78 - local tex = {} 79 - local ofs = vector.new(0,0,0) 80 - for idx, p in ipairs(e.pieces) do 81 - local ho, pieceID, pos 82 - if type(p) == 'string' then 83 - pieceID, pos, ho = p, vector.zero(), 1.0 84 - else 85 - pieceID, pos, ho = pc[1],pc[2],pc[3] 86 - end 87 - local pc = B.piece.db[pieceID] 88 - pos = pos + ofs 89 - if ho ~= 0.0 then 90 - ofs = vector.offset(ofs, 0, pc.height) 91 - end 92 - local sh = lib.node.boxwarped(pc.shape, function(b) 93 - -- { -x, -y, -z; 94 - -- +x, +y, +z } 95 - b[1] = b[1] + ofs.x b[4] = b[4] + ofs.x 96 - b[2] = b[2] + ofs.y b[5] = b[5] + ofs.y 97 - b[3] = b[3] + ofs.z b[6] = b[6] + ofs.z 98 - end) 99 - table.insert(box, sh) 100 - if type(pc.tex) == 'string' then 101 - table.insert(tex, pc.tex) 102 - else 103 - for i,t in ipairs(pc.tex) do 104 - table.insert(tex, t or '') 105 - end 106 - end 107 - end 108 - minetest.register_node(id, { 109 - description = 'Construction'; 110 - drawtype = 'nodebox'; 111 - paramtype = 'light'; 112 - paramtype2 = e.stateful or 'none'; 113 - textures = tex; 114 - node_box = box; 115 - group = { stage = 1 }; 116 - _starsoul = { 117 - stage = id; 118 - }; 119 - }) 120 -end) 121 - 122 -function B.pathLink(from, kind, what, to) 123 - if not B.path[from] then 124 - B.path[from] = {part={}, tool={}} 125 - end 126 - local k = B.path[from][kind] 127 - assert(k[what] == nil) 128 - k[what] = to 129 -end 130 - 131 -function B.pathFind(from, kind, what) 132 - if not B.path[from] then return nil end 133 - return B.path[from][kind][what] 134 -end 135 -
Deleted mods/starsoul-building/mod.conf version [41dba78d03].
1 -name = starsoul_building 2 -depends = starsoul_electronics, starsoul 3 -description = implements construction elements
Deleted mods/starsoul-electronics/init.lua version [4ca9d32d80].
1 -local lib = starsoul.mod.lib 2 - 3 -local E = {} 4 -starsoul.mod.electronics = E 5 - 6 ---------------------- 7 --- item registries -- 8 ---------------------- 9 - 10 --- a dynamo is any item that produces power and can be slotted into a power 11 --- source slot. this includes batteries, but also things like radiothermal 12 --- dynamos. 13 -starsoul.item.dynamo = lib.registry.mk 'starsoul_electronics:dynamo' 14 - 15 --- batteries hold a charge of power (measured in kJ). how much they can hold 16 --- (and how much power they can discharge?) depends on their quality 17 -starsoul.item.battery = lib.registry.mk 'starsoul_electronics:battery' 18 - 19 --- a battery has the properties: 20 --- class 21 --- |- capacity (J ): amount of energy the battery can hold 22 --- |- dischargeRate (W ): rate at which battery can supply power/be charged 23 --- |- decay (J/J): rate at which the battery capacity degrades while 24 --- discharging. decay=0 batteries require no maintenance; 25 --- decay=1 batteries are effectively disposable 26 --- |- leak (fac): charging inefficiency. depends on the energy storage 27 --- technology. when N J are drawn from a power source, 28 --- only (N*leak) J actually make it into the battery. 29 --- leak=0 is a supercapacitor, leak=1 is /dev/null 30 --- |- size (m): each suit has a limit to how big of a battery it can take 31 --- instance 32 --- |- degrade (mJ): how much the battery has degraded. instance max charge is 33 --- | determined by $capacity - @degrade 34 --- |- %wear ÷ 2¹⁶ : used as a factor to determine battery charge 35 - 36 --- chips are standardized data storage hardware that can contain a certain amount 37 --- of software. in addition to their flash storage, they also provide a given amount 38 --- of working memory and processor power. processor power speeds up operations like 39 --- crafting, while programs require a certain amount of memory. 40 --- chips have a variable number of program slots and a single bootloader slot 41 --- 42 -starsoul.item.chip = lib.registry.mk 'starsoul_electronics:chip' 43 - 44 --- software is of one of the following types: 45 --- schematic: program for your matter compiler that enables crafting a given item. 46 --- output: the result 47 --- driver: inserted into a Core to control attached hardware 48 --- suitPower: provides suit functionality like nanoshredding or healing 49 --- passive powers are iterated on suit application/configuration and upon fst-tick 50 --- cost: what the software needs to run. some fields are fab-specific 51 --- energy: for fab, total energy cost of process in joules 52 --- for suitPassive, added suit power consumption in watts 53 -starsoul.item.sw = lib.registry.mk 'starsoul_electronics:sw' 54 --- chip = lib.color(0, 0, .3); 55 - 56 -E.schematicGroups = lib.registry.mk 'starsoul_electronics:schematicGroups' 57 -E.schematicGroupMembers = {} 58 -E.schematicGroups.foreach('starsoul_electronics:ensure-memlist', {}, function(id,g) 59 - E.schematicGroupMembers[id] = {} 60 -end) 61 -function E.schematicGroupLink(group, item) 62 - table.insert(E.schematicGroupMembers[group], item) 63 -end 64 - 65 -E.schematicGroups.link('starsoul_electronics:chip', { 66 - title = 'Chip', icon = 'starsoul-item-chip.png'; 67 - description = 'Standardized data storage and compute modules'; 68 -}) 69 - 70 -E.schematicGroups.link('starsoul_electronics:battery', { 71 - title = 'Battery', icon = 'starsoul-item-battery.png'; 72 - description = 'Portable power storage cells are essential to all aspects of survival'; 73 -}) 74 - 75 -E.schematicGroups.link('starsoul_electronics:decayCell', { 76 - title = 'Decay Cell', icon = 'starsoul-item-decaycell.png'; 77 - description = "Radioisotope generators can pack much more power into a smaller amount of space than conventional batteries, but they can't be recharged, dump power and heat whether they're in use or not, and their power yield drops towards zero over their usable lifetime."; 78 -}) 79 - 80 - 81 -------------------------- 82 --- batteries & dynamos -- 83 -------------------------- 84 - 85 -E.battery = {} 86 -local function accessor(ty, fn) 87 - return function(stack, ...) 88 - local function fail() 89 - error(string.format('object %q is not a %s', stack:get_name(), ty)) 90 - end 91 - 92 - if not stack or stack:is_empty() then fail() end 93 - 94 - if minetest.get_item_group(stack:get_name(), ty) == 0 then fail() end 95 - 96 - return fn(stack, 97 - stack:get_definition()._starsoul[ty], 98 - stack:get_meta(), ...) 99 - end 100 -end 101 - 102 --- return a wear level that won't destroy the item 103 --- local function safeWear(fac) return math.min(math.max(fac,0),1) * 0xFFFE end 104 --- local function safeWearToFac(w) return w/0xFFFE end 105 - 106 -E.battery.update = accessor('battery', function(stack, batClass, meta) 107 - -- local cap = E.battery.capacity(stack) 108 - local charge = meta:get_float 'starsoul_electronics:battery_charge' 109 - meta:set_string('count_meta', string.format('%s%%', math.floor(charge * 100))) 110 - meta:set_int('count_alignment', bit.lshift(3, 2) + 2) 111 -end) 112 - 113 --- E.battery.capacity(bat) --> charge (J) 114 -E.battery.capacity = accessor('battery', function(stack, batClass, meta) 115 - local dmg = meta:get_int 'starsoul_electronics:battery_degrade' -- µJ/μW 116 - local dmg_J = dmg / 1000 117 - return (batClass.capacity - dmg_J) 118 -end) 119 - 120 --- E.battery.charge(bat) --> charge (J) 121 -E.battery.charge = accessor('battery', function(stack, batClass, meta) 122 - local fac = meta:get_float 'starsoul_electronics:battery_charge' 123 - -- local fac = 1 - safeWearToFac(stack:get_wear()) 124 - return E.battery.capacity(stack) * fac 125 -end) 126 - 127 --- E.battery.dischargeRate(bat) --> dischargeRate (W) 128 -E.battery.dischargeRate = accessor('battery', function(stack, batClass, meta) 129 - local dmg = meta:get_int 'starsoul_electronics:battery_degrade' -- µJ/μW 130 - local dmg_W = dmg / 1000 131 - return batClass.dischargeRate - dmg_W 132 -end); 133 - 134 - 135 --- E.battery.drawCurrent(bat, power, time, test) --> supply (J), wasteHeat (J) 136 --- bat = battery stack 137 --- power J = joules of energy user wishes to consume 138 --- time s = the amount of time available for this transaction 139 --- supply J = how much power was actually provided in $time seconds 140 --- wasteHeat J = how heat is generated in the process 141 --- test = if true, the battery is not actually modified 142 -E.battery.drawCurrent = accessor('battery', function(s, bc, m, power, time, test) 143 - local ch = E.battery.charge(s) 144 - local maxPower = math.min(E.battery.dischargeRate(s)*time, power, ch) 145 - ch = ch - maxPower 146 - 147 - if not test then 148 - local degrade = m:get_int 'starsoul_electronics:battery_degrade' or 0 149 - degrade = degrade + maxPower * bc.decay 150 - -- for each joule of power drawn, capacity degrades by `decay` J 151 - -- this should ordinarily be on the order of mJ or smaller 152 - m:set_int('starsoul_electronics:battery_degrade', degrade) 153 - -- s:set_wear(safeWear(1 - (ch / E.battery.capacity(s)))) 154 - m:set_float('starsoul_electronics:battery_charge', ch / E.battery.capacity(s)) 155 - E.battery.update(s) 156 - end 157 - 158 - return maxPower, 0 -- FIXME specify waste heat 159 -end) 160 - 161 --- E.battery.recharge(bat, power, time) --> draw (J) 162 --- bat = battery stack 163 --- power J = joules of energy user wishes to charge the battery with 164 --- time s = the amount of time available for this transaction 165 --- draw J = how much power was actually drawn in $time seconds 166 -E.battery.recharge = accessor('battery', function(s, bc, m, power, time) 167 - local ch = E.battery.charge(s) 168 - local cap = E.battery.capacity(s) 169 - local maxPower = math.min(E.battery.dischargeRate(s)*time, power) 170 - local total = math.min(ch + maxPower, cap) 171 - -- s:set_wear(safeWear(1 - (total/cap))) 172 - m:set_float('starsoul_electronics:battery_charge', total/cap) 173 - E.battery.update(s) 174 - return maxPower, 0 -- FIXME 175 -end) 176 - 177 -E.battery.setCharge = accessor('battery', function(s, bc, m, newPower) 178 - local cap = E.battery.capacity(s) 179 - local power = math.min(cap, newPower) 180 - -- s:set_wear(safeWear(1 - (power/cap))) 181 - m:set_float('starsoul_electronics:battery_charge', power/cap) 182 - E.battery.update(s) 183 -end) 184 -E.battery.setChargeF = accessor('battery', function(s, bc, m, newPowerF) 185 - local power = math.min(1.0, newPowerF) 186 - m:set_float('starsoul_electronics:battery_charge', power) 187 - E.battery.update(s) 188 -end) 189 - 190 -E.dynamo = { kind = {} } 191 - 192 -E.dynamo.drawCurrent = accessor('dynamo', function(s,c,m, power, time, test) 193 - return c.vtable.drawCurrent(s, power, time, test) 194 -end) 195 -E.dynamo.totalPower = accessor('dynamo', function(s,c,m) return c.vtable.totalPower(s) end) 196 -E.dynamo.dischargeRate = accessor('dynamo', function(s,c,m) return c.vtable.dischargeRate (s) end) 197 -E.dynamo.initialPower = accessor('dynamo', function(s,c,m) return c.vtable.initialPower(s) end) 198 -E.dynamo.wasteHeat = accessor('dynamo', function(s,c,m) return c.vtable.wasteHeat(s) end) 199 --- baseline waste heat, produced whether or not power is being drawn. for batteries this is 0, but for 200 --- radiothermal generators it may be high 201 - 202 -E.dynamo.kind.battery = { 203 - drawCurrent = E.battery.drawCurrent; 204 - totalPower = E.battery.charge; 205 - initialPower = E.battery.capacity; 206 - dischargeRate = E.battery.dischargeRate; 207 - wasteHeat = function() return 0 end; 208 -}; 209 - 210 -starsoul.item.battery.foreach('starsoul_electronics:battery-gen', {}, function(id, def) 211 - minetest.register_tool(id, { 212 - short_description = def.name; 213 - groups = { battery = 1; dynamo = 1; electronic = 1; }; 214 - inventory_image = def.img or 'starsoul-item-battery.png'; 215 - description = starsoul.ui.tooltip { 216 - title = def.name; 217 - desc = def.desc; 218 - color = lib.color(0,.2,1); 219 - props = { 220 - { title = 'Optimal Capacity', affinity = 'info'; 221 - desc = lib.math.si('J', def.capacity) }; 222 - { title = 'Discharge Rate', affinity = 'info'; 223 - desc = lib.math.si('W', def.dischargeRate) }; 224 - { title = 'Charge Efficiency', affinity = 'info'; 225 - desc = string.format('%s%%', (1-def.leak) * 100) }; 226 - { title = 'Size', affinity = 'info'; 227 - desc = lib.math.si('m', def.fab.size.print) }; 228 - }; 229 - }; 230 - _starsoul = { 231 - event = { 232 - create = function(st, how) 233 - --[[if not how.gift then -- cheap hack to make starting batteries fully charged 234 - E.battery.setCharge(st, 0) 235 - end]] 236 - E.battery.update(st) 237 - end; 238 - }; 239 - fab = def.fab; 240 - dynamo = { 241 - vtable = E.dynamo.kind.battery; 242 - }; 243 - battery = def; 244 - }; 245 - }) 246 -end) 247 - 248 - 249 --- to use the power functions, consider the following situation. you have 250 --- a high-tier battery charger that can draw 100kW. (for simplicity, assume 251 --- it supports only one battery). if you install a low-tier battery, and 252 --- the charging callback is called every five seconds, you might use 253 --- a `recharge` call that looks like 254 --- 255 --- starsoul.mod.electronics.battery.recharge(bat, 5 * 100*1e4, 5) 256 --- 257 --- this would offer the battery 500kJ over five seconds. the battery will 258 --- determine how much power it can actually make use of in 5 five seconds, 259 --- and then return that amount. 260 --- 261 --- always remember to save the battery back to its inventory slot after 262 --- modifying its ItemStack with one of these functions! 263 - 264 - 265 --- battery types 266 --- supercapacitor: low capacity, no degrade, high dischargeRate, no leak 267 --- chemical: high capacity, high degrade, mid dischargeRate, low leak 268 - 269 --- battery tiers 270 --- makeshift: cheap, weak, low quality 271 --- imperial ("da red wunz go fasta"): powerful, low quality 272 --- commune ("snooty sophisticates"): limited power, high quality, expensive 273 --- usukwinya ("value engineering"): high power, mid quality, affordable 274 --- eluthrai ("uncompromising"): high power, high quality, wildly expensive 275 --- firstborn ("god-tier"): exceptional 276 - 277 -local batteryTiers = { 278 - makeshift = { 279 - name = 'Makeshift'; capacity = .5, decay = 3, leak = 2, dischargeRate = 1, 280 - fab = starsoul.type.fab { 281 - metal = {copper=10}; 282 - }; 283 - desc = "Every attosecond this electrical abomination doesn't explode in your face is but the unearned grace of the Wild Gods."; 284 - complexity = 1; 285 - sw = {rarity = 1}; 286 - }; 287 - imperial = { 288 - name = 'Imperial'; capacity = 2, decay = 2, leak = 2, dischargeRate = 2; 289 - fab = starsoul.type.fab { 290 - metal = {copper=15, iron = 20}; 291 - size = { print = 0.1 }; 292 - }; 293 - desc = "The Empire's native technology is a lumbering titan: bulky, inefficient, unreliable, ugly, and awesomely powerful. Their batteries are no exception, with raw capacity and throughput that exceed even Usukinwya designs."; 294 - drm = 1; 295 - complexity = 2; 296 - sw = {rarity = 2}; 297 - }; 298 - commune = { 299 - name = 'Commune'; capacity = 1, decay = .5, leak = .2, dischargeRate = 1; 300 - fab = starsoul.type.fab { 301 - metal = {vanadium=50, steel=10}; 302 - size = { print = 0.05 }; 303 - }; 304 - desc = "The Commune's proprietary battery designs prioritize reliability, compactness, and maintenance concerns above raw throughput, with an elegance of engineering and design that would make a Su'ikuri cry."; 305 - complexity = 5; 306 - sw = {rarity = 3}; 307 - }; 308 - usukwinya = { 309 - name = 'Usukwinya'; capacity = 2, decay = 1, leak = 1, dischargeRate = 1.5, 310 - fab = starsoul.type.fab { 311 - metal = {vanadium=30, argon=10}; 312 - size = { print = 0.07 }; 313 - }; 314 - desc = "A race of consummate value engineers, the Usukwinya have spent thousands of years refining their tech to be as cheap to build as possible, without compromising much on quality. The Tradebirds drive an infamously hard bargain, but their batteries are more than worth their meagre cost."; 315 - drm = 2; 316 - sw = {rarity = 10}; 317 - complexity = 15; 318 - }; 319 - eluthrai = { 320 - name = 'Eluthrai'; capacity = 3, decay = .4, leak = .1, dischargeRate = 1.5, 321 - fab = starsoul.type.fab { 322 - metal = {beryllium=20, platinum=20, technetium = 1, cinderstone = 10 }; 323 - size = { print = 0.03 }; 324 - }; 325 - desc = "The uncompromising Eluthrai are never satisfied until every quantifiable characteristic of their tech is maximally optimised down to the picoscale. Their batteries are some of the best in the Reach, and unquestionably the most expensive -- especially for those lesser races trying to copy the designs without the benefit of the sublime autofabricator ecosystem of the Eluthrai themselves."; 326 - complexity = 200; 327 - sw = {rarity = 0}; -- you think you're gonna buy eluthran schematics on SuperDiscountNanoWare.space?? 328 - }; 329 - firstborn = { 330 - name = 'Firstborn'; capacity = 5, decay = 0.1, leak = 0, dischargeRate = 3; 331 - fab = starsoul.type.fab { 332 - metal = {neodymium=20, xenon=150, technetium=5, sunsteel = 10 }; 333 - crystal = {astrite = 1}; 334 - size = { print = 0.05 }; 335 - }; 336 - desc = "Firstborn engineering seamlessly merges psionic effects with a mastery of the physical universe unattained by even the greatest of the living Starsouls. Their batteries reach levels of performance that strongly imply Quantum Gravity Theory -- and several major holy books -- need to be rewritten. From the ground up."; 337 - complexity = 1000; 338 - sw = {rarity = 0}; -- lol no 339 - }; 340 -} 341 - 342 -local batterySizes = { 343 - small = {name = 'Small', capacity = .5, dischargeRate = .5, complexity = 1, matMult = .5, fab = starsoul.type.fab {size={print=0.1}}}; 344 - mid = { capacity = 1, dischargeRate = 1, complexity = 1, matMult = 1, fab = starsoul.type.fab {size={print=0.3}}}; 345 - large = {name = 'Large', capacity = 2, dischargeRate = 1.5, complexity = 1, matMult = 1.5, fab = starsoul.type.fab {size={print=0.5}}}; 346 - huge = {name = 'Huge', capacity = 3, dischargeRate = 2, complexity = 1, matMult = 2, fab = starsoul.type.fab {size={print=0.8}}}; 347 -} 348 - 349 -local batteryTypes = { 350 - supercapacitor = { 351 - name = 'Supercapacitor'; 352 - desc = 'Room-temperature superconductors make for very reliable, high-dischargeRate, but low-capacity batteries.'; 353 - fab = starsoul.type.fab { 354 - metal = { enodium = 5 }; 355 - size = {print=0.8}; 356 - }; 357 - sw = { 358 - cost = { 359 - cycles = 5e9; -- 5 bil cycles 360 - ram = 10e9; -- 10GB 361 - }; 362 - pgmSize = 2e9; -- 2GB 363 - rarity = 5; 364 - }; 365 - capacity = 50e3, dischargeRate = 1000; 366 - leak = 0, decay = 1e-6; 367 - 368 - complexity = 3; 369 - }; 370 - chemical = { 371 - name = 'Chemical'; 372 - desc = ''; 373 - fab = starsoul.type.fab { 374 - element = { lithium = 3}; 375 - metal = {iron = 5}; 376 - size = {print=1.0}; 377 - }; 378 - sw = { 379 - cost = { 380 - cycles = 1e9; -- 1 bil cycles 381 - ram = 2e9; -- 2GB 382 - }; 383 - pgmSize = 512e6; -- 512MB 384 - rarity = 2; 385 - }; 386 - capacity = 200e3, dischargeRate = 200; 387 - leak = 0.2, decay = 1e-2; 388 - complexity = 1; 389 - }; 390 - carbon = { 391 - name = 'Carbon'; 392 - desc = 'Carbon nanotubes form the basis of many important metamaterials, chief among them power-polymer.'; 393 - capacity = 1; 394 - fab = starsoul.type.fab { 395 - element = { carbon = 40 }; 396 - size = {print=0.5}; 397 - }; 398 - sw = { 399 - cost = { 400 - cycles = 50e9; -- 50 bil cycles 401 - ram = 64e9; -- 64GB 402 - }; 403 - pgmSize = 1e9; -- 1GB 404 - rarity = 10; 405 - }; 406 - capacity = 100e3, dischargeRate = 500; 407 - leak = 0.1, decay = 1e-3; 408 - complexity = 10; 409 - }; 410 - hybrid = { 411 - name = 'Hybrid'; 412 - desc = ''; 413 - capacity = 1; 414 - fab = starsoul.type.fab { 415 - element = { 416 - lithium = 3; 417 - }; 418 - metal = { 419 - iron = 5; 420 - }; 421 - size = {print=1.5}; 422 - }; 423 - sw = { 424 - cost = { 425 - cycles = 65e9; -- 65 bil cycles 426 - ram = 96e9; -- 96GB 427 - }; 428 - pgmSize = 5e9; -- 5GB 429 - rarity = 15; 430 - }; 431 - capacity = 300e3, dischargeRate = 350; 432 - leak = 0.3, decay = 1e-5; 433 - complexity = 30; 434 - }; 435 -} 436 - 437 -local function elemath(dest, src, mult) 438 - dest = dest or {} 439 - for k,v in pairs(src) do 440 - if not dest[k] then dest[k] = 0 end 441 - dest[k] = dest[k] + v*mult 442 - end 443 - return dest 444 -end 445 - 446 -for bTypeName, bType in pairs(batteryTypes) do 447 -for bTierName, bTier in pairs(batteryTiers) do 448 -for bSizeName, bSize in pairs(batterySizes) do 449 - -- elemath(elementCost, bType.fab.element or {}, bSize.matMult) 450 - -- elemath(elementCost, bTier.fab.element or {}, bSize.matMult) 451 - -- elemath(metalCost, bType.fab.metal or {}, bSize.matMult) 452 - -- elemath(metalCost, bTier.fab.metal or {}, bSize.matMult) 453 - local fab = bType.fab + bTier.fab + bSize.fab + starsoul.type.fab { 454 - element = {copper = 10, silicon = 5}; 455 - } 456 - local baseID = string.format('battery_%s_%s_%s', 457 - bTypeName, bTierName, bSizeName) 458 - local id = 'starsoul_electronics:'..baseID 459 - local name = string.format('%s %s Battery', bTier.name, bType.name) 460 - if bSize.name then name = bSize.name .. ' ' .. name end 461 - local function batStat(s) 462 - if s == 'size' then 463 - --return bType.fab[s] * (bTier.fab[s] or 1) * (bSize.fab[s] or 1) 464 - return fab.size and fab.size.print or 1 465 - else 466 - return bType[s] * (bTier[s] or 1) * (bSize[s] or 1) 467 - end 468 - end 469 - 470 - local swID = 'starsoul_electronics:schematic_'..baseID 471 - fab.reverseEngineer = { 472 - complexity = bTier.complexity * bSize.complexity * bType.complexity; 473 - sw = swID; 474 - } 475 - fab.flag = {print=true} 476 - 477 - starsoul.item.battery.link(id, { 478 - name = name; 479 - desc = table.concat({ 480 - bType.desc or ''; 481 - bTier.desc or ''; 482 - bSize.desc or ''; 483 - }, ' '); 484 - 485 - fab = fab; 486 - 487 - capacity = batStat 'capacity'; 488 - dischargeRate = batStat 'dischargeRate'; 489 - leak = batStat 'leak'; 490 - decay = batStat 'decay'; 491 - }) 492 - 493 - local rare 494 - if bType.sw.rarity == 0 or bTier.sw.rarity == 0 then 495 - -- rarity is measured such that the player has a 1/r 496 - -- chance of finding a given item, or if r=0, no chance 497 - -- whatsoever (the sw must be obtained e.g. by reverse- 498 - -- engineering alien tech) 499 - rare = 0 500 - else 501 - rare = bType.sw.rarity + bTier.sw.rarity 502 - end 503 - 504 - starsoul.item.sw.link(swID, { 505 - kind = 'schematic'; 506 - name = name .. ' Schematic'; 507 - output = id; 508 - size = bType.sw.pgmSize; 509 - cost = bType.sw.cost; 510 - rarity = rare; 511 - }) 512 - 513 - E.schematicGroupLink('starsoul_electronics:battery', swID) 514 - 515 -end end end 516 - 517 - 518 ------------ 519 --- chips -- 520 ------------ 521 - 522 -E.sw = {} 523 -function E.sw.findSchematicFor(item) 524 - local id = ItemStack(item):get_name() 525 - print(id) 526 - local fm = minetest.registered_items[id]._starsoul 527 - if not (fm and fm.fab and fm.fab.reverseEngineer) then return nil end 528 - local id = fm.fab.reverseEngineer.sw 529 - return id, starsoul.item.sw.db[id] 530 -end 531 - 532 -E.chip = { file = {} } 533 -do local T,G = lib.marshal.t, lib.marshal.g 534 - -- love too reinvent unions from first principles 535 - E.chip.data = G.struct { 536 - label = T.str; 537 - uuid = T.u64; 538 - files = G.array(16, G.class(G.struct { 539 - kind = G.enum { 540 - 'sw'; -- a piece of installed software 541 - 'note'; -- a user-readable text file 542 - 'research'; -- saved RE progress 543 - 'genome'; -- for use with plant biosequencer? 544 - 'blob'; -- opaque binary blob, so 3d-pty mods can use the 545 - -- file mechanism to store arbirary data. 546 - }; 547 - drm = T.u8; -- inhibit copying 548 - name = T.str; 549 - body = T.text; 550 - }, function(file) -- enc 551 - local b = E.chip.file[file.kind].enc(file.body) 552 - return { 553 - kind = file.kind; 554 - drm = file.drm; 555 - name = file.name; 556 - body = b; 557 - } 558 - end, function(file) -- dec 559 - local f, ns = E.chip.file[file.kind].dec(file.body) 560 - file.body = f 561 - return file, ns 562 - end)); 563 - bootSlot = T.u8; -- indexes into files; 0 = no bootloader 564 - } 565 - E.chip.file.sw = G.struct { 566 - pgmId = T.str; 567 - conf = G.array(16, G.struct { 568 - key = T.str, value = T.str; 569 - }); 570 - } 571 - E.chip.file.note = G.struct { 572 - author = T.str; 573 - entries = G.array(16, G.struct { 574 - title = T.str; 575 - body = T.str; 576 - }); 577 - } 578 - E.chip.file.research = G.struct { 579 - itemId = T.str; 580 - progress = T.clamp; 581 - } 582 - E.chip.file.blob = G.struct { 583 - kind = T.str; -- MT ID that identifies a blob file type belonging to an external mod 584 - size = T.u8; -- this must be manually reported since we don't know how to evaluate it 585 - data = T.text; 586 - } 587 - function E.chip.fileSize(file) 588 - -- boy howdy 589 - if file.kind == 'blob' then 590 - return file.body.size 591 - elseif file.kind == 'note' then 592 - local sz = 0x10 + #file.body.author 593 - for _, e in pairs(file.body.entries) do 594 - sz = sz + #e.title + #e.body + 0x10 -- header overhead 595 - end 596 - return sz 597 - elseif file.kind == 'research' then 598 - local re = assert(minetest.registered_items[file.body.itemId]._starsoul.fab.reverseEngineer) 599 - return starsoul.item.sw.db[re.sw].size * file.body.progress 600 - elseif file.kind == 'sw' then 601 - return starsoul.item.sw.db[file.body.pgmId].size 602 - elseif file.kind == 'genome' then 603 - return 0 -- TODO 604 - end 605 - end 606 - local metaKey = 'starsoul_electronics:chip' 607 - function E.chip.read(chip) 608 - local m = chip:get_meta() 609 - local blob = m:get_string(metaKey) 610 - if blob and blob ~= '' then 611 - return E.chip.data.dec(lib.str.meta_dearmor(blob)) 612 - else -- prepare to format the chip 613 - return { 614 - label = ''; 615 - bootSlot = 0; 616 - uuid = math.floor(math.random(0,2^32)); 617 - files = {}; 618 - } 619 - end 620 - end 621 - function E.chip.write(chip, data) 622 - local m = chip:get_meta() 623 - m:set_string(metaKey, lib.str.meta_armor(E.chip.data.enc(data))) 624 - E.chip.update(chip) 625 - end 626 - function E.chip.fileOpen(chip, inode, fn) 627 - local c = E.chip.read(chip) 628 - if fn(c.files[inode]) then 629 - E.chip.write(chip, c) 630 - return true 631 - end 632 - return false 633 - end 634 - function E.chip.fileWrite(chip, inode, file) 635 - local c = E.chip.read(chip) 636 - c.files[inode] = file 637 - E.chip.write(chip, c) 638 - end 639 - function E.chip.usedSpace(chip, d) 640 - d = d or E.chip.read(chip) 641 - local sz = 0 642 - for _, f in pairs(d.files) do 643 - sz = sz + E.chip.fileSize(f) 644 - end 645 - return sz 646 - end 647 - function E.chip.freeSpace(chip, d) 648 - local used = E.chip.usedSpace(chip,d) 649 - local max = assert(chip:get_definition()._starsoul.chip.flash) 650 - return max - used 651 - end 652 - function E.chip.install(chip, file) 653 - -- remember to write out the itemstack after using this function! 654 - local d = E.chip.read(chip) 655 - if E.chip.freeSpace(chip, d) - E.chip.fileSize(file) >= 0 then 656 - table.insert(d.files, file) 657 - E.chip.write(chip, d) 658 - return true 659 - else 660 - return false 661 - end 662 - end 663 -end 664 - 665 -function E.chip.files(ch) 666 - local m = ch:get_meta() 667 - if not m:contains 'starsoul_electronics:chip' then 668 - return nil 669 - end 670 - local data = E.chip.read(ch) 671 - local f = 0 672 - return function() 673 - f = f + 1 674 - return data.files[f], f 675 - end 676 -end 677 - 678 -function E.chip.describe(ch, defOnly) 679 - local def, data if defOnly then 680 - def, data = ch, {} 681 - else 682 - def = ch:get_definition() 683 - local m = ch:get_meta() 684 - if m:contains 'starsoul_electronics:chip' then 685 - data = E.chip.read(ch) 686 - else 687 - data = {} 688 - defOnly = true 689 - end 690 - def = assert(def._starsoul.chip) 691 - end 692 - local props = { 693 - {title = 'Clock Rate', affinity = 'info'; 694 - desc = lib.math.si('Hz', def.clockRate)}; 695 - {title = 'RAM', affinity = 'info'; 696 - desc = lib.math.si('B', def.ram)}; 697 - } 698 - if not defOnly then 699 - table.insert(props, { 700 - title = 'Free Storage', affinity = 'info'; 701 - desc = lib.math.si('B', E.chip.freeSpace(ch, data)) .. ' / ' 702 - .. lib.math.si('B', def.flash); 703 - }) 704 - local swAffMap = { 705 - schematic = 'schematic'; 706 - suitPower = 'ability'; 707 - driver = 'driver'; 708 - } 709 - for i, e in ipairs(data.files) do 710 - local aff = 'neutral' 711 - local name = e.name 712 - local disabled = false 713 - if e.kind == 'sw' then 714 - for _,cf in pairs(e.body.conf) do 715 - if cf.key == 'disable' and cf.value == 'yes' then 716 - disabled = true 717 - break 718 - end 719 - end 720 - local sw = starsoul.item.sw.db[e.body.pgmId] 721 - aff = swAffMap[sw.kind] or 'good' 722 - if name == '' then name = sw.name end 723 - end 724 - name = name or '<???>' 725 - table.insert(props, disabled and { 726 - title = name; 727 - affinity = aff; 728 - desc = '<off>'; 729 - } or { 730 - --title = name; 731 - affinity = aff; 732 - desc = name; 733 - }) 734 - end 735 - else 736 - table.insert(props, { 737 - title = 'Flash Storage', affinity = 'info'; 738 - desc = lib.math.si('B', def.flash); 739 - }) 740 - end 741 - return starsoul.ui.tooltip { 742 - title = data.label and data.label~='' and string.format('<%s>', data.label) or def.name; 743 - color = lib.color(.6,.6,.6); 744 - desc = def.desc; 745 - props = props; 746 - }; 747 -end 748 - 749 -function E.chip.update(chip) 750 - chip:get_meta():set_string('description', E.chip.describe(chip)) 751 -end 752 - 753 -starsoul.item.chip.foreach('starsoul_electronics:chip-gen', {}, function(id, def) 754 - minetest.register_craftitem(id, { 755 - short_description = def.name; 756 - description = E.chip.describe(def, true); 757 - inventory_image = def.img or 'starsoul-item-chip.png'; 758 - groups = {chip = 1}; 759 - _starsoul = { 760 - fab = def.fab; 761 - chip = def; 762 - }; 763 - }) 764 -end) 765 - 766 --- in case other mods want to define their own tiers 767 -E.chip.tiers = lib.registry.mk 'starsoul_electronics:chipTiers' 768 -E.chip.tiers.meld { 769 - -- GP chips 770 - tiny = {name = 'Tiny Chip', clockRate = 512e3, flash = 4096, ram = 1024, powerEfficiency = 1e9, size = 1}; 771 - small = {name = 'Small Chip', clockRate = 128e6, flash = 512e6, ram = 512e6, powerEfficiency = 1e8, size = 3}; 772 - med = {name = 'Chip', clockRate = 1e9, flash = 4e9, ram = 4e9, powerEfficiency = 1e7, size = 6}; 773 - large = {name = 'Large Chip', clockRate = 2e9, flash = 8e9, ram = 8e9, powerEfficiency = 1e6, size = 8}; 774 - -- specialized chips 775 - compute = {name = 'Compute Chip', clockRate = 4e9, flash = 24e6, ram = 64e9, powerEfficiency = 1e8, size = 4}; 776 - data = {name = 'Data Chip', clockRate = 128e3, flash = 2e12, ram = 32e3, powerEfficiency = 1e5, size = 4}; 777 - lp = {name = 'Low-Power Chip', clockRate = 128e6, flash = 64e6, ram = 1e9, powerEfficiency = 1e10, size = 4}; 778 - carbon = {name = 'Carbon Chip', clockRate = 64e6, flash = 32e6, ram = 2e6, powerEfficiency = 2e9, size = 2, circ='carbon'}; 779 -} 780 - 781 -E.chip.tiers.foreach('starsoul_electronics:genChips', {}, function(id, t) 782 - id = t.id or string.format('%s:chip_%s', minetest.get_current_modname(), id) 783 - local circMat = t.circ or 'silicon'; 784 - starsoul.item.chip.link(id, { 785 - name = t.name; 786 - clockRate = t.clockRate; 787 - flash = t.flash; 788 - ram = t.ram; 789 - powerEfficiency = t.powerEfficiency; -- cycles per joule 790 - fab = { 791 - flag = { 792 - silicompile = true; 793 - }; 794 - time = { 795 - silicompile = t.size * 24*60; 796 - }; 797 - cost = { 798 - energy = 50e3 + t.size * 15e2; 799 - }; 800 - element = { 801 - [circMat] = 50 * t.size; 802 - copper = 30; 803 - gold = 15; 804 - }; 805 - }; 806 - }) 807 -end) 808 - 809 -function E.chip.findBest(test, ...) 810 - local chip, bestFitness 811 - for id, c in pairs(starsoul.item.chip.db) do 812 - local fit, fitness = test(c, ...) 813 - if fit and (bestFitness == nil or fitness > bestFitness) then 814 - chip, bestFitness = id, fitness 815 - end 816 - end 817 - return chip, starsoul.item.chip.db[chip], bestFitness 818 -end 819 - 820 -function E.chip.findForStorage(sz) 821 - return E.chip.findBest(function(c) 822 - return c.flash >= sz, -math.abs(c.flash - sz) 823 - end) 824 -end 825 - 826 -function E.chip.sumCompute(chips) 827 - local c = { 828 - cycles = 0; 829 - ram = 0; 830 - flashFree = 0; 831 - powerEfficiency = 0; 832 - } 833 - local n = 0 834 - for _, e in pairs(chips) do 835 - n = n + 1 836 - if not e:is_empty() then 837 - local ch = e:get_definition()._starsoul.chip 838 - c.cycles = c.cycles + ch.clockRate 839 - c.ram = c.ram + ch.clockRate 840 - c.flashFree = c.flashFree + E.chip.freeSpace(e) 841 - c.powerEfficiency = c.powerEfficiency + ch.powerEfficiency 842 - end 843 - end 844 - if n > 0 then c.powerEfficiency = c.powerEfficiency / n end 845 - return c 846 -end 847 - 848 -E.chip.fileHandle = lib.class { 849 - __name = 'starsoul_electronics:chip.fileHandle'; 850 - construct = function(chip, inode) -- stack, int --> fd 851 - return { chip = chip, inode = inode } 852 - end; 853 - __index = { 854 - read = function(self) 855 - local dat = E.chip.read(self.chip) 856 - return dat.files[self.inode] 857 - end; 858 - write = function(self,data) 859 - -- print('writing', self.chip, self.inode) 860 - return E.chip.fileWrite(self.chip, self.inode, data) 861 - end; 862 - erase = function(self) 863 - local dat = E.chip.read(self.chip) 864 - table.remove(dat.files, self.inode) 865 - E.chip.write(self.chip, dat) 866 - self.inode = nil 867 - end; 868 - open = function(self,fn) 869 - return E.chip.fileOpen(self.chip, self.inode, fn) 870 - end; 871 - }; 872 -} 873 - 874 -function E.chip.usableSoftware(chips,pgm) 875 - local comp = E.chip.sumCompute(chips) 876 - local r = {} 877 - local unusable = {} 878 - local sw if pgm then 879 - if type(pgm) == 'string' then 880 - pgm = {starsoul.item.sw.db[pgm]} 881 - end 882 - sw = pgm 883 - else 884 - sw = {} 885 - for i, e in ipairs(chips) do 886 - if (not e:is_empty()) 887 - and minetest.get_item_group(e:get_name(), 'chip') ~= 0 888 - then 889 - for fl, inode in E.chip.files(e) do 890 - if fl.kind == 'sw' then 891 - local s = starsoul.item.sw.db[fl.body.pgmId] 892 - table.insert(sw, { 893 - sw = s, chip = e, chipSlot = i; 894 - file = fl, inode = inode; 895 - }) 896 - end 897 - end 898 - end 899 - end 900 - end 901 - 902 - for _, s in pairs(sw) do 903 - if s.sw.cost.ram <= comp.ram then 904 - table.insert(r, { 905 - sw = s.sw; 906 - chip = s.chip, chipSlot = s.chipSlot; 907 - file = s.file; 908 - fd = E.chip.fileHandle(s.chip, s.inode); 909 - speed = s.sw.cost.cycles / comp.cycles; 910 - powerCost = s.sw.cost.cycles / comp.powerEfficiency; 911 - comp = comp; 912 - }) 913 - else 914 - table.insert(unusable, { 915 - sw = s.sw; 916 - chip = s.chip; 917 - ramNeeded = s.sw.cost.ram - comp.ram; 918 - }) 919 - end 920 - end 921 - return r, unusable 922 -end 923 - 924 -starsoul.include 'sw'
Deleted mods/starsoul-electronics/mod.conf version [8bb64074c2].
1 -name = starsoul_electronics 2 -description = basic electronic components and logic 3 -depends = starsoul
Deleted mods/starsoul-electronics/sw.lua version [3b2a0cfbed].
1 --- [ʞ] sw.lua 2 --- ~ lexi hale <lexi@hale.su> 3 --- 🄯 EUPL v1.2 4 --- ? 5 - 6 -------------------------------- 7 --- basic suit nano abilities -- 8 -------------------------------- 9 -local function shredder(prop) 10 - local function getItemsForFab(fab) 11 - local elt 12 - if fab then 13 - elt = fab:elementalize() 14 - else 15 - elt = {} 16 - end 17 - local items = {} 18 - if elt.element then 19 - for k,v in pairs(elt.element) do 20 - local st = ItemStack { 21 - name = starsoul.world.material.element.db[k].form.element; 22 - count = v; 23 - } 24 - table.insert(items, st) 25 - end 26 - end 27 - return items 28 - end 29 - 30 - return function(user, ctx) 31 - local function cleanup() 32 - user.action.prog.shred = nil 33 - if user.action.sfx.shred then 34 - minetest.sound_fade(user.action.sfx.shred, 1, 0) 35 - user.action.sfx.shred = nil 36 - end 37 - if user.action.fx.shred then 38 - user.action.fx.shred.abort() 39 - end 40 - end 41 - 42 - if user.action.tgt.type ~= 'node' then return end 43 - local what = user.action.tgt.under 44 - if what == nil or user.entity:get_pos():distance(what) > prop.range then 45 - cleanup() 46 - return false 47 - end 48 - local shredTime = 1.0 49 - local soundPitch = 1.0 -- TODO 50 - local pdraw = prop.powerDraw or 0 51 - 52 - local node = minetest.get_node(what) 53 - local nd = minetest.registered_nodes[node.name] 54 - local elt, fab, vary 55 - if nd._starsoul then 56 - fab = nd._starsoul.recover or nd._starsoul.fab 57 - vary = nd._starsoul.recover_vary 58 - end 59 - if fab then 60 - if fab.flag then 61 - if fab.flag.unshreddable then 62 - cleanup() 63 - return false 64 - -- TODO error beep 65 - end 66 - end 67 - shredTime = fab.time and fab.time.shred or shredTime -- FIXME 68 - if fab.cost and fab.cost.shredPower then 69 - pdraw = pdraw * fab.cost.shredPower 70 - end 71 - end 72 - local maxW = user:getSuit():maxPowerUse() 73 - if maxW < pdraw then 74 - shredTime = shredTime * (pdraw/maxW) 75 - pdraw = maxW 76 - end 77 - if ctx.how.state == 'prog' then 78 - local pdx = pdraw * ctx.how.delta 79 - local p = user:suitDrawCurrent(pdx, ctx.how.delta, {kind='nano',label='Shredder'}, pdx) 80 - if p < pdx then 81 - cleanup() 82 - return false 83 - elseif not user.action.prog.shred then 84 - cleanup() -- kill danglers 85 - -- begin 86 - user.action.prog.shred = 0 87 - user.action.sfx.shred = minetest.sound_play('starsoul-nano-shred', { 88 - object = user.entity; 89 - max_hear_distance = prop.range*2; 90 - loop = true; 91 - pitch = soundPitch; 92 - }) 93 - user.action.fx.shred = starsoul.fx.nano.shred(user, what, prop, shredTime, node) 94 - else 95 - user.action.prog.shred = user.action.prog.shred + ctx.how.delta or 0 96 - end 97 - --print('shred progress: ', user.action.prog.shred) 98 - if user.action.prog.shred >= shredTime then 99 - if minetest.dig_node(what) then 100 - --print('shred complete') 101 - user:suitSound 'starsoul-success' 102 - if fab then 103 - local vf = fab 104 - if vary then 105 - local rng = (starsoul.world.seedbank+0xa891f62)[minetest.hash_node_position(what)] 106 - vf = vf + vary(rng, {}) 107 - end 108 - local items = getItemsForFab(vf) 109 - for i, it in ipairs(items) do user:give(it) end 110 - end 111 - else 112 - user:suitSound 'starsoul-error' 113 - end 114 - cleanup() 115 - end 116 - elseif ctx.how.state == 'halt' then 117 - cleanup() 118 - end 119 - return true 120 - end 121 -end 122 - 123 -starsoul.item.sw.link('starsoul_electronics:shred', { 124 - name = 'NanoShred'; 125 - kind = 'suitPower', powerKind = 'active'; 126 - desc = 'An open-source program used in its various forks and iterations all across human-inhabited space and beyond. Rumored to contain fragments of code stolen from the nanoware of the Greater Races by an elusive infoterrorist.'; 127 - size = 500e3; 128 - cost = { 129 - cycles = 100e6; 130 - ram = 500e6; 131 - }; 132 - run = shredder{range=2, powerDraw=200}; 133 -}) 134 - 135 -starsoul.item.sw.link('starsoul_electronics:compile_commune', { 136 - name = 'Compile Matter'; 137 - kind = 'suitPower', powerKind = 'direct'; 138 - desc = "A basic suit matter compiler program, rather slow but ruthlessly optimized for power- and memory-efficiency by some of the Commune's most fanatic coders."; 139 - size = 700e3; 140 - cost = { 141 - cycles = 300e6; 142 - ram = 2e9; 143 - }; 144 - ui = 'starsoul:compile-matter-component'; 145 - run = function(user, ctx) 146 - end; 147 -}) 148 - 149 -starsoul.item.sw.link('starsoul_electronics:compile_block_commune', { 150 - name = 'Compile Block'; 151 - kind = 'suitPower', powerKind = 'active'; 152 - desc = "An advanced suit matter compiler program, capable of printing complete devices and structure parts directly into the world."; 153 - size = 5e6; 154 - cost = { 155 - cycles = 700e6; 156 - ram = 4e9; 157 - }; 158 - ui = 'starsoul:compile-matter-block'; 159 - run = function(user, ctx) 160 - end; 161 -}) 162 - 163 -do local J = starsoul.store.compilerJob 164 - starsoul.item.sw.link('starsoul_electronics:driver_compiler_commune', { 165 - name = 'Matter Compiler'; 166 - kind = 'driver'; 167 - desc = "A driver for a standalone matter compiler, suitable for building larger components than your suit alone can handle."; 168 - size = 850e3; 169 - cost = { 170 - cycles = 400e6; 171 - ram = 2e9; 172 - }; 173 - ui = 'starsoul:device-compile-matter-component'; 174 - run = function(user, ctx) 175 - end; 176 - bgProc = function(user, ctx, interval, runState) 177 - if runState.flags.compiled == true then return false end 178 - -- only so many nanides to go around 179 - runState.flags.compiled = true 180 - local time = minetest.get_gametime() 181 - local cyclesLeft = ctx.comp.cycles * interval 182 - 183 - for id, e in ipairs(ctx.file.body.conf) do 184 - if e.key == 'job' then 185 - local t = J.dec(e.value) 186 - local remove = false 187 - local r = starsoul.item.sw.db[t.schematic] 188 - if not r then -- bad schematic 189 - remove = true 190 - else 191 - local ccost = ctx.sw.cost.cycles + r.cost.cycles 192 - local tcost = ccost / cyclesLeft 193 - t.progress = t.progress + (1/tcost)*interval 194 - cyclesLeft = cyclesLeft - ccost*interval 195 - if t.progress >= 1 then 196 - -- complete 197 - remove = true 198 - local i = starsoul.item.mk(r.output, { 199 - how = 'print'; 200 - user = user; -- for suit 201 - compiler = { 202 - node = ctx.compiler; -- for device 203 - sw = ctx.sw; 204 - install = ctx.fd; 205 - }; 206 - schematic = r; 207 - }) 208 - ctx.giveItem(i) 209 - end 210 - end 211 - if remove then 212 - table.remove(ctx.file.body.conf, id) 213 - else 214 - e.value = J.enc(t) 215 - end 216 - if not cyclesLeft > 0 then break end 217 - end 218 - end 219 - ctx.saveConf() 220 - end; 221 - }) 222 -end 223 - 224 -local function pasv_heal(effect, energy, lvl, pgmId) 225 - return function(user, ctx, interval, runState) 226 - if runState.flags.healed == true then return false end 227 - -- competing nanosurgical programs?? VERY bad idea 228 - runState.flags.healed = true 229 - 230 - local amt, f = user:effectiveStat 'health' 231 - local st = user:getSuit():powerState() 232 - if (st == 'on' and f < lvl) or (st == 'powerSave' and f < math.min(lvl,0.25)) then 233 - local maxPower = energy*interval 234 - local p = user:suitDrawCurrent(maxPower, interval, { 235 - id = 'heal'; 236 - src = 'suitPower'; 237 - pgmId = pgmId; 238 - healAmount = effect; 239 - }) 240 - if p > 0 then 241 - local heal = (p/maxPower) * ctx.speed * effect*interval 242 - --user:statDelta('health', math.max(1, heal)) 243 - starsoul.fx.nano.heal(user, {{player=user.entity}}, heal, 1) 244 - return true 245 - end 246 - end 247 - return false -- program did not run 248 - end; 249 -end 250 - 251 -starsoul.item.sw.link('starsoul_electronics:nanomed', { 252 - name = 'NanoMed'; 253 - kind = 'suitPower', powerKind = 'passive'; 254 - desc = 'Repair of the body is a Commune specialty, and their environment suits all come equipped with highly sophisticated nanomedicine suites, able to repair even the most grievous of wounds given sufficient energy input and time.'; 255 - size = 2e9; 256 - cost = { 257 - cycles = 400e6; 258 - ram = 3e9; 259 - }; 260 - run = pasv_heal(2, 20, 1); 261 -}) 262 - 263 -starsoul.item.sw.link('starsoul_electronics:autodoc_deluxe', { 264 - name = 'AutoDoc Deluxe'; 265 - kind = 'suitPower', powerKind = 'passive'; 266 - desc = "A flagship offering of the Excellence Unyielding nanoware division, AutoDoc Deluxe has been the top-rated nanocare package in the Celestial Shores Province for six centuries and counting. Every chip includes our comprehensive database of illnesses, prosyn schematics, and organ repair techniques, with free over-the-ether updates guaranteed for ten solariads from date of purchase! When professional medical care just isn't an option, 9/10 doctors recommend Excellence Unyielding AutoDoc Deluxe! The remaining doctor was bribed by our competitors."; 267 - size = 1e9; 268 - cost = { 269 - cycles = 700e6; 270 - ram = 1e9; 271 - }; 272 - run = pasv_heal(4, 50, .7); 273 -})
Deleted mods/starsoul-material/elements.lua version [8655f86d81].
1 -local lib = starsoul.mod.lib 2 -local W = starsoul.world 3 -local M = W.material 4 - 5 -M.element.meld { 6 - hydrogen = { 7 - name = 'hydrogen', sym = 'H', n = 1; 8 - gas = true; 9 - color = lib.color(1,0.8,.3); 10 - }; 11 - beryllium = { 12 - name = 'Beryllium', sym = 'Be', n = 4; 13 - metal = true; -- rare emerald-stuff 14 - color = lib.color(0.2,1,0.2); 15 - }; 16 - oxygen = { 17 - name = 'oxygen', sym = 'O', n = 8; 18 - gas = true; 19 - color = lib.color(.2,1,.2); 20 - }; 21 - carbon = { 22 - name = 'carbon', sym = 'C', n = 6; 23 - color = lib.color(.7,.2,.1); 24 - }; 25 - silicon = { 26 - name = 'silicon', sym = 'Si', n = 14; 27 - metal = true; -- can be forged into an ingot 28 - color = lib.color(.6,.6,.4); 29 - }; 30 - potassium = { 31 - name = 'potassium', sym = 'K', n = 19; 32 - -- potassium is technically a metal but it's so soft 33 - -- it can be easily nanoworked without high temps, so 34 - -- ingots make no sense 35 - color = lib.color(1,.8,0.1); 36 - }; 37 - calcium = { 38 - name = 'calcium', sym = 'Ca', n = 20; 39 - metal = true; 40 - color = lib.color(1,1,0.7); 41 - }; 42 - aluminum = { 43 - name = 'aluminum', sym = 'Al', n = 13; 44 - metal = true; 45 - color = lib.color(0.9,.95,1); 46 - }; 47 - iron = { 48 - name = 'iron', sym = 'Fe', n = 26; 49 - metal = true; 50 - color = lib.color(.3,.3,.3); 51 - }; 52 - copper = { 53 - name = 'copper', sym = 'Cu', n = 29; 54 - metal = true; 55 - color = lib.color(.8,.4,.1); 56 - }; 57 - lithium = { 58 - name = 'lithium', sym = 'Li', n = 3; 59 - -- i think lithium is considered a metal but we don't mark it as 60 - -- one here because making a 'lithium ingot' is insane (even possible?) 61 - color = lib.color(1,0.8,.3); 62 - }; 63 - titanium = { 64 - name = 'titanium', sym = 'Ti', n = 22; 65 - metal = true; 66 - color = lib.color(.7,.7,.7); 67 - }; 68 - vanadium = { 69 - name = 'vanadium', sym = 'V', n = 23; 70 - metal = true; 71 - color = lib.color(.3,0.5,.3); 72 - }; 73 - xenon = { 74 - name = 'xenon', sym = 'Xe', n = 54; 75 - gas = true; 76 - color = lib.color(.5,.1,1); 77 - }; 78 - argon = { 79 - name = 'argon', sym = 'Ar', n = 18; 80 - gas = true; 81 - color = lib.color(0,0.1,.9); 82 - }; 83 - osmium = { 84 - name = 'osmium', sym = 'Os', n = 76; 85 - metal = true; 86 - color = lib.color(.8,.1,1); 87 - }; 88 - iridium = { 89 - name = 'iridium', sym = 'Ir', n = 77; 90 - metal = true; 91 - color = lib.color(.8,0,.5); 92 - }; 93 - technetium = { 94 - name = 'technetium', sym = 'Tc', n = 43; 95 - desc = 'Prized by the higher Powers for subtle interactions that elude mere human scholars, technetium is of particular use in nuclear nanobatteries.'; 96 - metal = true; 97 - color = lib.color(.2,0.2,1); 98 - }; 99 - uranium = { 100 - name = 'uranium', sym = 'U', n = 92; 101 - desc = 'A weak but relatively plentiful nuclear fuel.'; 102 - metal = true; 103 - color = lib.color(.2,.7,0); 104 - }; 105 - thorium = { 106 - name = 'thorium', sym = 'Th', n = 90; 107 - desc = 'A frighteningly powerful nuclear fuel.'; 108 - metal = true; 109 - color = lib.color(.7,.3,.1); 110 - }; 111 - silver = { 112 - name = 'silver', sym = 'Ag', n = 47; 113 - metal = true; 114 - color = lib.color(.7,.7,.8); 115 - }; 116 - gold = { 117 - name = 'gold', sym = 'Au', n = 79; 118 - metal = true; 119 - color = lib.color(1,.8,0); 120 - }; 121 -}
Deleted mods/starsoul-material/init.lua version [4473abdf0e].
1 -local lib = starsoul.mod.lib 2 -local M = { 3 - canisterSizes = lib.registry.mk 'starsoul_material:canister-size'; 4 -} 5 -M.canisterSizes.foreach('starsoul_material:canister_link', {}, function(id, sz) 6 - starsoul.item.canister.link(minetest.get_current_modname() .. ':canister_' .. id, { 7 - name = sz.name; 8 - slots = sz.slots; 9 - vol = 0.1; -- too big for suit? 10 - desc = sz.desc; 11 - }) 12 -end) 13 -M.canisterSizes.meld { 14 - tiny = {name = 'Tiny Canister', slots = 1, vol = 0.05}; 15 - small = {name = 'Small Canister', slots = 3, vol = 0.2}; 16 - mid = {name = 'Canister', slots = 5, vol = 0.5}; 17 - large = {name = 'Large Canister', slots = 10, vol = 1.0}; 18 - storage = {name = 'Storage Canister', slots = 50, vol = 5.0}; 19 -} 20 - 21 -starsoul.include 'elements' 22 -
Deleted mods/starsoul-material/mod.conf version [4c6f9cbdac].
1 -name = starsoul_material 2 -description = defines the raw materials and alloys used in printing 3 -depends = starsoul
Deleted mods/starsoul-scenario/init.lua version [d3b56b60b6].
1 -local lib = starsoul.mod.lib 2 -local scenario = starsoul.world.scenario 3 - 4 -local function makeChip(label, schem, sw) 5 - local E = starsoul.mod.electronics 6 - local files = {} 7 - local sz = 0 8 - for _, e in ipairs(schem) do 9 - local p = E.sw.findSchematicFor(e[1]) 10 - if p then 11 - local file = { 12 - kind = 'sw', name = '', drm = e[2]; 13 - body = {pgmId = p}; 14 - } 15 - table.insert(files, file) 16 - sz = sz + E.chip.fileSize(file) 17 - end 18 - end 19 - for _, e in ipairs(sw) do 20 - local file = { 21 - kind = 'sw', name = '', drm = e[2]; 22 - body = {pgmId = e[1]}; 23 - } 24 - table.insert(files, file) 25 - sz = sz + E.chip.fileSize(file) 26 - end 27 - local chip = ItemStack(assert(E.chip.findBest(function(c) 28 - return c.flash >= sz, c.ram + c.clockRate 29 - end))) 30 - local r = E.chip.read(chip) 31 - r.label = label 32 - r.files = files 33 - E.chip.write(chip, r) 34 - return chip 35 -end 36 - 37 -local chipLibrary = { 38 - compendium = makeChip('The Gentleman Adventurer\'s Compleat Wilderness Compendium', { 39 - {'starsoul_electronics:battery_chemical_imperial_small', 0}; 40 - }, { 41 - {'starsoul_electronics:shred', 0}; 42 - --{'starsoul_electronics:compile_empire', 0}; 43 - {'starsoul_electronics:autodoc_deluxe', 1}; 44 - --{'starsoul_electronics:driver_compiler_empire', 0}; 45 - }); 46 - survivalware = makeChip('Emergency Survivalware', { 47 - {'starsoul_electronics:battery_chemical_commune_small', 0}; 48 - }, { 49 - {'starsoul_electronics:shred', 0}; 50 - {'starsoul_electronics:compile_commune', 0}; 51 - {'starsoul_electronics:nanomed', 0}; 52 - {'starsoul_electronics:driver_compiler_commune', 0}; 53 - }); 54 - misfortune = makeChip("Sold1er0fMisf0rtune TOP Schematic Crackz REPACK", { 55 - {'starsoul_electronics:battery_chemical_usukwinya_mid', 0}; 56 - {'starsoul_electronics:battery_hybrid_imperial_small', 0}; 57 - -- ammunition 58 - }, {}); 59 -} 60 - 61 -local battery = function(name) 62 - local s = ItemStack(name) 63 - starsoul.mod.electronics.battery.setChargeF(s, 1.0) 64 - return s 65 -end 66 - 67 - 68 -table.insert(scenario, { 69 - id = 'starsoul_scenario:imperialExpat'; 70 - name = 'Imperial Expat'; 71 - desc = "Hoping to escape a miserable life deep in the grinding gears of the capitalist machine for the bracing freedom of the frontier, you sought entry as a colonist to the new Commune world of Thousand Petal. Fate -- which is to say, terrorists -- intervened, and you wound up stranded on Farthest Shadow with little more than the nanosuit on your back, ship blown to tatters and your soul thoroughly mauled by the explosion of a twisted alien artifact -- which SOMEONE neglected to inform you your ride would be carrying.\nAt least you got some nifty psionic powers out of this whole clusterfuck. Hopefully they're safe to use."; 72 - 73 - species = 'human'; 74 - speciesVariant = 'female'; 75 - soul = { 76 - externalChannel = true; -- able to touch other souls in the spiritual realm 77 - physicalChannel = true; -- able to extend influence into physical realm 78 - damage = 1; 79 - }; 80 - social = { 81 - empire = 'workingClass'; 82 - commune = 'metic'; 83 - }; 84 - 85 - startingItems = { 86 - suit = ItemStack('starsoul_suit:suit_survival_commune'); 87 - suitBatteries = {battery 'starsoul_electronics:battery_carbon_commune_small'}; 88 - suitChips = { 89 - chipLibrary.survivalware; 90 - -- you didn't notice it earlier, but your Commune environment suit 91 - -- came with this chip already plugged in. it's apparently true 92 - -- what they say: the Commune is always prepared for everything. 93 - -- E V E R Y T H I N G. 94 - }; 95 - suitGuns = {}; 96 - suitAmmo = {}; 97 - suitCans = { 98 - ItemStack('starsoul_material:canister_small'); 99 - }; 100 - carry = { 101 - chipLibrary.compendium; 102 - -- you bought this on a whim before you left the Empire, and 103 - -- just happened to still have it on your person when everything 104 - -- went straight to the Wild Gods' privy 105 - }; 106 - }; 107 -}) 108 - 109 -table.insert(scenario, { 110 - id = 'starsoul_scenario:gentlemanAdventurer'; 111 - -- Othar Tryggvasson, 112 - name = 'Gentleman Adventurer'; 113 - desc = "Tired of the same-old-same-old, sick of your idiot contemporaries, exasperated with the shallow soul-rotting luxury of life as landless lordling, and earnestly eager to enrage your father, you resolved to see the Reach in all her splendor. Deftly evading the usual tourist traps, you finagled your way into the confidence of the Commune ambassador with a few modest infusions of Father's money -- now *that* should pop his monocle -- and secured yourself a seat on a ride to their brand-new colony at Thousand Petal. How exciting -- a genuine frontier outing!"; 114 - 115 - species = 'human'; 116 - speciesVariant = 'male'; 117 - soul = { 118 - externalChannel = true; 119 - physicalChannel = true; 120 - damage = 1; 121 - }; 122 - social = { 123 - empire = 'lord'; 124 - }; 125 - 126 - startingItems = { 127 - suit = 'starsoul_suit:suit_survival_imperial'; 128 - suitBatteries = {battery 'starsoul_electronics:battery_supercapacitor_imperial_mid'}; 129 - suitChips = { 130 - chipLibrary.compendium; 131 - -- Mother, bless her soul, simply insisted on buying you this as a parting 132 - -- gift. "it's dangerous out there for a young man," she proclaimed as 133 - -- if she had profound firsthand experience of the matter. mindful of the 134 - -- husband she endures, you suffered to humor her, and made a big show of 135 - -- installing it your brand-new nanosuit before you fled the family seat. 136 - }; 137 - suitGuns = {}; 138 - suitAmmo = {}; 139 - suitCans = { 140 - ItemStack('starsoul_material:canister_mid'); 141 - }; 142 - carry = {}; 143 - }; 144 -}) 145 - 146 -table.insert(scenario, { 147 - -- you start out with strong combat abilities but weak engineering, 148 - -- and will have to scavenge wrecks to find basic crafting gear 149 - id = 'starsoul_scenario:terroristTagalong'; 150 - name = 'Terrorist Tagalong'; 151 - desc = "It turns out there's a *reason* Crown jobs pay so well."; 152 - species = 'human'; 153 - speciesVariant = 'female'; 154 - social = { 155 - empire = 'lowlife'; 156 - commune = 'mostWanted'; 157 - underworldConnections = true; 158 - }; 159 - soul = { 160 - externalChannel = true; 161 - physicalChannel = true; 162 - damage = 2; -- closer to the blast 163 - }; 164 - startingItems = { 165 - suit = 'starsoul_suit:suit_combat_imperial'; 166 - suitBatteries = { 167 - ItemStack('starsoul_electronics:battery_supercapacitor_imperial_small'); 168 - ItemStack('starsoul_electronics:battery_chemical_imperial_large'); 169 - }; 170 - suitGuns = {}; 171 - suitAmmo = {}; 172 - carry = {}; 173 - }; 174 - suitChips = {chipLibrary.misfortune}; 175 -}) 176 - 177 -table.insert(scenario, { 178 - id = 'starsoul_scenario:tradebirdBodyguard'; 179 - name = 'Tradebird Bodyguard'; 180 - desc = "You've never understood why astropaths of all people *insist* on bodyguards. This one could probably make hash of a good-sized human batallion, if her feathers were sufficiently ruffled. Perhaps it's a status thing. Whatever the case, it's easy money.\nAt least, it was supposed to be.'"; 181 - species = 'usukwinya'; 182 - speciesVariant = 'male'; 183 - soul = { 184 - damage = 0; -- Inyukiriku and her entourage fled the ship when she sensed something serious was about to go down. lucky: the humans only survived because their souls were closed off to the Physical. less luckily, the explosion knocked your escape pod off course and the damn astropath was too busy saving her own skin to come after you 185 - externalChannel = true; -- usukwinya are already psionic 186 - physicalChannel = true; -- usukwinya are Starsouls 187 - }; 188 - startingItems = { 189 - suit = 'starsoul_suit:suit_combat_usukwinya'; 190 - suitBatteries = { 191 - ItemStack('starsoul_electronics:battery_hybrid_usukwinya_mid'); 192 - }; 193 - suitGuns = {}; 194 - suitChips = {}; 195 - suitAmmo = {}; 196 - carry = {}; 197 - }; 198 -})
Deleted mods/starsoul-scenario/mod.conf version [cb3b4db72f].
1 -name = starsoul_scenario 2 -description = built-in scenarios for Starsoul 3 -depends = starsoul, starsoul_suit, starsoul_electronics, starsoul_building, starsoul_material 4 -# be sure to add any mods from which you list new starting items!
Deleted mods/starsoul-secrets/init.lua version [f98f7f7b59].
1 ----------------------------------------------------- 2 -------------- CONTROLLED INFORMATION -------------- 3 ----------------------------------------------------- 4 --- THE INFORMATION CONTAINED IN THIS DOCUMENT IS -- 5 --- SUBJECT TO RESTRAINT OF TRANSMISSION PER THE -- 6 --- TERMS OF THE COMMUNE CHARTER INFOSECURITY -- 7 --- PROVISION. IF YOU ARE NOT AUTHORIZED UNDER THE -- 8 --- AEGIS OF THE APPROPRITE CONTROLLING AUTHORITY, -- 9 --- CLOSE THIS DOCUMENT IMMEDIATELY AND REPORT THE -- 10 --- SECURITY BREACH TO YOUR DESIGNATED INFORMATION -- 11 --- HYGIENE OVERSEER OR FACE CORRECTIVE DISCIPLINE -- 12 ----------------------------------------------------- 13 - 14 -local lib = starsoul.mod.lib 15 -local sec = {} 16 -starsoul.mod.secrets = sec 17 - 18 -sec.index = lib.registry.mk 'starsoul_secrets:secret' 19 - 20 ---[==[ 21 - 22 -a secret is a piece of information that is made available 23 -for review once certain conditions are met. despite the name, 24 -it doesn't necessarily have to be secret -- it could include 25 -e.g. journal entries about a character's background. a secret 26 -is defined in the following manner: 27 - 28 -{ 29 - title = the string that appears in the UI 30 - stages = { 31 - { 32 - prereqs = { 33 - {kind = 'fact', id = 'starsoul:terroristEmployer'} 34 - {kind = 'item', id = 'starsoul_electronic:firstbornDoomBong'} 35 - {kind = 'background', id = 'starsoul:terroristTagalong'} 36 - } 37 - body = { 38 - 'the firstborn smonked hella weed'; 39 - }; 40 - -- body can also be a function(user,secret) 41 - } 42 - } 43 -} 44 - 45 -TODO would it be useful to impl horn clauses and a general fact database? 46 - is that level of flexibility meaningful? or are simply flags better 47 - 48 -a secret can be a single piece of information predicated 49 -on a fact, in which case the secret and fact should share 50 -the same ID. the ID should be as non-indicative as possible 51 -to avoid spoilers for devs of unrelated code. 52 - 53 -a secret can also be manually unlocked e.g. by using an item 54 - 55 -]==]-- 56 - 57 -function sec.prereqCheck(user, pr) 58 -end
Deleted mods/starsoul-secrets/mod.conf version [26c2999a4f].
1 -name = starsoul_secrets 2 -title = starsoul secrets 3 -description = TS//NOFORN 4 -depends = starsoul
Deleted mods/starsoul-suit/init.lua version [d4fabd1162].
1 -local lib = starsoul.mod.lib 2 -local fab = starsoul.type.fab 3 - 4 -local facDescs = { 5 - commune = { 6 - survival = { 7 - suit = 'A light, simple, bare-bones environment suit that will provide heating, cooling, and nanide support to a stranded cosmonaut'; 8 - cc = 'The Survival Suit uses compact thermoelectrics to keep the wearer perfectly comfortable in extremes of heat or cold. It makes up for its heavy power usage with effective insulation that substantially reduces any need for climate control.'; 9 - }; 10 - engineer = { 11 - suit = 'A lightweight environment suit designed for indoor work, the Commune\'s Engineer Suit boasts advanced nanotech capable of constructing objects in place.'; 12 - cc = 'The Engineer Suit is designed for indoor work. Consequently, it features only a low-power thermoelectric cooler meant to keep its wearer comfortable during strenuous work.'; 13 - }; 14 - combat = { 15 - suit = 'A military-grade suit with the latest Commune technology. Designed for maximum force multiplication, the suit has dual weapon hardpoints and supports a gargantuan power reserve. Its nanotech systems are specialized for tearing through obstacles, but can also be used to manufacturer ammunition in a pinch.'; 16 - cc = 'This Combat Suit uses electrothermal cooling to keep an active soldier comfortable and effective, as well as conventional heating coils to enable operation in hostile atmospheres.'; 17 - }; 18 - }; 19 - 20 -} 21 - 22 - 23 -starsoul.world.tier.foreach('starsoul:suit-gen', {}, function(tid, t) 24 - local function hasTech(tech) 25 - return starsoul.world.tier.tech(tid, tech) 26 - end 27 - if not hasTech 'suit' then return end 28 - -- TODO tier customization 29 - -- 30 - local function fabsum(f) 31 - return starsoul.world.tier.fabsum(tid, 'suit') 32 - end 33 - local function fabReq(sz, days) 34 - local tierMatBase = ( 35 - (fabsum 'electric' * 4) + 36 - (fabsum 'basis' + fabsum 'suit') * sz 37 - ) 38 - local b = tierMatBase + fab { 39 - -- universal suit requirements 40 - time = { print = 60*60*24 * days }; 41 - size = { printBay = sz }; 42 - } 43 - b.flag = lib.tbl.set('print'); 44 - return b 45 - end 46 - local function facDesc(s, t) 47 - local default = 'A protective nanosuit' -- FIXME 48 - if not facDescs[tid] then return default end 49 - if not facDescs[tid][s] then return default end 50 - if not facDescs[tid][s][t] then return default end 51 - return facDescs[tid][s][t] 52 - end 53 - starsoul.item.suit.link('starsoul_suit:suit_survival_' .. tid, { 54 - name = t.name .. ' Survival Suit'; 55 - desc = facDesc('survival','suit'); 56 - fab = fabReq(1, 2.2) + fab { }; 57 - tex = { 58 - plate = { 59 - id = 'starsoul-suit-survival-plate'; 60 - tint = lib.color {hue = 210, sat = .5, lum = .5}; 61 - }; 62 - lining = { 63 - id = 'starsoul-suit-survival-lining'; 64 - tint = lib.color {hue = 180, sat = .2, lum = .7}; 65 - }; 66 - }; 67 - tints = {'suit_plate', 'suit_lining'}; 68 - temp = { 69 - desc = facDesc('survival','cc'); 70 - maxHeat = 0.7; -- can produce a half-degree Δ per second 71 - maxCool = 0.5; 72 - heatPower = 50; -- 50W 73 - coolPower = 50/t.efficiency; 74 - insulation = 0.5; -- prevent half of heat loss 75 - }; 76 - protection = { 77 - rad = 0.7; -- blocks 70% of ionizing radiation 78 - }; 79 - slots = { 80 - canisters = 1; 81 - batteries = math.ceil(math.max(1, t.power/2)); 82 - chips = 3; 83 - guns = 0; 84 - ammo = 0; 85 - }; 86 - nano = { 87 - compileSpeed = 0.1 * t.efficiency; 88 - shredSpeed = 0.1 * t.power; 89 - fabSizeLimit = 0.6; -- 60cm 90 - }; 91 - }) 92 - 93 - starsoul.item.suit.link('starsoul_suit:suit_engineer_' .. tid, { 94 - name = t.name .. ' Engineer Suit'; 95 - desc = facDesc('engineer','suit'); 96 - tex = { 97 - plate = { 98 - id = 'starsoul-suit-survival-plate'; 99 - tint = lib.color {hue = 0, sat = .5, lum = .7}; 100 - }; 101 - }; 102 - tints = {'suit_plate', 'suit_lining'}; 103 - fab = fabReq(.8, 7) + fab { }; 104 - temp = { 105 - desc = facDesc('engineer','cc'); 106 - maxHeat = 0; 107 - maxCool = 0.2; 108 - heatPower = 0; 109 - coolPower = 10 / t.efficiency; 110 - insulation = 0.1; -- no lining 111 - }; 112 - slots = { 113 - canisters = 2; 114 - batteries = 2; 115 - chips = 6; 116 - guns = 0; 117 - ammo = 0; 118 - }; 119 - compat = { 120 - maxBatterySize = 0.10 * t.power; -- 10cm 121 - }; 122 - protection = { 123 - rad = 0.1; -- blocks 10% of ionizing radiation 124 - }; 125 - nano = { 126 - compileSpeed = 1 * t.efficiency; 127 - shredSpeed = 0.7 * t.power; 128 - fabSizeLimit = 1.5; -- 1.5m (enables node compilation) 129 - }; 130 - }) 131 - 132 - if hasTech 'suitCombat' then 133 - starsoul.item.suit.link('starsoul_suit:suit_combat_' .. tid, { 134 - name = t.name .. ' Combat Suit'; 135 - desc = facDesc('combat','suit'); 136 - fab = fabReq(1.5, 14) + fab { 137 - metal = {iridium = 1e3}; 138 - }; 139 - tex = { 140 - plate = { 141 - id = 'starsoul-suit-survival-plate'; 142 - tint = lib.color {hue = 0, sat = 0, lum = 0}; 143 - }; 144 - lining = { 145 - id = 'starsoul-suit-survival-lining'; 146 - tint = lib.color {hue = 180, sat = .5, lum = .3}; 147 - }; 148 - }; 149 - tints = {'suit_plate', 'suit_lining'}; 150 - slots = { 151 - canisters = 1; 152 - batteries = math.ceil(math.max(3, 8*(t.power/2))); 153 - chips = 5; 154 - guns = 2; 155 - ammo = 1; 156 - }; 157 - compat = { 158 - maxBatterySize = 0.10 * t.power; -- 10cm 159 - }; 160 - temp = { 161 - desc = facDesc('combat','cc'); 162 - maxHeat = 0.3; 163 - maxCool = 0.6; 164 - heatPower = 20 / t.efficiency; 165 - coolPower = 40 / t.efficiency; 166 - insulation = 0.2; 167 - }; 168 - protection = { 169 - rad = 0.9; -- blocks 90% of ionizing radiation 170 - }; 171 - nano = { 172 - compileSpeed = 0.05; 173 - shredSpeed = 2 * t.power; 174 - fabSizeLimit = 0.3; -- 30cm 175 - }; 176 - }) 177 - end 178 -end) 179 - 180 -
Deleted mods/starsoul-suit/mod.conf version [dbff224e42].
1 -name = starsoul_suit 2 -description = defines the environment suits available in starsoul 3 -depends = starsoul, starsoul_electronics
Deleted mods/starsoul/container.lua version [72968ffd62].
1 --- a container item defines a 'container' structure listing its 2 --- inventories and their properties. a container object is created 3 --- in order to interact with a container 4 -local lib = starsoul.mod.lib 5 -starsoul.item.container = lib.class { 6 - __name = 'starsoul:container'; 7 - construct = function(stack, inv, def) 8 - local T,G = lib.marshal.t, lib.marshal.g 9 - local cdef = stack:get_definition()._starsoul.container; 10 - local sd = {} 11 - for k,v in pairs(cdef.list) do 12 - sd[k] = { 13 - key = v.key; 14 - type = T.inventoryList; 15 - } 16 - end 17 - return { 18 - stack = stack, inv = inv, pdef = def, cdef = cdef; 19 - store = lib.marshal.metaStore(sd)(stack); 20 - } 21 - end; 22 - __index = { 23 - slot = function(self, id) 24 - return string.format("%s_%s", self.pdef.pfx, id) 25 - end; 26 - clear = function(self) -- initialize or empty the metadata 27 - self:update(function() 28 - for k,v in pairs(self.cdef.list) do 29 - if v.sz > 0 then 30 - self.store.write(k, {}) 31 - end 32 - end 33 - end) 34 - end; 35 - list = function(self, k) return self.store.read(k) end; 36 - read = function(self) 37 - local lst = {} 38 - for k,v in pairs(self.cdef.list) do 39 - if v.sz > 0 then lst[k] = self:list(k) end 40 - end 41 - return lst 42 - end; 43 - pull = function(self) -- align the inventories with the metadata 44 - for k,v in pairs(self.cdef.list) do 45 - if v.sz > 0 then 46 - local stacks = self:list(k) 47 - local sid = self:slot(k) 48 - self.inv:set_size(sid, v.sz) 49 - self.inv:set_list(sid, stacks) 50 - end 51 - end 52 - end; 53 - update = function(self, fn) 54 - local old = ItemStack(self.stack) 55 - if fn then fn() end 56 - if self.cdef.handle then 57 - self.cdef.handle(self.stack, old) 58 - end 59 - end; 60 - push = function(self) -- align the metadata with the inventories 61 - self:update(function() 62 - for k,v in pairs(self.cdef.list) do 63 - if v.sz > 0 then 64 - local sid = self:slot(k) 65 - local lst = self.inv:get_list(sid) 66 - self.store.write(k, lst) 67 - end 68 - end 69 - end) 70 - end; 71 - drop = function(self) -- remove the inventories from the node/entity 72 - for k,v in pairs(self.cdef.list) do 73 - local sid = self:slot(k) 74 - self.inv:set_size(sid, 0) 75 - end 76 - end; 77 - slotAccepts = function(self, lst, slot, stack) 78 - end; 79 - }; 80 -} 81 - 82 -function starsoul.item.container.dropPrefix(inv, pfx) 83 - local lists = inv:get_lists() 84 - for k,v in pairs(lists) do 85 - if #k > #pfx then 86 - if string.sub(k, 1, #pfx + 1) == pfx .. '_' then 87 - inv:set_size(k, 0) 88 - end 89 - end 90 - end 91 -end
Deleted mods/starsoul/effect.lua version [2bec98314f].
1 --- ported from sorcery/spell.lua, hence the lingering refs to "magic" 2 --- 3 --- this file is used to track active effects, for the purposes of metamagic 4 --- like disjunction. a "effect" is a table consisting of several properties: 5 --- a "disjoin" function that, if present, is called when the effect is 6 --- abnormally interrupted, a "terminate" function that calls when the effect 7 --- completes, a "duration" property specifying how long the effect lasts in 8 --- seconds, and a "timeline" table that maps floats to functions called at 9 --- specific points during the function's activity. it can also have a 10 --- 'delay' property that specifies how long to wait until the effect sequence 11 --- starts; the effect is however still vulnerable to disjunction during this 12 --- period. there can also be a sounds table that maps timepoints to sounds 13 --- the same way timeline does. each value should be a table of form {sound, 14 --- where}. the `where` field may contain one of 'pos', 'caster', 'subjects', or 15 --- a vector specifying a position in the world, and indicate where the sound 16 --- should be played. by default 'caster' and 'subjects' sounds will be attached 17 --- to the objects they reference; 'attach=false' can be added to prevent this. 18 --- by default sounds will be faded out quickly when disjunction occurs; this 19 --- can be controlled by the fade parameter. 20 --- 21 --- effects can have various other properties, for instance 'disjunction', which 22 --- when true prevents other effects from being cast in its radius while it is 23 --- still in effect. disjunction is absolute; there is no way to overwhelm it. 24 --- 25 --- the effect also needs at least one of "anchor", "subjects", or "caster". 26 --- * an anchor is a position that, in combination with 'range', specifies the area 27 --- where a effect is in effect; this is used for determining whether it 28 --- is affected by a disjunction that incorporates part of that position 29 --- * subjects is an array of individuals affected by the effect. when 30 --- disjunction is cast on one of them, they will be removed from the 31 --- table. each entry should have at least a 'player' field; they can 32 --- also contain any other data useful to the effect. if a subject has 33 --- a 'disjoin' field it must be a function called when they are removed 34 --- from the list of effect targets. 35 --- * caster is the individual who cast the effect, if any. a disjunction 36 --- against their person will totally disrupt the effect. 37 -local log = starsoul.logger 'effect' 38 -local lib = starsoul.mod.lib 39 - 40 --- FIXME saving object refs is iffy, find a better alternative 41 -starsoul.effect = { 42 - active = {} 43 -} 44 - 45 -local get_effect_positions = function(effect) 46 - local effectpos 47 - if effect.anchor then 48 - effectpos = {effect.anchor} 49 - elseif effect.attach then 50 - if effect.attach == 'caster' then 51 - effectpos = {effect.caster:get_pos()} 52 - elseif effect.attach == 'subjects' or effect.attach == 'both' then 53 - if effect.attach == 'both' then 54 - effectpos = {effect.caster:get_pos()} 55 - else effectpos = {} end 56 - for _,s in pairs(effect.subjects) do 57 - effectpos[#effectpos+1] = s.player:get_pos() 58 - end 59 - else effectpos = {effect.attach:get_pos()} end 60 - else assert(false) end 61 - return effectpos 62 -end 63 - 64 -local ineffectrange = function(effect,pos,range) 65 - local effectpos = get_effect_positions(effect) 66 - 67 - for _,p in pairs(effectpos) do 68 - if vector.equals(pos,p) or 69 - (range and lib.math.vdcomp(range, pos,p)<=1) or 70 - (effect.range and lib.math.vdcomp(effect.range,p,pos)<=1) then 71 - return true 72 - end 73 - end 74 - return false 75 -end 76 - 77 -starsoul.effect.probe = function(pos,range) 78 - -- this should be called before any effects are performed. 79 - -- other mods can overlay their own functions to e.g. protect areas 80 - -- from effects 81 - local result = {} 82 - 83 - -- first we need to check if any active injunctions are in effect 84 - -- injunctions are registered as effects with a 'disjunction = true' 85 - -- property 86 - for id,effect in pairs(starsoul.effect.active) do 87 - if not (effect.disjunction and (effect.anchor or effect.attach)) then goto skip end 88 - if ineffectrange(effect,pos,range) then 89 - result.disjunction = true 90 - break 91 - end 92 - ::skip::end 93 - 94 - -- at some point we might also check to see if certain anti-effect 95 - -- blocks are nearby or suchlike. there could also be regions where 96 - -- perhaps certain kinds of effect are unusually empowered or weak 97 - return result 98 -end 99 -starsoul.effect.disjoin = function(d) 100 - local effects,targets = {},{} 101 - if d.effect then effects = {{v=d.effect}} 102 - elseif d.target then targets = {d.target} 103 - elseif d.pos then -- find effects anchored here and people in range 104 - for id,effect in pairs(starsoul.effect.active) do 105 - if not effect.anchor then goto skip end -- this intentionally excludes attached effects 106 - if ineffectrange(effect,d.pos,d.range) then 107 - effects[#effects+1] = {v=effect,i=id} 108 - end 109 - ::skip::end 110 - local ppl = minetest.get_objects_inside_radius(d.pos,d.range) 111 - if #targets == 0 then targets = ppl else 112 - for _,p in pairs(ppl) do targets[#targets+1] = p end 113 - end 114 - end 115 - 116 - -- iterate over targets to remove from any effect's influence 117 - for _,t in pairs(targets) do 118 - for id,effect in pairs(starsoul.effect.active) do 119 - if effect.caster == t then effects[#effects+1] = {v=effect,i=id} else 120 - for si, sub in pairs(effect.subjects) do 121 - if sub.player == t then 122 - if sub.disjoin then sub:disjoin(effect) end 123 - effect.release_subject(si) 124 - break 125 - end 126 - end 127 - end 128 - end 129 - end 130 - 131 - -- effects to disjoin entirely 132 - for _,s in pairs(effects) do local effect = s.v 133 - if effect.disjoin then effect:disjoin() end 134 - effect.abort() 135 - if s.i then starsoul.effect.active[s.i] = nil else 136 - for k,v in pairs(starsoul.effect.active) do 137 - if v == effect then starsoul.effect.active[k] = nil break end 138 - end 139 - end 140 - end 141 -end 142 - 143 -starsoul.effect.ensorcelled = function(player,effect) 144 - if type(player) == 'string' then player = minetest.get_player_by_name(player) end 145 - for _,s in pairs(starsoul.effect.active) do 146 - if effect and (s.name ~= effect) then goto skip end 147 - for _,sub in pairs(s.subjects) do 148 - if sub.player == player then return s end 149 - end 150 - ::skip::end 151 - return false 152 -end 153 - 154 -starsoul.effect.each = function(player,effect) 155 - local idx = 0 156 - return function() 157 - repeat idx = idx + 1 158 - local sp = starsoul.effect.active[idx] 159 - if sp == nil then return nil end 160 - if effect == nil or sp.name == effect then 161 - for _,sub in pairs(sp.subjects) do 162 - if sub.player == player then return sp end 163 - end 164 - end 165 - until idx >= #starsoul.effect.active 166 - end 167 -end 168 - 169 --- when a new effect is created, we analyze it and make the appropriate calls 170 --- to minetest.after to queue up the events. each job returned needs to be 171 --- saved in 'jobs' so they can be canceled if the effect is disjoined. no polling 172 --- necessary :D 173 - 174 -starsoul.effect.cast = function(proto) 175 - local s = table.copy(proto) 176 - s.jobs = s.jobs or {} s.vfx = s.vfx or {} s.sfx = s.sfx or {} 177 - s.impacts = s.impacts or {} s.subjects = s.subjects or {} 178 - s.delay = s.delay or 0 179 - s.visual = function(subj, def) 180 - s.vfx[#s.vfx + 1] = { 181 - handle = minetest.add_particlespawner(def); 182 - subject = subj; 183 - } 184 - end 185 - s.visual_caster = function(def) -- convenience function 186 - local d = table.copy(def) 187 - d.attached = s.caster 188 - s.visual(nil, d) 189 - end 190 - s.visual_subjects = function(def) 191 - for _,sub in pairs(s.subjects) do 192 - local d = table.copy(def) 193 - d.attached = sub.player 194 - s.visual(sub, d) 195 - end 196 - end 197 - s.affect = function(i) 198 - local etbl = {} 199 - for _,sub in pairs(s.subjects) do 200 - -- local eff = late.new_effect(sub.player, i) 201 - -- starsoul will not be using late 202 - local rec = { 203 - effect = eff; 204 - subject = sub; 205 - } 206 - s.impacts[#s.impacts+1] = rec 207 - etbl[#etbl+1] = rec 208 - end 209 - return etbl 210 - end 211 - s.abort = function() 212 - for _,j in ipairs(s.jobs) do j:cancel() end 213 - for _,v in ipairs(s.vfx) do minetest.delete_particlespawner(v.handle) end 214 - for _,i in ipairs(s.sfx) do s.silence(i) end 215 - for _,i in ipairs(s.impacts) do i.effect:stop() end 216 - end 217 - s.release_subject = function(si) 218 - local t = s.subjects[si] 219 - for _,f in pairs(s.sfx) do if f.subject == t then s.silence(f) end end 220 - for _,f in pairs(s.impacts) do if f.subject == t then f.effect:stop() end end 221 - for _,f in pairs(s.vfx) do 222 - if f.subject == t then minetest.delete_particlespawner(f.handle) end 223 - end 224 - s.subjects[si] = nil 225 - end 226 - local interpret_timespec = function(when) 227 - if when == nil then return 0 end 228 - local t if type(when) == 'number' then 229 - t = s.duration * when 230 - else 231 - t = (s.duration * (when.whence or 0)) + (when.secs or 0) 232 - end 233 - if t then return math.min(s.duration,math.max(0,t)) end 234 - 235 - log.err('invalid timespec ' .. dump(when)) 236 - return 0 237 - end 238 - s.queue = function(when,fn) 239 - local elapsed = s.starttime and minetest.get_server_uptime() - s.starttime or 0 240 - local timepast = interpret_timespec(when) 241 - if not timepast then timepast = 0 end 242 - local timeleft = s.duration - timepast 243 - local howlong = (s.delay + timepast) - elapsed 244 - if howlong < 0 then 245 - log.err('cannot time-travel! queue() called with `when` specifying timepoint that has already passed') 246 - howlong = 0 247 - end 248 - s.jobs[#s.jobs+1] = minetest.after(howlong, function() 249 - -- this is somewhat awkward. since we're using a non-polling approach, we 250 - -- need to find a way to account for a caster or subject walking into an 251 - -- existing antimagic field, or someone with an existing antimagic aura 252 - -- walking into range of the anchor. so every time a effect effect would 253 - -- take place, we first check to see if it's in range of something nasty 254 - if not s.disjunction and -- avoid self-disjunction 255 - ((s.caster and starsoul.effect.probe(s.caster:get_pos()).disjunction) or 256 - (s.anchor and starsoul.effect.probe(s.anchor,s.range).disjunction)) then 257 - starsoul.effect.disjoin{effect=s} 258 - else 259 - if not s.disjunction then for _,sub in pairs(s.subjects) do 260 - local sp = sub.player:get_pos() 261 - if starsoul.effect.probe(sp).disjunction then 262 - starsoul.effect.disjoin{pos=sp} 263 - end 264 - end end 265 - -- effect still exists and we've removed any subjects who have been 266 - -- affected by a disjunction effect, it's now time to actually perform 267 - -- the queued-up action 268 - fn(s,timepast,timeleft) 269 - end 270 - end) 271 - end 272 - s.play_now = function(spec) 273 - local specs, stbl = {}, {} 274 - local addobj = function(obj,sub) 275 - if spec.attach == false then specs[#specs+1] = { 276 - spec = { pos = obj:get_pos() }; 277 - obj = obj, subject = sub; 278 - } else specs[#specs+1] = { 279 - spec = { object = obj }; 280 - obj = obj, subject = sub; 281 - } end 282 - end 283 - 284 - if spec.where == 'caster' then addobj(s.caster) 285 - elseif spec.where == 'subjects' then 286 - for _,sub in pairs(s.subjects) do addobj(sub.player,sub) end 287 - elseif spec.where == 'pos' then specs[#specs+1] = { spec = {pos = s.anchor} } 288 - else specs[#specs+1] = { spec = {pos = spec.where} } end 289 - 290 - for _,sp in pairs(specs) do 291 - sp.spec.gain = sp.spec.gain or spec.gain 292 - local so = { 293 - handle = minetest.sound_play(spec.sound, sp.spec, spec.ephemeral); 294 - ctl = spec; 295 - -- object = sp.obj; 296 - subject = sp.subject; 297 - } 298 - stbl[#stbl+1] = so 299 - s.sfx[#s.sfx+1] = so 300 - end 301 - return stbl 302 - end 303 - s.play = function(when,spec) 304 - s.queue(when, function() 305 - local snds = s.play_now(spec) 306 - if spec.stop then 307 - s.queue(spec.stop, function() 308 - for _,snd in pairs(snds) do s.silence(snd) end 309 - end) 310 - end 311 - end) 312 - end 313 - s.silence = function(sound) 314 - if sound.ctl.fade == 0 then minetest.sound_stop(sound.handle) 315 - else minetest.sound_fade(sound.handle,sound.ctl.fade or 1,0) end 316 - end 317 - local startqueued, termqueued = false, false 318 - local myid = #starsoul.effect.active+1 319 - s.cancel = function() 320 - s.abort() 321 - starsoul.effect.active[myid] = nil 322 - end 323 - local perform_disjunction_calls = function() 324 - local positions = get_effect_positions(s) 325 - for _,p in pairs(positions) do 326 - starsoul.effect.disjoin{pos = p, range = s.range} 327 - end 328 - end 329 - if s.timeline then 330 - for when_raw,what in pairs(s.timeline) do 331 - local when = interpret_timespec(when_raw) 332 - if s.delay == 0 and when == 0 then 333 - startqueued = true 334 - if s.disjunction then perform_disjunction_calls() end 335 - what(s,0,s.duration) 336 - elseif when_raw == 1 or when >= s.duration then -- avoid race conditions 337 - if not termqueued then 338 - termqueued = true 339 - s.queue(1,function(s,...) 340 - what(s,...) 341 - if s.terminate then s:terminate() end 342 - starsoul.effect.active[myid] = nil 343 - end) 344 - else 345 - log.warn('multiple final timeline events not possible, ignoring') 346 - end 347 - elseif when == 0 and s.disjunction then 348 - startqueued = true 349 - s.queue(when_raw,function(...) 350 - perform_disjunction_calls() 351 - what(...) 352 - end) 353 - else s.queue(when_raw,what) end 354 - end 355 - end 356 - if s.intervals then 357 - for _,int in pairs(s.intervals) do 358 - local timeleft = s.duration - interpret_timespec(int.after) 359 - local iteration, itercount = 0, timeleft / int.period 360 - local function iterate(lastreturn) 361 - iteration = iteration + 1 362 - local nr = int.fn { 363 - effect = s; 364 - iteration = iteration; 365 - iterationcount = itercount; 366 - timeleft = timeleft; 367 - timeelapsed = s.duration - timeleft; 368 - lastreturn = lastreturn; 369 - } 370 - if nr ~= false and iteration < itercount then 371 - s.jobs[#s.jobs+1] = minetest.after(int.period, 372 - function() iterate(nr) end) 373 - end 374 - end 375 - if int.after 376 - then s.queue(int.after, iterate) 377 - else s.queue({whence=0, secs=s.period}, iterate) 378 - end 379 - end 380 - end 381 - if s.disjunction and not startqueued then 382 - if s.delay == 0 then perform_disjunction_calls() else 383 - s.queue(0, function() perform_disjunction_calls() end) 384 - end 385 - end 386 - if s.sounds then 387 - for when,what in pairs(s.sounds) do s.play(when,what) end 388 - end 389 - starsoul.effect.active[myid] = s 390 - if not termqueued then 391 - s.jobs[#s.jobs+1] = minetest.after(s.delay + s.duration, function() 392 - if s.terminate then s:terminate() end 393 - starsoul.effect.active[myid] = nil 394 - end) 395 - end 396 - s.starttime = minetest.get_server_uptime() 397 - return s 398 -end 399 - 400 -minetest.register_on_dieplayer(function(player) 401 - starsoul.effect.disjoin{target=player} 402 -end)
Deleted mods/starsoul/element.lua version [52033ae372].
1 -local lib = starsoul.mod.lib 2 -local W = starsoul.world 3 -local M = W.material 4 - 5 -M.element.foreach('starsoul:sort', {}, function(id, m) 6 - if m.metal then 7 - M.metal.link(id, { 8 - name = m.name; 9 - composition = starsoul.type.fab{element = {[id] = 1}}; 10 - color = m.color; 11 - -- n.b. this is a RATIO: it will be appropriately multiplied 12 - -- for the object in question; e.g a normal chunk will be 13 - -- 100 $element, an ingot will be 1000 $element 14 - }) 15 - elseif m.gas then 16 - M.gas.link(id, { 17 - name = m.name; 18 - composition = starsoul.type.fab{element = {[id] = 1}}; 19 - }) 20 - elseif m.liquid then 21 - M.liquid.link(id, { 22 - name = m.name; 23 - composition = starsoul.type.fab{element = {[id] = 1}}; 24 - }) 25 - end 26 -end) 27 - 28 -local F = string.format 29 - 30 -local function mkEltIndicator(composition) 31 - local indicator = '' 32 - local idx = 0 33 - local ccount = 0 34 - for _ in pairs(composition) do 35 - ccount = ccount + 1 36 - end 37 - local indsz,indpad = 28,4 38 - local ofs = math.min(11, (indsz-indpad)/ccount) 39 - for id, amt in pairs(composition) do 40 - idx = idx + 1 41 - indicator = indicator .. F( 42 - ':%s,3=starsoul-element-%s.png', 43 - (indsz-indpad) - (idx*ofs), id 44 - ) 45 - end 46 - indicator = lib.image(indicator) 47 - return function(s) 48 - return string.format('(%s^[resize:%sx%s)^[combine:%sx%s%s', 49 - s, 50 - indsz, indsz, 51 - indsz, indsz, 52 - indicator); 53 - end 54 -end 55 - 56 -M.element.foreach('starsoul:gen-forms', {}, function(id, m) 57 - local eltID = F('%s:element_%s', minetest.get_current_modname(), id) 58 - local eltName = F('Elemental %s', lib.str.capitalize(m.name)) 59 - local tt = function(t, d, g) 60 - return starsoul.ui.tooltip { 61 - title = t, desc = d; 62 - color = lib.color(0.1,0.2,0.1); 63 - props = { 64 - {title = 'Mass', desc = lib.math.si('g', g), affinity='info'} 65 - } 66 - } 67 - end 68 - local comp = {[id] = 1} 69 - local iblit = mkEltIndicator(comp) 70 - m.form = m.form or {} 71 - m.form.element = eltID 72 - 73 - local powder = F('starsoul-element-%s-powder.png', id); 74 - minetest.register_craftitem(eltID, { 75 - short_description = eltName; 76 - description = tt(eltName, F('Elemental %s kept in suspension by a nanide storage system, ready to be worked by a cold matter compiler', m.name), 1); 77 - inventory_image = iblit(powder); 78 - wield_image = powder; 79 - stack_max = 1000; -- 1kg 80 - groups = {element = 1, powder = 1, specialInventory = 1}; 81 - _starsoul = { 82 - mass = 1; 83 - material = { 84 - kind = 'element'; 85 - element = id; 86 - }; 87 - fab = starsoul.type.fab { 88 - element = comp; 89 - }; 90 - }; 91 - }); 92 -end) 93 - 94 - 95 -M.metal.foreach('starsoul:gen-forms', {}, function(id, m) 96 - local baseID = F('%s:metal_%s_', minetest.get_current_modname(), id) 97 - local brickID, ingotID = baseID .. 'brick', baseID .. 'ingot' 98 - local brickName, ingotName = 99 - F('%s Brick', lib.str.capitalize(m.name)), 100 - F('%s Ingot', lib.str.capitalize(m.name)) 101 - m.form = m.form or {} 102 - m.form.brick = brickID 103 - m.form.ingot = ingotID 104 - local tt = function(t, d, g) 105 - return starsoul.ui.tooltip { 106 - title = t, desc = d; 107 - color = lib.color(0.1,0.1,0.1); 108 - props = { 109 - {title = 'Mass', desc = lib.math.si('g', g), affinity='info'} 110 - } 111 - } 112 - end 113 - local mcomp = m.composition:elementalize().element 114 - local function comp(n) 115 - local t = {} 116 - for id, amt in pairs(mcomp) do 117 - t[id] = amt * n 118 - end 119 - return t 120 - end 121 - local iblit = mkEltIndicator(mcomp) 122 - local function img(s) 123 - return iblit(s:colorize(m.color):render()) 124 - end 125 - 126 - minetest.register_craftitem(brickID, { 127 - short_description = brickName; 128 - description = tt(brickName, F('A solid brick of %s, ready to be worked by a matter compiler', m.name), 100); 129 - inventory_image = img(lib.image 'starsoul-item-brick.png'); 130 - wield_image = lib.image 'starsoul-item-brick.png':colorize(m.color):render(); 131 - stack_max = 10; 132 - groups = {metal = 1, ingot = 1}; 133 - _starsoul = { 134 - mass = 100; 135 - material = { 136 - kind = 'metal'; 137 - metal = id; 138 - }; 139 - fab = starsoul.type.fab { 140 - flag = {smelt= true}; 141 - element = comp(1e2); 142 - }; 143 - }; 144 - }); 145 - 146 - minetest.register_craftitem(ingotID, { 147 - short_description = ingotName; 148 - description = tt(ingotName, F('A solid ingot of %s, ready to be worked by a large matter compiler', m.name), 1e3); 149 - inventory_image = img(lib.image('starsoul-item-ingot.png')); 150 - wield_image = lib.image 'starsoul-item-ingot.png':colorize(m.color):render(); 151 - groups = {metal = 1, ingot = 1}; 152 - stack_max = 5; 153 - _starsoul = { 154 - mass = 1e3; 155 - material = { 156 - kind = 'metal'; 157 - metal = id; 158 - }; 159 - fab = starsoul.type.fab { 160 - flag = {smelt= true}; 161 - element = comp(1e3); 162 - }; 163 - }; 164 - }); 165 - 166 - 167 -end) 168 - 169 -local function canisterDesc(stack, def) 170 - def = def or stack:get_definition()._starsoul.canister 171 - local props = { 172 - {title = 'Charge Slots', affinity = 'info', desc = tostring(def.slots)}; 173 - }; 174 - if stack then 175 - local inv = starsoul.item.container(stack) 176 - for i,e in ipairs(inv:list 'elem') do 177 - local comp = e:get_definition()._starsoul.fab 178 - table.insert(props, { 179 - title = comp:formula(); 180 - desc = lib.math.si('g', e:get_count()); 181 - affinity = 'good'; 182 - }) 183 - end 184 - -- TODO list masses 185 - end 186 - return starsoul.ui.tooltip { 187 - title = def.name, desc = def.desc or 'A canister that can store a charge of elemental powder, gas, or liquid'; 188 - color = lib.color(0.2,0.1,0.1); 189 - props = props; 190 - }; 191 -end 192 - 193 -starsoul.item.canister = lib.registry.mk 'starsoul:canister'; 194 -starsoul.item.canister.foreach('starsoul:item-gen', {}, function(id, c) 195 - minetest.register_craftitem(id, { 196 - short_description = c.name; 197 - description = canisterDesc(nil, c); 198 - inventory_image = c.image or 'starsoul-item-element-canister.png'; 199 - groups = {canister = 1}; 200 - stack_max = 1; 201 - _starsoul = { 202 - canister = c; 203 - container = { 204 - handle = function(stack, oldstack) 205 - stack:get_meta():set_string('description', canisterDesc(stack)) 206 - return stack 207 - end; 208 - list = { 209 - elem = { 210 - key = 'starsoul:canister_elem'; 211 - accept = 'powder'; 212 - sz = c.slots; 213 - }; 214 - }; 215 - }; 216 - }; 217 - }) 218 -end)
Deleted mods/starsoul/fab.lua version [d8c093fcd3].
1 --- [ʞ] fab.lua 2 --- ~ lexi hale <lexi@hale.su> 3 --- 🄯 EUPL1.2 4 --- ? fabrication spec class 5 --- a type.fab supports two operators: 6 --- 7 --- + used for compounding recipes. that is, 8 --- a+b = compose a new spec from the spec parts a and b. 9 --- this is used e.g. for creating tier-based 10 --- fabspecs. 11 --- 12 --- * used for determining quantities. that is, 13 --- f*x = spec to make x instances of f 14 --- 15 --- new fab fields must be defined in starsoul.type.fab.opClass. 16 --- this maps a name to fn(a,b,n) -> quant, where a is the first 17 --- argument, b is a compounding amount, and n is a quantity of 18 --- items to produce. fields that are unnamed will be underwritten 19 - 20 -local function fQuant(a,b,n) return ((a or 0)+(b or 0))*n end 21 -local function fFac (a,b,n) 22 - if a == nil and b == nil then return nil end 23 - local f if a == nil or b == nil then 24 - f = a or b 25 - else 26 - f = (a or 1)*(b or 1) 27 - end 28 - return f*n 29 -end 30 -local function fReq (a,b,n) return a or b end 31 -local function fFlag (a,b,n) return a and b end 32 -local function fSize (a,b,n) return math.max(a,b) end 33 -local opClass = { 34 - -- fabrication eligibility will be determined by which kinds 35 - -- of input a particular fabricator can introduce. e.g. a 36 - -- printer with a but no cache can only print items whose 37 - -- recipe only names elements as ingredients 38 - 39 - -- ingredients 40 - element = fQuant; -- (g) 41 - gas = fQuant; -- () 42 - liquid = fQuant; -- (l) 43 - crystal = fQuant; -- (g) 44 - item = fQuant; -- n 45 - metal = fQuant; -- (g) 46 - metalIngot = fQuant; -- (g) 47 - -- factors 48 - cost = fFac; -- units vary 49 - time = fFac; -- (s) 50 - -- print: base printing time 51 - size = fSize; 52 - -- printBay: size of the printer bay necessary to produce the item 53 - req = fReq; 54 - flag = fFlag; -- means that can be used to produce the item & misc flags 55 - -- print: allow production with a printer 56 - -- smelt: allow production with a smelter 57 - -- all else defaults to underwrite 58 -} 59 - 60 -local F = string.format 61 -local strClass = { 62 - element = function(x, n) 63 - local el = starsoul.world.material.element[x] 64 - return lib.math.si('g', n) .. ' ' .. (el.sym or el.name) 65 - end; 66 - metal = function(x, n) 67 - local met = starsoul.world.material.metal[x] 68 - return lib.math.si('g', n) .. ' ' .. met.name 69 - end; 70 - liquid = function(x, n) 71 - local liq = starsoul.world.material.liquid[x] 72 - return lib.math.si('L', n) .. ' ' .. liq.name 73 - end; 74 - gas = function(x, n) 75 - local gas = starsoul.world.material.gas[x] 76 - return lib.math.si('g', n) .. ' ' .. gas.name 77 - end; 78 - item = function(x, n) 79 - local i = minetest.registered_items[x] 80 - return tostring(n) .. 'x ' .. i.short_description 81 - end; 82 -} 83 - 84 -local order = { 85 - 'element', 'metal', 'liquid', 'gas', 'item' 86 -} 87 - 88 -local lib = starsoul.mod.lib 89 -local fab fab = lib.class { 90 - __name = 'starsoul:fab'; 91 - 92 - opClass = opClass; 93 - strClass = strClass; 94 - order = order; 95 - construct = function(q) return q end; 96 - __index = { 97 - elementalize = function(self) 98 - local e = fab {element = self.element or {}} 99 - for _, kind in pairs {'metal', 'gas', 'liquid'} do 100 - for m,mass in pairs(self[kind] or {}) do 101 - local mc = starsoul.world.material[kind][m].composition 102 - e = e + mc:elementalize()*mass 103 - end 104 - end 105 - return e 106 - end; 107 - 108 - elementSeq = function(self) 109 - local el = {} 110 - local em = self.element 111 - local s = 0 112 - local eldb = starsoul.world.material.element.db 113 - for k in pairs(em) do table.insert(el, k) s=s+eldb[k].n end 114 - table.sort(el, function(a,b) 115 - return eldb[a].n > eldb[b].n 116 - end) 117 - return el, em, s 118 - end; 119 - 120 - formula = function(self) 121 - print('make formula', dump(self)) 122 - local ts,f=0 123 - if self.element then 124 - f = {} 125 - local el, em, s = self:elementSeq() 126 - local eldb = starsoul.world.material.element.db 127 - for i, e in ipairs(el) do 128 - local sym, n = eldb[e].sym, em[e] 129 - if n > 0 then 130 - table.insert(f, string.format("%s%s", 131 - sym, n>1 and lib.str.nIdx(n) or '')) 132 - end 133 - end 134 - f = table.concat(f) 135 - ts = ts + s 136 - end 137 - 138 - local sub = {} 139 - for _, w in pairs {'metal', 'gas', 'liquid'} do 140 - if self[w] then 141 - local mdb = starsoul.world.material[w].db 142 - for k, amt in pairs(self[w]) do 143 - local mf, s = mdb[k].composition:formula() 144 - if amt > 0 then table.insert(sub, { 145 - f = string.format("(%s)%s",mf, 146 - lib.str.nIdx(amt)); 147 - s = s; 148 - }) end 149 - ts = ts + s*amt 150 - end 151 - end 152 - end 153 - table.sort(sub, function(a,b) return a.s > b.s end) 154 - local fml = {} 155 - for i, v in ipairs(sub) do fml[i] = v.f end 156 - if f then table.insert(fml, f) end 157 - fml = table.concat(fml, ' + ') 158 - 159 - return fml, ts 160 - end; 161 - }; 162 - 163 - __tostring = function(self) 164 - local t = {} 165 - for i,o in ipairs(order) do 166 - if self[o] then 167 - for mat,amt in pairs(self[o]) do 168 - if amt > 0 then 169 - table.insert(t, strClass[o](mat, amt)) 170 - end 171 - end 172 - end 173 - end 174 - return table.concat(t, ", ") 175 - end; 176 - 177 - 178 - __add = function(a,b) 179 - local new = fab {} 180 - for cat, vals in pairs(a) do 181 - new[cat] = lib.tbl.copy(vals) 182 - end 183 - for cat, vals in pairs(b) do 184 - if not new[cat] then 185 - new[cat] = lib.tbl.copy(vals) 186 - else 187 - local f = opClass[cat] 188 - for k,v in pairs(vals) do 189 - local n = f(new[cat][k], v, 1) 190 - new[cat][k] = n > 0 and n or nil 191 - end 192 - end 193 - end 194 - return new 195 - end; 196 - 197 - __mul = function(x,n) 198 - local new = fab {} 199 - for cat, vals in pairs(x) do 200 - new[cat] = {} 201 - local f = opClass[cat] 202 - for k,v in pairs(vals) do 203 - local num = f(v,nil,n) 204 - new[cat][k] = num > 0 and num or nil 205 - end 206 - end 207 - return new 208 - end; 209 - 210 - __div = function(x,n) 211 - return x * (1/n) 212 - end; 213 -} 214 - 215 -starsoul.type.fab = fab
Deleted mods/starsoul/fx/nano.lua version [4c580d7de8].
1 -local lib = starsoul.mod.lib 2 -local E = starsoul.effect 3 -local N = {} 4 -starsoul.fx.nano = N 5 -local nanopool= { 6 - { 7 - name = 'starsoul-fx-nano-spark-small.png'; 8 - scale_tween = {0,.5, style = 'pulse', rep = 3}; 9 - }; 10 - { 11 - name = 'starsoul-fx-nano-spark-small.png'; 12 - scale_tween = {0,1, style = 'pulse', rep = 2}; 13 - }; 14 - { 15 - name = 'starsoul-fx-nano-spark-big.png'; 16 - scale_tween = {0,1, style = 'pulse'}; 17 - }; 18 -} 19 - 20 -function N.heal(user, targets, amt, dur) 21 - local amthealed = {} 22 - local f = E.cast { 23 - caster = user.entity; 24 - subjects = targets; 25 - duration = dur; 26 - intervals = { 27 - { 28 - after = 0; 29 - period = 4; 30 - fn = function(c) 31 - for i,v in pairs(c.effect.subjects) do 32 - local u = starsoul.activeUsers[v.player:get_player_name()] 33 - if u then 34 - local heal = math.max(amt/4, 1) 35 - amthealed[u] = amthealed[u] or 0 36 - if amthealed[u] < amt then 37 - amthealed[u] = amthealed[u] + heal 38 - u:statDelta('health', heal) 39 - end 40 - end 41 - end 42 - end; 43 - } 44 - } 45 - } 46 - 47 - local casterIsTarget = false 48 - for _, sub in pairs(f.subjects) do 49 - if sub.player == user.entity then 50 - casterIsTarget = true 51 - end 52 - f.visual(sub, { 53 - amount = 50; 54 - time = dur; 55 - glow = 14; 56 - jitter = 0.01; 57 - attached = user.entity; 58 - vel = { min = -0.1, max = 0.1; }; 59 - pos = { 60 - min = vector.new(0,0.2,0); 61 - max = vector.new(0,1.2,0); 62 - }; 63 - radius = { min = 0.2; max = 0.6; bias = -1; }; 64 - exptime = {min=0.5,max=2}; 65 - attract = { 66 - kind = 'line'; 67 - strength = {min = 0.5, max = 2}; 68 - origin = 0; 69 - direction = vector.new(0,1,0); 70 - origin_attached = sub.player; 71 - direction_attached = sub.player; 72 - }; 73 - 74 - texpool = nanopool; 75 - }) 76 - end 77 - if not casterIsTarget then 78 - -- f.visual_caster { } 79 - end 80 - f.play(0.3, { 81 - where = 'subjects'; 82 - sound = 'starsoul-nano-heal'; 83 - ephemeral = true; 84 - spec = {gain = 0.3}; 85 - }) 86 - 87 - return f 88 -end 89 - 90 -function N.shred(user, pos, prop, time, node) 91 - local f = E.cast { 92 - caster = user.entity; 93 - subjects = {}; 94 - duration = time; 95 - } 96 - local sp,sv = user:lookupSpecies() 97 - local eh = sv.eyeHeight or sp.eyeHeight 98 - f.visual_caster { 99 - amount = 200 * time; 100 - pos = vector.new(0.12,eh - 0.1,0); 101 - radius = 0.2; 102 - time = time - (time/3); 103 - glow = 14; 104 - jitter = 0.1; 105 - size = {min = 0.2, max = 0.5}; 106 - exptime = {min=0.5,max=1}; 107 - vel_tween = { 108 - 0; 109 - { min = -0.4, max = 0.4; }; 110 - style = 'pulse', rep = time * 2; 111 - }; 112 - attract = { 113 - kind = 'point'; 114 - origin = pos; 115 - radius = 0.5; 116 - strength = {min=.3,max=2}; 117 - }; 118 - texpool = nanopool; 119 - }; 120 - f.queue(0.05, function(s, timepast, timeleft) 121 - f.visual(nil, { 122 - amount = timeleft * 40; 123 - time = timeleft; 124 - pos = pos; 125 - size_tween = { 126 - 0, {min = 0.5, max = 2}; 127 - }; 128 - vel = { 129 - min = vector.new(-1.2,0.5,-1.2); 130 - max = vector.new(1.2,3.5,1.2); 131 - }; 132 - acc = vector.new(0,-starsoul.world.planet.gravity,0); 133 - node = node; 134 - }) 135 - end); 136 - f.queue(0.9, function(s, timepast, timeleft) 137 - f.visual(nil, { 138 - amount = 200; 139 - time = timeleft; 140 - pos = pos; 141 - size = {min = 0.1, max = 0.3}; 142 - vel = { 143 - min = vector.new(-2,0.5,-2); 144 - max = vector.new(2,4,2); 145 - }; 146 - acc = vector.new(0,-starsoul.world.planet.gravity,0); 147 - node = node; 148 - }) 149 - end); 150 - f.queue(0.3, function(s, timepast, timeleft) 151 - local function v(fn) 152 - local def = { 153 - amount = timeleft * 100; 154 - pos = pos; 155 - time = timeleft; 156 - radius = 0.5; 157 - jitter = {min = 0.0, max = 0.2}; 158 - size = {min = 0.2, max = 0.5}; 159 - exptime = {min = 0.5, max = 1}; 160 - attract = { 161 - kind = 'point'; 162 - strength = {min=0.3, max = 1}; 163 - origin = vector.new(0,eh-0.1,0); 164 - radius = 0.5; 165 - origin_attached = user.entity; 166 - }; 167 - } 168 - fn(def) 169 - f.visual(nil, def) 170 - end 171 - v(function(t) t.texpool = nanopool t.glow = 14 end) 172 - v(function(t) 173 - t.node = node 174 - t.amount = timeleft * 20 175 - t.size = {min = 0.1, max = 0.3}; 176 - end) 177 - end) 178 - return f 179 - 180 -end
Deleted mods/starsoul/init.lua version [aca0a214d9].
1 --- [ʞ] starsoul/init.lua 2 --- ~ lexi hale <lexi@hale.su> 3 --- ? basic setup, game rules, terrain 4 --- © EUPL v1.2 5 - 6 -local T = minetest.get_translator 'starsoul' 7 - 8 --- TODO enforce latest engine version 9 - 10 -local mod = { 11 - -- subordinate mods register here 12 - lib = vtlib; 13 - -- vtlib should be accessed as starsoul.mod.lib by starsoul modules for the sake of proper encapsulation. vtlib should simply be a provider, not a hardcoded dependency 14 -} 15 -local lib = mod.lib 16 - 17 - 18 -starsoul = { 19 - ident = minetest.get_current_modname(); 20 - mod = mod; 21 - translator = T; 22 - 23 - constant = { 24 - light = { --minetest units 25 - dim = 3; 26 - lamp = 7; 27 - bright = 10; 28 - brightest = 14; -- only sun and growlights 29 - }; 30 - heat = { -- celsius 31 - freezing = 0; 32 - safe = 4; 33 - overheat = 32; 34 - boiling = 100; 35 - }; 36 - rad = { 37 - }; 38 - }; 39 - 40 - activeUsers = { 41 - -- map of username -> user object 42 - }; 43 - activeUI = { 44 - -- map of username -> UI context 45 - }; 46 - liveUI = { 47 - -- cached subset of activeUI containing those UIs needing live updates 48 - }; 49 - 50 - interface = lib.registry.mk 'starsoul:interface'; 51 - item = { 52 - }; 53 - 54 - region = { 55 - radiator = { 56 - store = AreaStore(); 57 - emitters = {} 58 - }; 59 - }; 60 - 61 - -- standardized effects 62 - fx = {}; 63 - 64 - type = {}; 65 - world = { 66 - defaultScenario = 'starsoul_scenario:imperialExpat'; 67 - seedbank = lib.math.seedbank(minetest.get_mapgen_setting 'seed'); 68 - mineral = lib.registry.mk 'starsoul:mineral'; 69 - material = { -- raw materials 70 - element = lib.registry.mk 'starsoul:element'; 71 - -- elements are automatically sorted into the following categories 72 - -- if they match. however, it's possible to have a metal/gas/liquid 73 - -- that *isn't* a pure element, so these need separate registries 74 - -- for alloys and mixtures like steel and water 75 - metal = lib.registry.mk 'starsoul:metal'; 76 - gas = lib.registry.mk 'starsoul:gas'; 77 - liquid = lib.registry.mk 'starsoul:liquid'; 78 - }; 79 - ecology = { 80 - plants = lib.registry.mk 'starsoul:plants'; 81 - trees = lib.registry.mk 'starsoul:trees'; 82 - biomes = lib.registry.mk 'starsoul:biome'; 83 - }; 84 - climate = {}; 85 - scenario = {}; 86 - planet = { 87 - gravity = 7.44; 88 - orbit = 189; -- 1 year is 189 days 89 - revolve = 20; -- 1 day is 20 irl minutes 90 - }; 91 - fact = lib.registry.mk 'starsoul:fact'; 92 - time = { 93 - calendar = { 94 - empire = { 95 - name = 'Imperial Regnal Calendar'; 96 - year = function(t, long) 97 - local reigns = { 98 - -- if anyone actually makes it to his Honor & Glory Unfailing Persigan I i will be 99 - -- exceptionally flattered 100 - {4, 'Emperor', 'Atavarka', 'the Bold'}; -- died at war 101 - {9, 'Emperor', 'Vatikserka', 'the Unconquered'}; -- died at war 102 - {22, 'Emperor', 'Rusifend', 'the Wise'}; -- poisoned at diplomacy 103 - {61, 'Empress', 'Tafseshendi', 'the Great'}; -- died of an 'insurrection of the innards' after a celebrated reign 104 - {291, 'Emperor', 'Treptebaska', 'the Unwise'}; -- murdered by his wife in short order 105 - {292, 'Empress', 'Vilintalti', 'the Impious'}; -- removed by the praetorian elite 106 - {298, 'Emperor', 'Radavan', 'the Reckless'}; -- died at war 107 - {316, 'Emperor', 'Suldibrand', 'the Forsaken of Men'}; -- fucked around. found out. 108 - {320, 'Emperor', 'Persigan', 'the Deathless'}; 109 - } 110 - local year, r = math.floor(t / 414) 111 - for i=1, #reigns do if reigns[i+1][1] < year then r = reigns[i+1] end end 112 - local reignBegin, title, name, epithet = lib.tbl.unpack(r) 113 - local ry = 1 + (year - reignBegin) 114 - return long and string.format('Year %s of the Reign of HH&GU %s %s %s', 115 - ry, title, name, epithet) or string.format('Y. %s %s', name, ry) 116 - end; 117 - time = function(t, long) 118 - local bellsInDay, candleSpansInBell = 5, 7 119 - local bell = bellsInDay*t 120 - local cspan = (bellsInDay*candleSpansInBell*t) % candleSpansInBell 121 - return string.format(long and 'Bell %s, Candlespan %s' or '%sb %sc', math.floor(bell), math.floor(cspan)) 122 - end; 123 - }; 124 - commune = { 125 - name = 'People\'s Calendar'; 126 - date = function(t, long) 127 - local year = math.floor(t / 256) + 314 128 - return string.format(long and 'Foundation %s' or 'F:%s', year) 129 - end; 130 - time = function(t, long) 131 - local hoursInDay, minutesInHour = 16, 16 132 - local hour = hoursInDay*t 133 - local min = (hoursInDay*minutesInHour*t) % minutesInHour 134 - 135 - local dawn = 0.24*hoursInDay 136 - local noon = 0.5*hoursInDay 137 - local dusk = 0.76*hoursInDay 138 - local midnight = 1.0*hoursInDay 139 - 140 - local tl, str 141 - if hour < dawn then 142 - tl = dawn - hour 143 - str = long and 'dawn' or 'D' 144 - elseif hour < noon then 145 - tl = noon - hour 146 - str = long and 'noon' or 'N' 147 - elseif hour < dusk then 148 - tl = dusk - hour 149 - str = long and 'dusk' or 'd' 150 - elseif hour < midnight then 151 - tl = midnight - hour 152 - str = long and 'midnight' or 'M' 153 - end 154 - return long 155 - and string.format('%s hours, %s minutes to %s', 156 - math.floor(tl), math.floor(minutesInHour - min), str) 157 - or string.format('%s.%sH.%sM', str, math.floor(tl), 158 - math.floor(minutesInHour - min)) 159 - end; 160 - }; 161 - }; 162 - }; 163 - }; 164 - 165 - jobs = {}; 166 -} 167 - 168 -starsoul.cfgDir = minetest.get_worldpath() .. '/' .. starsoul.ident 169 - 170 -local logger = function(module) 171 - local function argjoin(arg, nxt, ...) 172 - if arg and not nxt then return tostring(arg) end 173 - if not arg then return "(nil)" end 174 - return tostring(arg) .. ' ' .. argjoin(nxt, ...) 175 - end 176 - local lg = {} 177 - local setup = function(fn, lvl) 178 - lvl = lvl or fn 179 - local function emit(...) 180 - local call = (fn == 'fatal') and error 181 - or function(str) minetest.log(lvl, str) end 182 - if module 183 - then call(string.format('[%s :: %s] %s',starsoul.ident,module,argjoin(...))) 184 - else call(string.format('[%s] %s',starsoul.ident,argjoin(...))) 185 - end 186 - end 187 - lg[fn ] = function(...) emit(...) end 188 - lg[fn .. 'f'] = function(...) emit(string.format(...)) end -- convenience fn 189 - end 190 - setup('info') 191 - setup('warn','warning') 192 - setup('err','error') 193 - setup('act','action') 194 - setup('fatal') 195 - return lg 196 -end 197 - 198 -starsoul.logger = logger 199 - 200 -local log = logger() 201 - 202 -function starsoul.evaluate(name, ...) 203 - local path = minetest.get_modpath(minetest.get_current_modname()) 204 - local filename = string.format('%s/%s', path, name) 205 - log.info('loading', filename) 206 - local chunk, err = loadfile(filename, filename) 207 - if not chunk then error(err) end 208 - return chunk(...) 209 -end 210 - 211 -function starsoul.include(name, ...) -- semantic variant used for loading modules 212 - return starsoul.evaluate(name..'.lua', ...) 213 -end 214 - 215 -minetest.register_lbm { 216 - label = 'build radiator index'; 217 - name = 'starsoul:loadradiatorboxes'; 218 - nodenames = {'group:radiator'}; 219 - run_at_every_load = true; 220 - action = function(pos, node, dt) 221 - local R = starsoul.region 222 - local phash = minetest.hash_node_position(pos) 223 - if R.radiator.sources[phash] then return end -- already loaded 224 - 225 - local def = minetest.registered_nodes[node.name] 226 - local cl = def._starsoul.radiator 227 - local min,max = cl.maxEffectArea(pos) 228 - local id = R.radiator.store:insert_area(min,max, minetest.pos_to_string(pos)) 229 - R.radiator.sources[phash] = id 230 - end; 231 - -- NOTE: temp emitter nodes are responsible for decaching themselves in their on_destruct cb 232 -} 233 - 234 -function starsoul.startJob(id, interval, job) 235 - local lastRun 236 - local function start() 237 - starsoul.jobs[id] = minetest.after(interval, function() 238 - local t = minetest.get_gametime() 239 - local d = lastRun and t - lastRun or nil 240 - lastRun = t 241 - local continue = job(d, interval) 242 - if continue == true or continue == nil then 243 - start() 244 - elseif continue ~= false then 245 - interval = continue 246 - start() 247 - end 248 - end) 249 - end 250 - start() 251 -end 252 - 253 -starsoul.include 'stats' 254 -starsoul.include 'world' 255 -starsoul.include 'fab' 256 -starsoul.include 'tiers' 257 -starsoul.include 'species' 258 - 259 -starsoul.include 'store' 260 - 261 -starsoul.include 'ui' 262 -starsoul.include 'item' 263 -starsoul.include 'container' 264 -starsoul.include 'user' 265 -starsoul.include 'effect' 266 - 267 -starsoul.include 'fx/nano' 268 - 269 -starsoul.include 'element' 270 - 271 -starsoul.include 'terrain' 272 -starsoul.include 'interfaces' 273 -starsoul.include 'suit' 274 - 275 -minetest.settings:set('movement_gravity', starsoul.world.planet.gravity) -- ??? seriously??? 276 - 277 ---------------- 278 --- callbacks -- 279 ---------------- 280 --- here we connect our types up to the minetest API 281 - 282 -local function userCB(fn) 283 - return function(luser, ...) 284 - local name = luser:get_player_name() 285 - local user = starsoul.activeUsers[name] 286 - return fn(user, ...) 287 - end 288 -end 289 - 290 -minetest.register_on_joinplayer(function(luser, lastLogin) 291 - -- TODO check that necessary CSMs are installed 292 - local user = starsoul.type.user(luser) 293 - 294 - if lastLogin == nil then 295 - user:onSignup() 296 - end 297 - user:onJoin() 298 - 299 - starsoul.activeUsers[user.name] = user 300 -end) 301 - 302 -minetest.register_on_leaveplayer(function(luser) 303 - starsoul.activeUsers[luser:get_player_name()]:onPart() 304 -end) 305 - 306 -minetest.register_on_player_receive_fields(function(luser, formid, fields) 307 - local name = luser:get_player_name() 308 - local user = starsoul.activeUsers[name] 309 - if not user then return false end 310 - if formid == '' then -- main menu 311 - return starsoul.ui.userMenuDispatch(user,fields) 312 - end 313 - local ui = starsoul.interface.db[formid] 314 - local state = starsoul.activeUI[name] or {} 315 - if formid == '__builtin:help_cmds' 316 - or formid == '__builtin:help_privs' 317 - then return false end 318 - assert(state.form == formid) -- sanity check 319 - user:onRespond(ui, state, fields) 320 - if fields.quit then 321 - starsoul.activeUI[name] = nil 322 - end 323 - return true 324 -end) 325 - 326 -minetest.register_on_respawnplayer(userCB(function(user) 327 - return user:onRespawn() 328 -end)) 329 - 330 -minetest.register_on_dieplayer(userCB(function(user, reason) 331 - return user:onDie(reason) 332 -end)) 333 - 334 -minetest.register_on_punchnode(function(pos,node,puncher,point) 335 - local user = starsoul.activeUsers[puncher:get_player_name()] 336 - local oldTgt = user.action.tgt 337 - user.action.tgt = point 338 - if bit.band(user.action.bits, 0x80)==0 then 339 - user.action.bits = bit.bor(user.action.bits, 0x80) 340 - --user:trigger('primary', {state = 'init'}) 341 - else 342 - user:trigger('retarget', {oldTgt = oldTgt}) 343 - end 344 -end) 345 - 346 -local function pointChanged(a,b) 347 - return a.type ~= b.type 348 - or a.type == 'node' and vector.new(a.under) ~= vector.new(b.under) 349 - or a.type == 'object' and a.ref ~= b.ref 350 -end 351 -local function triggerPower(_, luser, point) 352 - local user = starsoul.activeUsers[luser:get_player_name()] 353 - local oldTgt = user.action.tgt 354 - user.action.tgt = point 355 - if bit.band(user.action.bits, 0x100)==0 then 356 - user.action.bits = bit.bor(user.action.bits, 0x100) 357 - --return user:trigger('secondary', {state = 'prog', delta = 0}) 358 - elseif pointChanged(oldTgt, point) then 359 - user:trigger('retarget', {oldTgt = oldTgt}) 360 - end 361 -end 362 --- sigh 363 -core.noneitemdef_default.on_place = function(...) 364 - if not triggerPower(...) then 365 - minetest.item_place(...) 366 - end 367 -end 368 -core.noneitemdef_default.on_use = function(...) triggerPower(...) end 369 -core.noneitemdef_default.on_secondary_use = function(...) triggerPower(...) end 370 - 371 -minetest.register_on_player_inventory_action(function(luser, act, inv, p) 372 - local name = luser:get_player_name() 373 - local user = starsoul.activeUsers[name] 374 - -- allow UIs to update on UI changes 375 - local state = starsoul.activeUI[name] 376 - if state then 377 - local ui = starsoul.interface.db[state.form] 378 - ui:cb('onMoveItem', user, act, inv, p) 379 - end 380 -end) 381 - 382 -minetest.register_on_player_hpchange(function(luser, delta, cause) 383 - local user = starsoul.activeUsers[luser:get_player_name()] 384 - if cause.type == 'fall' then 385 - delta = user:damageModifier('bluntForceTrauma', (delta * 50)) 386 - -- justification: a short fall can do around 387 - -- five points of damage, which is nearly 50% 388 - -- of the default hp_max. since we crank up 389 - -- hp by a factor of 50~40, damage should be 390 - -- cranked by similarly 391 - end 392 - return delta 393 -end, true) 394 - 395 -function minetest.handle_node_drops(pos, drops, digger) 396 - local function jitter(pos) 397 - local function r(x) return x+math.random(-0.2, 0.2) end 398 - return vector.new( 399 - r(pos.x), 400 - r(pos.y), 401 - r(pos.z) 402 - ) 403 - end 404 - for i, it in ipairs(drops) do 405 - minetest.add_item(jitter(pos), it) 406 - end 407 -end 408 - 409 - 410 --- TODO timer iterates live UI 411 -
Deleted mods/starsoul/interfaces.lua version [b552296eeb].
1 -local lib = starsoul.mod.lib 2 - 3 -function starsoul.ui.setupForUser(user) 4 - local function cmode(mode) 5 - if user.actMode == mode then return {hue = 150, sat = 0, lum = .3} end 6 - end 7 - user.entity:set_inventory_formspec(starsoul.ui.build { 8 - kind = 'vert', mode = 'sw'; 9 - padding = .5, spacing = 0.1; 10 - {kind = 'hztl'; 11 - {kind = 'contact', w=1.5,h=1.5, id = 'mode_nano', 12 - img='starsoul-ui-icon-nano.png', close=true, color = cmode'nano'}; 13 - {kind = 'contact', w=1.5,h=1.5, id = 'mode_weapon', 14 - img='starsoul-ui-icon-weapon.png', close=true, color = cmode'weapon'}; 15 - {kind = 'contact', w=1.5,h=1.5, id = 'mode_psi', 16 - img='starsoul-ui-icon-psi.png', close=true, color = cmode'psi'}; 17 - }; 18 - {kind = 'hztl'; 19 - {kind = 'contact', w=1.5,h=1.5, id = 'open_elements', 20 - img='starsoul-ui-icon-element.png'}; 21 - {kind = 'contact', w=1.5,h=1.5, id = 'open_suit', 22 - img='starsoul-item-suit.png^[hsl:200:-.7:0'}; 23 - {kind = 'contact', w=1.5,h=1.5, id = 'open_psi', 24 - img='starsoul-ui-icon-psi-cfg.png'}; 25 - {kind = 'contact', w=1.5,h=1.5, id = 'open_body', 26 - img='starsoul-ui-icon-self.png'}; 27 - }; 28 - {kind = 'list'; 29 - target = 'current_player', inv = 'main'; 30 - w = 6, h = 1, spacing = 0.1; 31 - }; 32 - }) 33 -end 34 - 35 -function starsoul.ui.userMenuDispatch(user, fields) 36 - local function setSuitMode(mode) 37 - if user.actMode == mode then 38 - user:actModeSet 'off' 39 - else 40 - user:actModeSet(mode) 41 - end 42 - end 43 - 44 - local modes = { nano = true, psi = false, weapon = true } 45 - for e,s in pairs(modes) do 46 - if fields['mode_' .. e] then 47 - if s and (user:naked() or user:getSuit():powerState() == 'off') then 48 - user:suitSound 'starsoul-error' 49 - else 50 - setSuitMode(e) 51 - end 52 - return true 53 - end 54 - end 55 - 56 - if fields.open_elements then 57 - user:openUI('starsoul:user-menu', 'compiler') 58 - return true 59 - elseif fields.open_psi then 60 - user:openUI('starsoul:user-menu', 'psi') 61 - return true 62 - elseif fields.open_suit then 63 - if not user:naked() then 64 - user:openUI('starsoul:user-menu', 'suit') 65 - end 66 - return true 67 - elseif fields.open_body then 68 - user:openUI('starsoul:user-menu', 'body') 69 - end 70 - return false 71 -end 72 - 73 -local function listWrap(n, max) 74 - local h = math.ceil(n / max) 75 - local w = math.min(max, n) 76 - return w, h 77 -end 78 - 79 -local function wrapMenu(w, h, rh, max, l) 80 - local root = {kind = 'vert', w=w, h=h} 81 - local bar 82 - local function flush() 83 - if bar and bar[1] then table.insert(root, bar) end 84 - bar = {kind = 'hztl'} 85 - end 86 - flush() 87 - 88 - for _, i in ipairs(l) do 89 - local bw = w/max 90 - if i.cfg then w = w - rh end 91 - 92 - table.insert(bar, { 93 - kind = 'button', close = i.close; 94 - color = i.color; 95 - fg = i.fg; 96 - label = i.label; 97 - icon = i.img; 98 - id = i.id; 99 - w = bw, h = rh; 100 - }) 101 - if i.cfg then 102 - table.insert(bar, { 103 - kind = 'button'; 104 - color = i.color; 105 - fg = i.fg; 106 - label = "CFG"; 107 - icon = i.img; 108 - id = i.id .. '_cfg'; 109 - w = rh, h = rh; 110 - }) 111 - end 112 - 113 - if bar[max] then flush() end 114 - end 115 - flush() 116 - 117 - return root 118 -end 119 - 120 -local function abilityMenu(a) 121 - -- select primary/secondary abilities or activate ritual abilities 122 - local p = {kind = 'vert'} 123 - for _, o in ipairs(a.order) do 124 - local m = a.menu[o] 125 - table.insert(p, {kind='label', text=m.label, w=a.w, h = .5}) 126 - table.insert(p, wrapMenu(a.w, a.h, 1.2, 2, m.opts)) 127 - end 128 - return p 129 -end 130 - 131 -local function pptrMatch(a,b) 132 - if a == nil or b == nil then return false end 133 - return a.chipID == b.chipID and a.pgmIndex == b.pgmIndex 134 -end 135 - 136 -starsoul.interface.install(starsoul.type.ui { 137 - id = 'starsoul:user-menu'; 138 - pages = { 139 - compiler = { 140 - setupState = function(state, user) 141 - -- nanotech/suit software menu 142 - local chips = user.entity:get_inventory():get_list 'starsoul_suit_chips' -- FIXME need better subinv api 143 - local sw = starsoul.mod.electronics.chip.usableSoftware(chips) 144 - state.suitSW = {} 145 - local dedup = {} 146 - for i, r in ipairs(sw) do if 147 - r.sw.kind == 'suitPower' 148 - then 149 - if not dedup[r.sw] then 150 - dedup[r.sw] = true 151 - table.insert(state.suitSW, r) 152 - end 153 - end end 154 - end; 155 - handle = function(state, user, act) 156 - if user:getSuit():powerState() == 'off' then return false end 157 - local pgm, cfg 158 - for k in next, act do 159 - local id, mode = k:match('^suit_pgm_([0-9]+)_(.*)$') 160 - if id then 161 - id = tonumber(id) 162 - if state.suitSW[id] then 163 - pgm = state.suitSW[id] 164 - cfg = mode == '_cfg' 165 - break 166 - end 167 - end 168 - end 169 - if not pgm then return false end -- HAX 170 - 171 - -- kind=active programs must be assigned to a command slot 172 - -- kind=direct programs must open their UI 173 - -- kind=passive programs must toggle on and off 174 - if pgm.sw.powerKind == 'active' then 175 - if cfg then 176 - user:openUI(pgm.sw.ui, 'index', { 177 - context = 'suit'; 178 - program = pgm; 179 - }) 180 - return false 181 - end 182 - local ptr = {chipID = starsoul.mod.electronics.chip.read(pgm.chip).uuid, pgmIndex = pgm.fd.inode} 183 - local pnan = user.power.nano 184 - if pnan.primary == nil then 185 - pnan.primary = ptr 186 - elseif pptrMatch(ptr, pnan.primary) then 187 - pnan.primary = nil 188 - elseif pptrMatch(ptr, pnan.secondary) then 189 - pnan.secondary = nil 190 - else 191 - pnan.secondary = ptr 192 - end 193 - user:suitSound 'starsoul-configure' 194 - elseif pgm.sw.powerKind == 'direct' then 195 - local ctx = { 196 - context = 'suit'; 197 - program = pgm; 198 - } 199 - if pgm.sw.ui then 200 - user:openUI(pgm.sw.ui, 'index', ctx) 201 - return false 202 - else 203 - pgm.sw.run(user, ctx) 204 - end 205 - elseif pgm.sw.powerKind == 'passive' then 206 - if cfg then 207 - user:openUI(pgm.sw.ui, 'index', { 208 - context = 'suit'; 209 - program = pgm; 210 - }) 211 - return false 212 - end 213 - 214 - local addDisableRec = true 215 - for i, e in ipairs(pgm.file.body.conf) do 216 - if e.key == 'disable' and e.value == 'yes' then 217 - addDisableRec = false 218 - table.remove(pgm.file.body.conf, i) 219 - break 220 - elseif e.key == 'disable' and e.value == 'no' then 221 - e.value = 'yes' 222 - addDisableRec = false 223 - break 224 - end 225 - end 226 - if addDisableRec then 227 - table.insert(pgm.file.body.conf, {key='disable',value='yes'}) 228 - end 229 - -- update the chip *wince* 230 - pgm.fd:write(pgm.file) 231 - user.entity:get_inventory():set_stack('starsoul_suit_chips', 232 - pgm.chipSlot, pgm.chip) 233 - user:reconfigureSuit() 234 - user:suitSound('starsoul-configure') 235 - 236 - end 237 - return true, true 238 - end; 239 - render = function(state, user) 240 - local suit = user:getSuit() 241 - local swm 242 - if user:getSuit():powerState() ~= 'off' then 243 - swm = { 244 - w = 8, h = 3; 245 - order = {'active','ritual','pasv'}; 246 - menu = { 247 - active = { 248 - label = 'Nanoware'; 249 - opts = {}; 250 - }; 251 - ritual = { 252 - label = 'Programs'; 253 - opts = {}; 254 - }; 255 - pasv = { 256 - label = 'Passive'; 257 - opts = {}; 258 - }; 259 - }; 260 - } 261 - for id, r in pairs(state.suitSW) do 262 - local color = {hue=300,sat=0,lum=0} 263 - local fg = nil 264 - local close = nil 265 - local tbl, cfg if r.sw.powerKind == 'active' then 266 - tbl = swm.menu.active.opts 267 - if r.sw.ui then cfg = true end 268 - local pnan = user.power.nano 269 - if pnan then 270 - local ptr = {chipID = starsoul.mod.electronics.chip.read(r.chip).uuid, pgmIndex = r.fd.inode} 271 - if pptrMatch(ptr, pnan.primary) then 272 - color.lum = 1 273 - elseif pptrMatch(ptr, pnan.secondary) then 274 - color.lum = 0.8 275 - end 276 - end 277 - elseif r.sw.powerKind == 'direct' then 278 - tbl = swm.menu.ritual.opts 279 - if not r.sw.ui then 280 - close = true 281 - end 282 - elseif r.sw.powerKind == 'passive' then 283 - tbl = swm.menu.pasv.opts 284 - if r.sw.ui then cfg = true end 285 - for i, e in ipairs(r.file.body.conf) do 286 - if e.key == 'disable' and e.value == 'yes' then 287 - color.lum = -.2 288 - fg = lib.color {hue=color.hue,sat=0.7,lum=0.7} 289 - break 290 - end 291 - end 292 - end 293 - if tbl then table.insert(tbl, { 294 - color = color, fg = fg; 295 - label = r.sw.label or r.sw.name; 296 - id = string.format('suit_pgm_%s_', id); 297 - cfg = cfg, close = close; 298 - }) end 299 - end 300 - end 301 - local menu = { kind = 'vert', mode = 'sw', padding = 0.5 } 302 - if swm then table.insert(menu, abilityMenu(swm)) end 303 - 304 - local inv = user.entity:get_inventory() 305 - local cans = inv:get_list 'starsoul_suit_canisters' 306 - if cans and next(cans) then for i, st in ipairs(cans) do 307 - local id = string.format('starsoul_canister_%u_elem', i) 308 - local esz = inv:get_size(id) 309 - if esz > 0 then 310 - local eltW, eltH = listWrap(esz, 5) 311 - table.insert(menu, {kind = 'hztl', 312 - {kind = 'img', desc='Elements', img = 'starsoul-ui-icon-element.png', w=1,h=1}; 313 - {kind = 'list', target = 'current_player', inv = id, 314 - listContent = 'element', w = eltW, h = eltH, spacing = 0.1}; 315 - }) 316 - end 317 - end end 318 - 319 - if #menu == 0 then 320 - table.insert(menu, { 321 - kind = 'img'; 322 - img = 'starsoul-ui-alert.png'; 323 - w=2, h=2; 324 - }) 325 - menu.padding = 1; 326 - end 327 - return starsoul.ui.build(menu) 328 - end; 329 - }; 330 - compilerListRecipes = { 331 - }; 332 - psi = { 333 - render = function(state, user) 334 - return starsoul.ui.build { 335 - kind = 'vert', mode = 'sw'; 336 - padding = 0.5; 337 - } 338 - end; 339 - }; 340 - body = { 341 - render = function(state, user) 342 - local barh = .75 343 - local tb = { 344 - kind = 'vert', mode = 'sw'; 345 - padding = 0.5, 346 - {kind = 'hztl', padding = 0.25; 347 - {kind = 'label', text = 'Name', w = 2, h = barh}; 348 - {kind = 'label', text = user.persona.name, w = 4, h = barh}}; 349 - } 350 - local statBars = {'hunger', 'thirst', 'fatigue', 'morale'} 351 - for idx, id in ipairs(statBars) do 352 - local s = starsoul.world.stats[id] 353 - local amt, sv = user:effectiveStat(id) 354 - local min, max = starsoul.world.species.statRange(user.persona.species, user.persona.speciesVariant, id) 355 - local st = string.format('%s / %s', s.desc(amt, true), s.desc(max)) 356 - table.insert(tb, {kind = 'hztl', padding = 0.25; 357 - {kind = 'label', w=2, h=barh, text = s.name}; 358 - {kind = 'hbar', w=4, h=barh, fac = sv, text = st, color=s.color}; 359 - }) 360 - end 361 - local abilities = { 362 - {id = 'abl_sprint', label = 'Sprint', img = 'starsoul-ui-icon-ability-sprint.png'}; 363 - } 364 - table.insert(tb, wrapMenu(6.25,4, 1,2, abilities)) 365 - return starsoul.ui.build(tb) 366 - end; 367 - }; 368 - suit = { 369 - render = function(state, user) 370 - local suit = user:getSuit() 371 - local suitDef = suit:def() 372 - local chipW, chipH = listWrap(suitDef.slots.chips, 5) 373 - local batW, batH = listWrap(suitDef.slots.batteries, 5) 374 - local canW, canH = listWrap(suitDef.slots.canisters, 5) 375 - local suitMode = suit:powerState() 376 - local function modeColor(mode) 377 - if mode == suitMode then return {hue = 180, sat = 0, lum = .5} end 378 - end 379 - return starsoul.ui.build { 380 - kind = 'vert', mode = 'sw'; 381 - padding = 0.5, spacing = 0.1; 382 - {kind = 'hztl', 383 - {kind = 'img', desc='Batteries', img = 'starsoul-item-battery.png', w=1,h=1}; 384 - {kind = 'list', target = 'current_player', inv = 'starsoul_suit_bat', 385 - listContent = 'power', w = batW, h = batH, spacing = 0.1}; 386 - }; 387 - {kind = 'hztl', 388 - {kind = 'img', desc='Chips', img = 'starsoul-item-chip.png', w=1,h=1}; 389 - {kind = 'list', target = 'current_player', inv = 'starsoul_suit_chips', 390 - listContent = 'chip', w = chipW, h = chipH, spacing = 0.1}; 391 - }; 392 - {kind = 'hztl', 393 - {kind = 'img', desc='Canisters', img = 'starsoul-item-element-canister.png', w=1,h=1}; 394 - {kind = 'list', target = 'current_player', inv = 'starsoul_suit_canisters', 395 - listContent = nil, w = canW, h = canH, spacing = 0.1}; 396 - }; 397 - {kind = 'hztl'; 398 - {kind = 'img', w=1,h=1, item = suit.item:get_name(), 399 - desc = suit.item:get_definition().short_description}; 400 - {kind = 'button', w=1.5,h=1, id = 'powerMode_off', label = 'Off'; 401 - color=modeColor'off'}; 402 - {kind = 'button', w=2.5,h=1, id = 'powerMode_save', label = 'Power Save'; 403 - color=modeColor'powerSave'}; 404 - {kind = 'button', w=1.5,h=1, id = 'powerMode_on', label = 'On'; 405 - color=modeColor'on'}; 406 - }; 407 - {kind = 'list', target = 'current_player', inv = 'main', w = 6, h = 1, spacing = 0.1}; 408 - } 409 - end; 410 - handle = function(state, user, q) 411 - local suitMode 412 - if q.powerMode_off then suitMode = 'off' 413 - elseif q.powerMode_save then suitMode = 'powerSave' 414 - elseif q.powerMode_on then suitMode = 'on' end 415 - if suitMode then 416 - user:suitPowerStateSet(suitMode) 417 - return true 418 - end 419 - end; 420 - }; 421 - }; 422 -}) 423 - 424 -starsoul.interface.install(starsoul.type.ui { 425 - id = 'starsoul:compile-matter-component'; 426 - pages = { 427 - index = { 428 - setupState = function(state, user, ctx) 429 - if ctx.context == 'suit' then 430 - end 431 - state.pgm = ctx.program 432 - end; 433 - render = function(state, user) 434 - return starsoul.ui.build { 435 - kind = 'vert', padding = 0.5; w = 5, h = 5, mode = 'sw'; 436 - {kind = 'label', w = 4, h = 1, text = 'hello'}; 437 - } 438 - end; 439 - }; 440 - }; 441 -})
Deleted mods/starsoul/item.lua version [597f09c699].
1 -local lib = starsoul.mod.lib 2 -local I = starsoul.item 3 - 4 -function I.mk(item, context) 5 - local st = ItemStack(item) 6 - local md = st:get_definition()._starsoul 7 - local ctx = context or {} 8 - if md and md.event then 9 - md.event.create(st, ctx) 10 - end 11 - if context.how == 'print' then 12 - if context.schematic and context.schematic.setup then 13 - context.schematic.setup(st, ctx) 14 - end 15 - end 16 - return st 17 -end
Deleted mods/starsoul/mod.conf version [7e3c22e1be].
1 -name = starsoul 2 -author = velartrill 3 -description = world logic and UI 4 -depends = vtlib
Deleted mods/starsoul/species.lua version [9c138bbb33].
1 -local lib = starsoul.mod.lib 2 - 3 -local paramTypes do local T,G = lib.marshal.t, lib.marshal.g 4 - paramTypes = { 5 - tone = G.struct { 6 - hue = T.angle; 7 - sat = T.clamp; 8 - lum = T.clamp; 9 - }; 10 - str = T.str; 11 - num = T.decimal; 12 - } 13 -end 14 - 15 --- constants 16 -local animationFrameRate = 60 17 - 18 -local species = { 19 - human = { 20 - name = 'Human'; 21 - desc = 'The weeds of the galactic flowerbed. Humans are one of the Lesser Races, excluded from the ranks of the Greatest Races by souls that lack, in normal circumstances, external psionic channels. Their mastery of the universe cut unexpectedly short, forever locked out of FTL travel, short-lived without augments, and alternately pitied or scorned by the lowest of the low, humans flourish nonetheless due to a capacity for adaptation unmatched among the Thinking Few, terrifyingly rapid reproductive cycles -- and a keen facility for bribery. While the lack of human psions remains a sensitive topic, humans (unlike the bitter and emotional Kruthandi) are practical enough to hire the talent they cannot possess, and have even built a small number of symbiotic civilizations with the more indulging of the Powers. In a galaxy where nearly all sophont life is specialized to a fault, humans have found the unique niche of occupying no particular niche.'; 22 - scale = 1.0; 23 - params = { 24 - {'eyeColor', 'Eye Color', 'tone', {hue=327, sat=0, lum=0}}; 25 - {'hairColor', 'Hair Color', 'tone', {hue=100, sat=0, lum=0}}; 26 - {'skinTone', 'Skin Tone', 'tone', {hue= 0, sat=0, lum=0}}; 27 - }; 28 - tempRange = { 29 - comfort = {18.3, 23.8}; -- needed for full stamina regen 30 - survivable = {5, 33}; -- anything below/above will cause progressively more damage 31 - }; 32 - variants = { 33 - female = { 34 - name = 'Human Female'; 35 - mesh = 'starsoul-body-female.x'; 36 - eyeHeight = 1.4; 37 - texture = function(t, adorn) 38 - local skin = lib.image 'starsoul-body-skin.png' : shift(t.skinTone) 39 - local eye = lib.image 'starsoul-body-eye.png' : shift(t.eyeColor) 40 - local hair = lib.image 'starsoul-body-hair.png' : shift(t.hairColor) 41 - 42 - local invis = lib.image '[fill:1x1:0,0:#00000000' 43 - local plate = adorn.suit and adorn.suit.plate or invis 44 - local lining = adorn.suit and adorn.suit.lining or invis 45 - 46 - return {lining, plate, skin, skin, eye, hair} 47 - end; 48 - stats = { 49 - psiRegen = 1.3; 50 - psiPower = 1.2; 51 - psi = 1.2; 52 - hunger = .8; -- women have smaller stomachs 53 - thirst = .8; 54 - staminaRegen = 1.0; 55 - morale = 0.8; -- you are not She-Bear Grylls 56 - }; 57 - traits = { 58 - health = 400; 59 - lungCapacity = .6; 60 - irradiation = 0.8; -- you are smaller, so it takes less rads to kill ya 61 - sturdiness = 0; -- women are more fragile and thus susceptible to blunt force trauma 62 - metabolism = 1800; --Cal 63 - painTolerance = 0.4; 64 - }; 65 - }; 66 - male = { 67 - name = 'Human Male'; 68 - eyeHeight = 1.6; 69 - stats = { 70 - psiRegen = 1.0; 71 - psiPower = 1.0; 72 - psi = 1.0; 73 - hunger = 1.0; 74 - staminaRegen = .7; -- men are strong but have inferior endurance 75 - }; 76 - traits = { 77 - health = 500; 78 - painTolerance = 1.0; 79 - lungCapacity = 1.0; 80 - sturdiness = 0.3; 81 - metabolism = 2200; --Cal 82 - }; 83 - }; 84 - }; 85 - traits = {}; 86 - }; 87 -} 88 - 89 -starsoul.world.species = { 90 - index = species; 91 - paramTypes = paramTypes; 92 -} 93 - 94 -function starsoul.world.species.mkDefaultParamsTable(pSpecies, pVariant) 95 - local sp = species[pSpecies] 96 - local var = sp.variants[pVariant] 97 - local vpd = var.defaults or {} 98 - local tbl = {} 99 - for _, p in pairs(sp.params) do 100 - local name, desc, ty, dflt = lib.tbl.unpack(p) 101 - tbl[name] = vpd[name] or dflt 102 - end 103 - return tbl 104 -end 105 - 106 - 107 -function starsoul.world.species.mkPersonaFor(pSpecies, pVariant) 108 - return { 109 - species = pSpecies; 110 - speciesVariant = pVariant; 111 - bodyParams = starsoul.world.species.paramsFromTable(pSpecies, 112 - starsoul.world.species.mkDefaultParamsTable(pSpecies, pVariant) 113 - ); 114 - statDeltas = {}; 115 - } 116 -end 117 - 118 -local function spLookup(pSpecies, pVariant) 119 - local sp = species[pSpecies] 120 - local var = sp.variants[pVariant or next(sp.variants)] 121 - return sp, var 122 -end 123 -starsoul.world.species.lookup = spLookup 124 - 125 -function starsoul.world.species.statRange(pSpecies, pVariant, pStat) 126 - local sp,spv = spLookup(pSpecies, pVariant) 127 - local min, max, base 128 - if pStat == 'health' then 129 - min,max = 0, spv.traits.health 130 - elseif pStat == 'breath' then 131 - min,max = 0, 65535 132 - else 133 - local spfac = spv.stats[pStat] 134 - local basis = starsoul.world.stats[pStat] 135 - min,max = basis.min, basis.max 136 - 137 - if spfac then 138 - min = min * spfac 139 - max = max * spfac 140 - end 141 - 142 - base = basis.base 143 - if base == true then 144 - base = max 145 - elseif base == false then 146 - base = min 147 - end 148 - 149 - end 150 - return min, max, base 151 -end 152 - 153 --- set the necessary properties and create a persona for a newspawned entity 154 -function starsoul.world.species.birth(pSpecies, pVariant, entity, circumstances) 155 - circumstances = circumstances or {} 156 - local sp,var = spLookup(pSpecies, pVariant) 157 - 158 - local function pct(st, p) 159 - local min, max = starsoul.world.species.statRange(pSpecies, pVariant, st) 160 - local delta = max - min 161 - return min + delta*p 162 - end 163 - local ps = starsoul.world.species.mkPersonaFor(pSpecies,pVariant) 164 - local startingHP = pct('health', 1.0) 165 - if circumstances.injured then startingHP = pct('health', circumstances.injured) end 166 - if circumstances.psiCharged then ps.statDeltas.psi = pct('psi', circumstances.psiCharged) end 167 - ps.statDeltas.warmth = 20 -- don't instantly start dying of frostbite 168 - 169 - entity:set_properties{hp_max = var.traits.health or sp.traits.health} 170 - entity:set_hp(startingHP, 'initial hp') 171 - return ps 172 -end 173 - 174 -function starsoul.world.species.paramsFromTable(pSpecies, tbl) 175 - local lst = {} 176 - local sp = species[pSpecies] 177 - for i, par in pairs(sp.params) do 178 - local name,desc,ty,dflt = lib.tbl.unpack(par) 179 - if tbl[name] then 180 - table.insert(lst, {id=name, value=paramTypes[ty].enc(tbl[name])}) 181 - end 182 - end 183 - return lst 184 -end 185 -function starsoul.world.species.paramsToTable(pSpecies, lst) 186 - local tymap = {} 187 - local sp = species[pSpecies] 188 - for i, par in pairs(sp.params) do 189 - local name,desc,ty,dflt = lib.tbl.unpack(par) 190 - tymap[name] = paramTypes[ty] 191 - end 192 - 193 - local tbl = {} 194 - for _, e in pairs(lst) do 195 - tbl[e.id] = tymap[e.id].dec(e.value) 196 - end 197 - return tbl 198 -end 199 - 200 -for speciesName, sp in pairs(species) do 201 - for varName, var in pairs(sp.variants) do 202 - if var.mesh then 203 - var.animations = starsoul.evaluate(string.format('models/%s.nla', var.mesh)).skel.action 204 - end 205 - end 206 -end 207 - 208 - 209 -function starsoul.world.species.updateTextures(ent, persona, adornment) 210 - local s,v = spLookup(persona.species, persona.speciesVariant) 211 - local paramTable = starsoul.world.species.paramsToTable(persona.species, persona.bodyParams) 212 - local texs = {} 213 - for i, t in ipairs(v.texture(paramTable, adornment)) do 214 - texs[i] = t:render() 215 - end 216 - ent:set_properties { textures = texs } 217 -end 218 - 219 -function starsoul.world.species.setupEntity(ent, persona) 220 - local s,v = spLookup(persona.species, persona.speciesVariant) 221 - local _, maxHealth = starsoul.world.species.statRange(persona.species, persona.speciesVariant, 'health') 222 - ent:set_properties { 223 - visual = 'mesh'; 224 - mesh = v.mesh; 225 - stepheight = .51; 226 - eye_height = v.eyeHeight; 227 - collisionbox = { -- FIXME 228 - -0.3, 0.0, -0.3; 229 - 0.3, 1.5, 0.3; 230 - }; 231 - visual_size = vector.new(10,10,10) * s.scale; 232 - 233 - hp_max = maxHealth; 234 - } 235 - local function P(v) 236 - if v then return {x=v[1],y=v[2]} end 237 - return {x=0,y=0} 238 - end 239 - ent:set_local_animation( 240 - P(v.animations.idle), 241 - P(v.animations.run), 242 - P(v.animations.work), 243 - P(v.animations.runWork), 244 - animationFrameRate) 245 - 246 -end
Deleted mods/starsoul/stats.lua version [39aabe1578].
1 -local lib = starsoul.mod.lib 2 - 3 -local function U(unit, prec, fixed) 4 - if fixed then 5 - return function(amt, excludeUnit) 6 - if excludeUnit then return tostring(amt/prec) end 7 - return string.format("%s %s", amt/prec, unit) 8 - end 9 - else 10 - return function(amt, excludeUnit) 11 - if excludeUnit then return tostring(amt/prec) end 12 - return lib.math.si(unit, amt/prec) 13 - end 14 - end 15 -end 16 - 17 -local function C(h, s, l) 18 - return lib.color {hue = h, sat = s or 1, lum = l or .7} 19 -end 20 -starsoul.world.stats = { 21 - psi = {min = 0, max = 500, base = 0, desc = U('ψ', 10), color = C(320), name = 'Numina'}; 22 - -- numina is measured in daψ 23 - warmth = {min = -1000, max = 1000, base = 0, desc = U('°C', 10, true), color = C(5), name = 'Warmth'}; 24 - -- warmth in measured in °C×10 25 - fatigue = {min = 0, max = 76 * 60, base = 0, desc = U('hr', 60, true), color = C(288,.3,.5), name = 'Fatigue'}; 26 - -- fatigue is measured in minutes one needs to sleep to cure it 27 - stamina = {min = 0, max = 20 * 100, base = true, desc = U('m', 100), color = C(88), name = 'Stamina'}; 28 - -- stamina is measured in how many 10th-nodes (== cm) one can sprint 29 - hunger = {min = 0, max = 20000, base = 0, desc = U('Cal', 1), color = C(43,.5,.4), name = 'Hunger'}; 30 - -- hunger is measured in calories one must consume to cure it 31 - thirst = {min = 0, max = 1600, base = 0, desc = U('l', 100), color = C(217, .25,.4), name = 'Thirst'}; 32 - -- thirst is measured in centiliters of H²O required to cure it 33 - morale = {min = 0, max = 24 * 60 * 10, base = true, desc = U('hr', 60, true), color = C(0,0,.8), name = 'Morale'}; 34 - -- morale is measured in minutes. e.g. at base rate morale degrades by 35 - -- 60 points every hour. morale can last up to 10 days 36 - irradiation = {min = 0, max = 20000, base = 0, desc = U('Gy', 1000), color = C(141,1,.5), name = 'Irradiation'}; 37 - -- irrad is measured is milligreys 38 - -- 1Gy counters natural healing 39 - -- ~3Gy counters basic nanomedicine 40 - -- 5Gy causes death within two weeks without nanomedicine 41 - -- radiation speeds up psi regen 42 - -- morale drain doubles with each 2Gy 43 - illness = {min = 0, max = 1000, base = 0, desc = U('%', 10, true), color = C(71,.4,.25), name = 'Illness'}; 44 - -- as illness increases, maximum stamina and health gain a corresponding limit 45 - -- illness is increased by certain conditions, and decreases on its own as your 46 - -- body heals when those conditions wear off. some drugs can lower accumulated illness 47 - -- but illness-causing conditions require specific cures 48 - -- illness also causes thirst and fatigue to increase proportionately 49 -}
Deleted mods/starsoul/store.lua version [efa19f35ab].
1 --- [ʞ] store.lua 2 --- ~ lexi hale <lexi@hale.su> 3 --- © EUPLv1.2 4 --- ? defines serialization datatypes that don't belong to 5 --- any individual class 6 - 7 -local lib = starsoul.mod.lib 8 -local T,G = lib.marshal.t, lib.marshal.g 9 -starsoul.store = {} -- the serialization equivalent of .type 10 - 11 -------------- 12 --- persona -- 13 -------------- ----------------------------------------------- 14 --- a Persona is a structure that defines the nature of -- 15 --- an (N)PC and how it interacts with the Starsoul-managed -- 16 --- portion of the game world -- things like name, species, -- 17 --- stat values, physical characteristics, and so forth -- 18 - 19 -local statStructFields = {} 20 -for k,v in pairs(starsoul.world.stats) do 21 - statStructFields[k] = v.srzType or ( 22 - (v.base == true or v.base > 0) and T.s16 or T.u16 23 - ) 24 -end 25 - 26 -starsoul.store.compilerJob = G.struct { 27 - schematic = T.str; 28 - progress = T.clamp; 29 -} 30 - 31 -starsoul.store.persona = G.struct { 32 - name = T.str; 33 - species = T.str; 34 - speciesVariant = T.str; 35 - background = T.str; 36 - bodyParams = G.array(8, G.struct {id = T.str, value = T.str}); --variant 37 - 38 - statDeltas = G.struct(statStructFields); 39 - 40 - facts = G.array(32, G.array(8, T.str)); 41 - -- facts stores information the player has discovered and narrative choices 42 - -- she has made. 43 - -- parametric facts are encoded as horn clauses 44 - -- non-parametric facts are encoded as {'fact-mod:fact-id'} 45 -} 46 - 47 -starsoul.store.suitMeta = lib.marshal.metaStore { 48 - batteries = {key = 'starsoul:suit_slots_bat', type = T.inventoryList}; 49 - chips = {key = 'starsoul:suit_slots_chips', type = T.inventoryList}; 50 - elements = {key = 'starsoul:suit_slots_elem', type = T.inventoryList}; 51 - guns = {key = 'starsoul:suit_slots_gun', type = T.inventoryList}; 52 - ammo = {key = 'starsoul:suit_slots_ammo', type = T.inventoryList}; 53 -}
Deleted mods/starsoul/suit.lua version [5d51eaa4b3].
1 -local lib = starsoul.mod.lib 2 - 3 -local suitStore = starsoul.store.suitMeta 4 -starsoul.item.suit = lib.registry.mk 'starsoul:suits'; 5 - 6 --- note that this cannot be persisted as a reference to a particular suit in the world 7 -local function suitContainer(stack, inv) 8 - return starsoul.item.container(stack, inv, { 9 - pfx = 'starsoul_suit' 10 - }) 11 -end 12 -starsoul.type.suit = lib.class { 13 - name = 'starsoul:suit'; 14 - construct = function(stack) 15 - return { 16 - item = stack; 17 - inv = suitStore(stack); 18 - } 19 - end; 20 - __index = { 21 - powerState = function(self) 22 - local s = self.item 23 - if not s then return nil end 24 - local m = s:get_meta():get_int('starsoul:power_mode') 25 - if m == 1 then return 'on' 26 - elseif m == 2 then return 'powerSave' 27 - else return 'off' end 28 - end; 29 - powerStateSet = function(self, state) 30 - local s = self.item 31 - if not s then return nil end 32 - local m 33 - if state == 'on' then m = 1 -- TODO check power level 34 - elseif state == 'powerSave' then m = 2 35 - else m = 0 end 36 - if self:powerLeft() <= 0 then m = 0 end 37 - s:get_meta():set_int('starsoul:power_mode', m) 38 - end; 39 - powerLeft = function(self) 40 - local batteries = self.inv.read 'batteries' 41 - local power = 0 42 - for idx, slot in pairs(batteries) do 43 - power = power + starsoul.mod.electronics.dynamo.totalPower(slot) 44 - end 45 - return power 46 - end; 47 - powerCapacity = function(self) 48 - local batteries = self.inv.read 'batteries' 49 - local power = 0 50 - for idx, slot in pairs(batteries) do 51 - power = power + starsoul.mod.electronics.dynamo.initialPower(slot) 52 - end 53 - return power 54 - end; 55 - maxPowerUse = function(self) 56 - local batteries = self.inv.read 'batteries' 57 - local w = 0 58 - for idx, slot in pairs(batteries) do 59 - w = w + starsoul.mod.electronics.dynamo.dischargeRate(slot) 60 - end 61 - return w 62 - end; 63 - onReconfigure = function(self, inv) 64 - -- apply any changes to item metadata and export any subinventories 65 - -- to the provided invref, as they may have changed 66 - local sc = starsoul.item.container(self.item, inv, {pfx = 'starsoul_suit'}) 67 - sc:push() 68 - self:pullCanisters(inv) 69 - end; 70 - onItemMove = function(self, user, list, act, what) 71 - -- called when the suit inventory is changed 72 - if act == 'put' then 73 - if list == 'starsoul_suit_bat' then 74 - user:suitSound('starsoul-suit-battery-in') 75 - elseif list == 'starsoul_suit_chips' then 76 - user:suitSound('starsoul-suit-chip-in') 77 - elseif list == 'starsoul_suit_canisters' then 78 - user:suitSound('starsoul-insert-snap') 79 - end 80 - elseif act == 'take' then 81 - if list == 'starsoul_suit_bat' then 82 - user:suitSound('starsoul-insert-snap') 83 - elseif list == 'starsoul_suit_chips' then 84 - --user:suitSound('starsoul-suit-chip-out') 85 - elseif list == 'starsoul_suit_canisters' then 86 - user:suitSound('starsoul-insert-snap') 87 - end 88 - end 89 - end; 90 - def = function(self) 91 - return self.item:get_definition()._starsoul.suit 92 - end; 93 - pullCanisters = function(self, inv) 94 - starsoul.item.container.dropPrefix(inv, 'starsoul_canister') 95 - self:forCanisters(inv, function(sc) sc:pull() end) 96 - end; 97 - pushCanisters = function(self, inv, st, i) 98 - self:forCanisters(inv, function(sc) 99 - sc:push() 100 - return true 101 - end) 102 - end; 103 - forCanisters = function(self, inv, fn) 104 - local cans = inv:get_list 'starsoul_suit_canisters' 105 - if cans and next(cans) then for i, st in ipairs(cans) do 106 - if not st:is_empty() then 107 - local pfx = 'starsoul_canister_' .. tostring(i) 108 - local sc = starsoul.item.container(st, inv, {pfx = pfx}) 109 - if fn(sc, st, i, pfx) then 110 - inv:set_stack('starsoul_suit_canisters', i, st) 111 - end 112 - end 113 - end end 114 - end; 115 - establishInventories = function(self, obj) 116 - local inv = obj:get_inventory() 117 - local ct = suitContainer(self.item, inv) 118 - ct:pull() 119 - self:pullCanisters(inv) 120 - 121 - --[[ 122 - local def = self:def() 123 - local sst = suitStore(self.item) 124 - local function readList(listName, prop) 125 - inv:set_size(listName, def.slots[prop]) 126 - if def.slots[prop] > 0 then 127 - local lst = sst.read(prop) 128 - inv:set_list(listName, lst) 129 - end 130 - end 131 - readList('starsoul_suit_chips', 'chips') 132 - readList('starsoul_suit_bat', 'batteries') 133 - readList('starsoul_suit_guns', 'guns') 134 - readList('starsoul_suit_elem', 'elements') 135 - readList('starsoul_suit_ammo', 'ammo') 136 - ]] 137 - end; 138 - }; 139 -} 140 - 141 --- TODO find a better place for this! 142 -starsoul.type.suit.purgeInventories = function(obj) 143 - local inv = obj:get_inventory() 144 - starsoul.item.container.dropPrefix(inv, 'starsoul_suit') 145 - starsoul.item.container.dropPrefix(inv, 'starsoul_canister') 146 - --[[inv:set_size('starsoul_suit_bat', 0) 147 - inv:set_size('starsoul_suit_guns', 0) 148 - inv:set_size('starsoul_suit_chips', 0) 149 - inv:set_size('starsoul_suit_ammo', 0) 150 - inv:set_size('starsoul_suit_elem', 0) 151 - ]] 152 -end 153 - 154 -starsoul.item.suit.foreach('starsoul:suit-gen', {}, function(id, def) 155 - local icon = lib.image(def.img or 'starsoul-item-suit.png') 156 - 157 - local iconColor = def.iconColor 158 - if not iconColor then 159 - iconColor = (def.tex and def.tex.plate and def.tex.plate.tint) 160 - or def.defaultColor 161 - iconColor = iconColor:to_hsl() 162 - iconColor.lum = 0 163 - end 164 - 165 - if iconColor then icon = icon:shift(iconColor) end 166 - 167 - if not def.adorn then 168 - function def.adorn(a, item, persona) 169 - local function imageFor(pfx) 170 - return lib.image(string.format("%s-%s-%s.png", pfx, persona.species, persona.speciesVariant)) 171 - end 172 - if not def.tex then return end 173 - a.suit = {} 174 - for name, t in pairs(def.tex) do 175 - local img = imageFor(t.id) 176 - local color 177 - 178 - local cstr = item:get_meta():get_string('starsoul:tint_suit_' .. name) 179 - if cstr and cstr ~= '' then 180 - color = lib.color.unmarshal(cstr) 181 - elseif t.tint then 182 - color = t.tint or def.defaultColor 183 - end 184 - 185 - if color then 186 - local hsl = color:to_hsl() 187 - local adjusted = { 188 - hue = hsl.hue; 189 - sat = hsl.sat * 2 - 1; 190 - lum = hsl.lum * 2 - 1; 191 - } 192 - img = img:shift(adjusted) 193 - end 194 - 195 - a.suit[name] = img 196 - end 197 - end 198 - end 199 - 200 - minetest.register_tool(id, { 201 - short_description = def.name; 202 - description = starsoul.ui.tooltip { 203 - title = def.name; 204 - desc = def.desc; 205 - color = lib.color(.1, .7, 1); 206 - }; 207 - groups = { 208 - suit = 1; 209 - inv = 1; -- has inventories 210 - batteryPowered = 1; -- has a battery inv 211 - programmable = 1; -- has a chip inv 212 - }; 213 - on_use = function(st, luser, pointed) 214 - local user = starsoul.activeUsers[luser:get_player_name()] 215 - if not user then return end 216 - -- have mercy on users who've lost their suits and wound 217 - -- up naked and dying of exposure 218 - if user:naked() then 219 - local ss = st:take_item(1) 220 - user:setSuit(starsoul.type.suit(ss)) 221 - user:suitSound('starsoul-suit-don') 222 - return st 223 - end 224 - end; 225 - inventory_image = icon:render(); 226 - _starsoul = { 227 - container = { 228 - workbench = { 229 - order = {'batteries','chips','guns','ammo'} 230 - }; 231 - list = { 232 - bat = { 233 - key = 'starsoul:suit_slots_bat'; 234 - accept = 'dynamo'; 235 - sz = def.slots.batteries; 236 - }; 237 - chips = { 238 - key = 'starsoul:suit_slots_chips'; 239 - accept = 'chip'; 240 - sz = def.slots.chips; 241 - }; 242 - canisters = { 243 - key = 'starsoul:suit_slots_canisters'; 244 - accept = 'canister'; 245 - sz = def.slots.canisters; 246 - }; 247 - guns = { 248 - key = 'starsoul:suit_slots_gun'; 249 - accept = 'weapon'; 250 - workbench = { 251 - label = 'Weapon'; 252 - icon = 'starsoul-ui-icon-gun'; 253 - color = lib.color(1,0,0); 254 - }; 255 - sz = def.slots.guns; 256 - }; 257 - ammo = { 258 - key = 'starsoul:suit_slots_ammo'; 259 - accept = 'ammo'; 260 - workbench = { 261 - label = 'Ammunition'; 262 - color = lib.color(1,.5,0); 263 - easySlots = true; -- all slots accessible on the go 264 - }; 265 - sz = def.slots.ammo; 266 - }; 267 - }; 268 - }; 269 - event = { 270 - create = function(st,how) 271 - local s = suitStore(st) 272 - -- make sure there's a defined powerstate 273 - starsoul.type.suit(st):powerStateSet 'off' 274 - suitContainer(st):clear() 275 - --[[ populate meta tables 276 - s.write('batteries', {}) 277 - s.write('guns', {}) 278 - s.write('ammo', {}) 279 - s.write('elements', {}) 280 - s.write('chips', {})]] 281 - end; 282 - }; 283 - suit = def; 284 - }; 285 - }); 286 -end) 287 - 288 -local slotProps = { 289 - starsoul_cfg = { 290 - itemClass = 'inv'; 291 - }; 292 - starsoul_suit_bat = { 293 - suitSlot = true; 294 - powerLock = true; 295 - itemClass = 'dynamo'; 296 - }; 297 - starsoul_suit_chips = { 298 - suitSlot = true; 299 - powerLock = true; 300 - itemClass = 'chip'; 301 - }; 302 - starsoul_suit_guns = { 303 - suitSlot = true; 304 - maintenanceNode = ''; 305 - itemClass = 'suitWeapon'; 306 - }; 307 - starsoul_suit_ammo = { 308 - suitSlot = true; 309 - maintenanceNode = ''; 310 - itemClass = 'suitAmmo'; 311 - }; 312 - starsoul_suit_canisters = { 313 - suitSlot = true; 314 - itemClass = 'canister'; 315 - }; 316 -} 317 - 318 -minetest.register_allow_player_inventory_action(function(luser, act, inv, p) 319 - local user = starsoul.activeUsers[luser:get_player_name()] 320 - local function grp(i,g) 321 - return minetest.get_item_group(i:get_name(), g) ~= 0 322 - end 323 - local function checkBaseRestrictions(list) 324 - local restrictions = slotProps[list] 325 - if not restrictions then return nil, true end 326 - if restrictions.suitSlot then 327 - if user:naked() then return restrictions, false end 328 - end 329 - if restrictions.powerLock then 330 - if user:getSuit():powerState() ~= 'off' then return restrictions, false end 331 - end 332 - return restrictions, true 333 - end 334 - local function itemFits(item, list) 335 - local rst, ok = checkBaseRestrictions(list) 336 - if not ok then return false end 337 - if rst == nil then return true end 338 - 339 - if rst.itemClass and not grp(item, rst.itemClass) then 340 - return false 341 - end 342 - if rst.maintenanceNode then return false end 343 - -- FIXME figure out best way to identify when the player is using a maintenance node 344 - 345 - if grp(item, 'specialInventory') then 346 - if grp(item, 'powder') and list ~= 'starsoul_suit_elem' then return false end 347 - -- FIXME handle containers 348 - if grp(item, 'psi') and list ~= 'starsoul_psi' then return false end 349 - end 350 - 351 - return true 352 - end 353 - local function itemCanLeave(item, list) 354 - local rst, ok = checkBaseRestrictions(list) 355 - if not ok then return false end 356 - if rst == nil then return true end 357 - 358 - if minetest.get_item_group(item:get_name(), 'specialInventory') then 359 - 360 - end 361 - 362 - if rst.maintenanceNode then return false end 363 - return true 364 - end 365 - 366 - if act == 'move' then 367 - local item = inv:get_stack(p.from_list, p.from_index) 368 - if not (itemFits(item, p.to_list) and itemCanLeave(item, p.from_list)) then 369 - return 0 370 - end 371 - elseif act == 'put' then 372 - if not itemFits(p.stack, p.listname) then return 0 end 373 - elseif act == 'take' then 374 - if not itemCanLeave(p.stack, p.listname) then return 0 end 375 - end 376 - return true 377 -end) 378 - 379 -minetest.register_on_player_inventory_action(function(luser, act, inv, p) 380 - local user = starsoul.activeUsers[luser:get_player_name()] 381 - local function slotChange(slot,a,item) 382 - local s = slotProps[slot] 383 - if slot == 'starsoul_suit' then 384 - user:updateSuit() 385 - if user:naked() then 386 - starsoul.type.suit.purgeInventories(user.entity) 387 - user.power.nano = {} 388 - end 389 - elseif s and s.suitSlot then 390 - local s = user:getSuit() 391 - s:onItemMove(user, slot, a, item) 392 - s:onReconfigure(user.entity:get_inventory()) 393 - user:setSuit(s) 394 - else return end 395 - user:updateHUD() 396 - end 397 - 398 - if act == 'put' or act == 'take' then 399 - local item = p.stack 400 - slotChange(p.listname, act, item) 401 - elseif act == 'move' then 402 - local item = inv:get_stack(p.to_list, p.to_index) 403 - slotChange(p.from_list, 'take', item) 404 - slotChange(p.to_list, 'put', item) 405 - end 406 -end) 407 - 408 -local suitInterval = 2.0 409 -starsoul.startJob('starsoul:suit-software', suitInterval, function(delta) 410 - local runState = { 411 - pgmsRun = {}; 412 - flags = {}; 413 - } 414 - for id, u in pairs(starsoul.activeUsers) do 415 - if not u:naked() then 416 - local reconfSuit = false 417 - local inv = u.entity:get_inventory() 418 - local chips = inv:get_list('starsoul_suit_chips') 419 - local suitprog = starsoul.mod.electronics.chip.usableSoftware(chips) 420 - for _, prop in pairs(suitprog) do 421 - local s = prop.sw 422 - if s.kind == 'suitPower' and (s.powerKind == 'passive' or s.bgProc) and (not runState.pgmsRun[s]) then 423 - local conf = prop.file.body.conf 424 - local enabled = true 425 - for _, e in ipairs(conf) do 426 - if e.key == 'disable' and e.value == 'yes' then 427 - enabled = false 428 - break 429 - end 430 - end 431 - local fn if s.powerKind == 'passive' 432 - then fn = s.run 433 - else fn = s.bgProc 434 - end 435 - function prop.saveConf(cfg) cfg = cfg or conf 436 - prop.fd:write(cfg) 437 - inv:set_stack('starsoul_suit_chips', prop.chipSlot, prop.fd.chip) 438 - reconfSuit = true 439 - end 440 - function prop.giveItem(st) 441 - u:thrustUpon(st) 442 - end 443 - 444 - if enabled and fn(u, prop, suitInterval, runState) then 445 - runState.pgmsRun[s] = true 446 - end 447 - end 448 - end 449 - if reconfSuit then 450 - u:reconfigureSuit() 451 - end 452 - end 453 - end 454 -end) 455 -
Deleted mods/starsoul/terrain.lua version [faca82716c].
1 -local T = starsoul.translator 2 -local lib = starsoul.mod.lib 3 - 4 -starsoul.terrain = {} 5 -local soilSounds = {} 6 -local grassSounds = {} 7 - 8 -minetest.register_node('starsoul:soil', { 9 - description = T 'Soil'; 10 - tiles = {'default_dirt.png'}; 11 - groups = {dirt = 1}; 12 - drop = ''; 13 - sounds = soilSounds; 14 - _starsoul = { 15 - onDestroy = function() end; 16 - kind = 'block'; 17 - elements = {}; 18 - }; 19 -}) 20 - 21 - 22 -minetest.register_node('starsoul:sand', { 23 - description = T 'Sand'; 24 - tiles = {'default_sand.png'}; 25 - groups = {dirt = 1}; 26 - drop = ''; 27 - sounds = soilSounds; 28 - _starsoul = { 29 - kind = 'block'; 30 - fab = starsoul.type.fab { element = { silicon = 25 } }; 31 - }; 32 -}) 33 -minetest.register_craftitem('starsoul:soil_clump', { 34 - short_description = T 'Soil'; 35 - description = starsoul.ui.tooltip { 36 - title = T 'Soil'; 37 - desc = 'A handful of nutrient-packed soil, suitable for growing plants'; 38 - color = lib.color(0.3,0.2,0.1); 39 - }; 40 - inventory_image = 'starsoul-item-soil.png'; 41 - groups = {soil = 1}; 42 - _starsoul = { 43 - fab = starsoul.type.fab { element = { carbon = 12 / 4 } }; 44 - }; 45 -}) 46 - 47 -function starsoul.terrain.createGrass(def) 48 - local function grassfst(i) 49 - local nextNode = def.name 50 - if i >= 0 then 51 - nextNode = nextNode .. '_walk_' .. tostring(i) 52 - end 53 - return { 54 - onWalk = function(pos) 55 - minetest.set_node_at(pos, def.name .. '_walk_2'); 56 - end; 57 - onDecay = function(pos,delta) 58 - minetest.set_node_at(pos, nextNode); 59 - end; 60 - onDestroy = function(pos) end; 61 - fab = def.fab; 62 - recover = def.recover; 63 - recover_vary = def.recover_vary; 64 - }; 65 - end 66 - local drop = { 67 - max_items = 4; 68 - items = { 69 - { 70 - items = {'starsoul:soil'}, rarity = 2; 71 - tool_groups = { 'shovel', 'trowel' }; 72 - }; 73 - }; 74 - } 75 - minetest.register_node(def.name, { 76 - description = T 'Greengraze'; 77 - tiles = { 78 - def.img .. '.png'; 79 - 'default_dirt.png'; 80 - { 81 - name = 'default_dirt.png^' .. def.img ..'_side.png'; 82 - tileable_vertical = false; 83 - }; 84 - }; 85 - groups = {grass = 1, sub_walk = 1}; 86 - drop = ''; 87 - sounds = grassSounds; 88 - _starsoul = grassfst(2); 89 - }) 90 - for i=2,0,-1 do 91 - local opacity = tostring((i/2.0) * 255) 92 - 93 - minetest.register_node(def.name, { 94 - description = def.desc; 95 - tiles = { 96 - def.img .. '.png^(default_footprint.png^[opacity:'..opacity..')'; 97 - 'default_dirt.png'; 98 - { 99 - name = 'default_dirt.png^' .. def.img ..'_side.png'; 100 - tileable_vertical = false; 101 - }; 102 - }; 103 - groups = {grass = 1, sub_walk = 1, sub_decay = 5}; 104 - drop = ''; 105 - _starsoul = grassfst(i-1); 106 - sounds = grassSounds; 107 - }) 108 - end 109 -end 110 - 111 - 112 -starsoul.terrain.createGrass { 113 - name = 'starsoul:greengraze'; 114 - desc = T 'Greengraze'; 115 - img = 'default_grass'; 116 - fab = starsoul.type.fab { 117 - element = { 118 - carbon = 12; 119 - }; 120 - time = { 121 - shred = 2.5; 122 - }; 123 - }; 124 -} 125 - 126 -for _, w in pairs {false,true} do 127 - minetest.register_node('starsoul:liquid_water' .. (w and '_flowing' or ''), { 128 - description = T 'Water'; 129 - drawtype = 'liquid'; 130 - waving = 3; 131 - tiles = { 132 - { 133 - name = "default_water_source_animated.png"; 134 - backface_culling = false; 135 - animation = { 136 - type = "vertical_frames"; 137 - aspect_w = 16; 138 - aspect_h = 16; 139 - length = 2.0; 140 - }; 141 - }; 142 - { 143 - name = "default_water_source_animated.png"; 144 - backface_culling = true; 145 - animation = { 146 - type = "vertical_frames"; 147 - aspect_w = 16; 148 - aspect_h = 16; 149 - length = 2.0; 150 - }; 151 - }; 152 - }; 153 - use_texture_alpha = 'blend'; 154 - paramtype = 'light'; 155 - walkable = false, pointable = false, diggable = false, buildable_to = true; 156 - is_ground_content = false; 157 - drop = ''; 158 - drowning = 1; 159 - liquidtype = w and 'flowing' or 'source'; 160 - liquid_alternative_flowing = 'starsoul:liquid_water_flowing'; 161 - liquid_alternative_source = 'starsoul:liquid_water'; 162 - liquid_viscosity = 1; 163 - liquid_renewable = true; 164 - liquid_range = 2; 165 - drowning = 40; 166 - post_effect_color = {a=103, r=10, g=40, b=70}; 167 - groups = {water = 3, liquid = 3}; 168 - }); 169 -end 170 - 171 - 172 -starsoul.world.mineral.foreach('starsoul:mineral_generate', {}, function(name,m) 173 - local node = string.format('starsoul:mineral_%s', name) 174 - local grp = {mineral = 1} 175 - minetest.register_node(node, { 176 - description = m.desc; 177 - tiles = m.tiles or 178 - (m.tone and { 179 - string.format('default_stone.png^[colorizehsl:%s:%s:%s', 180 - m.tone.hue, m.tone.sat, m.tone.lum) 181 - }) or {'default_stone.png'}; 182 - groups = grp; 183 - drop = m.rocks or ''; 184 - _starsoul = { 185 - kind = 'block'; 186 - elements = m.elements; 187 - fab = m.fab; 188 - recover = m.recover; 189 - recover_vary = m.recover_vary; 190 - }; 191 - }) 192 - if not m.excludeOre then 193 - local seed = 0 194 - grp.ore = 1 195 - for i = 1, #m.name do 196 - seed = seed*50 + string.byte(name, i) 197 - end 198 - minetest.register_ore { 199 - ore = node; 200 - ore_type = m.dist.kind; 201 - wherein = {m.dist.among}; 202 - clust_scarcity = m.dist.rare; 203 - y_max = m.dist.height[1], y_min = m.dist.height[2]; 204 - noise_params = m.dist.noise or { 205 - offset = 28; 206 - scale = 16; 207 - spread = vector.new(128,128,128); 208 - seed = seed; 209 - octaves = 1; 210 - }; 211 - } 212 - end 213 -end) 214 - 215 -starsoul.world.mineral.link('feldspar', { 216 - desc = T 'Feldspar'; 217 - excludeOre = true; 218 - recover = starsoul.type.fab { 219 - time = { 220 - shred = 3; 221 - }; 222 - cost = { 223 - shredPower = 3; 224 - }; 225 - }; 226 - recover_vary = function(rng, ctx) 227 - -- print('vary!', rng:int(), rng:int(0,10)) 228 - return starsoul.type.fab { 229 - element = { 230 - aluminum = rng:int(0,4); 231 - potassium = rng:int(0,2); 232 - calcium = rng:int(0,2); 233 - } 234 - }; 235 - end; 236 -}) 237 - 238 --- map generation 239 - 240 -minetest.register_alias('mapgen_stone', 'starsoul:mineral_feldspar') 241 -minetest.register_alias('mapgen_water_source', 'starsoul:liquid_water') 242 -minetest.register_alias('mapgen_river_water_source', 'starsoul:liquid_water') 243 -
Deleted mods/starsoul/tiers.lua version [97d9d01bfc].
1 -local lib = starsoul.mod.lib 2 - 3 -starsoul.world.tier = lib.registry.mk 'starsoul:tier' 4 -local T = starsoul.world.tier 5 -local fab = starsoul.type.fab 6 - 7 -function starsoul.world.tier.fabsum(name, ty) 8 - local dest = fab {} 9 - local t = starsoul.world.tier.db[name] 10 - assert(t, 'reference to nonexisting tier '..name) 11 - if t.super then 12 - dest = dest+starsoul.world.tier.fabsum(t.super, ty)*(t.cost or 1) 13 - end 14 - if t.fabclasses and t.fabclasses[ty] then 15 - dest = dest + t.fabclasses[ty] 16 - end 17 - return dest 18 -end 19 - 20 -function starsoul.world.tier.tech(name, tech) 21 - local t = starsoul.world.tier.db[name] 22 - if t.techs and t.techs[tech] ~= nil then return t.techs[tech] end 23 - if t.super then return starsoul.world.tier.tech(t.super, tech) end 24 - return false 25 -end 26 - 27 -T.meld { 28 - base = { 29 - fabclass = { 30 - electric = fab {metal={copper = 10}}; 31 - suit = fab {element={carbon = 1e3}}; 32 - psi = fab {metal={numinium = 1}}; 33 - bio = fab {element={carbon = 1}}; 34 - }; 35 - 36 - }; -- properties that apply to all tiers 37 - ------------------ 38 - -- tier classes -- 39 - ------------------ 40 - 41 - lesser = { 42 - name = 'Lesser', adj = 'Lesser'; 43 - super = 'base'; 44 - fabclasses = { 45 - basis = fab { 46 - metal = {aluminum=4}; 47 - }; 48 - }; 49 - }; 50 - greater = { 51 - name = 'Greater', adj = 'Greater'; 52 - super = 'base'; 53 - fabclasses = { 54 - basis = fab { 55 - metal = {vanadium=2}; 56 - }; 57 - }; 58 - }; 59 - starsoul = { 60 - name = 'Starsoul', adj = 'Starsoul'; 61 - super = 'base'; 62 - fabclasses = { 63 - basis = fab { 64 - metal = {osmiridium=1}; 65 - }; 66 - }; 67 - }; 68 - forevanished = { 69 - name = 'Forevanished One', adj = 'Forevanished'; 70 - super = 'base'; 71 - fabclasses = { 72 - basis = fab { 73 - metal = {elusium=1}; 74 - }; 75 - }; 76 - }; 77 - 78 - ------------------ 79 - -- Lesser Races -- 80 - ------------------ 81 - 82 - makeshift = { -- regular trash 83 - name = 'Makeshift', adj = 'Makeshift'; 84 - super = 'lesser'; 85 - techs = {tool = true, prim = true, electric = true}; 86 - power = 0.5; 87 - efficiency = 0.3; 88 - reliability = 0.2; 89 - cost = 0.3; 90 - fabclasses = { -- characteristic materials 91 - basis = fab { -- fallback 92 - metal = {iron=3}; 93 - }; 94 - }; 95 - }; 96 - 97 - imperial = { --powerful trash 98 - name = 'Imperial', adj = 'Imperial'; 99 - super = 'lesser'; 100 - techs = {tool = true, electric = true, electronic = true, suit = true, combatSuit = true, weapon = true, hover='ion'}; 101 - power = 2.0; 102 - efficiency = 0.5; 103 - reliability = 0.5; 104 - cost = 1.0; 105 - fabclasses = { 106 - basis = fab { 107 - metal = {steel=2}; 108 - }; 109 - }; 110 - }; 111 - 112 - commune = { --reliability 113 - name = 'Commune', adj = 'Commune'; 114 - super = 'lesser'; 115 - techs = {tool = true, electric = true, electronic = true, suit = true, combatSuit = true, weapon = true, gravitic = true, hover='grav'}; 116 - power = 1.0; 117 - efficiency = 2.0; 118 - reliability = 3.0; 119 - cost = 1.5; 120 - fabclasses = { 121 - basis = fab { 122 - metal = {titanium=1}; 123 - time = {print = 1.2}; -- commune stuff is intricate 124 - }; 125 - }; 126 - }; 127 - 128 - ------------------- 129 - -- Greater Races -- 130 - ------------------- 131 - 132 - 133 - ---------------- 134 - -- Starsouled -- 135 - ---------------- 136 - 137 - suIkuri = { --super-tier 138 - name = 'Su\'ikuri', adj = "Su'ikuruk"; 139 - super = 'starsoul'; 140 - techs = {psi = true, prim = true, bioSuit = true, psiSuit = true}; 141 - power = 1.5; 142 - efficiency = 1.0; 143 - reliability = 3.0; 144 - cost = 2.0; 145 - fabclasses = { 146 - psi = fab { 147 - metal = {numinium = 2.0}; 148 - crystal = {beryllium = 1.0}; 149 - }; 150 - bio = fab { 151 - crystal = {beryllium = 1.0}; 152 - }; 153 - }; 154 - }; 155 - 156 - usukwinya = { --value for 'money'; no weapons; no hovertech (they are birds) 157 - -- NOTA BENE: the ususkwinya *do* have weapons of their own; however, 158 - -- they are extremely restricted and never made available except to a 159 - -- very select number of that species. consequently, usuk players 160 - -- of a certain scenario may have usuk starting weapons, but these must 161 - -- be manually encoded to avoid injecting them into the overall crafting 162 - -- /loot system. because there are so few of these weapons in existence, 163 - -- all so tightly controlled, the odds of the weapons or plans winding 164 - -- up on Farthest Shadow are basically zero unless you bring them yourself 165 - name = 'Usukwinya', adj = 'Usuk'; 166 - super = 'starsoul'; 167 - techs = lib.tbl.set('tool', 'electric', 'electronic', 'suit', 'gravitic'); 168 - power = 2.0; 169 - efficiency = 2.0; 170 - reliability = 2.0; 171 - cost = 0.5; 172 - fabclasses = { 173 - basis = fab { 174 - crystal = {aluminum = 5}; -- ruby 175 - }; 176 - }; 177 - }; 178 - 179 - eluthrai = { --super-tier 180 - name = 'Eluthrai', adj = 'Eluthran'; 181 - super = 'starsoul'; 182 - techs = {tool = true, electric = true, electronic = true, weapon = true, gravitic = true, gravweapon = true, suit = true, combatSuit = true, hover = 'grav'}; 183 - power = 4.0; 184 - efficiency = 4.0; 185 - reliability = 4.0; 186 - cost = 4.0; 187 - fabclasses = { 188 - basis = fab { 189 - crystal = {carbon = 5}; -- diamond 190 - }; 191 - special = fab { 192 - metal = {technetium=1, cinderstone=1} 193 - }; 194 - }; 195 - }; 196 - 197 - ----------------------- 198 - -- Forevanished Ones -- 199 - ----------------------- 200 - 201 - firstborn = { --god-tier 202 - name = 'Firstborn', adj = 'Firstborn'; 203 - super = 'forevanished'; 204 - techs = {tool = true, electric = true, electronic = true, suit = true, psi = true, combatSuit = true, weapon = true, gravitic = true, gravweapon = true}; 205 - power = 10.0; 206 - efficiency = 5.0; 207 - reliability = 3.0; 208 - cost = 10.0; 209 - fabclasses = { 210 - basis = fab { 211 - metal = {technetium=2, neodymium=3, sunsteel=1}; 212 - crystal = {astrite=1}; 213 - }; 214 - }; 215 - }; 216 - 217 - forevanisher = { --godslayer-tier 218 - name = 'Forevanisher', adj = 'Forevanisher'; 219 - super = 'forevanished'; 220 - techs = {tool = true, electric = true, electronic = true, suit = true, psi = true, combatSuit = true, weapon = true, gravitic = true, gravweapon = true}; 221 - power = 20.0; 222 - efficiency = 1.0; 223 - reliability = 2.0; 224 - cost = 100.0; 225 - fabclasses = { 226 - basis = fab { 227 - metal = {}; 228 - crystal = {}; 229 - }; 230 - }; 231 - }; 232 - 233 -}
Deleted mods/starsoul/ui.lua version [e13ae59c08].
1 -local lib = starsoul.mod.lib 2 - 3 -starsoul.ui = {} 4 - 5 -starsoul.type.ui = lib.class { 6 - name = 'starsoul:ui'; 7 - __index = { 8 - action = function(self, user, state, fields) 9 - local pg = self.pages[state.page or 'index'] 10 - if not pg then return end 11 - if pg.handle then 12 - local redraw, reset = pg.handle(state, user, fields) 13 - if reset then pg.setupState(state,user) end 14 - if redraw then self:show(user) end 15 - end 16 - if fields.quit then self:cb('onClose', user) end 17 - end; 18 - cb = function(self, name, user, ...) 19 - local state = self:begin(user) 20 - if self[name] then self[name](state, user, ...) end 21 - local pcb = self.pages[state.page][name] 22 - if pcb then pcb(state, user, ...) end 23 - end; 24 - begin = function(self, user, page, ...) 25 - local state = starsoul.activeUI[user.name] 26 - if state and state.form ~= self.id then 27 - state = nil 28 - starsoul.activeUI[user.name] = nil 29 - end 30 - local created = state == nil 31 - 32 - if not state then 33 - state = { 34 - page = page or 'index'; 35 - form = self.id; 36 - } 37 - starsoul.activeUI[user.name] = state 38 - self:cb('setupState', user, ...) 39 - elseif page ~= nil and state.page ~= page then 40 - state.page = page 41 - local psetup = self.pages[state.page].setupState 42 - if psetup then psetup(state,user, ...) end 43 - end 44 - return state, created 45 - end; 46 - render = function(self, state, user) 47 - return self.pages[state.page].render(state, user) 48 - end; 49 - show = function(self, user) 50 - local state = self:begin(user) 51 - minetest.show_formspec(user.name, self.id,self:render(state, user)) 52 - end; 53 - open = function(self, user, page, ...) 54 - user:suitSound 'starsoul-nav' 55 - self:begin(user, page, ...) 56 - self:show(user) 57 - end; 58 - close = function(self, user) 59 - local state = starsoul.activeUI[user.name] 60 - if state and state.form == self.id then 61 - self:cb('onClose', user) 62 - starsoul.activeUI[user.name] = nil 63 - minetest.close_formspec(user.name, self.id) 64 - end 65 - end; 66 - }; 67 - construct = function(p) 68 - if not p.id then error('UI missing id') end 69 - p.pages = p.pages or {} 70 - return p 71 - end; 72 -} 73 - 74 -function starsoul.interface.install(ui) 75 - starsoul.interface.link(ui.id, ui) 76 -end 77 - 78 -function starsoul.ui.build(def, parent) 79 - local clr = def.color 80 - if clr and lib.color.id(clr) then 81 - clr = clr:to_hsl_o() 82 - end 83 - local state = { 84 - x = (def.x or 0); 85 - y = (def.y or 0); 86 - w = def.w or 0, h = def.h or 0; 87 - fixed = def.fixed or false; 88 - spacing = def.spacing or 0; 89 - padding = def.padding or 0; 90 - align = def.align or (parent and parent.align) or 'left'; 91 - lines = {}; 92 - mode = def.mode or (parent and parent.mode or nil); -- hw or sw 93 - gen = (parent and parent.gen or 0) + 1; 94 - fg = def.fg or (parent and parent.fg); 95 - color = clr or (parent and parent.color) or { 96 - hue = 260, sat = 0, lum = 0 97 - }; 98 - } 99 - local lines = state.lines 100 - local cmod = string.format('^[hsl:%s:%s:%s', 101 - state.color.hue, state.color.sat*0xff, state.color.lum*0xff) 102 - 103 - local E = minetest.formspec_escape 104 - if state.padding/2 > state.x then state.x = state.padding/2 end 105 - if state.padding/2 > state.y then state.y = state.padding/2 end 106 - 107 - local function btnColorDef(sel) 108 - local function climg(state,img) 109 - local selstr 110 - if sel == nil then 111 - selstr = string.format( 112 - 'button%s,' .. 113 - 'button_exit%s,' .. 114 - 'image_button%s,' .. 115 - 'item_image_button%s', 116 - state, state, state, state) 117 - else 118 - selstr = E(sel) .. state 119 - end 120 - 121 - return string.format('%s[%s;' .. 122 - 'bgimg=%s;' .. 123 - 'bgimg_middle=16;' .. 124 - 'content_offset=0,0' .. 125 - ']', sel and 'style' or 'style_type', 126 - selstr, E(img..'^[resize:48x48'..cmod)) 127 - end 128 - 129 - return climg('', 'starsoul-ui-button-sw.png') .. 130 - climg(':hovered', 'starsoul-ui-button-sw-hover.png') .. 131 - climg(':pressed', 'starsoul-ui-button-sw-press.png') 132 - end 133 - local function widget(...) 134 - table.insert(lines, string.format(...)) 135 - end 136 - if def.kind == 'vert' then 137 - for _, w in ipairs(def) do 138 - local src, st = starsoul.ui.build(w, state) 139 - widget('container[%s,%s]%scontainer_end[]', state.x, state.y, src) 140 - state.y=state.y + state.spacing + st.h 141 - state.w = math.max(state.w, st.w) 142 - end 143 - state.w = state.w + state.padding 144 - state.h = state.y + state.padding/2 145 - elseif def.kind == 'hztl' then 146 - for _, w in ipairs(def) do 147 - local src, st = starsoul.ui.build(w, state) 148 - widget('container[%s,%s]%scontainer_end[]', state.x, state.y, src) 149 - -- TODO alignments 150 - state.x=state.x + state.spacing + st.w 151 - state.h = math.max(state.h, st.h) 152 - end 153 - state.h = state.h + state.padding 154 - state.w = state.x + state.padding/2 155 - elseif def.kind == 'list' then 156 - local slotTypes = { 157 - plain = {hue = 200, sat = -.1, lum = 0}; 158 - element = {hue = 20, sat = -.3, lum = 0}; 159 - chip = {hue = 0, sat = -1, lum = 0}; 160 - psi = {hue = 300, sat = 0, lum = 0}; 161 - power = {hue = 50, sat = 0, lum = .2}; 162 - } 163 - local img 164 - if state.mode == 'hw' then 165 - img = lib.image('starsoul-ui-slot-physical.png'); 166 - else 167 - img = lib.image('starsoul-ui-slot.png'):shift(slotTypes[def.listContent or 'plain']); 168 - end 169 - local spac = state.spacing 170 - widget('style_type[list;spacing=%s,%s]',spac,spac) 171 - assert(def.w and def.h, 'ui-lists require a fixed size') 172 - for lx = 0, def.w-1 do 173 - for ly = 0, def.h-1 do 174 - local ox, oy = state.x + lx*(1+spac), state.y + ly*(1+spac) 175 - table.insert(lines, string.format('image[%s,%s;1.1,1.1;%s]', ox-0.05,oy-0.05, img:render())) 176 - end end 177 - table.insert(lines, string.format('listcolors[#00000000;#ffffff10]')) -- FIXME 178 - table.insert(lines, string.format('list[%s;%s;%s,%s;%s,%s;%s]', 179 - E(def.target), E(def.inv), 180 - state.x, state.y, 181 - def.w, def.h, 182 - def.idx)) 183 - local sm = 1 184 - state.w = def.w * sm + (spac * (def.w - 1)) 185 - state.h = def.h * sm + (spac * (def.h - 1)) 186 - elseif def.kind == 'contact' then 187 - if def.color then table.insert(lines, btnColorDef(def.id)) end 188 - widget('image_button%s[%s,%s;%s,%s;%s;%s;%s]', 189 - def.close and '_exit' or '', 190 - state.x, state.y, def.w, def.h, 191 - E(def.img), E(def.id), E(def.label or '')) 192 - elseif def.kind == 'button' then 193 - if def.color then table.insert(lines, btnColorDef(def.id)) end 194 - local label = E(def.label or '') 195 - if state.fg then label = lib.color(state.fg):fmt(label) end 196 - widget('button%s[%s,%s;%s,%s;%s;%s]', 197 - def.close and '_exit' or '', 198 - state.x, state.y, def.w, def.h, 199 - E(def.id), label) 200 - elseif def.kind == 'img' then 201 - widget('%s[%s,%s;%s,%s;%s]', 202 - def.item and 'item_image' or 'image', 203 - state.x, state.y, def.w, def.h, E(def.item or def.img)) 204 - elseif def.kind == 'label' then 205 - local txt = E(def.text) 206 - if state.fg then txt = lib.color(state.fg):fmt(txt) end 207 - widget('label[%s,%s;%s]', 208 - state.x, state.y + def.h*.5, txt) 209 - elseif def.kind == 'text' then 210 - -- TODO paragraph formatter 211 - widget('hypertext[%s,%s;%s,%s;%s;%s]', 212 - state.x, state.y, def.w, def.h, E(def.id), E(def.text)) 213 - elseif def.kind == 'hbar' or def.kind == 'vbar' then -- TODO fancy image bars 214 - local cl = lib.color(state.color) 215 - local fg = state.fg or cl:readable(.8,1) 216 - local wfac, hfac = 1,1 217 - local clamp = math.min(math.max(def.fac, 0), 1) 218 - if def.kind == 'hbar' 219 - then wfac = wfac * clamp 220 - else hfac = hfac * clamp 221 - end 222 - local x,y, w,h = state.x, state.y, def.w, def.h 223 - widget('box[%s,%s;%s,%s;%s]', 224 - x,y, w,h, cl:brighten(0.2):hex()) 225 - widget('box[%s,%s;%s,%s;%s]', 226 - x, y + (h*(1-hfac)), w * wfac, h * hfac, cl:hex()) 227 - if def.text then 228 - widget('hypertext[%s,%s;%s,%s;;%s]', 229 - state.x, state.y, def.w, def.h, 230 - string.format('<global halign=center valign=middle color=%s>%s', fg:hex(), E(def.text))) 231 - end 232 - end 233 - 234 - if def.desc then 235 - widget('tooltip[%s,%s;%s,%s;%s]', 236 - state.x, state.y, def.w, def.h, E(def.desc)) 237 - end 238 - 239 - local originX = (parent and parent.x or 0) 240 - local originY = (parent and parent.y or 0) 241 - local l = table.concat(lines) 242 - -- if state.fixed and (state.w < state.x or state.h < state.y) then 243 - -- l = string.format('scroll_container[%s,%s;%s,%s;scroll_%s;%s]%sscroll_container_end[]', 244 - -- (parent and parent.x or 0), (parent and parent.y or 0), 245 - -- state.w, state.h, state.gen, 246 - -- (state.x > state.w) and 'horizontal' or 'vertical', l) 247 - -- end 248 - 249 - 250 - if def.mode or def.container then 251 - if def.mode then 252 - l = string.format('background9[%s,%s;%s,%s;%s;false;64]', 253 - originX, originY, state.w, state.h, 254 - E(string.format('starsoul-ui-bg-%s.png%s^[resize:128x128', 255 - (def.mode == 'sw') and 'digital' 256 - or 'panel', cmod))) .. l 257 - end 258 - if parent == nil or state.color ~= parent.color then 259 - l = btnColorDef() .. l 260 - end 261 - end 262 - if not parent then 263 - return string.format('formspec_version[6]size[%s,%s]%s', state.w, state.h, l), state 264 - else 265 - return l, state 266 - end 267 -end 268 - 269 -starsoul.ui.tooltip = lib.ui.tooltipper { 270 - colors = { 271 - -- generic notes 272 - neutral = lib.color(.5,.5,.5); 273 - good = lib.color(.2,1,.2); 274 - bad = lib.color(1,.2,.2); 275 - info = lib.color(.4,.4,1); 276 - -- chip notes 277 - schemaic = lib.color(.2,.7,1); 278 - ability = lib.color(.7,.2,1); 279 - driver = lib.color(1,.7,.2); 280 - }; 281 -}
Deleted mods/starsoul/user.lua version [5326333d8a].
1 --- [ʞ] user.lua 2 --- ~ lexi hale <lexi@hale.su> 3 --- © EUPL v1.2 4 --- ? defines the starsoul.type.user class, which is 5 --- the main interface between the game world and the 6 --- client. it provides for initial signup and join, 7 --- managing the HUD, skinning the player model, 8 --- effecting weather changes, etc. 9 - 10 -local lib = starsoul.mod.lib 11 - 12 -local function hudAdjustBacklight(img) 13 - local night = math.abs(minetest.get_timeofday() - .5) * 2 14 - local opacity = night*0.8 15 - return img:fade(opacity) 16 -end 17 - 18 -local userStore = lib.marshal.metaStore { 19 - persona = { 20 - key = 'starsoul:persona'; 21 - type = starsoul.store.persona; 22 - }; 23 -} 24 - 25 -local suitStore = starsoul.store.suitMeta 26 - 27 -starsoul.type.user = lib.class { 28 - name = 'starsoul:user'; 29 - construct = function(ident) 30 - local name, luser 31 - if type(ident) == 'string' then 32 - name = ident 33 - luser = minetest.get_player_by_name(name) 34 - else 35 - luser = ident 36 - name = luser:get_player_name() 37 - end 38 - return { 39 - entity = luser; 40 - name = name; 41 - hud = { 42 - elt = {}; 43 - }; 44 - tree = {}; 45 - action = { 46 - bits = 0; -- for control deltas 47 - prog = {}; -- for recording action progress on a node; reset on refocus 48 - tgt = {type='nothing'}; 49 - sfx = {}; 50 - fx = {}; 51 - }; 52 - actMode = 'off'; 53 - power = { 54 - nano = {primary = nil, secondary = nil}; 55 - weapon = {primary = nil, secondary = nil}; 56 - psi = {primary = nil, secondary = nil}; 57 - maneuver = nil; 58 - }; 59 - pref = { 60 - calendar = 'commune'; 61 - }; 62 - } 63 - end; 64 - __index = { 65 - pullPersona = function(self) 66 - -- if later records are added in public updates, extend this function to merge them 67 - -- into one object 68 - local s = userStore(self.entity) 69 - self.persona = s.read 'persona' 70 - end; 71 - pushPersona = function(self) 72 - local s = userStore(self.entity) 73 - s.write('persona', self.persona) 74 - end; 75 - uiColor = function(self) return lib.color {hue=238,sat=.5,lum=.5} end; 76 - statDelta = function(self, stat, d, cause, abs) 77 - local dt = self.persona.statDeltas 78 - local base 79 - if abs then 80 - local min, max 81 - min, max, base = self:statRange(stat) 82 - if d == true then d = max 83 - elseif d == false then d = min end 84 - end 85 - if stat == 'health' then 86 - self.entity:set_hp(abs and d or (self.entity:get_hp() + d), cause) 87 - elseif stat == 'breath' then 88 - self.entity:set_breath(abs and d or (self.entity:get_breath() + d)) 89 - else 90 - if abs then 91 - dt[stat] = d - base 92 - else 93 - dt[stat] = dt[stat] + d 94 - end 95 - self:pushPersona() 96 - end 97 - self:updateHUD() 98 - -- TODO trigger relevant animations? 99 - end; 100 - lookupSpecies = function(self) 101 - return starsoul.world.species.lookup(self.persona.species, self.persona.speciesVariant) 102 - end; 103 - phenoTrait = function(self, trait) 104 - local s,v = self:lookupSpecies() 105 - return v.traits[trait] or s.traits[trait] or 0 106 - end; 107 - statRange = function(self, stat) --> min, max, base 108 - return starsoul.world.species.statRange( 109 - self.persona.species, self.persona.speciesVariant, stat) 110 - end; 111 - effectiveStat = function(self, stat) 112 - local val 113 - local min, max, base = self:statRange(stat) 114 - 115 - if stat == 'health' then 116 - val = self.entity:get_hp() 117 - elseif stat == 'breath' then 118 - val = self.entity:get_breath() 119 - else 120 - val = base + self.persona.statDeltas[stat] or 0 121 - end 122 - 123 - local d = max - min 124 - return val, (val - min) / d 125 - end; 126 - damageModifier = function(self, kind, amt) 127 - if kind == 'bluntForceTrauma' then 128 - local std = self:phenoTrait 'sturdiness' 129 - if std < 0 then 130 - amt = amt / 1+std 131 - else 132 - amt = amt * 1-std 133 - end 134 - end 135 - return amt 136 - end; 137 - attachImage = function(self, def) 138 - local user = self.entity 139 - local img = {} 140 - img.id = user:hud_add { 141 - type = 'image'; 142 - text = def.tex; 143 - scale = def.scale; 144 - alignment = def.align; 145 - position = def.pos; 146 - offset = def.ofs; 147 - z_index = def.z; 148 - } 149 - if def.update then 150 - img.update = function() 151 - def.update(user, function(prop, val) 152 - user:hud_change(img.id, prop, val) 153 - end, def) 154 - end 155 - end 156 - return img 157 - end; 158 - attachMeter = function(self, def) 159 - local luser = self.entity 160 - local m = {} 161 - local w = def.size or 80 162 - local szf = w / 80 163 - local h = szf * 260 164 - m.meter = luser:hud_add { 165 - type = 'image'; 166 - scale = {x = szf, y = szf}; 167 - alignment = def.align; 168 - position = def.pos; 169 - offset = def.ofs; 170 - z_index = def.z or 0; 171 - } 172 - local cx = def.ofs.x + (w/2)*def.align.x 173 - local cy = def.ofs.y + (h/2)*def.align.y 174 - local oy = cy + h/2 - 42 175 - -- this is so fucking fragile holy fuck 176 - m.readout = luser:hud_add { 177 - type = 'text'; 178 - scale = {x = w, y = h}; 179 - size = szf; 180 - style = 4; 181 - position = def.pos; 182 - alignment = {x=0,0}; 183 - offset = {x = cx, y = oy}; 184 - z_index = (def.z or 0)+1; 185 - number = 0xffffff; 186 - } 187 - m.destroy = function() 188 - luser:hud_remove(m.meter) 189 - luser:hud_remove(m.readout) 190 - end 191 - m.update = function() 192 - local v,txt,color,txtcolor = def.measure(luser,def) 193 - v = math.max(0, math.min(1, v)) 194 - local n = math.floor(v*16) + 1 195 - local img = hudAdjustBacklight(lib.image('starsoul-ui-meter.png')) 196 - :colorize(color or def.color) 197 - if def.flipX then 198 - img = img:transform 'FX' 199 - end 200 - img = img:render() 201 - img = img .. '^[verticalframe:17:' .. tostring(17 - n) 202 - luser:hud_change(m.meter, 'text', img) 203 - if txt then 204 - luser:hud_change(m.readout, 'text', txt) 205 - end 206 - if txtcolor then 207 - luser:hud_change(m.readout, 'number', txtcolor:hex()) 208 - end 209 - end 210 - return m 211 - end; 212 - attachTextBox = function(self, def) 213 - local luser = self.entity 214 - local box = {} 215 - box.id = luser:hud_add { 216 - type = 'text'; 217 - text = ''; 218 - alignment = def.align; 219 - number = def.color and def.color:int24() or 0xFFffFF; 220 - scale = def.bound; 221 - size = {x = def.size, y=0}; 222 - style = def.style; 223 - position = def.pos; 224 - offset = def.ofs; 225 - } 226 - box.update = function() 227 - local text, color = def.text(self, box, def) 228 - luser:hud_change(box.id, 'text', text) 229 - if color then 230 - luser:hud_change(box.id, 'number', color:int24()) 231 - end 232 - end 233 - return box 234 - end; 235 - attachStatBar = function(self, def) 236 - local luser = self.entity 237 - local bar = {} 238 - local img = lib.image 'starsoul-ui-bar.png' 239 - local colorized = img 240 - if type(def.color) ~= 'function' then 241 - colorized = colorized:shift(def.color) 242 - end 243 - 244 - bar.id = luser:hud_add { 245 - type = 'statbar'; 246 - position = def.pos; 247 - offset = def.ofs; 248 - name = def.name; 249 - text = colorized:render(); 250 - text2 = img:tint{hue=0, sat=-1, lum = -0.5}:fade(0.5):render(); 251 - number = def.size; 252 - item = def.size; 253 - direction = def.dir; 254 - alignment = def.align; 255 - size = {x=4,y=24}; 256 - } 257 - bar.update = function() 258 - local sv, sf = def.stat(self, bar, def) 259 - luser:hud_change(bar.id, 'number', def.size * sf) 260 - if type(def.color) == 'function' then 261 - local clr = def.color(sv, luser, sv, sf) 262 - luser:hud_change(bar.id, 'text', img:tint(clr):render()) 263 - end 264 - end 265 - return bar, {x=3 * def.size, y=16} -- x*2??? what 266 - end; 267 - createHUD = function(self) 268 - local function basicStat(statName) 269 - return function(user, bar) 270 - return self:effectiveStat(statName) 271 - end 272 - end 273 - local function batteryLookup(user) 274 - local max = user:suitPowerCapacity() 275 - if max == 0 then return 0, 0 end 276 - local ch = user:suitCharge() 277 - return (ch/max)*100, ch/max 278 - end 279 - local function C(h,s,l) return {hue=h,sat=s,lum=l} end 280 - local hbofs = (1+self.entity:hud_get_hotbar_itemcount()) * 25 281 - local bpad = 8 282 - self.hud.elt.health = self:attachStatBar { 283 - name = 'health', stat = basicStat 'health'; 284 - color = C(340,0,.3), size = 100; 285 - pos = {x=0.5, y=1}, ofs = {x = -hbofs, y=-48 - bpad}; 286 - dir = 1; 287 - align = {x=-1, y=-1}; 288 - } 289 - self.hud.elt.stamina = self:attachStatBar { 290 - name = 'stamina', stat = basicStat 'stamina'; 291 - color = C(60,0,.2), size = 100; 292 - pos = {x=0.5, y=1}, ofs = {x = -hbofs, y=-24 - bpad}; 293 - dir = 1; 294 - align = {x=-1, y=-1}; 295 - } 296 - self.hud.elt.bat = self:attachStatBar { 297 - name = 'battery', stat = batteryLookup; 298 - color = C(190,0,.2), size = 100; 299 - pos = {x=0.5, y=1}, ofs = {x = hbofs - 4, y=-48 - bpad}; 300 - dir = 0; 301 - align = {x=1, y=-1}; 302 - } 303 - self.hud.elt.psi = self:attachStatBar { 304 - name = 'psi', stat = basicStat 'psi'; 305 - color = C(320,0,.2), size = 100; 306 - pos = {x=0.5, y=1}, ofs = {x = hbofs - 4, y=-24 - bpad}; 307 - dir = 0; 308 - align = {x=1, y=-1}; 309 - } 310 - self.hud.elt.time = self:attachTextBox { 311 - name = 'time'; 312 - align = {x=0, y=1}; 313 - pos = {x=0.5, y=1}; 314 - ofs = {x=0,y=-95}; 315 - text = function(user) 316 - local cal = starsoul.world.time.calendar[user.pref.calendar] 317 - return cal.time(minetest.get_timeofday()) 318 - end; 319 - } 320 - self.hud.elt.temp = self:attachMeter { 321 - name = 'temp'; 322 - align = {x=1, y=-1}; 323 - pos = {x=0, y=1}; 324 - ofs = {x=20, y=-20}; 325 - measure = function(user) 326 - local warm = self:effectiveStat 'warmth' 327 - local n, color if warm < 0 then 328 - n = math.min(100, -warm) 329 - color = lib.color(0.1,0.3,1):lerp(lib.color(0.7, 1, 1), math.min(1, n/50)) 330 - else 331 - n = math.min(100, warm) 332 - color = lib.color(0.1,0.3,1):lerp(lib.color(1, 0, 0), math.min(1, n/50)) 333 - end 334 - local txt = string.format("%s°", math.floor(warm)) 335 - return (n/50), txt, color 336 - end; 337 - } 338 - self.hud.elt.geiger = self:attachMeter { 339 - name = 'geiger'; 340 - align = {x=-1, y=-1}; 341 - pos = {x=1, y=1}; 342 - ofs = {x=-20, y=-20}; 343 - flipX = true; 344 - measure = function(user) 345 - local hot = self:effectiveStat 'irradiation' 346 - local color = self:uiColor():lerp(lib.color(0.3, 1, 0), math.min(1, hot/5)) 347 - local txt = string.format("%sGy", math.floor(hot)) 348 - return (hot/5), txt, color 349 - end; 350 - } 351 - self.hud.elt.crosshair = self:attachImage { 352 - name = 'crosshair '; 353 - tex = ''; 354 - pos = {x=.5, y=.5}; 355 - scale = {x=1,y=1}; 356 - ofs = {x=0, y=0}; 357 - align = {x=0, y=0}; 358 - update = function(user, set) 359 - local imgs = { 360 - off = ''; 361 - nano = 'starsoul-ui-crosshair-nano.png'; 362 - psi = 'starsoul-ui-crosshair-psi.png'; 363 - weapon = 'starsoul-ui-crosshair-weapon.png'; 364 - } 365 - set('text', imgs[self.actMode] or imgs.off) 366 - end; 367 - }; 368 - local hudCenterBG = lib.image 'starsoul-ui-hud-bg.png':colorize(self:uiColor()) 369 - self.hud.elt.bg = self:attachImage { 370 - name = 'hudBg'; 371 - tex = hudCenterBG:render(); 372 - pos = {x=.5, y=1}; 373 - scale = {x=1,y=1}; 374 - ofs = {x=0, y=0}; 375 - align = {x=0, y=-1}; 376 - z = -1; 377 - update = function(user, set) 378 - set('text', hudAdjustBacklight(hudCenterBG):render()) 379 - end; 380 - }; 381 - end; 382 - onModeChange = function(self, oldMode, silent) 383 - self.hud.elt.crosshair.update() 384 - if not silent then 385 - local sfxt = { 386 - off = 'starsoul-mode-off'; 387 - nano = 'starsoul-mode-nano'; 388 - psi = 'starsoul-mode-psi'; 389 - weapon = 'starsoul-mode-weapon'; 390 - } 391 - local sfx = self.actMode and sfxt[self.actMode] or sfxt.off 392 - self:suitSound(sfx) 393 - end 394 - end; 395 - actModeSet = function(self, mode, silent) 396 - if not mode then mode = 'off' end 397 - local oldMode = self.actMode 398 - self.actMode = mode 399 - self:onModeChange(oldMode, silent) 400 - if mode ~= oldMode then 401 - starsoul.ui.setupForUser(self) 402 - end 403 - end; 404 - deleteHUD = function(self) 405 - for name, e in pairs(self.hud.elt) do 406 - self:hud_delete(e.id) 407 - end 408 - end; 409 - updateHUD = function(self) 410 - for name, e in pairs(self.hud.elt) do 411 - if e.update then e.update() end 412 - end 413 - end; 414 - clientInfo = function(self) 415 - return minetest.get_player_information(self.name) 416 - end; 417 - onSignup = function(self) 418 - local meta = self.entity:get_meta() 419 - local inv = self.entity:get_inventory() 420 - -- 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 421 - inv:set_size('main', 6) -- carried items and tools. main hotbar. 422 - 423 - inv:set_size('starsoul_suit', 1) -- your environment suit (change at wardrobe) 424 - inv:set_size('starsoul_cfg', 1) -- the item you're reconfiguring / container you're accessing 425 - 426 - local scenario 427 - for _, e in pairs(starsoul.world.scenario) do 428 - if e.id == starsoul.world.defaultScenario then 429 - scenario = e break 430 - end 431 - end assert(scenario) 432 - self.persona = starsoul.world.species.birth(scenario.species, scenario.speciesVariant, self.entity) 433 - self.persona.name = self.entity:get_player_name() -- a reasonable default 434 - self.persona.background = starsoul.world.defaultScenario 435 - self:pushPersona() 436 - 437 - local gifts = scenario.startingItems 438 - local inv = self.entity:get_inventory() 439 - inv:set_stack('starsoul_suit', 1, starsoul.item.mk(gifts.suit, self, {gift=true})) 440 - self:getSuit():establishInventories(self.entity) 441 - 442 - local function giveGifts(name, list) 443 - if inv:get_size(name) > 0 then 444 - for i, e in ipairs(list) do 445 - inv:add_item(name, starsoul.item.mk(e, self, {gift=true})) 446 - end 447 - end 448 - end 449 - 450 - giveGifts('starsoul_suit_bat', gifts.suitBatteries) 451 - giveGifts('starsoul_suit_chips', gifts.suitChips) 452 - giveGifts('starsoul_suit_guns', gifts.suitGuns) 453 - giveGifts('starsoul_suit_ammo', gifts.suitAmmo) 454 - giveGifts('starsoul_suit_canisters', gifts.suitCans) 455 - 456 - giveGifts('main', gifts.carry) 457 - 458 - self:reconfigureSuit() 459 - 460 - -- i feel like there has to be a better way 461 - local cx = math.random(-500,500) 462 - local startPoint 463 - repeat local temp = -100 464 - local cz = math.random(-500,500) 465 - local cy = minetest.get_spawn_level(cx, cz) 466 - if cy then 467 - startPoint = vector.new(cx,cy,cz) 468 - temp = starsoul.world.climate.eval(startPoint,.5,.5).surfaceTemp 469 - end 470 - if cx > 10000 then break end -- avoid infiniloop in pathological conditions 471 - until temp > -2 472 - self.entity:set_pos(startPoint) 473 - meta:set_string('starsoul_spawn', startPoint:to_string()) 474 - end; 475 - onDie = function(self, reason) 476 - local inv = self.entity:get_inventory() 477 - local where = self.entity:get_pos() 478 - local function dropInv(lst) 479 - local l = inv:get_list(lst) 480 - for i, o in ipairs(l) do 481 - if o and not o:is_empty() then 482 - minetest.item_drop(o, self.entity, where) 483 - end 484 - end 485 - inv:set_list(lst, {}) 486 - end 487 - dropInv 'main' 488 - dropInv 'starsoul_suit' 489 - self:statDelta('psi', 0, 'death', true) 490 - self:statDelta('hunger', 0, 'death', true) 491 - self:statDelta('thirst', 0, 'death', true) 492 - self:statDelta('fatigue', 0, 'death', true) 493 - self:statDelta('stamina', 0, 'death', true) 494 - self:updateSuit() 495 - end; 496 - onRespawn = function(self) 497 - local meta = self.entity:get_meta() 498 - self.entity:set_pos(vector.from_string(meta:get_string'starsoul_spawn')) 499 - self:updateSuit() 500 - return true 501 - end; 502 - onJoin = function(self) 503 - local me = self.entity 504 - local meta = me:get_meta() 505 - self:pullPersona() 506 - 507 - -- formspec_version and real_coordinates are apparently just 508 - -- completely ignored here 509 - me:set_formspec_prepend [[ 510 - bgcolor[#00000000;true] 511 - style_type[button,button_exit,image_button,item_image_button;border=false] 512 - style_type[button;bgimg=starsoul-ui-button-hw.png;bgimg_middle=8;content_offset=0,-2] 513 - style_type[button:hovered;bgimg=starsoul-ui-button-hw-hover.png;bgimg_middle=8] 514 - style_type[button:pressed;bgimg=starsoul-ui-button-hw-press.png;bgimg_middle=8;content_offset=0,1] 515 - ]] 516 - local hotbarSlots = me:get_inventory():get_size 'main'; 517 --- local slotTex = 'starsoul-ui-slot.png' 518 --- local hbimg = string.format('[combine:%sx128', 128 * hotbarSlots) 519 --- for i = 0, hotbarSlots-1 do 520 --- hbimg = hbimg .. string.format(':%s,0=%s', 128 * i, slotTex) 521 --- end 522 - --me:hud_set_hotbar_image(lib.image(hbimg):colorize(self:uiColor()):fade(.36):render()) 523 --- me:hud_set_hotbar_selected_image(lib.image(slotTex):colorize(self:uiColor()):render()) 524 - me:hud_set_hotbar_image('[fill:1x24:0,0:' .. self:uiColor():fade(.1):hex()) 525 - me:hud_set_hotbar_selected_image( 526 - '[fill:1x24,0,0:' .. self:uiColor():fade(.4):hex() .. '^[fill:1x1:0,23:#ffFFffff' 527 - ) 528 - me:hud_set_hotbar_itemcount(hotbarSlots) 529 - me:hud_set_flags { 530 - hotbar = true; 531 - healthbar = false; 532 - breathbar = false; 533 - basic_debug = false; 534 - crosshair = false; 535 - } 536 - -- disable builtin crafting 537 - local inv = me:get_inventory() 538 - inv:set_size('craftpreview', 0) 539 - inv:set_size('craftresult', 0) 540 - inv:set_size('craft', 0) 541 - 542 - me:set_stars { 543 - day_opacity = 0.7; 544 - } 545 - me:set_sky { 546 - sky_color = { 547 - day_sky = '#a7c2cd', day_horizon = '#ddeeff'; 548 - dawn_sky = '#003964', dawn_horizon = '#87ebff'; 549 - night_sky = '#000000', night_horizon = '#000E29'; 550 - fog_sun_tint = '#72e4ff'; 551 - fog_moon_tint = '#2983d0'; 552 - fog_tint_type = 'custom'; 553 - }; 554 - fog = { -- not respected?? 555 - -- TODO make this seasonal & vary with weather 556 - fog_distance = 40; 557 - fog_start = 0.3; 558 - }; 559 - } 560 - me:set_sun { 561 - texture = 'starsoul-sun.png'; 562 - sunrise = 'sunrisebg.png^[hsl:180:1:.7'; 563 - tonemap = 'sun_tonemap.png^[hsl:180:1:.7'; 564 - scale = 0.8; 565 - } 566 - me:set_lighting { 567 - shadows = { 568 - intensity = .5; 569 - }; 570 - exposure = { 571 - luminance_max = 3.0; 572 - speed_dark_bright = 0.5; 573 - speed_bright_dark = 1.0; 574 - }; 575 - volumetric_light = { 576 - strength = 0.3; 577 - }; 578 - } 579 - me:set_eye_offset(nil, vector.new(3,-.2,10)) 580 - -- TODO set_clouds speed in accordance with wind 581 - starsoul.world.species.setupEntity(me, self.persona) 582 - starsoul.ui.setupForUser(self) 583 - self:createHUD() 584 - self:updateSuit() 585 - end; 586 - suitStack = function(self) 587 - return self.entity:get_inventory():get_stack('starsoul_suit', 1) 588 - end; 589 - suitSound = function(self, sfx) 590 - -- trigger a sound effect from the player's suit computer 591 - minetest.sound_play(sfx, {object=self.entity, max_hear_distance=4}, true) 592 - end; 593 - suitPowerStateSet = function(self, state, silent) 594 - -- necessary to enable reacting to power state changes 595 - -- e.g. to play sound effects, display warnings 596 - local os 597 - self:forSuit(function(s) 598 - os=s:powerState() 599 - s:powerStateSet(state) 600 - end) 601 - if state == 'off' then 602 - if self.actMode == 'nano' or self.actMode == 'weapon' then 603 - self:actModeSet('off', silent) 604 - end 605 - end 606 - if not silent and os ~= state then 607 - local sfx 608 - if state == 'off' then 609 - sfx = 'starsoul-power-down' 610 - elseif os == 'off' then 611 - sfx = 'starsoul-power-up' 612 - elseif state == 'powerSave' or os == 'powerSave' then 613 - sfx = 'starsoul-configure' 614 - end 615 - if sfx then self:suitSound(sfx) end 616 - end 617 - end; 618 - species = function(self) 619 - return starsoul.world.species.index[self.persona.species] 620 - end; 621 - updateBody = function(self) 622 - local adornment = {} 623 - local suitStack = self:suitStack() 624 - if suitStack and not suitStack:is_empty() then 625 - local suit = suitStack:get_definition()._starsoul.suit 626 - suit.adorn(adornment, suitStack, self.persona) 627 - end 628 - starsoul.world.species.updateTextures(self.entity, self.persona, adornment) 629 - end; 630 - updateSuit = function(self) 631 - self:updateBody() 632 - local inv = self.entity:get_inventory() 633 - local sst = suitStore(self:suitStack()) 634 - if self:naked() then 635 - starsoul.type.suit.purgeInventories(self.entity) 636 - if self.actMode == 'nano' or self.actMode == 'weapon' then 637 - self:actModeSet 'off' 638 - end 639 - else 640 - local suit = self:getSuit() 641 - suit:establishInventories(self.entity) 642 - 643 - if self:suitCharge() <= 0 then 644 - self:suitPowerStateSet 'off' 645 - end 646 - end 647 - self:updateHUD() 648 - end; 649 - reconfigureSuit = function(self) 650 - -- and here's where things get ugly 651 - -- you can't have an inventory inside another item. to hack around this, 652 - -- we use the player as the location of the suit inventories, and whenever 653 - -- there's a change in the content of these inventories, this function is 654 - -- called to serialize those inventories out to the suit stack 655 - if self:naked() then return end 656 - local suit = self:getSuit() 657 - suit:onReconfigure(self.entity:get_inventory()) 658 - self:setSuit(suit) 659 - 660 - -- reconfiguring the suit can affect player abilities: e.g. removing 661 - -- / inserting a chip with a minimap program 662 - end; 663 - getSuit = function(self) 664 - local st = self:suitStack() 665 - if st:is_empty() then return nil end 666 - return starsoul.type.suit(st) 667 - end; 668 - setSuit = function(self, suit) 669 - self.entity:get_inventory():set_stack('starsoul_suit', 1, suit.item) 670 - end; 671 - changeSuit = function(self, ...) 672 - self:setSuit(...) 673 - self:updateSuit() 674 - end; 675 - forSuit = function(self, fn) 676 - local s = self:getSuit() 677 - if fn(s) ~= false then 678 - self:setSuit(s) 679 - end 680 - end; 681 - suitPowerCapacity = function(self) -- TODO optimize 682 - if self:naked() then return 0 end 683 - return self:getSuit():powerCapacity() 684 - end; 685 - suitCharge = function(self) -- TODO optimize 686 - if self:naked() then return 0 end 687 - return self:getSuit():powerLeft() 688 - end; 689 - suitDrawCurrent = function(self, power, time, whatFor, min) 690 - if self:naked() then return 0,0 end 691 - local inv = self.entity:get_inventory() 692 - local bl = inv:get_list('starsoul_suit_bat') 693 - local supply = 0 694 - local wasteHeat = 0 --TODO handle internally 695 - for slot, ps in ipairs(bl) do 696 - if not ps:is_empty() then 697 - local p, h = starsoul.mod.electronics.dynamo.drawCurrent(ps, power - supply, time) 698 - supply = supply + p 699 - wasteHeat = wasteHeat + h 700 - if power-supply <= 0 then break end 701 - end 702 - end 703 - if min and supply < min then return 0,0 end 704 - inv:set_list('starsoul_suit_bat', bl) 705 - self:reconfigureSuit() 706 - if whatFor then 707 - -- TODO display power use icon 708 - end 709 - return supply, wasteHeat 710 - end; 711 - naked = function(self) 712 - return self:suitStack():is_empty() 713 - end; 714 - onPart = function(self) 715 - starsoul.liveUI [self.name] = nil 716 - starsoul.activeUI [self.name] = nil 717 - starsoul.activeUsers[self.name] = nil 718 - end; 719 - openUI = function(self, id, page, ...) 720 - local ui = assert(starsoul.interface.db[id]) 721 - ui:open(self, page, ...) 722 - end; 723 - onRespond = function(self, ui, state, resp) 724 - ui:action(self, state, resp) 725 - end; 726 - 727 - updateWeather = function(self) 728 - end; 729 - 730 - canInteract = function(self, with) 731 - return true; -- TODO 732 - end; 733 - 734 - trigger = function(self, which, how) 735 - --print('trigger', which, dump(how)) 736 - local p 737 - local wld = self.entity:get_wielded_item() 738 - if which == 'maneuver' then 739 - p = self.power.maneuver 740 - elseif which == 'retarget' then 741 - self.action.prog = {} 742 - elseif wld and not wld:is_empty() then 743 - local wdef = wld:get_definition() 744 - if wdef._starsoul and wdef._starsoul.tool then 745 - p = {tool = wdef._starsoul.tool} 746 - end 747 - elseif self.actMode ~= 'off' then 748 - p = self.power[self.actMode][which] 749 - end 750 - if p == nil then return false end 751 - local ctx, run = { 752 - how = how; 753 - } 754 - if p.chipID then 755 - local inv = self.entity:get_inventory() 756 - local chips = inv:get_list 'starsoul_suit_chips' 757 - for chSlot, ch in pairs(chips) do 758 - if ch and not ch:is_empty() then 759 - local d = starsoul.mod.electronics.chip.read(ch) 760 - if d.uuid == p.chipID then 761 - local pgm = assert(d.files[p.pgmIndex], 'file missing for ability') 762 - ctx.file = starsoul.mod.electronics.chip.fileHandle(ch, p.pgmIndex) 763 - ctx.saveChip = function() 764 - inv:set_slot('starsoul_suit_chips', chSlot, ch) 765 - end 766 - local sw = starsoul.item.sw.db[pgm.body.pgmId] 767 - run = assert(sw.run, 'missing run() for active software ability ' .. pgm.body.pgmId) 768 - break 769 - end 770 - end 771 - end 772 - else 773 - error('bad ability pointer ' .. dump(p)) 774 - end 775 - if run then 776 - run(self, ctx) 777 - return true 778 - end 779 - return false 780 - end; 781 - give = function(self, item) 782 - local inv = self.entity:get_inventory() 783 - local function is(grp) 784 - return minetest.get_item_group(item:get_name(), grp) ~= 0 785 - end 786 - -- TODO notif popups 787 - if is 'specialInventory' then 788 - if is 'powder' then 789 - if self:naked() then return item end 790 - local cans = inv:get_list 'starsoul_suit_canisters' 791 - if cans and next(cans) then for i, st in ipairs(cans) do 792 - local lst = string.format('starsoul_canister_%u_elem', i) 793 - item = inv:add_item(lst, item) 794 - if item:is_empty() then break end 795 - end end 796 - self:forSuit(function(x) x:pushCanisters(inv) end) 797 - end 798 - return item 799 - else 800 - return inv:add_item('main', item) 801 - end 802 - end; 803 - thrustUpon = function(self, item) 804 - local r = self:give(st) 805 - if not r:is_empty() then 806 - return minetest.add_item(self.entity:get_pos(), r) 807 - end 808 - end; 809 - }; 810 -} 811 - 812 -local biointerval = 3.0 813 -starsoul.startJob('starsoul:bio', biointerval, function(delta) 814 - for id, u in pairs(starsoul.activeUsers) do 815 - 816 - end 817 -end) 818 - 819 -local cbit = { 820 - up = 0x001; 821 - down = 0x002; 822 - left = 0x004; 823 - right= 0x008; 824 - jump = 0x010; 825 - manv = 0x020; 826 - snk = 0x040; 827 - dig = 0x080; 828 - put = 0x100; 829 - zoom = 0x200; 830 -} 831 --- this is the painful part 832 -minetest.register_globalstep(function(delta) 833 - local doNothing,mustInit,mustHalt = 0,1,2 834 - for id, user in pairs(starsoul.activeUsers) do 835 - local ent = user.entity 836 - local bits = ent:get_player_control_bits() 837 - 838 - local function what(b) 839 - if bit.band(bits, b) ~= 0 and bit.band(user.action.bits, b) == 0 then 840 - return mustInit 841 - elseif bit.band(bits, b) == 0 and bit.band(user.action.bits, b) ~= 0 then 842 - return mustHalt 843 - else return doNothing end 844 - end 845 - local skipBits = 0 846 - if user.action.bits ~= bits then 847 - local mPrimary = what(cbit.dig) 848 - local mSecondary = what(cbit.put) 849 - if mPrimary == mustInit then -- ENGINE-BUG 850 - user.action.tgt = {type='nothing'} 851 - user.action.prog = {} 852 - elseif mPrimary == mustHalt then 853 - user:trigger('primary', {state='halt'}) 854 - end 855 - if mSecondary == mustHalt then 856 - user:trigger('secondary', {state='halt'}) 857 - end 858 - end 859 - --bits = bit.band(bits, bit.bnot(skipBits)) 860 - if bit.band(bits, cbit.dig)~=0 then 861 - user:trigger('primary', {state='prog', delta=delta}) 862 - end 863 - if bit.band(bits, cbit.put)~=0 then 864 - user:trigger('secondary', {state='prog', delta=delta}) 865 - end 866 - user.action.bits = bits 867 - -- ENGINE-BUG: dig and put are not handled equally in the 868 - -- engine. it is possible for the put bit to get stuck on 869 - -- if the key is hammered while the player is not moving. 870 - -- the bit will release as soon as the player looks or turns 871 - -- nonetheless this is obnoxious 872 - end 873 -end)
Deleted mods/starsoul/world.lua version [20212b373d].
1 -local lib = starsoul.mod.lib 2 -local world = starsoul.world 3 - 4 -function world.date() 5 - local days = minetest.get_day_count() 6 - local year = math.floor(days / world.planet.orbit); 7 - local day = days % world.planet.orbit; 8 - return { 9 - year = year, day = day; 10 - season = day / world.planet.orbit; 11 - } 12 -end 13 -local lerp = lib.math.lerp 14 - 15 -local function gradient(grad, pos) 16 - local n = #grad 17 - if n == 1 then return grad[1] end 18 - local op = pos*(n-1) 19 - local idx = math.floor(op) 20 - local t = op-idx 21 - return lerp(t, grad[1 + idx], grad[2 + idx]) 22 -end 23 - 24 -local altitudeCooling = 10 / 100 25 - 26 --- this function provides the basis for temperature calculation, 27 --- which is performed by adding this value to the ambient temperature, 28 --- determined by querying nearby group:heatSource items in accordance 29 --- with the inverse-square law 30 -function world.climate.eval(pos, tod, season) 31 - local data = minetest.get_biome_data(pos) 32 - local biome = world.ecology.biomes.db[minetest.get_biome_name(data.biome)] 33 - local heat, humid = data.heat, data.humidity 34 - tod = tod or minetest.get_timeofday() 35 - heat = lerp(math.abs(tod - 0.5)*2, heat, heat + biome.nightTempDelta) 36 - 37 - local td = world.date() 38 - heat = heat + gradient(biome.seasonalTemp, season or td.season) 39 - if pos.y > 0 then 40 - heat = heat - pos.y*altitudeCooling 41 - end 42 - 43 - return { 44 - surfaceTemp = heat; 45 - waterTemp = heat + biome.waterTempDelta; 46 - surfaceHumid = humid; 47 - } 48 -end 49 - 50 -local vdsq = lib.math.vdsq 51 -function world.climate.temp(pos) --> irradiance at pos in W 52 - local cl = world.climate.eval(pos) 53 - local radCenters = starsoul.region.radiator.store:get_areas_for_pos(pos, false, true) 54 - local irradiance = 0 55 - for _,e in pairs(radCenters) do 56 - local rpos = minetest.string_to_pos(e.data) 57 - local rdef = assert(minetest.registered_nodes[assert(minetest.get_node(rpos)).name]) 58 - local rc = rdef._starsoul.radiator 59 - local r_max = rc.radius(rpos) 60 - 61 - local dist_sq = vdsq(rpos,pos) 62 - if dist_sq <= r_max^2 then 63 - -- cheap bad way 64 - -- if minetest.line_of_sight(rpos,pos) then 65 - -- 66 - -- expensive way 67 - local obstruct = 0 68 - local ray = Raycast(rpos, pos, true, true) 69 - for p in ray do 70 - if p.type == 'node' then obstruct = obstruct + 1 end 71 - end 72 - 73 - if obstruct < 4 then 74 - local power, customFalloff = rc.radiate(rpos, pos) 75 - -- okay this isn't the real inverse square law but i 76 - -- couldn't figure out a better way to simplify the 77 - -- model without checking an ENORMOUS number of nodes 78 - -- maybe someone else who isn't completely 79 - -- mathtarded can do better. 80 - if not customFalloff then 81 - power = power * (1 - (dist_sq / ((r_max+1)^2))) 82 - end 83 - power = power * (1 - (obstruct/5)) 84 - irradiance = irradiance + power 85 - end 86 - end 87 - end 88 - return irradiance + cl.surfaceTemp 89 -end 90 - 91 -world.ecology.biomes.foreach('starsoul:biome-gen', {}, function(id, b) 92 - b.def.name = id 93 - minetest.register_biome(b.def) 94 -end) 95 - 96 -world.ecology.biomes.link('starsoul:steppe', { 97 - nightTempDelta = -30; 98 - waterTempDelta = 0; 99 - -- W Sp Su Au W 100 - seasonalTemp = {-50, -10, 5, 5, -20, -50}; 101 - def = { 102 - node_top = 'starsoul:greengraze', depth_top = 1; 103 - node_filler = 'starsoul:soil', depth_filler = 4; 104 - node_riverbed = 'starsoul:sand', depth_riverbed = 4; 105 - y_min = 0; 106 - y_max = 512; 107 - heat_point = 10; 108 - humidity_point = 30; 109 - }; 110 -}) 111 - 112 -world.ecology.biomes.link('starsoul:ocean', { 113 - nightTempDelta = -35; 114 - waterTempDelta = 5; 115 - seasonalTemp = {0}; -- no seasonal variance 116 - def = { 117 - y_max = 3; 118 - y_min = -512; 119 - heat_point = 15; 120 - humidity_point = 50; 121 - node_top = 'starsoul:sand', depth_top = 1; 122 - node_filler = 'starsoul:sand', depth_filler = 3; 123 - }; 124 -}) 125 - 126 -local toward = lib.math.toward 127 -local hfinterval = 1.5 128 -starsoul.startJob('starsoul:heatflow', hfinterval, function(delta) 129 - 130 - -- our base thermal conductivity (κ) is measured in °C/°C/s. say the 131 - -- player is in -30°C weather, and has an internal temperature of 132 - -- 10°C. then: 133 - -- κ = .1°C/C/s (which is apparently 100mHz) 134 - -- Tₚ = 10°C 135 - -- Tₑ = -30°C 136 - -- d = Tₑ − Tₚ = -40°C 137 - -- ΔT = κ×d = -.4°C/s 138 - -- our final change in temperature is computed as tΔC where t is time 139 - local kappa = .05 140 - for name,user in pairs(starsoul.activeUsers) do 141 - local tr = user:species().tempRange 142 - local t = starsoul.world.climate.temp(user.entity:get_pos()) 143 - local insul = 0 144 - local naked = user:naked() 145 - local suitDef 146 - if not naked then 147 - suitDef = user:suitStack():get_definition() 148 - insul = suitDef._starsoul.suit.temp.insulation 149 - end 150 - 151 - local warm = user:effectiveStat 'warmth' 152 - local tSafeMin, tSafeMax = tr.survivable[1], tr.survivable[2] 153 - local tComfMin, tComfMax = tr.comfort[1], tr.comfort[2] 154 - 155 - local tDelta = (kappa * (1-insul)) * (t - warm) * hfinterval 156 - local tgt = warm + tDelta 157 - 158 - -- old logic: we move the user towards the exterior temperature, modulated 159 - -- by her suit insulation. 160 - --local tgt = toward(warm, t, hfinterval * thermalConductivity * (1 - insul)) 161 - 162 - if not naked then 163 - local suit = user:getSuit() 164 - local suitPower = suit:powerState() 165 - local suitPowerLeft = suit:powerLeft() 166 - if suitPower ~= 'off' then 167 - local coilPower = 1.0 168 - local st = suitDef._starsoul.suit.temp 169 - if suitPower == 'powerSave' and (tgt >= tSafeMin and tgt <= tSafeMax) then coilPower = 0.5 end 170 - if tgt < tComfMin and st.maxHeat > 0 then 171 - local availPower = user:suitDrawCurrent(st.heatPower*coilPower, hfinterval) 172 - tgt = tgt + (availPower / st.heatPower) * st.maxHeat * coilPower * hfinterval 173 - end 174 - if tgt > tComfMax and st.maxCool > 0 then 175 - local availPower = user:suitDrawCurrent(st.coolPower*coilPower, hfinterval) 176 - tgt = tgt - (availPower / st.coolPower) * st.maxCool * coilPower * hfinterval 177 - end 178 - end 179 - end 180 - 181 - user:statDelta('warmth', tgt - warm) -- dopey but w/e 182 - 183 - warm = tgt -- for the sake of readable code 184 - 185 - if warm < tSafeMin or warm > tSafeMax then 186 - local dv 187 - if warm < tSafeMin then 188 - dv = math.abs(warm - tSafeMin) 189 - else 190 - dv = math.abs(warm - tSafeMax) 191 - end 192 - -- for every degree of difference you suffer 2 points of damage/s 193 - local dmg = math.ceil(dv * 2) 194 - user:statDelta('health', -dmg) 195 - end 196 - end 197 -end)
Modified src/lore.ct from [8a0d33ccae] to [176ec871ae].
1 -# starsoul lore 1 +# starlit lore 2 2 ! spoilers ahoy! 3 3 4 4 ## Thinking Few 5 5 the Galaxy teems with life, but only one in a trillion of its creatures is fully sophont, with a soul and mentality of their own. 6 6 7 7 ### Lesser Races 8 -the majority of the Thinking Few are held in thrall to the Starsouled, their null psionic potential locking them out of the higher levels of civilizational power and attainment. 8 +the majority of the Thinking Few are held in thrall to the Starlit, their null psionic potential locking them out of the higher levels of civilizational power and attainment. 9 9 10 10 #### Humans 11 11 The weeds of the galactic flowerbed. Humans are one of the Lesser Races, excluded from the ranks of the Greater Races 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 all but 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. 12 12 13 13 #### Kruthandi 14 14 The Kruthandi are a race of four-armed marsupialoids, with the rough body proportions of meerkats, though much larger. 15 15 16 16 Geographically, they occupy a small net of systems linked only through their home system. Unable to accept the reality that their lack of psionics doomed them to a subservient, sublight role, the Kruthandi indulged in a brief, petulant, and entirely futile war with a Su'ikuri state[^lizard-war], and then retreated to their home web to sulk. Access to their space is tightly controlled, and psionic races are absolutely barred from their worlds, even the mere Greater Races, tho this is hilariously unenforcible. In practice they are particularly vulnerable to psicrime – when you ban psionics, only criminals will have mind powers. The Kruthandi are generally viewed with pity and amusement as a pathetic basket case of a civilization, and engage in little intercourse with the broader galaxy. 17 17 18 18 lizard-war: The Su'ikuri sovereign in question was of a decidedly philosophical bent and was commendably gentle dealing with the upstarts, seeming more bemused than angered by the attack. He took a few of their leaders as battle trophies and spanked the remainder out of his system. There were no casualties on either side. 19 19 20 -However, the Kruthandi are an ancient and sophisticated civilization, and there is much more to their culture than mere wallowing in victimhood (though this certainly has a special place in the Kruthandi heart). Their hate-crush on the Starsouled Races has lead to an utter obsession with metric technology, and the Kruthandi have, by sheer brute force and fanatic tenacity, built a surprisingly sophisticated gravitics industry. While this makes little economic sense, it soothes the Kruthandic psyche. 20 +However, the Kruthandi are an ancient and sophisticated civilization, and there is much more to their culture than mere wallowing in victimhood (though this certainly has a special place in the Kruthandi heart). Their hate-crush on the Starlit Races has lead to an utter obsession with metric technology, and the Kruthandi have, by sheer brute force and fanatic tenacity, built a surprisingly sophisticated gravitics industry. While this makes little economic sense, it soothes the Kruthandic psyche. 21 21 22 22 #### Qir 23 23 a race of religious fanatics cleaving to no particular faith, Qir enthusiastically adopt and syncretize new religions as fast as Heaven can churn out prophets. their fanaticism seems to be a cultural evolution to compensate for exceptionally weak mememmune systems; unable to properly critique new ideas, the Qir need to outsource much of their reasoning to systems of sacred commandments, ideally those developed by species who know how to deal with memes. good old-fashioned natural selection does the rest. 24 24 25 25 ### Greater Races 26 -a handful of species have souls that are just barely capable of developing external psionic channels. the respected vassals of the Starsouled, they can touch the spirits of others, sending messages and reading thoughtforms -- or attacking with torturous sensations and ruinous emotion. however, their power does not extend to the physical universe; they rely entirely on their Starsouled masters for access to faster-than-light travel. 26 +a handful of species have souls that are just barely capable of developing external psionic channels. the respected vassals of the Starlit, they can touch the spirits of others, sending messages and reading thoughtforms -- or attacking with torturous sensations and ruinous emotion. however, their power does not extend to the physical universe; they rely entirely on their Starlit masters for access to faster-than-light travel. 27 27 28 -### Starsouled 29 -the unquestioned lords of space and time, the Starsouled Races are those lucky few to have evolved cognitive architectures that allow a soul to reach its full development potential, progressing from merely cogitating about the universe to manipulating it directly with innate power. seemingly as a consequence of the necessary neural architecture, the Starsouled are all ferociously intelligent -- IQs below 150 are unheard of. however, they are marred by a proportional tendency toward mental instability and psychosis. 28 +### Starlit Races 29 +the unquestioned lords of space and time, the Starlit Races are those lucky few to have evolved cognitive architectures that allow a soul to reach its full development potential, progressing from merely cogitating about the universe to manipulating it directly with innate power. seemingly as a consequence of the necessary neural architecture, the Starlit are all ferociously intelligent -- IQs below 150 are unheard of. however, they are marred by a proportional tendency toward mental instability and psychosis. 30 30 31 31 their civilizations are known as the Powers. 32 32 33 33 #### Su'ikuri 34 34 (sg. Su'ikutra, adj. Su'ikuruk) 35 35 a reptilian race of artists, aesthetes, hedonists, monks, and philosophers, the Su'ikuri are an idle, contemplative, and aristocratic people whose massive psionic sophistication numbers them among the Powers -- much to the annoyance of the Eluthrai. as any adult has the requisite level of finesse and raw power to tweak individual alleles throughout the whole body of a living organism, the Su'ikuri are a race of peerless organgineers. they eschew "dead" hylotechnology, and insist on using biotech wherever remotely practicable. 36 36 37 37 their 'spacecraft' are massive tree-like organisms housing whole ecosystems, propelled and protected against radiation by the psionic power of their crew. sometimes they are equipped with technology produced by a vassal race, but only when unavoidable. 38 38 39 39 Su'ikuri generally use Lesser Races for manual labor, and Greater Races to overseer these laborers. whether these are paid and respected laborers or outright slaves depends entirely on the ethos of the local civilization. 40 40 41 -Su'ikuruk society is strictly feudal, with a hierarchy based on psionic skill and wit. virtually all conflict is resolved with either a polite, prolonged philosophical debate (the Su'ikuruk version of a duel) or a brute psionic struggle -- the party overpowered by the greater psion is compelled to submit totally, and may achieve freedom only by strengthening their soul to the point of being able to overpower their former superior. even other members of other Starsouled Races can wind up enslaved this way. 41 +Su'ikuruk society is strictly feudal, with a hierarchy based on psionic skill and wit. virtually all conflict is resolved with either a polite, prolonged philosophical debate (the Su'ikuruk version of a duel) or a brute psionic struggle -- the party overpowered by the greater psion is compelled to submit totally, and may achieve freedom only by strengthening their soul to the point of being able to overpower their former superior. even other members of other Starlit Races can wind up enslaved this way. 42 42 43 43 Su'ikuri relations with the Eluthrai are, as a rule, extremely strained, and many small but high-energy wars have been fought between the Su'ikuruk Powers and the Corcordance. 44 44 45 -a motivated and talented Su'ikutra can reach astropathic levels of psionic power with only a century of practice, something otherwise unheard of among Starsouls. 45 +a motivated and talented Su'ikutra can reach astropathic levels of psionic power with only a century of practice, something otherwise unheard of among the Starlit. 46 46 47 47 #### Usukwinya 48 48 (sg. Usukwinti, adj. Usuk) 49 49 50 50 the Usukwinya, known affectionately as the "Tradebirds", are a psionic avian race. their adults range in height from 1 to 1.3m, and 20-30kg in weight. they lack precise manipulatory appendages and are physically weak, forcing them to rely heavily on their psionics for everyday dasks. however, they remain fully capable of flight even without psionic assistance. 51 51 52 52 culturally, the Usukwinya are a mercantile race. they exert their power not through physical force, but through obscene wealth, garnered by selling their painstakingly [^drm value-engineered] technologies to the highest bidder. some rare few Usukwinya will also rent out their psionics, even to the Lesser Races (if they can afford their prodigious fees). 53 53 drm: Usukwinya DRM is some of the most powerful in the Reach. 54 54 55 -the enthusiastic capitalism of the Usukwinya is tempered by a hardwired loyalty drive so powerful that before First Contact they had no concept of contract law. they are also noteworthy for having never fought a war among themselves, and seem utterly unwilling to resort to force unless physically provoked. their governments all work diligently to maintain peace among the other races, and the somewhat absurd spectacle of a Starsouled diplomat gently negotiating with two hysterical Lesser ambassadors tends to crop up when two factions the Usukwinya have good relationships with threaten war on one another. (the Eluthrai find this patently ridiculous, and prefer to maintain peace with a judicious application of preventive violence. several Usukwinya-organized peace conferences have dissolved when the Eluthrai summarily shattered the offending governments without a note of forewarning.) 55 +the enthusiastic capitalism of the Usukwinya is tempered by a hardwired loyalty drive so powerful that before First Contact they had no concept of contract law. they are also noteworthy for having never fought a war among themselves, and seem utterly unwilling to resort to force unless physically provoked. their governments all work diligently to maintain peace among the other races, and the somewhat absurd spectacle of a Starlit diplomat gently negotiating with two hysterical Lesser ambassadors tends to crop up when two factions the Usukwinya have good relationships with threaten war on one another. (the Eluthrai find this patently ridiculous, and prefer to maintain peace with a judicious application of preventive violence. several Usukwinya-organized peace conferences have dissolved when the Eluthrai summarily shattered the offending governments without a note of forewarning.) 56 56 57 57 their willingness to trade with or work for anyone and everyone mean that the Usukwinya are the main reason the Lesser Races have any ability to travel beyond the Great Web. however, Usuk astropaths are very selective: they will not use their powers to help their employers to commit acts of aggression, no matter how much you offer to pay them. many human captains chafe under the restrictions of their Tradebird astropaths, but short of relativistic travel, they have no other way to escape the confines of the Web. 58 58 59 59 Usukwinya get along with everyone and make excellent diplomats, so long as they can restrain their urge to make a quick profit at the first available opportunity. 60 60 61 61 #### Eluthrai 62 62 (sg. Eluthra) 63 63 64 -the greatest and most aloof of the living Starsouled races, the Eluthrai are a race of psionic warrior-poets. they are slim humanoids with subtly iridescent dark grey skin, lustrous white hair, red~violet eyes, and tapered, expressive ears. they are very few in number, with no more than ten thousand Eluthrai in the entire galaxy. it is popularly said, not without some reason, that the only reason the Eluthrai haven't conquered that entire galaxy is because they don't care to. 64 +the greatest and most aloof of the living Starlit races, the Eluthrai are a race of psionic warrior-poets. they are slim humanoids with subtly iridescent dark grey skin, lustrous white hair, red~violet eyes, and tapered, expressive ears. they are very few in number, with no more than ten thousand Eluthrai in the entire galaxy. it is popularly said, not without some reason, that the only reason the Eluthrai haven't conquered that entire galaxy is because they don't care to. 65 65 66 -natural immortals with a very low reproduction rate, the Eluthrai all have an exceptionally long-term worldview that frequently confounds mortal morals. little about them is known for certain, and they interact with the non-Starsouled very rarely, usually to deliver some form of unforeseen intervention that they typically refuse to explain. 66 +natural immortals with a very low reproduction rate, the Eluthrai all have an exceptionally long-term worldview that frequently confounds mortal morals. little about them is known for certain, and they interact openly with the non-Starlit very rarely, usually to deliver some form of unforeseen intervention that they typically refuse to explain. 67 67 68 68 the Eluthrai see themselves as the masters of the Thinking Few, and spare no expense in ensuring they maintain their position. to them, the Great Web is a garden, a place to be tended carefully and protected from the storms outside. their civilization is dedicated to combatting extra-Web threats -- in particular, guarding against the possible return of the Forevanishers. they have cultivated a strong & highly spiritual warrior ethos in consequence 69 69 70 70 within the Web itself, they mostly by clandestine means, using "Agents" selected from the Greater (and, occasionally, Lesser) Races to act on their behalf. in general they act directly only when overwhelming force is required, such as to exclude the Kuradoqshe, or to excise Suldibrand. 71 71 72 72 it is known that the Eluthrai are of great intelligence: a 200pt IQ makes you a laughable simpleton in their eyes. it is estimated that the average individual has an IQ of 290, close to the theoretical maximum where organized intelligence dissolves into a sea of blinding psychosis. consequently, they are very conservative and cautious of new ideas; their culture emphasises skepticism and avoiding rash action. 73 73 ................................................................................ 81 81 82 82 Eluthrai have two genders, and dramatic dimorphism. their women are much more intelligent than their men, and proportionately more prone to psychosis. traditionally most of their societies were matriarchal -- with the brains and psionic brawn to overpower the males, there was very little that could keep the Clan-Queens from exerting their will. the First Philosopher recognized however that the lesser intelligence of men was useful, due to their stabler psyches, and proposed patriarchy as part of his solution. this was made possible through a previously obscure psionic technique known as quelling -- with enough intimate exposure to the soul of another, it becomes possible to negate their psionics, even if that psion is stronger. 83 83 84 84 among the modern Concordant Eluthrai, a female's mate is expected to be capable of quelling her psionics. female Eluthrai generally cooperate with the practice; it is difficult to learn to quell someone who actively tries to stymie you. it is widely understood, however, that the female sex will only cooperate so long as their men rule wisely: in a celebrated case on a far-flung world where the men began to take too many liberties, the women carefully organized to overpower one another's mates and instituted a compensatory subjugation of the local males for a proportionate period, which the Philosopher-King himself agreed was the just and proper punishment. 85 85 86 86 Eluthran technology can be tidily summarized as "uncompromising." the Eluthrai demand excellence from their machines as much as one another, and will happily incur absurd expense to eliminate the smallest flaw. languishing for thousands of years of under such attentions, abetted by the most ferocious living intelligence to be found in the Reach, has created a technological ecosystem that is succeeded in its phenomenal capabilities only by its preposterous expense. an Eluthran computer requires about ten times the time and a hundred times the energy input to fabricate as does a conventional human computer, despite the vast gulf in manufacturing capabilities, but you can be [!damn] sure it'll still be working in ten million years' time. 87 87 88 -they enjoy a post-scarcity economy that is the envy of even the other Starsouled. 88 +they enjoy a post-scarcity economy that is the envy of even the other Starlit. 89 89 90 90 very few of the Greater Races, and vanishingly few of the Lesser Races, have ever had the opportunity to visit an Eluthran world. they admit only their mysterious Agents and the occasional individual subjected to penal servitude for some great crime against the interests of "the Garden". 91 91 92 92 while even their females are not nearly the psionic match of the Su'ikuri, they are nonetheless vastly powerful. their psionics are not as seamlessly integrated into their nervous system as in Usuk neurology, and deploying their power is consequently more effortful, requiring some concentration and intent, but they can bring far more energy to bear. where the Usukwinya have finesse and the Su'ikuri have brute power, the Eluthrai have technique: they can do things with their psionics that the other races never would have imagined possible. 93 93 94 94 the Eluthrai put a great deal of effort into foremodeling the universe, seeking to predict future events and trends. their models are far from infallible, but reliable enough that some supersitious Lessers have come to believe that Eluthran psionics can be used to see the future. intelligence-gathering is in the modern era the prime industry of that exalted race, second only to warfare. 95 95 96 96 ### Forevanished Ones 97 97 98 98 #### Forevanishers 99 99 a mysterious race or power thought to be responsible for exterminating a number of Forevanished Ones. no one knows for sure that they have themselves Vanished -- for all we know, they could be one of the contemporary Powers… 100 100 101 101 #### firstborn 102 -the architects of the Great Web, the Firstborn were the first civilization of which traces remain within the Reach. their psionic and scientific mastery, developed over ten million years of energetic industry, reached levels even the greatest of the modern Starsouled Races cannot hope to equal. while their Continuum Bridges form the backbone of the Lesser civilizations, little else of their manufacture seems to have survived into the present era. 102 +the architects of the Great Web, the Firstborn were the first civilization of which traces remain within the Reach. their psionic and scientific mastery, developed over ten million years of energetic industry, reached heights even the greatest of the modern Starlit Races cannot hope to equal. while their Continuum Bridges form the backbone of the Lesser civilizations, little else of their manufacture seems to have survived into the present era. 103 103 104 104 practically every trace of their existence that does remain is scored with weaponsfire. 105 105 106 106 the artifact which tore an external channel into the player's soul in the backstory is of Firstborn design and uncertain purpose. Commune scholars had hitherto ascertained only that it was a machine seemingly able to produce psionic effects -- something that [!should] have been a contradiction in terms. 107 107 108 108 ! possible plot: the Firstborn devised a means to produce psionic effects with carefully cultured neurons embedded in a mechanical matrix. essentially creating slaved psionic AI dedicated to a single purpose. while these rudimentary consciousnesses, barely fit to called souls, did not suffer, some other race or perhaps a faction among the Firstborn seems to have taken exception to the practice of trapping souls in metal, outside the thread of reincarnation, and exterminated the civilization to prevent its heresy. 109 -! if this was a real proper AAA game the player would face some epic choice to release the secret and free the Lesser Races (or a subset of them) from the dominion of the Starsouled, turning psionics into a mere commodity; keeping the secret but placing her power at the disposal of the Commune (or Empire, in return for elevation to the ranks of nobility); or joining the Eluthrai as an honorary citizen in recompense for keeping the secret. alas, i don't have a budget. 109 +! if this was a real proper AAA game the player would face some epic choice to release the secret and free the Lesser Races (or a subset of them) from the dominion of the Starlit, turning psionics into a mere commodity; keeping the secret but placing her power at the disposal of the Commune (or Empire, in return for elevation to the ranks of nobility); or joining the Eluthrai as an honorary citizen in recompense for keeping the secret. alas, i don't have a budget. 110 110 111 111 ## psionics 112 112 the ability of the soul to extend its will beyond the confines of its substrate. this power is technically defined as the presence of one or more external psionic channels in the structure of the soul. such a channel allows the soul to direct excess numina into other souls or into the numon field of the physical universe. 113 113 114 114 the delicate interlink between soul and body relies on quantum phenomena, and only carbon-based life seems able to maintain such a link. silicon-based intelligence is at most a simulacrum of true thought. 115 115 116 116 ### farspeakers
Modified src/sem.ct from [5017b1c243] to [770f4fca7d].
1 -# starsoul semantics 1 +# starlit semantics 2 2 3 3 ## tool levels 4 4 some items have a particular level requirement to enable digging. in general, level 0 should be used for things that can be dug by hand 5 5 nanotech can dismantle anything up to level 2 6 6 7 7 * sediment 8 8 ** 0: sand, dirt (diggable with hand)
Modified src/sfx/conf.lua from [7fad9bea38] to [ee9864718b].
16 16 17 17 local function envarg(n, default) 18 18 local v = os.getenv(n) 19 19 if v then return tonumber(v) end 20 20 return default 21 21 end 22 22 23 -local baseSeed = envarg('starsoul_sfx_vary', 420691917) 24 -local variants = envarg('starsoul_sfx_variants', 4) 25 -local fmt = os.getenv 'starsoul_sfx_fmt' or 'wav' 23 +local baseSeed = envarg('starlit_sfx_vary', 420691917) 24 +local variants = envarg('starlit_sfx_variants', 4) 25 +local fmt = os.getenv 'starlit_sfx_fmt' or 'wav' 26 26 27 27 local rules = {} 28 28 local all = {} 29 29 if fmt ~= 'ogg' then table.insert(rules, 30 - 'out/starsoul-%.ogg: %.' .. fmt .. '\n' .. 30 + 'out/starlit-%.ogg: %.' .. fmt .. '\n' .. 31 31 '\tffmpeg -y -i "$<" "$@"\n') 32 32 end 33 33 local function rule(out, file, seed) 34 34 if fmt == 'ogg' then 35 35 table.insert(rules, string.format( 36 - 'out/starsoul-%s.ogg: %s.csd digital.orc physical.orc psi.orc dqual.inc\n' .. 36 + 'out/starlit-%s.ogg: %s.csd digital.orc physical.orc psi.orc dqual.inc\n' .. 37 37 '\tcsound --omacro:seed=%d --format=ogg -o "$@" "$<"\n', 38 38 out, file, 39 39 seed 40 40 )) 41 41 else 42 42 table.insert(rules, string.format( 43 43 '%s.%s: %s.csd digital.orc physical.orc psi.orc dqual.inc\n' .. 44 44 '\tcsound --omacro:seed=%d --format=%s -o "$@" "$<"\n', 45 45 out, fmt, file, 46 46 seed, fmt 47 47 )) 48 48 end 49 - table.insert(all, string.format('out/starsoul-%s.ogg', out)) 49 + table.insert(all, string.format('out/starlit-%s.ogg', out)) 50 50 end 51 51 for _, v in ipairs(polyfx) do 52 52 local bn = v:match '^(.+).n.csd$' 53 53 for i=1, variants do 54 54 rule(bn .. '.' .. tostring(i), bn .. '.n', baseSeed + 4096*i) 55 55 end 56 56 end
Added starlit.ct version [5320fb7a47].
1 +# starlit 2 +[*starlit] is a sci-fi survival game. you play the survivor of a disaster in space, stranded on a just-barely-habitable world with nothing but your escape pod, your survival suit, and some handy new psionic powers that the accident seems to have unlocked in you. scavenge resources, search alien ruins, and build a base where you can survive indefinitely, but be careful: winter approaches, and your starting suit heater is already struggling to keep you alive in the comparatively warm days of "summer". 3 + 4 +## story 5 +### "Imperial Expat" background 6 +about a month ago, you woke up to unexpected good news. your application to join the new Commune colony at Thousand Petal, submitted in a moment of utter existential weariness and almost in jest, was actually accepted. your skillset was a "perfect match" for the budding colony's needs, claimed the Population Control Authority, and you'd earned yourself a free trip to your new home -- on a swanky state transport, no less. 7 + 8 +it took a few discreet threats and bribes from Commune diplomats, but after a week of wrangling with the surly Crown Service for Comings & Goings -- whose bureaucrats seemed outright [!offended] that you actually managed to find a way off that hateful rock -- you secured grudging clearance to depart. you celebrated by telling your slackjawed boss exactly what you thought of him in a meeting room packed with his fellow parasites -- and left House Taladran with a new appreciation for the value of your labor, as the nobs found themselves desperately scrabbling for a replacement on short notice. 9 + 10 +you almost couldn't believe it when the Commune ship -- a sleek, solid piece of engineering whose graceful descent onto the landing pad seemed to sneer at the lurching rattletraps arrayed all around it -- actually showed up. in a daze you handed over your worldly possessions -- all three of them -- to a valet with impeccable manners, and climbed up out of the wagie nightmare into high orbit around your homeworld. the mercenary psion aboard, a preening Usukwinti with her very own luxury suite, tore a bleeding hole in the spacetime metric, and five hundred hopeful souls dove through towards fancied salvation. "sure," you thought to yourself as you slipped into your sleek new nanotech environment suit, itself worth more than the sum total of your earnings on Flame of Unyielding Purification, "life won't be easy -- but damn it, it'll [!mean] something out there." 11 + 12 +a free life on the wild frontier with a nation of comrades to have your back, with the best tech humans can make, fresh, clean water that isn't laced with compliance serum, and -- best of all -- never having to worry about paying rent again. it was too good to be true, you mused. 13 + 14 +clearly, the terrorists who blew up your ship agreed. 15 + 16 +you're still not certain what happened. all you know for sure is that transport was carrying more than just people. in those last hectic moments, you caught a glimpse of something -- maybe machine, maybe artwork, and [!definitely] ancient beyond measure. you've seen abhuman artifacts before in museums, of course; in fact, thanks to a childhood fascination, you can still name all the Elder Races and the Forevanished Ones off the top of your head. 17 + 18 +you have no [!idea] what that [!thing] was or who in the sublimated [!fuck] could possibly have made it. 19 + 20 +but one thing is for certain: your ship wasn't the only thing it ripped open when it blew. because when you woke up in your tiny escape pod beyond the furthest edge of the Reach, circling Farthest Shadow in a suicide orbit, you discovered yourself transformed into something impossible. a contradiction in terms. 21 + 22 +a human psion. 23 + 24 +for years beyond counting, the Starlit species -- three of whom yet live and deign once every so often to notice the Lesser Races -- have held the galaxy in sway through their monopoly on psionic power. of all the Thinking Few, only they are free to wander the distant stars at whim, heedless of the lightspeed barrier. there are no mechanisms for FTL travel or reactionless drive without that innate power, and, they assured us, psionic channels are fixed in the soul. your species either has the power or it doesn't. 25 + 26 +[!liars], all of them. 27 + 28 +are there other survivors? have they been similarly changed? what was that artifact and who were those terrorists? important questions, all, but they pale in comparison with the most important one: 29 + 30 +how the fuck are you going to survive the next 24 hours? 31 + 32 +## engine 33 +starlit is developed against a bleeding-edge version of minetest. it definitely won't work with anything older than v5.7, and ideally you should build directly from master. 34 + 35 +starlit is best used with a patched version of minetest, though it is compatible with vanilla. the recommended patches are: 36 + 37 +* [>p11143 11143] - fix third-person view orientation 38 + 39 + p11143: https://github.com/minetest/minetest/pull/11143.diff 40 + 41 +### shadows 42 +i was delighted to see dynamic shadows land in minetest, and i hope the implementation will eventually mature. however, as it stands, there are severe issues with shadows that make them essentially incompatible with complex meshes like the Starlit player character meshes. for the sake of those who don't mind these glitches, Starlit does enable shadows, but i unfortunately have to recommend that you disable them until the minetest devs get their act together on this feature. 43 + 44 +## gameplay 45 +starlit is somewhat unusual in how it uses the minetest engine. it's a voxel game but not of the minecraft variety. 46 + 47 +### controls 48 +summon your Suit Interface by pressing the [*E] / [*Inventory] key. this will allow you to move items around in your inventory, but more importantly, it also allows you select or configure your Interaction Mode. 49 + 50 +the top three buttons can be used to select (or deactivate) an Interaction Mode. an Interaction Mode can be configured by pressing the button immediately below. the active Interaction Mode controls the behavior of the mouse buttons when no item is selected in the hotbar. 51 + 52 +the modes are: 53 + * [*Fabrication]: use your suit's onboard nanotech to directly manipulate matter in the world. 54 + ** [*Left Button] / [*Punch]: activate your primary nano program. by default this activates your nanoshredder, reducing the targeted object to monatomic powder and storing the resulting elements in your suit for use with the Matter Compiler 55 + ** [*Right Button] / [*Place]: activate your secondary nano program. by default, if your suit compiler can generate sufficiently large objects, creates a block of the configured type directly in the world without having to build it by hand 56 + * [*Psionics]: wield the awesome, if illicitly obtained, power of mind over matter 57 + ** [*Left Button] / [*Punch]: perform your selected Primary Power 58 + ** [*Right Button] / [*Place]: perform your selected Secondary Power 59 + * [*Weapon]: military-grade suits have built-in hardpoints for specialized weapon systems that draw directly on your suit battery for power (and in the most exotic cases, your psi reserve) 60 + ** [*Left Button] / [*Punch]: fire your primary weapon 61 + ** [*Right Button] / [*Place]: fire your offhand weapon / summon your shield 62 + 63 +to use a tool, select it in the hotbar. even if an Interaction Mode is active, the tool will take priority. press [*Left Button] / [*Punch] to use the tool on a block; for instance, to break a stone with a jackhammer. to configure a tool or use its secondary functions, if any, press [*Right Button] / [*Place]. 64 + 65 +hold [*Aux1] to activate your selected Maneuver. by default this is Sprint, which will consume stamina to allow you to run much faster. certain suits offer the Flight ability, which allows slow, mid-range flight. you can also unlock the psionic ability Lift, which allows very rapid flight but consumes psi at a prodigious rate. 66 + 67 +you can only have one Maneuver active at a time, whether this is a Psi Maneuver (consuming psi), a Suit Maneuver (consuming battery), or a Body Maneuver (consuming stamina). Maneuvers are activated in their respective panel. 68 + 69 +### psionics 70 +there are four types of psionic abilities: Manual, Maneuver, Ritual, and Contextual. 71 + 72 +you can assign two Manual abilities at any given time and access them with the mouse buttons in Psionics mode. 73 + 74 +you can select a Psi Maneuver in the Psionics panel and activate it by holding [*Aux1]. 75 + 76 +a Ritual is triggered directly from the psionics menu. as the name implies, these are complex, powerful abilities that require large amounts of Psi and time to meditate before they trigger, and any interruption will cancel the ability (though it will not restore any lost psi). the most famous Ritual is of course Conjoin Metric, which Starlit astropaths use in conjunction with powerful amplifiers to perform long-distance FTL jumps -- but without centuries of dedication to the art, the best you can hope for if you manage to learn this storied power is to move yourself a few kilometers. 77 + 78 +a Contextual ability is triggered in a specific situation, usually by interacting with a certain kind of object. Contextual abilities often require specialized equipment, to the point that many Starlit practitioners maintain their own Psionics Lab.
Deleted starsoul.ct version [81ada79a74].
1 -# starsoul 2 -[*starsoul] is a sci-fi survival game. you play the survivor of a disaster in space, stranded on a just-barely-habitable world with nothing but your escape pod, your survival suit, and some handy new psionic powers that the accident seems to have unlocked in you. scavenge resources, search alien ruins, and build a base where you can survive indefinitely, but be careful: winter approaches, and your starting suit heater is already struggling to keep you alive in the comparatively warm days of "summer". 3 - 4 -## story 5 -about a month ago, you woke up to unexpected good news. your application to join the new Commune colony at Thousand Petal, submitted in a moment of utter existential weariness and almost in jest, was actually accepted. your skillset was a "perfect match" for the budding colony's needs, claimed the Population Control Authority, and you'd earned yourself a free trip to your new home -- on a swanky state transport, no less. 6 - 7 -it took a few discreet threats and bribes from Commune diplomats, but after a week of wrangling with the surly Crown Service for Comings & Goings -- whose bureaucrats seemed outright [!offended] that you actually managed to find a way off that hateful rock -- you secured grudging clearance to depart. you celebrated by telling your slackjawed boss exactly what you thought of him in a meeting room packed with his fellow parasites -- and left Circumsolar Megascale with a new appreciation for the value of your labor, as they found themselves desperately scrabbling for a replacement on short notice. 8 - 9 -you almost couldn't believe it when the Commune ship -- a sleek, solid piece of engineering whose graceful descent onto the landing pad seemed to sneer at the lurching rattletraps arrayed all around it -- actually showed up. in a daze you handed over your worldly possessions -- all three of them -- to a valet with impeccable manners, and climbed up out of the wagie nightmare into high orbit around your homeworld. the mercenary psion aboard, a preening Usukwinti with her very own luxury suite, tore a bleeding hole in the spacetime metric, and five hundred hopeful souls dove through towards fancied salvation. "sure," you thought to yourself as you slipped into your sleek new nanotech environment suit, itself worth more than the sum total of your earnings on Flame of Unyielding Purification, "life won't be easy -- but damn it, it'll [!mean] something out there." 10 - 11 -a free life on the wild frontier with a nation of comrades to have your back, with the best tech humans can make, fresh, clean water that isn't laced with compliance serum, and -- best of all -- never having to worry about paying rent again. it was too good to be true, you mused. 12 - 13 -clearly, the terrorists who blew up your ship agreed. 14 - 15 -you're still not certain what happened. all you know for sure is that transport was carrying more than just people. in those last hectic moments, you caught a glimpse of something -- maybe machine, maybe artwork, and [!definitely] ancient beyond measure. you've seen artifacts before in museums, of course; in fact, thanks to a childhood fascination, you can still name all the Elder Races and the Forevanished Ones off the top of your head. 16 - 17 -you have no [!idea] what that [!thing] was or who in the sublimated [!fuck] could possibly have made it. 18 - 19 -but one thing is for certain: your ship wasn't the only thing it ripped open when it blew. because when you woke up in your tiny escape pod beyond the furthest edge of the Reach, circling Farthest Shadow in a suicide orbit, you discovered yourself transformed into something impossible. a contradiction in terms. 20 - 21 -a human psionic. 22 - 23 -for years beyond counting, the Starsouled species -- three of whom yet live and deign once every so often to notice the Lesser Races -- have held the galaxy in sway through their monopoly on psionic power. of all the Thinking Few, only they are free to wander the distant stars at whim, heedless of the lightspeed barrier. there are no mechanisms for FTL travel or reactionless drive without that innate power, and, they assured us, psionic channels are fixed in the soul. your species either has the power or it doesn't. 24 - 25 -[!liars], all of them. 26 - 27 -are there other survivors? have they been similarly changed? what was that artifact and who were those terrorists? important questions, all, but they pale in comparison with the most important one: 28 - 29 -how the fuck are you going to survive the next 24 hours? 30 - 31 -## engine 32 -starsoul is developed against a bleeding-edge version of minetest. it definitely won't work with anything older than v5.7, and ideally you should build directly from master. 33 - 34 -starsoul is best used with a patched version of minetest, though it is compatible with vanilla. the recommended patches are: 35 - 36 -* [>p11143 11143] - fix third-person view orientation 37 - 38 - p11143: https://github.com/minetest/minetest/pull/11143.diff 39 - 40 -### shadows 41 -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 Starsouled player character meshes. for the sake of those who don't mind these glitches, Starsoul does enable shadows, but i unfortunately have to recommend that you disable them until the minetest devs get their act together on this feature. 42 - 43 -## gameplay 44 -starsoul is somewhat unusual in how it uses the minetest engine. it's a voxel game but not of the minecraft variety. 45 - 46 -### controls 47 -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. 48 - 49 -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. 50 - 51 -the modes are: 52 - * [*Fabrication]: use your suit's onboard nanotech to directly manipulate matter in the world. 53 - ** [*Left Button] / [*Punch]: activate your primary nano program. by default this activates your nanoshredder, reducing the targeted object to monatomic powder and storing the resulting elements in your suit for use with the Matter Compiler 54 - ** [*Right Button] / [*Place]: activate your secondary nano program. by default, if your suit compiler can generate sufficiently large objects, creates a block of the configured type directly in the world without having to build it by hand 55 - * [*Psionics]: wield the awesome, if illicitly obtained, power of mind over matter 56 - ** [*Left Button] / [*Punch]: perform your selected Primary Power 57 - ** [*Right Button] / [*Place]: perform your selected Secondary Power 58 - * [*Weapon]: military-grade suits have built-in hardpoints for specialized weapon systems that draw directly on your suit battery for power (and in the most exotic cases, your psi reserve) 59 - ** [*Left Button] / [*Punch]: fire your primary weapon 60 - ** [*Right Button] / [*Place]: fire your offhand weapon / summon your shield 61 - 62 -to use a tool, select it in the hotbar. even if an Interaction Mode is active, the tool will take priority. press [*Left Button] / [*Punch] to use the tool on a block; for instance, to break a stone with a jackhammer. to configure a tool or use its secondary functions, if any, press [*Right Button] / [*Place]. 63 - 64 -hold [*Aux1] to activate your selected Maneuver. by default this is Sprint, which will consume stamina to allow you to run much faster. certain suits offer the Flight ability, which allows slow, mid-range flight. you can also unlock the psionic ability Lift, which allows very rapid flight but consumes psi at a prodigious rate. 65 - 66 -you can only have one Maneuver active at a time, whether this is a Psi Maneuver (consuming psi), a Suit Maneuver (consuming battery), or a Body Maneuver (consuming stamina). Maneuvers are activated in their respective panel. 67 - 68 -### psionics 69 -there are four types of psionic abilities: Manual, Maneuver, Ritual, and Contextual. 70 - 71 -you can assign two Manual abilities at any given time and access them with the mouse buttons in Psionics mode. 72 - 73 -you can select a Psi Maneuver in the Psionics panel and activate it by holding [*Aux1]. 74 - 75 -a Ritual is triggered directly from the psionics menu. as the name implies, these are complex, powerful abilities that require large amounts of Psi and time to meditate before they trigger, and any interruption will cancel the ability (though it will not restore any lost psi). the most famous Ritual is of course Conjoin Metric, which Starsouled astropaths use in conjunction with powerful amplifiers to perform long-distance FTL jumps -- but without centuries of dedication to the art, the best you can hope for if you manage to learn this storied power is to move yourself a few kilometers. 76 - 77 -a Contextual ability is triggered in a specific situation, usually by interacting with a certain kind of object. Contextual abilities often require specialized equipment, to the point that many Starsouled practitioners maintain their own Psionics Lab.