From 0e78d07ff3de9b04e3cc02f77d9e2b9fbc43ec2c Mon Sep 17 00:00:00 2001 From: Kyle Kemp Date: Thu, 29 Aug 2024 18:14:31 -0500 Subject: [PATCH] make magic reports work --- src/app/analysis/analysis.component.ts | 7 ++ src/app/services/analysis.service.ts | 93 +++++++++++++++++++ .../input-analysis-report.component.html | 2 +- .../input-analysis-report.component.ts | 40 ++++++-- src/interfaces/analysis.ts | 1 + 5 files changed, 135 insertions(+), 8 deletions(-) diff --git a/src/app/analysis/analysis.component.ts b/src/app/analysis/analysis.component.ts index d3b5d4e..468072d 100644 --- a/src/app/analysis/analysis.component.ts +++ b/src/app/analysis/analysis.component.ts @@ -37,6 +37,13 @@ export class AnalysisComponent { break; } + case AnalysisReportType.SpellPotency: { + report = this.analysisService.generateSpellReport( + $event.data.spellName ?? '' + ); + break; + } + case AnalysisReportType.TraitUsage: { report = this.analysisService.generateTraitReport(); break; diff --git a/src/app/services/analysis.service.ts b/src/app/services/analysis.service.ts index 0a78649..c36f91e 100644 --- a/src/app/services/analysis.service.ts +++ b/src/app/services/analysis.service.ts @@ -13,6 +13,7 @@ import { StatType, WeaponClass, } from '../../interfaces'; +import { ISTEM } from '../../interfaces/stem'; import { ModService } from './mod.service'; @Injectable({ @@ -801,4 +802,96 @@ export class AnalysisService { entries: [weaponReport, armorReport], }; } + + /** + * diceRoll(rolls: number, sides: number, minSidesDivisor = 2): number { + const min = sides / minSidesDivisor; + const max = sides; + + return rolls * (min + Math.floor(Math.random() * (max - min + 1))); + } + + */ + + // 5,20 -> [20, 20, 20, 20, 20] + private calculateSpellDamage( + spell: ISTEM['spell'], + skill: number, + stat: number + ): { min: number; max: number } { + const calcSkill = skill + 1; + const maxMult = spell.skillMultiplierChanges + .filter((m) => m[0] <= skill) + .reverse()[0][1]; + + const isStatic = spell.spellMeta.staticPotency; + const potencyMultiplier = spell.potencyMultiplier ?? 1; + + if (spell.spellMeta.useSkillAsPotency) + return { + min: calcSkill * potencyMultiplier, + max: calcSkill * potencyMultiplier, + }; + + const bonusRollsMin = isStatic ? 0 : spell.bonusRollsMin ?? 0; + const bonusRollsMax = isStatic ? 0 : spell.bonusRollsMax ?? 0; + + // base rolls + const baseRollsMin = calcSkill + bonusRollsMin; + const baseRollsMax = calcSkill + bonusRollsMax; + + // sides = stat + const basePotencyMin = baseRollsMin * (stat / 2); + const basePotencyMax = baseRollsMax * (stat / 2 + (stat - stat / 2 + 1)); + + const retPotencyMin = basePotencyMin * maxMult * potencyMultiplier; + const retPotencyMax = basePotencyMax * maxMult * potencyMultiplier; + + return { + min: retPotencyMin, + max: retPotencyMax, + }; + } + + public generateSpellReport(spellName: string): AnalysisReport { + const spellData = this.modService + .mod() + .stems.find((s) => s._gameId === spellName); + if (!spellData) return { entries: [] }; + + const allReports: AnalysisReportDisplay[] = []; + + for (let skill = 1; skill <= 30; skill++) { + const spellReport: AnalysisReportDisplay = { + type: AnalysisDisplayType.Table, + table: { + title: `Spell Damage Calculations (Skill ${skill})`, + headers: [ + 'Skill Level', + 'Primary Stat', + 'Minimum Expected Potency', + 'Maximum Expected Potency', + ], + rows: [], + }, + }; + + for (let stat = 10; stat <= 50; stat += 5) { + const damage = this.calculateSpellDamage(spellData.spell, skill, stat); + + spellReport.table.rows.push([ + { pretext: skill.toString() }, + { pretext: stat.toString() }, + { pretext: damage.min.toFixed(0) }, + { pretext: damage.max.toFixed(0) }, + ]); + } + + allReports.push(spellReport); + } + + return { + entries: [...allReports], + }; + } } diff --git a/src/app/shared/components/input-analysis-report/input-analysis-report.component.html b/src/app/shared/components/input-analysis-report/input-analysis-report.component.html index ba618e6..b777967 100644 --- a/src/app/shared/components/input-analysis-report/input-analysis-report.component.html +++ b/src/app/shared/components/input-analysis-report/input-analysis-report.component.html @@ -1,6 +1,6 @@
+ placeholder="Select report..." (change)="change.emit($event)" [searchFn]="search" [virtualScroll]="true">
diff --git a/src/app/shared/components/input-analysis-report/input-analysis-report.component.ts b/src/app/shared/components/input-analysis-report/input-analysis-report.component.ts index 42a4227..7e5f437 100644 --- a/src/app/shared/components/input-analysis-report/input-analysis-report.component.ts +++ b/src/app/shared/components/input-analysis-report/input-analysis-report.component.ts @@ -1,4 +1,11 @@ -import { Component, computed, input, model, output } from '@angular/core'; +import { + Component, + computed, + inject, + input, + model, + output, +} from '@angular/core'; import { sortBy } from 'lodash'; import { AnalysisReportType, @@ -10,11 +17,12 @@ import { WeaponClass, } from '../../../../interfaces'; import { armorClasses, weaponClasses } from '../../../helpers'; +import { ModService } from '../../../services/mod.service'; export type ReportModel = { category: string; type: AnalysisReportType; - data: { itemClasses?: ItemClassType[] }; + data: { itemClasses?: ItemClassType[]; spellName?: string }; value: string; }; @@ -24,29 +32,47 @@ export type ReportModel = { styleUrl: './input-analysis-report.component.scss', }) export class InputAnalysisReportComponent { + private modService = inject(ModService); + public report = model.required(); public label = input('Report'); public change = output(); + private allCalculableSpells = computed(() => { + return this.modService + .mod() + .stems.filter( + (s) => s._hasSpell && s.spell.skillMultiplierChanges?.length > 0 + ); + }); + public values = computed(() => { + const allSpells = this.allCalculableSpells(); return sortBy( [ + ...allSpells.map((spell) => ({ + category: 'Potency Estimator (Spell)', + value: spell._gameId, + type: AnalysisReportType.SpellPotency, + data: { spellName: spell._gameId }, + desc: `Level/skill varied damage calculator.`, + })), ...Object.values(WeaponClass).map((iClass) => ({ - category: 'Item Progression', + category: 'Item Progression (Singular)', value: iClass, type: AnalysisReportType.Progression, data: { itemClasses: [iClass] }, desc: `Level-by-level progression report.`, })), ...Object.values(ArmorClass).map((iClass) => ({ - category: 'Item Progression', + category: 'Item Progression (Singular)', value: iClass, type: AnalysisReportType.Progression, data: { itemClasses: [iClass] }, desc: `Level-by-level progression report.`, })), { - category: 'Aggregate Item Progression', + category: 'Item Progression (Aggregate)', value: 'Shield', type: AnalysisReportType.Progression, data: { @@ -55,7 +81,7 @@ export class InputAnalysisReportComponent { desc: `Level-by-level progression report (includes every Shield-adjacent item).`, }, { - category: 'Aggregate Item Progression', + category: 'Item Progression (Aggregate)', value: 'Armor', type: AnalysisReportType.Progression, data: { @@ -64,7 +90,7 @@ export class InputAnalysisReportComponent { desc: `Level-by-level progression report (includes every Armor-adjacent item).`, }, { - category: 'Aggregate Item Progression', + category: 'Item Progression (Aggregate)', value: 'Robe', type: AnalysisReportType.Progression, data: { diff --git a/src/interfaces/analysis.ts b/src/interfaces/analysis.ts index e46f745..aec5244 100644 --- a/src/interfaces/analysis.ts +++ b/src/interfaces/analysis.ts @@ -5,6 +5,7 @@ export enum AnalysisReportType { WeaponAverage = 'weaponaverage', // average weapon information by item class TraitUsage = 'traitusage', // a list of all traits that are used and unused StatUtilization = 'statutilization', // a list of all stats by utilization + SpellPotency = 'spellpotency', // a list of all skill/stat combos for spell damage estimation } export enum AnalysisDisplayType {