Initial showing of the bot window

This commit is contained in:
Malcolm Roberts 2024-01-04 15:28:57 -06:00
parent 7be649b56b
commit 45ded91030
24 changed files with 1302 additions and 203 deletions

View File

@ -69,7 +69,6 @@ add_library(ISXMr SHARED ${SOURCE_DIR}/ISXMr.cpp
src/isxeq2/Actor.cpp src/isxeq2/Actor.cpp
src/isxeq2/Actor.h src/isxeq2/Actor.h
src/isxeq2/LSObject.h src/isxeq2/LSObject.h
src/WriteUIFileToDisk.cpp
src/isxeq2/Point3f.h src/isxeq2/Point3f.h
src/isxeq2/Ability.cpp src/isxeq2/Ability.cpp
src/isxeq2/Ability.h src/isxeq2/Ability.h
@ -78,12 +77,12 @@ add_library(ISXMr SHARED ${SOURCE_DIR}/ISXMr.cpp
src/isxeq2/CharacterClass.h src/isxeq2/CharacterClass.h
src/isxeq2/AbilityEffect.h src/isxeq2/AbilityEffect.h
src/Logger.h src/Logger.h
src/Commands/ExecutableCommand.h src/Tasks/ExecutableTask.h
src/Commands/ExportCommand.cpp src/Tasks/ExportAbilitiesTask.cpp
src/Commands/ExportCommand.h src/Tasks/ExportAbilitiesTask.h
src/isxeq2/ExtensionTLOs.h src/isxeq2/ExtensionTLOs.h
src/Commands/CommandExecutor.cpp src/Tasks/TaskExecutor.cpp
src/Commands/CommandExecutor.h src/Tasks/TaskExecutor.h
src/BotSettings/ExportedAbility.h src/BotSettings/ExportedAbility.h
includes/argh/argh.h includes/argh/argh.h
src/isxeq2/EQ2.h src/isxeq2/EQ2.h
@ -94,6 +93,10 @@ add_library(ISXMr SHARED ${SOURCE_DIR}/ISXMr.cpp
src/Api/MrBotApi.h src/Api/MrBotApi.h
includes/json_struct/json_struct.h includes/json_struct/json_struct.h
includes/json_struct/json_struct_diff.h includes/json_struct/json_struct_diff.h
src/Tasks/BotTask.cpp
src/Tasks/BotTask.h
src/ScopedEnumBitwiseOperators.h
lgui2/UpdateUIPackageFile.h
) )
IF (WIN32) IF (WIN32)

687
lgui2/bot_cast_stack.json Normal file
View File

@ -0,0 +1,687 @@
{
"$schema": "http://www.lavishsoft.com/schema/lgui2Package.json",
"skin": "MRSkin",
"templates": {
"settings.abilityListEntry": {
"jsonTemplate": "listboxitem",
"padding": 2,
"content": {
"type": "stackpanel",
"orientation": "vertical",
"-contentContainer": {
"jsonTemplate": "listbox.contentContainerFitWidth"
},
"children": [
{
"type": "textblock",
"horizontalAlignment": "stretch",
"textBinding": {
"pullFormat": "${_CONTEXTITEMDATA_.Get[name]}"
}
}
]
}
},
"settings.castStack": {
"orientation": "vertical",
"children": [
{
"type": "panel",
"visibility": "hidden",
"name": "CastStackController.events"
},
{
"type": "stackpanel",
"orientation": "horizontal",
"heightFactor": 0.9,
"widthFactor": 1,
"horizontalAlignment": "left",
"children": [
{
"type": "stackpanel",
"orientation": "vertical",
"widthFactor": 0.3,
"margin": [
0,
0,
5,
0
],
"children": [
{
"type": "textblock",
"text": "Ability List",
"widthFactor": 1,
"horizontalAlignment": "left"
},
{
"type": "listbox",
"name": "abilityList",
"heightFactor": 0.9,
"itemsBinding": {
"pullFormat": "${CastStackController.abilityListItems}",
"pullOnce": true
},
"itemViewGenerators": {
"default": {
"type": "template",
"template": "settings.abilityListEntry"
}
},
"selectedItemBinding": {
"pullFormat": "${CastStackController.currentSelectedAvailableAbilityIndex}",
"pushFormat": [
"CastStackController:SetCurrentAbility[\"",
"\"]"
]
},
"-contentContainer": {
"jsonTemplate": "listbox.contentContainerFitWidth"
}
},
{
"type": "button",
"name": "castStack.addAbility",
"content": "Add Ability",
"horizontalAlignment": "stretch",
"eventHandlers": {
"onRelease": [
"method",
"CastStackController",
"AddAbility"
]
}
}
]
},
{
"type": "stackpanel",
"orientation": "vertical",
"widthFactor": 0.3,
"heightFactor": 1,
"verticalAlignment": "top",
"margin": [
0,
0,
5,
0
],
"padding": [
0,
16,
0,
0
],
"children": [
{
"type": "stackpanel",
"orientation": "horizontal",
"margin": [
0,
0,
0,
5
],
"children": [
{
"type": "textblock",
"text": "Type",
"width": 60
},
{
"type": "combobox",
"name": "castStack.ability.type",
"horizontalAlignment": "stretch",
"items": [
"Combat",
"CA",
"NamedCA",
"Heal",
"PowerHeal",
"Debuff",
"NamedDebuff",
"NonCombatBuff",
"Cure",
"Buff"
],
"selectedItemBinding": {
"pullFormat": "${CastStackController.newCastStackItem.Get[type]}",
"autoPull": false,
"pullHook": {
"elementName": "CastStackController.events",
"flags": "global",
"event": "onNewCastStackItemChanged"
}
},
"eventHandlers": {
"onSelectionChanged": {
"type": "method",
"object": "CastStackController",
"method": "OnCastStackAbilityComboChange"
}
}
}
]
},
{
"type": "stackpanel",
"orientation": "horizontal",
"margin": [
0,
0,
0,
5
],
"children": [
{
"type": "textblock",
"text": "Target",
"width": 60
},
{
"type": "combobox",
"name": "castStack.ability.target",
"horizontalAlignment": "stretch",
"itemsBinding": {
"pullFormat": "${CastStackController.GetTargetOptions}",
"pullOnce": true
},
"selectedItemBinding": {
"pullFormat": "${CastStackController.newCastStackItem.Get[target]}",
"autoPull": false,
"pullHook": {
"elementName": "CastStackController.events",
"flags": "global",
"event": "onNewCastStackItemChanged"
}
},
"eventHandlers": {
"onSelectionChanged": {
"type": "method",
"object": "CastStackController",
"method": "OnCastStackAbilityComboChange"
}
}
}
]
},
{
"type": "stackpanel",
"orientation": "horizontal",
"margin": [
0,
0,
0,
5
],
"children": [
{
"type": "textblock",
"text": "# targets",
"width": 60
},
{
"type": "textbox",
"name": "castStack.ability.targetCount",
"horizontalAlignment": "stretch",
"textBinding": {
"pullFormat": "${CastStackController.SafeGetNewCastStackItemProperty[targetCount]}",
"autoPull": false,
"pullHook": {
"elementName": "CastStackController.events",
"flags": "global",
"event": "onNewCastStackItemChanged"
},
"pushFormat": [
"CastStackController:SafeSetNewCastStackItemProperty[\"targetCount\",\"",
"\"]"
],
"autoPush": false
},
"hooks": {
"onLostFocus": {
"flags": "self",
"event": "lostKeyboardFocus",
"eventHandler": {
"type": "forward",
"event": "pushTextBinding",
"flags": "self"
}
},
"onLostMouseFocus": {
"flags": "self",
"event": "lostMouseFocus",
"eventHandler": {
"type": "forward",
"event": "pushTextBinding",
"flags": "self"
}
}
}
}
]
},
{
"type": "stackpanel",
"orientation": "horizontal",
"children": [
{
"type": "textblock",
"text": "HP/MP %",
"width": 60
},
{
"type": "textbox",
"name": "castStack.percent",
"horizontalAlignment": "stretch",
"textBinding": {
"pullFormat": "${CastStackController.SafeGetNewCastStackItemProperty[percent]}",
"autoPull": false,
"pullHook": {
"elementName": "CastStackController.events",
"flags": "global",
"event": "onNewCastStackItemChanged"
},
"pushFormat": [
"CastStackController:SafeSetNewCastStackItemProperty[\"percent\",\"",
"\"]"
],
"autoPush": false
},
"hooks": {
"onLostFocus": {
"flags": "self",
"event": "lostKeyboardFocus",
"eventHandler": {
"type": "forward",
"event": "pushTextBinding",
"flags": "self"
}
},
"onLostMouseFocus": {
"flags": "self",
"event": "lostMouseFocus",
"eventHandler": {
"type": "forward",
"event": "pushTextBinding",
"flags": "self"
}
}
}
}
]
},
{
"type": "checkbox",
"name": "castStack.ignoreDuration",
"content": "Ignore Duration",
"horizontalAlignment": "stretch",
"margin": [
2,
10,
0,
0
]
},
{
"type": "checkbox",
"name": "castStack.ignoreEncounterNukes",
"content": "Ignore Encounter Nukes",
"horizontalAlignment": "stretch"
},
{
"type": "checkbox",
"name": "castStack.ignoreAENukes",
"content": "Ignore AE Nukes",
"horizontalAlignment": "stretch"
},
{
"type": "checkbox",
"name": "castStack.maxIncrements",
"content": "Max Increments",
"horizontalAlignment": "stretch"
},
{
"type": "checkbox",
"name": "castStack.namedOnly",
"content": "Named Only",
"horizontalAlignment": "stretch"
},
{
"type": "stackpanel",
"orientation": "horizontal",
"horizontalAlignment": "stretch",
"margin": [
0,
10,
0,
5
],
"children": [
{
"type": "textblock",
"text": "Fervor",
"width": 70
},
{
"type": "textbox",
"name": "castStack.fervorRangeMin",
"width": 30
},
{
"type": "textblock",
"text": "-",
"font": {
"bold": true,
"height": 24
},
"margin": [
5,
0,
0,
0
],
"width": 10
},
{
"type": "textbox",
"name": "castStack.fervorRangeMax",
"width": 30
}
]
},
{
"type": "stackpanel",
"orientation": "horizontal",
"horizontalAlignment": "stretch",
"margin": [
0,
0,
0,
5
],
"children": [
{
"type": "textblock",
"text": "Dissonance",
"width": 70
},
{
"type": "textbox",
"name": "castStack.dissonanceRangeMin",
"width": 30
},
{
"type": "textblock",
"text": "-",
"font": {
"bold": true,
"height": 24
},
"margin": [
5,
0,
0,
0
],
"width": 10
},
{
"type": "textbox",
"name": "castStack.dissonanceRangeMax",
"width": 30
}
]
},
{
"type": "stackpanel",
"orientation": "horizontal",
"horizontalAlignment": "stretch",
"margin": [
0,
0,
0,
5
],
"children": [
{
"type": "textblock",
"text": "My HP",
"width": 70
},
{
"type": "textbox",
"name": "castStack.myHpRangeMin",
"width": 30
},
{
"type": "textblock",
"text": "-",
"font": {
"bold": true,
"height": 24
},
"margin": [
5,
0,
0,
0
],
"width": 10
},
{
"type": "textbox",
"name": "castStack.myHpRangeMax",
"width": 30
}
]
},
{
"type": "stackpanel",
"orientation": "horizontal",
"horizontalAlignment": "stretch",
"margin": [
0,
0,
0,
5
],
"children": [
{
"type": "textblock",
"text": "My Power",
"width": 70
},
{
"type": "textbox",
"name": "castStack.myPowerRangeMin",
"width": 30
},
{
"type": "textblock",
"text": "-",
"font": {
"bold": true,
"height": 24
},
"margin": [
5,
0,
0,
0
],
"width": 10
},
{
"type": "textbox",
"name": "castStack.myPowerRangeMax",
"width": 30
}
]
},
{
"type": "stackpanel",
"orientation": "horizontal",
"horizontalAlignment": "stretch",
"margin": [
0,
0,
0,
5
],
"children": [
{
"type": "textblock",
"text": "NPC HP",
"width": 70
},
{
"type": "textbox",
"name": "castStack.npcHpRangeMin",
"width": 30
},
{
"type": "textblock",
"text": "-",
"font": {
"bold": true,
"height": 24
},
"margin": [
5,
0,
0,
0
],
"width": 10
},
{
"type": "textbox",
"name": "castStack.npcHpRangeMax",
"width": 30
}
]
},
{
"type": "stackpanel",
"orientation": "horizontal",
"horizontalAlignment": "stretch",
"margin": [
0,
0,
0,
5
],
"children": [
{
"type": "textblock",
"text": "Aggro",
"width": 70
},
{
"type": "textbox",
"name": "castStack.aggroRangeMin",
"width": 30
},
{
"type": "textblock",
"text": "-",
"font": {
"bold": true,
"height": 24
},
"margin": [
5,
0,
0,
0
],
"width": 10
},
{
"type": "textbox",
"name": "castStack.aggroRangeMax",
"width": 30
}
]
}
]
},
{
"type": "stackpanel",
"orientation": "vertical",
"widthFactor": 1,
"heightFactor": 1,
"children": [
{
"type": "textblock",
"text": "Cast Order",
"widthFactor": 1,
"horizontalAlignment": "left"
},
{
"type": "listbox",
"name": "castStack.castOrder",
"horizontalAlignment": "stretch",
"heightFactor": 0.9,
"itemsBinding": {
"pullFormat": "${CastStackController.profile.Get[castStack].Keys}"
}
},
{
"type": "button",
"name": "castStack.castOrder.edit",
"content": "Edit Entry",
"horizontalAlignment": "stretch",
"eventHandlers": {
"onRelease": [
"method",
"CastStackController",
"EditEntry"
]
}
}
]
}
]
},
{
"type": "stackpanel",
"orientation": "horizontal",
"verticalAlignment": "stretch",
"horizontalAlignment": "stretch",
"children": [
{
"type": "stackpanel",
"orientation": "vertical",
"verticalAlignment": "stretch",
"horizontalAlignment": "left",
"children": [
{
"type": "button",
"name": "castStack.loadProfile",
"content": "Load Profile",
"horizontalAlignment": "left"
},
{
"type": "combobox",
"name": "castStack.profileList",
"horizontalAlignment": "left",
"itemsBinding": {
"pullFormat": "${CastStackController.ProfileList}",
"autoPull": true,
"pullHook": {
"elementName": "CastStackController.events",
"flags": "global",
"event": "onProfileListChanged"
}
}
}
]
}
]
}
]
}
}
}

174
lgui2/bot_window.json Normal file
View File

@ -0,0 +1,174 @@
{
"$schema": "http://www.lavishsoft.com/schema/lgui2Package.json",
"includes": [
"bot_cast_stack.json"
],
"skin": {
"name": "MRSkin",
"brushes": {
"window.titleBar.backgroundBrush": {
"color": "#211C18"
}
},
"templates": {
"window.title": {
"verticalAlignment": "center",
"margin": [
2,
0,
0,
0
]
},
"button": {
"jsonTemplate": "default:button",
"margin": [
2,
2,
2,
2
],
"color": "#f4f3ee"
},
"checkbox": {
"jsonTemplate": "default:checkbox",
"margin": [
2,
2,
2,
2
]
},
"window": {
"jsonTemplate": "default:window",
"backgroundBrush": {
"color": "#463f3a"
},
"color": "#f4f3ee",
"font": {
"face": "Segoe UI",
"height": 16
}
},
"listbox.contentContainerFitWidth": {
"jsonTemplate": "listbox.contentContainer",
"horizontalScroll": "fit"
}
}
},
"elements": [
{
"type": "window",
"skin": "MRSkin",
"title": "MR Bot",
"name": "mr.bot.miniwindow",
"borderThickness": 2,
"hideOnClose": false,
"minSize": {
"width": 100,
"height": 50
},
"maxSize": {
"width": 150,
"height": 125
},
"eventHandlers": {
"onCloseButtonClick": [
"method",
"BotController",
"OnClose"
]
},
"content": {
"type": "stackpanel",
"uniform": true,
"heightFactor": 1,
"children": [
{
"type": "button",
"content": "${BotController.StartButtonText}",
"horizontalAlignment": "stretch",
"eventHandlers": {
"onRelease": [
"method",
"BotController",
"ToggleBot"
]
}
},
{
"type": "button",
"content": "${BotController.SettingsButtonText}",
"horizontalAlignment": "stretch",
"eventHandlers": {
"onRelease": [
"method",
"BotController",
"ToggleSettings"
]
}
}
]
}
},
{
"type": "window",
"skin": "MRSkin",
"title": "MR Bot Settings",
"name": "mr.bot.settings",
"borderThickness": 2,
"hideOnClose": true,
"visibility": "hidden",
"minSize": {
"width": 450,
"height": 200
},
"maxSize": {
"height": 600,
"width": 800
},
"eventHandlers": {
"onCloseButtonClick": [
"method",
"BotController",
"OnCloseSettings"
]
},
"content": {
"type": "tabcontrol",
"heightFactor": 1,
"horizontalAlignment": "stretch",
"verticalAlignment": "stretch",
"tabs": [
{
"type": "tab",
"header": "Cast Stack",
"name": "mr.bot.settings.castStack",
"content": {
"jsonTemplate": "settings.castStack",
"type": "stackpanel"
}
},
{
"type": "tab",
"header": "General",
"content": {
"type": "dockpanel",
"_dock": "top",
"padding": 2,
"horizontalAlignment": "stretch",
"children": [
{
"type": "textblock",
"text": "General Settings",
"horizontalAlignment": "center",
"verticalAlignment": "center"
}
]
}
}
]
}
}
]
}

View File

@ -0,0 +1,29 @@
objectdef MRBotController
{
variable string test = "just a test"
method Initialize()
{
LGUI2:LoadPackageFile["${LavishScript.HomeDirectory}/scripts/mr/ui/bot_window.json"]
}
method Shutdown()
{
LGUI2:UnloadPackageFile["${LavishScript.HomeDirectory}/scripts/mr/ui/bot_window.json"]
}
method OnClose()
{
Event[MRBot_OnCloseButtonClicked]:Execute
}
}
variable(global) MRBotController BotController
; variable(global) MRSettingsController SettingsController
function main()
{
while 1
{
wait 5
}
}

View File

@ -0,0 +1,25 @@
objectdef MRCastStackController
{
method Initialize()
{
}
method Shutdown()
{
}
method OnClose()
{
Event[OnCloseButtonClicked]:Execute
}
}
variable(global) MRCastStackController CastStackController
function main()
{
while 1
{
wait 5
}
}

View File

@ -3,15 +3,13 @@
#include <regex> #include <regex>
#include <string> #include <string>
#include <vector> #include <vector>
//#include <nlohmann/json.hpp>
//#include <picojson/picojson.h>
#include <json_struct/json_struct.h> #include <json_struct/json_struct.h>
using namespace std; #include "ScopedEnumBitwiseOperators.h"
//using json = nlohmann::json;
enum AbilityTypeFlags { using namespace std;
enum class AbilityTypeFlags {
Debuff = 1, Debuff = 1,
Buff = 2, Buff = 2,
AE = 4, AE = 4,
@ -32,13 +30,16 @@ enum AbilityTypeFlags {
Self = 131072, Self = 131072,
}; };
enum AbilityRequirementsFlags { enum class AbilityRequirementsFlags {
NoEpic = 1, NoEpic = 1,
Flanking = 2, Flanking = 2,
Stealth = 4, Stealth = 4,
Ranged = 8, Ranged = 8,
}; };
JS_ENUM_DECLARE_VALUE_PARSER(AbilityTypeFlags);
JS_ENUM_DECLARE_VALUE_PARSER(AbilityRequirementsFlags);
struct ExportedAbility { struct ExportedAbility {
unsigned long Id; unsigned long Id;
string Name; string Name;
@ -75,8 +76,8 @@ struct ExportedAbility {
float MaxRange; float MaxRange;
unsigned int Damage; unsigned int Damage;
vector<string> Effects; vector<string> Effects;
unsigned long TypeFlags; AbilityTypeFlags TypeFlags;
unsigned short RequirementsFlags; AbilityRequirementsFlags RequirementsFlags;
JS_OBJ(Id, Name, Description, Tier, Level, HealthCost, PowerCost, DissonanceCost, SavageryCost, ConcentrationCost, JS_OBJ(Id, Name, Description, Tier, Level, HealthCost, PowerCost, DissonanceCost, SavageryCost, ConcentrationCost,
MainIconID, HOIconID, CastingTime, RecoveryTime, RecastTime, MaxDuration, NumClasses, NumEffects, MainIconID, HOIconID, CastingTime, RecoveryTime, RecastTime, MaxDuration, NumClasses, NumEffects,

View File

@ -4,35 +4,46 @@
#include "ISXMr.h" #include "ISXMr.h"
#include "Logger.h" #include "Logger.h"
#include "Commands/ExportCommand.h" #include "Tasks/BotTask.h"
#include "Tasks/ExportAbilitiesTask.h"
enum CommandType {
Export,
Test,
NotDefined
};
CommandType GetCommandType(const std::string &command) { TaskTypeEnum GetTaskType(const std::string &command) {
if (command == "export" || command == "e") { if (command == "export" || command == "e") {
return CommandType::Export; return TaskTypeEnum::Export;
} else if (command == "test" || command == "t") {
return CommandType::Test;
} }
return CommandType::NotDefined; if (command == "test" || command == "t") {
return TaskTypeEnum::Test;
}
if (command == "bot" || command == "b") {
return TaskTypeEnum::Bot;
}
return TaskTypeEnum::NotDefined;
} }
int CMD_Mr(int argc, char *argv[]) { int CMD_Mr(int argc, char *argv[]) {
const argh::parser cmdl(argv); const argh::parser cmdl(argv);
switch (const auto commandType = GetCommandType(cmdl[1])) { const auto taskType = GetTaskType(cmdl[1]);
case CommandType::Export:
executor.AddTask(std::make_shared<ExportCommand>()); if (executor.IsTaskTypeRunning(taskType)) {
logw << "Task of type " << cmdl[1] << " is already running" << std::endl;
return 0;
}
switch (taskType) {
case TaskTypeEnum::Export:
executor.AddTask(std::make_shared<ExportAbilitiesTask>());
break; break;
case CommandType::Test: case TaskTypeEnum::Bot:
executor.AddTask(BotTask::Instance());
break;
case TaskTypeEnum::Test:
log << LogLevel::Info << "Test command" << std::endl; log << LogLevel::Info << "Test command" << std::endl;
break; break;
default: default:
logw << "USAGE: mr [e|export]: Export abilities" << std::endl; logw << "USAGE: mr [e|export]: Export abilities\n"
<< " mr [b|bot]: Run the combat bot" << std::endl;
break; break;
} }

View File

@ -1,34 +0,0 @@
#ifndef COMMANDEXECUTOR_H
#define COMMANDEXECUTOR_H
#include <functional>
#include <future>
#include <mutex>
#include <queue>
#include "ExecutableCommand.h"
class CommandExecutor {
public:
CommandExecutor(): stop(false) {
}
void Shutdown();
void AddTask(std::shared_ptr<ExecutableCommand> command);
void RemoveFinishedTasks();
private:
struct Task {
std::shared_ptr<ExecutableCommand> command;
std::future<void> future;
};
std::vector<Task> tasks;
mutable std::mutex queueMutex;
std::atomic<bool> stop;
};
#endif //COMMANDEXECUTOR_H

View File

@ -1,36 +0,0 @@
#ifndef BASECOMMAND_H
#define BASECOMMAND_H
class ExecutableCommand {
public:
ExecutableCommand() : finished(false) {
}
virtual ~ExecutableCommand() = default;
virtual void Execute() = 0;
void RequestStop() {
stopRequested = true;
}
bool IsStopRequested() const {
return stopRequested;
}
bool IsFinished() const {
return finished;
}
protected:
void MarkFinished() {
finished = true;
}
bool stopRequested = false;
private:
bool finished;
};
#endif //BASECOMMAND_H

View File

@ -1,16 +0,0 @@
//
// Created by marob on 12/27/2023.
//
#ifndef EXPORT_H
#define EXPORT_H
#include "ExecutableCommand.h"
class ExportCommand final : public ExecutableCommand {
public:
void Execute() override;
};
#endif //EXPORT_H

View File

@ -22,7 +22,7 @@
#include "isxeq2/Character.h" #include "isxeq2/Character.h"
#include "../lgui2/test.json.h" #include "../lgui2/test.json.h"
#include "../scripts/bot.iss.h" #include "../scripts/bot.iss.h"
#include "Commands/CommandExecutor.h" #include "Tasks/TaskExecutor.h"
#pragma comment(lib,"isxdk.lib") #pragma comment(lib,"isxdk.lib")
// The mandatory pre-setup function. Our name is "ISXMr", and the class is ISXMr. // The mandatory pre-setup function. Our name is "ISXMr", and the class is ISXMr.
// This sets up a "ModulePath" variable which contains the path to this module in case we want it, // This sets up a "ModulePath" variable which contains the path to this module in case we want it,
@ -85,23 +85,23 @@ bool ISXMr::Initialize(ISInterface *p_ISInterface) {
* Most of the functionality in Initialize is completely optional and could be removed or * Most of the functionality in Initialize is completely optional and could be removed or
* changed if so desired. The defaults are simply a suggestion that can be easily followed. * changed if so desired. The defaults are simply a suggestion that can be easily followed.
*/ */
constexpr size_t innerspacePathBufferLength = 255; // constexpr size_t innerspacePathBufferLength = 255;
char innerspacePathBuffer[innerspacePathBufferLength]; // char innerspacePathBuffer[innerspacePathBufferLength];
const std::filesystem::path innerspacePath = p_ISInterface->GetInnerSpacePath( // const std::filesystem::path innerspacePath = p_ISInterface->GetInnerSpacePath(
innerspacePathBuffer, innerspacePathBufferLength); // innerspacePathBuffer, innerspacePathBufferLength);
std::filesystem::path fullPath = innerspacePath / R"(scripts\mr\ui\test.json)"; // std::filesystem::path fullPath = innerspacePath / R"(scripts\mr\ui\test.json)";
//
if (fullPath.has_parent_path() && !std::filesystem::exists(fullPath.parent_path())) { // if (fullPath.has_parent_path() && !std::filesystem::exists(fullPath.parent_path())) {
std::filesystem::create_directories(fullPath.parent_path()); // std::filesystem::create_directories(fullPath.parent_path());
} // }
//
std::ofstream file(fullPath, std::ios::binary); // std::ofstream file(fullPath, std::ios::binary);
if (!file) { // if (!file) {
std::cerr << "Error opening file for writing: " << fullPath << std::endl; // std::cerr << "Error opening file for writing: " << fullPath << std::endl;
} // }
//
file.write(reinterpret_cast<const char *>(test_json), test_json_len); // file.write(reinterpret_cast<const char *>(test_json), test_json_len);
file.close(); // file.close();
//__try // exception handling. See __except below. //__try // exception handling. See __except below.
@ -141,7 +141,7 @@ bool ISXMr::Initialize(ISInterface *p_ISInterface) {
// Register any text triggers built into ISXMr // Register any text triggers built into ISXMr
RegisterTriggers(); RegisterTriggers();
pISInterface->RunScriptFromBuffer("mrbot", reinterpret_cast<const char *>(bot_iss), bot_iss_len); // pISInterface->RunScriptFromBuffer("mrbot", reinterpret_cast<const char *>(bot_iss), bot_iss_len);
printf("ISXMr version %s Loaded", Mr_Version); printf("ISXMr version %s Loaded", Mr_Version);
return true; return true;
} }
@ -315,11 +315,11 @@ void __cdecl OnCloseButtonClicked(int argc, char *argv[], PLSOBJECT lsObj) {
} }
void ISXMr::RegisterEvents() { void ISXMr::RegisterEvents() {
onGetTargetEventId = pISInterface->RegisterEvent("OnGetTarget"); // onGetTargetEventId = pISInterface->RegisterEvent("OnGetTarget");
pISInterface->AttachEventTarget(onGetTargetEventId, OnGetTargetEvent); // pISInterface->AttachEventTarget(onGetTargetEventId, OnGetTargetEvent);
//
onCloseButtonClickedEventId = pISInterface->RegisterEvent("OnCloseButtonClicked"); // onCloseButtonClickedEventId = pISInterface->RegisterEvent("OnCloseButtonClicked");
pISInterface->AttachEventTarget(onCloseButtonClickedEventId, OnCloseButtonClicked); // pISInterface->AttachEventTarget(onCloseButtonClickedEventId, OnCloseButtonClicked);
} }
void ISXMr::DisconnectServices() { void ISXMr::DisconnectServices() {
@ -382,14 +382,14 @@ void ISXMr::UnRegisterServices() {
} }
void ISXMr::UnRegisterEvents() { void ISXMr::UnRegisterEvents() {
pISInterface->DetachEventTarget(onGetTargetEventId, OnGetTargetEvent); // pISInterface->DetachEventTarget(onGetTargetEventId, OnGetTargetEvent);
pISInterface->UnregisterEvent(onGetTargetEventId); // pISInterface->UnregisterEvent(onGetTargetEventId);
//
pISInterface->DetachEventTarget(onCloseButtonClickedEventId, OnCloseButtonClicked); // pISInterface->DetachEventTarget(onCloseButtonClickedEventId, OnCloseButtonClicked);
pISInterface->UnregisterEvent(onCloseButtonClickedEventId); // pISInterface->UnregisterEvent(onCloseButtonClickedEventId);
} }
CommandExecutor executor; TaskExecutor executor;
std::chrono::milliseconds interval(100);; std::chrono::milliseconds interval(100);;
std::chrono::steady_clock::time_point nextCleanup = std::chrono::steady_clock::now() + interval; std::chrono::steady_clock::time_point nextCleanup = std::chrono::steady_clock::now() + interval;

View File

@ -2,7 +2,7 @@
#include <ISXDK.h> #include <ISXDK.h>
#include <windows.h> #include <windows.h>
#include "Commands/CommandExecutor.h" #include "Tasks/TaskExecutor.h"
class ISXMr : class ISXMr :
@ -56,7 +56,7 @@ extern HISXSERVICE hHTTPService;
extern HISXSERVICE hTriggerService; extern HISXSERVICE hTriggerService;
extern HISXSERVICE hSystemService; extern HISXSERVICE hSystemService;
extern CommandExecutor executor;; extern TaskExecutor executor;;
extern ISXMr *pExtension; extern ISXMr *pExtension;
#define printf pISInterface->Printf #define printf pISInterface->Printf

View File

@ -75,7 +75,7 @@ private:
const auto now = std::chrono::system_clock::now(); const auto now = std::chrono::system_clock::now();
const auto now_c = std::chrono::system_clock::to_time_t(now); const auto now_c = std::chrono::system_clock::to_time_t(now);
std::tm now_tm; std::tm now_tm = {};
localtime_s(&now_tm, &now_c); // Thread-safe on Windows localtime_s(&now_tm, &now_c); // Thread-safe on Windows
std::ostringstream oss; std::ostringstream oss;

View File

@ -0,0 +1,59 @@
#ifndef SCOPEDENUMBITWISEOPERATORS_H
#define SCOPEDENUMBITWISEOPERATORS_H
#include <type_traits>
template<typename Enum>
std::enable_if_t<std::is_enum_v<Enum>, Enum>
operator^(Enum lhs, Enum rhs) {
using underlying = std::underlying_type_t<Enum>;
return static_cast<Enum>(static_cast<underlying>(lhs) ^ static_cast<underlying>(rhs));
}
template<typename Enum>
std::enable_if_t<std::is_enum_v<Enum>, Enum>
operator|(Enum lhs, Enum rhs) {
using underlying = std::underlying_type_t<Enum>;
return static_cast<Enum>(static_cast<underlying>(lhs) | static_cast<underlying>(rhs));
}
template<typename Enum>
std::enable_if_t<std::is_enum_v<Enum>, Enum>
operator&(Enum lhs, Enum rhs) {
using underlying = std::underlying_type_t<Enum>;
return static_cast<Enum>(static_cast<underlying>(lhs) & static_cast<underlying>(rhs));
}
template<typename Enum>
std::enable_if_t<std::is_enum_v<Enum>, Enum>
operator~(Enum rhs) {
using underlying = std::underlying_type_t<Enum>;
return static_cast<Enum>(~static_cast<underlying>(rhs));
}
template<typename Enum>
std::enable_if_t<std::is_enum_v<Enum>, Enum>
operator|=(Enum &lhs, Enum rhs) {
using underlying = std::underlying_type_t<Enum>;
lhs = static_cast<Enum>(static_cast<underlying>(lhs) | static_cast<underlying>(rhs));
return lhs;
}
template<typename Enum>
std::enable_if_t<std::is_enum_v<Enum>, Enum>
operator&=(Enum &lhs, Enum rhs) {
using underlying = std::underlying_type_t<Enum>;
lhs = static_cast<Enum>(static_cast<underlying>(lhs) & static_cast<underlying>(rhs));
return lhs;
}
template<typename Enum>
std::enable_if_t<std::is_enum_v<Enum>, Enum>
operator^=(Enum &lhs, Enum rhs) {
using underlying = std::underlying_type_t<Enum>;
lhs = static_cast<Enum>(static_cast<underlying>(lhs) ^ static_cast<underlying>(rhs));
return lhs;
}
#endif //SCOPEDENUMBITWISEOPERATORS_H

77
src/Tasks/BotTask.cpp Normal file
View File

@ -0,0 +1,77 @@
#include "BotTask.h"
#include "ISXMr.h"
#include <chrono>
#include <thread>
#include "../../lgui2/UpdateUIPackageFile.h"
#include "../../lgui2/bot_window.json.h"
#include "../../lgui2/bot_cast_stack.json.h"
#include "../../scripts/bot_controller.iss.h"
std::weak_ptr<BotTask> BotTask::instance;
std::shared_ptr<BotTask> BotTask::Instance() {
auto existingInstance = instance.lock();
if (!existingInstance) {
existingInstance = std::shared_ptr<BotTask>(new BotTask());
instance = existingInstance;
}
return existingInstance;
}
BotTask::BotTask() {
UpdateUIPackageFile(
"bot_window.json",
reinterpret_cast<const char *>(bot_window_json),
bot_window_json_len,
bot_window_json_last_modified
);
UpdateUIPackageFile(
"bot_cast_stack.json",
reinterpret_cast<const char *>(bot_cast_stack_json),
bot_cast_stack_json_len,
bot_cast_stack_json_last_modified
);
pISInterface->RunScriptFromBuffer(
ScriptName.c_str(),
reinterpret_cast<const char *>(bot_controller_iss),
bot_controller_iss_len
);
botController = make_shared<LSObject>(LSObject::FromDataParse("${BotController}"));
this->onCloseButtonClickedEventId = pISInterface->RegisterEvent(OnClosedEventName.c_str());
pISInterface->AttachEventTarget(this->onCloseButtonClickedEventId,
&BotTask::OnCloseEventHandler);
}
void __cdecl BotTask::OnCloseEventHandler(int argc, char *argv[], PLSOBJECT plsObject) {
Instance()->Close();
}
BotTask::~BotTask() {
const std::string bufferScriptName = "Buffer:" + ScriptName;
pISInterface->EndScript(bufferScriptName.c_str());
pISInterface->DetachEventTarget(this->onCloseButtonClickedEventId, &BotTask::OnCloseEventHandler);
pISInterface->UnregisterEvent(this->onCloseButtonClickedEventId);
}
void BotTask::Execute() {
while (!this->stopRequested && !this->IsFinished()) {
// sleep for 100 ms
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
void BotTask::Close() {
this->MarkFinished();
}
TaskTypeEnum BotTask::TaskType() const {
return TaskTypeEnum::Bot;
}

37
src/Tasks/BotTask.h Normal file
View File

@ -0,0 +1,37 @@
#ifndef BOTTASK_H
#define BOTTASK_H
#include <memory>
#include <string>
#include "ExecutableTask.h"
#include "isxeq2/LSObject.h"
class BotTask final : public ExecutableTask, public std::enable_shared_from_this<BotTask> {
public:
static std::shared_ptr<BotTask> Instance();
~BotTask() override;
void Execute() override;
void Close();
[[nodiscard]] TaskTypeEnum TaskType() const override;
private:
BotTask();
static std::weak_ptr<BotTask> instance;
const std::string ScriptName = "MRBot";
const std::string OnClosedEventName = "MRBot_OnCloseButtonClicked";
std::shared_ptr<LSObject> botController;
std::shared_ptr<LSObject> settingsController;
u_int onCloseButtonClickedEventId = 0;
static void __cdecl OnCloseEventHandler(int argc, char *argv[], PLSOBJECT plsObject);
};
#endif //BOTTASK_H

View File

@ -0,0 +1,46 @@
#ifndef BASETASK_H
#define BASETASK_H
enum class TaskTypeEnum {
Export,
Bot,
Test,
NotDefined
};
class ExecutableTask {
public:
ExecutableTask() : finished(false) {
}
virtual ~ExecutableTask() = default;
virtual void Execute() = 0;
[[nodiscard]]
virtual TaskTypeEnum TaskType() const = 0;
void RequestStop() {
stopRequested = true;
}
[[nodiscard]] bool IsStopRequested() const {
return stopRequested;
}
[[nodiscard]] bool IsFinished() const {
return finished;
}
protected:
void MarkFinished() {
finished = true;
}
bool stopRequested = false;
private:
bool finished;
};
#endif //BASETASK_H

View File

@ -5,7 +5,7 @@
#include<json_struct/json_struct.h> #include<json_struct/json_struct.h>
#include "ISXMr.h" #include "ISXMr.h"
#include "ExportCommand.h" #include "ExportAbilitiesTask.h"
#include <filesystem> #include <filesystem>
@ -23,7 +23,7 @@ AbilityInfo GetAbilityInfo(const int idx) {
return ability.GetAbilityInfo(); return ability.GetAbilityInfo();
} }
void ExportCommand::Execute() { void ExportAbilitiesTask::Execute() {
try { try {
log << "Exporting abilities" << endl; log << "Exporting abilities" << endl;
const auto numAbilities = ExtensionTLOs::Me().NumAbilities(); const auto numAbilities = ExtensionTLOs::Me().NumAbilities();

View File

@ -0,0 +1,15 @@
#ifndef EXPORTABILITIESTASK_H
#define EXPORTABILITIESTASK_H
#include "ExecutableTask.h"
class ExportAbilitiesTask final : public ExecutableTask {
public:
void Execute() override;
[[nodiscard]] TaskTypeEnum TaskType() const override {
return TaskTypeEnum::Export;
}
};
#endif //EXPORTABILITIESTASK_H

View File

@ -1,9 +1,9 @@
#include "CommandExecutor.h" #include "TaskExecutor.h"
#include "Logger.h" #include "Logger.h"
void CommandExecutor::Shutdown() { void TaskExecutor::Shutdown() {
stop = true; stop = true;
for (auto &[command, future]: tasks) { for (auto &[command, future]: tasks) {
if (future.valid()) { if (future.valid()) {
@ -22,7 +22,7 @@ void CommandExecutor::Shutdown() {
} }
void CommandExecutor::AddTask(std::shared_ptr<ExecutableCommand> command) { void TaskExecutor::AddTask(std::shared_ptr<ExecutableTask> command) {
std::lock_guard<std::mutex> lock(queueMutex); std::lock_guard<std::mutex> lock(queueMutex);
std::future<void> future = std::async(std::launch::async, [command]() { std::future<void> future = std::async(std::launch::async, [command]() {
try { try {
@ -34,7 +34,15 @@ void CommandExecutor::AddTask(std::shared_ptr<ExecutableCommand> command) {
tasks.push_back({command, std::move(future)}); tasks.push_back({command, std::move(future)});
} }
void CommandExecutor::RemoveFinishedTasks() { bool TaskExecutor::IsTaskTypeRunning(TaskTypeEnum taskType) const {
std::lock_guard lock(queueMutex);
return std::ranges::any_of(tasks, [taskType](const Task &task) {
return task.task->TaskType() == taskType;
});
}
void TaskExecutor::RemoveFinishedTasks() {
std::lock_guard<std::mutex> lock(queueMutex); std::lock_guard<std::mutex> lock(queueMutex);
std::erase_if(tasks, [](Task &task) { std::erase_if(tasks, [](Task &task) {
if (task.future.valid() && task.future.wait_for(std::chrono::seconds(0)) == std::future_status::ready) { if (task.future.valid() && task.future.wait_for(std::chrono::seconds(0)) == std::future_status::ready) {

42
src/Tasks/TaskExecutor.h Normal file
View File

@ -0,0 +1,42 @@
#ifndef TASKEXECUTOR_H
#define TASKEXECUTOR_H
#include <functional>
#include <future>
#include <mutex>
#include <queue>
#include "ExecutableTask.h"
enum TaskType {
Export,
Bot,
Test,
NotDefined
};
class TaskExecutor {
public:
TaskExecutor(): stop(false) {
}
void Shutdown();
void AddTask(std::shared_ptr<ExecutableTask> command);
bool IsTaskTypeRunning(TaskTypeEnum taskType) const;
void RemoveFinishedTasks();
private:
struct Task {
std::shared_ptr<ExecutableTask> task;
std::future<void> future;
};
std::vector<Task> tasks;
mutable std::mutex queueMutex;
std::atomic<bool> stop;
};
#endif //TASKEXECUTOR_H

View File

@ -1,37 +0,0 @@
#include <filesystem>
#include <fstream>
#include <iostream>
#include <chrono>
std::chrono::system_clock::time_point ParseEmbeddedTimestamp(const std::string& timestamp) {
std::tm tm = {};
std::istringstream ss(timestamp);
ss >> std::get_time(&tm, "%Y-%m-%d %H:%M:%S");
return std::chrono::system_clock::from_time_t(std::mktime(&tm));
}
void WriteUIFileToDiskIfChanged(
const std::string& filePath,
const unsigned char* data,
const size_t length,
const std::string& lastModified) {
auto embeddedTimestamp = ParseEmbeddedTimestamp(lastModified);
if (std::filesystem::exists(filePath)) {
const auto lastWriteTime = std::filesystem::last_write_time(filePath);
// Convert file_time_type to system_clock::time_point
const auto lastWriteTimeTp = std::chrono::time_point_cast<std::chrono::system_clock::duration>(
lastWriteTime - std::filesystem::file_time_type::clock::now() + std::chrono::system_clock::now()
);
if (lastWriteTimeTp < embeddedTimestamp) {
std::ofstream file(filePath, std::ios::binary);
file.write(reinterpret_cast<const char*>(data), length);
}
} else {
std::ofstream file(filePath, std::ios::binary);
file.write(reinterpret_cast<const char*>(data), length);
}
}

View File

@ -76,23 +76,23 @@ ExportedAbility Ability::ToExportedAbility() const {
} }
if (RequiresStealth(exportedAbility.Effects)) { if (RequiresStealth(exportedAbility.Effects)) {
exportedAbility.RequirementsFlags |= Stealth; exportedAbility.RequirementsFlags |= AbilityRequirementsFlags::Stealth;
} }
if (RequiresFlanking(exportedAbility.Effects)) { if (RequiresFlanking(exportedAbility.Effects)) {
exportedAbility.RequirementsFlags |= Flanking; exportedAbility.RequirementsFlags |= AbilityRequirementsFlags::Flanking;
} }
if (RequiresNonEpic(exportedAbility.Effects)) { if (RequiresNonEpic(exportedAbility.Effects)) {
exportedAbility.RequirementsFlags |= NoEpic; exportedAbility.RequirementsFlags |= AbilityRequirementsFlags::NoEpic;
} }
if (IsStun(exportedAbility.Effects)) { if (IsStun(exportedAbility.Effects)) {
exportedAbility.TypeFlags |= Stun; exportedAbility.TypeFlags |= AbilityTypeFlags::Stun;
} }
if (IsInterrupt(exportedAbility.Effects)) { if (IsInterrupt(exportedAbility.Effects)) {
exportedAbility.TypeFlags |= Interrupt; exportedAbility.TypeFlags |= AbilityTypeFlags::Interrupt;
} }

View File

@ -36,7 +36,7 @@ public:
// Use a fold expression to convert each argument to a string // Use a fold expression to convert each argument to a string
const std::vector<std::string> arguments = {toString(args)...}; const std::vector<std::string> arguments = {toString(args)...};
const int argc = arguments.size(); const size_t argc = arguments.size();
std::vector<char *> argv; std::vector<char *> argv;
for (auto &arg: arguments) { for (auto &arg: arguments) {
@ -49,7 +49,7 @@ public:
if (const auto result = this->lsObject->Type->GetMemberEx( if (const auto result = this->lsObject->Type->GetMemberEx(
this->lsObject->GetObjectData(), this->lsObject->GetObjectData(),
const_cast<char *>(memberName.c_str()), const_cast<char *>(memberName.c_str()),
argc, static_cast<int>(argc),
argv.data(), argv.data(),
response response
); !result) { ); !result) {
@ -75,7 +75,7 @@ public:
// Use a fold expression to convert each argument to a string // Use a fold expression to convert each argument to a string
const std::vector<std::string> arguments = {toString(args)...}; const std::vector<std::string> arguments = {toString(args)...};
const int argc = arguments.size(); const size_t argc = arguments.size();
std::vector<char *> argv; std::vector<char *> argv;
for (auto &arg: arguments) { for (auto &arg: arguments) {
@ -85,14 +85,22 @@ public:
this->lsObject->Type->GetMethodEx( this->lsObject->Type->GetMethodEx(
this->lsObject->GetObjectData(), this->lsObject->GetObjectData(),
const_cast<char *>(methodName.c_str()), const_cast<char *>(methodName.c_str()),
argc, static_cast<int>(argc),
argv.data()); argv.data());
} }
bool IsValid() const { [[nodiscard]] bool IsValid() const {
return this->lsObject.has_value(); return this->lsObject.has_value();
} }
static LSObject FromDataParse(const std::string &dataToParse) {
if (LSOBJECT rawObject; pISInterface->DataParse(dataToParse.c_str(), rawObject)) {
return LSObject(rawObject);
}
return LSObject(nullopt);
}
private: private:
optional<LSOBJECT> lsObject; optional<LSOBJECT> lsObject;
}; };