I've updated to old gem leveler to be compatible with the new API. All the logic remains the same. The only difference is that you will see your inventory window opening when comes the time to level up a gem.
Create a new folder called GemLeveler inside {your Exilebuddy folder}/plugins/
Inside this folder, create these files:
ConfigWindow.cs
ConfigWindow.Designer.cs
GemLeveler.cs
GemLevelerSettings.cs
SettingsGui.xaml
Report errors if you find some, I didn't test this too long.
Also, I would like feedback on this piece of code, at the very end of GemLeveler.cs. Is there a better way to proceed?

Create a new folder called GemLeveler inside {your Exilebuddy folder}/plugins/
Inside this folder, create these files:
ConfigWindow.cs
Code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using Loki.Game;
using Loki.Game.Inventory;
using Loki.Game.Objects.Items;
using Loki.Game.GameData;
using Loki.Game.Objects;
namespace GemLeveler
{
public partial class ConfigWindow : Form
{
public ConfigWindow()
{
InitializeComponent();
ReloadAllGems();
dataGridView1.DataSource = GemLevelerSettings.Instance.EnabledGems;
}
private void ReloadAllGems()
{
using (LokiPoe.AcquireFrame())
{
// Only allow this if we've cleared the list.
if (GemLevelerSettings.Instance.EnabledGems.Count != 0)
return;
IEnumerable<Inventory> inventories = GemLeveler.UsableInventories;
foreach (Inventory inv in inventories)
{
InventoryType type = inv.PageType;
Item item = inv.Items.FirstOrDefault();
// Either nothing equipped, or a 2H + nothing combo
if (item == null)
{
continue;
}
var socketedItem = item as SocketableItem;
// Quivers and whatnot
if (socketedItem == null)
{
continue;
}
foreach (SkillGem gem in socketedItem.SocketedGems)
{
if (gem == null)
continue;
var g = new GemLevelerSettings.EnabledGem {PageType = type, GemName = gem.Name, Enabled = true};
GemLevelerSettings.Instance.EnabledGems.Add(g);
}
}
GemLevelerSettings.Instance.Save();
}
}
private void HandleReloadAllGemsButtonClick(object sender, EventArgs e)
{
dataGridView1.DataSource = null;
// Clear the list first, then reload the known gems
GemLevelerSettings.Instance.EnabledGems.Clear();
ReloadAllGems();
dataGridView1.DataSource = GemLevelerSettings.Instance.EnabledGems;
}
}
}
ConfigWindow.Designer.cs
Code:
namespace GemLeveler
{
partial class ConfigWindow
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.label1 = new System.Windows.Forms.Label();
this.textBox1 = new System.Windows.Forms.TextBox();
this.label2 = new System.Windows.Forms.Label();
this.dataGridView1 = new System.Windows.Forms.DataGridView();
this.enabledGemBindingSource = new System.Windows.Forms.BindingSource(this.components);
this.button1 = new System.Windows.Forms.Button();
((System.ComponentModel.ISupportInitialize)(this.dataGridView1)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.enabledGemBindingSource)).BeginInit();
this.SuspendLayout();
//
// label1
//
this.label1.AutoSize = true;
this.label1.Location = new System.Drawing.Point(12, 9);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(113, 13);
this.label1.TabIndex = 0;
this.label1.Text = "Globally Ignored Gems";
//
// textBox1
//
this.textBox1.Location = new System.Drawing.Point(12, 25);
this.textBox1.Multiline = true;
this.textBox1.Name = "textBox1";
this.textBox1.Size = new System.Drawing.Size(187, 341);
this.textBox1.TabIndex = 1;
//
// label2
//
this.label2.AutoSize = true;
this.label2.Location = new System.Drawing.Point(210, 9);
this.label2.Name = "label2";
this.label2.Size = new System.Drawing.Size(137, 13);
this.label2.TabIndex = 2;
this.label2.Text = "Enabled by Socket && Name";
//
// dataGridView1
//
this.dataGridView1.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
this.dataGridView1.Location = new System.Drawing.Point(205, 25);
this.dataGridView1.Name = "dataGridView1";
this.dataGridView1.Size = new System.Drawing.Size(402, 341);
this.dataGridView1.TabIndex = 3;
//
// button1
//
this.button1.Location = new System.Drawing.Point(497, 372);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(110, 23);
this.button1.TabIndex = 4;
this.button1.Text = "Reload all Gems";
this.button1.UseVisualStyleBackColor = true;
this.button1.Click += new System.EventHandler(this.HandleReloadAllGemsButtonClick);
//
// ConfigWindow
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(619, 404);
this.Controls.Add(this.button1);
this.Controls.Add(this.dataGridView1);
this.Controls.Add(this.label2);
this.Controls.Add(this.textBox1);
this.Controls.Add(this.label1);
this.Name = "ConfigWindow";
this.Text = "Gem Leveler Config";
((System.ComponentModel.ISupportInitialize)(this.dataGridView1)).EndInit();
((System.ComponentModel.ISupportInitialize)(this.enabledGemBindingSource)).EndInit();
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.Label label1;
private System.Windows.Forms.TextBox textBox1;
private System.Windows.Forms.Label label2;
private System.Windows.Forms.DataGridView dataGridView1;
private System.Windows.Forms.BindingSource enabledGemBindingSource;
private System.Windows.Forms.Button button1;
}
}
GemLeveler.cs
Code:
using System;
using System.Collections.Generic;
using System.Linq;
using log4net;
using Loki.Game;
using Loki.Game.Inventory;
using Loki.Game.Objects.Items;
using Loki.Utilities;
using Loki.Game.GameData;
using Loki.Bot;
using Loki.Game.Objects;
using System.Windows.Controls;
using System.IO;
using System.Windows.Markup;
using System.Windows;
using System.Threading.Tasks;
using System.Threading;
namespace GemLeveler
{
public class GemLeveler : IPlugin
{
#region Implementation of IEquatable<IPlugin>
/// <summary>
/// Indicates whether the current object is equal to another object of the same type.
/// </summary>
/// <returns>
/// true if the current object is equal to the <paramref name="other" /> parameter; otherwise, false.
/// </returns>
/// <param name="other">An object to compare with this object.</param>
public bool Equals(IPlugin other)
{
return Name.Equals(other.Name);
}
#endregion
private static readonly ILog Log = Logger.GetLoggerInstanceForType();
private const bool DebugStatements = false;
private readonly WaitTimer _levelWait = WaitTimer.FiveSeconds;
private readonly Random _random = new Random();
#region Implementation of IPlugin
public string Author
{
get { return "Apoc"; }
}
public Version Version
{
get { return new Version(0, 1, 0, 0); }
}
public string Name
{
get { return "Gem Leveler"; }
}
public string Description
{
get { return "Levels gems as they become available for... leveling!"; }
}
/// <summary> Executes the pulse action. This is called every "tick" of the bot. </summary>
public void Tick()
{
// We never want to update gems while stash is open, because we access all inventories, which causes
// more stash pages to be requested than we want.
if (LokiPoe.InGameState.StashPanel.IsOpened)
return;
// Sooo... yeah...
// We basically only want to level a gem every couple of seconds.
// Anywhere between 1 and 3 seconds seems fine.
if (_levelWait.IsFinished)
{
_levelWait.Reset(TimeSpan.FromMilliseconds(_random.Next(1000, 3000)));
LevelGems();
}
}
/// <summary>Initializes this plugin.</summary>
public void Initialize()
{
Log.DebugFormat("[GemLeveler] Initialize");
}
/// <summary> The plugin start callback. Do any initialization here. </summary>
public void Start()
{
Log.DebugFormat("[GemLeveler] Start");
}
/// <summary> The plugin stop callback. Do any pre-dispose cleanup here. </summary>
public void Stop()
{
Log.DebugFormat("[GemLeveler] Stop");
}
public JsonSettings Settings
{
get { return GemLevelerSettings.Instance; }
}
/// <summary> The routine's settings control. This will be added to the Exilebuddy Settings tab.</summary>
public UserControl Control
{
get
{
using (var fs = new FileStream(@"Plugins\GemLeveler\SettingsGui.xaml", FileMode.Open))
{
var root = (UserControl)XamlReader.Load(fs);
// Your settings binding here.
Button openWindowButton = (Button)LogicalTreeHelper.FindLogicalNode(root, "OpenGemLevelerButton");
if (openWindowButton == null)
{
Log.DebugFormat(
"[SettingsControl] SetupButtonBinding failed for 'OpenGemLevelerButton'.");
throw new Exception("The SettingsControl could not be created.");
}
openWindowButton.Click += openGemLevelerWindow;
// Your settings event handlers here.
return root;
}
}
}
public void openGemLevelerWindow(object sender, RoutedEventArgs e)
{
Log.DebugFormat("[GemLeveler] click OpenGemLevelerButton");
ConfigWindow gemLevelerWindow = new ConfigWindow();
gemLevelerWindow.Show();
}
/// <summary> The plugin is being enabled.</summary>
public void OnEnable()
{
Log.DebugFormat("[GemLeveler] OnEnable");
}
/// <summary> The plugin is being disabled.</summary>
public void OnDisable()
{
Log.DebugFormat("[GemLeveler] OnDisable");
}
#endregion
#region Implementation of IDisposable
/// <summary> </summary>
public void Dispose()
{
}
#endregion
#region Override of Object
/// <summary>
///
/// </summary>
/// <returns></returns>
public override string ToString()
{
return Name + ": " + Description;
}
#endregion
public static IEnumerable<Inventory> UsableInventories
{
get
{
return new[]
{
LokiPoe.InGameState.InventoryPanel.LeftHandInventory,
LokiPoe.InGameState.InventoryPanel.RightHandInventory,
LokiPoe.InGameState.InventoryPanel.OffLeftHandInventory,
LokiPoe.InGameState.InventoryPanel.OffRightHandInventory,
LokiPoe.InGameState.InventoryPanel.HeadInventory,
LokiPoe.InGameState.InventoryPanel.ChestInventory,
LokiPoe.InGameState.InventoryPanel.GlovesInventory,
LokiPoe.InGameState.InventoryPanel.BootsInventory,
LokiPoe.InGameState.InventoryPanel.LeftRingInventory,
LokiPoe.InGameState.InventoryPanel.RightRingInventory,
};
}
}
private void LevelGems()
{
var me = LokiPoe.ObjectManager.Me;
// We need these stats for the "can level" checks.
var myS = me.GetStat(StatType.Strength);
var myD = me.GetStat(StatType.Dexterity);
var myI = me.GetStat(StatType.Intelligence);
var myL = me.Level;
foreach (var Inventory in UsableInventories)
{
var pageType = Inventory.PageType;
// Now, for each item, we need to check all the available skill gems.
// There's a max of 6, so we can simply iterate 6 :)
foreach (Item inventoryItem in Inventory.Items)
{
if (inventoryItem == null)
continue;
// This is a base-class of Weapon and Armor. This is exactly why it was split!
var item = inventoryItem as SocketableItem;
if (item == null)
{
if (DebugStatements)
{
Log.Debug(inventoryItem.FullName +
" is not socketable. Is this item a quiver or other non-socketable armor/weapon?");
}
continue;
}
var enabledGems =
GemLevelerSettings.Instance.EnabledGems.Where(s => s.PageType == pageType).ToList();
// Now, iterate the gems of the item... (There's always 6, but in case API changes to only provide 4 for gloves/boots, then we'll be safe)
for (int idx = 0; idx < item.SocketedGems.Length; idx++)
{
SkillGem gem = item.SocketedGems[idx];
// Not all sockets will have gems.
// So check the ones we actually care about.
// Thanks!
if (gem == null)
{
continue;
}
// Ignore any "globally ignored" gems. This just lets the user move gems around
// equipment, without having to worry about where or what it is.
if (GemLevelerSettings.Instance.IgnoredByNameGlobal.Contains(gem.Name))
continue;
// If our settings are disabled for the gem, ignore it.
// Check !Any() because if the gem isn't named, we don't want to level it, just in case.
// The user is responsible for making sure the gem is there, and enabled.
if (!enabledGems.Any(setting => setting.GemName == gem.Name && setting.Enabled))
continue;
// Srsly, ignore gems that aren't ready to be leveled.
if (gem.ExperiencePercent < 100)
{
continue;
}
if (gem.Experience == gem.ExperienceMaxLevel)
{
if (DebugStatements)
{
Log.Debug("I can't level up " + gem.Name + " because it is already at max level!");
}
continue;
}
// Now we just need to get the stats required for the next level.
int s, d, i, l;
gem.GetAttributeRequirements(out s, out d, out i, out l);
var canLevel = true;
if (myL < l)
{
canLevel = false;
if (DebugStatements)
{
Log.Debug("I'm not at the required level to level up " + gem.Name + "! (" + myL + "/" +
l + ")");
}
}
if (myS < s)
{
canLevel = false;
if (DebugStatements)
{
Log.Debug("I don't have enough strength to level up " + gem.Name + "! (" + myS + "/" + s +
")");
}
}
if (myD < d)
{
canLevel = false;
if (DebugStatements)
{
Log.Debug("I don't have enough dexterity to level up " + gem.Name + "! (" + myD + "/" +
d + ")");
}
}
if (myI < i)
{
canLevel = false;
if (DebugStatements)
{
Log.Debug("I don't have enough intelligence to level up " + gem.Name + "! (" + myI + "/" +
i + ")");
}
}
if (canLevel)
{
string gemName = gem.Name;
Log.Debug("I can level up " + gemName + "!");
Loki.Bot.v3.Coroutines.OpenInventoryPanel();
LokiPoe.InGameState.InventoryPanel.LevelSkillGem(gem);
Loki.Bot.v3.Coroutines.CloseBlockingWindows();
Log.Debug(gemName + " has been leveled!");
// Wait before we level the next gem so we don't spam gem level ups.
return;
}
}
}
}
}
}
}
GemLevelerSettings.cs
Code:
using System;
using System.Collections.Generic;
using Loki.Game;
using Loki.Game.Inventory;
using Loki.Utilities;
using Loki.Game.NativeWrappers;
using Loki.Game.GameData;
namespace GemLeveler
{
public class GemLevelerSettings : JsonSettings
{
private static GemLevelerSettings _instance;
private List<EnabledGem> _enabledGems;
private List<string> _ignoredByNameGlobal;
// It's highly recommended that you use the "Plugins" path under the Character/League folders for any plugin configuration.
// This will keep things organized on a per-character basis, and makes thing much easier to find for end-users.
public GemLevelerSettings()
: base(GetSettingsFilePath("Character", LokiPoe.InstanceInfo.League, LokiPoe.ObjectManager.Me.Name, "Plugins", "Gem Leveler", "Settings.json"))
{
if (EnabledGems == null)
{
EnabledGems = new List<EnabledGem>();
}
if (IgnoredByNameGlobal == null)
{
IgnoredByNameGlobal = new List<string>();
}
}
public static GemLevelerSettings Instance
{
get { return _instance ?? (_instance = new GemLevelerSettings()); }
}
public List<string> IgnoredByNameGlobal
{
get { return _ignoredByNameGlobal; }
set
{
if (Equals(value, _ignoredByNameGlobal))
{
return;
}
_ignoredByNameGlobal = value;
NotifyPropertyChanged(() => IgnoredByNameGlobal);
}
}
public List<EnabledGem> EnabledGems
{
get { return _enabledGems; }
set
{
if (Equals(value, _enabledGems))
{
return;
}
_enabledGems = value;
NotifyPropertyChanged(() => EnabledGems);
}
}
public class EnabledGem : IEquatable<EnabledGem>
{
public InventoryType PageType { get; set; }
public string GemName { get; set; }
public bool Enabled { get; set; }
#region Equality members
/// <summary>
/// Indicates whether the current object is equal to another object of the same type.
/// </summary>
/// <returns>
/// true if the current object is equal to the <paramref name="other" /> parameter; otherwise, false.
/// </returns>
/// <param name="other">An object to compare with this object.</param>
public bool Equals(EnabledGem other)
{
if (ReferenceEquals(null, other))
{
return false;
}
if (ReferenceEquals(this, other))
{
return true;
}
return PageType == other.PageType && string.Equals(GemName, other.GemName);
}
/// <summary>
/// Determines whether the specified <see cref="T:System.Object" /> is equal to the current
/// <see cref="T:System.Object" />.
/// </summary>
/// <returns>
/// true if the specified <see cref="T:System.Object" /> is equal to the current <see cref="T:System.Object" />;
/// otherwise, false.
/// </returns>
/// <param name="obj">The <see cref="T:System.Object" /> to compare with the current <see cref="T:System.Object" />. </param>
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
{
return false;
}
if (ReferenceEquals(this, obj))
{
return true;
}
if (obj.GetType() != GetType())
{
return false;
}
return Equals((EnabledGem) obj);
}
/// <summary>
/// Serves as a hash function for a particular type.
/// </summary>
/// <returns>
/// A hash code for the current <see cref="T:System.Object" />.
/// </returns>
public override int GetHashCode()
{
unchecked
{
return ((int) PageType * 397) ^ (GemName != null ? GemName.GetHashCode() : 0);
}
}
public static bool operator ==(EnabledGem left, EnabledGem right)
{
return Equals(left, right);
}
public static bool operator !=(EnabledGem left, EnabledGem right)
{
return !Equals(left, right);
}
#endregion
}
}
}
SettingsGui.xaml
Code:
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d">
<Grid x:Name="Root">
<Button x:Name="OpenGemLevelerButton" Height="30" Width="130">
<Label>Open Gem Leveler</Label>
</Button>
</Grid>
</UserControl>
Report errors if you find some, I didn't test this too long.
Also, I would like feedback on this piece of code, at the very end of GemLeveler.cs. Is there a better way to proceed?
Code:
Loki.Bot.v3.Coroutines.OpenInventoryPanel();
LokiPoe.InGameState.InventoryPanel.LevelSkillGem(gem);
Loki.Bot.v3.Coroutines.CloseBlockingWindows();