local lib = starlit.mod.lib
local paramTypes do local T,G = lib.marshal.t, lib.marshal.g
paramTypes = {
tone = G.struct {
hue = T.angle;
sat = T.clamp;
lum = T.clamp;
};
str = T.str;
num = T.decimal;
}
end
-- constants
local animationFrameRate = 60
local species = {
human = {
name = 'Human';
desc = 'The weeds of the galactic flowerbed. Humans are one of the Lesser Races, excluded from the ranks of the Starlit 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.';
scale = 1.0;
params = {
{'eyeColor', 'Eye Color', 'tone', {hue=327, sat=0, lum=0}};
{'hairColor', 'Hair Color', 'tone', {hue=100, sat=0, lum=0}};
{'skinTone', 'Skin Tone', 'tone', {hue= 0, sat=0, lum=0}};
};
tempRange = {
comfort = {18.3, 23.8}; -- needed for full stamina regen
survivable = {5, 33}; -- anything below/above will cause progressively more damage
};
variants = {
female = {
name = 'Human Female';
mesh = 'starlit-body-female.x';
eyeHeight = 1.4;
texture = function(t, adorn)
local skin = lib.image 'starlit-body-skin.png' : shift(t.skinTone)
local eye = lib.image 'starlit-body-eye.png' : shift(t.eyeColor)
local hair = lib.image 'starlit-body-hair.png' : shift(t.hairColor)
local invis = lib.image '[fill:1x1:0,0:#00000000'
local plate = adorn.suit and adorn.suit.plate or invis
local lining = adorn.suit and adorn.suit.lining or invis
return {lining, plate, skin, skin, eye, hair}
end;
stats = {
psiRegen = 1.3;
psiPower = 1.2;
psi = 1.2;
nutrition = .8; -- women have smaller stomachs
hydration = .8;
staminaRegen = 1.0;
morale = 0.8; -- you are not She-Bear Grylls
};
traits = {
health = 400;
lungCapacity = .6;
irradiation = 0.8; -- you are smaller, so it takes less rads to kill ya
sturdiness = 0; -- women are more fragile and thus susceptible to blunt force trauma
metabolism = .150; -- kCal/s
painTolerance = 0.4;
dehydration = 10e-4; -- L/s
};
};
male = {
name = 'Human Male';
eyeHeight = 1.6;
stats = {
psiRegen = 1.0;
psiPower = 1.0;
psi = 1.0;
nutrition = 1.0;
hydration = 1.0;
staminaRegen = .7; -- men are strong but have inferior endurance
};
traits = {
health = 500;
painTolerance = 1.0;
lungCapacity = 1.0;
sturdiness = 0.3;
metabolism = .150; -- kCal/s
dehydration = 15e-4; -- L/s
};
};
};
traits = {};
};
}
starlit.world.species = {
index = species;
paramTypes = paramTypes;
}
starlit.world.species.pheno = lib.class {
name = 'starlit:species.pheno';
construct = function(pSp, pVar)
local sp, var = starlit.world.species.lookup(pSp, pVar)
return {
species = sp, variant = var;
pSpecies = pSp, pVariant = pVar;
};
end;
__index = {
trait = function(me, st, dflt)
local v = me.variant.traits[st] or me.species.traits[st]
return v or dflt
end;
};
}
function starlit.world.species.mkDefaultParamsTable(pSpecies, pVariant)
local sp = species[pSpecies]
local var = sp.variants[pVariant]
local vpd = var.defaults or {}
local tbl = {}
for _, p in pairs(sp.params) do
local name, desc, ty, dflt = lib.tbl.unpack(p)
tbl[name] = vpd[name] or dflt
end
return tbl
end
function starlit.world.species.mkPersonaFor(pSpecies, pVariant)
return {
species = pSpecies;
speciesVariant = pVariant;
bodyParams = starlit.world.species.paramsFromTable(pSpecies,
starlit.world.species.mkDefaultParamsTable(pSpecies, pVariant)
);
statDeltas = {};
facts = {};
}
end
local function spLookup(pSpecies, pVariant)
local sp = species[pSpecies]
local var = sp.variants[pVariant or next(sp.variants)]
return sp, var
end
starlit.world.species.lookup = spLookup
function starlit.world.species.statRange(pSpecies, pVariant, pStat)
local sp,spv = spLookup(pSpecies, pVariant)
local min, max, base
if pStat == 'health' then
min,max = 0, spv.traits.health
elseif pStat == 'breath' then
min,max = 0, 65535
else
local spfac = spv.stats[pStat]
local basis = starlit.world.stats[pStat]
min,max = basis.min, basis.max
if spfac then
min = min * spfac
max = max * spfac
end
base = basis.base
if base == true then
base = max
elseif base == false then
base = min
end
end
return min, max, base or 0
end
-- set the necessary properties and create a persona for a newspawned entity
function starlit.world.species.birth(pSpecies, pVariant, entity, circumstances)
circumstances = circumstances or {}
local sp,var = spLookup(pSpecies, pVariant)
local function pct(st, p)
local min, max = starlit.world.species.statRange(pSpecies, pVariant, st)
local delta = max - min
return min + delta*p
end
local ps = starlit.world.species.mkPersonaFor(pSpecies,pVariant)
local startingHP = pct('health', 1.0)
if circumstances.injured then startingHP = pct('health', circumstances.injured) end
if circumstances.psiCharged then ps.statDeltas.psi = pct('psi', circumstances.psiCharged) end
for k,v in pairs(starlit.world.stats) do ps.statDeltas[k] = 0 end
ps.statDeltas.warmth = 20 -- don't instantly start dying of frostbite
ps.statDeltas.nutrition = 2000 -- shoulda packed more MRE :c
ps.statDeltas.hydration = 3 -- stay hydrated uwu
entity:set_properties{hp_max = var.traits.health or sp.traits.health}
entity:set_hp(startingHP, 'initial hp')
return ps
end
function starlit.world.species.paramsFromTable(pSpecies, tbl)
local lst = {}
local sp = species[pSpecies]
for i, par in pairs(sp.params) do
local name,desc,ty,dflt = lib.tbl.unpack(par)
if tbl[name] then
table.insert(lst, {id=name, value=paramTypes[ty].enc(tbl[name])})
end
end
return lst
end
function starlit.world.species.paramsToTable(pSpecies, lst)
local tymap = {}
local sp = species[pSpecies]
for i, par in pairs(sp.params) do
local name,desc,ty,dflt = lib.tbl.unpack(par)
tymap[name] = paramTypes[ty]
end
local tbl = {}
for _, e in pairs(lst) do
tbl[e.id] = tymap[e.id].dec(e.value)
end
return tbl
end
for speciesName, sp in pairs(species) do
for varName, var in pairs(sp.variants) do
if var.mesh then
var.animations = starlit.evaluate(string.format('models/%s.nla', var.mesh)).skel.action
end
end
end
function starlit.world.species.updateTextures(ent, persona, adornment)
local s,v = spLookup(persona.species, persona.speciesVariant)
local paramTable = starlit.world.species.paramsToTable(persona.species, persona.bodyParams)
local texs = {}
for i, t in ipairs(v.texture(paramTable, adornment)) do
texs[i] = t:render()
end
ent:set_properties { textures = texs }
end
function starlit.world.species.setupEntity(ent, persona)
local s,v = spLookup(persona.species, persona.speciesVariant)
local _, maxHealth = starlit.world.species.statRange(persona.species, persona.speciesVariant, 'health')
ent:set_properties {
visual = 'mesh';
mesh = v.mesh;
stepheight = .51;
eye_height = v.eyeHeight;
collisionbox = { -- FIXME
-0.3, 0.0, -0.3;
0.3, 1.5, 0.3;
};
visual_size = vector.new(10,10,10) * s.scale;
hp_max = maxHealth;
}
local function P(v)
if v then return {x=v[1],y=v[2]} end
return {x=0,y=0}
end
ent:set_local_animation(
P(v.animations.idle),
P(v.animations.run),
P(v.animations.work),
P(v.animations.runWork),
animationFrameRate)
end