Skill rebalancing, analytic data tracking
This commit is contained in:
		| @@ -13,6 +13,7 @@ export type SkillGoverning = { | ||||
|   cost: number, | ||||
|   note: string, | ||||
|   scoring: SkillScoring, | ||||
|   mortalServantValue: number, | ||||
| }; | ||||
| export type SkillProfile = { | ||||
|   name: string, | ||||
|   | ||||
| @@ -20,9 +20,9 @@ export class EndgameModal { | ||||
|     this.#drawpile = new DrawPile(); | ||||
|     this.#page = 0; | ||||
|  | ||||
|     this.show(getScorer().pickEnding()); | ||||
|     this.#ending = null; | ||||
|  | ||||
|     // debug | ||||
|     // this.show(getScorer().pickEnding()); | ||||
|   } | ||||
|  | ||||
|   get isShown(): boolean { | ||||
| @@ -58,17 +58,35 @@ export class EndgameModal { | ||||
|         D.drawText(rank, new Point(WIDTH / 2, 64), FG_BOLD, {alignX: AlignX.Center}) | ||||
|         D.drawText("You have achieved a DOMICILE STATUS of:", new Point(0, 96), FG_TEXT) | ||||
|         D.drawText(domicile, new Point(WIDTH / 2, 128), FG_BOLD, {alignX: AlignX.Center}) | ||||
|         D.drawText("where you live with many friends.", new Point(0, 160), FG_TEXT)  // TODO: Vary this text | ||||
|         let whereLabel = | ||||
|           mortalServants >= 25 ? "where you live with many friends." : | ||||
|           mortalServants >= 1 ? "where you live with a couple of friends." : | ||||
|           "where you live completely alone."; | ||||
|         D.drawText(whereLabel, new Point(0, 160), FG_TEXT) | ||||
|         D.drawText("You have achieved:", new Point(0, 192), FG_TEXT) | ||||
|         let itemsPurloinedText = itemsPurloined == 1 ? "item purloined" : "items purloined"; | ||||
|         let vampiricSkillsText = vampiricSkills == 1 ? "vampiric skill" : "vampiric skills"; | ||||
|         let mortalServantsText = mortalServants == 1 ? "mortal servant" : "mortal servants"; | ||||
|         let itemsPurloinedSpcr = itemsPurloined == 1 ? "              " : "               "; | ||||
|         let vampiricSkillsSpcr = vampiricSkills == 1 ? "              " : "               "; | ||||
|         let mortalServantsSpcr = mortalServants == 1 ? "              " : "               "; | ||||
|  | ||||
|         D.drawText( | ||||
|           `${itemsPurloined} items purloined\n${vampiricSkills} vampiric skills\n${mortalServants} mortal servants`, | ||||
|           `${itemsPurloined} ${itemsPurloinedText}\n${vampiricSkills} ${vampiricSkillsText}\n${mortalServants} ${mortalServantsText}`, | ||||
|           new Point(WIDTH / 2, 224), FG_TEXT, {alignX: AlignX.Center} | ||||
|         ) | ||||
|         D.drawText( | ||||
|           `${itemsPurloined}                \n${vampiricSkills}                \n${mortalServants}                `, | ||||
|           `${itemsPurloined} ${itemsPurloinedSpcr}\n${vampiricSkills} ${vampiricSkillsSpcr}\n${mortalServants} ${mortalServantsSpcr}`, | ||||
|           new Point(WIDTH / 2, 224), FG_BOLD, {alignX: AlignX.Center} | ||||
|         ); | ||||
|         D.drawText("That feels like a lot!", new Point(0, 288), FG_TEXT)  // TODO: Vary this text | ||||
|         let msg = "That's pretty dreadful." | ||||
|         if (mortalServants >= 10) { | ||||
|           msg = "That's more than zero." | ||||
|         } | ||||
|         if (mortalServants >= 30) { | ||||
|           msg = "That feels like a lot!" | ||||
|         } | ||||
|         D.drawText(msg, new Point(0, 288), FG_TEXT) | ||||
|         D.drawText("Your reign continues unimpeded from the shadows. It is now time to", new Point(0, 320), FG_TEXT, {forceWidth: WIDTH}) | ||||
|       }) | ||||
|       addButton( | ||||
|   | ||||
| @@ -89,6 +89,7 @@ export class HuntMode { | ||||
|       let amount = 1; | ||||
|       present.content = {type: "empty"}; | ||||
|       getPlayerProgress().add(stat, amount); | ||||
|       getPlayerProgress().purloinItem(); | ||||
|     } | ||||
|  | ||||
|     if (present.content.type == "resourcePickup") { | ||||
| @@ -96,6 +97,7 @@ export class HuntMode { | ||||
|       switch(resource) { | ||||
|         case "EXP": | ||||
|           getPlayerProgress().addExperience(25); | ||||
|           getPlayerProgress().purloinItem(); | ||||
|           break; | ||||
|         default: | ||||
|           throw `not sure how to add ${resource}` | ||||
|   | ||||
| @@ -5,6 +5,7 @@ export class PlayerProgress { | ||||
|   #stats: Record<Stat, number> | ||||
|   #exp: number; | ||||
|   #blood: number | ||||
|   #itemsPurloined: number | ||||
|   #skillsLearned: number[]  // use the raw ID representation for indexOf | ||||
|   #untrimmedSkillsAvailable: Skill[] | ||||
|  | ||||
| @@ -17,6 +18,7 @@ export class PlayerProgress { | ||||
|     }; | ||||
|     this.#exp = 0; | ||||
|     this.#blood = 0; | ||||
|     this.#itemsPurloined = 0; | ||||
|     this.#skillsLearned = []; | ||||
|     this.#untrimmedSkillsAvailable = [] | ||||
|  | ||||
| @@ -74,6 +76,14 @@ export class PlayerProgress { | ||||
|     return true; | ||||
|   } | ||||
|  | ||||
|   purloinItem() { | ||||
|     this.#itemsPurloined += 1; | ||||
|   } | ||||
|  | ||||
|   getItemsPurloined() { | ||||
|     return this.#itemsPurloined | ||||
|   } | ||||
|  | ||||
|   add(stat: Stat, amount: number) { | ||||
|     if (amount != Math.floor(amount)) { | ||||
|       throw `stat increment must be integer: ${amount}` | ||||
|   | ||||
| @@ -13,13 +13,21 @@ class Scorer { | ||||
|     let learnedSkills = getPlayerProgress().getLearnedSkills(); | ||||
|     let scores: Record<string, number> = {}; | ||||
|  | ||||
|     let itemsPurloined = getPlayerProgress().getItemsPurloined(); | ||||
|     let vampiricSkills = 0; | ||||
|     let mortalServants = itemsPurloined / 10; | ||||
|  | ||||
|     for (const skill of learnedSkills.values()) { | ||||
|       let data = getSkills().get(skill); | ||||
|       for (let [category, number] of Object.entries(data.governing.scoring)) { | ||||
|         scores[category] = (scores[category] ?? 0) + number; | ||||
|       } | ||||
|       mortalServants += data.governing.mortalServantValue; | ||||
|       vampiricSkills += 1; | ||||
|     } | ||||
|  | ||||
|     mortalServants = Math.floor(mortalServants); | ||||
|  | ||||
|     // NOTE: This approach isn't efficient but it's easy to understand | ||||
|     // and it allows me to arbitrate ties however I want | ||||
|     let runningScores: Record<string, number> = {...scores}; | ||||
| @@ -79,11 +87,11 @@ class Scorer { | ||||
|  | ||||
|     // TODO: Analytics tracker | ||||
|     let analytics = { | ||||
|       itemsPurloined: 0, | ||||
|       vampiricSkills: 0, | ||||
|       mortalServants: 0, | ||||
|       itemsPurloined, | ||||
|       vampiricSkills, | ||||
|       mortalServants, | ||||
|     } | ||||
|     let successorOptions = generateSuccessors(0);  // TODO: generate nImprovements from score | ||||
|     let successorOptions = generateSuccessors(0);  // TODO: generate nImprovements from mortalServants and the player's bsae improvements | ||||
|     let wishOptions = generateWishes(); | ||||
|  | ||||
|     return { | ||||
|   | ||||
| @@ -102,11 +102,12 @@ function governing(track: Track, difficulty: Difficulty): SkillGoverning { | ||||
|   let underTarget: number | ||||
|   let target: number | ||||
|   let cost: number | ||||
|   let mortalServantValue: number; | ||||
|   switch(difficulty) { | ||||
|     case 0: underTarget = 5; target = 15; cost = 50; break; | ||||
|     case 1: underTarget = 15; target = 40; cost = 100; break; | ||||
|     case 2: underTarget = 100; target = 150; cost = 250; break; | ||||
|     case 3: underTarget = 175; target = 250; cost = 500; break; | ||||
|     case 0: underTarget = 5; target = 15; cost = 50; mortalServantValue = 1; break; | ||||
|     case 1: underTarget = 15; target = 40; cost = 100; mortalServantValue = 2; break; | ||||
|     case 2: underTarget = 30; target = 70; cost = 125; mortalServantValue = 3; break; | ||||
|     case 3: underTarget = 50; target = 100; cost = 150; mortalServantValue = 10; break; | ||||
|   } | ||||
|   return { | ||||
|     stats: template.stats, | ||||
| @@ -115,6 +116,7 @@ function governing(track: Track, difficulty: Difficulty): SkillGoverning { | ||||
|     cost: cost, | ||||
|     note: template.note, | ||||
|     scoring: template.scoring, | ||||
|     mortalServantValue: mortalServantValue | ||||
|   } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -2,8 +2,6 @@ import {ALL_STATS, Stat, SuccessorOption} from "./datatypes.ts"; | ||||
| import {generateName, generateTitle} from "./namegen.ts"; | ||||
| import {choose} from "./utils.ts"; | ||||
|  | ||||
| // TODO: Take a "number of improvements", use that to improve | ||||
| //  each successor N times | ||||
| export function generateSuccessors(nImprovements: number): SuccessorOption[] { | ||||
|   let options = []; | ||||
|   while (options.length < 3) { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user