Skip to content

Commit

Permalink
Multiple changes (see description)
Browse files Browse the repository at this point in the history
- Add check to kill starting point NPC if it exists
- Add ability to cast heal spells if TP is over 50% (heal to full)
- Add emergency heal option when HP is under 10%
- Heal items heal past 60% health instead of just using one
- More consistent output text (aligned/easier to read)
  • Loading branch information
ethanmoffat committed May 9, 2021
1 parent 256041b commit 3d18fa7
Show file tree
Hide file tree
Showing 2 changed files with 107 additions and 18 deletions.
46 changes: 42 additions & 4 deletions EOBot/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using EOLib.Domain.Map;
using EOLib.Domain.Notifiers;
using EOLib.Domain.NPC;
using EOLib.Net.Communication;
using System;
using System.Linq;
using System.Threading.Tasks;
Expand All @@ -14,6 +13,7 @@ namespace EOBot
static class Program
{
private static BotFramework f;
private static Win32.ConsoleCtrlDelegate consoleControlHandler;

[AutoMappedType]
class NpcWalkNotifier : INPCActionNotifier
Expand Down Expand Up @@ -57,28 +57,61 @@ public void StartNPCWalkAnimation(int npcIndex)
class CharacterTakeDamageNotifier : IMainCharacterEventNotifier
{
private readonly ICharacterProvider _characterProvider;
private readonly IExperienceTableProvider _experienceTableProvider;

public CharacterTakeDamageNotifier(ICharacterProvider characterProvider)
public CharacterTakeDamageNotifier(ICharacterProvider characterProvider,
IExperienceTableProvider experienceTableProvider)
{
_characterProvider = characterProvider;
_experienceTableProvider = experienceTableProvider;
}

public void NotifyGainedExp(int expDifference)
{
var nextLevelExp = _experienceTableProvider.ExperienceByLevel[_characterProvider.MainCharacter.Stats[CharacterStat.Level] + 1];
var tnl = nextLevelExp - _characterProvider.MainCharacter.Stats[CharacterStat.Experience] - expDifference;
Console.WriteLine($"[EXP ] {expDifference,7} - {tnl} TNL");
}

public void NotifyTakeDamage(int damageTaken, int playerPercentHealth, bool isHeal)
{
Console.WriteLine($"[{(isHeal ? "HEAL" : "DMG ")}] {damageTaken,7} - {playerPercentHealth}% HP");

var hp = _characterProvider.MainCharacter.Stats[CharacterStat.HP];
if (!isHeal && hp - damageTaken <= 0)
{
Console.WriteLine("**** YOU DIED ****");
Console.WriteLine("[DEAD] **** YOU DIED ****");
}
}

public void TakeItemFromMap(short id, int amountTaken)
{
var weight = _characterProvider.MainCharacter.Stats[CharacterStat.Weight];
var maxWeight = _characterProvider.MainCharacter.Stats[CharacterStat.MaxWeight];
Console.WriteLine($"[ITEM] {weight,3}/{maxWeight,3} weight");
}
}

[AutoMappedType]
class CharacterAnimationNotifier : IOtherCharacterAnimationNotifier
{
private readonly ICharacterProvider _characterProvider;

public CharacterAnimationNotifier(ICharacterProvider characterProvider)
{
_characterProvider = characterProvider;
}

public void NotifySelfSpellCast(short playerId, short spellId, int spellHp, byte percentHealth)
{
if (playerId == _characterProvider.MainCharacter.ID && spellHp > 0)
Console.WriteLine($"[HEAL] {spellHp,7} - {percentHealth}% HP");
}

public void NotifyStartSpellCast(short playerId, short spellId) { }
public void NotifyTargetOtherSpellCast(short sourcePlayerID, short targetPlayerID, short spellId, int recoveredHP, byte targetPercentHealth) { }
public void StartOtherCharacterAttackAnimation(int characterID) { }
public void StartOtherCharacterWalkAnimation(int characterID) { }
}

static async Task Main(string[] args)
Expand All @@ -93,7 +126,12 @@ static async Task Main(string[] args)
"EOLib.Logger"
};

