Code: Select all
//!CompilerOption:AddRef:..\..\lib\Newtonsoft.Json.dll
using System;
using System.Collections;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Net;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Windows.Forms;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Viper.Scripting.Core.Interfaces;
namespace MyPlugin
{
public class CustomSynth : IPlugin
{
private readonly List<string> _tasks = new List<string>();
private readonly object _sync = new object();
private IGame _game;
private Thread _worker;
private volatile bool _running;
private volatile bool _botRunning;
private volatile bool _sendOnceRunning;
private Panel _panel;
private TextBox _txtUrl;
private NumericUpDown _numMinWait;
private NumericUpDown _numTimeout;
private NumericUpDown _numHold;
private CheckBox _chkRequireCrafting;
private CheckBox _chkFocusGame;
private Button _btnStartStop;
private Button _btnSendOnce;
private Button _btnSaveSettings;
private Label _lblState;
private Label _lblItem;
private Label _lblCondition;
private Label _lblStep;
private Label _lblProgress;
private Label _lblQuality;
private Label _lblDurability;
private Label _lblLastAction;
private Label _lblLastError;
private TextBox _txtLastRequest;
private TextBox _txtLastResponse;
private System.Windows.Forms.Timer _uiTimer;
private string _state = "Idle";
private string _lastAction = "";
private string _lastError = "";
private string _lastRequest = "";
private string _lastResponse = "";
private bool _loadingSettings;
public string Name { get { return "CustomSynth"; } }
public string Author { get { return "MMOViper"; } }
public string Description { get { return "Posts FFXIV synthesis state to a web endpoint and executes the returned crafting action."; } }
public float Version { get { return 0.10f; } }
public Panel Panel { get { return GetPanel(); } }
public List<string> Tasks { get { return _tasks; } }
public string AutoUpdateWebPath { get { return string.Empty; } }
public string Url { get { return "www.mmoviper.com"; } }
public bool WantsButton { get { return true; } }
public bool NeedsToRun { get { return false; } }
public void DoRun()
{
}
public void OnIdle()
{
}
public void OnConfigure()
{
Log("Configure opened.");
}
public void OnEnabled(IGame PluginHelper)
{
_game = PluginHelper;
SetState("Enabled");
StartUiTimer();
}
public void OnDisabled()
{
SaveSettings(false);
StopWorker();
StopUiTimer();
SetState("Disabled");
}
public void OnBotStart(IGame PluginHelper)
{
_game = PluginHelper;
_botRunning = true;
SetState("Bot running");
}
public void OnBotStop()
{
_botRunning = false;
SaveSettings(false);
StopWorker();
SetState("Bot stopped");
}
public void OnSendMessage(IPlugin fromPlugin, string text)
{
if (string.Equals(text, "start", StringComparison.OrdinalIgnoreCase))
StartWorker();
else if (string.Equals(text, "stop", StringComparison.OrdinalIgnoreCase))
StopWorker();
}
public void OnSendObject(IPlugin fromPlugin, object o)
{
}
public void SendMessage(IPlugin p, string text)
{
if (p != null)
p.OnSendMessage(this, text);
}
public void SendObject(IPlugin p, object o)
{
if (p != null)
p.OnSendObject(this, o);
}
private Panel GetPanel()
{
if (_panel != null)
return _panel;
_panel = new Panel();
_panel.Dock = DockStyle.Fill;
_panel.Padding = new Padding(10);
TableLayoutPanel layout = new TableLayoutPanel();
layout.Dock = DockStyle.Fill;
layout.ColumnCount = 2;
layout.RowCount = 18;
layout.AutoScroll = true;
layout.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 115));
layout.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100));
_txtUrl = new TextBox();
_txtUrl.Dock = DockStyle.Fill;
_txtUrl.Text = "http://127.0.0.1:8080/custom-synth";
AddRow(layout, 0, "URL", _txtUrl);
_numMinWait = new NumericUpDown();
_numMinWait.Minimum = 0;
_numMinWait.Maximum = 30000;
_numMinWait.Value = 2000;
_numMinWait.Increment = 250;
AddRow(layout, 1, "Min wait ms", _numMinWait);
_numTimeout = new NumericUpDown();
_numTimeout.Minimum = 1000;
_numTimeout.Maximum = 60000;
_numTimeout.Value = 10000;
_numTimeout.Increment = 500;
AddRow(layout, 2, "Step timeout", _numTimeout);
_numHold = new NumericUpDown();
_numHold.Minimum = 0;
_numHold.Maximum = 5000;
_numHold.Value = 50;
_numHold.Increment = 25;
AddRow(layout, 3, "Raw hold ms", _numHold);
_chkRequireCrafting = new CheckBox();
_chkRequireCrafting.Text = "Require Player.isCrafting";
_chkRequireCrafting.Checked = true;
_chkRequireCrafting.AutoSize = true;
AddRow(layout, 4, "Guard", _chkRequireCrafting);
_chkFocusGame = new CheckBox();
_chkFocusGame.Text = "Focus game before raw key press";
_chkFocusGame.Checked = false;
_chkFocusGame.AutoSize = true;
AddRow(layout, 5, "Input", _chkFocusGame);
FlowLayoutPanel buttons = new FlowLayoutPanel();
buttons.Dock = DockStyle.Fill;
buttons.AutoSize = true;
_btnStartStop = new Button();
_btnStartStop.Text = "Start";
_btnStartStop.Width = 90;
_btnStartStop.Click += delegate { ToggleWorker(); };
_btnSendOnce = new Button();
_btnSendOnce.Text = "Send Once";
_btnSendOnce.Width = 90;
_btnSendOnce.Click += delegate { QueueSendOnce(); };
_btnSaveSettings = new Button();
_btnSaveSettings.Text = "Save";
_btnSaveSettings.Width = 70;
_btnSaveSettings.Click += delegate { SaveSettings(true); };
buttons.Controls.Add(_btnStartStop);
buttons.Controls.Add(_btnSendOnce);
buttons.Controls.Add(_btnSaveSettings);
AddRow(layout, 6, "Actions", buttons);
_lblState = NewValueLabel();
AddRow(layout, 7, "State", _lblState);
_lblItem = NewValueLabel();
AddRow(layout, 8, "Item", _lblItem);
_lblCondition = NewValueLabel();
AddRow(layout, 9, "Condition", _lblCondition);
_lblStep = NewValueLabel();
AddRow(layout, 10, "Step", _lblStep);
_lblProgress = NewValueLabel();
AddRow(layout, 11, "Progress", _lblProgress);
_lblQuality = NewValueLabel();
AddRow(layout, 12, "Quality", _lblQuality);
_lblDurability = NewValueLabel();
AddRow(layout, 13, "Durability", _lblDurability);
_lblLastAction = NewValueLabel();
AddRow(layout, 14, "Last action", _lblLastAction);
_lblLastError = NewValueLabel();
AddRow(layout, 15, "Last error", _lblLastError);
_txtLastRequest = NewLogBox();
AddRow(layout, 16, "Last request", _txtLastRequest);
_txtLastResponse = NewLogBox();
AddRow(layout, 17, "Last response", _txtLastResponse);
LoadSettings();
HookSettingsChanged();
_panel.Controls.Add(layout);
StartUiTimer();
RefreshUi();
return _panel;
}
private void AddRow(TableLayoutPanel layout, int row, string labelText, Control valueControl)
{
while (layout.RowStyles.Count <= row)
layout.RowStyles.Add(new RowStyle(SizeType.AutoSize));
Label label = new Label();
label.Text = labelText;
label.AutoSize = true;
label.Margin = new Padding(3, 6, 3, 3);
valueControl.Margin = new Padding(3, 3, 3, 3);
layout.Controls.Add(label, 0, row);
layout.Controls.Add(valueControl, 1, row);
}
private Label NewValueLabel()
{
Label label = new Label();
label.AutoSize = true;
label.Margin = new Padding(3, 6, 3, 3);
label.Text = "-";
return label;
}
private TextBox NewLogBox()
{
TextBox textBox = new TextBox();
textBox.Dock = DockStyle.Fill;
textBox.Multiline = true;
textBox.ScrollBars = ScrollBars.Vertical;
textBox.Height = 95;
textBox.ReadOnly = true;
return textBox;
}
private void ToggleWorker()
{
if (_running)
StopWorker();
else
StartWorker();
}
private void QueueSendOnce()
{
if (_sendOnceRunning)
return;
_sendOnceRunning = true;
RefreshUi();
SaveSettings(false);
Thread thread = new Thread(delegate()
{
try
{
RunOneCycle(true);
}
catch (Exception ex)
{
if (!HasLastRequest())
SetLastRequest("Request not built: " + ex.Message);
SetError(ex.Message);
}
finally
{
_sendOnceRunning = false;
}
RefreshUi();
});
thread.IsBackground = true;
thread.Start();
}
private void StartWorker()
{
if (_game == null)
{
SetError("Plugin helper is not available yet.");
return;
}
if (_running)
return;
SaveSettings(false);
_running = true;
_worker = new Thread(WorkerLoop);
_worker.IsBackground = true;
_worker.Start();
SetState("Started");
RefreshUi();
}
private void StopWorker()
{
_running = false;
Thread worker = _worker;
_worker = null;
if (worker != null && worker.IsAlive)
worker.Join(250);
SetState("Stopped");
RefreshUi();
}
private void WorkerLoop()
{
while (_running)
{
try
{
RunOneCycle(false);
}
catch (Exception ex)
{
SetError(ex.Message);
SleepWhileRunning(1000);
}
RefreshUi();
}
}
private void RunOneCycle(bool sendOnly)
{
if (_game == null)
throw new InvalidOperationException("Plugin helper is not available.");
ClearError();
object synth = GetSynthesis();
if (synth == null)
{
SetLastRequest("Request not sent: Synthesis window is not available.");
SetState("Waiting for synthesis");
SleepWhileRunning(500);
return;
}
if (!sendOnly && RequireCrafting() && !IsCrafting())
{
SetLastRequest("Request not sent: Player.isCrafting is false.");
SetState("Waiting for crafting");
SleepWhileRunning(500);
return;
}
SetState("Building request");
int startStep = GetApiInt(synth, "StepNumber", 0);
JObject payload = BuildPayload(synth);
string requestJson = payload.ToString(Formatting.Indented);
SetLastRequest(requestJson);
SetState("Sending");
string responseJson = PostJson(GetEndpoint(), requestJson);
SetLastResponse(responseJson);
JObject response = JObject.Parse(responseJson);
if (GetBool(response, "stop", false))
{
Log("Crafting ended: server requested stop.");
SetState("Server requested stop");
_running = false;
return;
}
if (GetBool(response, "wait", false) || GetBool(response, "noop", false))
{
SetLastAction("wait");
Log("Crafting paused: server returned wait/noop.");
SetState("Waiting by response");
SleepWhileRunning(GetMinWaitMs());
return;
}
SetState("Processing response");
string actionText;
bool pressed = PressAction(response, out actionText);
if (!pressed)
{
Log("Crafting ended: no action pressed.");
SetState("No action pressed");
SleepWhileRunning(750);
return;
}
SetState("Waiting for step");
bool advanced = WaitForStepAdvance(startStep, GetMinWaitMs(), GetTimeoutMs(), !sendOnly);
LogStepAction(startStep, actionText);
if (advanced)
{
SetState("Step advanced");
}
else
{
Log("Crafting step " + startStep + " ended: step wait timed out.");
SetState("Step wait timed out");
}
}
private JObject BuildPayload(object synth)
{
JObject payload = new JObject();
payload["plugin"] = Name;
payload["version"] = Version;
payload["timestampUtc"] = DateTime.UtcNow.ToString("o");
payload["botRunning"] = _botRunning;
JObject synthJson = new JObject();
synthJson["itemName"] = GetApiString(synth, "ItemName");
synthJson["condition"] = GetApiString(synth, "Condition");
synthJson["hqLiteral"] = GetApiString(synth, "HQLiteral");
synthJson["craftEffectOverflow"] = GetApiString(synth, "CraftEffectOverflow");
synthJson["currentQuality"] = GetApiInt(synth, "CurrentQuality", 0);
synthJson["maxQuality"] = GetApiInt(synth, "MaxQuality", 0);
synthJson["currentProgress"] = GetApiInt(synth, "CurrentProgress", 0);
synthJson["maxProgress"] = GetApiInt(synth, "MaxProgress", 0);
synthJson["currentDurability"] = GetApiInt(synth, "CurrentDurability", 0);
synthJson["startingDurability"] = GetApiInt(synth, "StartingDurability", 0);
synthJson["hqPercentage"] = GetApiInt(synth, "HQPercentage", 0);
synthJson["stepNumber"] = GetApiInt(synth, "StepNumber", 0);
synthJson["collectabilityLow"] = GetApiInt(synth, "CollectabilityLow", 0);
synthJson["collectabilityMid"] = GetApiInt(synth, "CollectabilityMid", 0);
synthJson["collectabilityHigh"] = GetApiInt(synth, "CollectabilityHigh", 0);
synthJson["effects"] = BuildEffects(synth);
payload["synthesis"] = synthJson;
JObject playerJson = new JObject();
playerJson["isCrafting"] = IsCrafting();
playerJson["level"] = _game.Player.Level;
playerJson["hp"] = _game.Player.HP;
playerJson["curHp"] = _game.Player.CurHP;
playerJson["mp"] = _game.Player.MP;
playerJson["curMp"] = _game.Player.CurMP;
payload["player"] = playerJson;
payload["hotbar"] = BuildHotbar();
return payload;
}
private JArray BuildEffects(object synth)
{
JArray array = new JArray();
object effects = GetApiValue(synth, "Effects");
if (!(effects is IEnumerable))
return array;
foreach (object effect in (IEnumerable)effects)
{
if (effect == null)
continue;
JObject obj = new JObject();
obj["name"] = GetApiString(effect, "Name");
obj["stepsRemaining"] = GetApiInt(effect, "StepsRemaining", 0);
obj["count"] = GetApiInt(effect, "Count", 0);
obj["hoverText"] = GetApiString(effect, "HoverText");
array.Add(obj);
}
return array;
}
private JArray BuildHotbar()
{
JArray array = new JArray();
object hotbar = GetHotbar();
object dictionary = GetApiValue(hotbar, "Dictionary");
if (!(dictionary is IEnumerable))
return array;
foreach (object pair in (IEnumerable)dictionary)
{
object key = GetApiValue(pair, "Value");
if (key == null)
continue;
bool isReady = GetApiBool(key, "isReady", false);
if (!isReady)
continue;
JObject obj = new JObject();
obj["id"] = GetApiInt(key, "ID", 0);
obj["name"] = GetApiString(key, "Name");
obj["isReady"] = isReady;
obj["isComboActive"] = GetApiBool(key, "isComboActive", false);
obj["mpCost"] = GetApiInt(key, "MPCost", 0);
obj["count"] = GetApiInt(key, "Count", 0);
obj["range"] = GetApiInt(key, "Range", 0);
obj["radius"] = GetApiInt(key, "Radius", 0);
obj["castTime"] = GetApiString(key, "CastTime");
obj["cooldown"] = GetApiString(key, "Cooldown");
array.Add(obj);
}
return array;
}
private bool PressAction(JObject response, out string actionText)
{
actionText = "";
int hotbarId = GetInt(response, "hotbarId", 0);
if (hotbarId == 0)
hotbarId = GetInt(response, "id", 0);
if (hotbarId == 0)
hotbarId = GetInt(response, "actionId", 0);
object hotbar = GetHotbar();
if (hotbarId > 0 && hotbar != null)
{
object hotbarKey = InvokeApiMethod(hotbar, "GetKeyByID", new object[] { hotbarId });
if (hotbarKey == null)
{
SetError("Hotbar id not found: " + hotbarId);
return false;
}
InvokeApiMethod(hotbarKey, "PressKey", new object[0]);
actionText = GetApiString(hotbarKey, "Name") + " (" + hotbarId + ")";
SetLastAction(actionText);
return true;
}
string action = GetString(response, "hotbarName");
if (string.IsNullOrEmpty(action))
action = GetString(response, "action");
if (string.IsNullOrEmpty(action))
action = GetString(response, "skill");
if (!string.IsNullOrEmpty(action) && hotbar != null)
{
object hotbarKey = InvokeApiMethod(hotbar, "GetKeyByName", new object[] { action });
if (hotbarKey == null)
{
SetError("Hotbar action not found: " + action);
return false;
}
InvokeApiMethod(hotbarKey, "PressKey", new object[0]);
actionText = GetApiString(hotbarKey, "Name");
SetLastAction(actionText);
return true;
}
string keyText = GetString(response, "key");
if (string.IsNullOrEmpty(keyText))
keyText = GetString(response, "keys");
if (!string.IsNullOrEmpty(keyText))
{
if (FocusGameBeforeRawKey())
_game.Input.SendText("");
int holdMs = GetInt(response, "holdMs", GetRawHoldMs());
if (holdMs > 0)
_game.Input.PressKey(keyText, holdMs);
else
_game.Input.PressKey(keyText);
SetLastAction(keyText);
actionText = "raw key " + keyText;
return true;
}
SetError("Response did not contain key, action, hotbarName, or hotbarId.");
return false;
}
private void LogStepAction(int step, string actionText)
{
string resultText = GetCraftResultText();
if (string.IsNullOrEmpty(resultText))
Log("Step " + step + ": Pressed " + actionText);
else
Log("Step " + step + ": Pressed " + actionText + " " + resultText);
}
private string GetCraftResultText()
{
object synth = GetSynthesis();
if (synth == null)
return "";
int currentProgress = GetApiInt(synth, "CurrentProgress", 0);
int maxProgress = GetApiInt(synth, "MaxProgress", 0);
int progressPercent = 0;
if (maxProgress > 0)
progressPercent = (currentProgress * 100 + (maxProgress / 2)) / maxProgress;
return "Prog: " + progressPercent + "% HQ: " + GetApiInt(synth, "HQPercentage", 0) + "%";
}
private bool WaitForStepAdvance(int startStep, int minimumWaitMs, int timeoutMs, bool stopWhenNotRunning)
{
DateTime started = DateTime.UtcNow;
DateTime minimumUntil = started.AddMilliseconds(minimumWaitMs);
while (!stopWhenNotRunning || _running)
{
if ((DateTime.UtcNow - started).TotalMilliseconds >= timeoutMs)
return false;
object synth = GetSynthesis();
if (_game == null || synth == null)
return true;
if (RequireCrafting() && !IsCrafting())
return true;
int currentStep = GetApiInt(synth, "StepNumber", 0);
if (currentStep > startStep && DateTime.UtcNow >= minimumUntil)
return true;
SleepWhileRunning(100);
}
return false;
}
private string PostJson(string url, string json)
{
byte[] bytes = Encoding.UTF8.GetBytes(json);
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.Method = "POST";
request.ContentType = "application/json";
request.Accept = "application/json";
request.Timeout = 30000;
request.ReadWriteTimeout = 30000;
request.ContentLength = bytes.Length;
using (Stream requestStream = request.GetRequestStream())
requestStream.Write(bytes, 0, bytes.Length);
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
using (Stream responseStream = response.GetResponseStream())
using (StreamReader reader = new StreamReader(responseStream))
return reader.ReadToEnd();
}
private string GetEndpoint()
{
string url = _txtUrl == null ? "" : ReadTextBox(_txtUrl);
if (string.IsNullOrEmpty(url))
throw new InvalidOperationException("URL is empty.");
return url.Trim();
}
private string ReadTextBox(TextBox textBox)
{
if (textBox.InvokeRequired)
return (string)textBox.Invoke(new Func<string>(delegate { return textBox.Text; }));
return textBox.Text;
}
private bool ReadChecked(CheckBox checkBox)
{
if (checkBox.InvokeRequired)
return (bool)checkBox.Invoke(new Func<bool>(delegate { return checkBox.Checked; }));
return checkBox.Checked;
}
private bool RequireCrafting()
{
return _chkRequireCrafting != null && ReadChecked(_chkRequireCrafting);
}
private bool FocusGameBeforeRawKey()
{
return _chkFocusGame != null && ReadChecked(_chkFocusGame);
}
private int GetMinWaitMs()
{
return _numMinWait == null ? 2000 : ReadNumeric(_numMinWait);
}
private int GetTimeoutMs()
{
return _numTimeout == null ? 10000 : ReadNumeric(_numTimeout);
}
private int GetRawHoldMs()
{
return _numHold == null ? 50 : ReadNumeric(_numHold);
}
private int ReadNumeric(NumericUpDown numeric)
{
if (numeric.InvokeRequired)
return (int)numeric.Invoke(new Func<int>(delegate { return Convert.ToInt32(numeric.Value); }));
return Convert.ToInt32(numeric.Value);
}
private void HookSettingsChanged()
{
_txtUrl.Leave += delegate { SaveSettings(false); };
_numMinWait.ValueChanged += delegate { SaveSettings(false); };
_numTimeout.ValueChanged += delegate { SaveSettings(false); };
_numHold.ValueChanged += delegate { SaveSettings(false); };
_chkRequireCrafting.CheckedChanged += delegate { SaveSettings(false); };
_chkFocusGame.CheckedChanged += delegate { SaveSettings(false); };
}
private void LoadSettings()
{
string path = GetSettingsPath();
if (!File.Exists(path))
return;
_loadingSettings = true;
try
{
JObject settings = JObject.Parse(File.ReadAllText(path));
string url = GetString(settings, "url");
if (!string.IsNullOrEmpty(url))
_txtUrl.Text = url;
SetNumeric(_numMinWait, GetInt(settings, "minWaitMs", Convert.ToInt32(_numMinWait.Value)));
SetNumeric(_numTimeout, GetInt(settings, "stepTimeoutMs", Convert.ToInt32(_numTimeout.Value)));
SetNumeric(_numHold, GetInt(settings, "rawHoldMs", Convert.ToInt32(_numHold.Value)));
_chkRequireCrafting.Checked = GetBool(settings, "requireCrafting", _chkRequireCrafting.Checked);
_chkFocusGame.Checked = GetBool(settings, "focusGame", _chkFocusGame.Checked);
}
catch (Exception ex)
{
SetError("Could not load settings: " + ex.Message);
}
finally
{
_loadingSettings = false;
}
}
private void SaveSettings(bool showStatus)
{
if (_loadingSettings || _txtUrl == null || _numMinWait == null)
return;
if (_panel != null && !_panel.IsDisposed && _panel.InvokeRequired)
{
try { _panel.Invoke(new MethodInvoker(delegate { SaveSettings(showStatus); })); }
catch { }
return;
}
try
{
string path = GetSettingsPath();
string directory = Path.GetDirectoryName(path);
if (!Directory.Exists(directory))
Directory.CreateDirectory(directory);
JObject settings = new JObject();
settings["url"] = ReadTextBox(_txtUrl).Trim();
settings["minWaitMs"] = ReadNumeric(_numMinWait);
settings["stepTimeoutMs"] = ReadNumeric(_numTimeout);
settings["rawHoldMs"] = ReadNumeric(_numHold);
settings["requireCrafting"] = ReadChecked(_chkRequireCrafting);
settings["focusGame"] = ReadChecked(_chkFocusGame);
File.WriteAllText(path, settings.ToString(Formatting.Indented));
if (showStatus)
SetLastAction("settings saved");
}
catch (Exception ex)
{
SetError("Could not save settings: " + ex.Message);
}
}
private string GetSettingsPath()
{
return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Resources\\Plugins\\CustomSynth.settings.json");
}
private void SetNumeric(NumericUpDown numeric, int value)
{
if (value < numeric.Minimum)
value = Convert.ToInt32(numeric.Minimum);
if (value > numeric.Maximum)
value = Convert.ToInt32(numeric.Maximum);
numeric.Value = value;
}
private void SleepWhileRunning(int milliseconds)
{
int waited = 0;
while ((_running || waited == 0) && waited < milliseconds)
{
Thread.Sleep(Math.Min(100, milliseconds - waited));
waited += 100;
if (!_running)
break;
}
}
private string SafeString(string text)
{
return text == null ? string.Empty : text;
}
private object GetSynthesis()
{
return GetApiValue(_game, "Synthesis");
}
private object GetHotbar()
{
return GetApiValue(_game, "Hotbar");
}
private bool IsCrafting()
{
if (_game == null || _game.Player == null)
return false;
return GetApiBool(_game.Player, "isCrafting", false);
}
private object GetApiValue(object target, string propertyName)
{
if (target == null || string.IsNullOrEmpty(propertyName))
return null;
Type type = target.GetType();
PropertyInfo property = type.GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public);
if (property != null)
return property.GetValue(target, null);
Type[] interfaces = type.GetInterfaces();
for (int i = 0; i < interfaces.Length; i++)
{
property = interfaces[i].GetProperty(propertyName);
if (property != null)
return property.GetValue(target, null);
}
return null;
}
private string GetApiString(object target, string propertyName)
{
object value = GetApiValue(target, propertyName);
return value == null ? string.Empty : value.ToString();
}
private int GetApiInt(object target, string propertyName, int defaultValue)
{
object value = GetApiValue(target, propertyName);
if (value == null)
return defaultValue;
try { return Convert.ToInt32(value); }
catch
{
int parsed;
if (int.TryParse(value.ToString(), out parsed))
return parsed;
return defaultValue;
}
}
private bool GetApiBool(object target, string propertyName, bool defaultValue)
{
object value = GetApiValue(target, propertyName);
if (value == null)
return defaultValue;
try { return Convert.ToBoolean(value); }
catch
{
bool parsed;
if (bool.TryParse(value.ToString(), out parsed))
return parsed;
return defaultValue;
}
}
private object InvokeApiMethod(object target, string methodName, object[] args)
{
if (target == null || string.IsNullOrEmpty(methodName))
return null;
Type type = target.GetType();
object result;
if (TryInvokeMethod(type.GetMethods(BindingFlags.Instance | BindingFlags.Public), target, methodName, args, out result))
return result;
Type[] interfaces = type.GetInterfaces();
for (int i = 0; i < interfaces.Length; i++)
{
if (TryInvokeMethod(interfaces[i].GetMethods(), target, methodName, args, out result))
return result;
}
return null;
}
private bool TryInvokeMethod(MethodInfo[] methods, object target, string methodName, object[] args, out object result)
{
result = null;
if (args == null)
args = new object[0];
for (int i = 0; i < methods.Length; i++)
{
MethodInfo method = methods[i];
if (!string.Equals(method.Name, methodName, StringComparison.OrdinalIgnoreCase))
continue;
ParameterInfo[] parameters = method.GetParameters();
if (parameters.Length != args.Length)
continue;
object[] converted = new object[args.Length];
bool valid = true;
for (int argIndex = 0; argIndex < args.Length; argIndex++)
{
if (!TryConvertArgument(args[argIndex], parameters[argIndex].ParameterType, out converted[argIndex]))
{
valid = false;
break;
}
}
if (!valid)
continue;
result = method.Invoke(target, converted);
return true;
}
return false;
}
private bool TryConvertArgument(object value, Type targetType, out object converted)
{
converted = null;
if (targetType == null)
return false;
if (value == null)
{
if (targetType.IsValueType)
return false;
return true;
}
if (targetType.IsInstanceOfType(value))
{
converted = value;
return true;
}
try
{
converted = Convert.ChangeType(value, targetType);
return true;
}
catch
{
return false;
}
}
private string GetString(JObject obj, string name)
{
JToken token = GetToken(obj, name);
if (token == null || token.Type == JTokenType.Null)
return string.Empty;
return token.ToString();
}
private JToken GetToken(JObject obj, string name)
{
if (obj == null || string.IsNullOrEmpty(name))
return null;
foreach (JProperty property in obj.Properties())
{
if (string.Equals(property.Name, name, StringComparison.OrdinalIgnoreCase))
return property.Value;
}
return null;
}
private int GetInt(JObject obj, string name, int defaultValue)
{
string text = GetString(obj, name);
int value;
if (int.TryParse(text, out value))
return value;
return defaultValue;
}
private bool GetBool(JObject obj, string name, bool defaultValue)
{
string text = GetString(obj, name);
bool value;
if (bool.TryParse(text, out value))
return value;
int intValue;
if (int.TryParse(text, out intValue))
return intValue != 0;
return defaultValue;
}
private void SetState(string state)
{
lock (_sync)
{
if (_state == state)
return;
_state = state;
}
RefreshUi();
}
private void SetError(string error)
{
string text = error == null ? "" : error;
lock (_sync)
{
if (_lastError == text)
return;
_lastError = text;
}
Log("ERROR: " + _lastError);
RefreshUi();
}
private void ClearError()
{
lock (_sync)
{
if (_lastError == "")
return;
_lastError = "";
}
RefreshUi();
}
private void SetLastAction(string action)
{
string text = action == null ? "" : action;
lock (_sync)
{
if (_lastAction == text)
return;
_lastAction = text;
}
RefreshUi();
}
private void SetLastRequest(string request)
{
string text = request == null ? "" : request;
lock (_sync)
{
if (_lastRequest == text)
return;
_lastRequest = text;
}
RefreshUi();
}
private bool HasLastRequest()
{
lock (_sync)
return !string.IsNullOrEmpty(_lastRequest);
}
private void SetLastResponse(string response)
{
string text = response == null ? "" : response;
lock (_sync)
{
if (_lastResponse == text)
return;
_lastResponse = text;
}
RefreshUi();
}
private void RefreshUi()
{
if (_panel == null || _panel.IsDisposed)
return;
if (_panel.InvokeRequired)
{
try { _panel.BeginInvoke(new MethodInvoker(RefreshUi)); }
catch { }
return;
}
string state;
string lastAction;
string lastError;
string lastRequest;
string lastResponse;
lock (_sync)
{
state = _state;
lastAction = _lastAction;
lastError = _lastError;
lastRequest = _lastRequest;
lastResponse = _lastResponse;
}
SetTextIfChanged(_btnStartStop, _running ? "Stop" : "Start");
SetEnabledIfChanged(_btnSendOnce, !_sendOnceRunning);
SetTextIfChanged(_lblState, state);
SetTextIfChanged(_lblLastAction, string.IsNullOrEmpty(lastAction) ? "-" : lastAction);
SetTextIfChanged(_lblLastError, string.IsNullOrEmpty(lastError) ? "-" : lastError);
SetTextIfChanged(_txtLastRequest, lastRequest);
SetTextIfChanged(_txtLastResponse, Shorten(lastResponse, 5000));
try
{
object synth = GetSynthesis();
if (synth == null)
{
SetTextIfChanged(_lblItem, "-");
SetTextIfChanged(_lblCondition, "-");
SetTextIfChanged(_lblStep, "-");
SetTextIfChanged(_lblProgress, "-");
SetTextIfChanged(_lblQuality, "-");
SetTextIfChanged(_lblDurability, "-");
return;
}
SetTextIfChanged(_lblItem, GetApiString(synth, "ItemName"));
SetTextIfChanged(_lblCondition, GetApiString(synth, "Condition"));
SetTextIfChanged(_lblStep, GetApiInt(synth, "StepNumber", 0).ToString());
SetTextIfChanged(_lblProgress, GetApiInt(synth, "CurrentProgress", 0) + " / " + GetApiInt(synth, "MaxProgress", 0));
SetTextIfChanged(_lblQuality, GetApiInt(synth, "CurrentQuality", 0) + " / " + GetApiInt(synth, "MaxQuality", 0) + " (" + GetApiInt(synth, "HQPercentage", 0) + "%)");
SetTextIfChanged(_lblDurability, GetApiInt(synth, "CurrentDurability", 0) + " / " + GetApiInt(synth, "StartingDurability", 0));
}
catch
{
}
}
private void SetTextIfChanged(Control control, string text)
{
if (control != null && control.Text != text)
control.Text = text;
}
private void SetEnabledIfChanged(Control control, bool enabled)
{
if (control != null && control.Enabled != enabled)
control.Enabled = enabled;
}
private string Shorten(string text, int max)
{
if (string.IsNullOrEmpty(text) || text.Length <= max)
return text;
return text.Substring(0, max) + "\r\n...";
}
private void StartUiTimer()
{
if (_panel == null)
return;
if (_uiTimer != null)
return;
_uiTimer = new System.Windows.Forms.Timer();
_uiTimer.Interval = 500;
_uiTimer.Tick += delegate { RefreshUi(); };
_uiTimer.Start();
}
private void StopUiTimer()
{
if (_uiTimer == null)
return;
_uiTimer.Stop();
_uiTimer.Dispose();
_uiTimer = null;
}
private void Log(string text)
{
try
{
if (_game != null && _game.Log != null)
_game.Log.WriteLine(Name + "->" + text);
}
catch
{
}
}
}
}