diff --git a/sim/core/apl.go b/sim/core/apl.go index 73f09312ca..2e14e8eadc 100644 --- a/sim/core/apl.go +++ b/sim/core/apl.go @@ -24,6 +24,10 @@ type APLRotation struct { curWarnings []string prepullWarnings [][]string priorityListWarnings [][]string + + // Used for detecting infinite loops. + prevTime time.Duration + actionsWithoutAdvancing int } func (rot *APLRotation) validationWarning(message string, vals ...interface{}) { @@ -141,6 +145,8 @@ func (rot *APLRotation) allPrepullActions() []*APLAction { func (rot *APLRotation) reset(sim *Simulation) { rot.strictSequence = nil + rot.actionsWithoutAdvancing = 0 + rot.prevTime = 0 for _, action := range rot.allAPLActions() { action.impl.Reset(sim) } @@ -157,6 +163,18 @@ func (apl *APLRotation) DoNextAction(sim *Simulation) { if apl.unit.GCD.IsReady(sim) { apl.unit.WaitUntil(sim, sim.CurrentTime) } + + // Detect infinite loops. + if sim.CurrentTime == apl.prevTime { + apl.actionsWithoutAdvancing++ + if apl.actionsWithoutAdvancing > 1000 { + panic(fmt.Sprintf("[USER_ERROR] Infinite loop detected, current action:\n%s", action)) + } + } else { + apl.prevTime = sim.CurrentTime + apl.actionsWithoutAdvancing = 0 + } + return } } diff --git a/sim/core/apl_action.go b/sim/core/apl_action.go index b3f34c53b9..3e18d6f78b 100644 --- a/sim/core/apl_action.go +++ b/sim/core/apl_action.go @@ -1,6 +1,8 @@ package core import ( + "fmt" + "github.com/wowsims/wotlk/sim/core/proto" ) @@ -24,6 +26,14 @@ func (action *APLAction) GetAllActions() []*APLAction { return actions } +func (action *APLAction) String() string { + if action.condition == nil { + return fmt.Sprintf("ACTION = %s", action.impl) + } else { + return fmt.Sprintf("IF = %s\nACTION = %s", action.condition, action.impl) + } +} + type APLActionImpl interface { // Returns all inner Actions. GetInnerActions() []*APLAction @@ -39,8 +49,19 @@ type APLActionImpl interface { // Performs the action. Execute(*Simulation) + + // Pretty-print string for debugging. + String() string +} + +// Provides empty implementations for the action impl interface functions. +type defaultAPLActionImpl struct { } +func (impl defaultAPLActionImpl) GetInnerActions() []*APLAction { return nil } +func (impl defaultAPLActionImpl) Finalize(*APLRotation) {} +func (impl defaultAPLActionImpl) Reset(*Simulation) {} + func (rot *APLRotation) newAPLAction(config *proto.APLAction) *APLAction { if config == nil { return nil diff --git a/sim/core/apl_actions_core.go b/sim/core/apl_actions_core.go index 4635751263..fdbf80dc7f 100644 --- a/sim/core/apl_actions_core.go +++ b/sim/core/apl_actions_core.go @@ -1,10 +1,13 @@ package core import ( + "fmt" + "github.com/wowsims/wotlk/sim/core/proto" ) type APLActionCastSpell struct { + defaultAPLActionImpl spell *Spell target UnitReference } @@ -23,17 +26,18 @@ func (rot *APLRotation) newActionCastSpell(config *proto.APLActionCastSpell) APL target: target, } } -func (action *APLActionCastSpell) GetInnerActions() []*APLAction { return nil } -func (action *APLActionCastSpell) Finalize(*APLRotation) {} -func (action *APLActionCastSpell) Reset(*Simulation) {} func (action *APLActionCastSpell) IsReady(sim *Simulation) bool { return action.spell.CanCast(sim, action.target.Get()) } func (action *APLActionCastSpell) Execute(sim *Simulation) { action.spell.Cast(sim, action.target.Get()) } +func (action *APLActionCastSpell) String() string { + return fmt.Sprintf("Cast Spell(%s)", action.spell.ActionID) +} type APLActionMultidot struct { + defaultAPLActionImpl spell *Spell maxDots int32 maxOverlap APLValue @@ -67,8 +71,6 @@ func (rot *APLRotation) newActionMultidot(config *proto.APLActionMultidot) APLAc maxOverlap: maxOverlap, } } -func (action *APLActionMultidot) GetInnerActions() []*APLAction { return nil } -func (action *APLActionMultidot) Finalize(*APLRotation) {} func (action *APLActionMultidot) Reset(*Simulation) { action.nextTarget = nil } @@ -88,8 +90,12 @@ func (action *APLActionMultidot) IsReady(sim *Simulation) bool { func (action *APLActionMultidot) Execute(sim *Simulation) { action.spell.Cast(sim, action.nextTarget) } +func (action *APLActionMultidot) String() string { + return fmt.Sprintf("Multidot(%s)", action.spell.ActionID) +} type APLActionAutocastOtherCooldowns struct { + defaultAPLActionImpl character *Character nextReadyMCD *MajorCooldown @@ -101,8 +107,6 @@ func (rot *APLRotation) newActionAutocastOtherCooldowns(config *proto.APLActionA character: unit.Env.Raid.GetPlayerFromUnit(unit).GetCharacter(), } } -func (action *APLActionAutocastOtherCooldowns) GetInnerActions() []*APLAction { return nil } -func (action *APLActionAutocastOtherCooldowns) Finalize(*APLRotation) {} func (action *APLActionAutocastOtherCooldowns) Reset(*Simulation) { action.nextReadyMCD = nil } @@ -114,8 +118,12 @@ func (action *APLActionAutocastOtherCooldowns) Execute(sim *Simulation) { action.nextReadyMCD.tryActivateHelper(sim, action.character) action.character.UpdateMajorCooldowns() } +func (action *APLActionAutocastOtherCooldowns) String() string { + return fmt.Sprintf("Autocast Other Cooldowns") +} type APLActionWait struct { + defaultAPLActionImpl unit *Unit duration APLValue } @@ -127,12 +135,12 @@ func (rot *APLRotation) newActionWait(config *proto.APLActionWait) APLActionImpl duration: rot.coerceTo(rot.newAPLValue(config.Duration), proto.APLValueType_ValueTypeDuration), } } -func (action *APLActionWait) GetInnerActions() []*APLAction { return nil } -func (action *APLActionWait) Finalize(*APLRotation) {} -func (action *APLActionWait) Reset(*Simulation) {} func (action *APLActionWait) IsReady(sim *Simulation) bool { return action.duration != nil } func (action *APLActionWait) Execute(sim *Simulation) { action.unit.WaitUntil(sim, sim.CurrentTime+action.duration.GetDuration(sim)) } +func (action *APLActionWait) String() string { + return fmt.Sprintf("Wait(%s)", action.duration) +} diff --git a/sim/core/apl_actions_misc.go b/sim/core/apl_actions_misc.go index 8aa7e9c8c9..52b64233bd 100644 --- a/sim/core/apl_actions_misc.go +++ b/sim/core/apl_actions_misc.go @@ -1,10 +1,13 @@ package core import ( + "fmt" + "github.com/wowsims/wotlk/sim/core/proto" ) type APLActionChangeTarget struct { + defaultAPLActionImpl unit *Unit newTarget UnitReference } @@ -18,9 +21,6 @@ func (rot *APLRotation) newActionChangeTarget(config *proto.APLActionChangeTarge newTarget: newTarget, } } -func (action *APLActionChangeTarget) GetInnerActions() []*APLAction { return nil } -func (action *APLActionChangeTarget) Finalize(*APLRotation) {} -func (action *APLActionChangeTarget) Reset(*Simulation) {} func (action *APLActionChangeTarget) IsReady(sim *Simulation) bool { return action.unit.CurrentTarget != action.newTarget.Get() } @@ -30,8 +30,12 @@ func (action *APLActionChangeTarget) Execute(sim *Simulation) { } action.unit.CurrentTarget = action.newTarget.Get() } +func (action *APLActionChangeTarget) String() string { + return fmt.Sprintf("Change Target(%s)", action.newTarget.Get().Label) +} type APLActionCancelAura struct { + defaultAPLActionImpl aura *Aura } @@ -44,9 +48,6 @@ func (rot *APLRotation) newActionCancelAura(config *proto.APLActionCancelAura) A aura: aura.Get(), } } -func (action *APLActionCancelAura) GetInnerActions() []*APLAction { return nil } -func (action *APLActionCancelAura) Finalize(*APLRotation) {} -func (action *APLActionCancelAura) Reset(*Simulation) {} func (action *APLActionCancelAura) IsReady(sim *Simulation) bool { return action.aura.IsActive() } @@ -56,8 +57,12 @@ func (action *APLActionCancelAura) Execute(sim *Simulation) { } action.aura.Deactivate(sim) } +func (action *APLActionCancelAura) String() string { + return fmt.Sprintf("Cancel Aura(%s)", action.aura.ActionID) +} type APLActionTriggerICD struct { + defaultAPLActionImpl aura *Aura } @@ -70,9 +75,6 @@ func (rot *APLRotation) newActionTriggerICD(config *proto.APLActionTriggerICD) A aura: aura.Get(), } } -func (action *APLActionTriggerICD) GetInnerActions() []*APLAction { return nil } -func (action *APLActionTriggerICD) Finalize(*APLRotation) {} -func (action *APLActionTriggerICD) Reset(*Simulation) {} func (action *APLActionTriggerICD) IsReady(sim *Simulation) bool { return action.aura.IsActive() } @@ -82,3 +84,6 @@ func (action *APLActionTriggerICD) Execute(sim *Simulation) { } action.aura.Icd.Use(sim) } +func (action *APLActionTriggerICD) String() string { + return fmt.Sprintf("Trigger ICD(%s)", action.aura.ActionID) +} diff --git a/sim/core/apl_actions_sequences.go b/sim/core/apl_actions_sequences.go index df66c2c93a..f36b587c64 100644 --- a/sim/core/apl_actions_sequences.go +++ b/sim/core/apl_actions_sequences.go @@ -1,10 +1,14 @@ package core import ( + "fmt" + "strings" + "github.com/wowsims/wotlk/sim/core/proto" ) type APLActionSequence struct { + defaultAPLActionImpl unit *Unit name string actions []*APLAction @@ -41,8 +45,12 @@ func (action *APLActionSequence) Execute(sim *Simulation) { action.actions[action.curIdx].Execute(sim) action.curIdx++ } +func (action *APLActionSequence) String() string { + return "Sequence(" + strings.Join(MapSlice(action.actions, func(subaction *APLAction) string { return fmt.Sprintf("(%s)", subaction) }), "+") + ")" +} type APLActionResetSequence struct { + defaultAPLActionImpl unit *Unit name string sequence *APLActionSequence @@ -58,7 +66,6 @@ func (rot *APLRotation) newActionResetSequence(config *proto.APLActionResetSeque name: config.SequenceName, } } -func (action *APLActionResetSequence) GetInnerActions() []*APLAction { return nil } func (action *APLActionResetSequence) Finalize(rot *APLRotation) { for _, otherAction := range rot.allAPLActions() { if sequence, ok := otherAction.impl.(*APLActionSequence); ok && sequence.name == action.name { @@ -68,15 +75,18 @@ func (action *APLActionResetSequence) Finalize(rot *APLRotation) { } rot.validationWarning("No sequence with name: '%s'", action.name) } -func (action *APLActionResetSequence) Reset(*Simulation) {} func (action *APLActionResetSequence) IsReady(sim *Simulation) bool { return action.sequence != nil } func (action *APLActionResetSequence) Execute(sim *Simulation) { action.sequence.curIdx = 0 } +func (action *APLActionResetSequence) String() string { + return fmt.Sprintf("Reset Sequence(name = '%s')", action.name) +} type APLActionStrictSequence struct { + defaultAPLActionImpl unit *Unit actions []*APLAction curIdx int @@ -128,3 +138,6 @@ func (action *APLActionStrictSequence) Execute(sim *Simulation) { action.unit.Rotation.strictSequence = nil } } +func (action *APLActionStrictSequence) String() string { + return "Strict Sequence(" + strings.Join(MapSlice(action.actions, func(subaction *APLAction) string { return fmt.Sprintf("(%s)", subaction) }), "+") + ")" +} diff --git a/sim/core/apl_helpers.go b/sim/core/apl_helpers.go index 0a7443effb..f5f3e8a740 100644 --- a/sim/core/apl_helpers.go +++ b/sim/core/apl_helpers.go @@ -21,6 +21,10 @@ func (ur UnitReference) Get() *Unit { } } +func (ur *UnitReference) String() string { + return ur.Get().Label +} + func NewUnitReference(ref *proto.UnitReference, contextUnit *Unit) UnitReference { if ref == nil || ref.Type == proto.UnitReference_Unknown { return UnitReference{} @@ -70,6 +74,10 @@ func (ar *AuraReference) Get() *Aura { } } +func (ar *AuraReference) String() string { + return ar.Get().ActionID.String() +} + func newAuraReferenceHelper(sourceUnit UnitReference, auraId *proto.ActionID, auraGetter func(*Unit, ActionID) *Aura) AuraReference { if sourceUnit.Get() == nil { return AuraReference{} diff --git a/sim/core/apl_value.go b/sim/core/apl_value.go index 6c229351d1..202d9feb90 100644 --- a/sim/core/apl_value.go +++ b/sim/core/apl_value.go @@ -17,6 +17,9 @@ type APLValue interface { GetFloat(*Simulation) float64 GetDuration(*Simulation) time.Duration GetString(*Simulation) string + + // Pretty-print string for debugging. + String() string } // Provides empty implementations for the GetX() value interface functions. diff --git a/sim/core/apl_values_aura.go b/sim/core/apl_values_aura.go index 5db388d062..0e61b89197 100644 --- a/sim/core/apl_values_aura.go +++ b/sim/core/apl_values_aura.go @@ -1,6 +1,7 @@ package core import ( + "fmt" "time" "github.com/wowsims/wotlk/sim/core/proto" @@ -26,6 +27,9 @@ func (value *APLValueAuraIsActive) Type() proto.APLValueType { func (value *APLValueAuraIsActive) GetBool(sim *Simulation) bool { return value.aura.Get().IsActive() } +func (value *APLValueAuraIsActive) String() string { + return fmt.Sprintf("Aura Is Active(%s)", value.aura.String()) +} type APLValueAuraRemainingTime struct { defaultAPLValueImpl @@ -47,6 +51,9 @@ func (value *APLValueAuraRemainingTime) Type() proto.APLValueType { func (value *APLValueAuraRemainingTime) GetDuration(sim *Simulation) time.Duration { return value.aura.Get().RemainingDuration(sim) } +func (value *APLValueAuraRemainingTime) String() string { + return fmt.Sprintf("Aura Remaining Time(%s)", value.aura.String()) +} type APLValueAuraNumStacks struct { defaultAPLValueImpl @@ -72,6 +79,9 @@ func (value *APLValueAuraNumStacks) Type() proto.APLValueType { func (value *APLValueAuraNumStacks) GetInt(sim *Simulation) int32 { return value.aura.Get().GetStacks() } +func (value *APLValueAuraNumStacks) String() string { + return fmt.Sprintf("Aura Num Stacks(%s)", value.aura.String()) +} type APLValueAuraInternalCooldown struct { defaultAPLValueImpl @@ -93,3 +103,6 @@ func (value *APLValueAuraInternalCooldown) Type() proto.APLValueType { func (value *APLValueAuraInternalCooldown) GetDuration(sim *Simulation) time.Duration { return value.aura.Get().Icd.TimeToReady(sim) } +func (value *APLValueAuraInternalCooldown) String() string { + return fmt.Sprintf("Aura ICD(%s)", value.aura.String()) +} diff --git a/sim/core/apl_values_auto_attacks.go b/sim/core/apl_values_auto_attacks.go index bf5dde3bb2..31be9710e6 100644 --- a/sim/core/apl_values_auto_attacks.go +++ b/sim/core/apl_values_auto_attacks.go @@ -1,6 +1,7 @@ package core import ( + "fmt" "time" "github.com/wowsims/wotlk/sim/core/proto" @@ -22,3 +23,6 @@ func (value *APLValueAutoTimeToNext) Type() proto.APLValueType { func (value *APLValueAutoTimeToNext) GetDuration(sim *Simulation) time.Duration { return MaxDuration(0, value.unit.AutoAttacks.NextAttackAt()-sim.CurrentTime) } +func (value *APLValueAutoTimeToNext) String() string { + return fmt.Sprintf("Auto Time To Next") +} diff --git a/sim/core/apl_values_core.go b/sim/core/apl_values_core.go index 08d85a4289..ea477a6b84 100644 --- a/sim/core/apl_values_core.go +++ b/sim/core/apl_values_core.go @@ -1,6 +1,7 @@ package core import ( + "fmt" "time" "github.com/wowsims/wotlk/sim/core/proto" @@ -26,6 +27,9 @@ func (value *APLValueDotIsActive) Type() proto.APLValueType { func (value *APLValueDotIsActive) GetBool(sim *Simulation) bool { return value.dot.IsActive() } +func (value *APLValueDotIsActive) String() string { + return fmt.Sprintf("Dot Is Active(%s)", value.dot.Spell.ActionID) +} type APLValueDotRemainingTime struct { defaultAPLValueImpl @@ -47,3 +51,6 @@ func (value *APLValueDotRemainingTime) Type() proto.APLValueType { func (value *APLValueDotRemainingTime) GetDuration(sim *Simulation) time.Duration { return value.dot.RemainingDuration(sim) } +func (value *APLValueDotRemainingTime) String() string { + return fmt.Sprintf("Dot Remaining Time(%s)", value.dot.Spell.ActionID) +} diff --git a/sim/core/apl_values_encounter.go b/sim/core/apl_values_encounter.go index 29c1b8737e..250a5f9a52 100644 --- a/sim/core/apl_values_encounter.go +++ b/sim/core/apl_values_encounter.go @@ -1,6 +1,7 @@ package core import ( + "fmt" "time" "github.com/wowsims/wotlk/sim/core/proto" @@ -19,6 +20,9 @@ func (value *APLValueCurrentTime) Type() proto.APLValueType { func (value *APLValueCurrentTime) GetDuration(sim *Simulation) time.Duration { return sim.CurrentTime } +func (value *APLValueCurrentTime) String() string { + return fmt.Sprintf("Current Time") +} type APLValueCurrentTimePercent struct { defaultAPLValueImpl @@ -33,6 +37,9 @@ func (value *APLValueCurrentTimePercent) Type() proto.APLValueType { func (value *APLValueCurrentTimePercent) GetFloat(sim *Simulation) float64 { return sim.CurrentTime.Seconds() / sim.Duration.Seconds() } +func (value *APLValueCurrentTimePercent) String() string { + return fmt.Sprintf("Current Time %%") +} type APLValueRemainingTime struct { defaultAPLValueImpl @@ -47,6 +54,9 @@ func (value *APLValueRemainingTime) Type() proto.APLValueType { func (value *APLValueRemainingTime) GetDuration(sim *Simulation) time.Duration { return sim.GetRemainingDuration() } +func (value *APLValueRemainingTime) String() string { + return fmt.Sprintf("Remaining Time") +} type APLValueRemainingTimePercent struct { defaultAPLValueImpl @@ -61,6 +71,9 @@ func (value *APLValueRemainingTimePercent) Type() proto.APLValueType { func (value *APLValueRemainingTimePercent) GetFloat(sim *Simulation) float64 { return sim.GetRemainingDurationPercent() } +func (value *APLValueRemainingTimePercent) String() string { + return fmt.Sprintf("Remaining Time %%") +} type APLValueNumberTargets struct { defaultAPLValueImpl @@ -75,6 +88,9 @@ func (value *APLValueNumberTargets) Type() proto.APLValueType { func (value *APLValueNumberTargets) GetInt(sim *Simulation) int32 { return sim.GetNumTargets() } +func (value *APLValueNumberTargets) String() string { + return fmt.Sprintf("Num Targets") +} type APLValueIsExecutePhase struct { defaultAPLValueImpl @@ -103,3 +119,6 @@ func (value *APLValueIsExecutePhase) GetBool(sim *Simulation) bool { panic("Should never reach here") } } +func (value *APLValueIsExecutePhase) String() string { + return fmt.Sprintf("Is Execute Phase") +} diff --git a/sim/core/apl_values_gcd.go b/sim/core/apl_values_gcd.go index 5f185b98ef..2c0bb71be3 100644 --- a/sim/core/apl_values_gcd.go +++ b/sim/core/apl_values_gcd.go @@ -1,6 +1,7 @@ package core import ( + "fmt" "time" "github.com/wowsims/wotlk/sim/core/proto" @@ -22,6 +23,9 @@ func (value *APLValueGCDIsReady) Type() proto.APLValueType { func (value *APLValueGCDIsReady) GetBool(sim *Simulation) bool { return value.unit.GCD.IsReady(sim) } +func (value *APLValueGCDIsReady) String() string { + return fmt.Sprintf("GCD Is Ready") +} type APLValueGCDTimeToReady struct { defaultAPLValueImpl @@ -39,3 +43,6 @@ func (value *APLValueGCDTimeToReady) Type() proto.APLValueType { func (value *APLValueGCDTimeToReady) GetDuration(sim *Simulation) time.Duration { return value.unit.GCD.TimeToReady(sim) } +func (value *APLValueGCDTimeToReady) String() string { + return fmt.Sprintf("GCD Time To Ready") +} diff --git a/sim/core/apl_values_operators.go b/sim/core/apl_values_operators.go index 11ce6d94dd..577f8c1d2e 100644 --- a/sim/core/apl_values_operators.go +++ b/sim/core/apl_values_operators.go @@ -3,6 +3,7 @@ package core import ( "fmt" "strconv" + "strings" "time" "github.com/wowsims/wotlk/sim/core/proto" @@ -74,6 +75,9 @@ func (value *APLValueConst) GetDuration(sim *Simulation) time.Duration { func (value *APLValueConst) GetString(sim *Simulation) string { return value.stringVal } +func (value *APLValueConst) String() string { + return value.stringVal +} type APLValueCoerced struct { valueType proto.APLValueType @@ -166,6 +170,9 @@ func (value APLValueCoerced) GetString(sim *Simulation) string { } return "" } +func (value *APLValueCoerced) String() string { + return value.inner.String() +} // Wraps a value so that it is converted into a Boolean. func (rot *APLRotation) coerceTo(value APLValue, newType proto.APLValueType) APLValue { @@ -310,6 +317,9 @@ func (value *APLValueCompare) GetBool(sim *Simulation) bool { } return false } +func (value *APLValueCompare) String() string { + return fmt.Sprintf("%s %s %s", value.lhs, value.op, value.rhs) +} type APLValueMath struct { defaultAPLValueImpl @@ -404,6 +414,9 @@ func (value *APLValueMath) GetDuration(sim *Simulation) time.Duration { } return 0 } +func (value *APLValueMath) String() string { + return fmt.Sprintf("%s %s %s", value.lhs, value.op, value.rhs) +} type APLValueAnd struct { defaultAPLValueImpl @@ -433,6 +446,9 @@ func (value *APLValueAnd) GetBool(sim *Simulation) bool { } return true } +func (value *APLValueAnd) String() string { + return strings.Join(MapSlice(value.vals, func(subvalue APLValue) string { return fmt.Sprintf("(%s)", subvalue) }), " AND ") +} type APLValueOr struct { defaultAPLValueImpl @@ -462,6 +478,9 @@ func (value *APLValueOr) GetBool(sim *Simulation) bool { } return false } +func (value *APLValueOr) String() string { + return strings.Join(MapSlice(value.vals, func(subvalue APLValue) string { return fmt.Sprintf("(%s)", subvalue) }), " OR ") +} type APLValueNot struct { defaultAPLValueImpl @@ -483,3 +502,6 @@ func (value *APLValueNot) Type() proto.APLValueType { func (value *APLValueNot) GetBool(sim *Simulation) bool { return !value.val.GetBool(sim) } +func (value *APLValueNot) String() string { + return fmt.Sprintf("Not(%s)", value.val) +} diff --git a/sim/core/apl_values_resources.go b/sim/core/apl_values_resources.go index 9e2f02fa10..dbafec7cde 100644 --- a/sim/core/apl_values_resources.go +++ b/sim/core/apl_values_resources.go @@ -1,6 +1,8 @@ package core import ( + "fmt" + "github.com/wowsims/wotlk/sim/core/proto" ) @@ -28,6 +30,9 @@ func (value *APLValueCurrentHealth) Type() proto.APLValueType { func (value *APLValueCurrentHealth) GetFloat(sim *Simulation) float64 { return value.unit.Get().CurrentHealth() } +func (value *APLValueCurrentHealth) String() string { + return fmt.Sprintf("Current Health") +} type APLValueCurrentHealthPercent struct { defaultAPLValueImpl @@ -53,6 +58,9 @@ func (value *APLValueCurrentHealthPercent) Type() proto.APLValueType { func (value *APLValueCurrentHealthPercent) GetFloat(sim *Simulation) float64 { return value.unit.Get().CurrentHealthPercent() } +func (value *APLValueCurrentHealthPercent) String() string { + return fmt.Sprintf("Current Health %%") +} type APLValueCurrentMana struct { defaultAPLValueImpl @@ -78,6 +86,9 @@ func (value *APLValueCurrentMana) Type() proto.APLValueType { func (value *APLValueCurrentMana) GetFloat(sim *Simulation) float64 { return value.unit.Get().CurrentMana() } +func (value *APLValueCurrentMana) String() string { + return fmt.Sprintf("Current Mana") +} type APLValueCurrentManaPercent struct { defaultAPLValueImpl @@ -103,6 +114,9 @@ func (value *APLValueCurrentManaPercent) Type() proto.APLValueType { func (value *APLValueCurrentManaPercent) GetFloat(sim *Simulation) float64 { return value.unit.Get().CurrentManaPercent() } +func (value *APLValueCurrentManaPercent) String() string { + return fmt.Sprintf("Current Mana %%") +} type APLValueCurrentRage struct { defaultAPLValueImpl @@ -125,6 +139,9 @@ func (value *APLValueCurrentRage) Type() proto.APLValueType { func (value *APLValueCurrentRage) GetFloat(sim *Simulation) float64 { return value.unit.CurrentRage() } +func (value *APLValueCurrentRage) String() string { + return fmt.Sprintf("Current Rage") +} type APLValueCurrentEnergy struct { defaultAPLValueImpl @@ -147,6 +164,9 @@ func (value *APLValueCurrentEnergy) Type() proto.APLValueType { func (value *APLValueCurrentEnergy) GetFloat(sim *Simulation) float64 { return value.unit.CurrentEnergy() } +func (value *APLValueCurrentEnergy) String() string { + return fmt.Sprintf("Current Energy") +} type APLValueCurrentComboPoints struct { defaultAPLValueImpl @@ -169,6 +189,9 @@ func (value *APLValueCurrentComboPoints) Type() proto.APLValueType { func (value *APLValueCurrentComboPoints) GetInt(sim *Simulation) int32 { return value.unit.ComboPoints() } +func (value *APLValueCurrentComboPoints) String() string { + return fmt.Sprintf("Current Combo Points") +} type APLValueCurrentRunicPower struct { defaultAPLValueImpl @@ -191,3 +214,6 @@ func (value *APLValueCurrentRunicPower) Type() proto.APLValueType { func (value *APLValueCurrentRunicPower) GetInt(sim *Simulation) int32 { return int32(value.unit.CurrentRunicPower()) } +func (value *APLValueCurrentRunicPower) String() string { + return fmt.Sprintf("Current Runic Power") +} diff --git a/sim/core/apl_values_runes.go b/sim/core/apl_values_runes.go index 8048855cc8..66d6627ac1 100644 --- a/sim/core/apl_values_runes.go +++ b/sim/core/apl_values_runes.go @@ -1,6 +1,7 @@ package core import ( + "fmt" "time" "github.com/wowsims/wotlk/sim/core/proto" @@ -39,6 +40,9 @@ func (value *APLValueCurrentRuneCount) GetInt(sim *Simulation) int32 { } return 0 } +func (value *APLValueCurrentRuneCount) String() string { + return fmt.Sprintf("Current Rune Count(%s)", value.runeType) +} type APLValueCurrentNonDeathRuneCount struct { defaultAPLValueImpl @@ -71,6 +75,9 @@ func (value *APLValueCurrentNonDeathRuneCount) GetInt(sim *Simulation) int32 { } return 0 } +func (value *APLValueCurrentNonDeathRuneCount) String() string { + return fmt.Sprintf("Current Non-Death Rune Count(%s)", value.runeType) +} type APLValueCurrentRuneActive struct { defaultAPLValueImpl @@ -95,6 +102,9 @@ func (value *APLValueCurrentRuneActive) Type() proto.APLValueType { func (value *APLValueCurrentRuneActive) GetBool(sim *Simulation) bool { return value.unit.RuneIsActive(value.runeSlot) } +func (value *APLValueCurrentRuneActive) String() string { + return fmt.Sprintf("Current Rune Active(%d)", value.runeSlot) +} type APLValueCurrentRuneDeath struct { defaultAPLValueImpl @@ -119,6 +129,9 @@ func (value *APLValueCurrentRuneDeath) Type() proto.APLValueType { func (value *APLValueCurrentRuneDeath) GetBool(sim *Simulation) bool { return value.unit.RuneIsDeath(int8(value.runeSlot)) } +func (value *APLValueCurrentRuneDeath) String() string { + return fmt.Sprintf("Current Rune Death(%d)", value.runeSlot) +} type APLValueRuneCooldown struct { defaultAPLValueImpl @@ -151,6 +164,9 @@ func (value *APLValueRuneCooldown) GetDuration(sim *Simulation) time.Duration { } return 0 } +func (value *APLValueRuneCooldown) String() string { + return fmt.Sprintf("Rune Cooldoean(%s)", value.runeType) +} type APLValueNextRuneCooldown struct { defaultAPLValueImpl @@ -183,3 +199,6 @@ func (value *APLValueNextRuneCooldown) GetDuration(sim *Simulation) time.Duratio } return 0 } +func (value *APLValueNextRuneCooldown) String() string { + return fmt.Sprintf("Next Rune Cooldoean(%s)", value.runeType) +} diff --git a/sim/core/apl_values_spell.go b/sim/core/apl_values_spell.go index 316f360133..81a8e03031 100644 --- a/sim/core/apl_values_spell.go +++ b/sim/core/apl_values_spell.go @@ -1,6 +1,7 @@ package core import ( + "fmt" "time" "github.com/wowsims/wotlk/sim/core/proto" @@ -26,6 +27,9 @@ func (value *APLValueSpellCanCast) Type() proto.APLValueType { func (value *APLValueSpellCanCast) GetBool(sim *Simulation) bool { return value.spell.CanCast(sim, value.spell.Unit.CurrentTarget) } +func (value *APLValueSpellCanCast) String() string { + return fmt.Sprintf("Can Cast(%s)", value.spell.ActionID) +} type APLValueSpellIsReady struct { defaultAPLValueImpl @@ -47,6 +51,9 @@ func (value *APLValueSpellIsReady) Type() proto.APLValueType { func (value *APLValueSpellIsReady) GetBool(sim *Simulation) bool { return value.spell.IsReady(sim) } +func (value *APLValueSpellIsReady) String() string { + return fmt.Sprintf("Is Ready(%s)", value.spell.ActionID) +} type APLValueSpellTimeToReady struct { defaultAPLValueImpl @@ -68,6 +75,9 @@ func (value *APLValueSpellTimeToReady) Type() proto.APLValueType { func (value *APLValueSpellTimeToReady) GetDuration(sim *Simulation) time.Duration { return value.spell.TimeToReady(sim) } +func (value *APLValueSpellTimeToReady) String() string { + return fmt.Sprintf("Time To Ready(%s)", value.spell.ActionID) +} type APLValueSpellCastTime struct { defaultAPLValueImpl @@ -89,6 +99,9 @@ func (value *APLValueSpellCastTime) Type() proto.APLValueType { func (value *APLValueSpellCastTime) GetDuration(sim *Simulation) time.Duration { return value.spell.Unit.ApplyCastSpeedForSpell(value.spell.DefaultCast.CastTime, value.spell) } +func (value *APLValueSpellCastTime) String() string { + return fmt.Sprintf("Cast Time(%s)", value.spell.ActionID) +} type APLValueSpellChannelTime struct { defaultAPLValueImpl @@ -110,6 +123,9 @@ func (value *APLValueSpellChannelTime) Type() proto.APLValueType { func (value *APLValueSpellChannelTime) GetDuration(sim *Simulation) time.Duration { return value.spell.Unit.ApplyCastSpeedForSpell(value.spell.DefaultCast.ChannelTime, value.spell) } +func (value *APLValueSpellChannelTime) String() string { + return fmt.Sprintf("Channel Time(%s)", value.spell.ActionID) +} type APLValueSpellTravelTime struct { defaultAPLValueImpl @@ -131,3 +147,6 @@ func (value *APLValueSpellTravelTime) Type() proto.APLValueType { func (value *APLValueSpellTravelTime) GetDuration(sim *Simulation) time.Duration { return time.Duration(float64(time.Second) * value.spell.Unit.DistanceFromTarget / value.spell.MissileSpeed) } +func (value *APLValueSpellTravelTime) String() string { + return fmt.Sprintf("Travel Time(%s)", value.spell.ActionID) +} diff --git a/ui/core/sim_ui.ts b/ui/core/sim_ui.ts index 0d18515918..c99ef34915 100644 --- a/ui/core/sim_ui.ts +++ b/ui/core/sim_ui.ts @@ -277,6 +277,12 @@ export abstract class SimUI extends Component { } const errorStr = (error as SimError).errorStr; + if (errorStr.startsWith('[USER_ERROR] ')) { + const alertStr = errorStr.substring('[USER_ERROR] '.length); + alert(alertStr); + return; + } + if (window.confirm('Simulation Failure:\n' + errorStr + '\nPress Ok to file crash report')) { // Splice out just the line numbers const hash = this.hashCode(errorStr);