Add skills

This commit is contained in:
Pyrex 2025-02-02 20:05:52 -08:00
parent 810edb7e3b
commit 06a7263ad9
9 changed files with 588 additions and 45 deletions

41
src/button.ts Normal file
View File

@ -0,0 +1,41 @@
import {DrawPile} from "./drawpile.ts";
import {AlignX, AlignY, Point, Rect, Size} from "./engine/datatypes.ts";
import {BG_INSET, FG_BOLD, FG_TEXT} from "./colors.ts";
import {D} from "./engine/public.ts";
export function addButton(
drawpile: DrawPile,
label: string,
rect: Rect,
enabled: boolean,
cbClick: () => void,
) {
let padding = 2;
let topLeft = rect.top;
let topLeftPadded = topLeft.offset(new Point(padding, padding));
let sizePadded = new Size(rect.size.w - padding * 2, rect.size.h - padding * 2);
let center = topLeft.offset(new Point(rect.size.w / 2, rect.size.h / 2));
drawpile.addClickable(
0,
(hover) => {
let [bg, fg, fgLabel] = [BG_INSET, FG_TEXT, FG_BOLD];
if (hover) {
[bg, fg, fgLabel] = [FG_BOLD, BG_INSET, BG_INSET];
}
D.fillRect(
topLeftPadded.offset(new Point(-1, -1)),
sizePadded.add(new Size(2, 2)),
bg
);
D.drawRect(topLeftPadded, sizePadded, fg);
D.drawText(label, center, fgLabel, {
alignX: AlignX.Center,
alignY: AlignY.Middle,
})
},
new Rect(topLeftPadded, sizePadded),
enabled,
cbClick
);
}

View File

@ -1,3 +1,21 @@
export type Stat = "AGI" | "INT" | "CHA" | "PSI"; export type Stat = "AGI" | "INT" | "CHA" | "PSI";
export const ALL_STATS: Array<Stat> = ["AGI", "INT", "CHA", "PSI"]; export const ALL_STATS: Array<Stat> = ["AGI", "INT", "CHA", "PSI"];
export type SkillGoverning = {
stats: Stat[], target: number, cost: number, note: string
};
export type SkillProfile = {
name: string,
description: string,
}
export type SkillData = {
governing: SkillGoverning,
profile: SkillProfile,
prereqs: Skill[]
}
export type Skill = {
id: number
}

View File

@ -98,13 +98,15 @@ class Font {
return this.#glyphwise(text, forceWidth, () => {}); return this.#glyphwise(text, forceWidth, () => {});
} }
#glyphwise(text: string, forceWidth: number | undefined, callback: (x: number, y: number, char: string) => void): {w: number, h: number} { #glyphwise(text: string, forceWidth: number | undefined, callback: (x: number, y: number, char: string) => void): Size {
let cx = 0; let cx = 0;
let cy = 0; let cy = 0;
let cw = 0; let cw = 0;
let ch = 0; let ch = 0;
let wcx = forceWidth == undefined ? undefined : Math.floor(forceWidth / this.#px); let wcx = forceWidth == undefined ? undefined : Math.floor(forceWidth / this.#px);
text = betterWordWrap(text, wcx);
for (let i = 0; i < text.length; i++) { for (let i = 0; i < text.length; i++) {
let char = text[i] let char = text[i]
if (char == '\n') { if (char == '\n') {
@ -126,8 +128,19 @@ class Font {
} }
} }
return { w: cw * this.#px, h: ch * this.#py }; return new Size(cw * this.#px, ch * this.#py);
} }
} }
// https://stackoverflow.com/users/1993501/edi9999
function betterWordWrap(s: string, wcx?: number) {
if (wcx === undefined) {
return s;
}
return s.replace(
new RegExp(`(?![^\\n]{1,${wcx}}$)([^\\n]{1,${wcx}})\\s`, 'g'), '$1\n'
);
}
export let mainFont = new Font(fontSheet, new Size(32, 8), new Size(8, 16)); export let mainFont = new Font(fontSheet, new Size(32, 8), new Size(8, 16));

View File

