By taking a quick look at an unedited version of Apoc's Ranger Routine we can do some really quick psudo coding and figure out what exactly we need to do.
1. Getting Granite Flasks to be Used
First off, let's look at the IEnum that defines Granite Flasks and how the routine checks to see if a flask is in use or not.
Code:
private IEnumerable<InventoryItem> GraniteFlasks
{
get
{
IEnumerable<InventoryItem> inv = LokiPoe.Me.Inventory.Flasks.Items;
return from item in inv
let flask = item.Flask
where flask != null && item.Name == "Granite Flask" && flask.CanUse
select item;
}
}
Code:
private Player Me { get { return LokiPoe.Me; } }
private Player Me { get { return LokiPoe.Me; } }
private Composite CreateFlaskLogic()
{
return new PrioritySelector(
new Decorator(ret => _flaskCd.IsFinished && Me.HealthPercent < 80 && LifeFlasks.Count() != 0 && !Me.HasAura("flask_effect_life"),
new Action(ret =>
{
LifeFlasks.First().Use();
_flaskCd.Reset();
})),
new Decorator(ret => _flaskCd.IsFinished && Me.ManaPercent < 50 && ManaFlasks.Count() != 0 && !Me.HasAura("flask_effect_mana"),
new Action(ret =>
{
ManaFlasks.First().Use();
_flaskCd.Reset();
}))
);
}
As you
may or may not have noticed popping granite flasks is not implemented in the combat routine. So let's do a bit of quick copypasta.
Code:
private Composite CreateFlaskLogic()
{
return new PrioritySelector(
new Decorator(ret => _flaskCd.IsFinished && Me.HealthPercent < 80 && LifeFlasks.Count() != 0 && !Me.HasAura("flask_effect_life"),
new Action(ret =>
{
LifeFlasks.First().Use();
_flaskCd.Reset();
})),
new Decorator(ret => _flaskCd.IsFinished && Me.ManaPercent < 50 && ManaFlasks.Count() != 0 && !Me.HasAura("flask_effect_mana"),
new Action(ret =>
{
ManaFlasks.First().Use();
_flaskCd.Reset();
}))
);
}
Looking within the function we can assume that the following is psudo plausible:
Code:
new Decorator(ret => _flaskCd.IsFinished && Me.HealthPercent < 80 && LifeFlasks.Count() != 0 && !Me.HasAura("flask_effect_granite"),
new Action(ret =>
{
GraniteFlasks.First().Use();
_flaskCd.Reset();
})),
This portion determines when the flask is viable;
Code:
_flaskCd.IsFinished && Me.HealthPercent < 80 && LifeFlasks.Count() != 0 && !Me.HasAura("flask_effect_granite")
- Is current flask finished
- Is the character's HP less than 80%
- Does the character NOT have the granite flask effect applied?
There are two conditions that we can choose to apply, one which are; how many mobs are nearby and how far away they are.
Code:
&& NumberOfMobsNear(STRING, INT, INT)
Code:
&& MainTarget.Distance [comparer] INT)
So let's say that we want to be semi-paranoid about ranged monsters but that our focus should go to melee mobs.
Code:
&& NumberOfMobsNear(MainTarget, 20, 1) && MainTarget.Distance < 5)
These two conditions will only trigger if BOTH of them are true, so there are several monsters nearby that are within the range of 5 units.
So now we can go back up to our psudo granite flask logic and add our new conditions to the existing ones
Code:
new Decorator(ret => _flaskCd.IsFinished && Me.HealthPercent < 80 && LifeFlasks.Count() != 0 && !Me.HasAura("flask_effect_granite") && NumberOfMobsNear(MainTarget, 20, 1) && MainTarget.Distance < 5),
new Action(ret =>
{
GraniteFlasks.First().Use();
_flaskCd.Reset();
})),
2. Establishing a panic condition for low life situations.
The code provided by the flask logic function:
Code:
private Composite CreateFlaskLogic()
{
return new PrioritySelector(
new Decorator(ret => _flaskCd.IsFinished && Me.HealthPercent < 80 && LifeFlasks.Count() != 0 && !Me.HasAura("flask_effect_life"),
new Action(ret =>
{
LifeFlasks.First().Use();
_flaskCd.Reset();
})),
new Decorator(ret => _flaskCd.IsFinished && Me.ManaPercent < 50 && ManaFlasks.Count() != 0 && !Me.HasAura("flask_effect_mana"),
new Action(ret =>
{
ManaFlasks.First().Use();
_flaskCd.Reset();
}))
);
}
As before we're going to look at the code within the decorator pattern...
Code:
new Decorator(ret => _flaskCd.IsFinished && Me.HealthPercent < 80 && LifeFlasks.Count() != 0 && !Me.HasAura("flask_effect_life"),
new Action(ret =>
{
LifeFlasks.First().Use();
_flaskCd.Reset();
})),
Lets just remove the condition to check if we have already used a health pot and also add the condition to not use the panic routine if the bot has more than the "normal" decanter routine is.
Code:
new Decorator(ret => _flaskCd.IsFinished && Me.HealthPercent < 20 && LifeFlasks.Count() != 0,
new Action(ret =>
{
LifeFlasks.First().Use();
_flaskCd.Reset();
})),
Seems reasonable. In psudo terms.
Surviving More Easily
Now what else can we do to help with survivability, taking a look at the exile.cs routine that Apoc is updating gradually we can do a quick mishmash and have enduring cry, molten shell and immortal call utilized by the bot.
Code:
Cast("Enduring Cry", ret => !Me.HasAura("Endurance Charges")&& NumberOfMobsNear(MainTarget, 20, 1) && MainTarget.Distance > 5),
Cast("Molten Shell", ret => !Me.HasAura("Molten Shell")),
Cast("Immortal Call", ret => !Me.HasAura("Immortal Call")) && NumberOfMobsNear(MainTarget, 20, 1) && Me.HealthPercent < 10),
Implementing all of the modifications
- Open up your routine in notepad (or notepad++ or MSVS, whatever you have).
- Find "private Composite CreateFlaskLogic()"
- Replace the decantor code with our edited version:
Code:
private Composite CreateFlaskLogic()
{
return new PrioritySelector(
new Decorator(ret => _flaskCd.IsFinished && Me.HealthPercent < 80 && LifeFlasks.Count() != 0 && !Me.HasAura("flask_effect_life"),
new Action(ret =>
{
LifeFlasks.First().Use();
_flaskCd.Reset();
})),
new Decorator(ret => _flaskCd.IsFinished && Me.HealthPercent < 80 && LifeFlasks.Count() != 0 && !Me.HasAura("flask_effect_granite") && NumberOfMobsNear(MainTarget, 20, 1) && MainTarget.Distance < 5),
new Action(ret =>
{
GraniteFlasks.First().Use();
_flaskCd.Reset();
})),
new Decorator(ret => _flaskCd.IsFinished && Me.HealthPercent < 20 && LifeFlasks.Count() != 0,
new Action(ret =>
{
LifeFlasks.First().Use();
_flaskCd.Reset();
})),
new Decorator(ret => _flaskCd.IsFinished && Me.ManaPercent < 50 && ManaFlasks.Count() != 0 && !Me.HasAura("flask_effect_mana"),
new Action(ret =>
{
ManaFlasks.First().Use();
_flaskCd.Reset();
}))
);
}
4. Find the combat portion of the code that prioritizes what skills to use, it's initialized always at the end.
5. Add in our survivability spells
Code:
private Composite CreateProjectileLogic()
{
return new PrioritySelector(
Cast("Frenzy", ret => FrenzyTimeLeft.TotalSeconds <= 1.5 || FrenzyCharges < MaxFrenzyCharges),
Cast("Enduring Cry", ret => !Me.HasAura("Endurance Charges")&& NumberOfMobsNear(MainTarget, 20, 1) && MainTarget.Distance > 5),
Cast("Molten Shell", ret => !Me.HasAura("Molten Shell")),
Cast("Warlord's Mark", ret=> !MainTarget.HasAura("Warlord's Mark") && NumberOfMobsNear(MainTarget, 40, 2 && MainTarget.IsCursable && MainTarget.Distance > 5),
Cast("Enfeeble", ret=> !MainTarget.HasAura("Enfeeble") && MainTarget.IsCursable && MainTarget.Distance < 5),
Cast("Lightning Arrow", ret => NumberOfMobsNear(MainTarget, 40, 1)),
Cast("Frenzy"),
Cast("Default Attack")
);
}