Skip to content

Commit

Permalink
Make code in TrainerBot easier to read; add retries for NoDataSentExc…
Browse files Browse the repository at this point in the history
…eption
  • Loading branch information
ethanmoffat committed May 9, 2021
1 parent ca38f55 commit a4748cd
Show file tree
Hide file tree
Showing 2 changed files with 162 additions and 110 deletions.
269 changes: 161 additions & 108 deletions EOBot/TrainerBot.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@
using EOLib.Domain.Login;
using EOLib.Domain.Map;
using EOLib.IO;
using EOLib.IO.Pub;
using EOLib.IO.Repositories;
using EOLib.Net;
using EOLib.Net.Handlers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
Expand All @@ -25,6 +28,15 @@ internal class TrainerBot : BotBase
private readonly string _password;
private readonly string _character;

private ICharacterActions _characterActions;
private IMapActions _mapActions;

private ICharacterRepository _characterRepository;

private IPubFile<EIFRecord> _itemData;
private IPubFile<ENFRecord> _npcData;
private IPubFile<ESFRecord> _spellData;

public TrainerBot(int botIndex, string account, string password, string character)
: base(botIndex)
{
Expand Down Expand Up @@ -53,23 +65,19 @@ protected override async Task DoWorkAsync(CancellationToken ct)
await helper.LoginToCharacterAsync(_character);

var c = DependencyMaster.TypeRegistry[_index];
var mapActions = c.Resolve<IMapActions>();
var charRepo = c.Resolve<ICharacterRepository>();
var charActions = c.Resolve<ICharacterActions>();

_mapActions = c.Resolve<IMapActions>();
_characterRepository = c.Resolve<ICharacterRepository>();
_characterActions = c.Resolve<ICharacterActions>();
var mapCellStateProvider = c.Resolve<IMapCellStateProvider>();
var handler = c.Resolve<IOutOfBandPacketHandler>();
var itemData = c.Resolve<IEIFFileProvider>().EIFFile;
var npcData = c.Resolve<IENFFileProvider>().ENFFile;
var spellData = c.Resolve<IESFFileProvider>().ESFFile;
_itemData = c.Resolve<IEIFFileProvider>().EIFFile;
_npcData = c.Resolve<IENFFileProvider>().ENFFile;
_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();
var healItems = new List<IInventoryItem>();
var healSpells = new List<IInventorySpell>();

int attackCount = 0;
bool time_to_die = false;
Expand All @@ -78,133 +86,178 @@ protected override async Task DoWorkAsync(CancellationToken ct)
{
handler.PollForPacketsAndHandle();

var charRenderProps = charRepo.MainCharacter.RenderProperties;
var character = _characterRepository.MainCharacter;
var charRenderProps = character.RenderProperties;

var nextX = charRenderProps.GetDestinationX();
var nextY = charRenderProps.GetDestinationY();
var currentCellState = mapCellStateProvider.GetCellStateAt(charRenderProps.MapX, charRenderProps.MapY);
var cellState = mapCellStateProvider.GetCellStateAt(nextX, nextY);

if (time_to_die)
{
await Task.Delay(TimeSpan.FromSeconds(1.0 / 30.0));
continue;
}

if ((attackCount < CONSECUTIVE_ATTACK_COUNT && !currentCellState.NPC.HasValue) || cellState.NPC.HasValue)
if (!time_to_die)
{
if (cellState.NPC.HasValue && charRepo.MainCharacter.Stats[CharacterStat.HP] > charRepo.MainCharacter.Stats[CharacterStat.MaxHP] * .1)
if ((attackCount < CONSECUTIVE_ATTACK_COUNT && !currentCellState.NPC.HasValue) || cellState.NPC.HasValue)
{
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)
if (cellState.NPC.HasValue && character.Stats[CharacterStat.HP] > character.Stats[CharacterStat.MaxHP] * .1)
{
Console.WriteLine($"[TAKE] {item.Amount,7} - {itemData.Data.Single(x => x.ID == item.ItemID).Name}");
mapActions.PickUpItem(item);
await Attack(cellState);
attackCount++;
}
else if (cellState.Items.Any())
{
await PickUpItems(cellState);
}
else if (healSpells.Any() && character.Stats[CharacterStat.HP] < character.Stats[CharacterStat.MaxHP]
&& character.Stats[CharacterStat.TP] > character.Stats[CharacterStat.MaxTP] * .5)
{
await CastHealSpell(healSpells);
}
else if (healItems.Any() && character.Stats[CharacterStat.HP] < character.Stats[CharacterStat.MaxHP] * .3)
{
await UseHealItem(healItems, targetHealthPercent: .6);
}
else if (character.Stats[CharacterStat.Weight] >= character.Stats[CharacterStat.MaxWeight])
{
await SitDown();
time_to_die = true;
}
}
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 spellToUse = spellData.Data
.Where(x => healSpells.Any(y => y.ID == x.ID) && x.Target != SpellTarget.Group)
.OrderByDescending(x => x.HP)
.First();

Console.WriteLine($"[CAST] {spellToUse.HP,4} HP - {spellToUse.Name}");

charActions.PrepareCastSpell(spellToUse.ID);
await Task.Delay((int)Math.Round(spellToUse.CastTime / 2.0 * 950)); // ?
healItems = charInventoryRepo.ItemInventory
.Where(x => _itemData.Data.Any(y => y.ID == x.ItemID && y.Type == ItemType.Heal))
.ToList();

charActions.CastSpell(spellToUse.ID, charRepo.MainCharacter);
await Task.Delay((int)Math.Round(spellToUse.CastTime / 2.0 * 950)); // ?
healSpells = charInventoryRepo.SpellInventory
.Where(x => _spellData.Data.Any(y => y.ID == x.ID && y.Type == SpellType.Heal))
.ToList();
}
else if (healItems.Any() && charRepo.MainCharacter.Stats[CharacterStat.HP] < charRepo.MainCharacter.Stats[CharacterStat.MaxHP] * .3)
else
{
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;
if (!currentCellState.NPC.HasValue)
Console.WriteLine($"[MOVE] Walking due to consecutive attacks: {attackCount}");
else
Console.WriteLine($"[ATTK] Killing NPC at player location");

var originalDirection = charRenderProps.Direction;
await Walk();
await Face(originalDirection.Opposite());

Console.WriteLine($"[USE ] {itemToUse.Name} - {itemToUse.HP} HP - inventory: {amount - 1} - (other heal item types: {healItems.Count - 1})");
// kill NPC if it was in our starting point
while (currentCellState.NPC.HasValue && !TerminationRequested)
{
await Attack(currentCellState);

charActions.UseItem(itemToUse.ID);
await Task.Delay(ATTACK_BACKOFF_MS);
handler.PollForPacketsAndHandle();
currentCellState = mapCellStateProvider.GetCellStateAt(charRenderProps.MapX, charRenderProps.MapY);
}
}
else if (charRepo.MainCharacter.Stats[CharacterStat.Weight] >= charRepo.MainCharacter.Stats[CharacterStat.MaxWeight])
{
Console.WriteLine($"[SIT ] OVER WEIGHT LIMIT - TIME TO DIE");

charActions.ToggleSit();
time_to_die = true;
await PickUpItems(currentCellState);

await Walk();
await Face(originalDirection);

attackCount = 0;
}
}

