-- [ʞ] starlit/init.lua
-- ~ lexi hale <lexi@hale.su>
-- ? basic setup, game rules, terrain
-- © EUPL v1.2
local T = minetest.get_translator 'starlit'
-- TODO enforce latest engine version
local mod = {
-- subordinate mods register here
lib = vtlib;
-- 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
}
local lib = mod.lib
starlit = {
ident = minetest.get_current_modname();
mod = mod;
translator = T;
constant = {
light = { --minetest units
dim = 3;
lamp = 7;
bright = 10;
brightest = 14; -- only sun and growlights
};
heat = { -- celsius
freezing = 0;
safe = 4;
overheat = 32;
boiling = 100;
thermalConductivity = 0.05; -- κ
};
rad = {
};
phys = {
--- HACK HACK HAAAAAAAAAAACK
engineGravity = minetest.settings:get('movement_gravity') or 9.81
};
};
activeUsers = {
-- map of username -> user object
};
activeUI = {
-- map of username -> UI context
};
liveUI = {
-- cached subset of activeUI containing those UIs needing live updates
};
interface = lib.registry.mk 'starlit:interface';
item = {
food = lib.registry.mk 'starlit:food';
seed = lib.registry.mk 'starlit:seed';
};
-- complex algorithms that cut across namespaces or don't belong anywhere else
alg = {};
region = {
radiator = {
store = AreaStore();
emitters = {};
};
};
-- standardized effects
fx = {};
type = {};
world = {
defaultScenario = 'starlit_scenario:imperialExpat';
seedbank = lib.math.seedbank(minetest.get_mapgen_setting 'seed');
mineral = lib.registry.mk 'starlit:mineral';
material = { -- raw materials
element = lib.registry.mk 'starlit:element';
-- elements are automatically sorted into the following categories
-- if they match. however, it's possible to have a metal/gas/liquid
-- that *isn't* a pure element, so these need separate registries
-- for alloys and mixtures like steel and water
metal = lib.registry.mk 'starlit:metal';
gas = lib.registry.mk 'starlit:gas';
liquid = lib.registry.mk 'starlit:liquid';
};
ecology = {
plants = lib.registry.mk 'starlit:plants';
trees = lib.registry.mk 'starlit:trees';
biomes = lib.registry.mk 'starlit:biome';
};
climate = {};
scenario = {};
planet = {
gravity = 7.44;
orbit = 189; -- 1 year is 189 days
revolve = 20; -- 1 day is 20 irl minutes
};
fact = lib.registry.mk 'starlit:fact';
time = {
calendar = {
empire = {
name = 'Imperial Regnal Calendar';
year = function(t, long)
local reigns = {
-- if anyone actually makes it to his Honor & Glory Unfailing Persivan I i will be
-- exceptionally flattered
{4, 'Emperor', 'Atavarka', 'the Bold'}; -- died at war
{9, 'Emperor', 'Vatikserka', 'the Unconquered'}; -- died at war
{22, 'Emperor', 'Rusifend', 'the Wise'}; -- poisoned at diplomacy
{61, 'Empress', 'Tafseshendi', 'the Great'}; -- died of an 'insurrection of the innards' after a celebrated reign
{291, 'Emperor', 'Treptebaska', 'the Unwise'}; -- murdered by his wife in short order
{292, 'Empress', 'Vilintalti', 'the Impious'}; -- removed by the praetorian elite
{298, 'Emperor', 'Radavan', 'the Reckless'}; -- died at war
{316, 'Emperor', 'Suldibrand', 'the Forsaken of Men'}; -- fucked around. found out.
{350, 'Emperor', 'Persivan', 'the Deathless'};
}
local year, r = math.floor(t / 414)
for i=1, #reigns do if reigns[i+1][1] < year then r = reigns[i+1] end end
local reignBegin, title, name, epithet = lib.tbl.unpack(r)
local ry = 1 + (year - reignBegin)
return long and string.format('Year %s of the Reign of HH&GU %s %s %s',
ry, title, name, epithet) or string.format('Y. %s %s', name, ry)
end;
time = function(t, long)
local bellsInDay, candleSpansInBell = 5, 7
local bell = bellsInDay*t
local cspan = (bellsInDay*candleSpansInBell*t) % candleSpansInBell
return string.format(long and 'Bell %s, Candlespan %s' or '%sb %sc', math.floor(bell), math.floor(cspan))
end;
};
commune = {
name = 'People\'s Calendar';
date = function(t, long)
local year = math.floor(t / 256) + 314
return string.format(long and 'Foundation %s' or 'F:%s', year)
end;
time = function(t, long)
local hoursInDay, minutesInHour = 16, 16
local hour = hoursInDay*t
local min = (hoursInDay*minutesInHour*t) % minutesInHour
local dawn = 0.24*hoursInDay
local noon = 0.5*hoursInDay
local dusk = 0.76*hoursInDay
local midnight = 1.0*hoursInDay
local tl, str
if hour < dawn then
tl = dawn - hour
str = long and 'dawn' or 'D'
elseif hour < noon then
tl = noon - hour
str = long and 'noon' or 'N'
elseif hour < dusk then
tl = dusk - hour
str = long and 'dusk' or 'd'
elseif hour < midnight then
tl = midnight - hour
str = long and 'midnight' or 'M'
end
return long
and string.format('%s hours, %s minutes to %s',
math.floor(tl), math.floor(minutesInHour - min), str)
or string.format('%s.%sH.%sM', str, math.floor(tl),
math.floor(minutesInHour - min))
end;
};
};
};
};
jobs = {};
}
starlit.cfgDir = minetest.get_worldpath() .. '/' .. starlit.ident
local logger = function(module)
local function argjoin(arg, nxt, ...)
if arg and not nxt then return tostring(arg) end
if not arg then return "(nil)" end
return tostring(arg) .. ' ' .. argjoin(nxt, ...)
end
local lg = {}
local setup = function(fn, lvl)
lvl = lvl or fn
local function emit(...)
local call = (fn == 'fatal') and error
or function(str) minetest.log(lvl, str) end
if module
then call(string.format('[%s :: %s] %s',starlit.ident,module,argjoin(...)))
else call(string.format('[%s] %s',starlit.ident,argjoin(...)))
end
end
lg[fn ] = function(...) emit(...) end
lg[fn .. 'f'] = function(...) emit(string.format(...)) end -- convenience fn
end
setup('info')
setup('warn','warning')
setup('err','error')
setup('act','action')
setup('fatal')
return lg
end
starlit.logger = logger
local log = logger()
function starlit.evaluate(name, ...)
local path = minetest.get_modpath(minetest.get_current_modname())
local filename = string.format('%s/%s', path, name)
log.info('loading', filename)
local chunk, err = loadfile(filename, filename)
if not chunk then error(err) end
return chunk(...)
end
function starlit.include(name, ...) -- semantic variant used for loading modules
return starlit.evaluate(name..'.lua', ...)
end
minetest.register_lbm {
label = 'build radiator index';
name = 'starlit:loadradiatorboxes';
nodenames = {'group:radiator'};
run_at_every_load = true;
action = function(pos, node, dt)
local R = starlit.region
local phash = minetest.hash_node_position(pos)
if R.radiator.sources[phash] then return end -- already loaded
local def = minetest.registered_nodes[node.name]
local cl = def._starlit.radiator
local min,max = cl.maxEffectArea(pos)
local id = R.radiator.store:insert_area(min,max, minetest.pos_to_string(pos))
R.radiator.sources[phash] = id
end;
-- NOTE: temp emitter nodes are responsible for decaching themselves in their on_destruct cb
}
function starlit.startJob(id, interval, job)
local lastRun
local function start()
starlit.jobs[id] = minetest.after(interval, function()
local t = minetest.get_gametime()
local d = lastRun and t - lastRun or nil
lastRun = t
local continue = job(d, interval)
if continue == true or continue == nil then
start()
elseif continue ~= false then
interval = continue
start()
end
end)
end
start()
end
starlit.include 'stats'
starlit.include 'world'
starlit.include 'food'
starlit.include 'fab'
starlit.include 'tiers'
starlit.include 'species'
starlit.include 'store'
starlit.include 'ui'
starlit.include 'item'
starlit.include 'container'
starlit.include 'user'
starlit.include 'effect'
starlit.include 'fx/nano'
starlit.include 'element'
starlit.include 'terrain'
starlit.include 'interfaces'
starlit.include 'compile'
starlit.include 'suit'
-- minetest.settings:set('movement_gravity', starlit.world.planet.gravity) -- ??? seriously???
-- THIS OVERRIDES THE GLOBAL SETTING *AND PERSISTS IT* WHAT IN THE SATANIC FUCK
---------------
-- callbacks --
---------------
-- here we connect our types up to the minetest API
local function userCB(fn)
return function(luser, ...)
local name = luser:get_player_name()
local user = starlit.activeUsers[name]
return fn(user, ...)
end
end
minetest.register_on_joinplayer(function(luser, lastLogin)
-- TODO check that necessary CSMs are installed
local user = starlit.type.user(luser)
if lastLogin == nil then
user:onSignup()
end
user:onJoin()
starlit.activeUsers[user.name] = user
end)
minetest.register_on_leaveplayer(function(luser)
starlit.activeUsers[luser:get_player_name()]:onPart()
end)
minetest.register_on_player_receive_fields(function(luser, formid, fields)
local name = luser:get_player_name()
local user = starlit.activeUsers[name]
if not user then return false end
if formid == '' then -- main menu
return starlit.ui.userMenuDispatch(user,fields)
end
local ui = starlit.interface.db[formid]
local state = starlit.activeUI[name] or {}
if formid == '__builtin:help_cmds'
or formid == '__builtin:help_privs'
then return false end
if not (state.form == formid) then -- sanity check
log.warn('user %s attempted to send form %s while %s was active', name, formid, state.form)
return false
end
user:onRespond(ui, state, fields)
if fields.quit then
starlit.activeUI[name] = nil
end
return true
end)
minetest.register_on_respawnplayer(userCB(function(user)
return user:onRespawn()
end))
minetest.register_on_dieplayer(userCB(function(user, reason)
return user:onDie(reason)
end))
minetest.register_on_punchnode(function(pos,node,puncher,point)
local user = starlit.activeUsers[puncher:get_player_name()]
local oldTgt = user.action.tgt
user.action.tgt = point
if bit.band(user.action.bits, 0x80)==0 then
user.action.bits = bit.bor(user.action.bits, 0x80)
--user:trigger('primary', {state = 'init'})
else
user:trigger('retarget', {oldTgt = oldTgt})
end
end)
local function pointChanged(a,b)
return a.type ~= b.type
or a.type == 'node' and vector.new(a.under) ~= vector.new(b.under)
or a.type == 'object' and a.ref ~= b.ref
end
local function triggerPower(_, luser, point)
-- print("trigger", luser, luser:get_player_name())
local user = starlit.activeUsers[luser:get_player_name()]
local oldTgt = user.action.tgt
user.action.tgt = point
if bit.band(user.action.bits, 0x100)==0 then
user.action.bits = bit.bor(user.action.bits, 0x100)
--return user:trigger('secondary', {state = 'prog', delta = 0})
elseif pointChanged(oldTgt, point) then
user:trigger('retarget', {oldTgt = oldTgt})
end
end
-- sigh
--[[
core.noneitemdef_default.on_place = function(...)
if not triggerPower(...) then
minetest.item_place(...)
end
end
core.noneitemdef_default.on_use = function(...) triggerPower(...) end
core.noneitemdef_default.on_secondary_use = function(...) triggerPower(...) end
]]
minetest.register_item(":", {
type = "none",
wield_image = "wieldhand.png",
wield_scale = {x=1,y=1,z=2.5},
on_secondary_use = function(...) triggerPower(...) end;
-- on_use = function(...) print'base' end;
after_use = function(i,u,n,p)
if (u:is_player()) then triggerPower(i,u,p) end
end;
})
minetest.register_item("starlit:_hand_dig", {
type = "none",
wield_image = "wieldhand.png",
wield_scale = {x=1,y=1,z=2.5},
tool_capabilities = {
groupcaps = {
object = {maxlevel=1, times = {.10,.20,.40}};
plant = {maxlevel=1, times = {.50}};
-- sand, dirt, gravel
looseClump = {maxlevel=1, times = {1.5, 2.5}};
};
}
})
minetest.register_on_player_inventory_action(function(luser, act, inv, p)
local name = luser:get_player_name()
local user = starlit.activeUsers[name]
-- allow UIs to update on UI changes
local state = starlit.activeUI[name]
if state then
local ui = starlit.interface.db[state.form]
ui:cb('onMoveItem', user, act, inv, p)
end
end)
minetest.register_on_player_hpchange(function(luser, delta, cause)
local user = starlit.activeUsers[luser:get_player_name()]
if cause.type == 'fall' then
delta = user:damageModifier('bluntForceTrauma', (delta * 50))
-- justification: a short fall can do around
-- five points of damage, which is nearly 50%
-- of the default hp_max. since we crank up
-- hp by a factor of 50~40, damage should be
-- cranked by similarly
end
return delta
end, true)
function minetest.handle_node_drops(pos, drops, digger)
local function jitter(pos)
local function r(x) return x+math.random(-0.01, 0.01) end
return vector.new(
r(pos.x),
r(pos.y),
r(pos.z)
)
end
for i, it in ipairs(drops) do
if type(it) == 'string' then it = ItemStack(it) end
if not it:is_empty() then
local ent = minetest.add_item(jitter(pos), it)
if ent ~= nil then -- avoid crash when dropping unknown item
local dp = vector.new(0,0,0)
if digger then dp = digger:get_pos() end
local delta = dp - ent:get_pos()
ent:add_velocity(vector.new(delta.x,0,delta.z));
end
end
end
end
-- TODO timer iterates live UI