Rutherther / NosSmooth

Nostale bot library written in C#. May describe the current state of the game/character from packets. Multiple sources available for capturing the packets from running game.
MIT License
6 stars 3 forks source link

Add contract system #49

Closed Rutherther closed 1 year ago

Rutherther commented 1 year ago

Sometimes there is a need to wait for a confirmation or cancellation from NosTale server.

Possible use cases:

  1. skill used
  2. nosmate sent home
  3. put on sp
  4. item dropped - obtain the given GroundItem
  5. item picked up - obtain the slot of the item
  6. nosmate company
  7. group request, response
  8. invite to miniland request, response
  9. join game

I will think about how to write this, but the general idea is to make something called "Contract", that contract will have a condition that must be satisfied. It would be great if that condition could be anything. But realistically event fired and packet received will suffice. For these events and packets there will be conditions. It's possible that some contracts will have multiple conditions and in specific order.

The contracts should have a timeout behavior. In case the server is not responding. It would be great if the timeout was changing depending on the current timeout of other things. If the server is not responding to anything, we probably lost the connection. That means there is no point in cancelling the contract. If the connection was restored, the contract could be met. And if the connection is not restored, nothing can be done anyway.

Possibly even things such as walk and attack commands could be rewritten to use contracts instead of current blocking behavior.

The end goal is to have non-blocking methods whose result will be a contract that you may subscribe to.

Rutherther commented 1 year ago

After this is done, the simple apis should be rewritten to use contracts

Rutherther commented 1 year ago
enum SkillStates {
  None,
  UseSkillSent,
  Casted,
  Restored
}

enum SkillErrors {
  Unknown,
  NoAmmo,
  NoMana,
  Timeout
}

var skillContract = new ContractBuilder<TStates, TErrors, SkillUsedEvent>(contractor)
  .AddMoveGameFilter<SkillUsedEvent>(UseSkillSent, SkillUsed .. for the given vnum, Casted)
  .AddEventAction<SkillUsedEvent>(set)
  .AddErrorPacketFilter(CancelFilter, MapToSkillErrors)
  .AddAction(None, SendPacket(new UseSkill), move to UseSkillSent)

  .SetMoveTimeout(Casted, 1000, Restored)
  .Build();

var skillUsedEventResult = await skillContract
  .Register()
  .Execute()
  .WaitForAsync(Restored);

if (!skillUsedEventResult.IsDefined(out var skillUsedEvent))
{ // there was an error
  return skillUsedEventResult.Error;
}

skillUsedEvent.Target // information about target (hp, id, ...)
Rutherther commented 1 year ago

Revision v2:

enum SkillStates {
  None,
  UseSkillSent,
  Casted,
  Restored
}

enum SkillErrors {
  Unknown,
  NoAmmo,
  NoMana,
  Timeout
}

var skillVNum = 123;
var skillContract = new ContractBuilder<SkillStates, SkillErrors, SkillUsedEvent>(contractor, SkillStates.None)
  .SetMoveFilter<SkillUsedEvent>(UseSkillSent, e => e.Skill.VNum == skillVNum, Casted)
  .SetFillData<SkillUsedEvent>(Casted, e => e)
  .SetError<CancelPacket>(c => SkillErrors.Unknown, MapToSkillErrors)
  .SetMoveAction(SkillStates.None, () => _client.SendPacket(new UseSkillPacket() { ... }), SkillStates.UseSkillSent)

  .SetMoveTimeout(Casted, 1000, Restored) // ?
  .Build();

var skillUsedEventResult = await skillContract
  .WaitForAsync(Restored);

if (!skillUsedEventResult.IsDefined(out var skillUsedEvent))
{ // there was an error
  return skillUsedEventResult.Error;
}

skillUsedEvent.Target // information about target (hp, id, ...)