@ -4,7 +4,8 @@ import {IGame, Point, Size} from "./engine/datatypes.ts";
import {HuntMode} from "./huntmode.ts"; import {HuntMode} from "./huntmode.ts";
import {getPageLocation, Page, withCamera} from "./layout.ts"; import {getPageLocation, Page, withCamera} from "./layout.ts";
import {getHud} from "./hud.ts"; import {getHud} from "./hud.ts";
import {getHotbar} from "./hotbar.ts"; import {getHotbar, Hotbar} from "./hotbar.ts";
import {getSkillsModal, SkillsModal} from "./skillsmodal.ts";
class MenuCamera { class MenuCamera {
// measured in whole screens // measured in whole screens
@ -31,6 +32,7 @@ export class Game implements IGame {
camera: MenuCamera; camera: MenuCamera;
page: Page; page: Page;
huntMode: HuntMode; huntMode: HuntMode;
#bottomThing: SkillsModal | Hotbar | null;
constructor() { constructor() {
this.camera = new MenuCamera({ this.camera = new MenuCamera({
@ -40,6 +42,7 @@ export class Game implements IGame {
this.page = "Gameplay"; this.page = "Gameplay";
this.huntMode = HuntMode.generate({depth: 1}); this.huntMode = HuntMode.generate({depth: 1});
this.#bottomThing = null;
} }
update() { update() {
@ -79,11 +82,13 @@ export class Game implements IGame {
} }
updateGameplay() { updateGameplay() {
this.#chooseBottomThing();
withCamera("Gameplay", () => { withCamera("Gameplay", () => {
this.huntMode.update(); this.huntMode.update();
}); });
withCamera("HUD", () => { getHud().update() }) withCamera("HUD", () => { getHud().update() })
withCamera("Hotbar", () => { getHotbar().update() }) this.#bottomThing?.update();
} }
drawGameplay() { drawGameplay() {
@ -91,8 +96,26 @@ export class Game implements IGame {
this.huntMode.draw(); this.huntMode.draw();
}); });
withCamera("HUD", () => { getHud().draw() }) withCamera("HUD", () => { getHud().draw() })
withCamera("Hotbar", () => { getHotbar().draw() }) this.#bottomThing?.draw()
} }
#chooseBottomThing() {
// This is explicitly chosen because we would prefer that
// the same bottomThing be used in both draw and update,
// meaning that events after this in updateGameplay should not affect
// its value
let skillsModal = getSkillsModal();
if (skillsModal.isShown) {
this.#bottomThing = skillsModal;
return;
}
// use the hotbar only as a matter of last resort
this.#bottomThing = getHotbar();
}
// withCamera("Hotbar", () => { getHotbar().draw() })
} }

View File

@ -1,7 +1,8 @@
import {AlignX, AlignY, Point, Rect, Size} from "./engine/datatypes.ts"; import {Point, Rect, Size} from "./engine/datatypes.ts";
import {D} from "./engine/public.ts";
import {BG_INSET, FG_BOLD, FG_TEXT} from "./colors.ts";
import {DrawPile} from "./drawpile.ts"; import {DrawPile} from "./drawpile.ts";
import {withCamera} from "./layout.ts";
import {getSkillsModal} from "./skillsmodal.ts";
import {addButton} from "./button.ts";
type Button = { type Button = {
label: string, label: string,
@ -28,7 +29,9 @@ export class Hotbar {
let buttons: Button[] = []; let buttons: Button[] = [];
buttons.push({ buttons.push({
label: "Skills", label: "Skills",
cbClick: () => alert("beep"), cbClick: () => {
getSkillsModal().setShown(true)
}
}) })
/* /*
buttons.push({ buttons.push({
@ -39,6 +42,10 @@ export class Hotbar {
} }
update() { update() {
withCamera("Hotbar", () => this.#update())
}
#update() {
this.#drawpile.clear(); this.#drawpile.clear();
let buttons = this.#computeButtons(); let buttons = this.#computeButtons();
@ -46,35 +53,8 @@ export class Hotbar {
let cellSize = this.#cellSize; let cellSize = this.#cellSize;
let x = 0; let x = 0;
let padding = 2;
for (let b of buttons.values()) { for (let b of buttons.values()) {
let topLeft = new Point(x, 0); addButton(this.#drawpile, b.label, new Rect(new Point(x, 0), cellSize), true, b.cbClick);
let topLeftPadded = topLeft.offset(new Point(padding, padding));
let sizePadded = new Size(cellSize.w - padding * 2, cellSize.h - padding * 2);
let center = topLeft.offset(new Point(cellSize.w / 2, cellSize.h / 2));
this.#drawpile.addClickable(
0,
(hover) => {
let [bg, fg, fgLabel] = [BG_INSET, FG_TEXT, FG_BOLD];
if (hover) {
[bg, fg, fgLabel] = [FG_TEXT, BG_INSET, BG_INSET];
}
D.fillRect(
topLeftPadded.offset(new Point(-1, -1)),
sizePadded.add(new Size(2, 2)),
bg
);
D.drawRect(topLeftPadded, sizePadded, fg);
D.drawText(b.label, center, fgLabel, {
alignX: AlignX.Center,
alignY: AlignY.Middle,
})
},
new Rect(topLeftPadded, sizePadded),
true,
b.cbClick,
);
x += cellSize.w; x += cellSize.w;
} }
this.#drawpile.executeOnClick(); this.#drawpile.executeOnClick();
@ -83,7 +63,7 @@ export class Hotbar {
draw() { draw() {
// D.fillRect(new Point(-4, -4), this.size.add(new Size(8, 8)), BG_INSET); // D.fillRect(new Point(-4, -4), this.size.add(new Size(8, 8)), BG_INSET);
this.#drawpile.draw(); withCamera("Hotbar", () => this.#drawpile.draw());
} }
} }

View File

@ -23,9 +23,6 @@ export function getLayoutRect(
let {w: innerW, h: innerH} = size; let {w: innerW, h: innerH} = size;
let remainingSpaceX = marginalScreenW - innerW; let remainingSpaceX = marginalScreenW - innerW;
let remainingSpaceY = marginalScreenH - innerH; let remainingSpaceY = marginalScreenH - innerH;
if (options?.alignY == AlignY.Bottom) {
console.log(`Remaining space y: ${remainingSpaceY}`)
}
let alignXCoef = let alignXCoef =
options?.alignX == AlignX.Left ? 0.0 : options?.alignX == AlignX.Left ? 0.0 :
@ -55,10 +52,11 @@ export function withCamera(part: UIPart, cb: () => void) {
// specific // specific
export type Page = "Gameplay" | "Thralls"; export type Page = "Gameplay" | "Thralls";
export type UIPart = "Hotbar" | "HUD" | "Gameplay" | "Thralls"; export type UIPart = "BottomModal" | "Hotbar" | "HUD" | "Gameplay" | "Thralls";
export function getPartPage(part: UIPart): Page { export function getPartPage(part: UIPart): Page {
switch (part) { switch (part) {
case "BottomModal":
case "Hotbar": case "Hotbar":
case "HUD": case "HUD":
case "Gameplay": case "Gameplay":
@ -97,6 +95,11 @@ export function getPartLocation(part: UIPart): Rect {
export function internalGetPartLayoutRect(part: UIPart) { export function internalGetPartLayoutRect(part: UIPart) {
switch (part) { switch (part) {
case "BottomModal":
return getLayoutRect(new Size(384, 128), {
alignX: AlignX.Center,
alignY: AlignY.Bottom,
});
case "Gameplay": case "Gameplay":
case "Thralls": case "Thralls":
return getLayoutRect(new Size(384, 384)); return getLayoutRect(new Size(384, 384));
@ -108,7 +111,7 @@ export function internalGetPartLayoutRect(part: UIPart) {
case "HUD": case "HUD":
return getLayoutRect(getHud().size, { return getLayoutRect(getHud().size, {
alignX: AlignX.Left, alignX: AlignX.Left,
alignY: AlignY.Middle alignY: AlignY.Top
}) })
} }
throw `not sure what layout rect to use ${part}` throw `not sure what layout rect to use ${part}`

View File

@ -1,8 +1,11 @@
import {Stat} from "./datatypes.ts"; import {Skill, Stat} from "./datatypes.ts";
import {getSkills} from "./skills.ts";
export class PlayerProgress { export class PlayerProgress {
#stats: Record<Stat, number> #stats: Record<Stat, number>
#blood: number #blood: number
#skillsLearned: number[] // use the raw ID representation for indexOf
#untrimmedSkillsAvailable: Skill[]
constructor() { constructor() {
this.#stats = { this.#stats = {
@ -10,14 +13,63 @@ export class PlayerProgress {
INT: 10, INT: 10,
CHA: 10, CHA: 10,
PSI: 10, PSI: 10,
} };
this.#blood = 0; this.#blood = 0;
this.#skillsLearned = [];
this.#untrimmedSkillsAvailable = []
this.refill(); this.refill();
} }
refill() { refill() {
this.#blood = 2000; this.#blood = 2000;
let learnableSkills = []; // TODO: Also include costing info
for (let skill of getSkills().getAllAvailableSkills().values()) {
if (this.#canBeAvailable(skill)) {
learnableSkills.push(skill);
}
}
this.#untrimmedSkillsAvailable = learnableSkills
}
hasLearned(skill: Skill) {
return this.#skillsLearned.indexOf(skill.id) !== -1;
}
learnSkill(skill: Skill) {
if (this.#skillsLearned.indexOf(skill.id) != -1) {
return
}
this.#skillsLearned.push(skill.id);
// remove entries for that skill
let skills2 = [];
for (let entry of this.#untrimmedSkillsAvailable.values()) {
if (entry.id == skill.id) { continue; }
skills2.push(entry);
}
this.#untrimmedSkillsAvailable = skills2;
}
#canBeAvailable(skill: Skill) {
// make sure we haven't learned this skill already
if (this.hasLearned(skill)) {
return false;
}
let data = getSkills().get(skill);
// make sure the prereqs are met
for (let prereq of data.prereqs.values()) {
if (!this.hasLearned(prereq)) {
return false
}
}
// ok, we're good!!
return true;
} }
add(stat: Stat, amount: number) { add(stat: Stat, amount: number) {
@ -41,6 +93,11 @@ export class PlayerProgress {
spendBlood(amt: number) { spendBlood(amt: number) {
this.#blood -= amt; this.#blood -= amt;
} }
getAvailableSkills(): Skill[] {
// TODO: Sort by cost, then by name, then trim down to first 8
return this.#untrimmedSkillsAvailable
}
} }
let active: PlayerProgress = new PlayerProgress(); let active: PlayerProgress = new PlayerProgress();

262
src/skills.ts Normal file
View File

@ -0,0 +1,262 @@
import {Skill, SkillData, SkillGoverning, Stat} from "./datatypes.ts";
class SkillsTable {
#skills: SkillData[]
constructor() {
this.#skills = [];
}
add(data: SkillData): Skill {
let id = this.#skills.length;
this.#skills.push(data);
return {id};
}
get(skill: Skill): SkillData {
return this.#skills[skill.id]
}
getAllAvailableSkills(): Skill[] {
let skills = [];
for (let i = 0; i < this.#skills.length; i++) {
skills.push({id: i});
}
return skills;
}
}
type Difficulty = 0 | 1 | 2 | 3
type GoverningTemplate = {
stats: Stat[],
note: string
}
type Track = "bat" | "stealth" | "charm" | "stare" | "party" | "lore"
let templates: Record<Track, GoverningTemplate> = {
bat: { stats: ["AGI", "AGI", "PSI"], note: "Cheaper with AGI and PSI." },
stealth: { stats: ["AGI", "AGI", "INT"], note: "Cheaper with AGI and INT." },
charm: { stats: ["CHA", "PSI", "PSI"], note: "Cheaper with CHA and PSI." },
stare: { stats: ["PSI", "PSI"], note: "Cheaper with PSI." },
party: { stats: ["CHA", "CHA", "PSI"], note: "Cheaper with CHA and PSI." },
lore: { stats: ["INT", "INT", "CHA"], note: "Cheaper with INT and CHA." },
}
function governing(track: Track, difficulty: Difficulty): SkillGoverning {
let template = templates[track];
let target: number
let cost: number
switch(difficulty) {
case 0: target = 30; cost = 50; break;
case 1: target = 100; cost = 100; break;
case 2: target = 150; cost = 250; break;
case 3: target = 250; cost = 500; break;
}
return {
stats: template.stats,
target: target,
cost: cost,
note: template.note,
}
}
let table = new SkillsTable();
export let bat0 = table.add({
governing: governing("bat", 0),
profile: {
name: "Screech",
description: "Energy fills your body. You can't help but let go. It just feels so good to let that sound rip through you."
},
prereqs: []
});
export let bat1 = table.add({
governing: governing("bat", 1),
profile: {
name: "Flap",
description: "Sailing on your cloak is pleasurable, but it's better still to shake your limbs and FIGHT the wind."
},
prereqs: [bat0]
});
export let bat2 = table.add({
governing: governing("bat", 2),
profile: {
name: "Transform",
description: "Bare your fangs and let them out further than normal. Your nose wrinkles. You have a SNOUT??"
},
prereqs: [bat1]
});
export let bat3 = table.add({
governing: governing("bat", 3),
profile: {
name: "Eat Bugs",
description: "This is the forbidden pleasure. It supersedes even blood. Go on -- have a bite -- CRUNCH!"
},
prereqs: [bat2]
});
export let stealth0 = table.add({
governing: governing("stealth", 0),
profile: {
name: "Be Quiet",
description: "There's a thing in the brain that _wants_ to be caught. Mortals have it, vampires don't."
},
prereqs: []
});
export let stealth1 = table.add({
governing: governing("stealth", 1),
profile: {
name: "Disguise",
description: "First impressions: what you want them to see, they'll see. Just meet their gaze and start talking.",
},
prereqs: [stealth0]
});
export let stealth2 = table.add({
governing: governing("stealth", 2),
profile: {
name: "Sneak",
description: "Your unnatural pallor _should_ make you bright against the shadow. But it likes you, so you fade."
},
prereqs: [stealth1]
});
export let stealth3 = table.add({
governing: governing("stealth", 3),
profile: {
name: "Turn Invisible",
description: "No one sees any more of you than you'd like. You're as ghostly as your own reflection.",
},
prereqs: [stealth2]
});
export let charm0 = table.add({
governing: governing("charm", 0),
profile: {
name: "Flatter",
description: "No matter how weird you're being, people like praise. Praise in your voice has an intoxicating quality.",
},
prereqs: []
});
export let charm1 = table.add({
governing: governing("charm", 1),
profile: {
name: "Befriend",
description: "Cute: they think they've met the real you. They're even thinking about you when you're not around."
},
prereqs: [charm0]
});
export let charm2 = table.add({
governing: governing("charm", 2),
profile: {
name: "Seduce",
description: "Transfix them long and deep enough for them to realize how much they want you. \"No\" isn't \"no\" anymore.",
},
prereqs: [charm1]
});
export let charm3 = table.add({
governing: governing("charm", 3),
profile: {
name: "Infatuate",
description: "They were into mortals once. Now they can't get off without the fangs. The eyes. The pale dead flesh."
},
prereqs: [charm2]
});
export let stare0 = table.add({
governing: governing("stare", 0),
profile: {
name: "Dazzle",
description: "Your little light show can reduce anyone to a puddle of their own fluids. Stare and they give in instantly.",
},
prereqs: []
});
export let stare1 = table.add({
governing: governing("stare", 1),
profile: {
name: "Hypnotize",
description: "Say \"sleep\" and the mortal falls asleep. That is not a person: just a machine that acts when you require it."
},
prereqs: [stare0]
});
export let stare2 = table.add({
governing: governing("stare", 2),
profile: {
name: "Enthrall",
description: "Everyone's mind has room for one master. Reach into the meek fractured exterior and mean for it to be you."
},
prereqs: [stare1]
});
export let stare3 = table.add({
governing: governing("stare", 3),
profile: {
name: "Seal Memory",
description: "There was no existence before you and will be none after. Your mortals cannot imagine another existence."
},
prereqs: [stare2]
});
export let party0 = table.add({
governing: governing("party", 0),
profile: {
name: "Chug",
description: "This undead body can hold SO MUCH whiskey. (BRAAAAP.) \"You, mortal -- fetch me another drink!\""
},
prereqs: []
});
export let party1 = table.add({
governing: governing("party", 1),
profile: {
name: "Rave",
description: "You could jam glowsticks in your hair, but your eyes are a lot brighter. And they pulse with the music."
},
prereqs: [party0]
});
export let party2 = table.add({
governing: governing("party", 2),
profile: {
name: "Peer Pressure",
description: "Partying: it gets you out of your head. Makes you do things you wouldn't normally do. Controls you."
},
prereqs: [party1]
});
export let party3 = table.add({
governing: governing("party", 3),
profile: {
name: "Sleep It Off",
description: "Feels good. Never want it to end. But sober up. These feelings aren't for you. They're for your prey."
},
prereqs: [party2]
});
export let lore0 = table.add({
governing: governing("lore", 0),
profile: {
name: "Respect Elders",
description: "You're told not to bother learning much. The test is not to believe that. Bad vampires _disappear_."
},
prereqs: []
});
export let lore1 = table.add({
governing: governing("lore", 1),
profile: {
name: "Brick by Brick",
description: "Vampire history is a mix of fact and advice. Certain tips -- \"live in a castle\" -- seem very concrete."
},
prereqs: [lore0]
});
export let lore2 = table.add({
governing: governing("lore", 2),
profile: {
name: "Make Wine",
description: "Fruit bats grow the grapes. Insectivores fertilize the soil. What do vampire bats do? Is this a metaphor?"
},
prereqs: [lore1]
});
export let lore3 = table.add({
governing: governing("lore", 3),
profile: {
name: "Third Clade",
description: "Mortals love the day. They hate the night. There's a night deeper than any night that cannot be discussed."
},
prereqs: [lore2]
});
export function getSkills(): SkillsTable {
return table;
}

146
src/skillsmodal.ts Normal file
View File

@ -0,0 +1,146 @@
import {getPartLocation, withCamera} from "./layout.ts";
import {AlignX, Point, Rect, Size} from "./engine/datatypes.ts";
import {DrawPile} from "./drawpile.ts";
import {D} from "./engine/public.ts";
import {BG_INSET, FG_BOLD, FG_TEXT} from "./colors.ts";
import {addButton} from "./button.ts";
import {
getSkills,
} from "./skills.ts";
import {getPlayerProgress} from "./playerprogress.ts";
import {Skill, SkillData} from "./datatypes.ts";
export class SkillsModal {
#drawpile: DrawPile;
#shown: boolean;
#skillSelection: Skill | null;
constructor() {
this.#drawpile = new DrawPile();
this.#shown = false;
this.#skillSelection = null;
}
get #size(): Size {
// Instead of calculating this here, compute it from outside
// as it has to be the same for every bottom modal
return getPartLocation("BottomModal").size
}
get isShown(): boolean {
return this.#shown;
}
setShown(shown: boolean) {
this.#shown = shown
}
update() {
withCamera("BottomModal", () => this.#update())
}
draw() {
withCamera("BottomModal", () => this.#draw())
}
#update() {
this.#drawpile.clear();
let size = this.#size
this.#drawpile.add(0, () => {
D.fillRect(new Point(-4, -4), size.add(new Size(8, 8)), BG_INSET)
})
// draw skills
let availableSkills = getPlayerProgress().getAvailableSkills();
this.#fixSkillSelection(availableSkills);
let y = 0;
for (let skill of availableSkills) {
let data = getSkills().get(skill);
let y_ = y;
let selected = this.#skillSelection?.id == skill.id;
let skillRect = new Rect(new Point(0, y_), new Size(160 + 4, 16));
let enabled = true;
this.#drawpile.addClickable(
0,
(hover) => {
// two column layout
let [bg, fg] = [BG_INSET, FG_TEXT];
if (selected || hover) {
[bg, fg] = [FG_BOLD, BG_INSET];
}
D.fillRect(skillRect.top, skillRect.size, bg);
D.drawText(data.profile.name, new Point(4, y_), fg);
D.drawText("100", new Point(160 - 4, y_), fg, {alignX: AlignX.Right});
},
skillRect,
enabled,
() => {
this.#skillSelection = skill;
}
)
y += 16;
}
// add skill description
let selection = this.#skillSelection;
if (selection != null) {
let data = getSkills().get(selection);
let size = this.#size;
let remainingWidth = size.w - 160;
this.#drawpile.add(0, () => {
D.fillRect(new Point(160, 0), new Size(remainingWidth, 96), FG_BOLD)
D.drawText(createFullDescription(data), new Point(164, 0), BG_INSET, {forceWidth: remainingWidth - 8});
});
// add learn button
let drawButtonRect = new Rect(new Point(160, 96), new Size(remainingWidth, 32))
let caption = `Learn ${data.profile.name}`
addButton(this.#drawpile, caption, drawButtonRect, true, () => {
getPlayerProgress().learnSkill(selection);
})
}
// add close button
let closeRect = new Rect(new Point(0, 96), new Size(160, 32))
addButton(this.#drawpile, "Back", closeRect, true, () => {
this.setShown(false);
})
this.#drawpile.executeOnClick();
}
#fixSkillSelection(skills: Skill[]) {
// if we have selected a skill that is really available,
// that's fine
for (let s of skills.values()) {
if (s.id == this.#skillSelection?.id) {
return;
}
}
// select the first skill if one exists
if (skills.length == 0) {
this.#skillSelection = null;
} else {
this.#skillSelection = skills[0];
}
}
#draw() {
this.#drawpile.draw();
}
}
let active = new SkillsModal();
export function getSkillsModal(): SkillsModal {
return active;
}
function createFullDescription(data: SkillData) {
return data.profile.description + "\n\n" + data.governing.note
}