-- [ʞ] user.lua
-- ~ lexi hale <lexi@hale.su>
-- © EUPL v1.2
-- ? defines the starlit.type.user class, which is
-- the main interface between the game world and the
-- client. it provides for initial signup and join,
-- managing the HUD, skinning the player model,
-- effecting weather changes, etc.
local lib = starlit.mod.lib
local function hudAdjustBacklight(img)
local night = math.abs(minetest.get_timeofday() - .5) * 2
local opacity = night*0.8
return img:fade(opacity)
end
local userStore = lib.marshal.metaStore {
persona = {
key = 'starlit:persona';
type = starlit.store.persona;
};
}
local suitStore = starlit.store.suitMeta
starlit.type.user = lib.class {
name = 'starlit:user';
construct = function(ident)
local name, luser
if type(ident) == 'string' then
name = ident
luser = minetest.get_player_by_name(name)
else
luser = ident
name = luser:get_player_name()
end
return {
entity = luser;
name = name;
hud = {
elt = {};
};
tree = {};
action = {
bits = 0; -- for control deltas
prog = {}; -- for recording action progress on a node; reset on refocus
tgt = {type='nothing'};
sfx = {};
fx = {};
};
actMode = 'off';
power = {
nano = {primary = nil, secondary = nil};
weapon = {primary = nil, secondary = nil};
psi = {primary = nil, secondary = nil};
maneuver = nil;
};
pref = {
calendar = 'commune';
};
}
end;
__index = {
pullPersona = function(self)
-- if later records are added in public updates, extend this function to merge them
-- into one object
local s = userStore(self.entity)
self.persona = s.read 'persona'
self.pheno = starlit.world.species.pheno(self.persona.species, self.persona.speciesVariant)
end;
pushPersona = function(self)
local s = userStore(self.entity)
s.write('persona', self.persona)
end;
uiColor = function(self) return lib.color {hue=238,sat=.5,lum=.5} end;
statDelta = function(self, stat, d, cause, abs)
local dt = self.persona.statDeltas
local base
if abs then
local min, max
min, max, base = self:statRange(stat)
if d == true then d = max
elseif d == false then d = min end
end
if stat == 'health' then
self.entity:set_hp(abs and d or (self.entity:get_hp() + d), cause)
elseif stat == 'breath' then
self.entity:set_breath(abs and d or (self.entity:get_breath() + d))
else
if abs then
dt[stat] = d - base
else
dt[stat] = dt[stat] + d
end
self:pushPersona()
end
self:updateHUD()
-- TODO trigger relevant animations?
end;
lookupSpecies = function(self)
return starlit.world.species.lookup(self.persona.species, self.persona.speciesVariant)
end;
phenoTrait = function(self, trait, dflt)
-- local s,v = self:lookupSpecies()
-- return v.traits[trait] or s.traits[trait] or 0
return self.pheno:trait(trait, dflt)
end;
statRange = function(self, stat) --> min, max, base
return starlit.world.species.statRange(
self.persona.species, self.persona.speciesVariant, stat)
end;
effectiveStat = function(self, stat)
local val
local min, max, base = self:statRange(stat)
if stat == 'health' then
val = self.entity:get_hp()
elseif stat == 'breath' then
val = self.entity:get_breath()
else
val = base + self.persona.statDeltas[stat] or 0
end
local d = max - min
return val, (val - min) / d
end;
damageModifier = function(self, kind, amt)
if kind == 'bluntForceTrauma' then
local std = self:phenoTrait 'sturdiness'
if std < 0 then
amt = amt / 1+std
else
amt = amt * 1-std
end
end
return amt
end;
attachImage = function(self, def)
local user = self.entity
local img = {}
img.id = user:hud_add {
type = 'image';
text = def.tex;
scale = def.scale;
alignment = def.align;
position = def.pos;
offset = def.ofs;
z_index = def.z;
}
if def.update then
img.update = function()
def.update(user, function(prop, val)
user:hud_change(img.id, prop, val)
end, def)
end
end
return img
end;
attachMeter = function(self, def)
local luser = self.entity
local m = {}
local w = def.size or 80
local szf = w / 80
local h = szf * 260
m.meter = luser:hud_add {
type = 'image';
scale = {x = szf, y = szf};
alignment = def.align;
position = def.pos;
offset = def.ofs;
z_index = def.z or 0;
}
local cx = def.ofs.x + (w/2)*def.align.x
local cy = def.ofs.y + (h/2)*def.align.y
local oy = cy + h/2 - 42
-- this is so fucking fragile holy fuck
m.readout = luser:hud_add {
type = 'text';
scale = {x = w, y = h};
size = szf;
style = 4;
position = def.pos;
alignment = {x=0,0};
offset = {x = cx, y = oy};
z_index = (def.z or 0)+1;
number = 0xffffff;
}
m.destroy = function()
luser:hud_remove(m.meter)
luser:hud_remove(m.readout)
end
m.update = function()
local v,txt,color,txtcolor = def.measure(luser,def)
v = math.max(0, math.min(1, v))
local n = math.floor(v*16) + 1
local img = hudAdjustBacklight(lib.image('starlit-ui-meter.png'))
:colorize(color or def.color)
if def.flipX then
img = img:transform 'FX'
end
img = img:render()
img = img .. '^[verticalframe:17:' .. tostring(17 - n)
luser:hud_change(m.meter, 'text', img)
if txt then
luser:hud_change(m.readout, 'text', txt)
end
if txtcolor then
luser:hud_change(m.readout, 'number', txtcolor:hex())
end
end
return m
end;
attachTextBox = function(self, def)
local luser = self.entity
local box = {}
box.id = luser:hud_add {
type = 'text';
text = '';
alignment = def.align;
number = def.color and def.color:int24() or 0xFFffFF;
scale = def.bound;
size = {x = def.size, y=0};
style = def.style;
position = def.pos;
offset = def.ofs;
}
box.update = function()
local text, color = def.text(self, box, def)
luser:hud_change(box.id, 'text', text)
if color then
luser:hud_change(box.id, 'number', color:int24())
end
end
return box
end;
attachStatBar = function(self, def)
local luser = self.entity
local bar = {}
local img = lib.image 'starlit-ui-bar.png'
local colorized = img
if type(def.color) ~= 'function' then
colorized = colorized:shift(def.color)
end
bar.id = luser:hud_add {
type = 'statbar';
position = def.pos;
offset = def.ofs;
name = def.name;
text = colorized:render();
text2 = img:tint{hue=0, sat=-1, lum = -0.5}:fade(0.5):render();
number = def.size;
item = def.size;
direction = def.dir;
alignment = def.align;
size = {x=4,y=24};
}
bar.update = function()
local sv, sf = def.stat(self, bar, def)
luser:hud_change(bar.id, 'number', def.size * sf)
if type(def.color) == 'function' then
local clr = def.color(sv, luser, sv, sf)
luser:hud_change(bar.id, 'text', img:tint(clr):render())
end
end
return bar, {x=3 * def.size, y=16} -- x*2??? what
end;
createHUD = function(self)
local function basicStat(statName)
return function(user, bar)
return self:effectiveStat(statName)
end
end
local function batteryLookup(user)
local max = user:suitPowerCapacity()
if max == 0 then return 0, 0 end
local ch = user:suitCharge()
return (ch/max)*100, ch/max
end
local function C(h,s,l) return {hue=h,sat=s,lum=l} end
local hbofs = (1+self.entity:hud_get_hotbar_itemcount()) * 25
local bpad = 8
self.hud.elt.health = self:attachStatBar {
name = 'health', stat = basicStat 'health';
color = C(340,0,.3), size = 100;
pos = {x=0.5, y=1}, ofs = {x = -hbofs, y=-48 - bpad};
dir = 1;
align = {x=-1, y=-1};
}
self.hud.elt.stamina = self:attachStatBar {
name = 'stamina', stat = basicStat 'stamina';
color = C(60,0,.2), size = 100;
pos = {x=0.5, y=1}, ofs = {x = -hbofs, y=-24 - bpad};
dir = 1;
align = {x=-1, y=-1};
}
self.hud.elt.bat = self:attachStatBar {
name = 'battery', stat = batteryLookup;
color = C(190,0,.2), size = 100;
pos = {x=0.5, y=1}, ofs = {x = hbofs - 4, y=-48 - bpad};
dir = 0;
align = {x=1, y=-1};
}
self.hud.elt.psi = self:attachStatBar {
name = 'psi', stat = basicStat 'psi';
color = C(320,0,.2), size = 100;
pos = {x=0.5, y=1}, ofs = {x = hbofs - 4, y=-24 - bpad};
dir = 0;
align = {x=1, y=-1};
}
self.hud.elt.time = self:attachTextBox {
name = 'time';
align = {x=0, y=1};
pos = {x=0.5, y=1};
ofs = {x=0,y=-95};
text = function(user)
local cal = starlit.world.time.calendar[user.pref.calendar]
return cal.time(minetest.get_timeofday())
end;
}
self.hud.elt.temp = self:attachMeter {
name = 'temp';
align = {x=1, y=-1};
pos = {x=0, y=1};
ofs = {x=20, y=-20};
measure = function(user)
local warm = self:effectiveStat 'warmth'
local n, color if warm < 0 then
n = math.min(100, -warm)
color = lib.color(0.1,0.3,1):lerp(lib.color(0.7, 1, 1), math.min(1, n/50))
else
n = math.min(100, warm)
color = lib.color(0.1,0.3,1):lerp(lib.color(1, 0, 0), math.min(1, n/50))
end
local txt = string.format("%s°", math.floor(warm))
return (n/50), txt, color
end;
}
self.hud.elt.geiger = self:attachMeter {
name = 'geiger';
align = {x=-1, y=-1};
pos = {x=1, y=1};
ofs = {x=-20, y=-20};
flipX = true;
measure = function(user)
local hot = self:effectiveStat 'irradiation'
local color = self:uiColor():lerp(lib.color(0.3, 1, 0), math.min(1, hot/5))
local txt = string.format("%sGy", math.floor(hot))
return (hot/5), txt, color
end;
}
self.hud.elt.crosshair = self:attachImage {
name = 'crosshair';
tex = '';
pos = {x=.5, y=.5};
scale = {x=1,y=1};
ofs = {x=0, y=0};
align = {x=0, y=0};
update = function(user, set)
local imgs = {
off = '';
nano = 'starlit-ui-crosshair-nano.png';
psi = 'starlit-ui-crosshair-psi.png';
weapon = 'starlit-ui-crosshair-weapon.png';
}
set('text', imgs[self.actMode] or imgs.off)
end;
};
local hudCenterBG = lib.image 'starlit-ui-hud-bg.png':colorize(self:uiColor())
self.hud.elt.bg = self:attachImage {
name = 'hudBg';
tex = hudCenterBG:render();
pos = {x=.5, y=1};
scale = {x=1,y=1};
ofs = {x=0, y=0};
align = {x=0, y=-1};
z = -1;
update = function(user, set)
set('text', hudAdjustBacklight(hudCenterBG):render())
end;
};
end;
-- horrible horrible HACK
setModeHand = function(self)
local inv = self.entity:get_inventory()
local hnd
if self.actMode == 'off'
then hnd = ItemStack('starlit:_hand_dig')
else hnd = ItemStack()
end
inv:set_stack('hand', 1, hnd)
end;
onModeChange = function(self, oldMode, silent)
self.hud.elt.crosshair.update()
if not silent then
local sfxt = {
off = 'starlit-mode-off';
nano = 'starlit-mode-nano';
psi = 'starlit-mode-psi';
weapon = 'starlit-mode-weapon';
}
local sfx = self.actMode and sfxt[self.actMode] or sfxt.off
self:suitSound(sfx)
self:setModeHand()
end
end;
actModeSet = function(self, mode, silent)
if not mode then mode = 'off' end
local oldMode = self.actMode
self.actMode = mode
self:onModeChange(oldMode, silent)
if mode ~= oldMode then
starlit.ui.setupForUser(self)
end
end;
deleteHUD = function(self)
for name, e in pairs(self.hud.elt) do
self:hud_delete(e.id)
end
end;
updateHUD = function(self)
for name, e in pairs(self.hud.elt) do
if e.update then e.update() end
end
end;
clientInfo = function(self)
return minetest.get_player_information(self.name)
end;
onSignup = function(self)
local meta = self.entity:get_meta()
local inv = self.entity:get_inventory()
-- the sizes indicated here are MAXIMA. limitations on e.g. the number of elements that may be carried are defined by your suit and enforced through callbacks and UI generation code, not inventory size
inv:set_size('main', 6) -- carried items and tools. main hotbar.
inv:set_size('hand', 1) -- horrible hack to allow both tools and intrinsics
inv:set_size('starlit_suit', 1) -- your environment suit (change at wardrobe)
inv:set_size('starlit_cfg', 1) -- the item you're reconfiguring / container you're accessing
local scenario
for _, e in pairs(starlit.world.scenario) do
if e.id == starlit.world.defaultScenario then
scenario = e break
end
end assert(scenario)
self.persona = starlit.world.species.birth(scenario.species, scenario.speciesVariant, self.entity)
self.persona.name = self.entity:get_player_name() -- a reasonable default
self.persona.background = starlit.world.defaultScenario
self:pushPersona()
local gifts = scenario.startingItems
local inv = self.entity:get_inventory()
inv:set_stack('starlit_suit', 1, starlit.item.mk(gifts.suit, self, {gift=true}))
self:getSuit():establishInventories(self.entity)
local function giveGifts(name, list)
if inv:get_size(name) > 0 then
for i, e in ipairs(list) do
inv:add_item(name, starlit.item.mk(e, self, {gift=true}))
end
end
end
giveGifts('starlit_suit_bat', gifts.suitBatteries)
giveGifts('starlit_suit_chips', gifts.suitChips)
giveGifts('starlit_suit_guns', gifts.suitGuns)
giveGifts('starlit_suit_ammo', gifts.suitAmmo)
giveGifts('starlit_suit_canisters', gifts.suitCans)
giveGifts('main', gifts.carry)
self:reconfigureSuit()
-- i feel like there has to be a better way
local cx = math.random(-500,500)
local startPoint
repeat local temp = -100
local cz = math.random(-500,500)
local cy = minetest.get_spawn_level(cx, cz)
if cy then
startPoint = vector.new(cx,cy,cz)
temp = starlit.world.climate.eval(startPoint,.5,.5).surfaceTemp
end
if cx > 10000 then break end -- avoid infiniloop in pathological conditions
until temp > -2
self.entity:set_pos(startPoint)
meta:set_string('starlit_spawn', startPoint:to_string())
end;
onDie = function(self, reason)
local inv = self.entity:get_inventory()
local where = self.entity:get_pos()
local function dropInv(lst)
local l = inv:get_list(lst)
for i, o in ipairs(l) do
if o and not o:is_empty() then
minetest.item_drop(o, self.entity, where)
end
end
inv:set_list(lst, {})
end
dropInv 'main'
dropInv 'starlit_suit'
self:statDelta('psi', 0, 'death', true)
self:statDelta('hunger', 0, 'death', true)
self:statDelta('thirst', 0, 'death', true)
self:statDelta('fatigue', 0, 'death', true)
self:statDelta('stamina', 0, 'death', true)
self:updateSuit()
end;
onRespawn = function(self)
local meta = self.entity:get_meta()
self.entity:set_pos(vector.from_string(meta:get_string'starlit_spawn'))
self:updateSuit()
return true
end;
onJoin = function(self)
local me = self.entity
local meta = me:get_meta()
self:pullPersona()
self:setModeHand()
-- formspec_version and real_coordinates are apparently just
-- completely ignored here
me:set_formspec_prepend [[
bgcolor[#00000000;true]
style_type[button,button_exit,image_button,item_image_button;border=false]
style_type[button;bgimg=starlit-ui-button-hw.png;bgimg_middle=8;content_offset=0,-2]
style_type[button:hovered;bgimg=starlit-ui-button-hw-hover.png;bgimg_middle=8]
style_type[button:pressed;bgimg=starlit-ui-button-hw-press.png;bgimg_middle=8;content_offset=0,1]
]]
local hotbarSlots = me:get_inventory():get_size 'main';
-- local slotTex = 'starlit-ui-slot.png'
-- local hbimg = string.format('[combine:%sx128', 128 * hotbarSlots)
-- for i = 0, hotbarSlots-1 do
-- hbimg = hbimg .. string.format(':%s,0=%s', 128 * i, slotTex)
-- end
--me:hud_set_hotbar_image(lib.image(hbimg):colorize(self:uiColor()):fade(.36):render())
-- me:hud_set_hotbar_selected_image(lib.image(slotTex):colorize(self:uiColor()):render())
me:hud_set_hotbar_image('[fill:1x24:0,0:' .. self:uiColor():fade(.1):hex())
me:hud_set_hotbar_selected_image(
'[fill:1x24,0,0:' .. self:uiColor():fade(.4):hex() .. '^[fill:1x1:0,23:#ffFFffff'
)
me:hud_set_hotbar_itemcount(hotbarSlots)
me:hud_set_flags {
hotbar = true;
healthbar = false;
breathbar = false;
basic_debug = false;
crosshair = false;
}
-- disable builtin crafting
local inv = me:get_inventory()
inv:set_size('craftpreview', 0)
inv:set_size('craftresult', 0)
inv:set_size('craft', 0)
me:set_stars {
day_opacity = 0.7;
}
me:set_sky {
sky_color = {
day_sky = '#a7c2cd', day_horizon = '#ddeeff';
dawn_sky = '#003964', dawn_horizon = '#87ebff';
night_sky = '#000000', night_horizon = '#000E29';
fog_sun_tint = '#72e4ff';
fog_moon_tint = '#2983d0';
fog_tint_type = 'custom';
};
fog = { -- not respected??
-- TODO make this seasonal & vary with weather
fog_distance = 40;
fog_start = 0.3;
};
}
me:set_sun {
texture = 'starlit-sun.png';
sunrise = 'sunrisebg.png^[hsl:180:1:.7';
tonemap = 'sun_tonemap.png^[hsl:180:1:.7';
scale = 0.8;
}
me:set_lighting {
shadows = {
intensity = .5;
};
exposure = {
luminance_max = 3.0;
speed_dark_bright = 0.5;
speed_bright_dark = 1.0;
};
volumetric_light = {
strength = 0.3;
};
}
me:set_eye_offset(nil, vector.new(3,-.2,10))
-- TODO set_clouds speed in accordance with wind
starlit.world.species.setupEntity(me, self.persona)
starlit.ui.setupForUser(self)
self:createHUD()
self:updateSuit()
end;
suitStack = function(self)
return self.entity:get_inventory():get_stack('starlit_suit', 1)
end;
suitSound = function(self, sfx)
-- trigger a sound effect from the player's suit computer
minetest.sound_play(sfx, {object=self.entity, max_hear_distance=4}, true)
end;
suitPowerStateSet = function(self, state, silent)
-- necessary to enable reacting to power state changes
-- e.g. to play sound effects, display warnings
local os
self:forSuit(function(s)
os=s:powerState()
s:powerStateSet(state)
end)
if state == 'off' then
if self.actMode == 'nano' or self.actMode == 'weapon' then
self:actModeSet('off', silent)
end
end
if not silent and os ~= state then
local sfx
if state == 'off' then
sfx = 'starlit-power-down'
elseif os == 'off' then
sfx = 'starlit-power-up'
elseif state == 'powerSave' or os == 'powerSave' then
sfx = 'starlit-configure'
end
if sfx then self:suitSound(sfx) end
end
end;
species = function(self)
return starlit.world.species.index[self.persona.species]
end;
updateBody = function(self)
local adornment = {}
local suitStack = self:suitStack()
if suitStack and not suitStack:is_empty() then
local suit = suitStack:get_definition()._starlit.suit
suit.adorn(adornment, suitStack, self.persona)
end
starlit.world.species.updateTextures(self.entity, self.persona, adornment)
end;
updateSuit = function(self)
self:updateBody()
local inv = self.entity:get_inventory()
local sst = suitStore(self:suitStack())
if self:naked() then
starlit.type.suit.purgeInventories(self.entity)
if self.actMode == 'nano' or self.actMode == 'weapon' then
self:actModeSet 'off'
end
else
local suit = self:getSuit()
suit:establishInventories(self.entity)
if self:suitCharge() <= 0 then
self:suitPowerStateSet 'off'
end
end
self:updateHUD()
end;
reconfigureSuit = function(self)
-- and here's where things get ugly
-- you can't have an inventory inside another item. to hack around this,
-- we use the player as the location of the suit inventories, and whenever
-- there's a change in the content of these inventories, this function is
-- called to serialize those inventories out to the suit stack
if self:naked() then return end
local suit = self:getSuit()
suit:onReconfigure(self.entity:get_inventory())
self:setSuit(suit)
-- reconfiguring the suit can affect player abilities: e.g. removing
-- / inserting a chip with a minimap program
end;
getSuit = function(self)
local st = self:suitStack()
if st:is_empty() then return nil end
return starlit.type.suit(st)
end;
setSuit = function(self, suit)
self.entity:get_inventory():set_stack('starlit_suit', 1, suit.item)
end;
changeSuit = function(self, ...)
self:setSuit(...)
self:updateSuit()
end;
forSuit = function(self, fn)
local s = self:getSuit()
if fn(s) ~= false then
self:setSuit(s)
end
end;
suitPowerCapacity = function(self) -- TODO optimize
if self:naked() then return 0 end
return self:getSuit():powerCapacity()
end;
suitCharge = function(self) -- TODO optimize
if self:naked() then return 0 end
return self:getSuit():powerLeft()
end;
suitDrawCurrent = function(self, power, time, whatFor, min)
if self:naked() then return 0,0 end
local inv = self.entity:get_inventory()
local bl = inv:get_list('starlit_suit_bat')
local supply = 0
local wasteHeat = 0 --TODO handle internally
for slot, ps in ipairs(bl) do
if not ps:is_empty() then
local p, h = starlit.mod.electronics.dynamo.drawCurrent(ps, power - supply, time)
supply = supply + p
wasteHeat = wasteHeat + h
if power-supply <= 0 then break end
end
end
if min and supply < min then return 0,0 end
inv:set_list('starlit_suit_bat', bl)
self:reconfigureSuit()
if whatFor then
-- TODO display power use icon
end
return supply, wasteHeat
end;
naked = function(self)
return self:suitStack():is_empty()
end;
onPart = function(self)
starlit.liveUI [self.name] = nil
starlit.activeUI [self.name] = nil
starlit.activeUsers[self.name] = nil
end;
openUI = function(self, id, page, ...)
local ui = assert(starlit.interface.db[id])
ui:open(self, page, ...)
end;
onRespond = function(self, ui, state, resp)
ui:action(self, state, resp)
end;
updateWeather = function(self)
end;
canInteract = function(self, with)
return true; -- TODO
end;
trigger = function(self, which, how)
--print('trigger', which, dump(how))
local p
local wld = self.entity:get_wielded_item()
if which == 'maneuver' then
p = self.power.maneuver
elseif which == 'retarget' then
self.action.prog = {}
elseif wld and not wld:is_empty() then
local wdef = wld:get_definition()
if wdef._starlit and wdef._starlit.tool then
p = {tool = wdef._starlit.tool}
end
elseif self.actMode ~= 'off' then
p = self.power[self.actMode][which]
end
if p == nil then return false end
local ctx, run = {
how = how;
}
if p.chipID then
local inv = self.entity:get_inventory()
local chips = inv:get_list 'starlit_suit_chips'
for chSlot, ch in pairs(chips) do
if ch and not ch:is_empty() then
local d = starlit.mod.electronics.chip.read(ch)
if d.uuid == p.chipID then
local pgm = assert(d.files[p.pgmIndex], 'file missing for ability')
ctx.file = starlit.mod.electronics.chip.fileHandle(ch, p.pgmIndex)
ctx.saveChip = function()
inv:set_slot('starlit_suit_chips', chSlot, ch)
end
local sw = starlit.item.sw.db[pgm.body.pgmId]
run = assert(sw.run, 'missing run() for active software ability ' .. pgm.body.pgmId)
break
end
end
end
else
error('bad ability pointer ' .. dump(p))
end
if run then
run(self, ctx)
return true
end
return false
end;
give = function(self, item)
local inv = self.entity:get_inventory()
local function is(grp)
return minetest.get_item_group(item:get_name(), grp) ~= 0
end
-- TODO notif popups
if is 'specialInventory' then
--[[
if is 'powder' then
if self:naked() then return item end
local cans = inv:get_list 'starlit_suit_canisters'
if cans and next(cans) then for i, st in ipairs(cans) do
local lst = string.format('starlit_canister_%u_elem', i)
item = inv:add_item(lst, item)
if item:is_empty() then break end
end end
self:forSuit(function(x) x:pushCanisters(inv) end)
end
return item
]]
else
return inv:add_item('main', item)
end
end;
thrustUpon = function(self, item)
local r = self:give(st)
if not r:is_empty() then
return minetest.add_item(self.entity:get_pos(), r)
end
end;
consume = function(self, stack, n)
n = n or 1
if n == 0 then n = stack:get_count() end
local fd = stack:take_item(n)
local stats = starlit.world.food.effectiveStats(fd)
return stack
end;
};
}
local biointerval = 3.0
starlit.startJob('starlit:bio', biointerval, function(delta)
for id, u in pairs(starlit.activeUsers) do
local p = u.pheno
local bmr = p:trait 'metabolism' * biointerval
-- TODO apply modifiers
local dehydration = p:trait 'dehydration' * biointerval
-- you dehydrate faster in higher temp
dehydration = dehydration * math.max(1, starlit.world.climate.temp(u.entity:get_pos()) / 10)
u:statDelta('hunger', bmr)
u:statDelta('thirst', dehydration)
end
end)
local cbit = {
up = 0x001;
down = 0x002;
left = 0x004;
right= 0x008;
jump = 0x010;
manv = 0x020;
snk = 0x040;
dig = 0x080;
put = 0x100;
zoom = 0x200;
}
-- this is the painful part
minetest.register_globalstep(function(delta)
local doNothing,mustInit,mustHalt = 0,1,2
for id, user in pairs(starlit.activeUsers) do
local ent = user.entity
local bits = ent:get_player_control_bits()
local function what(b)
if bit.band(bits, b) ~= 0 and bit.band(user.action.bits, b) == 0 then
return mustInit
elseif bit.band(bits, b) == 0 and bit.band(user.action.bits, b) ~= 0 then
return mustHalt
else return doNothing end
end
local skipBits = 0
if user.action.bits ~= bits then
local mPrimary = what(cbit.dig)
local mSecondary = what(cbit.put)
if mPrimary == mustInit then -- ENGINE-BUG
user.action.tgt = {type='nothing'}
user.action.prog = {}
elseif mPrimary == mustHalt then
user:trigger('primary', {state='halt'})
end
if mSecondary == mustHalt then
user:trigger('secondary', {state='halt'})
end
end
--bits = bit.band(bits, bit.bnot(skipBits))
if bit.band(bits, cbit.dig)~=0 then
user:trigger('primary', {state='prog', delta=delta})
end
if bit.band(bits, cbit.put)~=0 then
user:trigger('secondary', {state='prog', delta=delta})
end
user.action.bits = bits
-- ENGINE-BUG: dig and put are not handled equally in the
-- engine. it is possible for the put bit to get stuck on
-- if the key is hammered while the player is not moving.
-- the bit will release as soon as the player looks or turns
-- nonetheless this is obnoxious
end
end)