Win32.SetConsoleCtrlHandler(HandleCtrl, true);
// this needs to be a delegate because it is getting garbage collected
consoleControlHandler = new Win32.ConsoleCtrlDelegate(HandleCtrl);
if (!Win32.SetConsoleCtrlHandler(consoleControlHandler, true))
{
Console.WriteLine("WARNING: Unable to set console control handler! CTRL+C will not terminate cleanly.");
}

ArgumentsParser parsedArgs = new ArgumentsParser(args);

Expand Down
79 changes: 65 additions & 14 deletions EOBot/TrainerBot.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,17 @@ protected override async Task DoWorkAsync(CancellationToken ct)
var handler = c.Resolve<IOutOfBandPacketHandler>();
var itemData = c.Resolve<IEIFFileProvider>().EIFFile;
var npcData = c.Resolve<IENFFileProvider>().ENFFile;
var spellData = c.Resolve<IESFFileProvider>().ESFFile;
var charInventoryRepo = c.Resolve<ICharacterInventoryRepository>();

var healItems = charInventoryRepo.ItemInventory
.Where(x => itemData.Data.Any(y => y.ID == x.ItemID && y.Type == ItemType.Heal))
.ToList();

var healSpells = charInventoryRepo.SpellInventory
.Where(x => spellData.Data.Any(y => y.ID == x.ID && y.Type == SpellType.Heal))
.ToList();

int attackCount = 0;

while (!TerminationRequested)
Expand All @@ -75,47 +80,75 @@ protected override async Task DoWorkAsync(CancellationToken ct)
var charRenderProps = charRepo.MainCharacter.RenderProperties;
var nextX = charRenderProps.GetDestinationX();
var nextY = charRenderProps.GetDestinationY();
var currentCellState = mapCellStateProvider.GetCellStateAt(charRenderProps.MapX, charRenderProps.MapY);
var cellState = mapCellStateProvider.GetCellStateAt(nextX, nextY);

