local lib = starsoul.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 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.';
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 = 'starsoul-body-female.x';
eyeHeight = 1.4;
texture = function(t, adorn)
local skin = lib.image 'starsoul-body-skin.png' : shift(t.skinTone)
local eye = lib.image 'starsoul-body-eye.png' : shift(t.eyeColor)
local hair = lib.image 'starsoul-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;
hunger = .8; -- women have smaller stomachs
thirst = .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 = 1800; --Cal
painTolerance = 0.4;
};
};
male = {
name = 'Human Male';
eyeHeight = 1.6;
stats = {
psiRegen = 1.0;
psiPower = 1.0;
psi = 1.0;
hunger = 1.0;
staminaRegen = .7; -- men are strong but have inferior endurance
};
traits = {
health = 500;
painTolerance = 1.0;
lungCapacity = 1.0;
sturdiness = 0.3;
metabolism = 2200; --Cal
};
};
};
traits = {};
};
}
starsoul.world.species = {
index = species;
paramTypes = paramTypes;
}
function starsoul.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 starsoul.world.species.mkPersonaFor(pSpecies, pVariant)
return {
species = pSpecies;
speciesVariant = pVariant;
bodyParams = starsoul.world.species.paramsFromTable(pSpecies,
starsoul.world.species.mkDefaultParamsTable(pSpecies, pVariant)
);
statDeltas = {};
}
end
local function spLookup(pSpecies, pVariant)
local sp = species[pSpecies]
local var = sp.variants[pVariant or next(sp.variants)]
return sp, var
end
starsoul.world.species.lookup = spLookup
function starsoul.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 = starsoul.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
end
-- set the necessary properties and create a persona for a newspawned entity
function starsoul.world.species.birth(pSpecies, pVariant, entity, circumstances)
circumstances = circumstances or {}
local sp,var = spLookup(pSpecies, pVariant)
local function pct(st, p)
local min, max = starsoul.world.species.statRange(pSpecies, pVariant, st)
local delta = max - min
return min + delta*p
end
local ps = starsoul.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
ps.statDeltas.warmth = 20 -- don't instantly start dying of frostbite
entity:set_properties{hp_max = var.traits.health or sp.traits.health}
entity:set_hp(startingHP, 'initial hp')
return ps
end
function starsoul.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 starsoul.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 = starsoul.evaluate(string.format('models/%s.nla', var.mesh)).skel.action
end
end
end
function starsoul.world.species.updateTextures(ent, persona, adornment)
local s,v = spLookup(persona.species, persona.speciesVariant)
local paramTable = starsoul.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 starsoul.world.species.setupEntity(ent, persona)
local s,v = spLookup(persona.species, persona.speciesVariant)
local _, maxHealth = starsoul.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