Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
bbf50c3
[Feature] Generic secondary resouces
May 3, 2025
2e79f66
Merge branch 'master' into feature/generic-resources
1337LutZ May 4, 2025
837193f
Remove unintended file
May 4, 2025
eda54c5
[Feature]Generic Resoruces: Update implementation and replace Holy P…
May 5, 2025
5d1fe07
[Feature] Generic Resources: Remove line comments from test
May 5, 2025
98dd829
[Feature] Generic Resource: Remove old proto mappings
May 5, 2025
ddea63f
[Feature] Generic resources - Part 1 APL implementation
May 5, 2025
758fb68
[Feature] Generic Resource: Add support for callbacks
May 5, 2025
c49ff3c
[Feature] Generic Resource: Add missing callback invoke
May 5, 2025
5d8d680
Add SecondaryResource class & extend APL dynamic string
1337LutZ May 5, 2025
1bd49b8
Merge branch 'feature/generic-resources' of https://github.com/wowsim…
1337LutZ May 5, 2025
d53ff8a
[Feature] Generic Resource: Fix APL
May 5, 2025
b1592a1
[Feature] Generic Resources: Make bar track Int32
May 5, 2025
0022a93
[Feature] Generic Resource: Update resource implementation
May 6, 2025
c4c9ecf
[Feature] Generic Resources: Make resource bar private to core
May 6, 2025
014be3c
[Feature] Generic Resource: Map secondary resource names correctly in…
May 6, 2025
e1ed415
Merge remote-tracking branch 'origin/master' into feature/generic-res…
May 6, 2025
5ac1cb8
[Feature] Generic resource: Remove chi from secondary resources
May 6, 2025
44bce62
Merge branch 'master' into feature/generic-resources
May 9, 2025
54c93d8
[Feature] Generic Resouce: Fixup unintended removed code
May 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions proto/spell.proto
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,5 @@ enum ResourceType {
ResourceTypeLunarEnergy = 13;
ResourceTypeHolyPower = 14;
ResourceTypeChi = 15;
ResourceTypeGenericResource = 16;
}
4 changes: 4 additions & 0 deletions sim/core/metrics_aggregator.go
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,10 @@ func (unit *Unit) NewFocusMetrics(actionID ActionID) *ResourceMetrics {
return unit.Metrics.NewResourceMetrics(actionID, proto.ResourceType_ResourceTypeFocus)
}

func (unit *Unit) NewGenericMetric(actionID ActionID) *ResourceMetrics {
return unit.Metrics.NewResourceMetrics(actionID, proto.ResourceType_ResourceTypeGenericResource)
}