await Task.Delay(ATTACK_BACKOFF_MS);
await Task.Delay(TimeSpan.FromSeconds(1.0 / 8.0));
}
}

healItems = charInventoryRepo.ItemInventory
.Where(x => itemData.Data.Any(y => y.ID == x.ItemID && y.Type == ItemType.Heal))
.ToList();
private async Task Attack(IMapCellState cellState)
{
Console.WriteLine($"[ATTK] {cellState.NPC.Value.Index,7} - {_npcData.Data.Single(x => x.ID == cellState.NPC.Value.ID).Name}");
await TrySend(_characterActions.Attack);
await Task.Delay(TimeSpan.FromMilliseconds(ATTACK_BACKOFF_MS));
}

healSpells = charInventoryRepo.SpellInventory
.Where(x => spellData.Data.Any(y => y.ID == x.ID && y.Type == SpellType.Heal))
.ToList();
}
else
{
if (!currentCellState.NPC.HasValue)
Console.WriteLine($"[MOVE] Walking due to consecutive attacks: {attackCount}");
else
Console.WriteLine($"[ATTK] Killing NPC at player location");
private async Task Walk()
{
var renderProps = _characterRepository.MainCharacter.RenderProperties;
Console.WriteLine($"[WALK] {renderProps.GetDestinationX(),3},{renderProps.GetDestinationY(),3}");
await TrySend(_characterActions.Walk);
await Task.Delay(TimeSpan.FromMilliseconds(WALK_BACKOFF_MS));
}

await Task.Delay(TimeSpan.FromMilliseconds(300));
private async Task Face(EODirection direction)
{
Console.WriteLine($"[FACE] {Enum.GetName(typeof(EODirection), direction),7}");
await TrySend(() => _characterActions.Face(direction.Opposite()));

var direction = charRenderProps.Direction;
charActions.Walk();
await Task.Delay(TimeSpan.FromMilliseconds(WALK_BACKOFF_MS));
// todo: character actions Face() should also change the character's direction instead of relying on client to update it separately
_characterRepository.MainCharacter = _characterRepository.MainCharacter
.WithRenderProperties(_characterRepository.MainCharacter.RenderProperties.WithDirection(direction.Opposite()));

charActions.Face(direction.Opposite());
// 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));
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));
private async Task PickUpItems(IMapCellState cellState)
{
foreach (var item in cellState.Items)
{
Console.WriteLine($"[TAKE] {item.Amount,7} - {_itemData.Data.Single(x => x.ID == item.ItemID).Name}");
await TrySend(() => _mapActions.PickUpItem(item));

handler.PollForPacketsAndHandle();
currentCellState = mapCellStateProvider.GetCellStateAt(charRenderProps.MapX, charRenderProps.MapY);
}
await Task.Delay(TimeSpan.FromMilliseconds(100));
}
}

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);
}
}
private async Task CastHealSpell(IEnumerable<IInventorySpell> healSpells)
{
var spellToUse = _spellData.Data
.Where(x => healSpells.Any(y => y.ID == x.ID) && x.Target != SpellTarget.Group)
.OrderByDescending(x => x.HP)
.First();

charActions.Walk();
await Task.Delay(TimeSpan.FromMilliseconds(WALK_BACKOFF_MS));
Console.WriteLine($"[CAST] {spellToUse.HP,4} HP - {spellToUse.Name}");

charActions.Face(direction);
charRepo.MainCharacter = charRepo.MainCharacter.WithRenderProperties(charRepo.MainCharacter.RenderProperties.WithDirection(direction));
await Task.Delay(TimeSpan.FromMilliseconds(FACE_BACKOFF_MS));
await TrySend(() => _characterActions.PrepareCastSpell(spellToUse.ID));
await Task.Delay((int)Math.Round(spellToUse.CastTime / 2.0 * 950)); // ?

attackCount = 0;
await TrySend(() => _characterActions.CastSpell(spellToUse.ID, _characterRepository.MainCharacter));
await Task.Delay((int)Math.Round(spellToUse.CastTime / 2.0 * 950)); // ?
}

