From 7fc096dffd36e17c1d562ababbf5ee51335455df Mon Sep 17 00:00:00 2001 From: TDL Date: Sat, 10 Aug 2024 22:24:19 +0200 Subject: [PATCH 01/12] Update paladin_leveling.go Changed initial skillbindings to match the level requirement. Only initial skillbind thats needed is tome of town portal --- internal/character/paladin_leveling.go | 561 +++++++++++++------------ 1 file changed, 297 insertions(+), 264 deletions(-) diff --git a/internal/character/paladin_leveling.go b/internal/character/paladin_leveling.go index 38eabb5b..f675d8b0 100644 --- a/internal/character/paladin_leveling.go +++ b/internal/character/paladin_leveling.go @@ -25,127 +25,6 @@ func (p PaladinLeveling) CheckKeyBindings(d game.Data) []skill.ID { return []skill.ID{} } -func (p PaladinLeveling) BuffSkills(d game.Data) []skill.ID { - if _, found := d.KeyBindings.KeyBindingForSkill(skill.HolyShield); found { - return []skill.ID{skill.HolyShield} - } - return []skill.ID{} -} - -func (p PaladinLeveling) PreCTABuffSkills(_ game.Data) []skill.ID { - return []skill.ID{} -} - -func (p PaladinLeveling) killMonster(npc npc.ID, t data.MonsterType) action.Action { - return p.KillMonsterSequence(func(d game.Data) (data.UnitID, bool) { - m, found := d.Monsters.FindOne(npc, t) - if !found { - return 0, false - } - - return m.UnitID, true - }, nil) -} - -func (p PaladinLeveling) KillCountess() action.Action { - return p.killMonster(npc.DarkStalker, data.MonsterTypeSuperUnique) -} - -func (p PaladinLeveling) KillAndariel() action.Action { - return p.killMonster(npc.Andariel, data.MonsterTypeNone) -} - -func (p PaladinLeveling) KillSummoner() action.Action { - return p.killMonster(npc.Summoner, data.MonsterTypeNone) -} - -func (p PaladinLeveling) KillDuriel() action.Action { - return p.killMonster(npc.Duriel, data.MonsterTypeNone) -} - -func (p PaladinLeveling) KillMephisto() action.Action { - return p.killMonster(npc.Mephisto, data.MonsterTypeNone) -} - -func (p PaladinLeveling) KillPindle(_ []stat.Resist) action.Action { - return p.killMonster(npc.DefiledWarrior, data.MonsterTypeSuperUnique) -} - -func (p PaladinLeveling) KillNihlathak() action.Action { - return p.killMonster(npc.Nihlathak, data.MonsterTypeSuperUnique) -} - -func (p PaladinLeveling) KillCouncil() action.Action { - return p.KillMonsterSequence(func(d game.Data) (data.UnitID, bool) { - var councilMembers []data.Monster - for _, m := range d.Monsters { - if m.Name == npc.CouncilMember || m.Name == npc.CouncilMember2 || m.Name == npc.CouncilMember3 { - councilMembers = append(councilMembers, m) - } - } - - // Order council members by distance - sort.Slice(councilMembers, func(i, j int) bool { - distanceI := pather.DistanceFromMe(d, councilMembers[i].Position) - distanceJ := pather.DistanceFromMe(d, councilMembers[j].Position) - - return distanceI < distanceJ - }) - - if len(councilMembers) > 0 { - return councilMembers[0].UnitID, true - } - - return 0, false - }, nil) -} - -func (p PaladinLeveling) KillDiablo() action.Action { - timeout := time.Second * 20 - startTime := time.Time{} - diabloFound := false - return action.NewChain(func(d game.Data) []action.Action { - if startTime.IsZero() { - startTime = time.Now() - } - - if time.Since(startTime) > timeout && !diabloFound { - p.logger.Error("Diablo was not found, timeout reached") - return nil - } - - diablo, found := d.Monsters.FindOne(npc.Diablo, data.MonsterTypeNone) - if !found || diablo.Stats[stat.Life] <= 0 { - // Already dead - if diabloFound { - return nil - } - - // Keep waiting... - return []action.Action{action.NewStepChain(func(d game.Data) []step.Step { - return []step.Step{step.Wait(time.Millisecond * 100)} - })} - } - - diabloFound = true - p.logger.Info("Diablo detected, attacking") - - return []action.Action{ - p.killMonster(npc.Diablo, data.MonsterTypeNone), - p.killMonster(npc.Diablo, data.MonsterTypeNone), - p.killMonster(npc.Diablo, data.MonsterTypeNone), - } - }, action.RepeatUntilNoSteps()) -} - -func (p PaladinLeveling) KillIzual() action.Action { - return p.killMonster(npc.Izual, data.MonsterTypeNone) -} - -func (p PaladinLeveling) KillBaal() action.Action { - return p.killMonster(npc.BaalCrab, data.MonsterTypeNone) -} - func (p PaladinLeveling) KillMonsterSequence(monsterSelector func(d game.Data) (data.UnitID, bool), skipOnImmunities []stat.Resist, opts ...step.AttackOption) action.Action { completedAttackLoops := 0 var previousUnitID data.UnitID = 0 @@ -153,17 +32,21 @@ func (p PaladinLeveling) KillMonsterSequence(monsterSelector func(d game.Data) ( return action.NewStepChain(func(d game.Data) []step.Step { id, found := monsterSelector(d) if !found { + p.logger.Debug("No monster found to attack") return []step.Step{} } if previousUnitID != id { + p.logger.Info("New monster targeted", "id", id) completedAttackLoops = 0 } if !p.preBattleChecks(d, id, skipOnImmunities) { + p.logger.Debug("Pre-battle checks failed") return []step.Step{} } if completedAttackLoops >= 10 { + p.logger.Info("Max attack loops reached", "loops", completedAttackLoops) return []step.Step{} } @@ -172,6 +55,7 @@ func (p PaladinLeveling) KillMonsterSequence(monsterSelector func(d game.Data) ( numOfAttacks := 5 if d.PlayerUnit.Skills[skill.BlessedHammer].Level > 0 { + p.logger.Debug("Using Blessed Hammer") // Add a random movement, maybe hammer is not hitting the target if previousUnitID == id { steps = append(steps, @@ -186,9 +70,11 @@ func (p PaladinLeveling) KillMonsterSequence(monsterSelector func(d game.Data) ( ) } else { if d.PlayerUnit.Skills[skill.Zeal].Level > 0 { + p.logger.Debug("Using Zeal") numOfAttacks = 1 } + p.logger.Debug("Using primary attack with Holy Fire aura") steps = append(steps, step.PrimaryAttack(id, numOfAttacks, false, step.Distance(1, 3), step.EnsureAura(skill.HolyFire)), ) @@ -196,51 +82,114 @@ func (p PaladinLeveling) KillMonsterSequence(monsterSelector func(d game.Data) ( completedAttackLoops++ previousUnitID = id + p.logger.Debug("Attack sequence completed", "steps", len(steps), "loops", completedAttackLoops) return steps }, action.RepeatUntilNoSteps()) } -func (p PaladinLeveling) StatPoints(d game.Data) map[stat.ID]int { +func (p PaladinLeveling) killMonster(npc npc.ID, t data.MonsterType) action.Action { + p.logger.Info("Killing monster", "npc", npc, "type", t) + return p.KillMonsterSequence(func(d game.Data) (data.UnitID, bool) { + m, found := d.Monsters.FindOne(npc, t) + if !found { + return 0, false + } + + return m.UnitID, true + }, nil) +} + +func (p PaladinLeveling) BuffSkills(d game.Data) []skill.ID { + skillsList := make([]skill.ID, 0) + if _, found := d.KeyBindings.KeyBindingForSkill(skill.HolyShield); found { + skillsList = append(skillsList, skill.HolyShield) + } + p.logger.Info("Buff skills", "skills", skillsList) + return skillsList +} + +func (p PaladinLeveling) PreCTABuffSkills(_ game.Data) []skill.ID { + return []skill.ID{} +} + +func (p PaladinLeveling) ShouldResetSkills(d game.Data) bool { lvl, _ := d.PlayerUnit.FindStat(stat.Level, 0) + if lvl.Value >= 21 && d.PlayerUnit.Skills[skill.HolyFire].Level > 10 { + p.logger.Info("Resetting skills: Level 21+ and Holy Fire level > 10") + return true + } - if lvl.Value >= 21 && lvl.Value < 30 { - return map[stat.ID]int{ - stat.Strength: 35, - stat.Vitality: 200, - stat.Energy: 0, - } + return false +} + +func (p PaladinLeveling) SkillsToBind(d game.Data) (skill.ID, []skill.ID) { + lvl, _ := d.PlayerUnit.FindStat(stat.Level, 0) + mainSkill := skill.AttackSkill + skillBindings := []skill.ID{} + + if lvl.Value >= 6 { + skillBindings = append(skillBindings, skill.Vigor) } - if lvl.Value >= 30 && lvl.Value < 45 { - return map[stat.ID]int{ - stat.Strength: 50, - stat.Dexterity: 40, - stat.Vitality: 220, - stat.Energy: 0, - } + if lvl.Value >= 24 { + skillBindings = append(skillBindings, skill.HolyShield) } - if lvl.Value >= 45 { - return map[stat.ID]int{ - stat.Strength: 86, - stat.Dexterity: 50, - stat.Vitality: 300, - stat.Energy: 0, + if d.PlayerUnit.Skills[skill.BlessedHammer].Level > 0 && lvl.Value >= 18 { + mainSkill = skill.BlessedHammer + } else if d.PlayerUnit.Skills[skill.Zeal].Level > 0 { + mainSkill = skill.Zeal + } + + if d.PlayerUnit.Skills[skill.Concentration].Level > 0 && lvl.Value >= 18 { + skillBindings = append(skillBindings, skill.Concentration) + } else { + if _, found := d.PlayerUnit.Skills[skill.HolyFire]; found { + skillBindings = append(skillBindings, skill.HolyFire) + } else if _, found := d.PlayerUnit.Skills[skill.Might]; found { + skillBindings = append(skillBindings, skill.Might) } } - return map[stat.ID]int{ - stat.Strength: 0, - stat.Dexterity: 25, - stat.Vitality: 150, - stat.Energy: 0, + p.logger.Info("Skills bound", "mainSkill", mainSkill, "skillBindings", skillBindings) + return mainSkill, skillBindings +} + +func (p PaladinLeveling) StatPoints(d game.Data) map[stat.ID]int { + lvl, _ := d.PlayerUnit.FindStat(stat.Level, 0) + statPoints := make(map[stat.ID]int) + + if lvl.Value < 21 { + statPoints[stat.Strength] = 0 + statPoints[stat.Dexterity] = 25 + statPoints[stat.Vitality] = 150 + statPoints[stat.Energy] = 0 + } else if lvl.Value < 30 { + statPoints[stat.Strength] = 35 + statPoints[stat.Vitality] = 200 + statPoints[stat.Energy] = 0 + } else if lvl.Value < 45 { + statPoints[stat.Strength] = 50 + statPoints[stat.Dexterity] = 40 + statPoints[stat.Vitality] = 220 + statPoints[stat.Energy] = 0 + } else { + statPoints[stat.Strength] = 86 + statPoints[stat.Dexterity] = 50 + statPoints[stat.Vitality] = 300 + statPoints[stat.Energy] = 0 } + + p.logger.Info("Assigning stat points", "level", lvl.Value, "statPoints", statPoints) + return statPoints } func (p PaladinLeveling) SkillPoints(d game.Data) []skill.ID { lvl, _ := d.PlayerUnit.FindStat(stat.Level, 0) + var skillPoints []skill.ID + if lvl.Value < 21 { - return []skill.ID{ + skillPoints = []skill.ID{ skill.Might, skill.Sacrifice, skill.ResistFire, @@ -264,143 +213,227 @@ func (p PaladinLeveling) SkillPoints(d game.Data) []skill.ID { skill.HolyFire, skill.HolyFire, } + } else { + // Hammerdin + skillPoints = []skill.ID{ + skill.HolyBolt, + skill.BlessedHammer, + skill.Prayer, + skill.Defiance, + skill.Cleansing, + skill.Vigor, + skill.Might, + skill.BlessedAim, + skill.Concentration, + skill.BlessedAim, + skill.BlessedAim, + skill.BlessedAim, + skill.BlessedAim, + skill.BlessedAim, + skill.BlessedAim, + // Level 19 + skill.BlessedHammer, + skill.Concentration, + skill.Vigor, + // Level 20 + skill.BlessedHammer, + skill.Vigor, + skill.BlessedHammer, + skill.BlessedHammer, + skill.BlessedHammer, + skill.Vigor, + skill.BlessedHammer, + skill.BlessedHammer, + skill.BlessedHammer, + skill.BlessedHammer, + skill.BlessedHammer, + skill.BlessedHammer, + skill.BlessedHammer, + skill.BlessedHammer, + skill.Smite, + skill.BlessedHammer, + skill.BlessedHammer, + skill.BlessedHammer, + skill.BlessedHammer, + skill.BlessedHammer, + skill.Charge, + skill.BlessedHammer, + skill.Vigor, + skill.Vigor, + skill.Vigor, + skill.HolyShield, + skill.Concentration, + skill.Vigor, + skill.Vigor, + skill.Vigor, + skill.Vigor, + skill.Vigor, + skill.Vigor, + skill.Vigor, + skill.Vigor, + skill.Vigor, + skill.Vigor, + skill.Vigor, + skill.Vigor, + skill.Vigor, + skill.Concentration, + skill.Concentration, + skill.Concentration, + skill.Concentration, + skill.Concentration, + skill.Concentration, + skill.Concentration, + skill.Concentration, + skill.Concentration, + skill.Concentration, + skill.Concentration, + skill.Concentration, + skill.Concentration, + skill.Concentration, + skill.Concentration, + skill.BlessedAim, + skill.BlessedAim, + skill.BlessedAim, + skill.BlessedAim, + skill.BlessedAim, + skill.BlessedAim, + skill.BlessedAim, + skill.BlessedAim, + skill.BlessedAim, + skill.BlessedAim, + skill.BlessedAim, + skill.BlessedAim, + } } - // Hammerdin - return []skill.ID{ - skill.HolyBolt, - skill.BlessedHammer, - skill.Prayer, - skill.Defiance, - skill.Cleansing, - skill.Vigor, - skill.Might, - skill.BlessedAim, - skill.Concentration, - skill.BlessedAim, - skill.BlessedAim, - skill.BlessedAim, - skill.BlessedAim, - skill.BlessedAim, - skill.BlessedAim, - // Level 19 - skill.BlessedHammer, - skill.Concentration, - skill.Vigor, - // Level 20 - skill.BlessedHammer, - skill.Vigor, - skill.BlessedHammer, - skill.BlessedHammer, - skill.BlessedHammer, - skill.Vigor, - skill.BlessedHammer, - skill.BlessedHammer, - skill.BlessedHammer, - skill.BlessedHammer, - skill.BlessedHammer, - skill.BlessedHammer, - skill.BlessedHammer, - skill.BlessedHammer, - skill.Smite, - skill.BlessedHammer, - skill.BlessedHammer, - skill.BlessedHammer, - skill.BlessedHammer, - skill.BlessedHammer, - skill.Charge, - skill.BlessedHammer, - skill.Vigor, - skill.Vigor, - skill.Vigor, - skill.HolyShield, - skill.Concentration, - skill.Vigor, - skill.Vigor, - skill.Vigor, - skill.Vigor, - skill.Vigor, - skill.Vigor, - skill.Vigor, - skill.Vigor, - skill.Vigor, - skill.Vigor, - skill.Vigor, - skill.Vigor, - skill.Vigor, - skill.Concentration, - skill.Concentration, - skill.Concentration, - skill.Concentration, - skill.Concentration, - skill.Concentration, - skill.Concentration, - skill.Concentration, - skill.Concentration, - skill.Concentration, - skill.Concentration, - skill.Concentration, - skill.Concentration, - skill.Concentration, - skill.Concentration, - skill.BlessedAim, - skill.BlessedAim, - skill.BlessedAim, - skill.BlessedAim, - skill.BlessedAim, - skill.BlessedAim, - skill.BlessedAim, - skill.BlessedAim, - skill.BlessedAim, - skill.BlessedAim, - skill.BlessedAim, - skill.BlessedAim, - } + p.logger.Info("Assigning skill points", "level", lvl.Value, "skillPoints", skillPoints) + return skillPoints } -func (p PaladinLeveling) SkillsToBind(d game.Data) (skill.ID, []skill.ID) { - mainSkill := skill.AttackSkill - skillBindings := []skill.ID{ - skill.Vigor, - skill.HolyShield, - } +func (p PaladinLeveling) KillCountess() action.Action { + p.logger.Info("Starting Countess kill sequence") + return p.killMonster(npc.DarkStalker, data.MonsterTypeSuperUnique) +} - lvl, _ := d.PlayerUnit.FindStat(stat.Level, 0) - if d.PlayerUnit.Skills[skill.BlessedHammer].Level > 0 && lvl.Value >= 18 { - mainSkill = skill.BlessedHammer - } else if d.PlayerUnit.Skills[skill.Zeal].Level > 0 { - mainSkill = skill.Zeal - } +func (p PaladinLeveling) KillAndariel() action.Action { + p.logger.Info("Starting Andariel kill sequence") + return p.killMonster(npc.Andariel, data.MonsterTypeNone) +} - if d.PlayerUnit.Skills[skill.Concentration].Level > 0 && lvl.Value >= 18 { - skillBindings = append(skillBindings, skill.Concentration) - } else { - if _, found := d.PlayerUnit.Skills[skill.HolyFire]; found { - skillBindings = append(skillBindings, skill.HolyFire) - } else if _, found := d.PlayerUnit.Skills[skill.Might]; found { - skillBindings = append(skillBindings, skill.Might) +func (p PaladinLeveling) KillSummoner() action.Action { + p.logger.Info("Starting Summoner kill sequence") + return p.killMonster(npc.Summoner, data.MonsterTypeNone) +} + +func (p PaladinLeveling) KillDuriel() action.Action { + p.logger.Info("Starting Duriel kill sequence") + return p.killMonster(npc.Duriel, data.MonsterTypeNone) +} + +func (p PaladinLeveling) KillCouncil() action.Action { + p.logger.Info("Starting Council kill sequence") + return p.KillMonsterSequence(func(d game.Data) (data.UnitID, bool) { + var councilMembers []data.Monster + for _, m := range d.Monsters { + if m.Name == npc.CouncilMember || m.Name == npc.CouncilMember2 || m.Name == npc.CouncilMember3 { + councilMembers = append(councilMembers, m) + } } - } - return mainSkill, skillBindings + // Order council members by distance + sort.Slice(councilMembers, func(i, j int) bool { + distanceI := pather.DistanceFromMe(d, councilMembers[i].Position) + distanceJ := pather.DistanceFromMe(d, councilMembers[j].Position) + + return distanceI < distanceJ + }) + + if len(councilMembers) > 0 { + p.logger.Debug("Targeting Council member", "id", councilMembers[0].UnitID) + return councilMembers[0].UnitID, true + } + + p.logger.Debug("No Council members found") + return 0, false + }, nil) } -func (p PaladinLeveling) ShouldResetSkills(d game.Data) bool { - lvl, _ := d.PlayerUnit.FindStat(stat.Level, 0) - if lvl.Value >= 21 && d.PlayerUnit.Skills[skill.HolyFire].Level > 10 { - return true - } +func (p PaladinLeveling) KillMephisto() action.Action { + p.logger.Info("Starting Mephisto kill sequence") + return p.killMonster(npc.Mephisto, data.MonsterTypeNone) +} - return false +func (p PaladinLeveling) KillIzual() action.Action { + p.logger.Info("Starting Izual kill sequence") + return p.killMonster(npc.Izual, data.MonsterTypeNone) +} + +func (p PaladinLeveling) KillDiablo() action.Action { + timeout := time.Second * 20 + startTime := time.Time{} + diabloFound := false + return action.NewChain(func(d game.Data) []action.Action { + if startTime.IsZero() { + startTime = time.Now() + p.logger.Info("Starting Diablo kill sequence") + } + + if time.Since(startTime) > timeout && !diabloFound { + p.logger.Error("Diablo was not found, timeout reached") + return nil + } + + diablo, found := d.Monsters.FindOne(npc.Diablo, data.MonsterTypeNone) + if !found || diablo.Stats[stat.Life] <= 0 { + if diabloFound { + p.logger.Info("Diablo killed or not found") + return nil + } + + return []action.Action{action.NewStepChain(func(d game.Data) []step.Step { + return []step.Step{step.Wait(time.Millisecond * 100)} + })} + } + + diabloFound = true + p.logger.Info("Diablo detected, attacking") + + return []action.Action{ + p.killMonster(npc.Diablo, data.MonsterTypeNone), + p.killMonster(npc.Diablo, data.MonsterTypeNone), + p.killMonster(npc.Diablo, data.MonsterTypeNone), + } + }, action.RepeatUntilNoSteps()) +} + +func (p PaladinLeveling) KillPindle(_ []stat.Resist) action.Action { + p.logger.Info("Starting Pindleskin kill sequence") + return p.killMonster(npc.DefiledWarrior, data.MonsterTypeSuperUnique) +} + +func (p PaladinLeveling) KillNihlathak() action.Action { + p.logger.Info("Starting Nihlathak kill sequence") + return p.killMonster(npc.Nihlathak, data.MonsterTypeSuperUnique) } func (p PaladinLeveling) KillAncients() action.Action { + p.logger.Info("Starting Ancients kill sequence") return action.NewChain(func(d game.Data) (actions []action.Action) { for _, m := range d.Monsters.Enemies(data.MonsterEliteFilter()) { actions = append(actions, + action.NewStepChain(func(d game.Data) []step.Step { + m, _ := d.Monsters.FindOne(m.Name, data.MonsterTypeSuperUnique) + p.logger.Info("Targeting Ancient", "name", m.Name) + return []step.Step{} + }), p.killMonster(m.Name, data.MonsterTypeSuperUnique), ) } return actions }) } + +func (p PaladinLeveling) KillBaal() action.Action { + p.logger.Info("Starting Baal kill sequence") + return p.killMonster(npc.BaalCrab, data.MonsterTypeNone) +} From e03bc31c6a93275ea697ed80f16bf4ea6956d90b Mon Sep 17 00:00:00 2001 From: TDL Date: Sat, 10 Aug 2024 22:24:47 +0200 Subject: [PATCH 02/12] Update sorceress_leveling.go Changed initial skillbindings to match the level requirement. Only initial skillbind thats needed is tome of town portal --- internal/character/sorceress_leveling.go | 543 ++++++++++++----------- 1 file changed, 272 insertions(+), 271 deletions(-) diff --git a/internal/character/sorceress_leveling.go b/internal/character/sorceress_leveling.go index 5776a6f8..df54f14c 100644 --- a/internal/character/sorceress_leveling.go +++ b/internal/character/sorceress_leveling.go @@ -26,80 +26,212 @@ func (s SorceressLeveling) CheckKeyBindings(d game.Data) []skill.ID { return []skill.ID{} } +func (s SorceressLeveling) KillMonsterSequence(monsterSelector func(d game.Data) (data.UnitID, bool), skipOnImmunities []stat.Resist, opts ...step.AttackOption) action.Action { + completedAttackLoops := 0 + previousUnitID := 0 + + return action.NewStepChain(func(d game.Data) []step.Step { + id, found := monsterSelector(d) + if !found { + s.logger.Debug("No monster found to attack") + return []step.Step{} + } + if previousUnitID != int(id) { + s.logger.Info("New monster targeted", "id", id) + completedAttackLoops = 0 + } + + if !s.preBattleChecks(d, id, skipOnImmunities) { + s.logger.Debug("Pre-battle checks failed") + return []step.Step{} + } + + if len(opts) == 0 { + opts = append(opts, step.Distance(1, 30)) + } + + if completedAttackLoops >= sorceressMaxAttacksLoop { + s.logger.Info("Max attack loops reached", "loops", sorceressMaxAttacksLoop) + return []step.Step{} + } + + steps := make([]step.Step, 0) + + lvl, _ := d.PlayerUnit.FindStat(stat.Level, 0) + if d.PlayerUnit.MPPercent() < 15 && lvl.Value < 15 { + s.logger.Debug("Low mana, using primary attack") + steps = append(steps, step.PrimaryAttack(id, 1, false, step.Distance(1, 3))) + } else { + if _, found := d.KeyBindings.KeyBindingForSkill(skill.Blizzard); found { + s.logger.Debug("Using Blizzard") + steps = append(steps, step.SecondaryAttack(skill.Blizzard, id, 1, step.Distance(25, 30))) + } else if _, found := d.KeyBindings.KeyBindingForSkill(skill.Meteor); found { + s.logger.Debug("Using Meteor") + steps = append(steps, step.SecondaryAttack(skill.Meteor, id, 1, step.Distance(25, 30))) + } else if _, found := d.KeyBindings.KeyBindingForSkill(skill.FireBall); found { + s.logger.Debug("Using FireBall") + steps = append(steps, step.SecondaryAttack(skill.FireBall, id, 4, step.Distance(25, 30))) + } else if _, found := d.KeyBindings.KeyBindingForSkill(skill.IceBolt); found { + s.logger.Debug("Using IceBolt") + steps = append(steps, step.SecondaryAttack(skill.IceBolt, id, 4, step.Distance(25, 30))) + } else { + s.logger.Debug("No secondary skills available, using primary attack") + steps = append(steps, step.PrimaryAttack(id, 1, false, step.Distance(1, 3))) + } + } + + completedAttackLoops++ + previousUnitID = int(id) + + s.logger.Debug("Attack sequence completed", "steps", len(steps), "loops", completedAttackLoops) + return steps + }, action.RepeatUntilNoSteps()) +} + +func (s SorceressLeveling) killMonster(npc npc.ID, t data.MonsterType) action.Action { + s.logger.Info("Killing monster", "npc", npc, "type", t) + return s.KillMonsterSequence(func(d game.Data) (data.UnitID, bool) { + m, found := d.Monsters.FindOne(npc, t) + if !found { + return 0, false + } + + return m.UnitID, true + }, nil) +} + +func (s SorceressLeveling) BuffSkills(d game.Data) []skill.ID { + skillsList := make([]skill.ID, 0) + if _, found := d.KeyBindings.KeyBindingForSkill(skill.FrozenArmor); found { + skillsList = append(skillsList, skill.FrozenArmor) + } + + if _, found := d.KeyBindings.KeyBindingForSkill(skill.EnergyShield); found { + skillsList = append(skillsList, skill.EnergyShield) + } + + s.logger.Info("Buff skills", "skills", skillsList) + return skillsList +} + +func (s SorceressLeveling) PreCTABuffSkills(_ game.Data) []skill.ID { + return []skill.ID{} +} + +func (s SorceressLeveling) staticFieldCasts() int { + casts := 6 + switch s.container.CharacterCfg.Game.Difficulty { + case difficulty.Normal: + casts = 8 + } + s.logger.Debug("Static Field casts", "count", casts) + return casts +} + func (s SorceressLeveling) ShouldResetSkills(d game.Data) bool { lvl, _ := d.PlayerUnit.FindStat(stat.Level, 0) - if lvl.Value >= 25 && d.PlayerUnit.Skills[skill.FireBall].Level > 10 { + if lvl.Value >= 24 && d.PlayerUnit.Skills[skill.FireBall].Level > 1 { + s.logger.Info("Resetting skills: Level 24+ and FireBall level > 1") return true } - return false } func (s SorceressLeveling) SkillsToBind(d game.Data) (skill.ID, []skill.ID) { + level, _ := d.PlayerUnit.FindStat(stat.Level, 0) skillBindings := []skill.ID{ - skill.FrozenArmor, - skill.StaticField, - skill.Teleport, skill.TomeOfTownPortal, } + if level.Value >= 4 { + skillBindings = append(skillBindings, skill.FrozenArmor) + } + if level.Value >= 6 { + skillBindings = append(skillBindings, skill.StaticField) + } + if level.Value >= 18 { + skillBindings = append(skillBindings, skill.Teleport) + } + if d.PlayerUnit.Skills[skill.Blizzard].Level > 0 { skillBindings = append(skillBindings, skill.Blizzard) - return skill.GlacialSpike, skillBindings + } else if d.PlayerUnit.Skills[skill.Meteor].Level > 0 { + skillBindings = append(skillBindings, skill.Meteor) } else if d.PlayerUnit.Skills[skill.FireBall].Level > 0 { skillBindings = append(skillBindings, skill.FireBall) - } else if d.PlayerUnit.Skills[skill.FireBolt].Level > 0 { - skillBindings = append(skillBindings, skill.FireBolt) + } else if d.PlayerUnit.Skills[skill.IceBolt].Level > 0 { + skillBindings = append(skillBindings, skill.IceBolt) } - return skill.AttackSkill, skillBindings + mainSkill := skill.AttackSkill + if d.PlayerUnit.Skills[skill.Blizzard].Level > 0 { + mainSkill = skill.Blizzard + } else if d.PlayerUnit.Skills[skill.Meteor].Level > 0 { + mainSkill = skill.Meteor + } + + s.logger.Info("Skills bound", "mainSkill", mainSkill, "skillBindings", skillBindings) + return mainSkill, skillBindings } func (s SorceressLeveling) StatPoints(d game.Data) map[stat.ID]int { lvl, _ := d.PlayerUnit.FindStat(stat.Level, 0) - if lvl.Value < 9 { - return map[stat.ID]int{ - stat.Vitality: 9999, - } + statPoints := make(map[stat.ID]int) + + if lvl.Value < 20 { + statPoints[stat.Vitality] = 9999 + } else { + statPoints[stat.Energy] = 80 + statPoints[stat.Strength] = 60 + statPoints[stat.Vitality] = 9999 } - if lvl.Value < 15 { - return map[stat.ID]int{ - stat.Energy: 45, - stat.Strength: 25, - stat.Vitality: 9999, - } - } - - return map[stat.ID]int{ - stat.Energy: 60, - stat.Strength: 50, - stat.Vitality: 9999, - } + s.logger.Info("Assigning stat points", "level", lvl.Value, "statPoints", statPoints) + return statPoints } func (s SorceressLeveling) SkillPoints(d game.Data) []skill.ID { lvl, _ := d.PlayerUnit.FindStat(stat.Level, 0) - if lvl.Value < 25 { - return []skill.ID{ + var skillPoints []skill.ID + + if lvl.Value < 24 { + skillPoints = []skill.ID{ + skill.FireBolt, + skill.FireBolt, skill.FireBolt, skill.FrozenArmor, skill.FireBolt, + skill.StaticField, skill.FireBolt, skill.Warmth, - skill.StaticField, + skill.FireBolt, + skill.Telekinesis, skill.FireBolt, skill.FireBolt, skill.FireBolt, skill.FireBolt, - skill.Telekinesis, + skill.IceBolt, + skill.IceBolt, + skill.IceBolt, + skill.Teleport, + skill.IceBolt, + skill.IceBolt, + skill.IceBolt, + skill.IceBolt, + skill.IceBolt, + } + } else { + skillPoints = []skill.ID{ + skill.FireBolt, + skill.Warmth, + skill.Inferno, + skill.Blaze, skill.FireBall, skill.FireBall, skill.FireBall, skill.FireBall, skill.FireBall, skill.FireBall, - skill.Teleport, skill.FireBall, skill.FireBall, skill.FireBall, @@ -114,84 +246,61 @@ func (s SorceressLeveling) SkillPoints(d game.Data) []skill.ID { skill.FireBall, skill.FireBall, skill.FireBall, + skill.Meteor, + skill.FireMastery, + skill.Meteor, + skill.Meteor, + skill.Meteor, + skill.Meteor, + skill.Meteor, + skill.Meteor, + skill.Meteor, + skill.Meteor, + skill.Meteor, + skill.Meteor, + skill.Meteor, + skill.Meteor, + skill.Meteor, + skill.Meteor, + skill.Meteor, + skill.Meteor, + skill.Meteor, + skill.Meteor, + skill.Meteor, + skill.FireMastery, + skill.FireMastery, + skill.FireMastery, + skill.FireMastery, + skill.FireMastery, + skill.FireMastery, + skill.FireMastery, + skill.FireMastery, + skill.FireMastery, + skill.FireMastery, + skill.FireMastery, + skill.FireMastery, + skill.FireMastery, + skill.FireMastery, skill.FireMastery, } } - return []skill.ID{ - skill.StaticField, - skill.StaticField, - skill.StaticField, - skill.StaticField, - skill.Telekinesis, - skill.Teleport, - skill.FrozenArmor, - skill.IceBolt, - skill.IceBlast, - skill.FrostNova, - skill.GlacialSpike, - skill.Blizzard, - skill.Blizzard, - skill.Warmth, - skill.GlacialSpike, - skill.GlacialSpike, - skill.GlacialSpike, - skill.GlacialSpike, - skill.GlacialSpike, - skill.GlacialSpike, - skill.GlacialSpike, - skill.GlacialSpike, - skill.IceBlast, - skill.IceBlast, - skill.IceBlast, - skill.IceBlast, - skill.IceBlast, - skill.Blizzard, - skill.Blizzard, - skill.Blizzard, - skill.Blizzard, - skill.ColdMastery, - skill.Blizzard, - skill.Blizzard, - skill.Blizzard, - skill.Blizzard, - skill.Blizzard, - skill.Blizzard, - skill.Blizzard, - skill.Blizzard, - skill.Blizzard, - skill.Blizzard, - skill.Blizzard, - skill.Blizzard, - skill.Blizzard, - skill.Blizzard, - skill.ColdMastery, - skill.ColdMastery, - skill.ColdMastery, - skill.ColdMastery, - skill.GlacialSpike, - skill.GlacialSpike, - skill.GlacialSpike, - skill.GlacialSpike, - skill.GlacialSpike, - skill.GlacialSpike, - skill.GlacialSpike, - skill.GlacialSpike, - skill.GlacialSpike, - skill.GlacialSpike, - skill.GlacialSpike, - } + s.logger.Info("Assigning skill points", "level", lvl.Value, "skillPoints", skillPoints) + return skillPoints } func (s SorceressLeveling) KillCountess() action.Action { + s.logger.Info("Starting Countess kill sequence") return s.killMonster(npc.DarkStalker, data.MonsterTypeSuperUnique) } func (s SorceressLeveling) KillAndariel() action.Action { + s.logger.Info("Starting Andariel kill sequence") return action.NewChain(func(d game.Data) []action.Action { return []action.Action{ action.NewStepChain(func(d game.Data) []step.Step { m, _ := d.Monsters.FindOne(npc.Andariel, data.MonsterTypeNone) + s.logger.Info("Casting Static Field on Andariel") return []step.Step{ step.SecondaryAttack(skill.StaticField, m.UnitID, s.staticFieldCasts(), step.Distance(3, 5)), } @@ -202,14 +311,17 @@ func (s SorceressLeveling) KillAndariel() action.Action { } func (s SorceressLeveling) KillSummoner() action.Action { + s.logger.Info("Starting Summoner kill sequence") return s.killMonster(npc.Summoner, data.MonsterTypeNone) } func (s SorceressLeveling) KillDuriel() action.Action { + s.logger.Info("Starting Duriel kill sequence") return action.NewChain(func(d game.Data) []action.Action { return []action.Action{ action.NewStepChain(func(d game.Data) []step.Step { m, _ := d.Monsters.FindOne(npc.Duriel, data.MonsterTypeNone) + s.logger.Info("Casting Static Field on Duriel") return []step.Step{ step.SecondaryAttack(skill.StaticField, m.UnitID, s.staticFieldCasts(), step.Distance(1, 5)), } @@ -219,48 +331,8 @@ func (s SorceressLeveling) KillDuriel() action.Action { }) } -func (s SorceressLeveling) KillMephisto() action.Action { - return action.NewChain(func(d game.Data) []action.Action { - // Let's try to moat trick if Teleport is available - //if step.CanTeleport(d) { - // moatTrickPosition := data.Position{X: 17611, Y: 8093} - // return []action.Action{ - // action.NewStepChain(func(d game.Data) []step.Step { - // mephisto, _ := d.Monsters.FindOne(npc.Mephisto, data.MonsterTypeNone) - // return []step.Step{ - // step.Wait(time.Second * 2), - // step.MoveTo(data.Position{X: 17580, Y: 8085}), - // step.Wait(time.Second * 3), - // step.MoveTo(moatTrickPosition), - // step.Wait(time.Second * 3), - // step.SecondaryAttack(s.container.CharacterCfg.Bindings.Sorceress.Blizzard, mephisto.UnitID, 3), - // } - // }), - // } - //} - - // If teleport is not available, just try to kill him with Static Field and Fire Ball - return []action.Action{ - action.NewStepChain(func(d game.Data) []step.Step { - mephisto, _ := d.Monsters.FindOne(npc.Mephisto, data.MonsterTypeNone) - return []step.Step{ - step.SecondaryAttack(skill.StaticField, mephisto.UnitID, s.staticFieldCasts(), step.Distance(1, 5)), - } - }), - s.killMonster(npc.Mephisto, data.MonsterTypeNone), - } - }) -} - -func (s SorceressLeveling) KillPindle(skipOnImmunities []stat.Resist) action.Action { - return s.killMonster(npc.DefiledWarrior, data.MonsterTypeSuperUnique) -} - -func (s SorceressLeveling) KillNihlathak() action.Action { - return s.killMonster(npc.Nihlathak, data.MonsterTypeSuperUnique) -} - func (s SorceressLeveling) KillCouncil() action.Action { + s.logger.Info("Starting Council kill sequence") return s.KillMonsterSequence(func(d game.Data) (data.UnitID, bool) { var councilMembers []data.Monster for _, m := range d.Monsters { @@ -269,7 +341,6 @@ func (s SorceressLeveling) KillCouncil() action.Action { } } - // Order council members by distance sort.Slice(councilMembers, func(i, j int) bool { distanceI := pather.DistanceFromMe(d, councilMembers[i].Position) distanceJ := pather.DistanceFromMe(d, councilMembers[j].Position) @@ -278,13 +349,53 @@ func (s SorceressLeveling) KillCouncil() action.Action { }) if len(councilMembers) > 0 { + s.logger.Debug("Targeting Council member", "id", councilMembers[0].UnitID) return councilMembers[0].UnitID, true } + s.logger.Debug("No Council members found") return 0, false }, nil) } +func (s SorceressLeveling) KillMephisto() action.Action { + return action.NewChain(func(d game.Data) []action.Action { + s.logger.Info("Starting Mephisto kill sequence") + return []action.Action{ + action.NewStepChain(func(d game.Data) []step.Step { + mephisto, _ := d.Monsters.FindOne(npc.Mephisto, data.MonsterTypeNone) + s.logger.Info("Casting Static Field on Mephisto") + return []step.Step{ + step.SecondaryAttack(skill.StaticField, mephisto.UnitID, s.staticFieldCasts(), step.Distance(1, 5)), + } + }), + s.killMonster(npc.Mephisto, data.MonsterTypeNone), + } + }) +} + +func (s SorceressLeveling) KillIzual() action.Action { + s.logger.Info("Starting Izual kill sequence") + return action.NewChain(func(d game.Data) []action.Action { + return []action.Action{ + action.NewStepChain(func(d game.Data) []step.Step { + monster, _ := d.Monsters.FindOne(npc.Izual, data.MonsterTypeNone) + s.logger.Info("Casting Static Field on Izual") + return []step.Step{ + step.SecondaryAttack(skill.StaticField, monster.UnitID, s.staticFieldCasts(), step.Distance(1, 4)), + } + }), + s.killMonster(npc.Izual, data.MonsterTypeNone), + s.killMonster(npc.Izual, data.MonsterTypeNone), + s.killMonster(npc.Izual, data.MonsterTypeNone), + s.killMonster(npc.Izual, data.MonsterTypeNone), + s.killMonster(npc.Izual, data.MonsterTypeNone), + s.killMonster(npc.Izual, data.MonsterTypeNone), + s.killMonster(npc.Izual, data.MonsterTypeNone), + } + }) +} + func (s SorceressLeveling) KillDiablo() action.Action { timeout := time.Second * 20 startTime := time.Time{} @@ -292,6 +403,7 @@ func (s SorceressLeveling) KillDiablo() action.Action { return action.NewChain(func(d game.Data) []action.Action { if startTime.IsZero() { startTime = time.Now() + s.logger.Info("Starting Diablo kill sequence") } if time.Since(startTime) > timeout && !diabloFound { @@ -301,12 +413,11 @@ func (s SorceressLeveling) KillDiablo() action.Action { diablo, found := d.Monsters.FindOne(npc.Diablo, data.MonsterTypeNone) if !found || diablo.Stats[stat.Life] <= 0 { - // Already dead if diabloFound { + s.logger.Info("Diablo killed or not found") return nil } - // Keep waiting... return []action.Action{action.NewStepChain(func(d game.Data) []step.Step { return []step.Step{step.Wait(time.Millisecond * 100)} })} @@ -326,51 +437,24 @@ func (s SorceressLeveling) KillDiablo() action.Action { }, action.RepeatUntilNoSteps()) } -func (s SorceressLeveling) KillIzual() action.Action { - return action.NewChain(func(d game.Data) []action.Action { - return []action.Action{ - action.NewStepChain(func(d game.Data) []step.Step { - monster, _ := d.Monsters.FindOne(npc.Izual, data.MonsterTypeNone) - return []step.Step{ - step.SecondaryAttack(skill.StaticField, monster.UnitID, s.staticFieldCasts(), step.Distance(1, 4)), - } - }), - // We will need a lot of cycles to kill him probably - s.killMonster(npc.Izual, data.MonsterTypeNone), - s.killMonster(npc.Izual, data.MonsterTypeNone), - s.killMonster(npc.Izual, data.MonsterTypeNone), - s.killMonster(npc.Izual, data.MonsterTypeNone), - s.killMonster(npc.Izual, data.MonsterTypeNone), - s.killMonster(npc.Izual, data.MonsterTypeNone), - s.killMonster(npc.Izual, data.MonsterTypeNone), - } - }) +func (s SorceressLeveling) KillPindle(skipOnImmunities []stat.Resist) action.Action { + s.logger.Info("Starting Pindleskin kill sequence") + return s.killMonster(npc.DefiledWarrior, data.MonsterTypeSuperUnique) } -func (s SorceressLeveling) KillBaal() action.Action { - return action.NewChain(func(d game.Data) []action.Action { - return []action.Action{ - action.NewStepChain(func(d game.Data) []step.Step { - baal, _ := d.Monsters.FindOne(npc.BaalCrab, data.MonsterTypeNone) - return []step.Step{ - step.SecondaryAttack(skill.StaticField, baal.UnitID, s.staticFieldCasts(), step.Distance(1, 4)), - } - }), - // We will need a lot of cycles to kill him probably - s.killMonster(npc.BaalCrab, data.MonsterTypeNone), - s.killMonster(npc.BaalCrab, data.MonsterTypeNone), - s.killMonster(npc.BaalCrab, data.MonsterTypeNone), - s.killMonster(npc.BaalCrab, data.MonsterTypeNone), - } - }) +func (s SorceressLeveling) KillNihlathak() action.Action { + s.logger.Info("Starting Nihlathak kill sequence") + return s.killMonster(npc.Nihlathak, data.MonsterTypeSuperUnique) } func (s SorceressLeveling) KillAncients() action.Action { + s.logger.Info("Starting Ancients kill sequence") return action.NewChain(func(d game.Data) (actions []action.Action) { for _, m := range d.Monsters.Enemies(data.MonsterEliteFilter()) { actions = append(actions, action.NewStepChain(func(d game.Data) []step.Step { m, _ := d.Monsters.FindOne(m.Name, data.MonsterTypeSuperUnique) + s.logger.Info("Targeting Ancient", "name", m.Name) return []step.Step{ step.SecondaryAttack(skill.StaticField, m.UnitID, s.staticFieldCasts(), step.Distance(8, 10)), step.MoveTo(data.Position{ @@ -386,104 +470,21 @@ func (s SorceressLeveling) KillAncients() action.Action { }) } -func (s SorceressLeveling) KillMonsterSequence(monsterSelector func(d game.Data) (data.UnitID, bool), skipOnImmunities []stat.Resist, opts ...step.AttackOption) action.Action { - completedAttackLoops := 0 - previousUnitID := 0 - - return action.NewStepChain(func(d game.Data) []step.Step { - id, found := monsterSelector(d) - if !found { - return []step.Step{} - } - if previousUnitID != int(id) { - completedAttackLoops = 0 - } - - if !s.preBattleChecks(d, id, skipOnImmunities) { - return []step.Step{} - } - - if len(opts) == 0 { - opts = append(opts, step.Distance(1, 30)) - } - - if completedAttackLoops >= sorceressMaxAttacksLoop { - return []step.Step{} - } - - steps := make([]step.Step, 0) - - // During early game stages amount of mana is ridiculous... - lvl, _ := d.PlayerUnit.FindStat(stat.Level, 0) - if d.PlayerUnit.MPPercent() < 15 && lvl.Value < 15 { - steps = append(steps, step.PrimaryAttack(id, 1, false, step.Distance(1, 3))) - } else { - if _, found := d.KeyBindings.KeyBindingForSkill(skill.Blizzard); found { - if completedAttackLoops%2 == 0 { - for _, m := range d.Monsters.Enemies() { - if d := pather.DistanceFromMe(d, m.Position); d < 4 { - s.logger.Debug("Monster detected close to the player, casting Blizzard over it") - steps = append(steps, step.SecondaryAttack(skill.Blizzard, m.UnitID, 1, step.Distance(25, 30))) - break - } - } +func (s SorceressLeveling) KillBaal() action.Action { + s.logger.Info("Starting Baal kill sequence") + return action.NewChain(func(d game.Data) []action.Action { + return []action.Action{ + action.NewStepChain(func(d game.Data) []step.Step { + baal, _ := d.Monsters.FindOne(npc.BaalCrab, data.MonsterTypeNone) + s.logger.Info("Casting Static Field on Baal") + return []step.Step{ + step.SecondaryAttack(skill.StaticField, baal.UnitID, s.staticFieldCasts(), step.Distance(1, 4)), } - - steps = append(steps, - step.SecondaryAttack(skill.Blizzard, id, 1, step.Distance(25, 30)), - step.PrimaryAttack(id, 3, false, step.Distance(25, 30)), - ) - } else if _, found := d.KeyBindings.KeyBindingForSkill(skill.FireBall); found { - steps = append(steps, step.SecondaryAttack(skill.FireBall, id, 4, step.Distance(1, 25))) - } else if _, found := d.KeyBindings.KeyBindingForSkill(skill.FireBolt); found { - steps = append(steps, step.SecondaryAttack(skill.FireBolt, id, 4, step.Distance(1, 25))) - } - } - - completedAttackLoops++ - previousUnitID = int(id) - - return steps - }, action.RepeatUntilNoSteps()) -} - -func (s SorceressLeveling) killMonster(npc npc.ID, t data.MonsterType) action.Action { - return s.KillMonsterSequence(func(d game.Data) (data.UnitID, bool) { - m, found := d.Monsters.FindOne(npc, t) - if !found { - return 0, false - } - - return m.UnitID, true - }, nil) -} - -func (s SorceressLeveling) BuffSkills(d game.Data) []skill.ID { - skillsList := make([]skill.ID, 0) - if _, found := d.KeyBindings.KeyBindingForSkill(skill.EnergyShield); found { - skillsList = append(skillsList, skill.EnergyShield) - } - - armors := []skill.ID{skill.ChillingArmor, skill.ShiverArmor, skill.FrozenArmor} - for _, armor := range armors { - if _, found := d.KeyBindings.KeyBindingForSkill(armor); found { - skillsList = append(skillsList, armor) - return skillsList + }), + s.killMonster(npc.BaalCrab, data.MonsterTypeNone), + s.killMonster(npc.BaalCrab, data.MonsterTypeNone), + s.killMonster(npc.BaalCrab, data.MonsterTypeNone), + s.killMonster(npc.BaalCrab, data.MonsterTypeNone), } - } - - return skillsList -} - -func (s SorceressLeveling) PreCTABuffSkills(_ game.Data) []skill.ID { - return []skill.ID{} -} - -func (s SorceressLeveling) staticFieldCasts() int { - switch s.container.CharacterCfg.Game.Difficulty { - case difficulty.Normal: - return 8 - } - - return 6 + }) } From 2901a525cd06c7bc4238a9c17424e13e9fac9e74 Mon Sep 17 00:00:00 2001 From: TDL Date: Sat, 10 Aug 2024 22:25:03 +0200 Subject: [PATCH 03/12] Update sorceress_leveling_lightning.go Changed initial skillbindings to match the level requirement. Only initial skillbind thats needed is tome of town portal --- .../character/sorceress_leveling_lightning.go | 556 +++++++++--------- 1 file changed, 290 insertions(+), 266 deletions(-) diff --git a/internal/character/sorceress_leveling_lightning.go b/internal/character/sorceress_leveling_lightning.go index d861f2c5..8b3bade8 100644 --- a/internal/character/sorceress_leveling_lightning.go +++ b/internal/character/sorceress_leveling_lightning.go @@ -26,9 +26,132 @@ func (s SorceressLevelingLightning) CheckKeyBindings(d game.Data) []skill.ID { return []skill.ID{} } +func (s SorceressLevelingLightning) KillMonsterSequence(monsterSelector func(d game.Data) (data.UnitID, bool), skipOnImmunities []stat.Resist, opts ...step.AttackOption) action.Action { + completedAttackLoops := 0 + previousUnitID := 0 + + return action.NewStepChain(func(d game.Data) []step.Step { + id, found := monsterSelector(d) + if !found { + s.logger.Debug("No monster found to attack") + return []step.Step{} + } + if previousUnitID != int(id) { + s.logger.Info("New monster targeted", "id", id) + completedAttackLoops = 0 + } + + if !s.preBattleChecks(d, id, skipOnImmunities) { + s.logger.Debug("Pre-battle checks failed") + return []step.Step{} + } + + if len(opts) == 0 { + opts = append(opts, step.Distance(1, 30)) + } + + if completedAttackLoops >= sorceressMaxAttacksLoop { + s.logger.Info("Max attack loops reached", "loops", sorceressMaxAttacksLoop) + return []step.Step{} + } + + steps := make([]step.Step, 0) + + lvl, _ := d.PlayerUnit.FindStat(stat.Level, 0) + if d.PlayerUnit.MPPercent() < 15 && lvl.Value < 15 { + s.logger.Debug("Low mana, using primary attack") + steps = append(steps, step.PrimaryAttack(id, 1, false, step.Distance(1, 3))) + } else { + if _, found := d.KeyBindings.KeyBindingForSkill(skill.Blizzard); found { + if completedAttackLoops%2 == 0 { + for _, m := range d.Monsters.Enemies() { + if d := pather.DistanceFromMe(d, m.Position); d < 4 { + s.logger.Debug("Monster close, casting Blizzard") + steps = append(steps, step.SecondaryAttack(skill.Blizzard, m.UnitID, 1, step.Distance(25, 30))) + break + } + } + } + + s.logger.Debug("Using Blizzard") + steps = append(steps, + step.SecondaryAttack(skill.Blizzard, id, 1, step.Distance(25, 30)), + step.PrimaryAttack(id, 3, false, step.Distance(25, 30)), + ) + } else if _, found := d.KeyBindings.KeyBindingForSkill(skill.Nova); found { + s.logger.Debug("Using Nova") + steps = append(steps, step.SecondaryAttack(skill.Nova, id, 4, step.Distance(1, 5))) + } else if _, found := d.KeyBindings.KeyBindingForSkill(skill.ChargedBolt); found { + s.logger.Debug("Using ChargedBolt") + steps = append(steps, step.SecondaryAttack(skill.ChargedBolt, id, 4, step.Distance(1, 5))) + } else if _, found := d.KeyBindings.KeyBindingForSkill(skill.FireBolt); found { + s.logger.Debug("Using FireBolt") + steps = append(steps, step.SecondaryAttack(skill.FireBolt, id, 4, step.Distance(1, 5))) + } else { + s.logger.Debug("No secondary skills available, using primary attack") + steps = append(steps, step.PrimaryAttack(id, 1, false, step.Distance(1, 3))) + } + } + + completedAttackLoops++ + previousUnitID = int(id) + + s.logger.Debug("Attack sequence completed", "steps", len(steps), "loops", completedAttackLoops) + return steps + }, action.RepeatUntilNoSteps()) +} + +func (s SorceressLevelingLightning) killMonster(npc npc.ID, t data.MonsterType) action.Action { + s.logger.Info("Killing monster", "npc", npc, "type", t) + return s.KillMonsterSequence(func(d game.Data) (data.UnitID, bool) { + m, found := d.Monsters.FindOne(npc, t) + if !found { + return 0, false + } + + return m.UnitID, true + }, nil) +} + +func (s SorceressLevelingLightning) BuffSkills(d game.Data) []skill.ID { + skillsList := make([]skill.ID, 0) + if _, found := d.KeyBindings.KeyBindingForSkill(skill.EnergyShield); found { + skillsList = append(skillsList, skill.EnergyShield) + } + + if _, found := d.KeyBindings.KeyBindingForSkill(skill.ThunderStorm); found { + skillsList = append(skillsList, skill.ThunderStorm) + } + + armors := []skill.ID{skill.ChillingArmor, skill.ShiverArmor, skill.FrozenArmor} + for _, armor := range armors { + if _, found := d.KeyBindings.KeyBindingForSkill(armor); found { + skillsList = append(skillsList, armor) + break + } + } + + return skillsList +} + +func (s SorceressLevelingLightning) PreCTABuffSkills(_ game.Data) []skill.ID { + return []skill.ID{} +} + +func (s SorceressLevelingLightning) staticFieldCasts() int { + casts := 6 + switch s.container.CharacterCfg.Game.Difficulty { + case difficulty.Normal: + casts = 8 + } + s.logger.Debug("Static Field casts", "count", casts) + return casts +} + func (s SorceressLevelingLightning) ShouldResetSkills(d game.Data) bool { lvl, _ := d.PlayerUnit.FindStat(stat.Level, 0) if lvl.Value >= 25 && d.PlayerUnit.Skills[skill.Nova].Level > 10 { + s.logger.Info("Resetting skills: Level 25+ and Nova level > 10") return true } @@ -36,13 +159,22 @@ func (s SorceressLevelingLightning) ShouldResetSkills(d game.Data) bool { } func (s SorceressLevelingLightning) SkillsToBind(d game.Data) (skill.ID, []skill.ID) { + level, _ := d.PlayerUnit.FindStat(stat.Level, 0) skillBindings := []skill.ID{ - skill.FrozenArmor, - skill.StaticField, - skill.Teleport, skill.TomeOfTownPortal, } + // Add skills only if the character level is high enough + if level.Value >= 4 { + skillBindings = append(skillBindings, skill.FrozenArmor) + } + if level.Value >= 6 { + skillBindings = append(skillBindings, skill.StaticField) + } + if level.Value >= 18 { + skillBindings = append(skillBindings, skill.Teleport) + } + if d.PlayerUnit.Skills[skill.Blizzard].Level > 0 { skillBindings = append(skillBindings, skill.Blizzard) } else if d.PlayerUnit.Skills[skill.Nova].Level > 1 { @@ -58,36 +190,36 @@ func (s SorceressLevelingLightning) SkillsToBind(d game.Data) (skill.ID, []skill mainSkill = skill.GlacialSpike } + s.logger.Info("Skills bound", "mainSkill", mainSkill, "skillBindings", skillBindings) return mainSkill, skillBindings } func (s SorceressLevelingLightning) StatPoints(d game.Data) map[stat.ID]int { lvl, _ := d.PlayerUnit.FindStat(stat.Level, 0) - if lvl.Value < 9 { - return map[stat.ID]int{ - stat.Vitality: 9999, - } - } + statPoints := make(map[stat.ID]int) - if lvl.Value < 15 { - return map[stat.ID]int{ - stat.Energy: 45, - stat.Strength: 25, - stat.Vitality: 9999, - } + if lvl.Value < 9 { + statPoints[stat.Vitality] = 9999 + } else if lvl.Value < 15 { + statPoints[stat.Energy] = 45 + statPoints[stat.Strength] = 25 + statPoints[stat.Vitality] = 9999 + } else { + statPoints[stat.Energy] = 60 + statPoints[stat.Strength] = 50 + statPoints[stat.Vitality] = 9999 } - return map[stat.ID]int{ - stat.Energy: 60, - stat.Strength: 50, - stat.Vitality: 9999, - } + s.logger.Info("Assigning stat points", "level", lvl.Value, "statPoints", statPoints) + return statPoints } func (s SorceressLevelingLightning) SkillPoints(d game.Data) []skill.ID { lvl, _ := d.PlayerUnit.FindStat(stat.Level, 0) + var skillPoints []skill.ID + if lvl.Value < 25 { - return []skill.ID{ + skillPoints = []skill.ID{ skill.ChargedBolt, skill.ChargedBolt, skill.ChargedBolt, @@ -121,82 +253,88 @@ func (s SorceressLevelingLightning) SkillPoints(d game.Data) []skill.ID { skill.Nova, skill.Nova, } + } else { + skillPoints = []skill.ID{ + skill.StaticField, + skill.StaticField, + skill.StaticField, + skill.StaticField, + skill.Telekinesis, + skill.Teleport, + skill.FrozenArmor, + skill.IceBolt, + skill.IceBlast, + skill.FrostNova, + skill.GlacialSpike, + skill.Blizzard, + skill.Blizzard, + skill.Warmth, + skill.GlacialSpike, + skill.GlacialSpike, + skill.GlacialSpike, + skill.GlacialSpike, + skill.GlacialSpike, + skill.GlacialSpike, + skill.GlacialSpike, + skill.GlacialSpike, + skill.IceBlast, + skill.IceBlast, + skill.IceBlast, + skill.IceBlast, + skill.IceBlast, + skill.Blizzard, + skill.Blizzard, + skill.Blizzard, + skill.Blizzard, + skill.ColdMastery, + skill.Blizzard, + skill.Blizzard, + skill.Blizzard, + skill.Blizzard, + skill.Blizzard, + skill.Blizzard, + skill.Blizzard, + skill.Blizzard, + skill.Blizzard, + skill.Blizzard, + skill.Blizzard, + skill.Blizzard, + skill.Blizzard, + skill.Blizzard, + skill.ColdMastery, + skill.ColdMastery, + skill.ColdMastery, + skill.ColdMastery, + skill.GlacialSpike, + skill.GlacialSpike, + skill.GlacialSpike, + skill.GlacialSpike, + skill.GlacialSpike, + skill.GlacialSpike, + skill.GlacialSpike, + skill.GlacialSpike, + skill.GlacialSpike, + skill.GlacialSpike, + skill.GlacialSpike, + } } - return []skill.ID{ - skill.StaticField, - skill.StaticField, - skill.StaticField, - skill.StaticField, - skill.Telekinesis, - skill.Teleport, - skill.FrozenArmor, - skill.IceBolt, - skill.IceBlast, - skill.FrostNova, - skill.GlacialSpike, - skill.Blizzard, - skill.Blizzard, - skill.Warmth, - skill.GlacialSpike, - skill.GlacialSpike, - skill.GlacialSpike, - skill.GlacialSpike, - skill.GlacialSpike, - skill.GlacialSpike, - skill.GlacialSpike, - skill.GlacialSpike, - skill.IceBlast, - skill.IceBlast, - skill.IceBlast, - skill.IceBlast, - skill.IceBlast, - skill.Blizzard, - skill.Blizzard, - skill.Blizzard, - skill.Blizzard, - skill.ColdMastery, - skill.Blizzard, - skill.Blizzard, - skill.Blizzard, - skill.Blizzard, - skill.Blizzard, - skill.Blizzard, - skill.Blizzard, - skill.Blizzard, - skill.Blizzard, - skill.Blizzard, - skill.Blizzard, - skill.Blizzard, - skill.Blizzard, - skill.Blizzard, - skill.ColdMastery, - skill.ColdMastery, - skill.ColdMastery, - skill.ColdMastery, - skill.GlacialSpike, - skill.GlacialSpike, - skill.GlacialSpike, - skill.GlacialSpike, - skill.GlacialSpike, - skill.GlacialSpike, - skill.GlacialSpike, - skill.GlacialSpike, - skill.GlacialSpike, - skill.GlacialSpike, - skill.GlacialSpike, - } + s.logger.Info("Assigning skill points", "level", lvl.Value, "skillPoints", skillPoints) + return skillPoints } func (s SorceressLevelingLightning) KillCountess() action.Action { + s.logger.Info("Starting Countess kill sequence") return s.killMonster(npc.DarkStalker, data.MonsterTypeSuperUnique) } func (s SorceressLevelingLightning) KillAndariel() action.Action { + s.logger.Info("Starting Andariel kill sequence") return action.NewChain(func(d game.Data) []action.Action { return []action.Action{ action.NewStepChain(func(d game.Data) []step.Step { m, _ := d.Monsters.FindOne(npc.Andariel, data.MonsterTypeNone) + s.logger.Info("Casting Static Field on Andariel") return []step.Step{ step.SecondaryAttack(skill.StaticField, m.UnitID, s.staticFieldCasts(), step.Distance(3, 5)), } @@ -207,14 +345,17 @@ func (s SorceressLevelingLightning) KillAndariel() action.Action { } func (s SorceressLevelingLightning) KillSummoner() action.Action { + s.logger.Info("Starting Summoner kill sequence") return s.killMonster(npc.Summoner, data.MonsterTypeNone) } func (s SorceressLevelingLightning) KillDuriel() action.Action { + s.logger.Info("Starting Duriel kill sequence") return action.NewChain(func(d game.Data) []action.Action { return []action.Action{ action.NewStepChain(func(d game.Data) []step.Step { m, _ := d.Monsters.FindOne(npc.Duriel, data.MonsterTypeNone) + s.logger.Info("Casting Static Field on Duriel") return []step.Step{ step.SecondaryAttack(skill.StaticField, m.UnitID, s.staticFieldCasts(), step.Distance(1, 5)), } @@ -224,48 +365,8 @@ func (s SorceressLevelingLightning) KillDuriel() action.Action { }) } -func (s SorceressLevelingLightning) KillMephisto() action.Action { - return action.NewChain(func(d game.Data) []action.Action { - // Let's try to moat trick if Teleport is available - //if step.CanTeleport(d) { - // moatTrickPosition := data.Position{X: 17611, Y: 8093} - // return []action.Action{ - // action.NewStepChain(func(d game.Data) []step.Step { - // mephisto, _ := d.Monsters.FindOne(npc.Mephisto, data.MonsterTypeNone) - // return []step.Step{ - // step.Wait(time.Second * 2), - // step.MoveTo(data.Position{X: 17580, Y: 8085}), - // step.Wait(time.Second * 3), - // step.MoveTo(moatTrickPosition), - // step.Wait(time.Second * 3), - // step.SecondaryAttack(s.container.CharacterCfg.Bindings.Sorceress.Blizzard, mephisto.UnitID, 3), - // } - // }), - // } - //} - - // If teleport is not available, just try to kill him with Static Field and Fire Ball - return []action.Action{ - action.NewStepChain(func(d game.Data) []step.Step { - mephisto, _ := d.Monsters.FindOne(npc.Mephisto, data.MonsterTypeNone) - return []step.Step{ - step.SecondaryAttack(skill.StaticField, mephisto.UnitID, s.staticFieldCasts(), step.Distance(1, 5)), - } - }), - s.killMonster(npc.Mephisto, data.MonsterTypeNone), - } - }) -} - -func (s SorceressLevelingLightning) KillPindle(skipOnImmunities []stat.Resist) action.Action { - return s.killMonster(npc.DefiledWarrior, data.MonsterTypeSuperUnique) -} - -func (s SorceressLevelingLightning) KillNihlathak() action.Action { - return s.killMonster(npc.Nihlathak, data.MonsterTypeSuperUnique) -} - func (s SorceressLevelingLightning) KillCouncil() action.Action { + s.logger.Info("Starting Council kill sequence") return s.KillMonsterSequence(func(d game.Data) (data.UnitID, bool) { var councilMembers []data.Monster for _, m := range d.Monsters { @@ -274,7 +375,6 @@ func (s SorceressLevelingLightning) KillCouncil() action.Action { } } - // Order council members by distance sort.Slice(councilMembers, func(i, j int) bool { distanceI := pather.DistanceFromMe(d, councilMembers[i].Position) distanceJ := pather.DistanceFromMe(d, councilMembers[j].Position) @@ -283,13 +383,53 @@ func (s SorceressLevelingLightning) KillCouncil() action.Action { }) if len(councilMembers) > 0 { + s.logger.Debug("Targeting Council member", "id", councilMembers[0].UnitID) return councilMembers[0].UnitID, true } + s.logger.Debug("No Council members found") return 0, false }, nil) } +func (s SorceressLevelingLightning) KillMephisto() action.Action { + return action.NewChain(func(d game.Data) []action.Action { + s.logger.Info("Starting Mephisto kill sequence") + return []action.Action{ + action.NewStepChain(func(d game.Data) []step.Step { + mephisto, _ := d.Monsters.FindOne(npc.Mephisto, data.MonsterTypeNone) + s.logger.Info("Casting Static Field on Mephisto") + return []step.Step{ + step.SecondaryAttack(skill.StaticField, mephisto.UnitID, s.staticFieldCasts(), step.Distance(1, 5)), + } + }), + s.killMonster(npc.Mephisto, data.MonsterTypeNone), + } + }) +} + +func (s SorceressLevelingLightning) KillIzual() action.Action { + s.logger.Info("Starting Izual kill sequence") + return action.NewChain(func(d game.Data) []action.Action { + return []action.Action{ + action.NewStepChain(func(d game.Data) []step.Step { + monster, _ := d.Monsters.FindOne(npc.Izual, data.MonsterTypeNone) + s.logger.Info("Casting Static Field on Izual") + return []step.Step{ + step.SecondaryAttack(skill.StaticField, monster.UnitID, s.staticFieldCasts(), step.Distance(1, 4)), + } + }), + s.killMonster(npc.Izual, data.MonsterTypeNone), + s.killMonster(npc.Izual, data.MonsterTypeNone), + s.killMonster(npc.Izual, data.MonsterTypeNone), + s.killMonster(npc.Izual, data.MonsterTypeNone), + s.killMonster(npc.Izual, data.MonsterTypeNone), + s.killMonster(npc.Izual, data.MonsterTypeNone), + s.killMonster(npc.Izual, data.MonsterTypeNone), + } + }) +} + func (s SorceressLevelingLightning) KillDiablo() action.Action { timeout := time.Second * 20 startTime := time.Time{} @@ -297,6 +437,7 @@ func (s SorceressLevelingLightning) KillDiablo() action.Action { return action.NewChain(func(d game.Data) []action.Action { if startTime.IsZero() { startTime = time.Now() + s.logger.Info("Starting Diablo kill sequence") } if time.Since(startTime) > timeout && !diabloFound { @@ -306,12 +447,11 @@ func (s SorceressLevelingLightning) KillDiablo() action.Action { diablo, found := d.Monsters.FindOne(npc.Diablo, data.MonsterTypeNone) if !found || diablo.Stats[stat.Life] <= 0 { - // Already dead if diabloFound { + s.logger.Info("Diablo killed or not found") return nil } - // Keep waiting... return []action.Action{action.NewStepChain(func(d game.Data) []step.Step { return []step.Step{step.Wait(time.Millisecond * 100)} })} @@ -331,51 +471,24 @@ func (s SorceressLevelingLightning) KillDiablo() action.Action { }, action.RepeatUntilNoSteps()) } -func (s SorceressLevelingLightning) KillIzual() action.Action { - return action.NewChain(func(d game.Data) []action.Action { - return []action.Action{ - action.NewStepChain(func(d game.Data) []step.Step { - monster, _ := d.Monsters.FindOne(npc.Izual, data.MonsterTypeNone) - return []step.Step{ - step.SecondaryAttack(skill.StaticField, monster.UnitID, s.staticFieldCasts(), step.Distance(1, 4)), - } - }), - // We will need a lot of cycles to kill him probably - s.killMonster(npc.Izual, data.MonsterTypeNone), - s.killMonster(npc.Izual, data.MonsterTypeNone), - s.killMonster(npc.Izual, data.MonsterTypeNone), - s.killMonster(npc.Izual, data.MonsterTypeNone), - s.killMonster(npc.Izual, data.MonsterTypeNone), - s.killMonster(npc.Izual, data.MonsterTypeNone), - s.killMonster(npc.Izual, data.MonsterTypeNone), - } - }) +func (s SorceressLevelingLightning) KillPindle(skipOnImmunities []stat.Resist) action.Action { + s.logger.Info("Starting Pindleskin kill sequence") + return s.killMonster(npc.DefiledWarrior, data.MonsterTypeSuperUnique) } -func (s SorceressLevelingLightning) KillBaal() action.Action { - return action.NewChain(func(d game.Data) []action.Action { - return []action.Action{ - action.NewStepChain(func(d game.Data) []step.Step { - baal, _ := d.Monsters.FindOne(npc.BaalCrab, data.MonsterTypeNone) - return []step.Step{ - step.SecondaryAttack(skill.StaticField, baal.UnitID, s.staticFieldCasts(), step.Distance(1, 4)), - } - }), - // We will need a lot of cycles to kill him probably - s.killMonster(npc.BaalCrab, data.MonsterTypeNone), - s.killMonster(npc.BaalCrab, data.MonsterTypeNone), - s.killMonster(npc.BaalCrab, data.MonsterTypeNone), - s.killMonster(npc.BaalCrab, data.MonsterTypeNone), - } - }) +func (s SorceressLevelingLightning) KillNihlathak() action.Action { + s.logger.Info("Starting Nihlathak kill sequence") + return s.killMonster(npc.Nihlathak, data.MonsterTypeSuperUnique) } func (s SorceressLevelingLightning) KillAncients() action.Action { + s.logger.Info("Starting Ancients kill sequence") return action.NewChain(func(d game.Data) (actions []action.Action) { for _, m := range d.Monsters.Enemies(data.MonsterEliteFilter()) { actions = append(actions, action.NewStepChain(func(d game.Data) []step.Step { m, _ := d.Monsters.FindOne(m.Name, data.MonsterTypeSuperUnique) + s.logger.Info("Targeting Ancient", "name", m.Name) return []step.Step{ step.SecondaryAttack(skill.StaticField, m.UnitID, s.staticFieldCasts(), step.Distance(8, 10)), step.MoveTo(data.Position{ @@ -391,110 +504,21 @@ func (s SorceressLevelingLightning) KillAncients() action.Action { }) } -func (s SorceressLevelingLightning) KillMonsterSequence(monsterSelector func(d game.Data) (data.UnitID, bool), skipOnImmunities []stat.Resist, opts ...step.AttackOption) action.Action { - completedAttackLoops := 0 - previousUnitID := 0 - - return action.NewStepChain(func(d game.Data) []step.Step { - id, found := monsterSelector(d) - if !found { - return []step.Step{} - } - if previousUnitID != int(id) { - completedAttackLoops = 0 - } - - if !s.preBattleChecks(d, id, skipOnImmunities) { - return []step.Step{} - } - - if len(opts) == 0 { - opts = append(opts, step.Distance(1, 30)) - } - - if completedAttackLoops >= sorceressMaxAttacksLoop { - return []step.Step{} - } - - steps := make([]step.Step, 0) - - // During early game stages amount of mana is ridiculous... - lvl, _ := d.PlayerUnit.FindStat(stat.Level, 0) - if d.PlayerUnit.MPPercent() < 15 && lvl.Value < 15 { - steps = append(steps, step.PrimaryAttack(id, 1, false, step.Distance(1, 3))) - } else { - if _, found := d.KeyBindings.KeyBindingForSkill(skill.Blizzard); found { - if completedAttackLoops%2 == 0 { - for _, m := range d.Monsters.Enemies() { - if d := pather.DistanceFromMe(d, m.Position); d < 4 { - s.logger.Debug("Monster detected close to the player, casting Blizzard over it") - steps = append(steps, step.SecondaryAttack(skill.Blizzard, m.UnitID, 1, step.Distance(25, 30))) - break - } - } +func (s SorceressLevelingLightning) KillBaal() action.Action { + s.logger.Info("Starting Baal kill sequence") + return action.NewChain(func(d game.Data) []action.Action { + return []action.Action{ + action.NewStepChain(func(d game.Data) []step.Step { + baal, _ := d.Monsters.FindOne(npc.BaalCrab, data.MonsterTypeNone) + s.logger.Info("Casting Static Field on Baal") + return []step.Step{ + step.SecondaryAttack(skill.StaticField, baal.UnitID, s.staticFieldCasts(), step.Distance(1, 4)), } - - steps = append(steps, - step.SecondaryAttack(skill.Blizzard, id, 1, step.Distance(25, 30)), - step.PrimaryAttack(id, 3, false, step.Distance(25, 30)), - ) - } else if _, found := d.KeyBindings.KeyBindingForSkill(skill.Nova); found { - steps = append(steps, step.SecondaryAttack(skill.Nova, id, 4, step.Distance(1, 5))) - } else if _, found := d.KeyBindings.KeyBindingForSkill(skill.ChargedBolt); found { - steps = append(steps, step.SecondaryAttack(skill.ChargedBolt, id, 4, step.Distance(1, 5))) - } else if _, found := d.KeyBindings.KeyBindingForSkill(skill.FireBolt); found { - steps = append(steps, step.SecondaryAttack(skill.FireBolt, id, 4, step.Distance(1, 5))) - } - } - - completedAttackLoops++ - previousUnitID = int(id) - - return steps - }, action.RepeatUntilNoSteps()) -} - -func (s SorceressLevelingLightning) killMonster(npc npc.ID, t data.MonsterType) action.Action { - return s.KillMonsterSequence(func(d game.Data) (data.UnitID, bool) { - m, found := d.Monsters.FindOne(npc, t) - if !found { - return 0, false - } - - return m.UnitID, true - }, nil) -} - -func (s SorceressLevelingLightning) BuffSkills(d game.Data) []skill.ID { - skillsList := make([]skill.ID, 0) - if _, found := d.KeyBindings.KeyBindingForSkill(skill.EnergyShield); found { - skillsList = append(skillsList, skill.EnergyShield) - } - - if _, found := d.KeyBindings.KeyBindingForSkill(skill.ThunderStorm); found { - skillsList = append(skillsList, skill.ThunderStorm) - } - - armors := []skill.ID{skill.ChillingArmor, skill.ShiverArmor, skill.FrozenArmor} - for _, armor := range armors { - if _, found := d.KeyBindings.KeyBindingForSkill(armor); found { - skillsList = append(skillsList, armor) - return skillsList + }), + s.killMonster(npc.BaalCrab, data.MonsterTypeNone), + s.killMonster(npc.BaalCrab, data.MonsterTypeNone), + s.killMonster(npc.BaalCrab, data.MonsterTypeNone), + s.killMonster(npc.BaalCrab, data.MonsterTypeNone), } - } - - return skillsList -} - -func (s SorceressLevelingLightning) PreCTABuffSkills(_ game.Data) []skill.ID { - return []skill.ID{} -} - -func (s SorceressLevelingLightning) staticFieldCasts() int { - switch s.container.CharacterCfg.Game.Difficulty { - case difficulty.Normal: - return 8 - } - - return 6 + }) } From a71e3ea58a76df5a4d299588486a40a7aac0e1ef Mon Sep 17 00:00:00 2001 From: TDL Date: Sat, 10 Aug 2024 22:29:29 +0200 Subject: [PATCH 04/12] Update leveling_act1.go Changed normal run to: - Clear Blood Moor until level 3/4 - Do Den of Evil until level 6/7 - Do Tristram Runs until level 14/15 - Do Countess Runs until level 17/18 Also changed how the Cain stones are selected. Credits to wheresmycode on which I added some things to make leveling smoother. (https://github.com/wheresmycode/koolo/tree/master) --- internal/run/leveling_act1.go | 67 ++++++++++++++++------------------- 1 file changed, 30 insertions(+), 37 deletions(-) diff --git a/internal/run/leveling_act1.go b/internal/run/leveling_act1.go index b3fb9707..6e24f925 100644 --- a/internal/run/leveling_act1.go +++ b/internal/run/leveling_act1.go @@ -26,22 +26,44 @@ func (a Leveling) act1() action.Action { } running = true - if !d.Quests[quest.Act1DenOfEvil].Completed() { - return a.denOfEvil() + + // clear Bloodmoor until level 3 + if lvl, _ := d.PlayerUnit.FindStat(stat.Level, 0); lvl.Value <= 3 { + return a.bloodMoor() } - if lvl, _ := d.PlayerUnit.FindStat(stat.Level, 0); lvl.Value < 13 { - return a.countess() + // do Den of Evil until level 6 + if lvl, _ := d.PlayerUnit.FindStat(stat.Level, 0); lvl.Value <= 6 || !d.Quests[quest.Act1DenOfEvil].Completed() { + return a.denOfEvil() } if !a.isCainInTown(d) && !d.Quests[quest.Act1TheSearchForCain].Completed() { return a.deckardCain(d) } + // do Tristram Runs until level 14 + if lvl, _ := d.PlayerUnit.FindStat(stat.Level, 0); lvl.Value <= 14 { + return a.tristram() + } + + // do Countess Runs until level 17 + if lvl, _ := d.PlayerUnit.FindStat(stat.Level, 0); lvl.Value <= 17 { + return a.countess() + } + return a.andariel(d) }) } +func (a Leveling) bloodMoor() []action.Action { + a.logger.Info("Starting Blood Moor run") + return []action.Action{ + a.builder.MoveToArea(area.BloodMoor), + a.builder.Buff(), + a.builder.ClearArea(false, data.MonsterAnyFilter()), + } +} + func (a Leveling) denOfEvil() []action.Action { a.logger.Info("Starting Den of Evil run") return []action.Action{ @@ -53,38 +75,10 @@ func (a Leveling) denOfEvil() []action.Action { } } -//func (a Leveling) bloodRaven() action.Action { -// return action.NewChain(func(d game.Data) []action.Action { -// a.logger.Info("Starting Blood Raven quest") -// return []action.Action{ -// a.builder.WayPoint(area.ColdPlains), -// a.builder.MoveToArea(area.BurialGrounds), -// a.char.Buff(), -// action.NewStepChain(func(d game.Data) []step.Step { -// for _, l := range d.AdjacentLevels { -// if l.Area == area.Mausoleum { -// return []step.Step{step.MoveTo(l.Position, step.StopAtDistance(50))} -// } -// } -// -// return []step.Step{} -// }), -// a.char.KillMonsterSequence(func(d game.Data) (data.UnitID, bool) { -// for _, m := range d.Monsters.Enemies() { -// if pather.DistanceFromMe(d, m.Position) < 3 { -// return m.UnitID, true -// } -// -// if m.Name == npc.BloodRaven { -// return m.UnitID, true -// } -// } -// -// return 0, false -// }, nil, step.Distance(5, 15)), -// } -// }) -//} +func (a Leveling) tristram() []action.Action { + a.logger.Info("Starting Tristram run") + return Tristram{baseRun: a.baseRun}.BuildActions() +} func (a Leveling) countess() []action.Action { a.logger.Info("Starting Countess run") @@ -215,7 +209,6 @@ func (a Leveling) andariel(d game.Data) []action.Action { ) actions = append(actions, - a.builder.UsePortalInTown(), a.builder.MoveTo(func(d game.Data) (data.Position, bool) { return andarielStartingPosition, true }), From 11356d91c323267dc8c358b32d167c5fce96f113 Mon Sep 17 00:00:00 2001 From: TDL Date: Sat, 10 Aug 2024 22:33:00 +0200 Subject: [PATCH 05/12] Update tristram.go Changed the way the Cairn stones are selected to prevent getting stuck there. --- internal/run/tristram.go | 39 +++++++++++++++++++++++++++++++++------ 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/internal/run/tristram.go b/internal/run/tristram.go index 85fc805a..a51cc32c 100644 --- a/internal/run/tristram.go +++ b/internal/run/tristram.go @@ -1,9 +1,11 @@ package run import ( + "fmt" + "time" + "github.com/hectorgimenez/koolo/internal/config" "github.com/hectorgimenez/koolo/internal/game" - "time" "github.com/hectorgimenez/d2go/pkg/data" "github.com/hectorgimenez/d2go/pkg/data/area" @@ -36,8 +38,8 @@ func (a Tristram) BuildActions() []action.Action { } // Clear monsters around the portal - if a.CharacterCfg.Game.Tristram.ClearPortal { - actions = append(actions, a.builder.ClearAreaAroundPlayer(10, data.MonsterAnyFilter())) + if a.CharacterCfg.Game.Tristram.ClearPortal || a.CharacterCfg.Game.Runs[0] == "leveling" { + actions = append(actions, a.builder.ClearAreaAroundPlayer(20, data.MonsterAnyFilter())) } actions = append(actions, a.openPortalIfNotOpened()) @@ -61,7 +63,7 @@ func (a Tristram) BuildActions() []action.Action { })} } else { filter := data.MonsterAnyFilter() - if a.CharacterCfg.Game.Tristram.FocusOnElitePacks { + if a.CharacterCfg.Game.Tristram.FocusOnElitePacks && a.CharacterCfg.Game.Runs[0] != "leveling" { filter = data.MonsterEliteFilter() } return []action.Action{a.builder.ClearArea(false, filter)} @@ -84,7 +86,10 @@ func (a Tristram) openPortalIfNotOpened() action.Action { } // We don't know which order the stones are, so we activate all of them one by one in sequential order, 5 times - for range 5 { + //activeStones := 0 + for range 6 { + stoneTries := 0 + activeStones := 0 for _, cainStone := range []object.Name{ object.CairnStoneAlpha, object.CairnStoneBeta, @@ -94,8 +99,30 @@ func (a Tristram) openPortalIfNotOpened() action.Action { } { st := cainStone stone, _ := d.Objects.FindOne(st) - actions = append(actions, a.builder.InteractObject(stone.Name, nil)) + if stone.Selectable { + actions = append(actions, a.builder.InteractObject(stone.Name, func(d game.Data) bool { + if stoneTries < 5 { + stoneTries++ + helper.Sleep(200) + x, y := a.PathFinder.GameCoordsToScreenCords(d.PlayerUnit.Position.X, d.PlayerUnit.Position.Y, stone.Position.X, stone.Position.Y) + a.HID.Click(game.LeftButton, x+3*stoneTries, y) + a.logger.Debug(fmt.Sprintf("Tried to click %s at screen pos %vx%v", stone.Desc().Name, x, y)) + return false + } + stoneTries = 0 + return true + }), + ) + } else { + helper.Sleep(500) + activeStones++ + } + _, tristPortal := d.Objects.FindOne(object.PermanentTownPortal) + if activeStones >= 5 || tristPortal { + break + } } + } // Wait until portal is open From 4f3f986f71a0da79dc093057a2fc07d9d0ae3cfe Mon Sep 17 00:00:00 2001 From: TDL Date: Sun, 11 Aug 2024 10:17:38 +0200 Subject: [PATCH 06/12] Added check for town portal keybind --- internal/character/paladin_leveling.go | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/internal/character/paladin_leveling.go b/internal/character/paladin_leveling.go index f675d8b0..7a723f33 100644 --- a/internal/character/paladin_leveling.go +++ b/internal/character/paladin_leveling.go @@ -1,6 +1,7 @@ package character import ( + "log/slog" "sort" "time" @@ -19,10 +20,21 @@ type PaladinLeveling struct { BaseCharacter } -func (p PaladinLeveling) CheckKeyBindings(d game.Data) []skill.ID { +func (s PaladinLeveling) CheckKeyBindings(d game.Data) []skill.ID { + requireKeybindings := []skill.ID{skill.TomeOfTownPortal} + missingKeybindings := []skill.ID{} - // Not implemented - return []skill.ID{} + for _, cskill := range requireKeybindings { + if _, found := d.KeyBindings.KeyBindingForSkill(cskill); !found { + missingKeybindings = append(missingKeybindings, cskill) + } + } + + if len(missingKeybindings) > 0 { + s.logger.Debug("There are missing required key bindings.", slog.Any("Bindings", missingKeybindings)) + } + + return missingKeybindings } func (p PaladinLeveling) KillMonsterSequence(monsterSelector func(d game.Data) (data.UnitID, bool), skipOnImmunities []stat.Resist, opts ...step.AttackOption) action.Action { From 65a4e667c1eb6155abf0d1715114e0c616312d80 Mon Sep 17 00:00:00 2001 From: TDL Date: Sun, 11 Aug 2024 10:18:02 +0200 Subject: [PATCH 07/12] Added check for town portal keybind --- internal/character/sorceress_leveling.go | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/internal/character/sorceress_leveling.go b/internal/character/sorceress_leveling.go index df54f14c..a5ed590b 100644 --- a/internal/character/sorceress_leveling.go +++ b/internal/character/sorceress_leveling.go @@ -1,6 +1,7 @@ package character import ( + "log/slog" "sort" "time" @@ -21,9 +22,20 @@ type SorceressLeveling struct { } func (s SorceressLeveling) CheckKeyBindings(d game.Data) []skill.ID { + requireKeybindings := []skill.ID{skill.TomeOfTownPortal} + missingKeybindings := []skill.ID{} - // Not implemented - return []skill.ID{} + for _, cskill := range requireKeybindings { + if _, found := d.KeyBindings.KeyBindingForSkill(cskill); !found { + missingKeybindings = append(missingKeybindings, cskill) + } + } + + if len(missingKeybindings) > 0 { + s.logger.Debug("There are missing required key bindings.", slog.Any("Bindings", missingKeybindings)) + } + + return missingKeybindings } func (s SorceressLeveling) KillMonsterSequence(monsterSelector func(d game.Data) (data.UnitID, bool), skipOnImmunities []stat.Resist, opts ...step.AttackOption) action.Action { From 7e957da41db4d33069b6a3836034b97a3c43e34c Mon Sep 17 00:00:00 2001 From: TDL Date: Sun, 11 Aug 2024 10:18:25 +0200 Subject: [PATCH 08/12] Added check for town portal keybind --- .../character/sorceress_leveling_lightning.go | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/internal/character/sorceress_leveling_lightning.go b/internal/character/sorceress_leveling_lightning.go index 8b3bade8..61044c8c 100644 --- a/internal/character/sorceress_leveling_lightning.go +++ b/internal/character/sorceress_leveling_lightning.go @@ -1,6 +1,7 @@ package character import ( + "log/slog" "sort" "time" @@ -21,9 +22,20 @@ type SorceressLevelingLightning struct { } func (s SorceressLevelingLightning) CheckKeyBindings(d game.Data) []skill.ID { + requireKeybindings := []skill.ID{skill.TomeOfTownPortal} + missingKeybindings := []skill.ID{} - // Not implemented - return []skill.ID{} + for _, cskill := range requireKeybindings { + if _, found := d.KeyBindings.KeyBindingForSkill(cskill); !found { + missingKeybindings = append(missingKeybindings, cskill) + } + } + + if len(missingKeybindings) > 0 { + s.logger.Debug("There are missing required key bindings.", slog.Any("Bindings", missingKeybindings)) + } + + return missingKeybindings } func (s SorceressLevelingLightning) KillMonsterSequence(monsterSelector func(d game.Data) (data.UnitID, bool), skipOnImmunities []stat.Resist, opts ...step.AttackOption) action.Action { From eb03ada3f326fbfec683c372a601d44848be030d Mon Sep 17 00:00:00 2001 From: TDL Date: Sun, 11 Aug 2024 17:49:00 +0200 Subject: [PATCH 09/12] Added Legacy leveling mode --- internal/action/leveling_tools.go | 36 ++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/internal/action/leveling_tools.go b/internal/action/leveling_tools.go index 82dbb9a7..a1358235 100644 --- a/internal/action/leveling_tools.go +++ b/internal/action/leveling_tools.go @@ -37,6 +37,22 @@ var uiSkillPagePosition = [3]data.Position{ var uiSkillRowPosition = [6]int{190, 250, 310, 365, 430, 490} var uiSkillColumnPosition = [3]int{920, 1010, 1095} +var uiStatButtonPositionLegacy = map[stat.ID]data.Position{ + stat.Strength: {X: 430, Y: 180}, + stat.Dexterity: {X: 430, Y: 250}, + stat.Vitality: {X: 430, Y: 360}, + stat.Energy: {X: 430, Y: 435}, +} + +var uiSkillPagePositionLegacy = [3]data.Position{ + {X: 970, Y: 510}, + {X: 970, Y: 390}, + {X: 970, Y: 260}, +} + +var uiSkillRowPositionLegacy = [6]int{110, 195, 275, 355, 440, 520} +var uiSkillColumnPositionLegacy = [3]int{690, 770, 855} + func (b *Builder) EnsureStatPoints() *StepChainAction { return NewStepChain(func(d game.Data) []step.Step { char, isLevelingChar := b.ch.(LevelingCharacter) @@ -69,7 +85,13 @@ func (b *Builder) EnsureStatPoints() *StepChainAction { } } - statBtnPosition := uiStatButtonPosition[st] + var statBtnPosition data.Position + if d.LegacyGraphics { + statBtnPosition = uiStatButtonPositionLegacy[st] + } else { + statBtnPosition = uiStatButtonPosition[st] + } + return []step.Step{ step.SyncStep(func(_ game.Data) error { helper.Sleep(100) @@ -132,9 +154,17 @@ func (b *Builder) EnsureSkillPoints() *StepChainAction { step.SyncStep(func(_ game.Data) error { assignAttempts++ helper.Sleep(100) - b.HID.Click(game.LeftButton, uiSkillPagePosition[skillDesc.Page-1].X, uiSkillPagePosition[skillDesc.Page-1].Y) + if d.LegacyGraphics { + b.HID.Click(game.LeftButton, uiSkillPagePositionLegacy[skillDesc.Page-1].X, uiSkillPagePositionLegacy[skillDesc.Page-1].Y) + } else { + b.HID.Click(game.LeftButton, uiSkillPagePosition[skillDesc.Page-1].X, uiSkillPagePosition[skillDesc.Page-1].Y) + } helper.Sleep(200) - b.HID.Click(game.LeftButton, uiSkillColumnPosition[skillDesc.Column-1], uiSkillRowPosition[skillDesc.Row-1]) + if d.LegacyGraphics { + b.HID.Click(game.LeftButton, uiSkillColumnPositionLegacy[skillDesc.Column-1], uiSkillRowPositionLegacy[skillDesc.Row-1]) + } else { + b.HID.Click(game.LeftButton, uiSkillColumnPosition[skillDesc.Column-1], uiSkillRowPosition[skillDesc.Row-1]) + } helper.Sleep(500) return nil }), From 56b6b3105d87ff2673ab80cafb4464704309fcbd Mon Sep 17 00:00:00 2001 From: TDL Date: Mon, 12 Aug 2024 09:09:50 +0200 Subject: [PATCH 10/12] Update leveling_act1.go Changed Act 1 leveling to: - Clear Blood Moor until level 3 - Clear Cold Plains until level 6 - Do Den of Evil Quest - Clear Stony Field until level 9 - Do the Rescue Cain Quest - Do Tristram Runs until level 14 - Do Countess Runs until level 17 - Kill Andariel and Finish Act 1 --- internal/run/leveling_act1.go | 158 +++++++++++++++++----------------- 1 file changed, 81 insertions(+), 77 deletions(-) diff --git a/internal/run/leveling_act1.go b/internal/run/leveling_act1.go index 6e24f925..a702b1ac 100644 --- a/internal/run/leveling_act1.go +++ b/internal/run/leveling_act1.go @@ -27,27 +27,36 @@ func (a Leveling) act1() action.Action { running = true - // clear Bloodmoor until level 3 - if lvl, _ := d.PlayerUnit.FindStat(stat.Level, 0); lvl.Value <= 3 { + // clear Blood Moor until level 3 + if lvl, _ := d.PlayerUnit.FindStat(stat.Level, 0); lvl.Value < 3 { return a.bloodMoor() } - // do Den of Evil until level 6 - if lvl, _ := d.PlayerUnit.FindStat(stat.Level, 0); lvl.Value <= 6 || !d.Quests[quest.Act1DenOfEvil].Completed() { + // clear Cold Plains until level 6 + if lvl, _ := d.PlayerUnit.FindStat(stat.Level, 0); lvl.Value < 6 { + return a.coldPlains() + } + + if lvl, _ := d.PlayerUnit.FindStat(stat.Level, 0); lvl.Value == 6 || !d.Quests[quest.Act1DenOfEvil].Completed() { return a.denOfEvil() } + // clear Stony Field until level 9 + if lvl, _ := d.PlayerUnit.FindStat(stat.Level, 0); lvl.Value < 9 { + return a.stonyField() + } + if !a.isCainInTown(d) && !d.Quests[quest.Act1TheSearchForCain].Completed() { - return a.deckardCain(d) + return a.deckardCain() } // do Tristram Runs until level 14 - if lvl, _ := d.PlayerUnit.FindStat(stat.Level, 0); lvl.Value <= 14 { + if lvl, _ := d.PlayerUnit.FindStat(stat.Level, 0); lvl.Value < 14 { return a.tristram() } // do Countess Runs until level 17 - if lvl, _ := d.PlayerUnit.FindStat(stat.Level, 0); lvl.Value <= 17 { + if lvl, _ := d.PlayerUnit.FindStat(stat.Level, 0); lvl.Value < 17 { return a.countess() } @@ -64,90 +73,91 @@ func (a Leveling) bloodMoor() []action.Action { } } +func (a Leveling) coldPlains() []action.Action { + a.logger.Info("Starting Blood Moor run") + return []action.Action{ + a.builder.WayPoint(area.ColdPlains), + a.builder.Buff(), + a.builder.ClearArea(false, data.MonsterAnyFilter()), + } +} + func (a Leveling) denOfEvil() []action.Action { - a.logger.Info("Starting Den of Evil run") + a.logger.Info("Starting Den of Evil Quest") return []action.Action{ a.builder.MoveToArea(area.BloodMoor), a.builder.Buff(), a.builder.MoveToArea(area.DenOfEvil), - a.builder.Buff(), a.builder.ClearArea(false, data.MonsterAnyFilter()), + a.builder.ReturnTown(), + a.builder.InteractNPC( + npc.Akara, + step.KeySequence(win.VK_ESCAPE), + ), } } -func (a Leveling) tristram() []action.Action { - a.logger.Info("Starting Tristram run") - return Tristram{baseRun: a.baseRun}.BuildActions() -} - -func (a Leveling) countess() []action.Action { - a.logger.Info("Starting Countess run") - return Countess{baseRun: a.baseRun}.BuildActions() +func (a Leveling) stonyField() []action.Action { + a.logger.Info("Starting Blood Moor run") + return []action.Action{ + a.builder.WayPoint(area.StonyField), + a.builder.Buff(), + a.builder.ClearArea(false, data.MonsterAnyFilter()), + } } -func (a Leveling) deckardCain(d game.Data) (actions []action.Action) { - a.logger.Info("Rescuing Cain") - if _, found := d.Inventory.Find("KeyToTheCairnStones"); !found { - actions = []action.Action{ - a.builder.WayPoint(area.DarkWood), - a.builder.Buff(), - a.builder.MoveTo(func(d game.Data) (data.Position, bool) { - for _, o := range d.Objects { - if o.Name == object.InifussTree { - return o.Position, true - } - } - - return data.Position{}, false - }), - a.builder.InteractObject(object.InifussTree, func(d game.Data) bool { - _, found := d.Inventory.Find(scrollOfInifuss) - return found - }), - a.builder.ItemPickup(false, 30), - a.builder.ReturnTown(), - a.builder.InteractNPC( - npc.Akara, - step.KeySequence(win.VK_ESCAPE), - ), - } - - // Heal and refill pots - actions = append(actions, - a.builder.ReturnTown(), - a.builder.RecoverCorpse(), - a.builder.IdentifyAll(false), - a.builder.Stash(false), - a.builder.VendorRefill(false, true), - ) - - if a.CharacterCfg.Game.Leveling.EnsurePointsAllocation { - actions = append(actions, - a.builder.EnsureStatPoints(), - a.builder.EnsureSkillPoints(), - ) - } +func (a Leveling) isCainInTown(d game.Data) bool { + _, found := d.Monsters.FindOne(npc.DeckardCain5, data.MonsterTypeNone) - if a.CharacterCfg.Game.Leveling.EnsureKeyBinding { - actions = append(actions, - a.builder.EnsureSkillBindings(), - ) - } + return found +} - actions = append(actions, - a.builder.Heal(), - a.builder.ReviveMerc(), - a.builder.HireMerc(), - a.builder.Repair(), - ) - } +func (a Leveling) deckardCain() []action.Action { + a.logger.Info("Starting Rescue Cain Quest") + var actions []action.Action + actions = append(actions, + a.builder.WayPoint(area.RogueEncampment), + a.builder.WayPoint(area.DarkWood), + a.builder.Buff(), + a.builder.ClearArea(false, data.MonsterAnyFilter()), + // after clearing the area, go save Cain + a.builder.MoveTo(func(d game.Data) (data.Position, bool) { + for _, o := range d.Objects { + if o.Name == object.InifussTree { + return o.Position, true + } + } + return data.Position{}, false + }), + a.builder.ClearAreaAroundPlayer(20, data.MonsterAnyFilter()), + a.builder.InteractObject(object.InifussTree, func(d game.Data) bool { + _, found := d.Inventory.Find(scrollOfInifuss) + return found + }), + a.builder.ItemPickup(true, 0), + a.builder.ReturnTown(), + a.builder.InteractNPC( + npc.Akara, + step.KeySequence(win.VK_ESCAPE), + ), + ) // Reuse Tristram Run actions actions = append(actions, Tristram{baseRun: a.baseRun}.BuildActions()...) return actions } +func (a Leveling) tristram() []action.Action { + a.logger.Info("Starting Tristram run") + return Tristram{baseRun: a.baseRun}.BuildActions() +} + +func (a Leveling) countess() []action.Action { + a.logger.Info("Starting Countess run") + return Countess{baseRun: a.baseRun}.BuildActions() +} + func (a Leveling) andariel(d game.Data) []action.Action { a.logger.Info("Starting Andariel run") actions := []action.Action{ @@ -219,9 +229,3 @@ func (a Leveling) andariel(d game.Data) []action.Action { return actions } - -func (a Leveling) isCainInTown(d game.Data) bool { - _, found := d.Monsters.FindOne(npc.DeckardCain5, data.MonsterTypeNone) - - return found -} From bf02ef4e9144d19ff924828774e477afcea0c3e3 Mon Sep 17 00:00:00 2001 From: TDL Date: Mon, 12 Aug 2024 16:29:17 +0200 Subject: [PATCH 11/12] Update tristram.go Added an extra move to Cairn stones if portal isnt detected. For some reason it sometimes doesnt detect its there while it is. --- internal/run/tristram.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/internal/run/tristram.go b/internal/run/tristram.go index a51cc32c..1afe6af6 100644 --- a/internal/run/tristram.go +++ b/internal/run/tristram.go @@ -80,6 +80,17 @@ func (a Tristram) openPortalIfNotOpened() action.Action { return nil } + // Sometimes the portal is out of detection range for some reason, this way it moves to the stones and enters the portal. + st, stone := d.Objects.FindOne(object.CairnStoneAlpha) + if stone { + actions = append(actions, a.builder.MoveToCoords(st.Position)) + actions = append(actions, a.builder.InteractObject(object.PermanentTownPortal, func(d game.Data) bool { + return d.PlayerUnit.Area == area.Tristram + }, step.Wait(time.Second))) + + return actions + } + if !logged { a.logger.Debug("Tristram portal not detected, trying to open it") logged = true From fddbc4cb61d5f9abdc33c3790c2777b8c88abb87 Mon Sep 17 00:00:00 2001 From: TDL Date: Mon, 12 Aug 2024 16:31:30 +0200 Subject: [PATCH 12/12] Update move.go Additional click on barrel to avoid getting stuck on them. --- internal/action/move.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/internal/action/move.go b/internal/action/move.go index 9a1096a4..277b25b2 100644 --- a/internal/action/move.go +++ b/internal/action/move.go @@ -177,6 +177,9 @@ func (b *Builder) MoveTo(toFunc func(d game.Data) (data.Position, bool), opts .. return []Action{NewStepChain(func(d game.Data) []step.Step { return []step.Step{step.InteractObjectByID(o.ID, func(d game.Data) bool { obj, found := d.Objects.FindByID(o.ID) + //additional click on barrel to avoid getting stuck + x, y := b.PathFinder.GameCoordsToScreenCords(d.PlayerUnit.Position.X, d.PlayerUnit.Position.Y, o.Position.X, o.Position.Y) + b.HID.Click(game.LeftButton, x, y) return found && !obj.Selectable })} })}