Skip to content

Commit

Permalink
Addes prepull apl action to put on cd spell linked to gear when equipped
Browse files Browse the repository at this point in the history
  • Loading branch information
Polynomix committed Nov 5, 2024
1 parent 7ec824e commit 46cd46f
Show file tree
Hide file tree
Showing 14 changed files with 115 additions and 16 deletions.
1 change: 1 addition & 0 deletions proto/api.proto
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,7 @@ message SpellStats {
bool encounter_only = 8; // Whether this spell may only be cast during the encounter (not prepull).
bool has_cast_time = 9; // Whether this spell has a cast time or not.
bool is_friendly = 10; // Whether this spell should be cast on player units
bool has_gscd = 11; // Wether this spell is associated to an item that incurs a 30s cd when equipped.
}
message APLActionStats {
repeated string warnings = 1;
Expand Down
7 changes: 6 additions & 1 deletion proto/apl.proto
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ message APLListItem {
APLAction action = 3; // The action to be performed.
}

// NextIndex: 24
// NextIndex: 25
message APLAction {
APLValue condition = 1; // If set, action will only execute if value is true or != 0.

Expand Down Expand Up @@ -73,6 +73,7 @@ message APLAction {
APLActionItemSwap item_swap = 17;
APLActionMove move = 21;
APLActionMoveDuration move_duration = 22;
APLActionTriggerGSCD trigger_gscd = 24;

// Class or Spec-specific actions
APLActionCatOptimalRotationAction cat_optimal_rotation_action = 18;
Expand Down Expand Up @@ -321,6 +322,10 @@ message APLActionMoveDuration {
APLValue duration = 1;
}

message APLActionTriggerGSCD {
ActionID spell_id = 1;
}

message APLActionCustomRotation {
}

Expand Down
2 changes: 2 additions & 0 deletions sim/core/apl_action.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,8 @@ func (rot *APLRotation) newAPLActionImpl(config *proto.APLAction) APLActionImpl
return rot.newActionMove(config.GetMove())
case *proto.APLAction_MoveDuration:
return rot.newActionMoveDuration(config.GetMoveDuration())
case *proto.APLAction_TriggerGscd:
return rot.newActionTriggerGSCD(config.GetTriggerGscd())
case *proto.APLAction_CustomRotation:
return rot.newActionCustomRotation(config.GetCustomRotation())

Expand Down
27 changes: 27 additions & 0 deletions sim/core/apl_actions_misc.go
Original file line number Diff line number Diff line change
Expand Up @@ -256,3 +256,30 @@ func (action *APLActionMoveDuration) IsReady(sim *Simulation) bool {
func (action *APLActionMoveDuration) String() string {
return "MoveDuration()"
}

type APLActionTriggerGSCD struct {
defaultAPLActionImpl
spell *Spell
}

func (rot *APLRotation) newActionTriggerGSCD(config *proto.APLActionTriggerGSCD) APLActionImpl {
spell := rot.GetAPLSpell(config.SpellId)
if spell == nil {
return nil
}
return &APLActionTriggerGSCD{
spell: spell,
}
}
func (action *APLActionTriggerGSCD) IsReady(sim *Simulation) bool {
return action.spell.GearSwapCD.IsReady(sim)
}
func (action *APLActionTriggerGSCD) Execute(sim *Simulation) {
if sim.Log != nil {
action.spell.Unit.Log(sim, "Triggering Gear Swap CD %s", action.spell.ActionID)
}
action.spell.GearSwapCD.Use(sim)
}
func (action *APLActionTriggerGSCD) String() string {
return fmt.Sprintf("Trigger Gear Swap CD(%s)", action.spell.ActionID)
}
19 changes: 17 additions & 2 deletions sim/core/cast.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,9 @@ type CastConfig struct {
// Automatically set if GCD and cast times are all 0, e.g. for empty casts.
IgnoreHaste bool

CD Cooldown
SharedCD Cooldown
CD Cooldown
SharedCD Cooldown
GearSwapCD Cooldown

CastTime func(spell *Spell) time.Duration
}
Expand Down Expand Up @@ -128,6 +129,13 @@ func (spell *Spell) makeCastFunc(config CastConfig) CastSuccessFunc {
}
}

if config.GearSwapCD.Timer != nil {
// By panicking if spell is on CD, we force each sim to properly check for their own CDs.
if !spell.GearSwapCD.IsReady(sim) {
return spell.castFailureHelper(sim, "still on swapped gear cooldown for %s, curTime = %s", spell.GearSwapCD.TimeToReady(sim), sim.CurrentTime)
}
}

// By panicking if spell is on CD, we force each sim to properly check for their own CDs.
if spell.CurCast.GCD > 0 && !spell.Unit.GCD.IsReady(sim) {
return spell.castFailureHelper(sim, "GCD on cooldown for %s, curTime = %s", spell.Unit.GCD.TimeToReady(sim), sim.CurrentTime)
Expand Down Expand Up @@ -243,6 +251,13 @@ func (spell *Spell) makeCastFuncSimple() CastSuccessFunc {
}
}

if spell.GearSwapCD.Timer != nil {
// By panicking if spell is on CD, we force each sim to properly check for their own CDs.
if !spell.GearSwapCD.IsReady(sim) {
return spell.castFailureHelper(sim, "still on swapped gear cooldown for %s, curTime = %s", spell.GearSwapCD.TimeToReady(sim), sim.CurrentTime)
}
}

if sim.Log != nil && !spell.Flags.Matches(SpellFlagNoLogs) {
spell.Unit.Log(sim, "Casting %s (Cost = %0.03f, Cast Time = %s, Effective Time = %s)",
spell.ActionID, 0.0, "0s", "0s")
Expand Down
4 changes: 4 additions & 0 deletions sim/core/consumes.go
Original file line number Diff line number Diff line change
Expand Up @@ -962,6 +962,10 @@ func registerTinkerHandsCD(agent Agent, consumes *proto.Consumes) {
Timer: character.GetOffensiveTrinketCD(),
Duration: time.Second * 10,
},
GearSwapCD: Cooldown{
Timer: character.NewTimer(),
Duration: time.Second * 30,
},
},

ApplyEffects: func(sim *Simulation, _ *Unit, _ *Spell) {
Expand Down
14 changes: 10 additions & 4 deletions sim/core/cooldown.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,29 +62,35 @@ func (cd *Cooldown) Reduce(t time.Duration) {
cd.Set(cd.ReadyAt() - t)
}

func BothTimersReadyAt(t1 *Timer, t2 *Timer) time.Duration {
func AllTimersReadyAt(t1 *Timer, t2 *Timer, t3 *Timer) time.Duration {
readyAt := time.Duration(0)
if t1 != nil {
readyAt = t1.ReadyAt()
}
if t2 != nil {
readyAt = max(readyAt, t2.ReadyAt())
}
if t3 != nil {
readyAt = max(readyAt, t3.ReadyAt())
}
return readyAt
}

func BothTimersReady(t1 *Timer, t2 *Timer, sim *Simulation) bool {
return (t1 == nil || t1.IsReady(sim)) && (t2 == nil || t2.IsReady(sim))
func AllTimersReady(t1 *Timer, t2 *Timer, t3 *Timer, sim *Simulation) bool {
return (t1 == nil || t1.IsReady(sim)) && (t2 == nil || t2.IsReady(sim)) && (t3 == nil || t3.IsReady(sim))
}

func MaxTimeToReady(t1 *Timer, t2 *Timer, sim *Simulation) time.Duration {
func MaxTimeToReady(t1 *Timer, t2 *Timer, t3 *Timer, sim *Simulation) time.Duration {
remaining := time.Duration(0)
if t1 != nil {
remaining = t1.TimeToReady(sim)
}
if t2 != nil {
remaining = max(remaining, t2.TimeToReady(sim))
}
if t3 != nil {
remaining = max(remaining, t3.TimeToReady(sim))
}
return remaining
}

Expand Down
6 changes: 6 additions & 0 deletions sim/core/item_effects.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,12 @@ func NewSimpleStatItemActiveEffect(itemID int32, bonus stats.Stats, duration tim
}
},
sharedCDFunc,
func(character *Character) Cooldown {
return Cooldown{
Timer: character.NewTimer(),
Duration: time.Second * 30,
}
},
)

if otherEffects == nil {
Expand Down
5 changes: 4 additions & 1 deletion sim/core/major_cooldown.go
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,7 @@ func RegisterTemporaryStatsOnUseCD(character *Character, auraLabel string, tempS
}

// Helper function to make an ApplyEffect for a temporary stats on-use cooldown.
func MakeTemporaryStatsOnUseCDRegistration(auraLabel string, tempStats stats.Stats, duration time.Duration, config SpellConfig, cdFunc func(*Character) Cooldown, sharedCDFunc func(*Character) Cooldown) ApplyEffect {
func MakeTemporaryStatsOnUseCDRegistration(auraLabel string, tempStats stats.Stats, duration time.Duration, config SpellConfig, cdFunc func(*Character) Cooldown, sharedCDFunc func(*Character) Cooldown, gearSwapCDFunc func(*Character) Cooldown) ApplyEffect {
return func(agent Agent) {
localConfig := config
character := agent.GetCharacter()
Expand All @@ -399,6 +399,9 @@ func MakeTemporaryStatsOnUseCDRegistration(auraLabel string, tempStats stats.Sta
if sharedCDFunc != nil {
localConfig.Cast.SharedCD = sharedCDFunc(character)
}
if gearSwapCDFunc != nil {
localConfig.Cast.GearSwapCD = gearSwapCDFunc(character)
}
RegisterTemporaryStatsOnUseCD(character, auraLabel, tempStats, duration, localConfig)
}
}
16 changes: 11 additions & 5 deletions sim/core/spell.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ type Spell struct {
DefaultCast Cast // Default cast parameters with all static effects applied.
CD Cooldown
SharedCD Cooldown
GearSwapCD Cooldown
ExtraCastCondition CanCastCondition

// Optional range constraints. If supplied, these are used to modify the ExtraCastCondition above to additionally check for DistanceFromTarget.
Expand Down Expand Up @@ -193,6 +194,10 @@ func (unit *Unit) RegisterSpell(config SpellConfig) *Spell {
panic("Cast.SharedCD w/o Duration specified for spell " + config.ActionID.String())
}

if config.Cast.GearSwapCD.Timer != nil && config.Cast.GearSwapCD.Duration == 0 {
panic("Cast.SwapGearCD w/o Duration specified for spell " + config.ActionID.String())
}

if config.Cast.CastTime == nil {
config.Cast.CastTime = func(spell *Spell) time.Duration {
return spell.Unit.ApplyCastSpeedForSpell(spell.DefaultCast.CastTime, spell)
Expand All @@ -211,6 +216,7 @@ func (unit *Unit) RegisterSpell(config SpellConfig) *Spell {
DefaultCast: config.Cast.DefaultCast,
CD: config.Cast.CD,
SharedCD: config.Cast.SharedCD,
GearSwapCD: config.Cast.GearSwapCD,
ExtraCastCondition: config.ExtraCastCondition,

castTimeFn: config.Cast.CastTime,
Expand Down Expand Up @@ -288,7 +294,7 @@ func (unit *Unit) RegisterSpell(config SpellConfig) *Spell {
}

if spell.DefaultCast == emptyCast {
if config.ExtraCastCondition == nil && config.Cast.CD.Timer == nil && config.Cast.SharedCD.Timer == nil {
if config.ExtraCastCondition == nil && config.Cast.CD.Timer == nil && config.Cast.SharedCD.Timer == nil && config.Cast.GearSwapCD.Timer == nil {
spell.castFn = spell.makeCastFuncAutosOrProcs()
} else {
spell.castFn = spell.makeCastFuncSimple()
Expand Down Expand Up @@ -481,18 +487,18 @@ func (spell *Spell) HealthMetrics(target *Unit) *ResourceMetrics {
}

func (spell *Spell) ReadyAt() time.Duration {
return BothTimersReadyAt(spell.CD.Timer, spell.SharedCD.Timer)
return AllTimersReadyAt(spell.CD.Timer, spell.SharedCD.Timer, spell.GearSwapCD.Timer)
}

func (spell *Spell) IsReady(sim *Simulation) bool {
if spell == nil {
return false
}
return BothTimersReady(spell.CD.Timer, spell.SharedCD.Timer, sim)
return AllTimersReady(spell.CD.Timer, spell.SharedCD.Timer, spell.GearSwapCD.Timer, sim)
}

func (spell *Spell) TimeToReady(sim *Simulation) time.Duration {
return MaxTimeToReady(spell.CD.Timer, spell.SharedCD.Timer, sim)
return MaxTimeToReady(spell.CD.Timer, spell.SharedCD.Timer, spell.GearSwapCD.Timer, sim)
}

// Returns whether a call to Cast() would be successful, without actually
Expand Down Expand Up @@ -532,7 +538,7 @@ func (spell *Spell) CanCast(sim *Simulation, target *Unit) bool {
return false
}

if !BothTimersReady(spell.CD.Timer, spell.SharedCD.Timer, sim) {
if !AllTimersReady(spell.CD.Timer, spell.SharedCD.Timer, spell.GearSwapCD.Timer, sim) {
//if sim.Log != nil {
// sim.Log("Cant cast because of CDs")
//}
Expand Down
4 changes: 2 additions & 2 deletions sim/core/spell_queueing.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ func (spell *Spell) CanQueue(sim *Simulation, target *Unit) bool {
}

// Spells that are within one SQW of coming off cooldown can also be queued
if MaxTimeToReady(spell.CD.Timer, spell.SharedCD.Timer, sim) > MaxSpellQueueWindow {
if MaxTimeToReady(spell.CD.Timer, spell.SharedCD.Timer, spell.GearSwapCD.Timer, sim) > MaxSpellQueueWindow {
return false
}

Expand All @@ -121,7 +121,7 @@ func (spell *Spell) CastOrQueue(sim *Simulation, target *Unit) {
spell.Cast(sim, target)
} else if spell.CanQueue(sim, target) {
// Determine which timer the spell is waiting on
queueTime := max(spell.Unit.Hardcast.Expires, BothTimersReadyAt(spell.CD.Timer, spell.SharedCD.Timer))
queueTime := max(spell.Unit.Hardcast.Expires, AllTimersReadyAt(spell.CD.Timer, spell.SharedCD.Timer, spell.GearSwapCD.Timer))

if (spell.DefaultCast.GCD > 0) || spell.Flags.Matches(SpellFlagMCD) {
queueTime = max(queueTime, spell.Unit.GCD.ReadyAt())
Expand Down
1 change: 1 addition & 0 deletions sim/core/unit.go
Original file line number Diff line number Diff line change
Expand Up @@ -638,6 +638,7 @@ func (unit *Unit) GetMetadata() *proto.UnitMetadata {
EncounterOnly: spell.Flags.Matches(SpellFlagEncounterOnly),
HasCastTime: spell.DefaultCast.CastTime > 0,
IsFriendly: spell.Flags.Matches(SpellFlagHelpful),
HasGscd: spell.GearSwapCD.Timer != nil,
}
})

Expand Down
9 changes: 9 additions & 0 deletions ui/core/components/individual_sim_ui/apl_actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
APLActionWait,
APLActionWaitUntil,
APLValue,
APLActionTriggerGSCD,
} from '../../proto/apl.js';
import { Spec } from '../../proto/common.js';
import { FeralDruid_Rotation_AplType } from '../../proto/druid.js';
Expand Down Expand Up @@ -586,6 +587,14 @@ const actionKindFactories: { [f in NonNullable<APLActionKind>]: ActionKindConfig
newValue: () => APLActionTriggerICD.create(),
fields: [AplHelpers.actionIdFieldConfig('auraId', 'icd_auras')],
}),
['triggerGscd']: inputBuilder({
label: 'Trigger Gear Swap CD',
submenu: ['Misc'],
shortDescription: "Triggers an item's spell CD of 30s when swapped in, putting it on cooldown. Example usage would be to emulate a trinket swap before combat starts.",
includeIf: (player: Player<any>, isPrepull: boolean) => isPrepull,
newValue: () => APLActionTriggerGSCD.create(),
fields: [AplHelpers.actionIdFieldConfig('spellId', 'gscd_spells')],
}),
['itemSwap']: inputBuilder({
label: 'Item Swap',
submenu: ['Misc'],
Expand Down
16 changes: 15 additions & 1 deletion ui/core/components/individual_sim_ui/apl_helpers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ export type ACTION_ID_SET =
| 'castable_dot_spells'
| 'shield_spells'
| 'non_instant_spells'
| 'friendly_spells';
| 'friendly_spells'
| 'gscd_spells';

const actionIdSets: Record<
ACTION_ID_SET,
Expand Down Expand Up @@ -243,6 +244,19 @@ const actionIdSets: Record<
});
},
},
gscd_spells : {
defaultLabel: 'Spell',
getActionIDs: async metadata => {
return metadata
.getSpells()
.filter(spell => spell.data.hasGscd)
.map(actionId => {
return {
value: actionId.id,
};
});
},
},
};

export type DEFAULT_UNIT_REF = 'self' | 'currentTarget';
Expand Down

0 comments on commit 46cd46f

Please sign in to comment.