if (attackCount < CONSECUTIVE_ATTACK_COUNT || cellState.NPC.HasValue)
if ((attackCount < CONSECUTIVE_ATTACK_COUNT && !currentCellState.NPC.HasValue) || cellState.NPC.HasValue)
{
if (cellState.NPC.HasValue)
if (cellState.NPC.HasValue && charRepo.MainCharacter.Stats[CharacterStat.HP] > charRepo.MainCharacter.Stats[CharacterStat.MaxHP] * .1)
{
Console.WriteLine($"Attacking NPC index {cellState.NPC.Value.Index,3} : {npcData.Data.Single(x => x.ID == cellState.NPC.Value.ID).Name}");
Console.WriteLine($"[ATTK] {cellState.NPC.Value.Index,7} - {npcData.Data.Single(x => x.ID == cellState.NPC.Value.ID).Name}");
charActions.Attack();
attackCount++;
}
else if (cellState.Items.Any())
{
foreach (var item in cellState.Items)
{
Console.WriteLine($"Picking up item {itemData.Data.Single(x => x.ID == item.ItemID).Name}x{item.Amount}");
Console.WriteLine($"[TAKE] {item.Amount,7} {itemData.Data.Single(x => x.ID == item.ItemID).Name}");
mapActions.PickUpItem(item);
}
}
else if (healItems.Any() && charRepo.MainCharacter.Stats[CharacterStat.HP] < charRepo.MainCharacter.Stats[CharacterStat.MaxHP] * .3)
else if (healSpells.Any() && charRepo.MainCharacter.Stats[CharacterStat.HP] < charRepo.MainCharacter.Stats[CharacterStat.MaxHP]
&& charRepo.MainCharacter.Stats[CharacterStat.TP] > charRepo.MainCharacter.Stats[CharacterStat.MaxTP] * .5)
{
var itemToUse = itemData.Data
.Where(x => healItems.Any(y => y.ItemID == x.ID))
.OrderBy(x => x.HP)
var spellToUse = spellData.Data
.Where(x => healSpells.Any(y => y.ID == x.ID) && x.Target != SpellTarget.Group)
.OrderByDescending(x => x.HP)
.First();
var amount = healItems.Single(x => x.ItemID == itemToUse.ID).Amount;

Console.WriteLine($"Using heal item {itemToUse.Name} (heal: {itemToUse.HP}) (remaining: {amount - 1}) (other heal item types: {healItems.Count - 1})");
Console.WriteLine($"[CAST] {spellToUse.HP,4} HP - {spellToUse.Name}");

charActions.PrepareCastSpell(spellToUse.ID);
await Task.Delay((int)Math.Round(spellToUse.CastTime / 2.0 * 950)); // ?

charActions.UseItem(itemToUse.ID);
await Task.Delay(ATTACK_BACKOFF_MS);
charActions.CastSpell(spellToUse.ID, charRepo.MainCharacter);
await Task.Delay((int)Math.Round(spellToUse.CastTime / 2.0 * 950)); // ?
}
else if (healItems.Any() && charRepo.MainCharacter.Stats[CharacterStat.HP] < charRepo.MainCharacter.Stats[CharacterStat.MaxHP] * .3)
{
while (charRepo.MainCharacter.Stats[CharacterStat.HP] < charRepo.MainCharacter.Stats[CharacterStat.MaxHP] * .6)
{
var itemToUse = itemData.Data
.Where(x => healItems.Any(y => y.ItemID == x.ID))
.OrderBy(x => x.HP)
.First();
var amount = healItems.Single(x => x.ItemID == itemToUse.ID).Amount;

Console.WriteLine($"[USE ] {itemToUse.Name} - {itemToUse.HP} HP - inventory: {amount - 1} - (other heal item types: {healItems.Count - 1})");

charActions.UseItem(itemToUse.ID);
await Task.Delay(ATTACK_BACKOFF_MS);
}
}

await Task.Delay(ATTACK_BACKOFF_MS);

healItems = charInventoryRepo.ItemInventory
.Where(x => itemData.Data.Any(y => y.ID == x.ItemID && y.Type == ItemType.Heal))
.ToList();

healSpells = charInventoryRepo.SpellInventory
.Where(x => spellData.Data.Any(y => y.ID == x.ID && y.Type == SpellType.Heal))
.ToList();
}
else
{
Console.WriteLine($"Walking due to consecutive attacks: {attackCount}");
if (!currentCellState.NPC.HasValue)
Console.WriteLine($"[MOVE] Walking due to consecutive attacks: {attackCount}");
else
Console.WriteLine($"[ATTK] Killing NPC at player location");

await Task.Delay(TimeSpan.FromMilliseconds(300));

var direction = charRenderProps.Direction;
Expand All @@ -126,7 +159,25 @@ protected override async Task DoWorkAsync(CancellationToken ct)
// todo: character actions Face() should also change the character's direction instead of relying on client to update it separately
charRepo.MainCharacter = charRepo.MainCharacter.WithRenderProperties(charRepo.MainCharacter.RenderProperties.WithDirection(direction.Opposite()));
await Task.Delay(TimeSpan.FromMilliseconds(FACE_BACKOFF_MS));


// kill NPC if it was in our starting point
while (currentCellState.NPC.HasValue && !TerminationRequested)
{
Console.WriteLine($"[ATTK] {currentCellState.NPC.Value.Index,7} : {npcData.Data.Single(x => x.ID == currentCellState.NPC.Value.ID).Name}");
charActions.Attack();
await Task.Delay(TimeSpan.FromMilliseconds(ATTACK_BACKOFF_MS));
currentCellState = mapCellStateProvider.GetCellStateAt(charRenderProps.MapX, charRenderProps.MapY);
}

if (currentCellState.Items.Any())
{
foreach (var item in currentCellState.Items)
{
Console.WriteLine($"[TAKE] {item.Amount,7} {itemData.Data.Single(x => x.ID == item.ItemID).Name}");
mapActions.PickUpItem(item);
}
}

charActions.Walk();
await Task.Delay(TimeSpan.FromMilliseconds(WALK_BACKOFF_MS));

Expand Down

0 comments on commit 3d18fa7

Please sign in to comment.