Skip to content

Commit

Permalink
Documented strategy system. Will add full docs to markdown docs soon.
Browse files Browse the repository at this point in the history
  • Loading branch information
Rodi Degirmenci committed May 13, 2020
1 parent 4934445 commit 13e8d11
Show file tree
Hide file tree
Showing 7 changed files with 186 additions and 1 deletion.
16 changes: 16 additions & 0 deletions RBot/Strategy/BuyItemStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,27 @@

namespace RBot.Strategy
{
/// <summary>
/// Buys an item from a shop to obtain it.
/// </summary>
public class BuyItemStrategy : ItemStrategy
{
public const int DefaultBuyPreference = 3;

/// <summary>
/// The map to join before loading the shop to buy the item.
/// </summary>
public string Map { get; set; }
/// <summary>
/// The ID of the shop to load and buy the item from.
/// </summary>
public int ShopID { get; set; }
public override int Preference => DefaultBuyPreference;

/// <summary>
/// Executes the strategy by joining the map (if it's not null), loading the shop, and buying the specified item the required number of times.
/// </summary>
/// <returns>True if the player's inventory contains the required quantity of the given item after the strategy has executed.</returns>
public override bool Execute(ScriptInterface bot, int required)
{
if (Map != null)
Expand Down
14 changes: 14 additions & 0 deletions RBot/Strategy/DropStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,28 @@

namespace RBot.Strategy
{
/// <summary>
/// Obtains an item by hunting a monster or monsters that drop it.
/// </summary>
public class DropStrategy : ItemStrategy
{
public const int DropDefaultPreference = 10;

/// <summary>
/// The map where the monsters dropping the item reside.
/// </summary>
public string Map { get; set; }
/// <summary>
/// The name(s) of the monster(s) which drop the item. The names are separated by a | character.
/// </summary>
public string Monsters { get; set; }
public override int Preference => DropDefaultPreference;

/// <summary>
/// Executes the drop strategy by joining the map and using HuntForItems combining the DropAggregate list and the item this strategy is meant to obtain.
/// </summary>
/// <param name="required">The number of the item to acquire.</param>
/// <returns>True always as HuntForItems' reliability is assumed.</returns>
public override bool Execute(ScriptInterface bot, int required)
{
bot.Strategy.GetNavigator(Map).Navigate(bot);
Expand Down
26 changes: 26 additions & 0 deletions RBot/Strategy/ItemStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,48 @@

namespace RBot.Strategy
{
/// <summary>
/// A class with virtual methods that is used to define a strategy which is followed to obtain a given item in a given quantity.
/// </summary>
public class ItemStrategy
{
/// <summary>
/// The name of the item that this strategy obtains.
/// </summary>
public string Item { get; set; }
/// <summary>
/// Whether or not the item obtained by this strategy is a temporary item.
/// </summary>
public bool TempItem { get; set; }
/// <summary>
/// The preference of this strategy. Strategies with higher preferences are chosen first.
/// </summary>
public virtual int Preference { get; } = 0;

/// <summary>
/// Checks whether this strategy can be used in the given circumstances.
/// THIS METHOD IS CURRENTLY UNUSED.
/// </summary>
/// <returns>True if this strategy can be used, false otherwise.</returns>
public virtual bool CanUse(ScriptInterface bot)
{
return true;
}

/// <summary>
/// Called to execute this strategy and obtain the required item in the required quantity.
/// </summary>
/// <param name="required">The quantity of the item to obtain.</param>
/// <returns>True if the item was successfully obtained, false otherwise.</returns>
public virtual bool Execute(ScriptInterface bot, int required)
{
return false;
}

/// <summary>
/// Gets a list of required items for this strategy. The list returned is used to build the drop aggregate when StrategyDatabase#AggregateDrops is called.
/// </summary>
/// <returns>A list of items to add to the drop aggregate.</returns>
public virtual List<string> GetRequiredItems(ScriptInterface bot) => new List<string>();
}
}
14 changes: 14 additions & 0 deletions RBot/Strategy/MergeItemStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,18 @@

namespace RBot.Strategy
{
/// <summary>
/// Obtains an item by buying it from a merge shop.
/// This strategy recursively calls StrategyDatabase#Obtain to obtain merge requirements.
/// </summary>
public class MergeItemStrategy : BuyItemStrategy
{
/// <summary>
/// Executes the merge item strategy by joining the map (if it's not null), loading the merge shop, and buying the specified merge item until the required amount is in the player's inventory.
/// </summary>
/// <param name="bot"></param>
/// <param name="required"></param>
/// <returns></returns>
public override bool Execute(ScriptInterface bot, int required)
{
if (Map != null)
Expand All @@ -34,6 +44,10 @@ public override bool Execute(ScriptInterface bot, int required)
return bot.Inventory.Contains(Item, required);
}

/// <summary>
/// Gets a list of items required to merge the item this strategy obtains.
/// </summary>
/// <returns>The list of required items for this merge.</returns>
public override List<string> GetRequiredItems(ScriptInterface bot)
{
return bot.Strategy.GetCachedMerge(ShopID, Item)?.Requirements.Select(x => x.Name).ToList() ?? new List<string>();
Expand Down
21 changes: 21 additions & 0 deletions RBot/Strategy/Navigator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,33 @@

namespace RBot.Strategy
{
/// <summary>
/// An interface implemented to perform custom navigation logic to transfer a player to a map.
/// </summary>
public interface INavigator
{
/// <summary>
/// This method should implement the logic to transfer the player to a map.
/// </summary>
void Navigate(ScriptInterface bot);
}

/// <summary>
/// A default navigator implementation that simply calls ScriptPlayer#Join to join the given map.
/// </summary>
public class DefaultNavigator : INavigator
{
/// <summary>
/// The name of the map to join.
/// </summary>
public string Map { get; set; }
/// <summary>
/// The name of the cell to jump to when the map is joined.
/// </summary>
public string Cell { get; set; }
/// <summary>
/// The name of the pad to jump to when the map is joined.
/// </summary>
public string Pad { get; set; }

public DefaultNavigator(string map, string cell = "Enter", string pad = "Spawn")
Expand All @@ -24,6 +42,9 @@ public DefaultNavigator(string map, string cell = "Enter", string pad = "Spawn")
Pad = pad;
}

/// <summary>
/// Calls ScriptPlayer#Join in order to join the given map.
/// </summary>
public void Navigate(ScriptInterface bot)
{
bot.Player.Join(Map, Cell, Pad);
Expand Down
18 changes: 18 additions & 0 deletions RBot/Strategy/QuestStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,26 @@

namespace RBot.Strategy
{
/// <summary>
/// Obtains an item by completing a quest of which it is a reward.
/// This strategy recursively calls StrategyDatabase#Obtain to fulfil the quest's completion requirements.
/// </summary>
public class QuestStrategy : ItemStrategy
{
public const int DefaultQuestPreference = 5;

public override int Preference => DefaultQuestPreference;
/// <summary>
/// The ID of the quest to complete to obtain the item.
/// </summary>
public int QuestID { get; set; }

/// <summary>
/// Executes this strategy by obtaining the items required to turn in the quest and then turning in the quest until the required quantity of the item is obtained.
/// This strategy calls StrategyDatabase#PickupAggregate every time the quest is turned in.
/// </summary>
/// <param name="required">The quantity of the item to obtain by completing the quest.</param>
/// <returns>False if the quest cannot be accepted (if the player does not have the required items to accept it) or if there is no strategy registered to obtain a requirement or if the registered strategy fails for any requirement. True otherwise.</returns>
public override bool Execute(ScriptInterface bot, int required)
{
Quest q = bot.Strategy.GetCachedQuest(QuestID);
Expand Down Expand Up @@ -43,6 +57,10 @@ public override bool Execute(ScriptInterface bot, int required)
return true;
}

/// <summary>
/// Gets a list of items required to turn in the quest.
/// </summary>
/// <returns>The list of items required to turn in the quest.</returns>
public override List<string> GetRequiredItems(ScriptInterface bot)
{
return bot.Strategy.GetCachedQuest(QuestID)?.Requirements.Select(x => x.Name).ToList() ?? base.GetRequiredItems(bot);
Expand Down
78 changes: 77 additions & 1 deletion RBot/Strategy/StrategyDatabase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,30 +12,59 @@ namespace RBot.Strategy
{
public class StrategyDatabase : ScriptableObject
{
/// <summary>
/// A dictionary mapping room names to custom navigators. Navigators offer a way of transferring a player to a given room.
/// An example of when to use a custom navigator would be for joining tercessuinotlim as this requires first joining citadel.
/// </summary>
public Dictionary<string, INavigator> Navigators { get; } = new Dictionary<string, INavigator>();
/// <summary>
/// The list of registered item strategies in the database.
/// </summary>
public List<ItemStrategy> ItemStrategies { get; } = new List<ItemStrategy>();

private Dictionary<int, List<ShopItem>> _shops = new Dictionary<int, List<ShopItem>>();
private Dictionary<int, List<MergeItem>> _merges = new Dictionary<int, List<MergeItem>>();
private Dictionary<int, Quest> _quests = new Dictionary<int, Quest>();

/// <summary>
/// A list of drops which is built when AggregateDrops is called. Items in this list are picked up during all strategies' execution.
/// </summary>
public List<string> DropAggregate { get; } = new List<string>();

/// <summary>
/// Gets the navigator to be used to transfer the player to the given map.
/// </summary>
/// <param name="map">The name of the map.</param>
/// <returns>A navigator which has a Navigate method. If no custom navigator is defined, a default navigator which makes a call to ScriptPlayer#Join is returned.</returns>
public INavigator GetNavigator(string map)
{
return Navigators.TryGetValue(map, out INavigator nav) ? nav : new DefaultNavigator(map);
}

/// <summary>
/// Gets the strategy to obtain the given item with the highest priority.
/// </summary>
/// <param name="item">The name of the item to find a strategy for.</param>
/// <returns>The strategy which can be used to obtain the given item. If no such strategy exists, null is returned.</returns>
public ItemStrategy GetStrategy(string item)
{
return ItemStrategies.FindAll(x => x.Item.Equals(item, StringComparison.OrdinalIgnoreCase)).OrderByDescending(x => x.Preference).FirstOrDefault();
}

/// <summary>
/// Adds the given strategy to the database.
/// </summary>
/// <param name="strat">The strategy to add.</param>
public void Register(ItemStrategy strat)
{
ItemStrategies.Add(strat);
}

/// <summary>
/// Loads shop with the given id and registers a BuyItemStrategy for every item in the shop.
/// </summary>
/// <param name="id">The id of the shop to create BuyItemStrategy objects for.</param>
/// <param name="map">The map the player needs to be in to load the shop (prevents disconnects). If this is null, the player will not join a map before loading the shop.</param>
public void RegisterShop(int id, string map = null)
{
if (!_shops.ContainsKey(id))
Expand All @@ -56,6 +85,11 @@ public void RegisterShop(int id, string map = null)
}
}

/// <summary>
/// Loads the given merge shop and registers a MergeItemStrategy for every item in the shop.
/// </summary>
/// <param name="id">The id of the shop to create MergeItemStrategy objects for.</param>
/// <param name="map">The map the player needs to be in to load the shop (prevents disconnects). If this is null, the player will not join a map before loading the shop.</param>
public void RegisterMerge(int id, string map = null)
{
if (!_merges.ContainsKey(id))
Expand All @@ -76,6 +110,10 @@ public void RegisterMerge(int id, string map = null)
}
}

/// <summary>
/// Loads the given quest and registers a QuestStrategy for each reward of the quest.
/// </summary>
/// <param name="id">The id of the quest to register.</param>
public void RegisterQuest(int id)
{
if (!_quests.ContainsKey(id))
Expand All @@ -93,6 +131,13 @@ public void RegisterQuest(int id)
}
}

/// <summary>
/// Registers a DropStrategy with the given parameters.
/// </summary>
/// <param name="map">The name of the map where the monster exists.</param>
/// <param name="monsters">The name of the monster(s) (separated by |) to kill for the drop.</param>
/// <param name="drop">The name of the drop obtained through killing the monsters.</param>
/// <param name="temp">Whether or not the drop is a temporary item.</param>
public void RegisterDrop(string map, string monsters, string drop, bool temp = false)
{
Register(new DropStrategy()
Expand All @@ -104,21 +149,43 @@ public void RegisterDrop(string map, string monsters, string drop, bool temp = f
});
}

/// <summary>
/// Gets a cached shop item. Shop items are cached when RegisterShop is called.
/// </summary>
/// <param name="shop">The id of the shop.</param>
/// <param name="name">The name of the cached item to get.</param>
/// <returns>The cached ShopItem object or null if the item was not cached.</returns>
public ShopItem GetCachedShop(int shop, string name)
{
return _shops.TryGetValue(shop, out List<ShopItem> items) ? items.Find(x => x.Name.Equals(name, StringComparison.OrdinalIgnoreCase)) : null;
}

/// <summary>
/// Gets a cached merge shop item. Merge shop items are cached when RegisterMerge is called.
/// </summary>
/// <param name="shop">The id of the shop.</param>
/// <param name="name">The name of the cached item to get.</param>
/// <returns>The cached MergeItem object or null if the item was not cached.</returns>
public MergeItem GetCachedMerge(int shop, string name)
{
return _merges.TryGetValue(shop, out List<MergeItem> items) ? items.Find(x => x.Name.Equals(name, StringComparison.OrdinalIgnoreCase)) : null;
}


/// <summary>
/// Gets a cached Quest object. Quests are cached when RegisterQuest is called.
/// </summary>
/// <param name="id">The id of the quest.</param>
/// <returns>The cached Quest object or null if the quest has not been cached.</returns>
public Quest GetCachedQuest(int id)
{
return _quests.TryGetValue(id, out Quest q) ? q : null;
}

/// <summary>
/// Aggregates all drops based on all strategies that will be used to obtain the given item.
/// This should be called before Obtain for maximum efficiency. The drop names that are aggregated are picked up (through PickupAggregate) during all strategies' execution.
/// </summary>
/// <param name="item"></param>
public void AggregateDrops(string item)
{
DropAggregate.Clear();
Expand All @@ -127,13 +194,22 @@ public void AggregateDrops(string item)
DropAggregate.AddRange(strat.GetRequiredItems(Bot));
}

/// <summary>
/// Picks up all aggregated drop names in the list generated by AggregateDrops. This is called automatically during each strategy's execution and does not need to be called manually.
/// </summary>
public void PickupAggregate()
{
string[] drops = Bot.Player.CurrentDrops.Where(x => DropAggregate.Find(y => y.Equals(x, StringComparison.OrdinalIgnoreCase)) != null).ToArray();
Bot.Player.Pickup(drops);
Bot.Player.RejectExcept(drops);
}

/// <summary>
/// Obtains the specified item in the specified quantity using the registered strategies with the highest priorities.
/// </summary>
/// <param name="item">The name of the item to obtain.</param>
/// <param name="quantity">The quantity of the item to obtain.</param>
/// <returns>True if the item was successfully obtained through strategies. False if no strategy was found for the given item, or if any intermediate strategy was unsuccessful in its execution.</returns>
public bool Obtain(string item, int quantity)
{
if (Bot.Bank.Contains(item))
Expand Down

0 comments on commit 13e8d11

Please sign in to comment.