I created this code to be able to cache certain values within the game client with my out-of-process multi-boxing project. I found that my AI was doing an awful lot of the same reads to determine which of the characters were mounted, which ones were casting spells, all within a very narrow time period. Because of these many reads, the frame rate could get quite horrid at times, and the CPU usage of my multi-boxing application would spike, not very high, but high enough that I wanted to mitigate any impact it might have in the future.
Not all values within the game client need to be cached, I just do the most frequently accessed ones such as spell cool downs, auras, spell casting, is mounted, is sitting, etc. These cached values are checked many times per update loop by the AI, usually within a very short period of time.
Most people are not going to find a use for this code, but if you are having issues with your out-of-process bot doing too many reads at one time and slowing everything down, or if you want to multi-box more than five characters on a single workstation and remain out-of-process then this code can help with the amount of game client memory reads.
Code:
using System;
namespace WarcraftVoodoo
{
/// <summary>
/// This delegate writes a value immediately to the game client and updates the
/// last time that the value was refreshed.
/// </summary>
/// <typeparam name="T">The defined type that the memory writer delegate takes.</typeparam>
/// <param name="value">The value to write to the game client memory.</param>
public delegate void CachedWriterCallback<T>(T value);
/// <summary>
/// This delegate either reads a value from the game client or the cache
/// depending on whether the last read occured within a defined time period.
/// </summary>
/// <typeparam name="T">The defined type that the memory reader delegate returns.</typeparam>
/// <returns>The value read by the reader function either from the game client memory or the cached version.</returns>
public delegate T CachedReaderCallback<T>();
/// <summary>
/// This structure maintains a cached value for the game client.
/// Its purpose is to reduce the amount of reading of the game client memory
/// that needs to take place on a moment to moment basis, especially if the
/// same memory location is checked multiple times within the AI framework.
/// </summary>
/// <typeparam name="T"></typeparam>
public struct CachedValue<T>
{
// The default interval of time that must elapse before the cached value is refreshed.
public static readonly TimeSpan DefaultUpdateInterval = TimeSpan.FromMilliseconds(50);
/// <summary>
/// The update interval of time that must elapse before the cached value is refreshed.
/// </summary>
public TimeSpan UpdateInterval;
/// <summary>
/// The delegate that handles reading of the client memory.
/// </summary>
public CachedReaderCallback<T> m_reader;
/// <summary>
/// The delegate that handles writing of the client memory.
/// </summary>
public CachedWriterCallback<T> m_writer;
// The value that was last read from the game client and is currently being cached.
private T m_value;
/// The last time the cached value was refreshed
private DateTime m_lastUpdate;
/// <summary>
/// Constructor that takes a reader delegate.
/// </summary>
/// <param name="reader">The delegate that performs reading of the value from the game client.</param>
public CachedValue(CachedReaderCallback<T> reader)
: this()
{
UpdateInterval = DefaultUpdateInterval;
m_lastUpdate = DateTime.MinValue;
m_reader = reader;
m_writer = null;
}
/// <summary>
/// Constructor that takes both a reader and a writer delegate.
/// </summary>
/// <param name="reader">The delegate that performs reading of the value from the game client.</param>
/// <param name="writer">The delegate that performs writing of the value to the game client.</param>
public CachedValue(CachedReaderCallback<T> reader, CachedWriterCallback<T> writer)
: this()
{
UpdateInterval = DefaultUpdateInterval;
m_lastUpdate = DateTime.MinValue;
m_reader = reader;
m_writer = writer;
}
/// <summary>
/// Get/set the cached value in the game client.
/// Set will immediately write the value to the game client. Get will read
/// from the cached value if the amount of time elapsed since the last read
/// is within the update interval.
/// </summary>
public T Value
{
set
{
// If no writer delegate has been set, throw an error
// otherwise, invoke the writer delegate, update the locally cached
// value and update when the last time the cached value was refreshed
if (m_writer == null)
{
throw new InvalidOperationException("Delegate for writing operation has not been set.");
}
m_writer(value);
m_value = value;
m_lastUpdate = DateTime.Now;
}
get
{
// if no reader delegate has been set, throw an error
// otherwise, check the last cache refresh time
// if the last cache refresh time is within the update interval
// return the cached value
// otherwise, invoke the reader delegate and update the last cached time.
if (m_reader == null)
{
throw new InvalidOperationException("Delegate for reading operation has not been set.");
}
if ((DateTime.Now - m_lastUpdate) < UpdateInterval)
{
return m_value;
}
m_value = m_reader();
m_lastUpdate = DateTime.Now;
return m_value;
}
}
}
}
This is how I use the above code to read values from the game client:
Code:
// declare a member variable that needs to be cached
private CachedValue<bool> m_isMounted;
Code:
// set up the reader delegate for the cached value
m_isMounted = new CachedValue<bool>(() =>
{
if (ReadOffset<int>(UnitOffset.IsMounted1) <= 0)
{
return false;
}
if ((ReadOffset<int>(UnitOffset.IsMounted2) & 0x10000000) != 0)
{
return false;
}
return true;
});
Make use of the cached value in my code:
Code:
return m_isMounted.Value;
Another example using a more complex type:
Code:
m_actionButtons = new CachedValue<ActionButton[]>(() =>
{
return GetAllActionButtons();
});
where ActionButton is a structure that contains information about the buttons found on the action bar and the function GetAllActionButtons() returns an array of ActionButton structures.