Skip to content

Commit

Permalink
fix: spells are usable only if they meet all of their power requireme…
Browse files Browse the repository at this point in the history
…nts (#746)

* Consider very small power regeneration rates to be zero

The Blizzard API function GetPowerRegen() can return very small
numbers when the actual regeneration rate is zero. Treat very small
numbers to be equal to zero in getPowerRateAt().

Also, add some type signatures to getPowerRateAt() while we are
here.

* Check "time to power" for every power needed for a spell

Modify getTimeToPowerStateAt() to check every power type if one is
not specified, instead of just the primary resource type.

Fixes issue #737.
  • Loading branch information
johnnylam88 authored Dec 17, 2020
1 parent d8df7bd commit 18d7714
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 50 deletions.
2 changes: 1 addition & 1 deletion src/engine/best-action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,7 @@ export class OvaleBestActionClass {
if (timeToPower > timeToCd) {
result.actionResourceExtend = timeToPower - timeToCd;
this.tracer.Log(
"Spell ID '%s' requires an extra %fs for primary resource.",
"Spell ID '%s' requires an extra %f seconds for power requirements.",
spellId,
result.actionResourceExtend
);
Expand Down
1 change: 0 additions & 1 deletion src/ioc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,6 @@ export class IoC {
this.profiler,
this.data,
this.baseState,
this.paperDoll,
this.spellBook,
combat
);
Expand Down
132 changes: 85 additions & 47 deletions src/states/Power.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,11 @@ import {
import { isNumber, OneTimeMessage } from "../tools/tools";
import { OvaleDebugClass, Tracer } from "../engine/debug";
import { BaseState } from "./BaseState";
import { OvaleDataClass, SpellInfo } from "../engine/data";
import { OvaleDataClass } from "../engine/data";
import { OvaleClass } from "../Ovale";
import { AceModule } from "@wowts/tsaddon";
import { States, StateModule } from "../engine/state";
import { OvaleProfilerClass, Profiler } from "../engine/profiler";
import { OvalePaperDollClass } from "./PaperDoll";
import { OvaleSpellBookClass } from "./SpellBook";
import { OvaleCombatClass } from "./combat";
import { OptionUiAll } from "../ui/acegui-helpers";
Expand Down Expand Up @@ -107,7 +106,6 @@ export class OvalePowerClass extends States<PowerState> implements StateModule {
ovaleProfiler: OvaleProfilerClass,
private ovaleData: OvaleDataClass,
private baseState: BaseState,
private ovalePaperDoll: OvalePaperDollClass,
private ovaleSpellBook: OvaleSpellBookClass,
private combat: OvaleCombatClass
) {
Expand Down Expand Up @@ -606,12 +604,25 @@ export class OvalePowerClass extends States<PowerState> implements StateModule {
* Power regeneration rate for the given powerType.
* @param powerType
*/
getPowerRateAt(state: PowerState, powerType: string, atTime: number) {
getPowerRateAt(
state: PowerState,
powerType: string,
atTime: number
): number {
let rate: number;
if (this.combat.isInCombat(atTime)) {
return state.activeRegen[powerType];
rate = state.activeRegen[powerType] || 0;
} else {
return state.inactiveRegen[powerType];
rate = state.inactiveRegen[powerType] || 0;
}
const REGEN_RATE_MIN_THRESHOLD = 0.05;
if (
(rate > 0 && rate < REGEN_RATE_MIN_THRESHOLD) ||
(rate < 0 && rate > -1 * REGEN_RATE_MIN_THRESHOLD)
) {
rate = 0;
}
return rate;
}
/**
* Power atTime for the given powerType.
Expand All @@ -626,27 +637,24 @@ export class OvalePowerClass extends States<PowerState> implements StateModule {
let power = state.power[powerType] || 0;
const now = this.baseState.currentTime;
const seconds = atTime - now;
const powerRate = this.getPowerRateAt(state, powerType, atTime) || 0;
const powerRate = this.getPowerRateAt(state, powerType, atTime);
power = power + powerRate * seconds;
return power;
}

hasPowerFor(spellInfo: SpellInfo, atTime: number) {
for (const [, powerType] of kpairs(this.POWER_TYPE)) {
const cost = this.ovaleData.getSpellInfoProperty(
spellInfo,
atTime,
powerType
);
this.tracer.Log("Spell has cost of %d for %s", cost, powerType);
if (
cost !== undefined &&
cost > this.getPowerAt(this.GetState(atTime), powerType, atTime)
) {
return false;
}
}
return true;
hasPowerFor(
spellId: number,
atTime: number,
targetGUID?: string
): boolean {
const seconds = this.getTimeToPowerStateAt(
this.GetState(atTime),
spellId,
atTime,
targetGUID,
undefined
);
return (seconds === 0);
}

/**
Expand Down Expand Up @@ -742,33 +750,63 @@ export class OvalePowerClass extends States<PowerState> implements StateModule {
targetGUID: string | undefined,
powerType: PowerType | undefined,
extraPower?: number
) {
let seconds = 0;
powerType = powerType || POOLED_RESOURCE[this.ovalePaperDoll.class];
if (powerType) {
let [cost] = this.getPowerCostAt(
state,
spellId,
powerType,
atTime,
targetGUID
);
if (cost > 0) {
const power = this.getPowerAt(state, powerType, atTime);
if (extraPower) {
cost = cost + extraPower;
}
if (power < cost) {
const powerRate =
this.getPowerRateAt(state, powerType, atTime) || 0;
if (powerRate > 0) {
seconds = (cost - power) / powerRate;
} else {
seconds = INFINITY;
): number {
let timeToPower = 0;
const si = this.ovaleData.spellInfo[spellId];
if (si) {
for (const [, powerInfo] of kpairs(this.POWER_INFO)) {
const pType = powerInfo.type;
if (powerType === undefined || powerType == pType) {
let [cost] = this.getPowerCostAt(
state,
spellId,
pType,
atTime,
targetGUID
);
if (cost > 0) {
this.tracer.Log(" Spell ID '%d' has cost of %d %s",
spellId,
cost,
pType
);
if (powerType == pType && extraPower) {
this.tracer.Log(
" Including extra power %d for %s",
extraPower,
pType
);
cost = cost + extraPower;
}
const power = this.getPowerAt(state, pType, atTime);
if (power < cost) {
const powerRate =
this.getPowerRateAt(state, pType, atTime);
if (powerRate > 0) {
const seconds = (cost - power) / powerRate;
this.tracer.Log(
" Requires %f seconds to %d %s",
seconds,
cost,
pType
);
if (timeToPower < seconds) {
timeToPower = seconds;
}
} else {
timeToPower = INFINITY;
break;
}
}
}
}
}
}
return seconds;
this.tracer.Log(
"Spell ID '%d' requires %f seconds for power requirements.",
spellId,
timeToPower
);
return timeToPower;
}
}
2 changes: 1 addition & 1 deletion src/states/Spells.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ export class OvaleSpellsClass implements StateModule {
isUsable = false;
}
if (isUsable) {
noMana = !this.power.hasPowerFor(si, atTime);
noMana = !this.power.hasPowerFor(spellId, atTime, targetGUID);
if (noMana) {
isUsable = false;
this.tracer.Log(
Expand Down

0 comments on commit 18d7714

Please sign in to comment.