diff --git a/ImprovedCommands.sln b/ImprovedCommands.sln new file mode 100644 index 0000000..79c31c8 --- /dev/null +++ b/ImprovedCommands.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26403.7 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ImprovedCommands", "ImprovedCommands\ImprovedCommands.vcxproj", "{26CE17E3-A40E-43EF-A538-9C1BE4050303}" +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 + {26CE17E3-A40E-43EF-A538-9C1BE4050303}.Debug|x64.ActiveCfg = Debug|x64 + {26CE17E3-A40E-43EF-A538-9C1BE4050303}.Debug|x64.Build.0 = Debug|x64 + {26CE17E3-A40E-43EF-A538-9C1BE4050303}.Debug|x86.ActiveCfg = Debug|Win32 + {26CE17E3-A40E-43EF-A538-9C1BE4050303}.Debug|x86.Build.0 = Debug|Win32 + {26CE17E3-A40E-43EF-A538-9C1BE4050303}.Release|x64.ActiveCfg = Release|x64 + {26CE17E3-A40E-43EF-A538-9C1BE4050303}.Release|x64.Build.0 = Release|x64 + {26CE17E3-A40E-43EF-A538-9C1BE4050303}.Release|x86.ActiveCfg = Release|Win32 + {26CE17E3-A40E-43EF-A538-9C1BE4050303}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/ImprovedCommands/ImprovedCommands.cpp b/ImprovedCommands/ImprovedCommands.cpp new file mode 100644 index 0000000..7d7efd6 --- /dev/null +++ b/ImprovedCommands/ImprovedCommands.cpp @@ -0,0 +1,384 @@ +#define _USE_MATH_DEFINES + +#include +#include +#include +#include +#include +#include +#include "API/Base.h" +#include "API/UE.h" +#include "Tools.h" +#include "ImprovedCommands.h" + +#pragma comment(lib, "ArkApi.lib") + +typedef void(*callback_function)(void); + +void DoRespec(RCONClientConnection* rconClientConnection, RCONPacket* rconPacket, UWorld* uWorld) +{ + FString msg = rconPacket->Body; + + TArray Parsed; + msg.ParseIntoArray(&Parsed, L" ", true); + + if (Parsed.IsValidIndex(1)) + { + unsigned __int64 steamId; + + try + { + steamId = std::stoull(*Parsed[1]); + } + catch (const std::exception&) + { + return; + } + + AShooterPlayerController* aShooterPC = FindPlayerControllerFromSteamId(steamId); + if (aShooterPC) + { + AShooterPlayerState* playerState = static_cast(aShooterPC->GetPlayerStateField()); + if (playerState) { + playerState->DoRespec(); + // Send a reply + FString reply = L"Successfully triggered player respec\n"; + rconClientConnection->SendMessageW(rconPacket->Id, 0, &reply); + } + } + } +} + +//Note: This command does not check tribe permissions +void DestroyMyDino(AShooterPlayerController* aShooterPlayerController, FString* message, int mode) +{ + UWorld* world = Ark::GetWorld(); + if (!world) return; + if (!aShooterPlayerController) return; + + ACharacter* character = aShooterPlayerController->GetCharacterField(); + if (!character || !character->IsA(APrimalCharacter::GetPrivateStaticClass())) return; + + APrimalCharacter* primalCharacter = static_cast(character); + AActor* actor = primalCharacter->GetAimedActor(ECC_GameTraceChannel2, 0i64, 0.0, 0.0, 0i64, 0i64, 0, 0); + if (!actor || !actor->IsA(APrimalDinoCharacter::GetPrivateStaticClass())) return; + + APrimalDinoCharacter* dino = static_cast(actor); + int dinoTeam = dino->GetTargetingTeamField(); + int playerTeam = aShooterPlayerController->GetTargetingTeamField(); + if (dinoTeam != playerTeam) return; + + FString* className = new FString(); + dino->GetDinoNameTagField().ToString(className); //species name + std::string name = dino->GetTamedNameField().ToString(); //tamed name + std::string classNameStr = className->ToString(); + + dino->Destroy(false, true); + + std::stringstream ss; + + if (name.length() > 0) ss << "Destroyed creature '" << name << "' (" << classNameStr << ")"; + else ss << "Destroyed targeted creature (" << classNameStr << ")"; + + wchar_t* wcstring = ConvertToWideStr(ss.str()); + SendChatMessage(aShooterPlayerController, L"[system]", wcstring); + + delete className; + delete[] wcstring; +} + +void DestroyAllStructuresForTeamId(RCONClientConnection* rconClientConnection, RCONPacket* rconPacket, UWorld* uWorld) +{ + FString msg = rconPacket->Body; + + TArray Parsed; + msg.ParseIntoArray(&Parsed, L" ", true); + + if (Parsed.IsValidIndex(1)) + { + __int64 teamId; + + try + { + teamId = std::stoull(*Parsed[1]); + } + catch (const std::exception&) + { + return; + } + + if (teamId < 50000) return; + + UWorld* world = Ark::GetWorld(); + if (!world) return; + + TArray* FoundActors = new TArray(); + + UGameplayStatics::GetAllActorsOfClass(world, APrimalStructure::GetPrivateStaticClass(), FoundActors); + + std::stringstream ss; + + //todo: maybe destroy rafts aswell? + int num = 0; + for (uint32_t i = 0; i < FoundActors->Num(); i++) + { + AActor* actor = (*FoundActors)[i]; + + APrimalStructure* structure = static_cast(actor); + + int structureTeam = structure->GetTargetingTeamField(); + if (structureTeam == teamId) + { + structure->Destroy(false, true); + num++; + } + } + + ss << "Destroyed " << num << " structures belonging to team " << teamId << "\n"; + + wchar_t* wcstring = ConvertToWideStr(ss.str()); + + FString reply(wcstring); + rconClientConnection->SendMessageW(rconPacket->Id, 0, &reply); + + delete FoundActors; + delete[] wcstring; + } +} + +void DestroyStructuresForTeamIdAtPosition(RCONClientConnection* rconClientConnection, RCONPacket* rconPacket, UWorld* uWorld) +{ + FString msg = rconPacket->Body; + + TArray Parsed; + msg.ParseIntoArray(&Parsed, L" ", true); + + //if (Parsed.IsValidIndex(5)) + if (Parsed.IsValidIndex(5)) + { + __int64 teamId; + float x; + float y; + //float z; + float radius; + bool rafts; + + try + { + teamId = std::stoull(*Parsed[1]); + x = std::stof(*Parsed[2]); + y = std::stof(*Parsed[3]); + radius = std::stof(*Parsed[4]); + rafts = (bool)std::stoi(*Parsed[5]); + //z = std::stof(*Parsed[4]); + //radius = std::stof(*Parsed[5]); + } + catch (const std::exception&) + { + return; + } + + if (teamId < 50000) return; + if (radius <= 0) return; + + UWorld* world = Ark::GetWorld(); + if (!world) return; + + std::stringstream ss; + int num = 0; + FVector* pos = new FVector(); + + TArray* FoundActors = new TArray(); + UGameplayStatics::GetAllActorsOfClass(world, APrimalStructure::GetPrivateStaticClass(), FoundActors); + + for (uint32_t i = 0; i < FoundActors->Num(); i++) + { + AActor* actor = (*FoundActors)[i]; + + APrimalStructure* structure = static_cast(actor); + + int structureTeam = structure->GetTargetingTeamField(); + structure->GetRootComponentField()->GetCustomLocation(pos); + //if (structureTeam == teamId && IsPointInsideSphere(pos, x, y, z, radius)) + if (structureTeam == teamId && IsPointInside2dCircle(*pos, x, y, radius)) + { + structure->Destroy(false, true); + num++; + } + } + delete FoundActors; + + if (rafts) { + TArray* FoundActors = new TArray(); + UGameplayStatics::GetAllActorsOfClass(world, APrimalDinoCharacter::GetPrivateStaticClass(), FoundActors); + + for (uint32_t i = 0; i < FoundActors->Num(); i++) + { + AActor* actor = (*FoundActors)[i]; + + APrimalDinoCharacter* dino = static_cast(actor); + + int dinoTeam = dino->GetTargetingTeamField(); + + dino->GetRootComponentField()->GetCustomLocation(pos); + //if (structureTeam == teamId && IsPointInsideSphere(pos, x, y, z, radius)) + if (dinoTeam == teamId && IsPointInside2dCircle(*pos, x, y, radius)) + { + FString* className = new FString(); + FName bp = actor->GetClassField()->GetNameField(); + bp.ToString(className); + std::string classNameStr = className->ToString(); + + if (classNameStr.compare("Raft_BP_C") == 0 || classNameStr.compare("MotorRaft_BP_C") == 0) { + dino->Destroy(false, true); + num++; + } + + delete className; + } + } + delete FoundActors; + } + + //ss << "Destroyed " << num << " structures belonging to team " << teamId << " at position (x: " << x << ", y: " << y << ", z: " << z << ", r: " << radius << ")" << "\n"; + ss << "Destroyed " << num << " structures belonging to team " << teamId << " at position (x: " << x << ", y: " << y << ", r: " << radius << ", rafts: " << rafts << ")" << "\n"; + + wchar_t* wcstring = ConvertToWideStr(ss.str()); + + FString reply(wcstring); + rconClientConnection->SendMessageW(rconPacket->Id, 0, &reply); + + delete pos; + delete[] wcstring; + } +} + +void DestroyDinosForTeamId(RCONClientConnection* rconClientConnection, RCONPacket* rconPacket, UWorld* uWorld) +{ + FString msg = rconPacket->Body; + + TArray Parsed; + msg.ParseIntoArray(&Parsed, L" ", true); + + if (Parsed.IsValidIndex(1)) + { + __int64 teamId; + + try + { + teamId = std::stoull(*Parsed[1]); + } + catch (const std::exception&) + { + return; + } + + if (teamId < 50000) return; + + UWorld* world = Ark::GetWorld(); + if (!world) return; + + TArray* FoundActors = new TArray(); + UGameplayStatics::GetAllActorsOfClass(world, APrimalDinoCharacter::GetPrivateStaticClass(), FoundActors); + + std::stringstream ss; + + int num = 0; + for (uint32_t i = 0; i < FoundActors->Num(); i++) + { + AActor* actor = (*FoundActors)[i]; + + APrimalDinoCharacter* dino = static_cast(actor); + + int dinoTeam = dino->GetTargetingTeamField(); + if (dinoTeam == teamId) + { + dino->Destroy(false, true); + num++; + } + } + + ss << "Destroyed " << num << " dinos belonging to team " << teamId << "\n"; + + wchar_t* wcstring = ConvertToWideStr(ss.str()); + + FString reply(wcstring); + rconClientConnection->SendMessageW(rconPacket->Id, 0, &reply); + + delete FoundActors; + delete[] wcstring; + } +} + +void MyDinoStats(AShooterPlayerController* aShooterPlayerController, FString* message, int mode) +{ + UWorld* world = Ark::GetWorld(); + if (!world) return; + if (!aShooterPlayerController) return; + + ACharacter* character = aShooterPlayerController->GetCharacterField(); + if (!character || !character->IsA(APrimalCharacter::GetPrivateStaticClass())) return; + + APrimalCharacter* primalCharacter = static_cast(character); + AActor* actor = primalCharacter->GetAimedActor(ECC_GameTraceChannel2, 0i64, 0.0, 0.0, 0i64, 0i64, 0, 0); + if (!actor || !actor->IsA(APrimalDinoCharacter::GetPrivateStaticClass())) return; + + APrimalDinoCharacter* dino = static_cast(actor); + int dinoTeam = dino->GetTargetingTeamField(); + int playerTeam = aShooterPlayerController->GetTargetingTeamField(); + if (dinoTeam != playerTeam) return; + + std::stringstream ss; + + UPrimalCharacterStatusComponent* status = dino->GetCharacterStatusComponent(); + if (status) { + char* stats = status->GetNumberOfLevelUpPointsAppliedField(); + int health = (int)stats[0]; + int stamina = (int)stats[1]; + int torpor = (int)stats[2]; + int oxygen = (int)stats[3]; + int food = (int)stats[4]; + int water = (int)stats[5]; + int temperature = (int)stats[6]; + int weight = (int)stats[7]; + int meleeDamage = (int)stats[8]; + int movementSpeed = (int)stats[9]; + int fortitude = (int)stats[10]; + int craftingSpeed = (int)stats[11]; + + ss << "Stats: " << health << ", " << stamina << ", " << oxygen << ", " << food << ", " << weight << ", " << meleeDamage << ", " << movementSpeed << ""; + } + + wchar_t* wcstring = ConvertToWideStr(ss.str()); + SendChatMessage(aShooterPlayerController, L"[system]", wcstring); + + delete[] wcstring; +} + +void Init() +{ + //chat (for all players) + Ark::AddChatCommand(L"/mydinostats", &MyDinoStats); + Ark::AddChatCommand(L"/destroymydino", &DestroyMyDino); + + //rcon + Ark::AddRconCommand(L"DoRespec", &DoRespec); + Ark::AddRconCommand(L"DestroyAllStructuresForTeamId", &DestroyAllStructuresForTeamId); + Ark::AddRconCommand(L"DestroyStructuresForTeamIdAtPosition", &DestroyStructuresForTeamIdAtPosition); + Ark::AddRconCommand(L"DestroyDinosForTeamId", &DestroyDinosForTeamId); +} + +BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) +{ + switch (ul_reason_for_call) + { + case DLL_PROCESS_ATTACH: + Init(); + break; + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + case DLL_PROCESS_DETACH: + break; + } + return TRUE; +} diff --git a/ImprovedCommands/ImprovedCommands.h b/ImprovedCommands/ImprovedCommands.h new file mode 100644 index 0000000..6f70f09 --- /dev/null +++ b/ImprovedCommands/ImprovedCommands.h @@ -0,0 +1 @@ +#pragma once diff --git a/ImprovedCommands/ImprovedCommands.vcxproj b/ImprovedCommands/ImprovedCommands.vcxproj new file mode 100644 index 0000000..1e26457 --- /dev/null +++ b/ImprovedCommands/ImprovedCommands.vcxproj @@ -0,0 +1,127 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {26CE17E3-A40E-43EF-A538-9C1BE4050303} + ImprovedCommands + 10.0.15063.0 + + + + Application + true + v141 + MultiByte + + + Application + false + v141 + true + MultiByte + + + Application + true + v141 + MultiByte + + + DynamicLibrary + false + v141 + true + Unicode + + + + + + + + + + + + + + + + + + + + + C:\ArkApi\include;$(IncludePath) + C:\ArkApi\lib;$(LibraryPath) + + + + Level3 + Disabled + true + + + + + Level3 + Disabled + true + + + + + Level3 + MaxSpeed + true + true + true + + + true + true + + + + + Level3 + MaxSpeed + true + true + true + + + true + true + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ImprovedCommands/ImprovedCommands.vcxproj.filters b/ImprovedCommands/ImprovedCommands.vcxproj.filters new file mode 100644 index 0000000..cbdce58 --- /dev/null +++ b/ImprovedCommands/ImprovedCommands.vcxproj.filters @@ -0,0 +1,33 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + \ No newline at end of file diff --git a/ImprovedCommands/Tools.cpp b/ImprovedCommands/Tools.cpp new file mode 100644 index 0000000..c02288a --- /dev/null +++ b/ImprovedCommands/Tools.cpp @@ -0,0 +1,56 @@ +#include "Tools.h" + +AShooterPlayerController* FindPlayerControllerFromSteamId(unsigned __int64 steamId) +{ + AShooterPlayerController* result = nullptr; + + auto playerControllers = Ark::GetWorld()->GetPlayerControllerListField(); + for (uint32_t i = 0; i < playerControllers.Num(); ++i) + { + auto playerController = playerControllers[i]; + + APlayerState* playerState = playerController->GetPlayerStateField(); + __int64 currentSteamId = playerState->GetUniqueIdField()->UniqueNetId->GetUniqueNetIdField(); + + if (currentSteamId == steamId) + { + AShooterPlayerController* aShooterPC = static_cast(playerController.Get()); + + result = aShooterPC; + break; + } + } + + return result; +} + +wchar_t* ConvertToWideStr(const std::string& str) +{ + size_t newsize = str.size() + 1; + + wchar_t* wcstring = new wchar_t[newsize]; + + size_t convertedChars = 0; + mbstowcs_s(&convertedChars, wcstring, newsize, str.c_str(), _TRUNCATE); + + return wcstring; +} + +bool IsPointInsideSphere(FVector point, float sphereX, float sphereY, float sphereZ, float sphereRadius) +{ + long double x = point.X - sphereX; + long double y = point.Y - sphereY; + long double z = point.Z - sphereZ; + + long double distancesq = x * x + y * y + z * z; + return distancesq < sphereRadius * sphereRadius; +} + +bool IsPointInside2dCircle(FVector point, float circleX, float circleY, float circleRadius) +{ + long double x = point.X - circleX; + long double y = point.Y - circleY; + + long double distancesq = x * x + y * y; + return distancesq < circleRadius * circleRadius; +} \ No newline at end of file diff --git a/ImprovedCommands/Tools.h b/ImprovedCommands/Tools.h new file mode 100644 index 0000000..fba8961 --- /dev/null +++ b/ImprovedCommands/Tools.h @@ -0,0 +1,69 @@ +#pragma once +#include +#include +#include "API/Base.h" + +template +void SendChatMessage(AShooterPlayerController* playerController, const FString& senderName, const wchar_t* msg, Args&&... args) +{ + size_t size = swprintf(nullptr, 0, msg, std::forward(args)...) + 1; + + wchar_t* buffer = new wchar_t[size]; + _snwprintf_s(buffer, size, _TRUNCATE, msg, std::forward(args)...); + + FString cmd(buffer); + + FChatMessage* chatMessage = static_cast(malloc(sizeof(FChatMessage))); + if (chatMessage) + { + chatMessage->SenderName = senderName; + chatMessage->SenderSteamName = L""; + chatMessage->SenderTribeName = L""; + chatMessage->SenderId = 0; + chatMessage->Message = cmd; + chatMessage->Receiver = L""; + chatMessage->SenderTeamIndex = 0; + chatMessage->ReceivedTime = -1; + chatMessage->SendMode = EChatSendMode::GlobalChat; + chatMessage->RadioFrequency = 0; + chatMessage->ChatType = EChatType::GlobalChat; + chatMessage->SenderIcon = 0; + chatMessage->UserId = L""; + + void* mem = malloc(sizeof(FChatMessage)); + if (mem) + { + FChatMessage* chat = new(mem) FChatMessage(chatMessage); + + playerController->ClientChatMessage(chat); + + chat->~FChatMessage(); + free(mem); + } + + free(chatMessage); + } + + delete[] buffer; +} + +template +void SendDirectMessage(AShooterPlayerController* playerController, const wchar_t* msg, Args&&... args) +{ + size_t size = swprintf(nullptr, 0, msg, std::forward(args)...) + 1; + + wchar_t* buffer = new wchar_t[size]; + _snwprintf_s(buffer, size, _TRUNCATE, msg, std::forward(args)...); + + FString cmd(buffer); + + FLinearColor msgColor = { 1,1,1,1 }; + playerController->ClientServerChatDirectMessage(&cmd, msgColor, false); + + delete[] buffer; +} + +AShooterPlayerController* FindPlayerControllerFromSteamId(unsigned __int64 steamId); +wchar_t* ConvertToWideStr(const std::string& str); +bool IsPointInsideSphere(FVector point, float sphereX, float sphereY, float sphereZ, float sphereRadius); +bool IsPointInside2dCircle(FVector point, float circleX, float circleY, float circleRadius); \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..602a6a9 --- /dev/null +++ b/README.md @@ -0,0 +1,31 @@ +# ARK Beyond API: Improved Commands (Plugin) + +## Introduction + +Adds additional commands to ARK Survival Evolved servers using ARK Beyond API. + +Chat +* **/MyDinoStats**: Prints the base stats (levels) of a owned and tamed dino in-front of the player. +* **/DestroyMyDino**: Deletes the owned and tamed dino in-front of the player from the ARK. + +Rcon +* **DoRespec ``**: Force respec (mindwipe) of a character. +Example: `DoRespec 12345678912345678` + +* **DestroyAllStructuresForTeamId ``**: Destroys all structures owned by a player or team. +Example: `DestroyAllStructuresForTeamId 123456789` + +* **DestroyStructuresForTeamIdAtPosition `` `` `` `` ``**: Destroys all structures owned by a player or team within a given range from a position (optionally including rafts). +Example: `DestroyStructuresForTeamIdAtPosition 1234567890 85000.0 275000.0 1000 1` + +* **DestroyDinosForTeamId ``**: Destroys all tamed dinos owned by a player or team. +Example: `DestroyDinosForTeamId 123456789` + +## Acknowledgements + +This plugin is based on Michidu's work on Ark-Server-Plugins and ARK Beyond API. The basic plumbing code is copied directly from those plugins. + +## Links +My ARK Beyond API Fork (https://github.com/tsebring/ARK-Server-Beyond-API) +ARK Beyond API by Michidu (https://github.com/Michidu/ARK-Server-Beyond-API) +Ark-Server-Plugins (https://github.com/Michidu/Ark-Server-Plugins) \ No newline at end of file