diff --git a/release/docs/MultiSend.txt b/release/docs/MultiSend.txt
new file mode 100644
index 0000000..7a1e68c
--- /dev/null
+++ b/release/docs/MultiSend.txt
@@ -0,0 +1,16 @@
+Multisend is a replacement for servo. No synchronization is needed, just load and go.
+Commands are as follows(all can be prefixed with /ms or /multisend):
+
+/ms send [command] - Sends the command to all characters with MultiSend loaded.
+/ms sendto CharName [command] - Sends the command to a specific character, if they have MultiSend loaded.
+/ms sendgroup GroupName [command] - Sends the command to all characters listed in a group. Groups can be defined in Ashita/Config/MultiSend.xml. See XML Structure file for example.
+/ms followme on/off - Enables or disables followme. When followme is enabled, any characters with follow enabled will follow the player who last activated followme.
+/ms follow on/off - Enables or disables follow. Any character with follow enabled with follow the player who last activated followme.
+/ms ignoreself on/off - When enabled, any command that would effect the local character is not executed by the local character.
+/ms debug on/off - When enabled, all received commands are printed to log and any character with followme updated will spam log with position updates. For debug purposes, obviously.
+/ms reload - Will reload groups xml.
+/ms help - Print an ingame reference.
+
+You may use [t] [me] or [p0] [p1] [p2] [p3] [p4] [p5] to replace the <> equivalents, if you wish to resolve them on local character. This will replace them with the appropriate server IDs, allowing the command to resolve on receiving character.
+
+Example: Typing /ms send /ma "Fire IV" [t] will make all characters with multisend loaded cast Fire IV on the character who typed the command's target, rather than their own current target.
\ No newline at end of file
diff --git a/release/docs/XML Structure.xml b/release/docs/XML Structure.xml
new file mode 100644
index 0000000..3820e71
--- /dev/null
+++ b/release/docs/XML Structure.xml
@@ -0,0 +1,10 @@
+
+
+ Avesta
+ Stanlee
+
+
+ Deadpool
+ Wolverine
+
+
\ No newline at end of file
diff --git a/release/plugins/MultiSend.dll b/release/plugins/MultiSend.dll
new file mode 100644
index 0000000..1bb6b2c
Binary files /dev/null and b/release/plugins/MultiSend.dll differ
diff --git a/src/MultiSend.sln b/src/MultiSend.sln
new file mode 100644
index 0000000..f786c16
--- /dev/null
+++ b/src/MultiSend.sln
@@ -0,0 +1,28 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 14
+VisualStudioVersion = 14.0.25420.1
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MultiSend", "MultiSend\MultiSend.vcxproj", "{3C74E1A4-E44B-4523-85DC-9A94AC2D6457}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {3C74E1A4-E44B-4523-85DC-9A94AC2D6457}.Debug|x64.ActiveCfg = Debug|x64
+ {3C74E1A4-E44B-4523-85DC-9A94AC2D6457}.Debug|x64.Build.0 = Debug|x64
+ {3C74E1A4-E44B-4523-85DC-9A94AC2D6457}.Debug|x86.ActiveCfg = Debug|Win32
+ {3C74E1A4-E44B-4523-85DC-9A94AC2D6457}.Debug|x86.Build.0 = Debug|Win32
+ {3C74E1A4-E44B-4523-85DC-9A94AC2D6457}.Release|x64.ActiveCfg = Release|x64
+ {3C74E1A4-E44B-4523-85DC-9A94AC2D6457}.Release|x64.Build.0 = Release|x64
+ {3C74E1A4-E44B-4523-85DC-9A94AC2D6457}.Release|x86.ActiveCfg = Release|Win32
+ {3C74E1A4-E44B-4523-85DC-9A94AC2D6457}.Release|x86.Build.0 = Release|Win32
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/src/MultiSend/Commands.cpp b/src/MultiSend/Commands.cpp
new file mode 100644
index 0000000..65a50d4
--- /dev/null
+++ b/src/MultiSend/Commands.cpp
@@ -0,0 +1,241 @@
+#include "MultiSend.h"
+
+bool MultiSend::HandleCommand(const char* command, int32_t type)
+{
+ std::string arg;
+ const char* com = command + GetArg(command, &arg);
+ if ((_stricmp(arg.c_str(), "/ms"))
+ && (_stricmp(arg.c_str(), "/multisend")))
+ {
+ return false;
+ }
+
+ if (strlen(com) < 2) return true;
+ com++;
+ com += GetArg(com, &arg);
+
+
+ if (_stricmp(arg.c_str(), "send") == 0)
+ {
+ if (strlen(com) < 2) return true;
+ SendCommand(0xFFFF0000, com + 1);
+ return true;
+ }
+
+ if (_stricmp(arg.c_str(), "sendto") == 0)
+ {
+ if (strlen(com) < 2)
+ {
+ m_AshitaCore->GetChatManager()->Write("MultiSend: Invalid command.");
+ return true;
+ }
+ com++;
+ com += GetArg(com, &arg);
+
+ Claim(&(MMF_Pointer->Name.ProcessID), 0);
+ for (int x = 0; x < 100; x++)
+ {
+ if (MMF_Pointer->Name.Names[x].Active)
+ {
+ if (_stricmp((const char*)&(MMF_Pointer->Name.Names[x].Name), arg.c_str()) == 0)
+ {
+ SendCommand(MMF_Pointer->Name.Names[x].Process, com + 1);
+ InterlockedExchange(&(MMF_Pointer->Name.ProcessID), 0);
+ return true;
+ }
+ }
+ }
+
+ InterlockedExchange(&(MMF_Pointer->Name.ProcessID), 0);
+ m_AshitaCore->GetChatManager()->Write("MultiSend: Character not found.");
+ }
+
+ if (_stricmp(arg.c_str(), "sendgroup") == 0)
+ {
+ if (strlen(com) < 3)
+ {
+ m_AshitaCore->GetChatManager()->Write("MultiSend: Invalid command.");
+ return true;
+ }
+ com++;
+ com += GetArg(com, &arg);
+
+ for (std::map::iterator it = m_Group.Map.begin(); it != m_Group.Map.end(); it++)
+ {
+ if (_stricmp((*it).second.c_str(), arg.c_str()) == 0)
+ {
+ SendCommand(0x8FFF0000 + (*it).first, com + 1);
+ return true;
+ }
+ }
+
+ m_AshitaCore->GetChatManager()->Write("MultiSend: Group not found.");
+ return true;
+ }
+
+ if (_stricmp(arg.c_str(), "followme") == 0)
+ {
+ if (strlen(com) < 2) return true;
+ com++;
+ com += GetArg(com, &arg);
+
+ if (_stricmp(arg.c_str(), "on") == 0)
+ {
+ InterlockedExchange(&(MMF_Pointer->Follow.FollowID), GetCurrentProcessId());
+ if (!Zoning)
+ {
+ Following = false;
+ uint16_t myindex = m_AshitaCore->GetDataManager()->GetParty()->GetMemberTargetIndex(0);
+ uint32_t mPosX = (uint32_t)floor(m_AshitaCore->GetDataManager()->GetEntity()->GetLocalX(myindex) * 100);
+ uint32_t mPosZ = (uint32_t)floor(m_AshitaCore->GetDataManager()->GetEntity()->GetLocalZ(myindex) * 100);
+ MMF_Pointer->Follow.PosX = mPosX;
+ MMF_Pointer->Follow.PosZ = mPosZ;
+ MMF_Pointer->Follow.Follow = 1;
+ }
+ else
+ {
+ Following = false;
+ MMF_Pointer->Follow.Follow = 3;
+ }
+ m_AshitaCore->GetChatManager()->Write("MultiSend: Followme enabled.");
+ }
+
+ else if (_stricmp(arg.c_str(), "off") == 0)
+ {
+ InterlockedExchange(&(MMF_Pointer->Follow.FollowID), 0);
+ MMF_Pointer->Follow.Follow = 0;
+ m_AshitaCore->GetChatManager()->Write("MultiSend: Followme disabled.");
+ }
+
+ else
+ {
+ m_AshitaCore->GetChatManager()->Writef("MultiSend: Followme is currently %s.", (MMF_Pointer->Follow.FollowID == GetCurrentProcessId()) ? "enabled" : "disabled");
+ }
+
+ return true;
+ }
+
+ if (_stricmp(arg.c_str(), "follow") == 0)
+ {
+ if (strlen(com) < 2) return true;
+ com++;
+ com += GetArg(com, &arg);
+
+ if (_stricmp(arg.c_str(), "on") == 0)
+ {
+ FollowEnabled = true;
+ m_AshitaCore->GetChatManager()->Write("MultiSend: Follow enabled. Note that if someone is not currently using followme, no movement will occur.");
+ }
+
+ else if (_stricmp(arg.c_str(), "off") == 0)
+ {
+ FollowEnabled = false;
+ m_AshitaCore->GetChatManager()->Write("MultiSend: Follow disabled.");
+ }
+
+ else
+ {
+ m_AshitaCore->GetChatManager()->Writef("MultiSend: Follow is currently %s.", FollowEnabled ? "enabled" : "disabled");
+ }
+
+ return true;
+ }
+
+ if (_stricmp(arg.c_str(), "ignoreself") == 0)
+ {
+ if (strlen(com) < 2) return true;
+ com++;
+ com += GetArg(com, &arg);
+
+ if (_stricmp(arg.c_str(), "on") == 0)
+ {
+ IgnoreSelf = true;
+ m_AshitaCore->GetChatManager()->Write("MultiSend: Now ignoring self-published commands.");
+ }
+
+ else if (_stricmp(arg.c_str(), "off") == 0)
+ {
+ IgnoreSelf = false;
+ m_AshitaCore->GetChatManager()->Write("MultiSend: Now accepting self-published commands.");
+ }
+
+ else
+ {
+ m_AshitaCore->GetChatManager()->Writef("MultiSend: Ignore self is currently %s.", IgnoreSelf ? "enabled" : "disabled");
+ }
+ return true;
+ }
+
+ if (_stricmp(arg.c_str(), "debug") == 0)
+ {
+ if (strlen(com) < 2) return true;
+ com++;
+ com += GetArg(com, &arg);
+
+ if (_stricmp(arg.c_str(), "on") == 0)
+ {
+ IgnoreSelf = true;
+ m_AshitaCore->GetChatManager()->Write("MultiSend: Debug prints enabled.");
+ }
+
+ else if (_stricmp(arg.c_str(), "off") == 0)
+ {
+ IgnoreSelf = false;
+ m_AshitaCore->GetChatManager()->Write("MultiSend: Debug prints disabled.");
+ }
+
+ else
+ {
+ m_AshitaCore->GetChatManager()->Writef("MultiSend: Debug prints currently %s.", _Debug ? "enabled" : "disabled");
+ }
+
+ return true;
+ }
+
+ if (_stricmp(arg.c_str(), "reload") == 0)
+ {
+ LoadGroups();
+ return true;
+ }
+
+ if (_stricmp(arg.c_str(), "help") == 0)
+ {
+ m_AshitaCore->GetChatManager()->Write("Multisend Command Listing");
+ m_AshitaCore->GetChatManager()->Write("/ms send [command] - Sends [command] to all characters with multisend loaded.");
+ m_AshitaCore->GetChatManager()->Write("/ms sendto [char name] [command] - Sends [command] to all characters with multisend loaded named [char name].");
+ m_AshitaCore->GetChatManager()->Write("/ms sendgroup [group name] [command] - Sends [command] to all characters with multisend loaded that are defined within the group [group name].");
+ m_AshitaCore->GetChatManager()->Write("/ms follow on/off - When enabled, the current character will obey followme.");
+ m_AshitaCore->GetChatManager()->Write("/ms followme on/off - When enabled, all characters with follow enabled will follow this character. Only one character can have this active at a time, if you activate it on a second the first will deactivate automatically.");
+ m_AshitaCore->GetChatManager()->Write("/ms reload - Reloads group file without reloading MultiSend.");
+ m_AshitaCore->GetChatManager()->Write("/ms ignoreself on/off - When enabled, send and sendgroup commands sent by this character will not execute on this character.");
+ m_AshitaCore->GetChatManager()->Write("/ms debug on/off - When enabled, debug prints will be visible.");
+ return true;
+ }
+
+ return true;
+}
+
+uint32_t MultiSend::GetArg(const char* text, std::string* buffer)
+{
+ std::string working(text);
+
+ if (working[0] == '"')
+ {
+ size_t second = working.substr(1).find_first_of('"');
+ if (second != string::npos)
+ {
+ *buffer = working.substr(1, second);
+ return second + 1;
+ }
+ }
+
+ size_t space = working.find_first_of(' ');
+ if (space != string::npos)
+ {
+ *buffer = working.substr(0, space);
+ return space;
+ }
+
+ *buffer = string(text);
+ return strlen(text);
+}
\ No newline at end of file
diff --git a/src/MultiSend/Exports.def b/src/MultiSend/Exports.def
new file mode 100644
index 0000000..f04134d
--- /dev/null
+++ b/src/MultiSend/Exports.def
@@ -0,0 +1,5 @@
+LIBRARY "MultiSend"
+EXPORTS
+ GetInterfaceVersion
+ CreatePluginInfo
+ CreatePlugin
\ No newline at end of file
diff --git a/src/MultiSend/Follow.cpp b/src/MultiSend/Follow.cpp
new file mode 100644
index 0000000..121a6c9
--- /dev/null
+++ b/src/MultiSend/Follow.cpp
@@ -0,0 +1,43 @@
+#include "MultiSend.h"
+
+bool MultiSend::Direct3DInitialize(IDirect3DDevice8* lpDevice)
+{
+ this->m_Direct3DDevice = lpDevice;
+ return true;
+}
+
+/**
+* @brief Direct3D prerender call to allow this plugin to prepare for rendering.
+*
+* @note This will only be called if you returned true in Direct3DInitialize!
+*/
+void MultiSend::Direct3DPreRender(void)
+{
+ if (FollowEnabled)
+ {
+ if (Following)
+ {
+ uint16_t myindex = m_AshitaCore->GetDataManager()->GetParty()->GetMemberTargetIndex(0);
+ float MyX = m_AshitaCore->GetDataManager()->GetEntity()->GetLocalX(myindex);
+ float MyZ = m_AshitaCore->GetDataManager()->GetEntity()->GetLocalZ(myindex);
+ double Distance = sqrt(pow(PositionX - MyX, 2) + pow(PositionZ - MyZ, 2));
+
+ if (Distance > 0.4f)
+ {
+ StructPointer->DirX = PositionX - MyX;
+ StructPointer->DirY = 0;
+ StructPointer->DirZ = PositionZ - MyZ;
+ StructPointer->Autorun = 1;
+ }
+ else
+ {
+ StructPointer->Autorun = 0;
+ }
+ }
+ else if (StopFollow)
+ {
+ StructPointer->Autorun = 0;
+ StopFollow = false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/MultiSend/Groups.cpp b/src/MultiSend/Groups.cpp
new file mode 100644
index 0000000..0b97005
--- /dev/null
+++ b/src/MultiSend/Groups.cpp
@@ -0,0 +1,126 @@
+#include "MultiSend.h"
+#include
+#include
+using namespace rapidxml;
+
+bool MultiSend::MatchID(uint32_t ID)
+{
+ if ((ID & 0xFFFF0000) == 0xFFFF0000)
+ {
+ return true;
+ }
+ if ((ID & 0x8FFF0000) == 0x8FFF0000)
+ {
+ uint16_t GroupID = (ID & 0x0000FFFF);
+ return (std::find(Groups.begin(), Groups.end(), GroupID) != Groups.end());
+ }
+ return (::GetCurrentProcessId() == ID);
+}
+
+void MultiSend::LoadGroups()
+{
+ m_Group.Map.clear();
+ Groups.clear();
+ string Path = m_AshitaCore->GetAshitaInstallPathA();
+ Path += "config\\MultiSend.xml";
+
+ std::ifstream Reader(Path, ios::in | ios::binary | ios::ate);
+ if (Reader.is_open())
+ {
+ Reader.seekg(0, ios::end);
+ uint32_t Size = (uint32_t)Reader.tellg();
+
+ m_Group.rawdata = new char[Size + 1];
+ Reader.seekg(0, ios::beg);
+ Reader.read(m_Group.rawdata, Size);
+ Reader.close();
+ m_Group.rawdata[Size] = '\0';
+ try
+ {
+ m_Group.doc.parse<0>(m_Group.rawdata);
+ m_AshitaCore->GetChatManager()->Write("MultiSend: Groups loaded.");
+ m_Group.Loaded = true;
+ }
+ catch (...)
+ {
+ m_AshitaCore->GetChatManager()->Write("MultiSend: Groups failed to parse.");
+ m_Group.doc.clear();
+ m_Group.Loaded = false;
+ delete m_Group.rawdata;
+ }
+ }
+ else
+ {
+ if (m_Group.Loaded)
+ {
+ m_Group.doc.clear();
+ delete m_Group.rawdata;
+ m_Group.Loaded = false;
+ }
+ m_AshitaCore->GetChatManager()->Write("MultiSend: No groups file found.");
+ }
+
+ if (m_Group.Loaded)
+ {
+ ReadXML();
+ MatchGroups();
+ }
+}
+
+void MultiSend::ReadXML()
+{
+ m_Group.Map.clear();
+
+ xml_node<> *Node = m_Group.doc.first_node();
+ if (_stricmp(Node->name(), "multisend") == 0) Node = Node->first_node();
+ while (Node)
+ {
+ if (_stricmp(Node->name(), "group") == 0)
+ {
+ xml_attribute<> *Attr = Node->first_attribute("index");
+ if (Attr)
+ {
+ xml_attribute<> *Attr2 = Node->first_attribute("name");
+ if (Attr2)
+ {
+ m_Group.Map.insert(std::make_pair((uint16_t)atoi(Attr->value()), std::string(Attr2->value())));
+ }
+ }
+ }
+ Node = Node->next_sibling();
+ }
+}
+
+void MultiSend::MatchGroups()
+{
+ Groups.clear();
+ if (CurrentName.length() < 3) return;
+ if (!m_Group.Loaded) return;
+
+ xml_node<> *Node = m_Group.doc.first_node();
+ if (_stricmp(Node->name(), "multisend") == 0) Node = Node->first_node();
+ while (Node)
+ {
+ if (_stricmp(Node->name(), "group") == 0)
+ {
+ xml_attribute<> *Attr = Node->first_attribute("index");
+ if (Attr)
+ {
+ xml_attribute<> *Attr2 = Node->first_attribute("name");
+ if (Attr2)
+ {
+ uint16_t Index = (uint16_t)atoi(Attr->value());
+ for (xml_node<> *SubNode = Node->first_node("char"); SubNode != NULL; SubNode = SubNode->next_sibling("char"))
+ {
+ if (_stricmp(CurrentName.c_str(), SubNode->value()) == 0)
+ {
+ Groups.push_back(Index);
+ break;
+ }
+ }
+ }
+ }
+ }
+ Node = Node->next_sibling();
+ }
+}
\ No newline at end of file
diff --git a/src/MultiSend/IO.cpp b/src/MultiSend/IO.cpp
new file mode 100644
index 0000000..cbe8bde
--- /dev/null
+++ b/src/MultiSend/IO.cpp
@@ -0,0 +1,147 @@
+#include "MultiSend.h"
+
+bool MultiSend::ReadCommand()
+{
+ if (MMF_Pointer->Command.Command[Position].Active)
+ {
+ if (MatchID(MMF_Pointer->Command.Command[Position].Targets))
+ {
+ if ((!IgnoreSelf)
+ || (GetCurrentProcessId() != MMF_Pointer->Command.Command[Position].SendProcess))
+ {
+ const char* text = new char[248];
+ memset((void*)text, 0, 248);
+ memcpy((void*)text, &(MMF_Pointer->Command.Command[Position].Command), 248);
+ if (_Debug)
+ {
+ m_AshitaCore->GetChatManager()->Writef("Sending command: %s", text);
+ }
+ m_AshitaCore->GetChatManager()->QueueCommand(text, 0);
+ delete text;
+ }
+ }
+ Position++;
+ if (Position == 100) Position = 0;
+ return true;
+ }
+ return false;
+}
+
+void MultiSend::SetFollow(bool Active)
+{
+ if (Active)
+ {
+ Following = false;
+ uint16_t myindex = m_AshitaCore->GetDataManager()->GetParty()->GetMemberTargetIndex(0);
+ uint32_t mPosX = (uint32_t)floor(m_AshitaCore->GetDataManager()->GetEntity()->GetLocalX(myindex) * 100);
+ uint32_t mPosZ = (uint32_t)floor(m_AshitaCore->GetDataManager()->GetEntity()->GetLocalZ(myindex) * 100);
+ MMF_Pointer->Follow.PosX = mPosX;
+ MMF_Pointer->Follow.PosZ = mPosZ;
+ MMF_Pointer->Follow.Follow = 1;
+ }
+ else
+ {
+ MMF_Pointer->Follow.Follow = 0;
+ }
+}
+
+void MultiSend::SendCommand(uint32_t ID, const char* Command)
+{
+ char* Text = new char[248];
+ memset((void*)Text, 0, 248);
+ memcpy(Text, Command, strlen(Command));
+ SubValues(Text);
+
+ Claim(&(MMF_Pointer->Command.ProcessID), 0);
+
+ int NextPosition = MMF_Pointer->Command.Position + 1;
+ if (NextPosition == 100) NextPosition = 0;
+
+ MMF_Pointer->Command.Command[NextPosition].Active = false;
+
+ memset(&(MMF_Pointer->Command.Command[MMF_Pointer->Command.Position].Command), 0, 248);
+ if (strlen(Text) > 247)
+ {
+ memcpy(&(MMF_Pointer->Command.Command[MMF_Pointer->Command.Position].Command), Text, 247);
+ }
+ else
+ {
+ memcpy(&(MMF_Pointer->Command.Command[MMF_Pointer->Command.Position].Command), Text, strlen(Text));
+ }
+ if (_Debug)
+ {
+ m_AshitaCore->GetChatManager()->Writef("Publishing position %d : %s", MMF_Pointer->Command.Position, Text);
+ }
+ MMF_Pointer->Command.Command[MMF_Pointer->Command.Position].SendProcess = GetCurrentProcessId();
+ MMF_Pointer->Command.Command[MMF_Pointer->Command.Position].Targets = ID;
+ MMF_Pointer->Command.Command[MMF_Pointer->Command.Position].Active = true;
+ MMF_Pointer->Command.Position = NextPosition;
+ InterlockedExchange(&(MMF_Pointer->Command.ProcessID), 0);
+ delete Text;
+}
+
+void MultiSend::UpdateName(std::string Name)
+{
+ Claim(&(MMF_Pointer->Name.ProcessID), 0xFFFF0000);
+ bool Written = false;
+ for (int x = 0; x < 100; x++)
+ {
+ if (!MMF_Pointer->Name.Names[x].Active)
+ {
+ if (!Written)
+ {
+ memcpy(&(MMF_Pointer->Name.Names[x].Name), Name.c_str(), strlen(Name.c_str()) + 1);
+ MMF_Pointer->Name.Names[x].Process = GetCurrentProcessId();
+ MMF_Pointer->Name.Names[x].Active = 1;
+ Written = true;
+ }
+ continue;
+ }
+
+ else if (MMF_Pointer->Name.Names[x].Process == GetCurrentProcessId())
+ {
+ memset(&(MMF_Pointer->Name.Names[x]), 0, sizeof(MMF_Name_Single));
+ if (!Written)
+ {
+ memcpy(&(MMF_Pointer->Name.Names[x].Name), Name.c_str(), strlen(Name.c_str()) + 1);
+ MMF_Pointer->Name.Names[x].Process = GetCurrentProcessId();
+ MMF_Pointer->Name.Names[x].Active = 1;
+ Written = true;
+ }
+ }
+
+ else if (strcmp((const char*)&(MMF_Pointer->Name.Names[x].Name), Name.c_str()) == 0)
+ {
+ memset(&(MMF_Pointer->Name.Names[x]), 0, sizeof(MMF_Name_Single));
+ if (!Written)
+ {
+ memcpy(&(MMF_Pointer->Name.Names[x].Name), Name.c_str(), strlen(Name.c_str()) + 1);
+ MMF_Pointer->Name.Names[x].Process = GetCurrentProcessId();
+ MMF_Pointer->Name.Names[x].Active = 1;
+ Written = true;
+ }
+ }
+ }
+ CurrentName = Name;
+ MatchGroups();
+ InterlockedExchange(&(MMF_Pointer->Name.ProcessID), 0);
+}
+
+void MultiSend::Claim(uint32_t* Target, uint32_t Mod)
+{
+ uint32_t val = GetCurrentProcessId() + Mod;
+ int FailCount = 0;
+ InterlockedCompareExchange(Target, val, 0);
+
+ while (*Target != val)
+ {
+ FailCount++;
+ if (FailCount == 50)
+ {
+ InterlockedExchange(Target, val);
+ return;
+ }
+ ::Sleep(1);
+ InterlockedCompareExchange(Target, val, 0);
+ }
+}
\ No newline at end of file
diff --git a/src/MultiSend/Main.cpp b/src/MultiSend/Main.cpp
new file mode 100644
index 0000000..e55fe9a
--- /dev/null
+++ b/src/MultiSend/Main.cpp
@@ -0,0 +1,126 @@
+#include "MultiSend.h"
+#pragma comment(lib, "psapi.lib")
+#include
+#include
+
+MultiSend::MultiSend(void)
+ : m_AshitaCore(NULL)
+ , m_PluginId(0)
+ , m_Direct3DDevice(NULL)
+{ }
+MultiSend::~MultiSend(void)
+{ }
+
+plugininfo_t* g_PluginInfo = NULL;
+
+plugininfo_t MultiSend::GetPluginInfo(void)
+{
+ return (*g_PluginInfo);
+}
+
+bool MultiSend::Initialize(IAshitaCore* core, ILogManager* log, uint32_t id)
+{
+ this->m_AshitaCore = core;
+ this->m_PluginId = id;
+ this->m_LogManager = log;
+
+ LoadGroups();
+
+ MODULEINFO mod = { 0 };
+ if (!::GetModuleInformation(::GetCurrentProcess(), ::GetModuleHandle("FFXiMain.dll"), &mod, sizeof(MODULEINFO)))
+ return false;
+
+ unsigned char* Pointer = (unsigned char*)Ashita::Memory::FindPattern((uintptr_t)mod.lpBaseOfDll, (uintptr_t)mod.SizeOfImage,
+ "8BCFE8????FFFF8B0D????????E8????????8BE885ED750CB9",
+ 0, 0);
+ if (Pointer == NULL) return false;
+
+ Pointer += 25;
+ StructPointer = (sFollow*)(*((DWORD*)Pointer));
+
+ //create a handle to the MMF, size matches the struct we're using for it
+ HANDLE hMapFile = CreateFileMapping(
+ INVALID_HANDLE_VALUE,
+ NULL,
+ PAGE_READWRITE,
+ 0, // maximum object size (high-order DWORD)
+ sizeof(MMF_Global), // maximum object size (low-order DWORD)
+ "FFXI_MultiSend");
+
+ bool made = (GetLastError() == 0);
+
+ if (hMapFile == NULL)
+ {
+ throw exception("Could not open or create MMF.");
+ }
+
+
+ MMF_Pointer = (MMF_Global*)MapViewOfFile(hMapFile, // handle to map object
+ FILE_MAP_ALL_ACCESS, // read/write permission
+ 0,
+ 0,
+ sizeof(MMF_Global));
+ if (MMF_Pointer == NULL)
+ {
+ CloseHandle(hMapFile);
+ throw exception("Could not map MMF.");
+ }
+
+ if (made)
+ {
+ memset(MMF_Pointer, 0, sizeof(MMF_Global));
+ }
+ Position = MMF_Pointer->Command.Position;
+
+ FollowEnabled = true;
+ ZoneExtra = false;
+ IgnoreSelf = false;
+
+ CurrentName = "";
+ if (m_AshitaCore->GetDataManager()->GetParty()->GetMemberActive(0))
+ {
+ uint16_t myindex = m_AshitaCore->GetDataManager()->GetParty()->GetMemberTargetIndex(0);
+ UpdateName(std::string(m_AshitaCore->GetDataManager()->GetEntity()->GetName(myindex)));
+ }
+
+ _Debug = false;
+
+ this->Start();
+
+ return true;
+}
+
+void MultiSend::Release(void)
+{
+ this->Stop();
+ if (MMF_Pointer)
+ {
+ UnmapViewOfFile((LPCVOID)MMF_Pointer);
+ }
+ if (hMapFile)
+ {
+ CloseHandle(hMapFile);
+ }
+}
+
+__declspec(dllexport) double __stdcall GetInterfaceVersion(void)
+{
+ return ASHITA_INTERFACE_VERSION;
+}
+
+__declspec(dllexport) void __stdcall CreatePluginInfo(plugininfo_t* lpBuffer)
+{
+ g_PluginInfo = lpBuffer;
+
+ strcpy_s(g_PluginInfo->Name, sizeof(g_PluginInfo->Name), "MultiSend");
+ strcpy_s(g_PluginInfo->Author, sizeof(g_PluginInfo->Author), "Thorny");
+
+ g_PluginInfo->InterfaceVersion = ASHITA_INTERFACE_VERSION;
+ g_PluginInfo->PluginVersion = 1.00f;
+ g_PluginInfo->Priority = 0;
+}
+
+__declspec(dllexport) IPlugin* __stdcall CreatePlugin(void)
+{
+ return (IPlugin*)new MultiSend();
+}
diff --git a/src/MultiSend/MultiSend.h b/src/MultiSend/MultiSend.h
new file mode 100644
index 0000000..32c025f
--- /dev/null
+++ b/src/MultiSend/MultiSend.h
@@ -0,0 +1,109 @@
+#ifndef __ASHITA_MMF_H_INCLUDED__
+#define __ASHITA_MMF_H_INCLUDED__
+
+#if defined (_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+#define SAFE_DELETE(p) if(p) { delete p; p = NULL; }
+
+#include "C:\Ashita 3\Plugins\ADK\Ashita.h"
+#include "Structs.h"
+#include "..\..\pluginheaders\Utilities.h"
+#include "..\..\pluginheaders\rapidxml.hpp"
+#include
+#include
+#include