A lot of code has broken with the Ascendancy update due to massive internal changes as well as game changes. Documenting all these changes is not currently feasible, because the update took place over 6 weeks, and there were many sets of changes to the same systems. Quite literally, there was about 200 SVN commits, and posting those wouldn't help much.
This thread is for devs whose code is now broken, and they can't figure out how to get started making things compatible. I'll keep a list of commonly recurring things so please read them before asking. I'll try to reply to every post when possible, but there's a lot going on right now, so you might have to wait for certain answers.
Before posting, please check the raw SVN log of changes.
References
Stash Tab Changes
Currency Tabs
Sleeps
CursorItemOverlay
Using Items
This thread is for devs whose code is now broken, and they can't figure out how to get started making things compatible. I'll keep a list of commonly recurring things so please read them before asking. I'll try to reply to every post when possible, but there's a lot going on right now, so you might have to wait for certain answers.
Before posting, please check the raw SVN log of changes.
References
Stash Tab Changes
Due to the GUI changes, the tab control logic is now separate from any GUI that implements it. This means additional logic is required in cases where the old API would take care of it. The most notable example is waiting for a stash tab to be loaded.
In the old code, the NextTab/PrevTab functions contained the logic for waiting for the tab to change, and the contents to be loaded. This is no longer the case, because the tab control logic only handles the tab control itself, not what is inside the tab.
Handling this is pretty straight forward though. The following function, WaitForStashTabChange shows some code the current bot uses:
Here is an example of that coroutine being used:
In the old code, the NextTab/PrevTab functions contained the logic for waiting for the tab to change, and the contents to be loaded. This is no longer the case, because the tab control logic only handles the tab control itself, not what is inside the tab.
Handling this is pretty straight forward though. The following function, WaitForStashTabChange shows some code the current bot uses:
Code:
/// <summary>
/// Waits for a stash tab to change. Pass -1 to lastId to wait for the initial tab.
/// </summary>
/// <param name="lastId">The last InventoryId before changing tabs.</param>
/// <param name="timeout">The timeout of the function.</param>
/// <returns>true if the tab was changed and false otherwise.</returns>
public static async Task<bool> WaitForStashTabChange(int lastId, int timeout=5000)
{
var sw = Stopwatch.StartNew();
var invTab = LokiPoe.InGameState.StashUi.StashTabInfo;
while (invTab == null || invTab.InventoryId == lastId)
{
Log.InfoFormat("[WaitForStashTabChange] Waiting...");
await Coroutine.Sleep(1);
invTab = LokiPoe.InGameState.StashUi.StashTabInfo;
if (sw.ElapsedMilliseconds > timeout)
{
Log.InfoFormat("[WaitForStashTabChange] Timeout...");
return false;
}
}
return true;
}
Here is an example of that coroutine being used:
Code:
// As long as the tab stays the same, we should be able to cache these.
var invTab = LokiPoe.InGameState.StashUi.StashTabInfo;
if (invTab.IsRemoveOnly || invTab.IsPublic)
{
if (invTab.IsRemoveOnly)
Log.DebugFormat("[IdItemsUsingStashCoroutine] The current tab is Remove only. Skipping it.");
else
Log.DebugFormat("[IdItemsUsingStashCoroutine] The current tab is Public. Skipping it.");
var oldId = invTab.InventoryId;
var result = LokiPoe.InGameState.StashUi.TabControl.NextTabKeyboard();
if (result != SwitchToTabResult.None)
{
Log.ErrorFormat("[IdItemsUsingStashCoroutine] NextTabKeyboard returned {0}.", result);
break;
}
await WaitForStashTabChange(oldId);
await ReactionWait();
continue;
}
Currency Tabs
Currency tabs must be handled differently from normal stash tabs. This is because each currency tab is made up of multiple inventory controls, much like the main inventory GUI, while normal stash tabs have only 1 main inventory control.
A currency tab can be detected by checking LokiPoe.InGameState.StashUi.StashTabInfo.IsPremiumCurrency.
If true, the InventoryControl property should not be used to access items, because it is null. Instead, the currency tab inventory controls, such as InventoryControl_ScrollOfWisdom or InventoryControl_Misc1. Each currency tab inventory only interacts with 1 specific item, but typically contains most of the other items, which should not be used because the items they reference are not always correct.
For example, if you use the InventoryControl_ScrollOfWisdom, you should lookup and only use Scroll of Wisdom items. Other items will be referenced, but they cannot be correctly used in the API, To get the item, you can access the CurrencyTabItem property.
API functions that do not use item ids are specifically for currency tab items, because they only hold one item. Users must use these variants of API functions as the others are coded to not work. For example, in a normal stash inventory, you'd call FastMove with the id of the item to move. For currency tab inventories, you'd simply call the FastMove function without the item id.
Lastly, all features of currency tabs should be supported due to the generic nature of the inventory control. This means there is support for the 5 misc slots and the utility slot for faster currency use, although it's not really needed since the API can perform the actions for you just the same.
A currency tab can be detected by checking LokiPoe.InGameState.StashUi.StashTabInfo.IsPremiumCurrency.
If true, the InventoryControl property should not be used to access items, because it is null. Instead, the currency tab inventory controls, such as InventoryControl_ScrollOfWisdom or InventoryControl_Misc1. Each currency tab inventory only interacts with 1 specific item, but typically contains most of the other items, which should not be used because the items they reference are not always correct.
For example, if you use the InventoryControl_ScrollOfWisdom, you should lookup and only use Scroll of Wisdom items. Other items will be referenced, but they cannot be correctly used in the API, To get the item, you can access the CurrencyTabItem property.
API functions that do not use item ids are specifically for currency tab items, because they only hold one item. Users must use these variants of API functions as the others are coded to not work. For example, in a normal stash inventory, you'd call FastMove with the id of the item to move. For currency tab inventories, you'd simply call the FastMove function without the item id.
Lastly, all features of currency tabs should be supported due to the generic nature of the inventory control. This means there is support for the 5 misc slots and the utility slot for faster currency use, although it's not really needed since the API can perform the actions for you just the same.
Sleeps
The previous bot implementation used a lot of arbitrary waits and rough latency bound values. This contributed to a lot of unnecessary delays and slow performance from the bot. That system has been remodeled around a latency wait, and then a reaction wait.
A latency wait, is a wait that is based on the current average latency times a constant factor, currently 1.5. The current coroutine, LatencyWait is implemented as follows:
This coroutine (or similar) should be called after any input action that results in the client needing a response from the server. This is to help prevent logic from executing too soon, although users should still implement additional checking to see if the action has actually completed.
A reaction wait is a wait that is based around average human reaction time. For example, on average, a person can only click 4-5 times per second, and has an average reaction time anywhere from 200-300+ms. There are several factors that influence this, such as computer hardware, but it's all treated the same. The current coroutine ReactionWait is implemend as follows:
This coroutine is to help avoid the bot from having a bot-like reaction time to everything. While the bot itself can be made to operate at a super fast, non-human rate, that might result in it being easily detected on the server, so some reasonable slowdowns are added.
A latency wait, is a wait that is based on the current average latency times a constant factor, currently 1.5. The current coroutine, LatencyWait is implemented as follows:
Code:
/// <summary>
/// Performs a random sleep baed on the current latency average.
/// </summary>
/// <param name="factor">A scaling factor to apply to the average latency.</param>
/// <returns>The number of ms slept.</returns>
public static async Task<int> LatencyWait(float factor = 1.5f)
{
var sleep = LatencyTracker.Average * factor;
Log.InfoFormat("[LatencyWait] Now sleeping {0} ms.", sleep);
await Coroutine.Sleep(sleep);
return sleep;
}
This coroutine (or similar) should be called after any input action that results in the client needing a response from the server. This is to help prevent logic from executing too soon, although users should still implement additional checking to see if the action has actually completed.
A reaction wait is a wait that is based around average human reaction time. For example, on average, a person can only click 4-5 times per second, and has an average reaction time anywhere from 200-300+ms. There are several factors that influence this, such as computer hardware, but it's all treated the same. The current coroutine ReactionWait is implemend as follows:
Code:
private static int _minSleepDelay = 250;
private static int _maxSleepDelay = 300;
/// <summary>
/// The min sleep delay for v3 coroutines. The minimal value is 100
/// </summary>
public static int ReactionMinSleepDelay
{
get
{
return _minSleepDelay;
}
set
{
_minSleepDelay = value;
if (_minSleepDelay >= _maxSleepDelay)
_minSleepDelay = _maxSleepDelay - 1;
if (_minSleepDelay < 100)
_minSleepDelay = 100;
Log.InfoFormat("ReactionMinSleepDelay = {0}", _minSleepDelay);
}
}
/// <summary>
/// The max sleep delay for v3 coroutines. The minimal value is 101 ms.
/// </summary>
public static int ReactionMaxSleepDelay
{
get
{
return _maxSleepDelay;
}
set
{
_maxSleepDelay = value;
if (_maxSleepDelay <= _minSleepDelay)
_maxSleepDelay = _minSleepDelay + 1;
if (_maxSleepDelay < 101)
_maxSleepDelay = 101;
Log.InfoFormat("ReactionMaxSleepDelay = {0}", _maxSleepDelay);
}
}
/// <summary>
/// Performs a random sleep to simulate reaction time.
/// </summary>
/// <returns>The number of ms slept.</returns>
public static async Task<int> ReactionWait()
{
var sleep = LokiPoe.Random.Next(ReactionMinSleepDelay, ReactionMaxSleepDelay);
Log.InfoFormat("[ReactionWait] Now sleeping {0} ms.", sleep);
await Coroutine.Sleep(sleep);
return sleep;
}
This coroutine is to help avoid the bot from having a bot-like reaction time to everything. While the bot itself can be made to operate at a super fast, non-human rate, that might result in it being easily detected on the server, so some reasonable slowdowns are added.
CursorItemOverlay
The cursor item system has been greatly reworked to support a lot of new things in a much better way than before.
To get started, users should check LokiPoe.InGameState.CursorItemOverlay.Mode to get the current cursor item operation. Then, LokiPoe.InGameState.CursorItemOverlay.Item can be checked to see if there's still an item being referenced for the operation. If that is null, it means a multi-use item operation is in process, so you should have kept track of the item being used already.
The InventoryControlWrapper ApplyCursorTo and ApplyCursorAt functions take this into consideration already, so end users just need to code their logic around properly implementing the item applying process.
To get started, users should check LokiPoe.InGameState.CursorItemOverlay.Mode to get the current cursor item operation. Then, LokiPoe.InGameState.CursorItemOverlay.Item can be checked to see if there's still an item being referenced for the operation. If that is null, it means a multi-use item operation is in process, so you should have kept track of the item being used already.
The InventoryControlWrapper ApplyCursorTo and ApplyCursorAt functions take this into consideration already, so end users just need to code their logic around properly implementing the item applying process.
Using Items
The design of using items on other items has changed. This is because of the change to a more generic API design that allows more functionality than before.
One common request in the past has been to properly support shift click using currency items to speed up the process. That is now properly supported. Likewise, the design change allows for things such as using currency items on stash tab items, which means support for using the currency tab utility slot is supported by default.
Here's a code snippet from the IdItemsUsingInventoryCoroutine.
While that code still performs a single item use operation to id items, users can instead start a multiuse operation by passing true to BeginApplyCursor, then calling ApplyCursorTo to each item they want to use cursor on.
One common request in the past has been to properly support shift click using currency items to speed up the process. That is now properly supported. Likewise, the design change allows for things such as using currency items on stash tab items, which means support for using the currency tab utility slot is supported by default.
Here's a code snippet from the IdItemsUsingInventoryCoroutine.
Code:
var err1 = LokiPoe.InGameState.InventoryUi.InventoryControl_Main.UseItem(sow.LocalId);
if (err1 != UseItemResult.None)
{
Log.InfoFormat("[IdItemsUsingInventoryCoroutine] UseItem returned {0}.", err1);
return IdItemsCoroutineError.UseItemFailed;
}
await LatencyWait();
await ReactionWait();
var err = InventoryControlWrapper.BeginApplyCursor();
if (err != ApplyCursorResult.None)
{
Log.InfoFormat("[IdItemsUsingInventoryCoroutine] BeginApplyCursor returned {0}.", err);
return IdItemsCoroutineError.UseItemFailed;
}
err = LokiPoe.InGameState.InventoryUi.InventoryControl_Main.ApplyCursorTo(item.LocalId);
if (err != ApplyCursorResult.None)
{
Log.InfoFormat("[IdItemsUsingInventoryCoroutine] ApplyCursorTo returned {0}.", err);
}
await LatencyWait();
await ReactionWait();
var err2 = InventoryControlWrapper.EndApplyCursor();
if (err2 != ApplyCursorResult.None)
{
Log.InfoFormat("[IdItemsUsingInventoryCoroutine] EndApplyCursor returned {0}.", err2);
return IdItemsCoroutineError.UseItemFailed;
}
if (err != ApplyCursorResult.None)
{
return IdItemsCoroutineError.UseItemFailed;
}
While that code still performs a single item use operation to id items, users can instead start a multiuse operation by passing true to BeginApplyCursor, then calling ApplyCursorTo to each item they want to use cursor on.
Last edited: