Merge branch 'main' into more-behaviors

This commit is contained in:
David Markowitz 2025-06-10 21:49:40 -07:00
commit 263c329932
22 changed files with 162 additions and 68 deletions

View File

@ -16,7 +16,10 @@ body:
I have validated that this issue is not a syntax error of either MySQL or SQLite. I have validated that this issue is not a syntax error of either MySQL or SQLite.
required: true required: true
- label: > - label: >
I have pulled the latest version of the main branch of DarkflameServer and have confirmed that the issue exists there. I have downloaded/pulled the latest version of the main branch of DarkflameServer and have confirmed that the issue exists there.
required: true
- label: >
I have verified that my boot.cfg is configured as per the [README](https://github.com/DarkflameUniverse/DarkflameServer?tab=readme-ov-file#allowing-a-user-to-connect-to-your-server).
required: true required: true
- type: input - type: input
id: server-version id: server-version

View File

@ -324,13 +324,15 @@ While a character has a gmlevel of anything but `0`, some gameplay behavior will
Some changes to the client `boot.cfg` file are needed to play on your server. Some changes to the client `boot.cfg` file are needed to play on your server.
## Allowing a user to connect to your server ## Allowing a user to connect to your server
**ALL OF THESE CHANGES ARE REQUIRED. PLEASE FULLY READ THIS SECTION**
To connect to a server follow these steps: To connect to a server follow these steps:
* In the client directory, locate `boot.cfg` * In the client directory, locate `boot.cfg`
* Open it in a text editor and locate where it says `AUTHSERVERIP=0:` * Open `boot.cfg` in a text editor and locate the line `UGCUSE3DSERVICES=7:`
* Replace the contents after to `:` and the following `,` with what you configured as the server's public facing IP. For example `AUTHSERVERIP=0:localhost` for locally hosted servers
* Next locate the line `UGCUSE3DSERVICES=7:`
* Ensure the number after the 7 is a `0` * Ensure the number after the 7 is a `0`
* Alternatively, remove the line with `UGCUSE3DSERVICES` altogether * Alternatively, remove the line with `UGCUSE3DSERVICES` altogether
* Next locate where it says `AUTHSERVERIP=0:`
* Replace the contents after to `:` and the following `,` with what you configured as the server's public facing IP. For example `AUTHSERVERIP=0:localhost` for locally hosted servers
* Launch `legouniverse.exe`, through `wine` if on a Unix-like operating system * Launch `legouniverse.exe`, through `wine` if on a Unix-like operating system
* Note that if you are on WSL2, you will need to configure the public IP in the server and client to be the IP of the WSL2 instance and not localhost, which can be found by running `ifconfig` in the terminal. Windows defaults to WSL1, so this will not apply to most users. * Note that if you are on WSL2, you will need to configure the public IP in the server and client to be the IP of the WSL2 instance and not localhost, which can be found by running `ifconfig` in the terminal. Windows defaults to WSL1, so this will not apply to most users.
As an example, here is what the boot.cfg is required to contain for a server with the ip 12.34.56.78 As an example, here is what the boot.cfg is required to contain for a server with the ip 12.34.56.78

View File

@ -4,7 +4,7 @@
#include <assert.h> #include <assert.h>
#ifdef _DEBUG #ifdef _DEBUG
# define DluAssert(expression) do { assert(expression) } while(0) # define DluAssert(expression) do { assert(expression); } while(0)
#else #else
# define DluAssert(expression) # define DluAssert(expression)
#endif #endif

View File

@ -83,6 +83,12 @@ public:
this->value = value; this->value = value;
} }
//! Initializer
LDFData(const std::string& key, const T& value) {
this->key = GeneralUtils::ASCIIToUTF16(key);
this->value = value;
}
//! Destructor //! Destructor
~LDFData(void) override {} ~LDFData(void) override {}

View File

