FFXIV Bot - 2.3.6.5 Fixed Synth.

Read-only announcements from the team.
Post Reply
Message
Author
PitViper
Site Admin
Posts: 20200
Joined: Tue Oct 16, 2007 7:01 am

FFXIV Bot - 2.3.6.5 Fixed Synth.

#1 Post

========= MMOViperBot Change Notes =========
============================================
May 20, 2026 MMOViperBot Version 2.3.6.5

Fintal Fantasy XIV (FFXIV Bot)
-- Bugfix: ISynthesis API was broke

One user wanted an example of how to handle the synth routine outside the bot.. ie like send it to an AI and have it decide what to press. This example plugin just sends all needed info to a web page.. and waits for a reply of what key to press. Hvae the AI decide! It sends your hotkeys, buffs, synth state, condition, etc.
CustomSynth.rar
(7.67 KiB) Downloaded 16 times

PitViper
Site Admin
Posts: 20200
Joined: Tue Oct 16, 2007 7:01 am

Re: FFXIV Bot - 2.3.6.5 Fixed Synth.

#2 Post

Here is what the custom synth script looks like:

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
            {
            }
        }
    }
}

Post Reply

Return to “Announcements”