diff --git a/assets/database/db.bin b/assets/database/db.bin index 5b13992627..fe8c6e6558 100644 Binary files a/assets/database/db.bin and b/assets/database/db.bin differ diff --git a/assets/database/db.json b/assets/database/db.json index 9204cdbb50..32dd634c33 100644 --- a/assets/database/db.json +++ b/assets/database/db.json @@ -9143,6 +9143,7 @@ {"id":54044,"name":"Carrion Feeder","icon":"ability_racial_cannibalize"}, {"id":54118,"name":"Improved Soul Leech","icon":"ability_warlock_improvedsoulleech"}, {"id":54151,"name":"Judgements of the Pure","icon":"ability_paladin_judgementofthepure"}, +{"id":54277,"name":"Backdraft","icon":"ability_warlock_backdraft","hasBuff":true}, {"id":54354,"name":"Prismatic Cloak","icon":"spell_arcane_prismaticcloak"}, {"id":54446,"name":"Rune of Swordbreaking","icon":"ability_parry"}, {"id":54447,"name":"Rune of Spellbreaking","icon":"spell_shadow_antimagicshell"}, @@ -10127,6 +10128,7 @@ ], "encounters":[ {"path":"Default/Raid Target","targets":[{"path":"Default/Raid Target","target":{"id":31146,"name":"Raid Target","level":88,"mobType":7,"stats":[0,0,0,0,0,0,0,0,0,0,0,650,0,0,0,0,0,0,11977,0,0,0,0,0,0,120016403,0,0,0,0,0,0,0],"minBaseDamage":210000,"damageSpread":0.4,"swingSpeed":2.5}}]}, +{"path":"Default/Movement","targets":[{"path":"Default/Movement","target":{"id":31147,"name":"Movement","level":88,"mobType":7,"stats":[0,0,0,0,0,0,0,0,0,0,0,650,0,0,0,0,0,0,11977,0,0,0,0,0,0,120016403,0,0,0,0,0,0,0],"minBaseDamage":210000,"damageSpread":0.4,"swingSpeed":2.5,"targetInputs":[{"inputType":1,"label":"Movement Interval","tooltip":"How often the player will move in seconds","numberValue":10},{"inputType":1,"label":"Reaction Time","tooltip":"How long the player can wait for casts to finish before moving in seconds","numberValue":1.5},{"inputType":1,"label":"Yards","tooltip":"How many yards the player moves","numberValue":5}]}}]}, {"path":"Blackwing Descent/Magmaw 10","targets":[{"path":"Blackwing Descent/Magmaw 10","target":{"id":41570,"name":"Magmaw 10","level":88,"mobType":1,"stats":[0,0,0,0,0,0,0,0,0,0,0,650,0,0,0,0,0,0,11977,0,0,0,0,0,0,26798304,0,0,0,0,0,0,0],"minBaseDamage":110000,"damageSpread":0.4,"swingSpeed":2.5,"targetInputs":[{"inputType":1,"label":"Impale Reaction Time","tooltip":"How long will the Raid take to Impale Head in Seconds. (After the initial 10s)","numberValue":5}]}}]}, {"path":"Blackwing Descent/Magmaw 25","targets":[{"path":"Blackwing Descent/Magmaw 25","target":{"id":41571,"name":"Magmaw 25","level":88,"mobType":1,"stats":[0,0,0,0,0,0,0,0,0,0,0,650,0,0,0,0,0,0,11977,0,0,0,0,0,0,81082048,0,0,0,0,0,0,0],"minBaseDamage":150000,"damageSpread":0.4,"swingSpeed":2.5,"targetInputs":[{"inputType":1,"label":"Impale Reaction Time","tooltip":"How long will the Raid take to Impale Head in Seconds. (After the initial 10s)","numberValue":5}]}}]}, {"path":"Blackwing Descent/Magmaw 10 H","targets":[{"path":"Blackwing Descent/Magmaw 10 H","target":{"id":41572,"name":"Magmaw 10 H","level":88,"mobType":1,"stats":[0,0,0,0,0,0,0,0,0,0,0,650,0,0,0,0,0,0,11977,0,0,0,0,0,0,39200000,0,0,0,0,0,0,0],"minBaseDamage":150000,"damageSpread":0.4,"swingSpeed":2.5,"targetInputs":[{"inputType":1,"label":"Impale Reaction Time","tooltip":"How long will the Raid take to Impale Head in Seconds. (After the initial 10s)","numberValue":5}]}}]}, diff --git a/sim/encounters/default_ai.go b/sim/encounters/default_ai.go index 255f718053..72c3c33b47 100644 --- a/sim/encounters/default_ai.go +++ b/sim/encounters/default_ai.go @@ -66,4 +66,5 @@ func (ai *DefaultAI) ExecuteCustomRotation(sim *core.Simulation) { ability.Spell.Cast(sim, ai.Target.CurrentTarget) } } + } diff --git a/sim/encounters/movement_ai.go b/sim/encounters/movement_ai.go new file mode 100644 index 0000000000..f973af7863 --- /dev/null +++ b/sim/encounters/movement_ai.go @@ -0,0 +1,133 @@ +package encounters + +import ( + "time" + + "github.com/wowsims/cata/sim/core" + "github.com/wowsims/cata/sim/core/proto" + "github.com/wowsims/cata/sim/core/stats" +) + +func addMovementAI() { + core.AddPresetTarget(&core.PresetTarget{ + PathPrefix: "Default", + Config: &proto.Target{ + Id: 31147, + Name: "Movement", + Level: 88, + MobType: proto.MobType_MobTypeMechanical, + TankIndex: 0, + + Stats: stats.Stats{ + stats.Health: 120_016_403, + stats.Armor: 11977, + stats.AttackPower: 650, + }.ToFloatArray(), + + SpellSchool: proto.SpellSchool_SpellSchoolPhysical, + SwingSpeed: 2.5, + MinBaseDamage: 210000, + DamageSpread: 0.4, + SuppressDodge: false, + ParryHaste: false, + DualWield: false, + DualWieldPenalty: false, + TargetInputs: []*proto.TargetInput{ + { + Label: "Movement Interval", + Tooltip: "How often the player will move in seconds", + InputType: proto.InputType_Number, + NumberValue: 10.0, + }, + { + Label: "Reaction Time", + Tooltip: "How long the player can wait for casts to finish before moving in seconds", + InputType: proto.InputType_Number, + NumberValue: 1.5, + }, + { + Label: "Yards", + Tooltip: "How many yards the player moves", + InputType: proto.InputType_Number, + NumberValue: 5, + }, + }, + }, + AI: NewMovementAI(), + }) + core.AddPresetEncounter("Movement", []string{ + "Default/Movement", + }) +} + +type MovementAI struct { + Target *core.Target + LastMoveTime time.Duration + MoveInterval time.Duration // How often moves happen + ReactionTime time.Duration // Time available to react before area should be cleared + MoveYards float64 // Duration of the move +} + +func NewMovementAI() core.AIFactory { + return func() core.TargetAI { + return &MovementAI{} + } +} + +func (ai *MovementAI) Initialize(target *core.Target, config *proto.Target) { + ai.Target = target + + if len(config.TargetInputs) > 0 { + ai.MoveInterval = core.DurationFromSeconds(config.TargetInputs[0].NumberValue) + } else { + ai.MoveInterval = core.DurationFromSeconds(10) + } + + if len(config.TargetInputs) > 1 { + ai.ReactionTime = core.DurationFromSeconds(config.TargetInputs[1].NumberValue) + } else { + ai.ReactionTime = core.DurationFromSeconds(1.5) + } + + if len(config.TargetInputs) > 2 { + ai.MoveYards = config.TargetInputs[2].NumberValue + } else { + ai.MoveYards = 5.0 + } +} + +func (ai *MovementAI) Reset(sim *core.Simulation) { + ai.LastMoveTime = 0 +} + +func (ai *MovementAI) ExecuteCustomRotation(sim *core.Simulation) { + players := sim.Raid.AllPlayerUnits + + for i := 0; i < len(players); i++ { + player := players[i] + duration := ai.TimeToMove(ai.MoveYards, player) + if player.Hardcast.Expires > sim.CurrentTime && !player.Hardcast.CanMove { + castEndsAt := player.Hardcast.Expires - sim.CurrentTime + // if castEndsAt < ai.ReactionTime { + core.StartDelayedAction(sim, core.DelayedActionOptions{ + DoAt: sim.CurrentTime + castEndsAt, + Priority: core.ActionPriorityPrePull + 1, + OnAction: func(s *core.Simulation) { + player.MoveDuration(duration, sim) + }, + }) + // } else { + // // Cancel casted spell and move immediately + // // For now we do nothing in this scenario + // return + // } + } else { + player.MoveDuration(duration, sim) + } + } + + ai.Target.WaitUntil(sim, sim.CurrentTime+ai.MoveInterval) +} +func (ai *MovementAI) TimeToMove(distance float64, unit *core.Unit) time.Duration { + return core.DurationFromSeconds(distance / unit.GetMovementSpeed()) +} diff --git a/sim/encounters/register_all.go b/sim/encounters/register_all.go index 64656a43f4..de336bc9c3 100644 --- a/sim/encounters/register_all.go +++ b/sim/encounters/register_all.go @@ -9,7 +9,9 @@ import ( func init() { AddDefaultPresetEncounter() + addMovementAI() bwd.Register() + } func AddSingleTargetBossEncounter(presetTarget *core.PresetTarget) {