@ -320,7 +320,7 @@ const std::unordered_map<std::string, LWOOBJID>& EntityManager::GetSpawnPointEnt
return m_SpawnPoints; return m_SpawnPoints;
} }
void EntityManager::ConstructEntity(Entity* entity, const SystemAddress& sysAddr, const bool skipChecks) { void EntityManager::ConstructEntity(Entity* entity, const SystemAddress& sysAddr) {
if (!entity) { if (!entity) {
LOG("Attempted to construct null entity"); LOG("Attempted to construct null entity");
return; return;
@ -363,10 +363,9 @@ void EntityManager::ConstructEntity(Entity* entity, const SystemAddress& sysAddr
entity->WriteComponents(stream, eReplicaPacketType::CONSTRUCTION); entity->WriteComponents(stream, eReplicaPacketType::CONSTRUCTION);
if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) { if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) {
if (skipChecks) {
Game::server->Send(stream, UNASSIGNED_SYSTEM_ADDRESS, true);
} else {
for (auto* player : PlayerManager::GetAllPlayers()) { for (auto* player : PlayerManager::GetAllPlayers()) {
// Don't need to construct the player to themselves
if (entity->GetObjectID() == player->GetObjectID()) continue;
if (player->GetPlayerReadyForUpdates()) { if (player->GetPlayerReadyForUpdates()) {
Game::server->Send(stream, player->GetSystemAddress(), false); Game::server->Send(stream, player->GetSystemAddress(), false);
} else { } else {
@ -374,7 +373,6 @@ void EntityManager::ConstructEntity(Entity* entity, const SystemAddress& sysAddr
if (ghostComponent) ghostComponent->AddLimboConstruction(entity->GetObjectID()); if (ghostComponent) ghostComponent->AddLimboConstruction(entity->GetObjectID());
} }
} }
}
} else { } else {
Game::server->Send(stream, sysAddr, false); Game::server->Send(stream, sysAddr, false);
} }

View File

@ -42,7 +42,7 @@ public:
const std::unordered_map<LWOOBJID, Entity*> GetAllEntities() const { return m_Entities; } const std::unordered_map<LWOOBJID, Entity*> GetAllEntities() const { return m_Entities; }
#endif #endif
void ConstructEntity(Entity* entity, const SystemAddress& sysAddr = UNASSIGNED_SYSTEM_ADDRESS, bool skipChecks = false); void ConstructEntity(Entity* entity, const SystemAddress& sysAddr = UNASSIGNED_SYSTEM_ADDRESS);
void DestructEntity(Entity* entity, const SystemAddress& sysAddr = UNASSIGNED_SYSTEM_ADDRESS); void DestructEntity(Entity* entity, const SystemAddress& sysAddr = UNASSIGNED_SYSTEM_ADDRESS);
void SerializeEntity(Entity* entity); void SerializeEntity(Entity* entity);
void SerializeEntity(const Entity& entity); void SerializeEntity(const Entity& entity);

View File

@ -334,7 +334,7 @@ bool ActivityComponent::IsPlayedBy(LWOOBJID playerID) const {
return false; return false;
} }
bool ActivityComponent::TakeCost(Entity* player) const { bool ActivityComponent::CheckCost(Entity* player) const {
if (m_ActivityInfo.optionalCostLOT <= 0 || m_ActivityInfo.optionalCostCount <= 0) if (m_ActivityInfo.optionalCostLOT <= 0 || m_ActivityInfo.optionalCostCount <= 0)
return true; return true;
@ -345,11 +345,19 @@ bool ActivityComponent::TakeCost(Entity* player) const {
if (inventoryComponent->GetLotCount(m_ActivityInfo.optionalCostLOT) < m_ActivityInfo.optionalCostCount) if (inventoryComponent->GetLotCount(m_ActivityInfo.optionalCostLOT) < m_ActivityInfo.optionalCostCount)
return false; return false;
inventoryComponent->RemoveItem(m_ActivityInfo.optionalCostLOT, m_ActivityInfo.optionalCostCount);
return true; return true;
} }
bool ActivityComponent::TakeCost(Entity* player) const{
auto* inventoryComponent = player->GetComponent<InventoryComponent>();
if (CheckCost(player)) {
inventoryComponent->RemoveItem(m_ActivityInfo.optionalCostLOT, m_ActivityInfo.optionalCostCount);
return true;
}
else return false;
}
void ActivityComponent::PlayerReady(Entity* player, bool bReady) { void ActivityComponent::PlayerReady(Entity* player, bool bReady) {
for (Lobby* lobby : m_Queue) { for (Lobby* lobby : m_Queue) {
for (LobbyPlayer* lobbyPlayer : lobby->players) { for (LobbyPlayer* lobbyPlayer : lobby->players) {
@ -382,7 +390,7 @@ ActivityInstance* ActivityComponent::NewInstance() {
void ActivityComponent::LoadPlayersIntoInstance(ActivityInstance* instance, const std::vector<LobbyPlayer*>& lobby) const { void ActivityComponent::LoadPlayersIntoInstance(ActivityInstance* instance, const std::vector<LobbyPlayer*>& lobby) const {
for (LobbyPlayer* player : lobby) { for (LobbyPlayer* player : lobby) {
auto* entity = player->GetEntity(); auto* entity = player->GetEntity();
if (entity == nullptr || !TakeCost(entity)) { if (entity == nullptr || !CheckCost(entity)) {
continue; continue;
} }

View File

@ -234,10 +234,17 @@ public:
*/ */
bool IsPlayedBy(LWOOBJID playerID) const; bool IsPlayedBy(LWOOBJID playerID) const;
/**
* Checks if the entity has enough cost to play this activity
* @param player the entity to check
* @return true if the entity has enough cost to play this activity, false otherwise
*/
bool CheckCost(Entity* player) const;
/** /**
* Removes the cost of the activity (e.g. green imaginate) for the entity that plays this activity * Removes the cost of the activity (e.g. green imaginate) for the entity that plays this activity
* @param player the entity to take cost for * @param player the entity to take cost for
* @return true if the cost was successfully deducted, false otherwise * @return true if the cost was taken, false otherwise
*/ */
bool TakeCost(Entity* player) const; bool TakeCost(Entity* player) const;

View File

@ -54,6 +54,7 @@ MovementAIComponent::MovementAIComponent(Entity* parent, MovementAIInfo info) :
m_SourcePosition = m_Parent->GetPosition(); m_SourcePosition = m_Parent->GetPosition();
m_Paused = false; m_Paused = false;
m_SavedVelocity = NiPoint3Constant::ZERO; m_SavedVelocity = NiPoint3Constant::ZERO;
m_IsBounced = false;
if (!m_Parent->GetComponent<BaseCombatAIComponent>()) SetPath(m_Parent->GetVarAsString(u"attached_path")); if (!m_Parent->GetComponent<BaseCombatAIComponent>()) SetPath(m_Parent->GetVarAsString(u"attached_path"));
} }
@ -158,8 +159,9 @@ void MovementAIComponent::Update(const float deltaTime) {
if (m_Path->pathBehavior == PathBehavior::Loop) { if (m_Path->pathBehavior == PathBehavior::Loop) {
SetPath(m_Path->pathWaypoints); SetPath(m_Path->pathWaypoints);
} else if (m_Path->pathBehavior == PathBehavior::Bounce) { } else if (m_Path->pathBehavior == PathBehavior::Bounce) {
m_IsBounced = !m_IsBounced;
std::vector<PathWaypoint> waypoints = m_Path->pathWaypoints; std::vector<PathWaypoint> waypoints = m_Path->pathWaypoints;
std::reverse(waypoints.begin(), waypoints.end()); if (m_IsBounced) std::reverse(waypoints.begin(), waypoints.end());
SetPath(waypoints); SetPath(waypoints);
} else if (m_Path->pathBehavior == PathBehavior::Once) { } else if (m_Path->pathBehavior == PathBehavior::Once) {
Stop(); Stop();

View File

@ -321,6 +321,8 @@ private:
bool m_Paused; bool m_Paused;
NiPoint3 m_SavedVelocity; NiPoint3 m_SavedVelocity;
bool m_IsBounced{};
}; };
#endif // MOVEMENTAICOMPONENT_H #endif // MOVEMENTAICOMPONENT_H

View File

@ -42,35 +42,6 @@ std::unordered_map<LWOOBJID, LWOOBJID> PetComponent::activePets{};
* Maps all the pet lots to a flag indicating that the player has caught it. All basic pets have been guessed by ObjID * Maps all the pet lots to a flag indicating that the player has caught it. All basic pets have been guessed by ObjID
* while the faction ones could be checked using their respective missions. * while the faction ones could be checked using their respective missions.
*/ */
const std::map<LOT, int32_t> PetComponent::petFlags{
{ 3050, 801 }, // Elephant
{ 3054, 803 }, // Cat
{ 3195, 806 }, // Triceratops
{ 3254, 807 }, // Terrier
{ 3261, 811 }, // Skunk
{ 3672, 813 }, // Bunny
{ 3994, 814 }, // Crocodile
{ 5635, 815 }, // Doberman
{ 5636, 816 }, // Buffalo
{ 5637, 818 }, // Robot Dog
{ 5639, 819 }, // Red Dragon
{ 5640, 820 }, // Tortoise
{ 5641, 821 }, // Green Dragon
{ 5643, 822 }, // Panda, see mission 786
{ 5642, 823 }, // Mantis
{ 6720, 824 }, // Warthog
{ 3520, 825 }, // Lion, see mission 1318
{ 7638, 826 }, // Goat
{ 7694, 827 }, // Crab
{ 12294, 829 }, // Reindeer
{ 12431, 830 }, // Stegosaurus, see mission 1386
{ 12432, 831 }, // Saber cat, see mission 1389
{ 12433, 832 }, // Gryphon, see mission 1392
{ 12434, 833 }, // Alien, see mission 1188
// 834: unknown?, see mission 506, 688
{ 16210, 836 }, // Ninjago Earth Dragon, see mission 1836
{ 13067, 838 }, // Skeleton dragon
};
PetComponent::PetComponent(Entity* parentEntity, uint32_t componentId) : Component{ parentEntity } { PetComponent::PetComponent(Entity* parentEntity, uint32_t componentId) : Component{ parentEntity } {
m_PetInfo = CDClientManager::GetTable<CDPetComponentTable>()->GetByID(componentId); // TODO: Make reference when safe m_PetInfo = CDClientManager::GetTable<CDPetComponentTable>()->GetByID(componentId); // TODO: Make reference when safe
@ -556,9 +527,8 @@ void PetComponent::NotifyTamingBuildSuccess(NiPoint3 position) {
); );
// Triggers the catch a pet missions // Triggers the catch a pet missions
if (petFlags.find(m_Parent->GetLOT()) != petFlags.end()) { constexpr auto PET_FLAG_BASE = 800;
tamer->GetCharacter()->SetPlayerFlag(petFlags.at(m_Parent->GetLOT()), true); tamer->GetCharacter()->SetPlayerFlag(PET_FLAG_BASE + m_ComponentId, true);
}
auto* missionComponent = tamer->GetComponent<MissionComponent>(); auto* missionComponent = tamer->GetComponent<MissionComponent>();

View File

@ -250,11 +250,6 @@ private:
*/ */
static std::unordered_map<LWOOBJID, LWOOBJID> currentActivities; static std::unordered_map<LWOOBJID, LWOOBJID> currentActivities;
/**
* Flags that indicate that a player has tamed a pet, indexed by the LOT of the pet
*/
static const std::map<LOT, int32_t> petFlags;
/** /**
* The ID of the component in the pet component table * The ID of the component in the pet component table
*/ */

View File

@ -272,6 +272,10 @@ void PropertyManagementComponent::OnStartBuilding() {
model->HandleMsg(reset); model->HandleMsg(reset);
} }
} }
for (auto* const entity : Game::entityManager->GetEntitiesInGroup("SpawnedPropertyEnemies")) {
if (entity) entity->Smash();
}
} }
void PropertyManagementComponent::OnFinishBuilding() { void PropertyManagementComponent::OnFinishBuilding() {
@ -296,6 +300,10 @@ void PropertyManagementComponent::OnFinishBuilding() {
model->HandleMsg(reset); model->HandleMsg(reset);
} }
} }
for (auto* const entity : Game::entityManager->GetEntitiesInGroup("SpawnedPropertyEnemies")) {
if (entity) entity->Smash();
}
} }
void PropertyManagementComponent::UpdateModelPosition(const LWOOBJID id, const NiPoint3 position, NiQuaternion rotation) { void PropertyManagementComponent::UpdateModelPosition(const LWOOBJID id, const NiPoint3 position, NiQuaternion rotation) {

View File

@ -84,9 +84,11 @@ void Strip::HandleMsg(MigrateActionsMessage& msg) {
template<> template<>
void Strip::HandleMsg(GameMessages::RequestUse& msg) { void Strip::HandleMsg(GameMessages::RequestUse& msg) {
if (m_PausedTime > 0.0f) return; if (m_PausedTime > 0.0f || !HasMinimumActions()) return;
if (m_Actions[m_NextActionIndex].GetType() == "OnInteract") { auto& nextAction = GetNextAction();
if (nextAction.GetType() == "OnInteract") {
IncrementAction(); IncrementAction();
m_WaitingForAction = false; m_WaitingForAction = false;
} }
@ -113,7 +115,9 @@ void Strip::Spawn(LOT lot, Entity& entity) {
info.pos = entity.GetPosition(); info.pos = entity.GetPosition();
info.rot = NiQuaternionConstant::IDENTITY; info.rot = NiQuaternionConstant::IDENTITY;
info.spawnerID = entity.GetObjectID(); info.spawnerID = entity.GetObjectID();
Game::entityManager->ConstructEntity(Game::entityManager->CreateEntity(info, nullptr, &entity)); auto* const spawnedEntity = Game::entityManager->CreateEntity(info, nullptr, &entity);
spawnedEntity->AddToGroup("SpawnedPropertyEnemies");
Game::entityManager->ConstructEntity(spawnedEntity);
} }
// Spawns a specific drop for all // Spawns a specific drop for all
@ -139,6 +143,7 @@ void Strip::ProcNormalAction(float deltaTime, ModelComponent& modelComponent) {
// Default velocity is 3 units per second. // Default velocity is 3 units per second.
entity.SetVelocity(NiPoint3{0.0f, isFlyDown ? -3.0f : 3.0f, 0.0f}); entity.SetVelocity(NiPoint3{0.0f, isFlyDown ? -3.0f : 3.0f, 0.0f});
modelComponent.SetVelocity(NiPoint3{0.0f, isFlyDown ? -3.0f : 3.0f, 0.0f});
} }
else if (nextActionType == "MoveRight" || nextActionType == "MoveLeft") { else if (nextActionType == "MoveRight" || nextActionType == "MoveLeft") {
bool isMoveLeft = nextActionType == "MoveLeft"; bool isMoveLeft = nextActionType == "MoveLeft";
@ -205,7 +210,6 @@ void Strip::ProcNormalAction(float deltaTime, ModelComponent& modelComponent) {
LOG("Tried to play action (%s) which is not supported.", nextActionType.data()); LOG("Tried to play action (%s) which is not supported.", nextActionType.data());
g_WarnedActions.insert(nextActionType.data()); g_WarnedActions.insert(nextActionType.data());
} }
return;
} }
IncrementAction(); IncrementAction();
@ -259,13 +263,19 @@ bool Strip::CheckMovement(float deltaTime, ModelComponent& modelComponent) {
} }
void Strip::Update(float deltaTime, ModelComponent& modelComponent) { void Strip::Update(float deltaTime, ModelComponent& modelComponent) {
// No point in running a strip with only one action.
// Strips are also designed to have 2 actions or more to run.
if (!HasMinimumActions()) return;
if (!CheckMovement(deltaTime, modelComponent)) return; if (!CheckMovement(deltaTime, modelComponent)) return;
// Don't run this strip if we're paused.
m_PausedTime -= deltaTime; m_PausedTime -= deltaTime;
if (m_PausedTime > 0.0f) return; if (m_PausedTime > 0.0f) return;
m_PausedTime = 0.0f; m_PausedTime = 0.0f;
// Return here if we're waiting for external interactions to continue.
if (m_WaitingForAction) return; if (m_WaitingForAction) return;
auto& entity = *modelComponent.GetParent(); auto& entity = *modelComponent.GetParent();

View File

@ -37,6 +37,9 @@ public:
void SpawnDrop(LOT dropLOT, Entity& entity); void SpawnDrop(LOT dropLOT, Entity& entity);
void ProcNormalAction(float deltaTime, ModelComponent& modelComponent); void ProcNormalAction(float deltaTime, ModelComponent& modelComponent);
void RemoveStates(ModelComponent& modelComponent) const; void RemoveStates(ModelComponent& modelComponent) const;
// 2 actions are required for strips to work
bool HasMinimumActions() const { return m_Actions.size() >= 2; }
private: private:
// Indicates this Strip is waiting for an action to be taken upon it to progress to its actions // Indicates this Strip is waiting for an action to be taken upon it to progress to its actions
bool m_WaitingForAction{ false }; bool m_WaitingForAction{ false };

View File

@ -336,6 +336,7 @@
#include "NsRaceServer.h" #include "NsRaceServer.h"
#include "TrialFactionArmorServer.h" #include "TrialFactionArmorServer.h"
#include "ImaginationBackPack.h" #include "ImaginationBackPack.h"
#include "NsWinterRaceServer.h"
#include <map> #include <map>
#include <string> #include <string>
@ -656,6 +657,7 @@ namespace {
//FB //FB
{"scripts\\ai\\NS\\WH\\L_ROCKHYDRANT_BROKEN.lua", []() {return new RockHydrantBroken();}}, {"scripts\\ai\\NS\\WH\\L_ROCKHYDRANT_BROKEN.lua", []() {return new RockHydrantBroken();}},
{"scripts\\ai\\NS\\L_NS_WH_FANS.lua", []() {return new WhFans();}}, {"scripts\\ai\\NS\\L_NS_WH_FANS.lua", []() {return new WhFans();}},
{"scripts\\ai\\RACING\\TRACK_NS_WINTER\\NS_WINTER_RACE_SERVER.lua", []() {return new NsWinterRaceServer();}},
//WBL //WBL
{"scripts\\zone\\LUPs\\WBL_generic_zone.lua", []() {return new WblGenericZone();}}, {"scripts\\zone\\LUPs\\WBL_generic_zone.lua", []() {return new WblGenericZone();}},

View File

@ -13,6 +13,12 @@ foreach(file ${DSCRIPTS_SOURCES_AI_RACING_TRACK_NS})
set(DSCRIPTS_SOURCES_AI_RACING ${DSCRIPTS_SOURCES_AI_RACING} "TRACK_NS/${file}") set(DSCRIPTS_SOURCES_AI_RACING ${DSCRIPTS_SOURCES_AI_RACING} "TRACK_NS/${file}")
endforeach() endforeach()
add_subdirectory(TRACK_NS_WINTER)
foreach(file ${DSCRIPTS_SOURCES_AI_RACING_TRACK_NS_WINTER})
set(DSCRIPTS_SOURCES_AI_RACING ${DSCRIPTS_SOURCES_AI_RACING} "TRACK_NS_WINTER/${file}")
endforeach()
add_subdirectory(TRACK_GF) add_subdirectory(TRACK_GF)
foreach(file ${DSCRIPTS_SOURCES_AI_RACING_TRACK_GF}) foreach(file ${DSCRIPTS_SOURCES_AI_RACING_TRACK_GF})
@ -26,5 +32,5 @@ foreach(file ${DSCRIPTS_SOURCES_AI_RACING_TRACK_FV})
endforeach() endforeach()
add_library(dScriptsAiRacing OBJECT ${DSCRIPTS_SOURCES_AI_RACING}) add_library(dScriptsAiRacing OBJECT ${DSCRIPTS_SOURCES_AI_RACING})
target_include_directories(dScriptsAiRacing PUBLIC "." "OBJECTS" "TRACK_NS" "TRACK_GF" "TRACK_FV") target_include_directories(dScriptsAiRacing PUBLIC "." "OBJECTS" "TRACK_NS" "TRACK_NS_WINTER" "TRACK_GF" "TRACK_FV")
target_precompile_headers(dScriptsAiRacing REUSE_FROM dScriptsBase) target_precompile_headers(dScriptsAiRacing REUSE_FROM dScriptsBase)

View File

@ -0,0 +1,3 @@
set(DSCRIPTS_SOURCES_AI_RACING_TRACK_NS_WINTER
"NsWinterRaceServer.cpp"
PARENT_SCOPE)

View File

@ -0,0 +1,53 @@
#include "NsWinterRaceServer.h"
#include "GameMessages.h"
#include "RacingControlComponent.h"
using std::unique_ptr;
using std::make_unique;
void NsWinterRaceServer::OnStartup(Entity* self) {
GameMessages::ConfigureRacingControl config;
auto& raceSet = config.racingSettings;
raceSet.push_back(make_unique<LDFData<std::u16string>>("GameType", u"Racing"));
raceSet.push_back(make_unique<LDFData<std::u16string>>("GameState", u"Starting"));
raceSet.push_back(make_unique<LDFData<int32_t>>("Number_Of_PlayersPerTeam", 6));
raceSet.push_back(make_unique<LDFData<int32_t>>("Minimum_Players_to_Start", 2));
raceSet.push_back(make_unique<LDFData<int32_t>>("Minimum_Players_for_Group_Achievments", 2));
raceSet.push_back(make_unique<LDFData<int32_t>>("Car_Object", 7703));
raceSet.push_back(make_unique<LDFData<std::u16string>>("Race_PathName", u"MainPath"));
raceSet.push_back(make_unique<LDFData<int32_t>>("Current_Lap", 1));
raceSet.push_back(make_unique<LDFData<int32_t>>("Number_of_Laps", 3));
raceSet.push_back(make_unique<LDFData<int32_t>>("activityID", 42));
raceSet.push_back(make_unique<LDFData<int32_t>>("Place_1", 100));
raceSet.push_back(make_unique<LDFData<int32_t>>("Place_2", 90));
raceSet.push_back(make_unique<LDFData<int32_t>>("Place_3", 80));
raceSet.push_back(make_unique<LDFData<int32_t>>("Place_4", 70));
raceSet.push_back(make_unique<LDFData<int32_t>>("Place_5", 60));
raceSet.push_back(make_unique<LDFData<int32_t>>("Place_6", 50));
raceSet.push_back(make_unique<LDFData<int32_t>>("Num_of_Players_1", 15));
raceSet.push_back(make_unique<LDFData<int32_t>>("Num_of_Players_2", 25));
raceSet.push_back(make_unique<LDFData<int32_t>>("Num_of_Players_3", 50));
raceSet.push_back(make_unique<LDFData<int32_t>>("Num_of_Players_4", 85));
raceSet.push_back(make_unique<LDFData<int32_t>>("Num_of_Players_5", 90));
raceSet.push_back(make_unique<LDFData<int32_t>>("Num_of_Players_6", 100));
raceSet.push_back(make_unique<LDFData<int32_t>>("Number_of_Spawn_Groups", 1));
raceSet.push_back(make_unique<LDFData<int32_t>>("Red_Spawners", 4847));
raceSet.push_back(make_unique<LDFData<int32_t>>("Blue_Spawners", 4848));
raceSet.push_back(make_unique<LDFData<int32_t>>("Blue_Flag", 4850));
raceSet.push_back(make_unique<LDFData<int32_t>>("Red_Flag", 4851));
raceSet.push_back(make_unique<LDFData<int32_t>>("Red_Point", 4846));
raceSet.push_back(make_unique<LDFData<int32_t>>("Blue_Point", 4845));
raceSet.push_back(make_unique<LDFData<int32_t>>("Red_Mark", 4844));
raceSet.push_back(make_unique<LDFData<int32_t>>("Blue_Mark", 4843));
const auto racingControllers = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::RACING_CONTROL);
for (auto* const racingController : racingControllers) {
auto* const racingComponent = racingController->GetComponent<RacingControlComponent>();
if (racingComponent) racingComponent->MsgConfigureRacingControl(config);
}
}

View File

@ -0,0 +1,15 @@
// Darkflame Universe
// Copyright 2025
#ifndef NSWINTERRACESERVER_H
#define NSWINTERRACESERVER_H
#include "CppScripts.h"
#include "RaceImaginationServer.h"
class NsWinterRaceServer : public RaceImaginationServer {
public:
void OnStartup(Entity* self) override;
};
#endif //!NSWINTERRACESERVER_H

View File

@ -713,12 +713,12 @@ void HandleMasterPacket(Packet* packet) {
//Create our user and send them in: //Create our user and send them in:
UserManager::Instance()->CreateUser(it->second.sysAddr, username.GetAsString(), userHash); UserManager::Instance()->CreateUser(it->second.sysAddr, username.GetAsString(), userHash);
auto zone = Game::zoneManager->GetZone(); if (Game::zoneManager->HasZone()) {
if (zone) {
float x = 0.0f; float x = 0.0f;
float y = 0.0f; float y = 0.0f;
float z = 0.0f; float z = 0.0f;
auto zone = Game::zoneManager->GetZone();
if (zone->GetZoneID().GetMapID() == 1100) { if (zone->GetZoneID().GetMapID() == 1100) {
auto pos = zone->GetSpawnPos(); auto pos = zone->GetSpawnPos();
x = pos.x; x = pos.x;
@ -1045,7 +1045,7 @@ void HandlePacket(Packet* packet) {
const auto respawnPoint = player->GetCharacter()->GetRespawnPoint(Game::zoneManager->GetZone()->GetWorldID()); const auto respawnPoint = player->GetCharacter()->GetRespawnPoint(Game::zoneManager->GetZone()->GetWorldID());
Game::entityManager->ConstructEntity(player, UNASSIGNED_SYSTEM_ADDRESS, true); Game::entityManager->ConstructEntity(player, UNASSIGNED_SYSTEM_ADDRESS);
if (respawnPoint != NiPoint3Constant::ZERO) { if (respawnPoint != NiPoint3Constant::ZERO) {
GameMessages::SendPlayerReachedRespawnCheckpoint(player, respawnPoint, NiQuaternionConstant::IDENTITY); GameMessages::SendPlayerReachedRespawnCheckpoint(player, respawnPoint, NiQuaternionConstant::IDENTITY);

View File

@ -29,6 +29,7 @@ public:
/* Gets a pointer to the currently loaded zone. */ /* Gets a pointer to the currently loaded zone. */
Zone* GetZoneMut() const; Zone* GetZoneMut() const;
const Zone* GetZone() const { return GetZoneMut(); }; const Zone* GetZone() const { return GetZoneMut(); };
bool HasZone() const { return m_pZone != nullptr; };
void LoadZone(const LWOZONEID& zoneID); //Discard the current zone (if any) and loads a new zone. void LoadZone(const LWOZONEID& zoneID); //Discard the current zone (if any) and loads a new zone.
/* Adds a spawner to the zone with the specified ID. */ /* Adds a spawner to the zone with the specified ID. */