From 783dcd0ca3e577b502d71196374c64ae8e71aa46 Mon Sep 17 00:00:00 2001 From: Nyeogmi Date: Sun, 2 Feb 2025 21:09:10 -0800 Subject: [PATCH] Skill costing 1 --- src/datatypes.ts | 2 +- src/game.ts | 12 ++++++- src/hotbar.ts | 7 ++++ src/huntmode.ts | 21 +++++++++--- src/playerprogress.ts | 16 +++++++-- src/skills.ts | 41 +++++++++++++++++++--- src/skillsmodal.ts | 3 +- src/sleepmodal.ts | 80 +++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 169 insertions(+), 13 deletions(-) create mode 100644 src/sleepmodal.ts diff --git a/src/datatypes.ts b/src/datatypes.ts index f2a7cab..96afce5 100644 --- a/src/datatypes.ts +++ b/src/datatypes.ts @@ -3,7 +3,7 @@ export type Stat = "AGI" | "INT" | "CHA" | "PSI"; export const ALL_STATS: Array = ["AGI", "INT", "CHA", "PSI"]; export type SkillGoverning = { - stats: Stat[], target: number, cost: number, note: string + stats: Stat[], underTarget: number, target: number, cost: number, note: string }; export type SkillProfile = { name: string, diff --git a/src/game.ts b/src/game.ts index a6eebf5..4d38f76 100644 --- a/src/game.ts +++ b/src/game.ts @@ -6,6 +6,7 @@ import {getPageLocation, Page, withCamera} from "./layout.ts"; import {getHud} from "./hud.ts"; import {getHotbar, Hotbar} from "./hotbar.ts"; import {getSkillsModal, SkillsModal} from "./skillsmodal.ts"; +import {getSleepModal, SleepModal} from "./sleepmodal.ts"; class MenuCamera { // measured in whole screens @@ -32,7 +33,7 @@ export class Game implements IGame { camera: MenuCamera; page: Page; huntMode: HuntMode; - #bottomThing: SkillsModal | Hotbar | null; + #bottomThing: SkillsModal | SleepModal | Hotbar | null; constructor() { this.camera = new MenuCamera({ @@ -111,6 +112,15 @@ export class Game implements IGame { return; } + // between skillsModal and sleepModal, skillsModal wins + // this is important because skillsModal can be + // activated from sleepModal + let sleepModal = getSleepModal(); + if (sleepModal.isShown) { + this.#bottomThing = sleepModal; + return; + } + // use the hotbar only as a matter of last resort this.#bottomThing = getHotbar(); } diff --git a/src/hotbar.ts b/src/hotbar.ts index 385e4f4..801da91 100644 --- a/src/hotbar.ts +++ b/src/hotbar.ts @@ -3,6 +3,7 @@ import {DrawPile} from "./drawpile.ts"; import {withCamera} from "./layout.ts"; import {getSkillsModal} from "./skillsmodal.ts"; import {addButton} from "./button.ts"; +import {getSleepModal} from "./sleepmodal.ts"; type Button = { label: string, @@ -38,6 +39,12 @@ export class Hotbar { label: "Thralls" }) */ + buttons.push({ + label: "Sleep", + cbClick: () => { + getSleepModal().setShown(true) + } + }) return buttons; } diff --git a/src/huntmode.ts b/src/huntmode.ts index 1396765..a74db68 100644 --- a/src/huntmode.ts +++ b/src/huntmode.ts @@ -146,14 +146,23 @@ export class HuntMode { let stat = present.content.stat; let amount = 1; present.content = {type: "empty"}; - getPlayerProgress().spendBlood(90); getPlayerProgress().add(stat, amount); } } + #computeCostToMoveTo(mapPosition: Point): number | null { + let present = this.cells.get(mapPosition); + if (present.content.type == "statPickup") { + return 100; + } + if (present.content.type == "empty") { + return 10; + } + return null; + } + movePlayerTo(newPosition: Point) { this.player = newPosition; - getPlayerProgress().spendBlood(10); this.#updateVisibilityAndPossibleMoves(); this.#collectResources(); } @@ -220,14 +229,18 @@ export class HuntMode { } // draw inset zone + let cost = this.#computeCostToMoveTo(mapPosition); this.drawpile.addClickable(onFloor, (hover: boolean) => { D.fillRect(cellTopLeft, cellSize, hover ? FG_TEXT : BG_INSET) }, new Rect(cellTopLeft, cellSize), - cellData.nextMoveAccessible, + cellData.nextMoveAccessible && cost != null && cost <= getPlayerProgress().getBlood(), () => { - this.movePlayerTo(mapPosition) + if (cost != null) { + getPlayerProgress().spendBlood(cost); + this.movePlayerTo(mapPosition) + } } ); diff --git a/src/playerprogress.ts b/src/playerprogress.ts index bf32824..94db985 100644 --- a/src/playerprogress.ts +++ b/src/playerprogress.ts @@ -95,8 +95,20 @@ export class PlayerProgress { } getAvailableSkills(): Skill[] { - // TODO: Sort by cost, then by name, then trim down to first 8 - return this.#untrimmedSkillsAvailable + // Sort by cost, then by name, then trim down to first 6 + let skillsAvailable = [...this.#untrimmedSkillsAvailable]; + skillsAvailable.sort((a, b) => { + return getSkills().computeCost(a) - getSkills().computeCost(b) + }); + skillsAvailable.sort((a, b) => { + let name1 = getSkills().get(a).profile.name; + let name2 = getSkills().get(b).profile.name; + + if (name1 < name2) { return -1; } + if (name1 > name2) { return 1; } + return 0; + }); + return skillsAvailable.slice(0, 6) } } diff --git a/src/skills.ts b/src/skills.ts index 95cc5af..fdd6faa 100644 --- a/src/skills.ts +++ b/src/skills.ts @@ -1,4 +1,5 @@ import {Skill, SkillData, SkillGoverning, Stat} from "./datatypes.ts"; +import {getPlayerProgress} from "./playerprogress.ts"; class SkillsTable { #skills: SkillData[] @@ -24,6 +25,36 @@ class SkillsTable { } return skills; } + + computeCost(skill: Skill) { + let data = this.get(skill); + + let governingStatValue = 0; + for (let stat of data.governing.stats.values()) { + governingStatValue += getPlayerProgress().getStat(stat) / data.governing.stats.length; + } + + return Math.floor(geomInterpolate( + governingStatValue, + data.governing.underTarget, data.governing.target, + data.governing.cost, 999 + )) + } +} + +function geomInterpolate( + x: number, + lowIn: number, + highIn: number, + lowOut: number, + highOut: number, +) { + if (x < lowIn) { return highOut; } + if (x >= highIn) { return lowOut; } + + const proportion = 1.0 - (x - lowIn) / (highIn - lowIn); + console.log(`proportion: ${x} ${proportion}`) + return lowOut * Math.pow(highOut / lowOut, proportion) } type Difficulty = 0 | 1 | 2 | 3 @@ -44,16 +75,18 @@ let templates: Record = { function governing(track: Track, difficulty: Difficulty): SkillGoverning { let template = templates[track]; + let underTarget: number 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; + case 0: underTarget = 5; target = 15; cost = 50; break; + case 1: underTarget = 50; target = 100; cost = 100; break; + case 2: underTarget = 100; target = 150; cost = 250; break; + case 3: underTarget = 175; target = 250; cost = 500; break; } return { stats: template.stats, + underTarget: underTarget, target: target, cost: cost, note: template.note, diff --git a/src/skillsmodal.ts b/src/skillsmodal.ts index 44a1293..7f75a3d 100644 --- a/src/skillsmodal.ts +++ b/src/skillsmodal.ts @@ -58,6 +58,7 @@ export class SkillsModal { let y = 0; for (let skill of availableSkills) { let data = getSkills().get(skill); + let cost = getSkills().computeCost(skill); let y_ = y; let selected = this.#skillSelection?.id == skill.id; let skillRect = new Rect(new Point(0, y_), new Size(160 + 4, 16)); @@ -73,7 +74,7 @@ export class SkillsModal { } 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}); + D.drawText("" + cost, new Point(160 - 4, y_), fg, {alignX: AlignX.Right}); }, skillRect, enabled, diff --git a/src/sleepmodal.ts b/src/sleepmodal.ts new file mode 100644 index 0000000..8f37172 --- /dev/null +++ b/src/sleepmodal.ts @@ -0,0 +1,80 @@ +import {DrawPile} from "./drawpile.ts"; +import {Point, Rect, Size} from "./engine/datatypes.ts"; +import {getPartLocation, withCamera} from "./layout.ts"; +import {addButton} from "./button.ts"; +import {D} from "./engine/public.ts"; +import {BG_INSET} from "./colors.ts"; +import {getSkillsModal} from "./skillsmodal.ts"; +import {getPlayerProgress} from "./playerprogress.ts"; + +export class SleepModal { + #drawpile: DrawPile; + #shown: boolean; + + constructor() { + this.#drawpile = new DrawPile(); + this.#shown = false; + } + + get #size(): Size { + // We share this logic with SkillModal: + // 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) + }) + + // add close button + let closeRect = new Rect(new Point(0, 96), new Size(80, 32)) + addButton(this.#drawpile, "Back", closeRect, true, () => { + this.setShown(false); + }) + + let skillsRect = new Rect(new Point(80, 96), new Size(80, 32)); + addButton(this.#drawpile, "Skills", skillsRect, true, () => { + getSkillsModal().setShown(true); + }) + + let remainingWidth = size.w - 160; + let nextRect = new Rect(new Point(160, 96), new Size(remainingWidth, 32)); + addButton(this.#drawpile, "Sleep All Day", nextRect, true, () => { + getPlayerProgress().refill(); + getSleepModal().setShown(false); + // TODO: Advance huntmode + }); + + this.#drawpile.executeOnClick(); + } + + #draw() { + this.#drawpile.draw(); + } +} + +let active = new SleepModal(); +export function getSleepModal(): SleepModal { + return active; +} \ No newline at end of file