Forked from
tome / Tales of MajEyal
527 commits behind the upstream repository.
-
DarkGod authored
New Wanderer class. It's akin to adventurer but more chaotic! Instead of choosing talent trees you get a random set as you levelup! The set is determined by a seed that you can share with others to have friendly random competitions, or just share cool combos ;)
DarkGod authoredNew Wanderer class. It's akin to adventurer but more chaotic! Instead of choosing talent trees you get a random set as you levelup! The set is determined by a seed that you can share with others to have friendly random competitions, or just share cool combos ;)
Birther.lua 69.51 KiB
-- TE4 - T-Engine 4
-- Copyright (C) 2009 - 2019 Nicolas Casalini
--
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with this program. If not, see <http://www.gnu.org/licenses/>.
--
-- Nicolas Casalini "DarkGod"
-- darkgod@te4.org
require "engine.class"
local Dialog = require "engine.ui.Dialog"
local Birther = require "engine.Birther"
local List = require "engine.ui.List"
local TreeList = require "engine.ui.TreeList"
local ListColumns = require "engine.ui.ListColumns"
local Button = require "engine.ui.Button"
local Dropdown = require "engine.ui.Dropdown"
local Textbox = require "engine.ui.Textbox"
local Checkbox = require "engine.ui.Checkbox"
local Textzone = require "engine.ui.Textzone"
local ImageList = require "engine.ui.ImageList"
local TextzoneList = require "engine.ui.TextzoneList"
local Separator = require "engine.ui.Separator"
local NameGenerator = require "engine.NameGenerator"
local NameGenerator2 = require "engine.NameGenerator2"
local Savefile = require "engine.Savefile"
local Module = require "engine.Module"
local Tiles = require "engine.Tiles"
local Particles = require "engine.Particles"
local CharacterVaultSave = require "engine.CharacterVaultSave"
local Object = require "mod.class.Object"
local OptionTree = require "mod.dialogs.OptionTree"
local Entity = require "engine.Entity"
module(..., package.seeall, class.inherit(Birther))
_M.cosmetic_options_config = {
hairs = "single",
skin = "single",
facial_features = "multiple",
tatoos = "single",
horns = "single",
special = "multiple",
golem = "single",
}
function _M:setSubclassIcon(t)
t.image32 = "class-icons/"..(t.name:lower():gsub("[^a-z0-9]", "_")).."_32_bg.png"
t.image128 = "class-icons/"..(t.name:lower():gsub("[^a-z0-9]", "_")).."_128_bg.png"
if not fs.exists(Tiles.baseImageFile(t.image32)) then
t.image32 = "class-icons/unknown_32_bg.png"
t.image128 = "class-icons/unknown_128_bg.png"
end
t.display_entity32 = Entity.new{image=t.image32}
t.display_entity128 = Entity.new{image=t.image128}
end
for _, t in pairs(_M.birth_descriptor_def.subclass) do _M:setSubclassIcon(t) end
--- Instanciates a birther for the given actor
function _M:init(title, actor, order, at_end, quickbirth, w, h)
self.quickbirth = quickbirth
self.actor = actor:cloneFull()
self.actor_base = actor
self.order = order
self.at_end = at_end
self.selected_cosmetic_options = nil
self.tiles = Tiles.new(64, 64, nil, nil, true, nil)
Dialog.init(self, title and title or _t"Character Creation", w or 600, h or 400)
self.obj_list = Object:loadList("/data/general/objects/objects.lua")
self.obj_list_by_name = {}
for i, e in ipairs(self.obj_list) do if e.name and (e.rarity or e.define_as) then self.obj_list_by_name[e.name] = e end end
self.descriptors = {}
self.descriptors_by_type = {}
self.to_reset_cosmetic = {}
self.c_ok = Button.new{text=_t" Play! ", fct=function() self:atEnd("created") end}
self.c_random = Button.new{text=_t"Random!", fct=function() self:randomBirth() end}
self.c_premade = Button.new{text=_t"Load premade", fct=function() self:loadPremadeUI() end}
self.c_tile = Button.new{text=_t"Select custom tile", fct=function() self:selectTile() end}
self.c_cancel = Button.new{text=_t"Cancel", fct=function() self:atEnd("quit") end}
self.c_tut = Button.new{text=_t"Tutorial", fct=function() self:tutorial() end}
self.c_options = Button.new{text=_t"Customize", fct=function() self:customizeOptions() end}
self.c_options.hide = true
self.c_extra_options = Button.new{text=_t"Extra Options", fct=function() self:extraOptions() end}
self.c_extra_options.hide = not game.extra_birth_option_defs or #game.extra_birth_option_defs == 0
self.c_name = Textbox.new{title=_t"Name: ", text=(not config.settings.cheat and game.player_name == "player") and "" or game.player_name, chars=30, max_len=50, fct=function()
if config.settings.cheat then self:makeDefault() end
end, on_change=function() self:setDescriptor() end, on_mouse = function(button) if button == "right" then self:randomName() end end}
self.c_female = Checkbox.new{title=_t"Female", default=true,
fct=function() end,
on_change=function(s) self.c_male.checked = not s self:setDescriptor("sex", s and "Female" or "Male") end
}
self.c_male = Checkbox.new{title=_t"Male", default=false,
fct=function() end,
on_change=function(s) self.c_female.checked = not s self:setDescriptor("sex", s and "Male" or "Female") end
}
self:generateCampaigns()
self.c_campaign_text = Textzone.new{auto_width=true, auto_height=true, text=_t"Campaign: "}
self.c_campaign = Dropdown.new{width=400, fct=function(item) self:campaignUse(item) end, on_select=function(item) self:updateDesc(item) end, list=self.all_campaigns, nb_items=#self.all_campaigns}
self:generateDifficulties()
self.c_difficulty_text = Textzone.new{auto_width=true, auto_height=true, text=_t"Difficulty: "}
self.c_difficulty = Dropdown.new{width=100, fct=function(item) self:difficultyUse(item) end, on_select=function(item) self:updateDesc(item) end, list=self.all_difficulties, nb_items=#self.all_difficulties}
self:generatePermadeaths()
self.c_permadeath_text = Textzone.new{auto_width=true, auto_height=true, text=_t"Permadeath: "}
self.c_permadeath = Dropdown.new{width=150, fct=function(item) self:permadeathUse(item) end, on_select=function(item) self:updateDesc(item) end, list=self.all_permadeaths, nb_items=#self.all_permadeaths}
self.c_desc = TextzoneList.new{width=math.floor(self.iw / 3 - 10), height=self.ih - self.c_female.h - self.c_ok.h - self.c_difficulty.h - self.c_campaign.h - 10, scrollbar=true, pingpong=20, no_color_bleed=true}
self:setDescriptor("base", "base")
self:setDescriptor("world", self.default_campaign)
self:setDescriptor("difficulty", self.default_difficulty)
self:setDescriptor("permadeath", self.default_permadeath)
self:setDescriptor("sex", "Female")
self:generateRaces()
self.c_race = TreeList.new{width=math.floor(self.iw / 3 - 10), height=self.ih - self.c_female.h - self.c_ok.h - (self.c_extra_options.hide and 0 or self.c_extra_options.h) - self.c_difficulty.h - self.c_campaign.h - 10, scrollbar=true, columns={
{width=100, display_prop="name"},
}, tree=self.all_races,
fct=function(item, sel, v) self:raceUse(item, sel, v) end,
select=function(item, sel) self:updateDesc(item) end,
on_expand=function(item) end,
}
self:generateClasses()
self.c_class = TreeList.new{width=math.floor(self.iw / 3 - 10), height=self.ih - self.c_female.h - self.c_ok.h - self.c_difficulty.h - self.c_campaign.h - 10, scrollbar=true, columns={
{width=100, display_prop="name"},
}, tree=self.all_classes,
fct=function(item, sel, v) self:classUse(item, sel, v) end,
select=function(item, sel) self:updateDesc(item) end,
on_expand=function(item) end,
on_drawitem=function(item, s, startx, h)
if not item.def or not item.def.display_entity32 then return startx end
local sc = item.def.display_entity32:getEntityFinalSurface(self.tiles, h, h)
if sc then s:merge(sc, startx, 0) end
return startx + h
end,
}
self.cur_order = 1
self.sel = 1
self:loadUI{
-- First line
{left=0, top=0, ui=self.c_name},
{left=self.c_name, top=0, ui=self.c_female},
{left=self.c_female, top=0, ui=self.c_male},
-- Second line
{left=0, top=self.c_name, ui=self.c_campaign_text},
{left=self.c_campaign_text, top=self.c_name, ui=self.c_campaign},
-- Third line
{left=0, top=self.c_campaign, ui=self.c_difficulty_text},
{left=self.c_difficulty_text, top=self.c_campaign, ui=self.c_difficulty},
{left=self.c_difficulty, top=self.c_campaign, ui=self.c_permadeath_text},
{left=self.c_permadeath_text, top=self.c_campaign, ui=self.c_permadeath},
{right=0, top=self.c_name, ui=self.c_tut},
-- Lists
{left=0, top=self.c_permadeath, ui=self.c_race},
{left=self.c_race, top=self.c_permadeath, ui=self.c_class},
{right=0, top=self.c_permadeath, ui=self.c_desc},
-- Buttons
{left=0, bottom=0, ui=self.c_ok, hidden=true},
{left=self.c_ok, bottom=0, ui=self.c_random},
{left=self.c_random, bottom=0, ui=self.c_premade},
{left=self.c_premade, bottom=0, ui=self.c_tile},
{left=self.c_tile, bottom=0, ui=self.c_options},
{right=0, bottom=0, ui=self.c_cancel},
{left=0, bottom=self.c_ok, ui=self.c_extra_options},
}
self:setupUI()
if self.descriptors_by_type.difficulty == "Tutorial" then
self:permadeathUse(self.all_permadeaths[1], 1)
self:raceUse(self.all_races[1], 1)
self:raceUse(self.all_races[1].nodes[1], 2)
self:classUse(self.all_classes[1], 1)
self:classUse(self.all_classes[1].nodes[1], 2)
end
for i, item in ipairs(self.c_campaign.c_list.list) do if self.default_campaign == item.id then self.c_campaign.c_list.sel = i break end end
for i, item in ipairs(self.c_difficulty.c_list.list) do if self.default_difficulty == item.id then self.c_difficulty.c_list.sel = i break end end
for i, item in ipairs(self.c_permadeath.c_list.list) do if self.default_permadeath == item.id then self.c_permadeath.c_list.sel = i break end end
if config.settings.tome.default_birth and config.settings.tome.default_birth.sex then
self.c_female.checked = config.settings.tome.default_birth.sex == "Female"
self.c_male.checked = config.settings.tome.default_birth.sex ~= "Female"
self:setDescriptor("sex", self.c_female.checked and "Female" or "Male")
end
self:setFocus(self.c_campaign)
self:setFocus(self.c_name)
if not profile.mod.allow_build.tutorial_done then
self:setFocus(self.c_tut)
self.c_tut.glow = 0.70
end
end
function _M:checkNew(fct)
local function checkfct()
local savename = self.c_name.text:gsub("[^a-zA-Z0-9_-.]", "_")
if fs.exists(("/save/%s/game.teag"):format(savename)) then
Dialog:yesnoPopup(_t"Overwrite character?", _t"There is already a character with this name, do you want to overwrite it?", function(ret)
if not ret then fct() end
end, _t"No", _t"Yes")
else
fct()
end
end
local is_antimagic, is_magic = false, false
for i, d in ipairs(self.descriptors) do
if d.copy then
if d.copy.forbid_arcane then is_antimagic = true end
end
if d.talents_types then
local tt = d.talents_types
if type(tt) == "function" then tt = tt(self) end
for t, v in pairs(tt) do
local ttdef = self.actor:getTalentTypeFrom(t)
if ttdef and ttdef.is_spell then is_magic = true break end
end
end
end
if is_magic and is_antimagic then
Dialog:yesnoPopup(_t"Antimagic Magic combo", _t"The selected race/class has both magic and antimagic, this is unlikely to work. Continue?", function(ret) if not ret then
checkfct()
end end, _t"No", _t"Yes I'm sure")
else
checkfct()
end
end
function _M:applyingDescriptor(i, d, self_contained)
if d.unlockable_talents_types then
for t, v in pairs(d.unlockable_talents_types) do
if profile.mod.allow_build[v[3]] then
local mastery
if type(v) == "table" then
v, mastery = v[1], v[2]
else
v, mastery = v, 0
end
self.actor:learnTalentType(t, v)
self.actor.talents_types_mastery[t] = (self.actor.talents_types_mastery[t] or 0) + mastery
end
end
end
if not self_contained then
if d.party_copy then
local copy = table.clone(d.party_copy, true)
-- Append array part
while #copy > 0 do
local f = table.remove(copy)
table.insert(game.party, f)
end
-- Copy normal data
table.merge(game.party, copy, true)
end
self:applyGameState(d)
end
end
function _M:applyGameState(d)
if d.game_state then
local copy = table.clone(d.game_state, true)
-- Append array part
while #copy > 0 do
local f = table.remove(copy)
table.insert(game.state.birth, f)
end
-- Copy normal data
table.merge(game.state.birth, copy, true)
end
if d.game_state_execute then
d.game_state_execute()
end
end
function _M:atEnd(v)
if v == "created" and not self.ui_by_ui[self.c_ok].hidden then
self:checkNew(function()
local ps = self.actor:getParticlesList()
for i, p in ipairs(ps) do self.actor:removeParticles(p) end
self.actor:defineDisplayCallback()
self.actor:removeAllMOs()
game:unregisterDialog(self)
self.actor = self.actor_base
if self.has_custom_tile then
self:setTile(self.has_custom_tile.f, self.has_custom_tile.w, self.has_custom_tile.h, true)
self.actor.has_custom_tile = self.has_custom_tile.f
end
self:resetAttachementSpots()
-- Prevent the game from auto-assigning talents if necessary.
if (not config.settings.tome.autoassign_talents_on_birth) and not game.state.birth.always_learn_birth_talents then
for _, d in pairs(self.descriptors) do
local unlearned_talents = { }
if (d.talents ~= nil and (d.type == "race" or d.type == "subrace" or d.type == "class" or d.type == "subclass")) then
for t_id, t_level in pairs(d.talents) do
local talent = self.actor:getTalentFromId(t_id)
if (talent ~= nil and not talent.no_unlearn_last) then
--[[ Award talent points based on the highest available level of the talent
This is in the potential case of a player selecting a race with two points in phase door
and Archmage as his class. Archmage starts with one point in phase door. Cases like this may
result in a conflict of what the player might expect to get back in points. The highest
amount of points is always awarded to the player (two, in this case).
]]
if (unlearned_talents[talent] == nil) then
unlearned_talents[talent] = t_level
elseif (unlearned_talents[talent] < t_level) then
unlearned_talents[talent] = t_level
end
self.actor:learnPool(talent)
print("[BIRTHER] Ignoring auto-assign for " .. t_id .. " (from " .. d.type .. " descriptor \"" .. d.name .. "\")")
d.talents[t_id] = nil
end
end
-- Give the player the appropriate amount of talent points
for talent, t_level in pairs(unlearned_talents) do
if (talent.generic == true) then
self.actor.unused_generics = self.actor.unused_generics + t_level
else
self.actor.unused_talents = self.actor.unused_talents + t_level
end
end
end
end
end
self:apply()
if self.has_custom_tile then
self.actor.make_tile = nil
self.actor.moddable_tile = nil
end
self:applyCosmeticActor(true)
game:setPlayerName(self.c_name.text)
local save = Savefile.new(game.save_name)
save:delete()
save:close()
if self.actor.descriptor.difficulty ~= "Tutorial" then game:saveSettings("tome.default_birth", ("tome.default_birth = {permadeath=%q, difficulty=%q, sex=%q, campaign=%q}\n"):format(self.actor.descriptor.permadeath, self.actor.descriptor.difficulty, self.actor.descriptor.sex, self.actor.descriptor.world)) end
self.at_end(false)
end)
elseif v == "loaded" then
self:checkNew(function()
game:unregisterDialog(self)
game:setPlayerName(self.c_name.text)
for type, kind in pairs(game.player.descriptor) do
local d = self:getBirthDescriptor(type, kind)
if d then self:applyGameState(d) end
end
self.at_end(true)
end)
elseif v == "quit" then
util.showMainMenu()
end
end
--- Make a default character when using cheat mode, for easier testing
function _M:makeDefault()
self:setDescriptor("sex", "Female")
self:setDescriptor("world", "Maj'Eyal")
-- self:setDescriptor("world", "Infinite")
self:setDescriptor("difficulty", "Normal")
self:setDescriptor("permadeath", "Adventure")
self:setDescriptor("race", "Human")
self:setDescriptor("subrace", "Cornac")
self:setDescriptor("class", "Adventurer")
self:setDescriptor("subclass", "Wanderer")
-- self:setDescriptor("class", "Warrior")
-- self:setDescriptor("subclass", "Berserker")
__module_extra_info.no_birth_popup = true
self:atEnd("created")
end
--- Run one of the tutorials
function _M:tutorial()
local run = function(t)
self:setDescriptor("sex", "Female")
self:setDescriptor("world", "Maj'Eyal")
self:setDescriptor("difficulty", "Tutorial")
self:setDescriptor("permadeath", "Adventure")
self:setDescriptor("race", "Tutorial Human")
self:setDescriptor("subrace", "Tutorial "..t)
self:setDescriptor("class", "Tutorial Adventurer")
self:setDescriptor("subclass", "Tutorial Adventurer")
self:randomName()
self:atEnd("created")
end
local d = Dialog.new(_t"Tutorials", 280, 100)
local basic = Button.new{text=_t"Basic Gameplay (recommended)", fct=function() run("Basic") d.key:triggerVirtual("EXIT") end}
-- local stats = Button.new{text=_t"Stats and effects (advanced players)", fct=function() run("Stats") d.key:triggerVirtual("EXIT") end}
local cancel = Button.new{text=_t"Cancel", fct=function() d.key:triggerVirtual("EXIT") end}
local sep = Separator.new{dir="vertical", size=230}
d:loadUI{
{hcenter=0, top=0, ui=basic},
-- {hcenter=0, top=basic.h, ui=stats},
{hcenter=0, bottom=cancel.h, ui=sep},
{hcenter=0, bottom=0, ui=cancel},
}
d:setupUI(false, true)
d.key:addBind("EXIT", function() game:unregisterDialog(d) end)
game:registerDialog(d)
end
function _M:randomBirth()
-- Random sex
local sex = rng.percent(50)
self.c_male.checked = sex
self.c_female.checked = not sex
self:setDescriptor("sex", sex and "Male" or "Female")
self.descriptors_by_type.race = nil
self.descriptors_by_type.subrace = nil
self.descriptors_by_type.class = nil
self.descriptors_by_type.subclass = nil
--[[
-- Random campaign
local camp, camp_id = nil
repeat camp, camp_id = rng.table(self.c_campaign.c_list.list)
until not camp.locked
self.c_campaign.c_list.sel = camp_id
self:campaignUse(camp)
-- Random difficulty
local diff, diff_id = nil
repeat diff, diff_id = rng.table(self.c_difficulty.c_list.list)
until diff.name ~= "Tutorial" and not diff.locked
self.c_difficulty.c_list.sel = diff_id
self:difficultyUse(diff)
--]]
-- Random race
local race, race_id = nil
repeat race, race_id = rng.table(self.all_races)
until not race.locked
self:raceUse(race)
-- Random subrace
local subrace, subrace_id = nil
repeat subrace, subrace_id = rng.table(self.all_races[race_id].nodes)
until not subrace.locked
self:raceUse(subrace)
-- Random class
local class, class_id = nil
repeat class, class_id = rng.table(self.all_classes)
until not class or not class.locked
self:classUse(class)
-- Random subclass
if class then
local subclass, subclass_id = nil
repeat subclass, subclass_id = rng.table(self.all_classes[class_id].nodes)
until not subclass.locked
self:classUse(subclass)
end
self:randomName()
end
function _M:randomName()
if not self.descriptors_by_type.sex or not self.descriptors_by_type.subrace then return end
local sex_def = self.birth_descriptor_def.sex[self.descriptors_by_type.sex]
local race_def = self.birth_descriptor_def.race[self.descriptors_by_type.race]
local subrace_def = self.birth_descriptor_def.subrace[self.descriptors_by_type.subrace]
local name_def = nil
if subrace_def.copy and subrace_def.copy.random_name_def then name_def = subrace_def
elseif race_def.copy and race_def.copy.random_name_def then name_def = race_def end
if name_def then
local namegen = NameGenerator2.new("/data/languages/names/"..name_def.copy.random_name_def:gsub("#sex#", sex_def.copy.female and "female" or "male")..".txt")
self.c_name:setText(namegen:generate(nil, name_def.copy.random_name_min_syllables, name_def.copy.random_name_max_syllables))
else
local namegen = NameGenerator.new((not sex_def.copy.female) and {
phonemesVocals = "a, e, i, o, u, y",
phonemesConsonants = "b, c, ch, ck, cz, d, dh, f, g, gh, h, j, k, kh, l, m, n, p, ph, q, r, rh, s, sh, t, th, ts, tz, v, w, x, z, zh",
syllablesStart = "Aer, Al, Am, An, Ar, Arm, Arth, B, Bal, Bar, Be, Bel, Ber, Bok, Bor, Bran, Breg, Bren, Brod, Cam, Chal, Cham, Ch, Cuth, Dag, Daim, Dair, Del, Dr, Dur, Duv, Ear, Elen, Er, Erel, Erem, Fal, Ful, Gal, G, Get, Gil, Gor, Grin, Gun, H, Hal, Han, Har, Hath, Hett, Hur, Iss, Khel, K, Kor, Lel, Lor, M, Mal, Man, Mard, N, Ol, Radh, Rag, Relg, Rh, Run, Sam, Tarr, T, Tor, Tul, Tur, Ul, Ulf, Unr, Ur, Urth, Yar, Z, Zan, Zer",
syllablesMiddle = "de, do, dra, du, duna, ga, go, hara, kaltho, la, latha, le, ma, nari, ra, re, rego, ro, rodda, romi, rui, sa, to, ya, zila",
syllablesEnd = "bar, bers, blek, chak, chik, dan, dar, das, dig, dil, din, dir, dor, dur, fang, fast, gar, gas, gen, gorn, grim, gund, had, hek, hell, hir, hor, kan, kath, khad, kor, lach, lar, ldil, ldir, leg, len, lin, mas, mnir, ndil, ndur, neg, nik, ntir, rab, rach, rain, rak, ran, rand, rath, rek, rig, rim, rin, rion, sin, sta, stir, sus, tar, thad, thel, tir, von, vor, yon, zor",
rules = "$s$v$35m$10m$e",
} or {
phonemesVocals = "a, e, i, o, u, y",
syllablesStart = "Ad, Aer, Ar, Bel, Bet, Beth, Ce'N, Cyr, Eilin, El, Em, Emel, G, Gl, Glor, Is, Isl, Iv, Lay, Lis, May, Ner, Pol, Por, Sal, Sil, Vel, Vor, X, Xan, Xer, Yv, Zub",
syllablesMiddle = "bre, da, dhe, ga, lda, le, lra, mi, ra, ri, ria, re, se, ya",
syllablesEnd = "ba, beth, da, kira, laith, lle, ma, mina, mira, na, nn, nne, nor, ra, rin, ssra, ta, th, tha, thra, tira, tta, vea, vena, we, wen, wyn",
rules = "$s$v$35m$10m$e",
})
self.c_name:setText(namegen:generate())
end
end
function _M:on_focus(id, ui)
if self.focus_ui and self.focus_ui.ui == self.c_name then self.c_desc:switchItem(self.c_name, _t"This is the name of your character.\nRight mouse click to generate a random name based on race and sex.")
elseif self.focus_ui and self.focus_ui.ui == self.c_female then self.c_desc:switchItem(self.c_female, self.birth_descriptor_def.sex.Female.desc)
elseif self.focus_ui and self.focus_ui.ui == self.c_male then self.c_desc:switchItem(self.c_male, self.birth_descriptor_def.sex.Male.desc)
elseif self.focus_ui and self.focus_ui.ui == self.c_campaign then
local item = self.c_campaign.c_list.list[self.c_campaign.c_list.sel]
self.c_desc:switchItem(item, item.desc)
elseif self.focus_ui and self.focus_ui.ui == self.c_difficulty then
local item = self.c_difficulty.c_list.list[self.c_difficulty.c_list.sel]
self.c_desc:switchItem(item, item.desc)
elseif self.focus_ui and self.focus_ui.ui == self.c_permadeath then
local item = self.c_permadeath.c_list.list[self.c_permadeath.c_list.sel]
self.c_desc:switchItem(item, item.desc)
end
end
function _M:updateDesc(item)
if item and item.desc then
self.c_desc:switchItem(item, item.desc)
end
end
function _M:campaignUse(item)
if not item then return end
if item.locked then
self.c_campaign.c_list.sel = self.c_campaign.previous
else
self:setDescriptor("world", item.id)
self:generateDifficulties()
self:generatePermadeaths()
self:generateRaces()
self:generateClasses()
end
end
function _M:difficultyUse(item)
if not item then return end
if item.locked then
self.c_difficulty.c_list.sel = self.c_difficulty.previous
else
self:setDescriptor("difficulty", item.id)
self:generatePermadeaths()
self:generateRaces()
self:generateRaces()
self:generateClasses()
end
end
function _M:permadeathUse(item)
if not item then return end
if item.locked then
self.c_permadeath.c_list.sel = self.c_permadeath.previous
if item.locked_select then item.locked_select(self) end
else
self:setDescriptor("permadeath", item.id)
self:generateRaces()
self:generateClasses()
end
end
function _M:raceUse(item, sel, v)
if not item then return end
if item.nodes then
for i, item in ipairs(self.c_race.tree) do if item.shown then self.c_race:treeExpand(false, item) end end
self.c_race:treeExpand(nil, item)
elseif not item.locked and item.basename then
if self.sel_race then
self.sel_race.name = self.sel_race.basename
self.c_race:drawItem(self.sel_race)
end
self:setDescriptor("race", item.pid)
self:setDescriptor("subrace", item.id)
self.sel_race = item
self.sel_race.name = tstring{{"font","bold"}, {"color","LIGHT_GREEN"}, self.sel_race.basename:toString(), {"font","normal"}}
self.c_race:drawItem(item)
self:generateClasses()
end
end
function _M:classUse(item, sel, v)
if not item then return end
if item.nodes then
for i, item in ipairs(self.c_class.tree) do if item.shown then self.c_class:treeExpand(false, item) end end
self.c_class:treeExpand(nil, item)
elseif not item.locked and item.basename then
if self.sel_class then
self.sel_class.name = self.sel_class.basename
self.c_class:drawItem(self.sel_class)
end
self:setDescriptor("class", item.pid)
self:setDescriptor("subclass", item.id)
self.sel_class = item
self.sel_class.name = tstring{{"font","bold"}, {"color","LIGHT_GREEN"}, self.sel_class.basename:toString(), {"font","normal"}}
self.c_class:drawItem(item)
end
end
function _M:updateDescriptors()
self.descriptors = {}
table.insert(self.descriptors, self.birth_descriptor_def.base[self.descriptors_by_type.base])
table.insert(self.descriptors, self.birth_descriptor_def.world[self.descriptors_by_type.world])
table.insert(self.descriptors, self.birth_descriptor_def.difficulty[self.descriptors_by_type.difficulty])
table.insert(self.descriptors, self.birth_descriptor_def.permadeath[self.descriptors_by_type.permadeath])
table.insert(self.descriptors, self.birth_descriptor_def.sex[self.descriptors_by_type.sex])
if self.descriptors_by_type.subrace then
table.insert(self.descriptors, self.birth_descriptor_def.race[self.descriptors_by_type.race])
table.insert(self.descriptors, self.birth_descriptor_def.subrace[self.descriptors_by_type.subrace])
end
if self.descriptors_by_type.subclass then
table.insert(self.descriptors, self.birth_descriptor_def.class[self.descriptors_by_type.class])
table.insert(self.descriptors, self.birth_descriptor_def.subclass[self.descriptors_by_type.subclass])
end
self.cosmetic_options = {}
self.cosmetic_options_flat = {}
for _, d in ipairs(self.descriptors) do if d.cosmetic_options then
for kind, list in pairs(d.cosmetic_options) do
local clist = {}
local sublists = {}
for _, data in ipairs(list) do
if (not data.unlock or profile.mod.allow_build[data.unlock]) and (not data.check or data.check(self)) then
local ok = true
if data.addons then for i, v in ipairs(data.addons) do
if not game:isAddonActive(v) then ok = false break end
end end
if data.only_for then for k, v in pairs(data.only_for) do
if self.descriptors_by_type[k] ~= v then ok = false break end
end end
if data.birth_only and self.not_birthing then ok = false end
if ok then
data.kind = kind
data.color = function(item)
if item.selected then return colors.simple(colors.LIGHT_GREEN)
else return colors.simple(colors.WHITE) end
end
if data.subkind then
sublists[data.subkind] = sublists[data.subkind] or {}
table.insert(sublists[data.subkind], data)
else
table.insert(clist, data)
end
table.insert(self.cosmetic_options_flat, data)
end
end
end
for name, list in pairs(sublists) do
table.sort(list, function(a, b) return a.name < b.name end)
table.insert(clist, {name=name, subkind=name, color=function() return colors.simple(colors.ROYAL_BLUE) end, nodes=list})
end
if #clist > 0 then
table.sort(clist, function(a, b) return a.name < b.name end)
table.insert(self.cosmetic_options, {name=_t(kind:gsub("_", " "):capitalize(), "birth facial category"), kind=kind, color=function() return colors.simple(colors.GOLD) end, nodes=clist})
end
end
end end
table.sort(self.cosmetic_options, function(a, b) return a.name < b.name end)
self.c_options.hide = #self.cosmetic_options == 0
if self.old_cosmetic_sex ~= self.descriptors_by_type.sex or self.old_cosmetic_race ~= self.descriptors_by_type.race or self.old_cosmetic_subrace ~= self.descriptors_by_type.subrace then
self.selected_cosmetic_options = nil
end
self.old_cosmetic_sex = self.descriptors_by_type.sex
self.old_cosmetic_race = self.descriptors_by_type.race
self.old_cosmetic_subrace = self.descriptors_by_type.subrace
end
function _M:selectRandomCosmetics(filters)
self.selected_cosmetic_options = {}
local function select(items)
local item = rng.table(items)
if item then
if self.cosmetic_options_config[item.kind] == "single" then
self.selected_cosmetic_options[item.kind] = item
elseif self.cosmetic_options_config[item.kind] == "multiple" then
self.selected_cosmetic_options[item.kind] = self.selected_cosmetic_options[item.kind] or {}
table.insert(self.selected_cosmetic_options[item.kind], item)
end
end
end
for _, e in ipairs(filters) do
if not e.percent or rng.percent(e.percent) then
local items = {}
if e.filter[1] == "all" then
for _, item in ipairs(self.cosmetic_options_flat) do
if (e.allow_mtx_pack or not item.mtx_pack) and item.kind == e.kind then items[#items+1] = item end
end
elseif e.filter[1] == "oneof" then
local list = table.reverse(e.filter[2])
for _, item in ipairs(self.cosmetic_options_flat) do
if (e.allow_mtx_pack or not item.mtx_pack) and item.kind == e.kind and list[item.name] then items[#items+1] = item end
end
elseif e.filter[1] == "findname" then
for _, item in ipairs(self.cosmetic_options_flat) do
if (e.allow_mtx_pack or not item.mtx_pack) and item.kind == e.kind and item.name:find(e.filter[2]) then items[#items+1] = item end
end
end
select(items)
end
end
end
function _M:isDescriptorSet(key, val)
return self.descriptors_by_type[key] == val
end
function _M:setDescriptor(key, val)
if key then
self.descriptors_by_type[key] = val
print("[BIRTHER] set descriptor", key, val)
end
self:updateDescriptors()
self:setTile()
local ok = self.c_name.text:len() >= 2
for i, o in ipairs(self.order) do
if not self.descriptors_by_type[o] then
ok = false
print("Missing ", o)
break
end
end
self:toggleDisplay(self.c_ok, ok)
end
function _M:isDescriptorAllowed(d, ignore_type)
self:updateDescriptors()
if type(ignore_type) == "string" then
ignore_type = {[ignore_type] = true}
end
ignore_type = ignore_type or {}
local allowed = true
local type = d.type
print("[BIRTHER] checking allowance for ", d.name, d.type, "::", table.serialize(ignore_type, nil, true))
for j, od in ipairs(self.descriptors) do
if od.descriptor_choices and od.descriptor_choices[type] and not ignore_type[type] then
local what = util.getval(od.descriptor_choices[type][d.name], self) or util.getval(od.descriptor_choices[type].__ALL__, self)
if what and what == "allow" then
allowed = true
elseif what and what == "nolore" then
allowed = "nolore"
elseif what and what == "allow-nochange" then
if not allowed then allowed = true end
elseif what and (what == "never" or what == "disallow") then
allowed = false
elseif what and what == "forbid" then
allowed = nil
end
print("[BIRTHER] test against ", od.name, "=>", what, allowed)
if allowed == nil then break end
end
end
if d.special_check and not d.special_check(self) then return nil end
-- Check it is allowed
return allowed and not d.never_show
end
function _M:getLock(d)
if not d.locked then return false end
local ret = d.locked(self)
if ret == "hide" then return "hide" end
return not ret
end
function _M:generateCampaigns()
local locktext = _t"\n\n#GOLD#This is a locked birth option. Performing certain actions and completing certain quests will make locked campaigns, races and classes permanently available."
local list = {}
for i, d in ipairs(self.birth_descriptor_def.world) do
if self:isDescriptorAllowed(d, {difficulty=true, permadeath=true, race=true, subrace=true, class=true, subclass=true}) then
local locked = self:getLock(d)
if locked == true then
list[#list+1] = { name = tstring{{"font", "italic"}, {"color", "GREY"}, _t"-- locked --", {"font", "normal"}}:toString(), id=d.name, locked=true, desc=util.getval(d.locked_desc, self)..locktext }
elseif locked == false then
local desc = d.desc
if type(desc) == "table" then desc = table.concat(d.desc, "\n") end
list[#list+1] = { name = tstring{d.display_name}:toString(), id=d.name, desc=desc }
if util.getval(d.selection_default) then self.default_campaign = d.name end
end
end
end
self.all_campaigns = list
if not self.default_campaign then self.default_campaign = list[1].id end
end
function _M:generateDifficulties()
local locktext = _t"\n\n#GOLD#This is a locked birth option. Performing certain actions and completing certain quests will make locked campaigns, races and classes permanently available."
local list = {}
local oldsel = nil
if self.c_difficulty then
oldsel = self.c_difficulty.c_list.list[self.c_difficulty.c_list.sel].id
end
for i, d in ipairs(self.birth_descriptor_def.difficulty) do
if self:isDescriptorAllowed(d, {permadeath=true, race=true, subrace=true, class=true, subclass=true}) then
local locked = self:getLock(d)
if locked == true then
list[#list+1] = { name = tstring{{"font", "italic"}, {"color", "GREY"}, _t"-- locked --", {"font", "normal"}}:toString(), id=d.name, locked=true, desc=util.getval(d.locked_desc, self)..locktext }
elseif locked == false then
local desc = d.desc
if type(desc) == "table" then desc = table.concat(d.desc, "\n") end
list[#list+1] = { name = tstring{d.display_name}:toString(), id=d.name, desc=desc }
if oldsel == d.name then oldsel = #list end
if util.getval(d.selection_default) then self.default_difficulty = d.name end
end
end
end
self.all_difficulties = list
if self.c_difficulty then
self.c_difficulty.c_list.list = self.all_difficulties
self.c_difficulty.c_list:generate()
if type(oldsel) == "number" then self.c_difficulty.c_list.sel = oldsel end
end
end
function _M:generatePermadeaths()
local locktext = _t"\n\n#GOLD#This is a locked birth option. Performing certain actions and completing certain quests will make locked campaigns, races and classes permanently available."
local list = {}
local oldsel = nil
if self.c_permadeath then
oldsel = self.c_permadeath.c_list.list[self.c_permadeath.c_list.sel].id
end
for i, d in ipairs(self.birth_descriptor_def.permadeath) do
if self:isDescriptorAllowed(d, {race=true, subrace=true, class=true, subclass=true}) then
local locked = self:getLock(d)
if locked == true then
list[#list+1] = { name = tstring{{"font", "italic"}, {"color", "GREY"}, _t"-- locked --", {"font", "normal"}}:toString(), id=d.name, locked=true, desc=util.getval(d.locked_desc, self)..locktext, locked_select=d.locked_select }
elseif locked == false then
local desc = d.desc
if type(desc) == "table" then desc = table.concat(d.desc, "\n") end
list[#list+1] = { name = tstring{d.display_name}:toString(), id=d.name, desc=desc }
if oldsel == d.name then oldsel = #list end
if util.getval(d.selection_default) then self.default_permadeath = d.name end
end
end
end
self.all_permadeaths = list
if self.c_permadeath then
self.c_permadeath.c_list.list = self.all_permadeaths
self.c_permadeath.c_list:generate()
if type(oldsel) == "number" then self.c_permadeath.c_list.sel = oldsel end
end
end
function _M:generateRaces()
local locktext = _t"\n\n#GOLD#This is a locked birth option. Performing certain actions and completing certain quests will make locked campaigns, races and classes permanently available."
local oldtree = {}
for i, t in ipairs(self.all_races or {}) do oldtree[t.id] = t.shown end
local tree = {}
local newsel = nil
for i, d in ipairs(self.birth_descriptor_def.race) do
if self:isDescriptorAllowed(d, {class=true, subclass=true}) then
local nodes = {}
for si, sd in ipairs(self.birth_descriptor_def.subrace) do
if d.descriptor_choices.subrace[sd.name] == "allow" then
local locked = self:getLock(sd)
if locked == true then
nodes[#nodes+1] = { name = tstring{{"font", "italic"}, {"color", "GREY"}, _t"-- locked --", {"font", "normal"}}, id=sd.name, pid=d.name, locked=true, desc=util.getval(sd.locked_desc, self)..locktext }
elseif locked == false then
local desc = sd.desc
if type(desc) == "table" then desc = table.concat(sd.desc, "\n") end
nodes[#nodes+1] = { name = sd.display_name, basename = sd.display_name, id=sd.name, pid=d.name, desc=desc }
if self.sel_race and self.sel_race.id == sd.name then newsel = nodes[#nodes] end
end
end
end
local locked = self:getLock(d)
if locked == true then
tree[#tree+1] = { name = tstring{{"font", "italic"}, {"color", "GREY"}, _t"-- locked --", {"font", "normal"}}, id=d.name, shown = oldtree[d.name], nodes = nodes, locked=true, desc=util.getval(d.locked_desc, self)..locktext }
elseif locked == false then
local desc = d.desc
if type(desc) == "table" then desc = table.concat(d.desc, "\n") end
tree[#tree+1] = { name = tstring{{"font", "italic"}, {"color", "LIGHT_SLATE"}, d.display_name, {"font", "normal"}}, id=d.name, shown = oldtree[d.name], nodes = nodes, desc=desc }
end
end
end
self.all_races = tree
if self.c_race then
self.c_race.tree = self.all_races
self.c_race:generate()
if newsel then self:raceUse(newsel)
else
self.sel_race = nil
self:setDescriptor("race", nil)
self:setDescriptor("subrace", nil)
end
if self.descriptors_by_type.difficulty == "Tutorial" then
self:raceUse(tree[1], 1)
self:raceUse(tree[1].nodes[1], 2)
end
end
end
function _M:generateClasses()
local locktext = _t"\n\n#GOLD#This is a locked birth option. Performing certain actions and completing certain quests will make locked campaigns, races and classes permanently available."
local oldtree = {}
for i, t in ipairs(self.all_classes or {}) do oldtree[t.id] = t.shown end
local tree = {}
local newsel = nil
for i, d in ipairs(self.birth_descriptor_def.class) do
if self:isDescriptorAllowed(d, {subclass=true}) then
local nodes = {}
for si, sd in ipairs(self.birth_descriptor_def.subclass) do
if (d.descriptor_choices.subclass[sd.name] == "allow" or d.descriptor_choices.subclass[sd.name] == "allow-nochange" or d.descriptor_choices.subclass[sd.name] == "nolore") and self:isDescriptorAllowed(sd, {subclass=true, class=true}) then
local locked = self:getLock(sd)
if locked == true then
nodes[#nodes+1] = { name = tstring{{"font", "italic"}, {"color", "GREY"}, _t"-- locked --", {"font", "normal"}}, id=sd.name, pid=d.name, locked=true, desc=util.getval(sd.locked_desc, self)..locktext }
elseif locked == false then
local old = self.descriptors_by_type.subclass
self.descriptors_by_type.subclass = nil
local how = self:isDescriptorAllowed(sd, {class=true})
self.descriptors_by_type.subclass = old
local desc = sd.desc
if type(desc) == "table" then desc = table.concat(sd.desc, "\n") end
if how == "nolore" and self.descriptors_by_type.subrace then
desc = _t"#CRIMSON#Playing this class with the race you selected does not make much sense lore-wise. You can still do it but might miss on some special quests/...#WHITE#\n" .. desc
end
nodes[#nodes+1] = { name = sd.display_name, basename=sd.display_name, id=sd.name, pid=d.name, desc=desc, def=sd }
if self.sel_class and self.sel_class.id == sd.name then newsel = nodes[#nodes] end
end
end
end
local locked = self:getLock(d)
if locked == true then
tree[#tree+1] = { name = tstring{{"font", "italic"}, {"color", "GREY"}, _t"-- locked --", {"font", "normal"}}, id=d.name, shown=oldtree[d.name], nodes = nodes, locked=true, desc=util.getval(d.locked_desc, self)..locktext }
elseif locked == false then
local desc = d.desc
if type(desc) == "table" then desc = table.concat(d.desc, "\n") end
tree[#tree+1] = { name = tstring{{"font", "italic"}, {"color", "LIGHT_SLATE"}, d.display_name, {"font", "normal"}}, id=d.name, shown=oldtree[d.name], nodes = nodes, desc=desc }
end
end
end
self.all_classes = tree
if self.c_class then
self.c_class.tree = self.all_classes
self.c_class:generate()
if newsel then self:classUse(newsel)
else
self.sel_class = nil
self:setDescriptor("class", nil)
self:setDescriptor("subclass", nil)
end
if self.descriptors_by_type.difficulty == "Tutorial" then
self:classUse(tree[1], 1)
self:classUse(tree[1].nodes[1], 2)
elseif tree[1].id == "None" then
self:classUse(tree[1], 1)
self:classUse(tree[1].nodes[1], 2)
end
end
end
function _M:loadPremade(pm)
local fallback = pm.force_fallback
-- Load the entities directly
if not fallback and pm.module_version and pm.module_version[1] == game.__mod_info.version[1] and pm.module_version[2] == game.__mod_info.version[2] and pm.module_version[3] == game.__mod_info.version[3] then
savefile_pipe:ignoreSaveToken(true)
local qb = savefile_pipe:doLoad(pm.short_name, "entity", "engine.CharacterVaultSave", "character")
savefile_pipe:ignoreSaveToken(false)
-- Load the player directly
if qb then
game.party = qb
game.player = nil
game.party:setPlayer(1, true)
self.c_name:setText(game.player.name)
self:atEnd("loaded")
else
fallback = true
end
else
fallback = true
end
-- Fill in the descriptors and validate
if fallback then
local ok = 0
-- Name
self.c_name:setText(pm.short_name)
-- Sex
self.c_male.checked = pm.descriptors.sex == "Male"
self.c_female.checked = pm.descriptors.sex == "Female"
self:setDescriptor("sex", pm.descriptors.sex and "Male" or "Female")
-- Campaign
for i, item in ipairs(self.all_campaigns) do if not item.locked and item.id == pm.descriptors.world then
self:campaignUse(item)
self.c_campaign.c_list.sel = i
ok = ok + 1
break
end end
-- Difficulty
for i, item in ipairs(self.all_difficulties) do if not item.locked and item.id == pm.descriptors.difficulty then
self:difficultyUse(item)
self.c_difficulty.c_list.sel = i
ok = ok + 1
break
end end
-- Permadeath
for i, item in ipairs(self.all_permadeaths) do if not item.locked and item.id == pm.descriptors.permadeath then
self:permadeathUse(item)
self.c_permadeath.c_list.sel = i
ok = ok + 1
break
end end
-- Race
for i, pitem in ipairs(self.all_races) do
for j, item in ipairs(pitem.nodes) do
if not item.locked and item.id == pm.descriptors.subrace and pitem.id == pm.descriptors.race then
self:raceUse(pitem)
self:raceUse(item)
ok = ok + 1
break
end
end
end
-- Class
for i, pitem in ipairs(self.all_classes) do
for j, item in ipairs(pitem.nodes) do
if not item.locked and item.id == pm.descriptors.subclass and pitem.id == pm.descriptors.class then
self:classUse(pitem)
self:classUse(item)
ok = ok + 1
break
end
end
end
if ok == 4 then self:atEnd("created") end
end
end
function _M:loadPremadeUI()
local lss = Module:listVaultSavesForCurrent()
local d = Dialog.new(_t"Characters Vault", 600, 550)
local sel = nil
local sep = Separator.new{dir="horizontal", size=400}
local desc = TextzoneList.new{width=220, height=400}
local list list = List.new{width=350, list=lss, height=400,
fct=function(item)
local oldsel, oldscroll = list.sel, list.scroll
if sel == item then self:loadPremade(sel) game:unregisterDialog(d) end
if sel then sel.color = nil end
item.color = colors.simple(colors.LIGHT_GREEN)
sel = item
list:generate()
list.sel, list.scroll = oldsel, oldscroll
end,
select=function(item) desc:switchItem(item, item.description) end
}
local load = Button.new{text=_t" Load ", fct=function() if sel then self:loadPremade(sel) game:unregisterDialog(d) end end}
local del = Button.new{text=_t"Delete", fct=function() if sel then
self:yesnoPopup(sel.name, ("Really delete premade: %s"):tformat(sel.name), function(ret) if ret then
local vault = CharacterVaultSave.new(sel.short_name)
vault:delete()
vault:close()
lss = Module:listVaultSavesForCurrent()
list.list = lss
list:generate()
sel = nil
end end)
end end}
d:loadUI{
{left=0, top=0, ui=list},
{left=list, top=0, ui=sep},
{right=0, top=0, ui=desc},
{left=0, bottom=0, ui=load},
{right=0, bottom=0, ui=del},
}
d:setupUI(true, true)
d.key:addBind("EXIT", function() game:unregisterDialog(d) end)
game:registerDialog(d)
end
-- Disable stuff from the base Birther
function _M:updateList() end
function _M:selectType(type) end
function _M:on_register()
if __module_extra_info.auto_quickbirth then
local qb_short_name = __module_extra_info.auto_quickbirth:gsub("[^a-zA-Z0-9_-.]", "_")
local lss = Module:listVaultSavesForCurrent()
for i, pm in ipairs(lss) do
if pm.short_name == qb_short_name then
self:loadPremade(pm)
break
end
end
end
end
-- Display the player tile
function _M:innerDisplay(x, y, nb_keyframes)
if self.actor.image then
self.actor:toScreen(self.tiles, x + self.iw - 64, y, 64, 64)
elseif self.actor.image and self.actor.add_mos then
self.actor:toScreen(self.tiles, x + self.iw - 64, y - 64, 128, 64)
end
if self.descriptors_by_type.subclass then
local sc = self.birth_descriptor_def.subclass[self.descriptors_by_type.subclass]
if sc.display_entity128 then
sc.display_entity128:toScreen(self.tiles, x + self.iw - self.c_desc.w, y + 16, 64, 64)
end
end
end
--- Fake a body & starting equipment
function _M:fakeEquip(v)
-- if not v then
-- self.actor.body = nil
-- self.actor.inven = {}
-- else
self.actor.inven = {}
local fake_body = { INVEN = 1000, QS_MAINHAND = 1, QS_OFFHAND = 1, MAINHAND = 1, OFFHAND = 1, FINGER = 2, NECK = 1, LITE = 1, BODY = 1, HEAD = 1, CLOAK = 1, HANDS = 1, BELT = 1, FEET = 1, TOOL = 1, QUIVER = 1 }
self.actor.body = fake_body
self.actor:initBody()
local c = self.birth_descriptor_def.class[self.descriptors_by_type.class or "Warrior"]
local sc = self.birth_descriptor_def.subclass[self.descriptors_by_type.subclass or "Berserker"]
local function apply_equip(r)
for i, f in ipairs(r[1]) do
local o = self.obj_list_by_name[f.name]
if o and o.slot then
o = o:clone()
o:resolve()
o:resolve(nil, true)
local inven = self.actor:getInven(o.slot)
if inven[1] and o.offslot then inven = self.actor:getInven(o.offslot) end
if not inven[1] then inven[1] = o end
end
end
end
if v then
for _, r in pairs(c.copy or {}) do if type(r) == "table" and r.__resolver == "equip" then apply_equip(r) end end
for _, r in pairs(sc.copy or {}) do if type(r) == "table" and r.__resolver == "equip" then apply_equip(r) end end
end
-- end
end
function _M:resetAttachementSpots()
self.actor.attachement_spots = nil
if self.has_custom_tile then
self.actor.attachement_spots = self.has_custom_tile.f
return
end
local dbr = self.birth_descriptor_def.race[self.descriptors_by_type.race or "Human"]
local dr = self.birth_descriptor_def.subrace[self.descriptors_by_type.subrace or "Cornac"]
local ds = self.birth_descriptor_def.sex[self.descriptors_by_type.sex or "Female"]
local moddable_attachement_spots = dr.moddable_attachement_spots or dbr.moddable_attachement_spots
local moddable_attachement_spots_sexless = dr.moddable_attachement_spots_sexless or dbr.moddable_attachement_spots_sexless
if moddable_attachement_spots then
if moddable_attachement_spots_sexless then self.actor.attachement_spots = "dolls_"..moddable_attachement_spots.."_all"
elseif self.descriptors_by_type.sex == "Female" then self.actor.attachement_spots = "dolls_"..moddable_attachement_spots.."_female"
else self.actor.attachement_spots = "dolls_"..moddable_attachement_spots.."_male"
end
end
end
function _M:setTile(f, w, h, last, nude)
self.actor:removeAllMOs()
if not f then
if not self.has_custom_tile then
local dbr = self.birth_descriptor_def.race[self.descriptors_by_type.race or "Human"]
local dr = self.birth_descriptor_def.subrace[self.descriptors_by_type.subrace or "Cornac"]
local ds = self.birth_descriptor_def.sex[self.descriptors_by_type.sex or "Female"]
local drc = dr.copy or {}
local dbrc = dbr.copy or {}
self.actor.image = "player/"..(self.descriptors_by_type.subrace or "Cornac"):lower():gsub("[^a-z0-9_]", "_").."_"..(self.descriptors_by_type.sex or "Female"):lower():gsub("[^a-z0-9_]", "_")..".png"
self.actor.add_mos = nil
self.actor.female = ds.copy.female
self.actor.male = ds.copy.male
self.actor.moddable_tile = drc.moddable_tile
self.actor.moddable_tile_base = drc.moddable_tile_base
self.actor.moddable_tile_ornament = drc.moddable_tile_ornament
self.actor.moddable_tile_ornament2 = drc.moddable_tile_ornament2
self.actor.moddable_tile_nude = drc.moddable_tile_nude or dbrc.moddable_tile_nude
end
else
self.actor.make_tile = nil
self.actor.moddable_tile = nil
if h > w then
self.actor.image = "invis.png"
self.actor.add_mos = {{image=f, display_h=2, display_y=-1}}
else
self.actor.add_mos = nil
self.actor.image = f
end
self.has_custom_tile = {f=f,w=w,h=h}
end
self:resetAttachementSpots()
if not last then
-- Add an example particles if any
local ps = self.actor:getParticlesList("all")
for i, p in ipairs(ps) do self.actor:removeParticles(p) end
if self.actor.shader_auras then self.actor.shader_auras = {} end
self.replace_display = nil
if not nude then
if self.descriptors_by_type.subclass then
local d = self.birth_descriptor_def.subclass[self.descriptors_by_type.subclass]
if d and d.birth_example_particles then
local p = d.birth_example_particles
if type(p) == "table" then p = rng.table(p) end
p = util.getval(p, self.actor, self)
if type(p) == "string" then self.actor:addParticles(Particles.new(p, 1)) end
end
end
end
self:fakeEquip(not nude and true or false)
self:applyCosmeticActor(false)
self.actor:updateModdableTile()
self:fakeEquip(false)
else
self:applyCosmeticActor(true)
end
end
function _M:applyCosmeticActor(last)
for i, d in ipairs(self.to_reset_cosmetic) do
d(self.actor)
end
self.to_reset_cosmetic = {}
self.actor.moddable_tile_hair = nil
self.actor.moddable_tile_facial_features = nil
self.actor.moddable_tile_tatoo = nil
self.actor.moddable_tile_horn = nil
-- Grab defaults if we have no custom selection
local cosmetics = self.selected_cosmetic_options
if not cosmetics then
cosmetics = {}
local function finder(kind, name, only_for)
if only_for then for k, v in pairs(only_for) do
if self.descriptors_by_type[k] ~= v then return end
end end
for i, d in ipairs(self.cosmetic_options_flat) do
if d.kind == kind and d.name == name then return d end
end
end
local dbr = self.birth_descriptor_def.race[self.descriptors_by_type.race or "Human"]
local dr = self.birth_descriptor_def.subrace[self.descriptors_by_type.subrace or "Cornac"]
local default_cosmetics = nil
if dr.default_cosmetics then default_cosmetics = dr.default_cosmetics
elseif dbr.default_cosmetics then default_cosmetics = dbr.default_cosmetics
end
if default_cosmetics then for _, d in ipairs(default_cosmetics) do
if self.cosmetic_options_config[d[1]] == "single" then
local c = finder(d[1], d[2], d[3])
if c then cosmetics[d[1]] = c end
elseif self.cosmetic_options_config[d[1]] == "multiple" then
cosmetics[d[1]] = cosmetics[d[1]] or {}
local c = finder(d[1], d[2], d[3])
if c then table.insert(cosmetics[d[1]], c) end
end
end end
end
-- Apply!
for kind, d in pairs(cosmetics) do
if kind == "hairs" then
self.actor.moddable_tile_hair = d.file
elseif kind == "tatoos" then
self.actor.moddable_tile_tatoo = d.file
elseif kind == "horns" then
self.actor.moddable_tile_horn = d.file
elseif kind == "skin" then
self.actor.moddable_tile_base = d.file..".png"
elseif kind == "facial_features" then
for _, dd in ipairs(d) do
self.actor.moddable_tile_facial_features = self.actor.moddable_tile_facial_features or {}
table.insert(self.actor.moddable_tile_facial_features, dd.file)
end
end
if self.cosmetic_options_config[kind] == "multiple" then
for _, dd in ipairs(d) do if dd.on_actor then
dd.on_actor(self.actor, self, last)
if not last and dd.reset then
self.to_reset_cosmetic[#self.to_reset_cosmetic+1] = dd.reset
end
end end
elseif self.cosmetic_options_config[kind] == "single" then
if d.on_actor then
d.on_actor(self.actor, self, last)
if not last and d.reset then
self.to_reset_cosmetic[#self.to_reset_cosmetic+1] = d.reset
end
end
end
end
end
function _M:selectExplorationNoDonations()
Dialog:yesnoLongPopup(_t"Exploration mode",
_t[[Exploration mode provides the characters using it with infinite lives.
Tales of Maj'Eyal is meant to be a very replayable game in which you get better by learning from mistakes (and thus from dying too).
I realize this can not please everybody and after multiple requests I have decided to grant exploration mode to donators, because it will allow player that like the game to see it all if they wish.
Beware though, infinite lives does not mean the difficulty is reduced, only that you can try as much as you want without restarting.
If you'd like to use this feature and find this game good you should consider donating. It will help ensure its survival.
While this is a free game that I am doing for fun, if it can help feed my family a bit I certainly will not complain as real life can be harsh sometimes.
You will need an online profile active and connected for the tile selector to enable. If you choose to donate now you will need to restart the game to be granted access.
Donators will also gain access to the custom tiles for their characters.]], 400, function(ret)
if not ret then
game:registerDialog(require("mod.dialogs.Donation").new("exploration-mode"))
end
end, _t"Later", _t"Donate!")
end
function _M:selectTileNoDonations()
Dialog:yesnoLongPopup(_t"Custom tiles",
_t[[Custom Tiles have been added as a thank you to everyone that has donated to ToME.
They are a fun cosmetic feature that allows you to choose a tile for your character from a list of nearly 180 (with more to be added over time), ranging from special humanoid tiles to downright wonky ones!
If you'd like to use this feature and find this game good you should consider donating. It will help ensure its survival.
While this is a free game that I am doing for fun, if it can help feed my family a bit I certainly will not complain as real life can be harsh sometimes.
You will need an online profile active and connected for the tile selector to enable. If you choose to donate now you will need to restart the game to be granted access.
Donators will also gain access to the Exploration Mode featuring infinite lives.]], 400, function(ret)
if not ret then
game:registerDialog(require("mod.dialogs.Donation").new("custom-tiles"))
end
end, _t"Later", _t"Donate!")
end
function _M:selectTile()
local d = Dialog.new(_t"Select a Tile", 600, 550)
local list = {
"npc/alchemist_golem.png",
"npc/armored_skeleton_warrior.png",
"npc/barrow_wight.png",
"npc/construct_golem_alchemist_golem.png",
"npc/degenerated_skeleton_warrior.png",
"npc/elder_vampire.png",
"npc/emperor_wight.png",
"npc/forest_wight.png",
"npc/golem.png",
"npc/grave_wight.png",
"npc/horror_corrupted_dremling.png",
"npc/horror_corrupted_drem_master.png",
"npc/horror_eldritch_headless_horror.png",
"npc/horror_eldritch_luminous_horror.png",
"npc/horror_eldritch_worm_that_walks.png",
"npc/horror_temporal_cronolith_clone.png",
"npc/humanoid_dwarf_dwarven_earthwarden.png",
"npc/humanoid_dwarf_dwarven_guard.png",
"npc/humanoid_dwarf_dwarven_paddlestriker.png",
"npc/humanoid_dwarf_dwarven_summoner.png",
"npc/humanoid_dwarf_lumberjack.png",
"npc/humanoid_dwarf_norgan.png",
"npc/humanoid_dwarf_ziguranth_warrior.png",
"npc/humanoid_elenulach_thief.png",
"npc/humanoid_elf_anorithil.png",
"npc/humanoid_elf_elven_archer.png",
"npc/humanoid_elf_elven_sun_mage.png",
"npc/humanoid_elf_fillarel_aldaren.png",
"npc/humanoid_elf_limmir_the_jeweler.png",
"npc/humanoid_elf_star_crusader.png",
"npc/humanoid_halfling_derth_guard.png",
"npc/humanoid_halfling_halfling_citizen.png",
"npc/humanoid_halfling_halfling_gardener.png",
"npc/humanoid_halfling_halfling_guard.png",
"npc/humanoid_halfling_halfling_slinger.png",
"npc/humanoid_halfling_master_slinger.png",
"npc/humanoid_halfling_protector_myssil.png",
"npc/humanoid_halfling_sm_halfling.png",
"npc/humanoid_human_alchemist.png",
"npc/humanoid_human_aluin_the_fallen.png",
"npc/humanoid_human_apprentice_mage.png",
"npc/humanoid_human_arcane_blade.png",
"npc/humanoid_human_argoniel.png",
"npc/humanoid_human_assassin.png",
"npc/humanoid_human_bandit_lord.png",
"npc/humanoid_human_bandit.png",
"npc/humanoid_human_ben_cruthdar__the_cursed.png",
"npc/humanoid_human_blood_mage.png",
"npc/humanoid_human_celia.png",
"npc/humanoid_human_cryomancer.png",
"npc/humanoid_human_cutpurse.png",
"npc/humanoid_human_derth_guard.png",
"npc/humanoid_human_enthralled_slave.png",
"npc/humanoid_human_fallen_sun_paladin_aeryn.png",
"npc/humanoid_human_fire_wyrmic.png",
"npc/humanoid_human_fryjia_loren.png",
"npc/humanoid_human_geomancer.png",
"npc/humanoid_human_gladiator.png",
"npc/humanoid_human_great_gladiator.png",
"npc/humanoid_human_harno__herald_of_last_hope.png",
"npc/humanoid_human_hexer.png",
"npc/humanoid_human_high_gladiator.png",
"npc/humanoid_human_high_slinger.png",
"npc/humanoid_human_high_sun_paladin_aeryn.png",
"npc/humanoid_human_high_sun_paladin_rodmour.png",
"npc/humanoid_human_human_citizen.png",
"npc/humanoid_human_human_farmer.png",
"npc/humanoid_human_human_guard.png",
"npc/humanoid_human_human_sun_paladin.png",
"npc/humanoid_human_ice_wyrmic.png",
"npc/humanoid_human_last_hope_guard.png",
"npc/humanoid_human_linaniil_supreme_archmage.png",
"npc/humanoid_human_lumberjack.png",
"npc/humanoid_human_martyr.png",
"npc/humanoid_human_master_alchemist.png",
"npc/humanoid_human_multihued_wyrmic.png",
"npc/humanoid_human_necromancer.png",
"npc/humanoid_human_pyromancer.png",
"npc/humanoid_human_reaver.png",
"npc/humanoid_human_rej_arkatis.png",
"npc/humanoid_human_riala_shalarak.png",
"npc/humanoid_human_rogue.png",
"npc/humanoid_human_sand_wyrmic.png",
"npc/humanoid_human_shadowblade.png",
"npc/humanoid_human_shady_cornac_man.png",
"npc/humanoid_human_slave_combatant.png",
"npc/humanoid_human_slinger.png",
"npc/humanoid_human_spectator02.png",
"npc/humanoid_human_spectator03.png",
"npc/humanoid_human_spectator.png",
"npc/humanoid_human_storm_wyrmic.png",
"npc/humanoid_human_subject_z.png",
"npc/humanoid_human_sun_paladin_guren.png",
"npc/humanoid_human_tannen.png",
"npc/humanoid_human_tempest.png",
"npc/humanoid_human_thief.png",
"npc/humanoid_human_trickster.png",
"npc/humanoid_human_urkis__the_high_tempest.png",
"npc/humanoid_human_valfred_loren.png",
"npc/humanoid_human_ziguranth_wyrmic.png",
"npc/humanoid_orc_brotoq_the_reaver.png",
"npc/humanoid_orc_fiery_orc_wyrmic.png",
"npc/humanoid_orc_golbug_the_destroyer.png",
"npc/humanoid_orc_gorbat__supreme_wyrmic_of_the_pride.png",
"npc/humanoid_orc_grushnak__battlemaster_of_the_pride.png",
"npc/humanoid_orc_icy_orc_wyrmic.png",
"npc/humanoid_orc_krogar.png",
"npc/humanoid_orc_massok_the_dragonslayer.png",
"npc/humanoid_orc_orc_archer.png",
"npc/humanoid_orc_orc_assassin.png",
"npc/humanoid_orc_orc_berserker.png",
"npc/humanoid_orc_orc_blood_mage.png",
"npc/humanoid_orc_orc_corruptor.png",
"npc/humanoid_orc_orc_cryomancer.png",
"npc/humanoid_orc_orc_elite_berserker.png",
"npc/humanoid_orc_orc_elite_fighter.png",
"npc/humanoid_orc_orc_fighter.png",
"npc/humanoid_orc_orc_grand_master_assassin.png",
"npc/humanoid_orc_orc_grand_summoner.png",
"npc/humanoid_orc_orc_high_cryomancer.png",
"npc/humanoid_orc_orc_high_pyromancer.png",
"npc/humanoid_orc_orc_mage_hunter.png",
"npc/humanoid_orc_orc_master_assassin.png",
"npc/humanoid_orc_orc_master_wyrmic.png",
"npc/humanoid_orc_orc_necromancer.png",
"npc/humanoid_orc_orc_pyromancer.png",
"npc/humanoid_orc_orc_soldier.png",
"npc/humanoid_orc_orc_summoner.png",
"npc/humanoid_orc_orc_warrior.png",
"npc/humanoid_orc_rak_shor_cultist.png",
"npc/humanoid_orc_rak_shor__grand_necromancer_of_the_pride.png",
"npc/humanoid_orc_ukruk_the_fierce.png",
"npc/humanoid_orc_vor__grand_geomancer_of_the_pride.png",
"npc/humanoid_orc_warmaster_gnarg.png",
"npc/humanoid_shalore_archmage_tarelion.png",
"npc/humanoid_shalore_elandar.png",
"npc/humanoid_shalore_elvala_guard.png",
"npc/humanoid_shalore_elven_blood_mage.png",
"npc/humanoid_shalore_elven_corruptor.png",
"npc/humanoid_shalore_elven_cultist.png",
"npc/humanoid_shalore_elven_elite_warrior.png",
"npc/humanoid_shalore_elven_guard.png",
"npc/humanoid_shalore_elven_mage.png",
"npc/humanoid_shalore_elven_tempest.png",
"npc/humanoid_shalore_elven_warrior.png",
"npc/humanoid_shalore_grand_corruptor.png",
"npc/humanoid_shalore_mean_looking_elven_guard.png",
"npc/humanoid_shalore_rhaloren_inquisitor.png",
"npc/humanoid_shalore_shalore_rune_master.png",
"npc/humanoid_thalore_thalore_hunter.png",
"npc/humanoid_thalore_thalore_wilder.png",
"npc/humanoid_thalore_ziguranth_summoner.png",
"npc/humanoid_yaech_blood_master.png",
"npc/humanoid_yaech_murgol__the_yaech_lord.png",
"npc/humanoid_yaech_slaver.png",
"npc/humanoid_yaech_yaech_diver.png",
"npc/humanoid_yaech_yaech_hunter.png",
"npc/humanoid_yaech_yaech_mindslayer.png",
"npc/humanoid_yaech_yaech_psion.png",
"npc/humanoid_yeek_yeek_wayist.png",
"npc/humanoid_yeek_yeek_summoner.png",
"npc/humanoid_yeek_yeek_psionic.png",
"npc/humanoid_yeek_yeek_mindslayer.png",
"npc/humanoid_yeek_yeek_commoner_01.png",
"npc/humanoid_yeek_yeek_commoner_02.png",
"npc/humanoid_yeek_yeek_commoner_03.png",
"npc/humanoid_yeek_yeek_commoner_04.png",
"npc/humanoid_yeek_yeek_commoner_05.png",
"npc/humanoid_yeek_yeek_commoner_06.png",
"npc/humanoid_yeek_yeek_commoner_07.png",
"npc/humanoid_yeek_yeek_commoner_08.png",
"npc/jawa_01.png",
"npc/lesser_vampire.png",
"npc/master_skeleton_archer.png",
"npc/master_skeleton_warrior.png",
"npc/master_vampire.png",
"npc/skeleton_archer.png",
"npc/skeleton_mage.png",
"npc/skeleton_warrior.png",
"npc/undead_skeleton_cowboy.png",
"npc/the_master.png",
"npc/vampire_lord.png",
"npc/vampire.png",
"npc/undead_skeleton_filio_flightfond.png",
"npc/undead_ghoul_borfast_the_broken.png",
"npc/horror_eldritch_umbral_horror.png",
"npc/demon_major_general_of_urh_rok.png",
"npc/demon_major_shasshhiy_kaish.png",
"npc/undead_vampire_arch_zephyr.png",
"npc/undead_ghoul_rotting_titan.png",
"npc/humanoid_human_townsfolk_aimless_looking_merchant01_64.png",
"npc/humanoid_human_townsfolk_battlescarred_veteran01_64.png",
"npc/humanoid_human_townsfolk_blubbering_idiot01_64.png",
"npc/humanoid_human_townsfolk_boilcovered_wretch01_64.png",
"npc/humanoid_human_townsfolk_farmer_maggot01_64.png",
"npc/humanoid_human_townsfolk_filthy_street_urchin01_64.png",
"npc/humanoid_human_townsfolk_mangy_looking_leper01_64.png",
"npc/humanoid_human_townsfolk_meanlooking_mercenary01_64.png",
"npc/humanoid_human_townsfolk_pitiful_looking_beggar01_64.png",
"npc/humanoid_human_townsfolk_singing_happy_drunk01_64.png",
"npc/humanoid_human_townsfolk_squinteyed_rogue01_64.png",
"npc/humanoid_human_townsfolk_village_idiot01_64.png",
"npc/humanoid_naga_lady_zoisla_the_tidebringer.png",
"npc/humanoid_naga_naga_nereid.png",
"npc/humanoid_naga_naga_tidecaller.png",
"npc/humanoid_naga_naga_tidewarden.png",
"npc/humanoid_naga_slasul.png",
"npc/naga_myrmidon_2.png",
"npc/naga_myrmidon_no_armor.png",
"npc/naga_myrmidon.png",
"npc/naga_psyren2_2.png",
"npc/naga_psyren2.png",
"npc/naga_psyren.png",
"npc/naga_tide_huntress_2.png",
"npc/naga_tide_huntress.png",
"npc/snowman01.png",
"npc/snaproot_pimp.png",
"npc/R2D2_01.png",
"npc/humanoid_female_sluttymaid.png",
"npc/humanoid_male_sluttymaid.png",
"player/ascii_player_dorfhelmet_01_64.png",
"player/ascii_player_fedora_feather_04_64.png",
"player/ascii_player_helmet_02_64.png",
"player/ascii_player_mario_01_64.png",
"player/ascii_player_rogue_cloak_01_64.png",
"player/ascii_player_wizardhat_01_64.png",
"player/ascii_player_gentleman_01_64.png",
"player/ascii_player_red_hood_01.png",
"player/ascii_player_pink_amazone_01.png",
"player/ascii_player_bunny_01.png",
"player/ascii_player_exotic_01.png",
"player/ascii_player_shopper_01.png",
"player/original_handdrawn_player.png",
}
fs.mkdir("/data/gfx/custom-tiles/")
for file in fs.iterate("/data/gfx/custom-tiles/", function(file) return file:find("%.png") end) do
list[#list+1] = "custom-tiles/"..file
end
self:triggerHook{"Birther:donatorTiles", list=list}
local remove = Button.new{text=_t"Use default tile", width=240, fct=function()
game:unregisterDialog(d)
self.has_custom_tile = nil
self:setTile()
end}
local custom = Button.new{text=_t"Use custom-made tile", width=240, fct=function()
self:simpleLongPopup(_t"Howto: Custom-made tiles", ([[You can use your own custom tiles if you are a donator.
For the game to use them you must simply respect a few rules:
- they must be 64x64 or 64x128 tiles
- they must be saved as PNG files
- you must place them in folder #LIGHT_BLUE#%s#WHITE#
Once you have done so, simply restart the game and the tiles will be listed at the bottom of the list.]]):tformat(fs.getRealPath("/data/gfx/custom-tiles/")), 500)
end}
local list = ImageList.new{width=500, height=500, tile_w=64, tile_h=64, padding=10, scrollbar=true, list=list, fct=function(item)
game:unregisterDialog(d)
if not self:isDonator() then
self:selectTileNoDonations()
else
self:setTile(item.f, item.w, item.h)
end
end}
d:loadUI{
{left=0, top=0, ui=list},
{left=0, bottom=0, ui=remove},
{left=250, bottom=0, ui=custom},
}
d:setupUI(true, true)
d.key:addBind("EXIT", function() game:unregisterDialog(d) end)
game:registerDialog(d)
end
function _M:isDonator()
return profile:isDonator(1)
end
function _M:customizeOptions(cosmetic_actor, on_exit, title)
if not cosmetic_actor then
cosmetic_actor = self.actor:cloneFull()
end
local function cosmeticSetTile(...)
local oldactor = self.actor
self.actor = cosmetic_actor
self:setTile(...)
self.actor = oldactor
end
local d = Dialog.new(title or _t"Cosmetic Options", 600, 550)
local sel = nil
local list list = TreeList.new{width=450, tree=self.cosmetic_options, height=400, scrollbar=true, all_clicks=true,
columns={
{name=_t"Name", width=100, display_prop="name"},
},
fct=function(item, sel, button)
if item.nodes then
list:treeExpand(nil, item)
return
end
if button == "right" then
local function recurs(list)
for _, ii in ipairs(list.nodes or list) do
if ii == item then return list end
if ii.nodes then local r = recurs(ii) if r then return r end end
end
end
local ii = recurs(self.cosmetic_options)
if ii then
list:treeExpand(false, ii)
end
return
end
if not self:isDonator() then
self:yesnoPopup(_t"Donator Feature", _t"Cosmetic customization is a donator-only feature.", function(ret) if ret then
game:registerDialog(require("mod.dialogs.Donation").new())
end end, _t"I want to help!", _t"Dismiss")
end
local selected = false
self.selected_cosmetic_options = self.selected_cosmetic_options or {}
if self.cosmetic_options_config[item.kind] == "single" then
if self.selected_cosmetic_options[item.kind] == item then selected = false self.selected_cosmetic_options[item.kind] = nil
else selected = true self.selected_cosmetic_options[item.kind] = item end
elseif self.cosmetic_options_config[item.kind] == "multiple" then
self.selected_cosmetic_options[item.kind] = self.selected_cosmetic_options[item.kind] or {}
if table.hasInList(self.selected_cosmetic_options[item.kind], item) then selected = false table.removeFromList(self.selected_cosmetic_options[item.kind], item)
else selected = true table.insert(self.selected_cosmetic_options[item.kind], item) end
if #self.selected_cosmetic_options[item.kind] == 0 then self.selected_cosmetic_options[item.kind] = nil end
end
item.selected = selected
if self.cosmetic_options_config[item.kind] == "single" and selected then
for _, ii in ipairs(self.cosmetic_options_flat) do
if ii.kind == item.kind and item.name ~= ii.name and ii.selected then
ii.selected = false
list:drawItem(ii, 0)
end
end
end
list:drawItem(item, 0)
cosmeticSetTile(nil, nil, nil, false, true)
end,
}
function d.innerDisplay(this, x, y, nb_keyframes)
if cosmetic_actor.image then
cosmetic_actor:toScreen(self.tiles, x + this.iw - 90, y + 64, 128, 128)
elseif cosmetic_actor.image and cosmetic_actor.add_mos then
cosmetic_actor:toScreen(self.tiles, x + this.iw - 90, y + 64, 256, 128)
end
end
d:loadUI{
{left=0, top=0, ui=list},
}
d:setupUI(false, true)
d.key:addBind("EXIT", function() self:setTile() if on_exit then on_exit(cosmetic_actor) end game:unregisterDialog(d) end)
game:registerDialog(d)
cosmeticSetTile(nil, nil, nil, false, true)
end
function _M:extraOptions()
local options = OptionTree.new(game.extra_birth_option_defs, _t'Birth Options', 600, 550)
options:initialize()
game:registerDialog(options)
end
--------------------------------------------------
-- Statics
--------------------------------------------------
function _M:showCosmeticCustomizer(actor, title, on_end)
if not actor.descriptor or not actor.descriptor.sex or not actor.descriptor.race or not actor.descriptor.subrace then
return nil, "no actor descriptor infos"
end
local clone_dummy = actor:cloneFull()
local clone = actor:cloneFull()
local birther = _M.new("", clone_dummy, {}, function() end, nil, nil, nil)
birther.not_birthing = true
birther:setDescriptor("sex", actor.descriptor.sex)
birther:setDescriptor("race", actor.descriptor.race)
birther:setDescriptor("subrace", actor.descriptor.subrace)
birther:customizeOptions(clone, function()
self:yesnoPopup(_t"Confirm", ("Apply the selected cosmetics to %s?"):tformat(actor:getName()), function(ret) if ret then
local oldactor = birther.actor
birther.actor = actor
birther:applyCosmeticActor(true)
birther.actor = oldactor
actor:removeAllMOs()
actor:updateModdableTile()
if on_end then on_end() end
end end)
end, title)
return true
end