Skip to content

Commit

Permalink
Advanced Startup Solars and Minor Solar Fixes (#419)
Browse files Browse the repository at this point in the history
* feat: Clean template for Advanced Startup Solars Plugin

* feat: Connect IPC interfaces for solar.dll and npc.dll, checks to ensure they're loaded at startup

* fix: Corrections for namespace and config files

* feat: Include `RandomNumber` (Definitely going to need this)

* feat: Reflect configs completed, some minor validation of user inputs

* fix: Adjust IPC for solar_control, it now takes a wstring so CreateSolarFormation() can actually be used

* feat: First fully functional build of advanced_startup_solars

* feat: Adjust config files so we can have cross-system solarFamilies

* chore: Rename .cpp and .hpp files

* feat: Write inline documentation for plugin

* chore: Update changelog

* fix: Fixes and formatting changes requested by @Lazrius

* fix: Adjustment requested by @oliverpechey
  • Loading branch information
IrateRedKite authored Apr 26, 2024
1 parent 2761cbb commit 071be12
Show file tree
Hide file tree
Showing 9 changed files with 401 additions and 8 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Release
*.vcxproj.user
*.log
vcpkg_installed/
*.user

!misc/acctpath_patch/bin/*.cfg

Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
231 changes: 231 additions & 0 deletions plugins/advanced_startup_solars/AdvancedStartupSolars.cpp
Original file line number Diff line number Diff line change
@@ -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 <random>

namespace Plugins::AdvancedStartupSolars
{
const auto global = std::make_unique<Global>();

// 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<int> 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<Config>();
global->config = std::make_unique<Config>(std::move(config));

// Set the npcCommunicator and solarCommunicator interfaces and check if they are availlable
global->npcCommunicator =
static_cast<Plugins::Npc::NpcCommunicator*>(PluginCommunicator::ImportPluginCommunicator(Plugins::Npc::NpcCommunicator::pluginName));

global->solarCommunicator = static_cast<Plugins::SolarControl::SolarCommunicator*>(
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);
}
62 changes: 62 additions & 0 deletions plugins/advanced_startup_solars/AdvancedStartupSolars.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#pragma once

#include <FLHook.hpp>
#include <plugin.h>
#include <ranges>
#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<std::wstring, int> npcs;
//! The weight for this formation to spawn within the SolarFamily
int spawnWeight = 0;
};

//! Positional data for SolarFamily
struct Position final : Reflectable
{
std::vector<float> 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<SolarFormation> solarFormations;
//! A vector containing possible positions to spawn solarFormations at
std::vector<Position> 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<SolarFamily> solarFamilies;
};

//! Global data for this plugin
struct Global
{
std::unique_ptr<Config> 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
Loading

0 comments on commit 071be12

Please sign in to comment.