private async Task UseHealItem(IEnumerable<IInventoryItem> healItems, double targetHealthPercent)
{
while (_characterRepository.MainCharacter.Stats[CharacterStat.HP] < _characterRepository.MainCharacter.Stats[CharacterStat.MaxHP] * targetHealthPercent)
{
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})");

await TrySend(() => _characterActions.UseItem(itemToUse.ID));

await Task.Delay(ATTACK_BACKOFF_MS);
}
}

private async Task SitDown()
{
Console.WriteLine($"[SIT ] OVER WEIGHT LIMIT - TIME TO DIE");
await TrySend(_characterActions.ToggleSit);
}

private async Task TrySend(Action action, int attempts = 3)
{
for (int i = 1; i <= attempts; i++)
{
try
{
action();
break;
}
catch (NoDataSentException)
{
Console.WriteLine($"[EX ] {i} / {attempts} - No data sent");
if (i == attempts)
throw;

await Task.Delay(TimeSpan.FromSeconds(1.0 / 30.0));
await Task.Delay(TimeSpan.FromSeconds(i * i));
}
}
}
}
Expand Down
3 changes: 1 addition & 2 deletions EOLib/PacketHandlers/ConnectionPlayerHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,7 @@ public override bool HandlePacket(IPacket packet)
var response = new PacketBuilder(PacketFamily.Connection, PacketAction.Ping).Build();
try
{
_packetSendService.SendPacketAsync(response)
.Wait();
_packetSendService.SendPacket(response);
}
catch (NoDataSentException)
{
Expand Down

0 comments on commit a4748cd

Please sign in to comment.