diff --git a/.gitignore b/.gitignore index 25e57054d..ebed08a8a 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ Release *.vcxproj.user *.log vcpkg_installed/ +*.user !misc/acctpath_patch/bin/*.cfg diff --git a/CHANGELOG.md b/CHANGELOG.md index a3ce8b91c..26b42fe41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 4.0.29 +- Advanced Startup Solars: Finer control over solars that spawn on startup, requires solar.dll and npc.dll to function +- Fix the IPC interface for `CreateUserDefinedSolarFormation()` in solar control + ## 4.0.28 - Improve checks for spawning NPCs - Implement 'Solar Control': Spawn solars and formations of solars via admin command. diff --git a/plugins/advanced_startup_solars/AdvancedStartupSolars.cpp b/plugins/advanced_startup_solars/AdvancedStartupSolars.cpp new file mode 100644 index 000000000..4e4bd8a3b --- /dev/null +++ b/plugins/advanced_startup_solars/AdvancedStartupSolars.cpp @@ -0,0 +1,231 @@ +/** + * @date April, 2024 + * @author IrateRedKite + * @defgroup AdvancedStartupSolars Advanced Startup Solars + * @brief + * This plugin allows server owners to specify startupSolars with more finesse, allowing for mutually exclusive spawns + * + * @paragraph cmds Player Commands + * None + * + * @paragraph adminCmds Admin Commands + * None + * + * @paragraph configuration Configuration + * @code + * + * { + * "solarFamilies": [ + * { + * "name": "largestation1", + * "solarFormations": [ + * { + * "formation": "largestation1", + * "npcs": { + * "example": 2 + * }, + * "spawnWeight": 1 + * } + * ], + * "spawnChance": 75, + * "spawnLocations": [ + * { + * "location": [ + * -31630.4453125, + * 1000.0, + * -25773.7421875 + * ], + * "system": "li01" + * }, + * { + * "location": [ + * -30401.986328125, + * -1000.0, + * -25730.84375 + * ], + * "system": "li01" + * } + * ], + * "spawnQuantity": 1 + * } + * ] + * } + * + * @endcode + * + * @paragraph ipc IPC Interfaces Exposed + * None + * + * @paragraph optional Optional Plugin Dependencies + * Solar Control and NPC Control + */ + +// This is free software; you can redistribute it and/or modify it as +// you wish without restriction. If you do then I would appreciate +// being notified and/or mentioned somewhere. + +// Includes +#include "AdvancedStartupSolars.hpp" + +#include + +namespace Plugins::AdvancedStartupSolars +{ + const auto global = std::make_unique(); + + // Function: Generates a random int between min and max + int RandomNumber(int min, int max) + { + static std::random_device dev; + static auto engine = std::mt19937(dev()); + auto range = std::uniform_int_distribution(min, max); + return range(engine); + } + + SolarFormation SelectSolarFormation(SolarFamily family) + { + std::vector weights {}; + + for (auto formation : family.solarFormations) + { + weights.emplace_back(formation.spawnWeight); + } + + std::discrete_distribution<> dist(weights.begin(), weights.end()); + static std::mt19937 engine; + auto numberIndex = dist(engine); + + auto solarFormation = family.solarFormations[numberIndex]; + family.solarFormations.erase(family.solarFormations.begin() + numberIndex); + + return solarFormation; + } + + Position SelectSpawnLocation(SolarFamily family) + { + auto locationIndex = RandomNumber(0, family.spawnLocations.size() - 1); + + Position spawnPosition; + + spawnPosition.location = {{family.spawnLocations[locationIndex].location[0]}, + {family.spawnLocations[locationIndex].location[1]}, + {family.spawnLocations[locationIndex].location[2]}}; + spawnPosition.system = family.spawnLocations[locationIndex].system; + + // Remove the spawnLocation from the pool of possible locations before returning the vector; + family.spawnLocations.erase(family.spawnLocations.begin() + locationIndex); + + return spawnPosition; + } + + // Put things that are performed on plugin load here! + void LoadSettings() + { + // Load JSON config + auto config = Serializer::JsonToObject(); + global->config = std::make_unique(std::move(config)); + + // Set the npcCommunicator and solarCommunicator interfaces and check if they are availlable + global->npcCommunicator = + static_cast(PluginCommunicator::ImportPluginCommunicator(Plugins::Npc::NpcCommunicator::pluginName)); + + global->solarCommunicator = static_cast( + PluginCommunicator::ImportPluginCommunicator(Plugins::SolarControl::SolarCommunicator::pluginName)); + + // Prevent the plugin from progressing further and disable all functions if either interface is not found. + if (!global->npcCommunicator) + { + Console::ConErr(std::format("npc.dll not found. The plugin is required for this module to function.")); + global->pluginActive = false; + } + + if (!global->solarCommunicator) + { + Console::ConErr(std::format("solar.dll not found. The plugin is required for this module to function.")); + global->pluginActive = false; + } + + if (!global->pluginActive) + { + Console::ConErr( + std::format("Critical components of Advanced Startup Solars were not found or were configured incorrectly. The plugin has been disabled.")); + return; + } + + // Validate our user defined config + int formationCount = 0; + for (auto solarFamily : global->config->solarFamilies) + { + // Clamps spawnChance between 0 and 100 + solarFamily.spawnChance = std::clamp(solarFamily.spawnChance, 0, 100); + + for (const auto& formation : solarFamily.solarFormations) + { + Console::ConDebug(std::format("Loaded formation '{}' into the {} solarFamily pool", wstos(formation.formation), solarFamily.name)); + formationCount++; + } + } + + Console::ConDebug(std::format("Loaded {} solarFamilies into the spawn pool", global->config->solarFamilies.size())); + Console::ConDebug(std::format("Loaded a total of {} formations between the collective solarFamily pool", formationCount)); + } + + // We have to spawn here since the Startup/LoadSettings hooks are too early + void Login([[maybe_unused]] struct SLoginInfo const& loginInfo, [[maybe_unused]] const uint& client) + { + if (!global->firstRun) + { + return; + } + + for (const auto& family : global->config->solarFamilies) + { + auto dist = RandomNumber(0, 100); + + if (dist <= family.spawnChance) + { + for (int i = 0; i < family.spawnQuantity; i++) + { + auto spawnPosition = SelectSpawnLocation(family); + auto solarFormation = SelectSolarFormation(family); + auto spawnSystem = CreateID(spawnPosition.system.c_str()); + + Vector spawnLocation = {{spawnPosition.location[0]}, {spawnPosition.location[1]}, {spawnPosition.location[2]}}; + + global->solarCommunicator->CreateSolarFormation(solarFormation.formation, spawnLocation, spawnSystem); + + for (const auto& [key, value] : solarFormation.npcs) + { + for (int j = 0; j < value; j++) + { + global->npcCommunicator->CreateNpc(key, spawnLocation, EulerMatrix({0, 0, 0}), spawnSystem, true); + } + } + } + } + } + + global->firstRun = false; + } + +} // namespace Plugins::AdvancedStartupSolars + +using namespace Plugins::AdvancedStartupSolars; +REFL_AUTO(type(SolarFormation), field(formation), field(npcs), field(spawnWeight)); +REFL_AUTO(type(Position), field(location), field(system)); +REFL_AUTO(type(SolarFamily), field(name), field(solarFormations), field(spawnLocations), field(spawnChance), field(spawnQuantity)); +REFL_AUTO(type(Config), field(solarFamilies)); + +DefaultDllMainSettings(LoadSettings); + +extern "C" EXPORT void ExportPluginInfo(PluginInfo* pi) +{ + pi->name("Advanced Startup Solars"); + pi->shortName("advanced_startup_solars"); + pi->mayUnload(true); + pi->returnCode(&global->returnCode); + pi->versionMajor(PluginMajorVersion::VERSION_04); + pi->versionMinor(PluginMinorVersion::VERSION_00); + pi->emplaceHook(HookedCall::IServerImpl__Login, &Login); + pi->emplaceHook(HookedCall::FLHook__LoadSettings, &LoadSettings, HookStep::After); +} \ No newline at end of file diff --git a/plugins/advanced_startup_solars/AdvancedStartupSolars.hpp b/plugins/advanced_startup_solars/AdvancedStartupSolars.hpp new file mode 100644 index 000000000..a32d0fd9e --- /dev/null +++ b/plugins/advanced_startup_solars/AdvancedStartupSolars.hpp @@ -0,0 +1,62 @@ +#pragma once + +#include +#include +#include +#include "../npc_control/NPCControl.h" +#include "../solar_control/SolarControl.h" + +namespace Plugins::AdvancedStartupSolars +{ + + //! Additional data for formations to spawn from solarFamilies + struct SolarFormation final : Reflectable + { + //! The formation name from solar.json to use + std::wstring formation; + //! NPCs from npc.json and the number to spawn + std::map npcs; + //! The weight for this formation to spawn within the SolarFamily + int spawnWeight = 0; + }; + + //! Positional data for SolarFamily + struct Position final : Reflectable + { + std::vector location; + std::string system; + }; + + //! A SolarFamily struct that contains grouped formations and potential spawn locations + struct SolarFamily final : Reflectable + { + //! The name of the SolarFamily. Principally used for debug messages + std::string name; + //! A vector containing possible solarFormations to spawn + std::vector solarFormations; + //! A vector containing possible positions to spawn solarFormations at + std::vector spawnLocations; + //! The overall spawn chance for the whole SolarFamily + int spawnChance = 0; + //! The number of formations to spawn within the family + int spawnQuantity = 0; + }; + + struct Config final : Reflectable + { + std::string File() override { return "config/advanced_startup_solars.json"; } + //! A vector containing overal SolarFamily groups + std::vector solarFamilies; + }; + + //! Global data for this plugin + struct Global + { + std::unique_ptr config = nullptr; + ReturnCode returnCode = ReturnCode::Default; + Plugins::Npc::NpcCommunicator* npcCommunicator = nullptr; + Plugins::SolarControl::SolarCommunicator* solarCommunicator = nullptr; + bool pluginActive = true; + bool firstRun = true; + }; +} // namespace Plugins::AdvancedStartupSolars \ No newline at end of file diff --git a/plugins/advanced_startup_solars/advanced_startup_solars.vcxproj b/plugins/advanced_startup_solars/advanced_startup_solars.vcxproj new file mode 100644 index 000000000..f65f1b656 --- /dev/null +++ b/plugins/advanced_startup_solars/advanced_startup_solars.vcxproj @@ -0,0 +1,88 @@ + + + + + ReleaseWithDebug + Win32 + + + Release + Win32 + + + + {ECC3FEAC-C696-40FA-B3D4-75BED5436199} + $projectname$ + Win32Proj + 10.0 + Advanced Startup Solars + + + + DynamicLibrary + v143 + false + MultiByte + + + DynamicLibrary + v143 + false + MultiByte + + + + + + + + + + + + + + + + $safeprojectname$ + + + advanced_startup_solars + + + + SERVER;_VC80_UPGRADE=0x0710;%(PreprocessorDefinitions) + stdcpp20 + true + true + 5222 + + + + + SERVER;_VC80_UPGRADE=0x0710;%(PreprocessorDefinitions) + stdcpp20 + true + true + 5222 + stdcpp20 + + + + true + + + + + + + {fe6eb3c9-da22-4492-aec3-068c9553a623} + + + + + + + + + \ No newline at end of file diff --git a/plugins/autobuy/autobuy.user b/plugins/autobuy/autobuy.user deleted file mode 100644 index 88a550947..000000000 --- a/plugins/autobuy/autobuy.user +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/plugins/solar_control/SolarControl.cpp b/plugins/solar_control/SolarControl.cpp index fc0783e95..5def83066 100644 --- a/plugins/solar_control/SolarControl.cpp +++ b/plugins/solar_control/SolarControl.cpp @@ -359,9 +359,9 @@ namespace Plugins::SolarControl /** @ingroup SolarControl * @brief Creates a premade group of solars defined in the solar json file */ - void CreateUserDefinedSolarFormation(const SolarArchFormation& formation, const Vector& position, uint system) + void CreateUserDefinedSolarFormation(const std::wstring& formation, const Vector& position, uint system) { - for (const auto& component : formation.components) + for (auto component : global->config->solarArchFormations[formation].components) { CreateUserDefinedSolar(stows(component.solarArchName), Vector { @@ -454,7 +454,7 @@ namespace Plugins::SolarControl Matrix rot {}; pub::SpaceObj::GetLocation(ship, pos, rot); - CreateUserDefinedSolarFormation(global->config->solarArchFormations[formationName], pos, system); + CreateUserDefinedSolarFormation(formationName, pos, system); } /** @ingroup SolarControl diff --git a/plugins/solar_control/SolarControl.h b/plugins/solar_control/SolarControl.h index a3df39fd0..62a7674df 100644 --- a/plugins/solar_control/SolarControl.h +++ b/plugins/solar_control/SolarControl.h @@ -64,7 +64,7 @@ namespace Plugins::SolarControl explicit SolarCommunicator(const std::string& plug); uint PluginCall(CreateSolar, const std::wstring& name, Vector position, const Matrix& rotation, SystemId system, bool varyPosition, bool mission); - void PluginCall(CreateSolarFormation, const SolarArchFormation& formation, const Vector& position, uint system); + void PluginCall(CreateSolarFormation, const std::wstring& formation, const Vector& position, uint system); }; //! Global data for this plugin diff --git a/project/FLHook.sln b/project/FLHook.sln index 20bbc91da..f6aa764c0 100644 --- a/project/FLHook.sln +++ b/project/FLHook.sln @@ -88,6 +88,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Daily Tasks", "..\plugins\d EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Solar Control", "..\plugins\solar_control\solar_control.vcxproj", "{6F69FEED-80B6-4DFB-865E-90A195281BAB}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Advanced Startup Solars", "..\plugins\advanced_startup_solars\advanced_startup_solars.vcxproj", "{ECC3FEAC-C696-40FA-B3D4-75BED5436199}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Release|Win32 = Release|Win32 @@ -310,6 +312,14 @@ Global {6F69FEED-80B6-4DFB-865E-90A195281BAB}.ReleaseWithDebug|Win32.Build.0 = ReleaseWithDebug|Win32 {6F69FEED-80B6-4DFB-865E-90A195281BAB}.ReleaseWithDebug|x64.ActiveCfg = ReleaseWithDebug|Win32 {6F69FEED-80B6-4DFB-865E-90A195281BAB}.ReleaseWithDebug|x64.Build.0 = ReleaseWithDebug|Win32 + {ECC3FEAC-C696-40FA-B3D4-75BED5436199}.Release|Win32.ActiveCfg = Release|Win32 + {ECC3FEAC-C696-40FA-B3D4-75BED5436199}.Release|Win32.Build.0 = Release|Win32 + {ECC3FEAC-C696-40FA-B3D4-75BED5436199}.Release|x64.ActiveCfg = Release|Win32 + {ECC3FEAC-C696-40FA-B3D4-75BED5436199}.Release|x64.Build.0 = Release|Win32 + {ECC3FEAC-C696-40FA-B3D4-75BED5436199}.ReleaseWithDebug|Win32.ActiveCfg = ReleaseWithDebug|Win32 + {ECC3FEAC-C696-40FA-B3D4-75BED5436199}.ReleaseWithDebug|Win32.Build.0 = ReleaseWithDebug|Win32 + {ECC3FEAC-C696-40FA-B3D4-75BED5436199}.ReleaseWithDebug|x64.ActiveCfg = ReleaseWithDebug|Win32 + {ECC3FEAC-C696-40FA-B3D4-75BED5436199}.ReleaseWithDebug|x64.Build.0 = ReleaseWithDebug|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -350,6 +360,7 @@ Global {DECED50E-AEE2-4327-94FC-6DAAEA6D8B61} = {88754785-84CA-4F78-8906-F8C39564BA83} {4270E660-9DF3-4BB3-B55D-5107824D9D10} = {88754785-84CA-4F78-8906-F8C39564BA83} {6F69FEED-80B6-4DFB-865E-90A195281BAB} = {88754785-84CA-4F78-8906-F8C39564BA83} + {ECC3FEAC-C696-40FA-B3D4-75BED5436199} = {88754785-84CA-4F78-8906-F8C39564BA83} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {BAC9ADCA-E201-4AA0-8494-1C3804461C24}