// Adds the results of a spell to the character metrics.
func (unitMetrics *UnitMetrics) addSpellMetrics(spell *Spell, actionID ActionID, spellMetrics []SpellMetrics) {
actionMetrics, ok := unitMetrics.actions[actionID]
Expand Down
143 changes: 143 additions & 0 deletions sim/core/resource_bar.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
// Implements a generic resource bar that can be used to implement secondary resources
// TODO: Check whether pre-pull OOC resource loss needs to be supported for DemonicFury
package core

import "math"

type SecondaryResourceType int32

const (
SoulShards SecondaryResourceType = 117198
HolyPower SecondaryResourceType = 138248
Maelstrom SecondaryResourceType = 53817
Chi SecondaryResourceType = 97272
ArcaneCharges SecondaryResourceType = 36032
ShadowOrbs SecondaryResourceType = 95740
BurningEmbers SecondaryResourceType = 108647
DemonicFury SecondaryResourceType = 104315
)

type SecondaryResourceBar interface {
CanSpend(limit float64) bool // Check whether the current resource is available or not
Spend(amount float64, action ActionID, sim *Simulation) // Spend the specified amount of resource
SpendUpTo(limit float64, action ActionID, sim *Simulation) float64 // Spends as much resource as possible up to the speciefied limit; Returns the amount of resource spent
Gain(amount float64, action ActionID, sim *Simulation) // Gain the amount specified from the action
Reset(sim *Simulation) // Resets the current resource bar
Value() float64 // Returns the current amount of resource
}

type SecondaryResourceConfig struct {
Type SecondaryResourceType // The type of resource the bar tracks
Max float64 // The maximum amount the bar tracks
Default float64 // The default value this bar should be initialized with
}

type _SecondaryResourceBar struct {
config SecondaryResourceConfig
value float64
unit *Unit
metrics map[ActionID]*ResourceMetrics
}

// CanSpend implements SecondaryResourceBar.
func (bar *_SecondaryResourceBar) CanSpend(limit float64) bool {
return bar.value >= limit
}

// Gain implements SecondaryResourceBar.
func (bar *_SecondaryResourceBar) Gain(amount float64, action ActionID, sim *Simulation) {
bar.value += amount
baseAmount := amount
if bar.value > bar.config.Max {
amount -= bar.value - bar.config.Max
bar.value = bar.config.Max
}

metrics := bar.GetMetric(action)
metrics.AddEvent(baseAmount, amount)
if sim.Log != nil {
bar.unit.Log(
sim,
"Gained %0.0f generic resource from %s (%0.0f --> %0.0f) of %0.0f total.",
amount,
action,
bar.value-amount,
bar.value,
bar.config.Max,
)
}
}

// Reset implements SecondaryResourceBar.
func (bar *_SecondaryResourceBar) Reset(sim *Simulation) {
bar.value = 0
if bar.config.Default > 0 {
bar.Gain(bar.config.Default, ActionID{SpellID: int32(bar.config.Type)}, sim)
}
}

// Spend implements SecondaryResourceBar.
func (bar *_SecondaryResourceBar) Spend(amount float64, action ActionID, sim *Simulation) {
if amount > bar.value {
panic("Trying to spend more resource than is available.")
}

metrics := bar.GetMetric(action)
if sim.Log != nil {
bar.unit.Log(
sim,
"Spent %0.0f generic resource from %s (%0.0f --> %0.0f) of %0.0f total.",
amount,
metrics.ActionID,
bar.value,
bar.value-amount,
bar.config.Max,
)
}

metrics.AddEvent(-amount, -amount)
bar.value -= amount
}

// SpendUpTo implements SecondaryResourceBar.
func (bar *_SecondaryResourceBar) SpendUpTo(limit float64, action ActionID, sim *Simulation) float64 {
if bar.value > limit {
bar.Spend(limit, action, sim)
return limit
}

max := math.Floor(bar.value)
bar.Spend(max, action, sim)
return max
}

// Value implements SecondaryResourceBar.
func (bar *_SecondaryResourceBar) Value() float64 {
return bar.value
}

func (bar *_SecondaryResourceBar) GetMetric(action ActionID) *ResourceMetrics {
metric, ok := bar.metrics[action]
if !ok {
metric = bar.unit.NewGenericMetric(action)
bar.metrics[action] = metric
}

return metric
}

func (unit *Unit) NewSecondaryResourceBar(config SecondaryResourceConfig) SecondaryResourceBar {
if config.Type <= 0 {
panic("Invalid SecondaryResourceType given.")
}

if config.Max <= 0 {
panic("Invalid maximum resource value given.")
}

if config.Default < 0 || config.Default > config.Max {
panic("Invalid default value given for resource bar")
}

return &_SecondaryResourceBar{config: config, unit: unit, metrics: make(map[ActionID]*ResourceMetrics)}
}
18 changes: 18 additions & 0 deletions sim/core/unit.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,8 @@ type Unit struct {
focusBar
runicPowerBar

SecondaryResourceBar SecondaryResourceBar

// All spells that can be cast by this unit.
Spellbook []*Spell
spellRegistrationHandlers []SpellRegisteredHandler
Expand Down Expand Up @@ -588,6 +590,10 @@ func (unit *Unit) reset(sim *Simulation, _ Agent) {
unit.rageBar.reset(sim)
unit.runicPowerBar.reset(sim)

if unit.SecondaryResourceBar != nil {
unit.SecondaryResourceBar.Reset(sim)
}

unit.AutoAttacks.reset(sim)

if unit.Rotation != nil {
Expand Down Expand Up @@ -710,3 +716,15 @@ func (unit *Unit) GetTotalAvoidanceChance(atkTable *AttackTable) float64 {
block := unit.GetTotalBlockChanceAsDefender(atkTable)
return miss + dodge + parry + block
}

func (unit *Unit) RegisterSecondaryResourceBar(config SecondaryResourceConfig) {
if unit.SecondaryResourceBar != nil {
panic("A secondary resource bar has already been registered.")
}

if unit.Env.State == Finalized {
panic("Can not add secondary resource bar after unit has been finalized")
}

unit.SecondaryResourceBar = unit.NewSecondaryResourceBar(config)
}
5 changes: 3 additions & 2 deletions ui/core/components/detailed_results.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { REPO_NAME } from '../constants/other';
import { IndividualSimUI } from '../individual_sim_ui';
import { DetailedResultsUpdate, SimRun, SimRunData } from '../proto/ui';
import { SimResult } from '../proto_utils/sim_result';
import { SimUI } from '../sim_ui';
Expand Down Expand Up @@ -222,7 +223,7 @@ export abstract class DetailedResults extends Component {
new ResourceMetricsTable({
parent: this.rootElem.querySelector('.resource-metrics')!,
resultsEmitter: this.resultsEmitter,
});
}, simUI?.isIndividualSim ? (simUI as IndividualSimUI<any>).individualConfig.secondaryResource : undefined);
new PlayerDamageMetricsTable(
{ parent: this.rootElem.querySelector('.player-damage-metrics')!, resultsEmitter: this.resultsEmitter },
this.resultsFilter,
Expand Down Expand Up @@ -260,7 +261,7 @@ export abstract class DetailedResults extends Component {
parent: this.rootElem.querySelector('.timeline')!,
cssScheme: cssScheme,
resultsEmitter: this.resultsEmitter,
});
}, simUI?.isIndividualSim ? (simUI as IndividualSimUI<any>).individualConfig.secondaryResource : undefined);

const tabEl = document.querySelector('button[data-bs-target="#timelineTab"]');
tabEl?.addEventListener('shown.bs.tab', () => {
Expand Down
10 changes: 8 additions & 2 deletions ui/core/components/detailed_results/resource_metrics.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { SecondaryResourceConfig } from '../../individual_sim_ui';
import { ResourceType } from '../../proto/spell';
import { resourceNames } from '../../proto_utils/names';
import { ResourceMetrics } from '../../proto_utils/sim_result';
Expand All @@ -6,14 +7,19 @@ import { ColumnSortType, MetricsTable } from './metrics_table/metrics_table';
import { ResultComponent, ResultComponentConfig, SimResultData } from './result_component';

export class ResourceMetricsTable extends ResultComponent {
constructor(config: ResultComponentConfig) {
constructor(config: ResultComponentConfig, secondaryResourceConfig?: SecondaryResourceConfig) {
config.rootCssClass = 'resource-metrics-root';
super(config);

orderedResourceTypes.forEach(resourceType => {
let resourceName = resourceNames.get(resourceType);
if (resourceType == ResourceType.ResourceTypeGenericResource && secondaryResourceConfig !== undefined) {
resourceName = secondaryResourceConfig.name
}

const containerElem = (
<div className="resource-metrics-table-container hide">
<span className="resource-metrics-table-title">{resourceNames.get(resourceType)}</span>
<span className="resource-metrics-table-title">{resourceName}</span>
</div>
) as HTMLElement;
this.rootElem.appendChild(containerElem);
Expand Down
18 changes: 15 additions & 3 deletions ui/core/components/detailed_results/timeline.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { TypedEvent } from '../../typed_event';
import { bucket, distinct, fragmentToString, maxIndex, stringComparator } from '../../utils';
import { actionColors } from './color_settings';
import { ResultComponent, ResultComponentConfig, SimResultData } from './result_component';
import { SecondaryResourceConfig } from '../../individual_sim_ui';

type TooltipHandler = (dataPointIndex: number) => Element;

Expand Down Expand Up @@ -51,14 +52,17 @@ export class Timeline extends ResultComponent {
keysToKeep: 2,
});

constructor(config: ResultComponentConfig) {
private secondaryResourceConfig?: SecondaryResourceConfig;

constructor(config: ResultComponentConfig, secondaryResourceConfig?: SecondaryResourceConfig) {
config.rootCssClass = 'timeline-root';
super(config);
this.resultData = null;
this.prevResultData = null;
this.rendered = false;
this.hiddenIds = [];
this.hiddenIdsChangeEmitter = new TypedEvent<void>();
this.secondaryResourceConfig = secondaryResourceConfig;

this.rootElem.appendChild(
<div className="timeline-disclaimer">
Expand Down Expand Up @@ -770,14 +774,22 @@ export class Timeline extends ResultComponent {

return group.maxValue;
};

let resourceName = resourceNames.get(resourceType)
let resourceIcon = resourceTypeToIcon[resourceType]
if (resourceType == ResourceType.ResourceTypeGenericResource && this.secondaryResourceConfig !== undefined) {
resourceName = this.secondaryResourceConfig.name
resourceIcon = this.secondaryResourceConfig.icon
}

const labelElem = (
<div className="rotation-label rotation-row">
<a
className="rotation-label-icon"
style={{
backgroundImage: `url('${resourceTypeToIcon[resourceType]}')`,
backgroundImage: `url('${resourceIcon}')`,
}}></a>
<span className="rotation-label-text">{resourceNames.get(resourceType)}</span>
<span className="rotation-label-text">{resourceName}</span>
</div>
);

Expand Down
8 changes: 8 additions & 0 deletions ui/core/individual_sim_ui.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,13 @@ export interface IndividualSimUIConfig<SpecType extends Spec> extends PlayerConf
};

raidSimPresets: Array<RaidSimPreset<SpecType>>;
secondaryResource?: SecondaryResourceConfig;
}

export interface SecondaryResourceConfig {
name: string;
icon: string;
color: string;
}

export function registerSpecConfig<SpecType extends Spec>(spec: SpecType, config: IndividualSimUIConfig<SpecType>): IndividualSimUIConfig<SpecType> {
Expand Down Expand Up @@ -218,6 +225,7 @@ export abstract class IndividualSimUI<SpecType extends Spec> extends SimUI {
dpsRefStat?: Stat;
healRefStat?: Stat;
tankRefStat?: Stat;
secondaryResourceConfig?: SecondaryResourceConfig;

readonly bt: BulkTab | null = null;

Expand Down
Binary file added ui/core/proto_utils/.action_id.ts.swp
Binary file not shown.
1 change: 1 addition & 0 deletions ui/core/proto_utils/action_id.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1062,6 +1062,7 @@ export const resourceTypeToIcon: Record<ResourceType, string> = {
[ResourceType.ResourceTypeSolarEnergy]: 'https://wow.zamimg.com/images/wow/icons/large/ability_druid_eclipseorange.jpg',
[ResourceType.ResourceTypeLunarEnergy]: 'https://wow.zamimg.com/images/wow/icons/large/ability_druid_eclipse.jpg',
[ResourceType.ResourceTypeHolyPower]: 'https://wow.zamimg.com/images/wow/icons/medium/spell_holy_holybolt.jpg',
[ResourceType.ResourceTypeGenericResource]: 'https://wow.zamimg.com/images/wow/icons/medium/spell_holy_holybolt.jpg',
};

// Use this to connect a buff row to a cast row in the timeline view
Expand Down
2 changes: 1 addition & 1 deletion ui/core/proto_utils/logs_parser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -821,7 +821,7 @@ export class ResourceChangedLog extends SimLog {

static parse(params: SimLogParams): Promise<ResourceChangedLog> | null {
const match = params.raw.match(
/(Gained|Spent) (\d+\.?\d*) (health|mana|energy|focus|rage|chi|combo points|runic power|blood rune|frost rune|unholy rune|death rune|solar energy|lunar energy|holy power) from (.*?) \((\d+\.?\d*) --> (\d+\.?\d*)\)( of (\d+\.?\d*) total)?/,
/(Gained|Spent) (\d+\.?\d*) (health|mana|energy|focus|rage|chi|combo points|runic power|blood rune|frost rune|unholy rune|death rune|solar energy|lunar energy|holy power|generic resource) from (.*?) \((\d+\.?\d*) --> (\d+\.?\d*)\)( of (\d+\.?\d*) total)?/,
);
if (match) {
const resourceType = stringToResourceType(match[3]);
Expand Down
2 changes: 2 additions & 0 deletions ui/core/proto_utils/names.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ export const resourceNames: Map<ResourceType, string> = new Map([
[ResourceType.ResourceTypeSolarEnergy, 'Solar Energy'],
[ResourceType.ResourceTypeLunarEnergy, 'Lunar Energy'],
[ResourceType.ResourceTypeHolyPower, 'Holy Power'],
[ResourceType.ResourceTypeGenericResource, 'Generic Resource'],
]);

export const resourceColors: Map<ResourceType, string> = new Map([
Expand All @@ -232,6 +233,7 @@ export const resourceColors: Map<ResourceType, string> = new Map([
[ResourceType.ResourceTypeSolarEnergy, '#d2952b'],
[ResourceType.ResourceTypeLunarEnergy, '#2c4f8f'],
[ResourceType.ResourceTypeHolyPower, '#ffa07b'],
[ResourceType.ResourceTypeGenericResource, '#ffffff'],
]);

export function stringToResourceType(str: string): ResourceType {
Expand Down
1 change: 1 addition & 0 deletions ui/core/proto_utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2034,6 +2034,7 @@ export const orderedResourceTypes: Array<ResourceType> = [
ResourceType.ResourceTypeLunarEnergy,
ResourceType.ResourceTypeSolarEnergy,
ResourceType.ResourceTypeHolyPower,
ResourceType.ResourceTypeGenericResource,
];

export const AL_CATEGORY_HARD_MODE = 'Hard Mode';
Expand Down
6 changes: 6 additions & 0 deletions ui/priest/shadow/sim.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,12 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecShadowPriest, {
},
},
],

secondaryResource: {
color: "#b8a8f0",
icon: "https://wow.zamimg.com/images/wow/icons/large/spell_priest_shadoworbs.jpg",
name: "Shadow Orb",
}
});

export class ShadowPriestSimUI extends IndividualSimUI<Spec.SpecShadowPriest> {
Expand Down
Loading