From f9cee040a6824b1b7ce84d2e19fd5649caec7c12 Mon Sep 17 00:00:00 2001 From: Serkan <14278530+SergeantSerk@users.noreply.github.com> Date: Fri, 26 Jun 2026 16:31:57 +0100 Subject: [PATCH 01/12] Crude magic selection implementation --- include/server/tracker/RaceTracker.hpp | 1 + src/libserver/network/Server.cpp | 2 +- src/server/race/RaceNetworkHandler.cpp | 95 ++++++++++++++++++++------ 3 files changed, 76 insertions(+), 22 deletions(-) diff --git a/include/server/tracker/RaceTracker.hpp b/include/server/tracker/RaceTracker.hpp index ca277ab02..c937712d1 100644 --- a/include/server/tracker/RaceTracker.hpp +++ b/include/server/tracker/RaceTracker.hpp @@ -77,6 +77,7 @@ class RaceTracker uint32_t jumpComboValue{}; uint32_t courseTime{InvalidCourseTime}; std::optional magicItem{}; + float progress{}; //! A set of tracked items in racer's proximity. std::unordered_set trackedDecks; diff --git a/src/libserver/network/Server.cpp b/src/libserver/network/Server.cpp index 102e06c3e..55d4c2a09 100644 --- a/src/libserver/network/Server.cpp +++ b/src/libserver/network/Server.cpp @@ -31,7 +31,7 @@ namespace server::network namespace { -constexpr std::size_t MaxConnectionsPerAddress = 3; +constexpr std::size_t MaxConnectionsPerAddress = 10; constexpr std::size_t MaxTotalConnections = 1024; constexpr std::size_t MaxConnectRatePerAddress = 10; constexpr auto RateWindow = std::chrono::seconds(30); diff --git a/src/server/race/RaceNetworkHandler.cpp b/src/server/race/RaceNetworkHandler.cpp index a151874fa..8aab0db9b 100644 --- a/src/server/race/RaceNetworkHandler.cpp +++ b/src/server/race/RaceNetworkHandler.cpp @@ -53,32 +53,79 @@ uint32_t GetMountStatValue( return 0; } +// Function to select a random item based on position weights +uint32_t SelectMagicTypeByPosition(uint32_t position, bool isTeam) +{ + // Validate position + if (position > 7) + throw std::out_of_range("Position must be between 0 and 7"); + + // Weights for each position (P1 to P8) + // Each inner vector corresponds to the weights for: + // Bolt, Shield, Boost, Feather, Ice, Chains, Haze, Dragon, Lightning, Wand, Gauge Boost, Team Boost + const std::vector> positionWeights = { + {5, 100, 10, 0, 70, 0, 0, 5, 0, 10, 0, 10}, // P1 + {30, 25, 45, 5, 15, 10, 30, 20, 5, 10, 0, 5}, // P2 + {35, 10, 35, 5, 10, 10, 35, 35, 5, 15, 0, 10}, // P3 + {35, 5, 35, 5, 10, 15, 30, 45, 5, 10, 0, 10}, // P4 + {25, 5, 45, 5, 5, 20, 30, 35, 5, 10, 0, 10}, // P5 + {40, 5, 45, 5, 5, 20, 30, 25, 5, 10, 0, 10}, // P6 + {20, 5, 55, 15, 5, 10, 30, 25, 10, 10, 0, 10}, // P7 + {10, 5, 70, 35, 5, 10, 15, 10, 10, 15, 0, 10} // P8 + }; + + // Get the weights for the specified position + const std::vector& weights = positionWeights[position]; + auto weightsEndIter = weights.end(); + if (not isTeam) + // Exclude team item weights + weightsEndIter -= 3; + + // Create a discrete distribution based on the weights + std::discrete_distribution dist(weights.begin(), weightsEndIter); + + // Random number generator + static std::random_device rd; + static std::mt19937 gen(rd()); + + // Select a random index based on the distribution + // and map the discrete index to the corresponding magic item + const uint32_t selectedWeight = dist(gen); + + // Discrete weights to magic item id mapping example: + // TODO: sensitive to weights ordering + // 0: (0 + 1) * 2 = 2 (Bolt) + // 5: (5 + 1) * 2 = 12 (Chains) + // 8: (8 + 1) * 2 = 18 (Lightning) + return (selectedWeight + 1) * 2; +} + registry::Magic::SlotInfo RandomMagicItem( ServerInstance& serverInstance, - const tracker::RaceTracker::Racer& racer) + tracker::RaceTracker& tracker, + data::Uid racerUid) { - const auto& itemPool = (racer.team == tracker::RaceTracker::Racer::Team::Solo - ? serverInstance.GetMagicRegistry().GetSoloPool() - : serverInstance.GetMagicRegistry().GetTeamPool()); + const auto& racer = tracker.GetRacer(racerUid); - // Build weights: Lightning (type 18) gets a reduced roll chance. - // Booster, HotRodding, and team-only items get a slightly reduced chance as well. - // TODO: Replace with a proper per-spell weight system. - const auto& magicRegistry = serverInstance.GetMagicRegistry(); - std::vector weights; - weights.reserve(itemPool.size()); - for (const uint32_t type : itemPool) + // Get this racer's track progress + const float racerTrackProgress = racer.progress; + + // Determine the racer's position (0 = 1st place) + uint32_t racerPosition = 0; + for (const auto& [uid, instanceRacer] : tracker.GetRacers()) { - const auto& slotInfo = magicRegistry.GetSlotInfo(type); - const uint32_t w = (type == 18) ? 1u - : (slotInfo.basicType == 6 || slotInfo.basicType == 8 || slotInfo.basicType == 12) ? 2u - : (slotInfo.teamMode != 0) ? 2u - : 4u; - weights.push_back(w); + if (uid == racerUid) + continue; + if (instanceRacer.progress > racerTrackProgress) + racerPosition++; } + + const uint32_t positionMagicType = SelectMagicTypeByPosition( + racerPosition, + racer.team == protocol::TeamColor::Red or racer.team == protocol::TeamColor::Blue); - std::discrete_distribution distribution(weights.begin(), weights.end()); - auto magicSlotInfo = magicRegistry.GetSlotInfo(itemPool[distribution(_randomDevice)]); + const auto& magicRegistry = serverInstance.GetMagicRegistry(); + auto magicSlotInfo = magicRegistry.GetSlotInfo(positionMagicType); uint32_t critChanceBp = magicRegistry.GetBaseCritChanceBp(); if (magicSlotInfo.criticalType != 0) { @@ -2130,6 +2177,7 @@ void RaceNetworkHandler::HandleRaceUserPos( // TODO: player position anticheat racer.position = command.position; + racer.progress = command.member6; } void RaceNetworkHandler::HandleChat( @@ -2431,7 +2479,8 @@ void RaceNetworkHandler::HandleRequestMagicItem( std::scoped_lock lock(_raceInstancesMutex); auto& raceInstance = GetRaceInstance(clientContext); const auto& parameters = raceInstance.GetParameters(); - auto& racer = raceInstance.GetTracker().GetRacer(clientContext.characterUid); + auto& tracker = raceInstance.GetTracker(); + auto& racer = tracker.GetRacer(clientContext.characterUid); // TODO: Revise this on NPC races if (command.characterOid != racer.oid) @@ -2451,7 +2500,11 @@ void RaceNetworkHandler::HandleRequestMagicItem( return; } - racer.magicItem.emplace(RandomMagicItem(_serverInstance, racer).type); + const auto& magicItemSlotInfo = RandomMagicItem( + _serverInstance, + tracker, + clientContext.characterUid); + racer.magicItem.emplace(magicItemSlotInfo.type); racer.starPointValue = 0; protocol::AcCmdCRStarPointGetOK starPointResponse{ From 37f07cad664fe012de9603b51be61f35f91f00c9 Mon Sep 17 00:00:00 2001 From: Serkan <14278530+SergeantSerk@users.noreply.github.com> Date: Fri, 26 Jun 2026 21:51:31 +0100 Subject: [PATCH 02/12] Extract magic item weights to magic config --- include/libserver/registry/MagicRegistry.hpp | 5 +++++ resources/config/game/magic.yaml | 13 ++++++++++++ src/libserver/registry/MagicRegistry.cpp | 15 +++++++++++++ src/server/race/RaceNetworkHandler.cpp | 22 ++++++-------------- 4 files changed, 39 insertions(+), 16 deletions(-) diff --git a/include/libserver/registry/MagicRegistry.hpp b/include/libserver/registry/MagicRegistry.hpp index 59c56c00b..81f6d2dd2 100644 --- a/include/libserver/registry/MagicRegistry.hpp +++ b/include/libserver/registry/MagicRegistry.hpp @@ -122,6 +122,9 @@ class MagicRegistry //! Returns the stat scaling for a basicType, or nullptr if none applies. [[nodiscard]] const Magic::StatScaling* GetStatScaling(uint32_t basicType) const; + //! Returns the position weights for use in random magic selection. + [[nodiscard]] const std::vector& GetPositionWeights(uint32_t position) const; + private: std::unordered_map _slotInfo{}; std::vector _soloPool{}; @@ -130,6 +133,8 @@ class MagicRegistry uint32_t _baseCritChanceBp{500}; //! Keyed by basicType. std::unordered_map _statScalings{}; + //! Position weights for use in random magic selection. + std::vector> _positionWeights; }; } // namespace server::registry diff --git a/resources/config/game/magic.yaml b/resources/config/game/magic.yaml index f4d138ce1..167f24ba9 100644 --- a/resources/config/game/magic.yaml +++ b/resources/config/game/magic.yaml @@ -713,6 +713,19 @@ magic: critChanceBp: 500 + # Weights for each position (P1 to P8) + # Each inner vector corresponds to the weights for: + # Bolt, Shield, Boost, Feather, Ice, Chains, Haze, Dragon, Lightning, Wand, Gauge Boost, Team Boost + positionWeights: + - [5, 100, 10, 0, 70, 0, 0, 5, 0, 10, 0, 10] # P1 + - [30, 25, 45, 5, 15, 10, 30, 20, 5, 10, 0, 5 ] # P2 + - [35, 10, 35, 5, 10, 10, 35, 35, 5, 15, 0, 10] # P3 + - [35, 5, 35, 5, 10, 15, 30, 45, 5, 10, 0, 10] # P4 + - [25, 5, 45, 5, 5, 20, 30, 35, 5, 10, 0, 10] # P5 + - [40, 5, 45, 5, 5, 20, 30, 25, 5, 10, 0, 10] # P6 + - [20, 5, 55, 15, 5, 10, 30, 25, 10, 10, 0, 10] # P7 + - [10, 5, 70, 35, 5, 10, 15, 10, 10, 15, 0, 10] # P8 + statScalings: - basicType: 4 # WaterShield stat: agility diff --git a/src/libserver/registry/MagicRegistry.cpp b/src/libserver/registry/MagicRegistry.cpp index d959827aa..5feae93f3 100644 --- a/src/libserver/registry/MagicRegistry.cpp +++ b/src/libserver/registry/MagicRegistry.cpp @@ -80,6 +80,16 @@ void MagicRegistry::ReadConfig(const std::filesystem::path& configPath) if (not magicSection) throw std::runtime_error("Missing magic section"); + // Position weights + { + const auto positionWeightsSection = magicSection["positionWeights"]; + if (not positionWeightsSection) + throw std::runtime_error("Missing magic positionWeights section"); + + for (const auto& entry : positionWeightsSection) + _positionWeights.push_back(entry.as>()); + } + // Slot info { const auto slotSection = magicSection["slotInfo"]; @@ -202,4 +212,9 @@ const Magic::StatScaling* MagicRegistry::GetStatScaling(uint32_t basicType) cons return it == _statScalings.cend() ? nullptr : &it->second; } +const std::vector& MagicRegistry::GetPositionWeights(uint32_t position) const +{ + return _positionWeights.at(position); +} + } // namespace server::registry diff --git a/src/server/race/RaceNetworkHandler.cpp b/src/server/race/RaceNetworkHandler.cpp index 8aab0db9b..bf1a60eb8 100644 --- a/src/server/race/RaceNetworkHandler.cpp +++ b/src/server/race/RaceNetworkHandler.cpp @@ -54,28 +54,17 @@ uint32_t GetMountStatValue( } // Function to select a random item based on position weights -uint32_t SelectMagicTypeByPosition(uint32_t position, bool isTeam) +uint32_t SelectMagicTypeByPosition( + const registry::MagicRegistry& registry, + uint32_t position, + bool isTeam) { // Validate position if (position > 7) throw std::out_of_range("Position must be between 0 and 7"); - // Weights for each position (P1 to P8) - // Each inner vector corresponds to the weights for: - // Bolt, Shield, Boost, Feather, Ice, Chains, Haze, Dragon, Lightning, Wand, Gauge Boost, Team Boost - const std::vector> positionWeights = { - {5, 100, 10, 0, 70, 0, 0, 5, 0, 10, 0, 10}, // P1 - {30, 25, 45, 5, 15, 10, 30, 20, 5, 10, 0, 5}, // P2 - {35, 10, 35, 5, 10, 10, 35, 35, 5, 15, 0, 10}, // P3 - {35, 5, 35, 5, 10, 15, 30, 45, 5, 10, 0, 10}, // P4 - {25, 5, 45, 5, 5, 20, 30, 35, 5, 10, 0, 10}, // P5 - {40, 5, 45, 5, 5, 20, 30, 25, 5, 10, 0, 10}, // P6 - {20, 5, 55, 15, 5, 10, 30, 25, 10, 10, 0, 10}, // P7 - {10, 5, 70, 35, 5, 10, 15, 10, 10, 15, 0, 10} // P8 - }; - // Get the weights for the specified position - const std::vector& weights = positionWeights[position]; + const std::vector& weights = registry.GetPositionWeights(position); auto weightsEndIter = weights.end(); if (not isTeam) // Exclude team item weights @@ -121,6 +110,7 @@ registry::Magic::SlotInfo RandomMagicItem( } const uint32_t positionMagicType = SelectMagicTypeByPosition( + serverInstance.GetMagicRegistry(), racerPosition, racer.team == protocol::TeamColor::Red or racer.team == protocol::TeamColor::Blue); From 22c02907c71e7546bf02c42482d5ff3755f82035 Mon Sep 17 00:00:00 2001 From: Serkan <14278530+SergeantSerk@users.noreply.github.com> Date: Fri, 26 Jun 2026 22:24:34 +0100 Subject: [PATCH 03/12] Pass registry into RandomMagicItem directly --- src/server/race/RaceNetworkHandler.cpp | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/server/race/RaceNetworkHandler.cpp b/src/server/race/RaceNetworkHandler.cpp index bf1a60eb8..bca7493c9 100644 --- a/src/server/race/RaceNetworkHandler.cpp +++ b/src/server/race/RaceNetworkHandler.cpp @@ -55,7 +55,7 @@ uint32_t GetMountStatValue( // Function to select a random item based on position weights uint32_t SelectMagicTypeByPosition( - const registry::MagicRegistry& registry, + const registry::MagicRegistry& magicRegistry, uint32_t position, bool isTeam) { @@ -64,7 +64,7 @@ uint32_t SelectMagicTypeByPosition( throw std::out_of_range("Position must be between 0 and 7"); // Get the weights for the specified position - const std::vector& weights = registry.GetPositionWeights(position); + const std::vector& weights = magicRegistry.GetPositionWeights(position); auto weightsEndIter = weights.end(); if (not isTeam) // Exclude team item weights @@ -90,7 +90,7 @@ uint32_t SelectMagicTypeByPosition( } registry::Magic::SlotInfo RandomMagicItem( - ServerInstance& serverInstance, + const registry::MagicRegistry& magicRegistry, tracker::RaceTracker& tracker, data::Uid racerUid) { @@ -110,11 +110,10 @@ registry::Magic::SlotInfo RandomMagicItem( } const uint32_t positionMagicType = SelectMagicTypeByPosition( - serverInstance.GetMagicRegistry(), + magicRegistry, racerPosition, racer.team == protocol::TeamColor::Red or racer.team == protocol::TeamColor::Blue); - const auto& magicRegistry = serverInstance.GetMagicRegistry(); auto magicSlotInfo = magicRegistry.GetSlotInfo(positionMagicType); uint32_t critChanceBp = magicRegistry.GetBaseCritChanceBp(); if (magicSlotInfo.criticalType != 0) @@ -128,7 +127,7 @@ registry::Magic::SlotInfo RandomMagicItem( if ((rand() % 10000) < static_cast(critChanceBp)) { - magicSlotInfo = serverInstance.GetMagicRegistry().GetSlotInfo(magicSlotInfo.criticalType); + magicSlotInfo = magicRegistry.GetSlotInfo(magicSlotInfo.criticalType); } return magicSlotInfo; } @@ -2491,7 +2490,7 @@ void RaceNetworkHandler::HandleRequestMagicItem( } const auto& magicItemSlotInfo = RandomMagicItem( - _serverInstance, + _serverInstance.GetMagicRegistry(), tracker, clientContext.characterUid); racer.magicItem.emplace(magicItemSlotInfo.type); From ddf4613c080cbec545fd3f63012d241604acf01d Mon Sep 17 00:00:00 2001 From: Serkan <14278530+SergeantSerk@users.noreply.github.com> Date: Sat, 27 Jun 2026 00:16:50 +0100 Subject: [PATCH 04/12] Store positional weights per item --- include/libserver/registry/MagicRegistry.hpp | 11 ++++- resources/config/game/magic.yaml | 25 ++++++----- src/libserver/registry/MagicRegistry.cpp | 45 ++++++++++++++------ src/server/race/RaceNetworkHandler.cpp | 25 +++++------ 4 files changed, 64 insertions(+), 42 deletions(-) diff --git a/include/libserver/registry/MagicRegistry.hpp b/include/libserver/registry/MagicRegistry.hpp index 81f6d2dd2..7b25f4910 100644 --- a/include/libserver/registry/MagicRegistry.hpp +++ b/include/libserver/registry/MagicRegistry.hpp @@ -20,6 +20,7 @@ #ifndef MAGICREGISTRY_HPP #define MAGICREGISTRY_HPP +#include #include #include #include @@ -30,6 +31,8 @@ namespace server::registry struct Magic { + using SlotWeight = uint32_t; + //! Per-magic-slot definition (MagicSlotInfo). struct SlotInfo { @@ -67,6 +70,8 @@ struct Magic uint32_t affectByCriticalAura{}; uint32_t criticalByDarkFire{}; + //! Weights for each position on the scoreboard. + std::array positionalWeights{}; }; //! Magic gauge (SP) regen settings for Magic mode. @@ -123,7 +128,8 @@ class MagicRegistry [[nodiscard]] const Magic::StatScaling* GetStatScaling(uint32_t basicType) const; //! Returns the position weights for use in random magic selection. - [[nodiscard]] const std::vector& GetPositionWeights(uint32_t position) const; + [[nodiscard]] const std::vector>& GetSoloPositionWeights(uint32_t position) const; + [[nodiscard]] const std::vector>& GetTeamPositionWeights(uint32_t position) const; private: std::unordered_map _slotInfo{}; @@ -134,7 +140,8 @@ class MagicRegistry //! Keyed by basicType. std::unordered_map _statScalings{}; //! Position weights for use in random magic selection. - std::vector> _positionWeights; + std::array>, 8> _soloPositionWeights; + std::array>, 8> _teamPositionWeights; }; } // namespace server::registry diff --git a/resources/config/game/magic.yaml b/resources/config/game/magic.yaml index 167f24ba9..23ec00122 100644 --- a/resources/config/game/magic.yaml +++ b/resources/config/game/magic.yaml @@ -31,6 +31,7 @@ magic: affectByCriticalAura: 1 criticalByDarkFire: 1 attackRank: 2 + positionalWeights: [5, 30, 35, 35, 25, 40, 20, 10] - type: 3 # FireBall (Critical) basicType: 2 @@ -90,6 +91,7 @@ magic: massEffect: 0 affectByCriticalAura: 1 criticalByDarkFire: 0 + positionalWeights: [100, 25, 10, 5, 5, 5, 5, 5] - type: 5 # WaterShield (Critical) basicType: 4 @@ -148,6 +150,7 @@ magic: massEffect: 0 affectByCriticalAura: 1 criticalByDarkFire: 0 + positionalWeights: [10, 45, 35, 35, 45, 45, 55, 70] - type: 7 # Booster (Critical) basicType: 6 @@ -206,6 +209,7 @@ magic: massEffect: 0 affectByCriticalAura: 1 criticalByDarkFire: 0 + positionalWeights: [0, 5, 5, 5, 5, 5, 15, 35] - type: 9 # HotRodding (Critical) basicType: 8 @@ -265,6 +269,7 @@ magic: affectByCriticalAura: 1 criticalByDarkFire: 0 attackRank: 1 + positionalWeights: [70, 15, 10, 10, 5, 5, 5, 5] - type: 11 # IceWall (Critical) basicType: 10 @@ -324,6 +329,7 @@ magic: massEffect: 0 affectByCriticalAura: 1 criticalByDarkFire: 0 + positionalWeights: [0, 10, 10, 15, 20, 20, 10, 10] - type: 13 # JumpStun (Critical) basicType: 12 @@ -382,6 +388,7 @@ magic: massEffect: 0 affectByCriticalAura: 1 criticalByDarkFire: 0 + positionalWeights: [0, 30, 35, 30, 30, 30, 30, 15] - type: 15 # DarkFire (Critical) basicType: 14 @@ -441,6 +448,7 @@ magic: affectByCriticalAura: 1 criticalByDarkFire: 0 attackRank: 2 + positionalWeights: [5, 20, 35, 45, 35, 25, 25, 10] - type: 17 # Summon (Critical) basicType: 16 @@ -501,6 +509,7 @@ magic: affectByCriticalAura: 1 criticalByDarkFire: 1 attackRank: 3 + positionalWeights: [0, 5, 5, 5, 5, 5, 10, 10] - type: 19 # Lightning (Critical) basicType: 18 @@ -560,6 +569,7 @@ magic: massEffect: 0 affectByCriticalAura: 1 criticalByDarkFire: 0 + positionalWeights: [10, 10, 15, 10, 10, 10, 10, 15] - type: 21 # BufPower (Critical) basicType: 20 @@ -618,6 +628,7 @@ magic: massEffect: 0 affectByCriticalAura: 0 criticalByDarkFire: 0 + positionalWeights: [0, 0, 0, 0, 0, 0, 0, 0] - type: 23 # BufGauge (Critical) basicType: 22 @@ -676,6 +687,7 @@ magic: massEffect: 0 affectByCriticalAura: 1 criticalByDarkFire: 0 + positionalWeights: [10, 5, 10, 10, 10, 10, 10, 10] - type: 25 # BufSpeed (Critical) basicType: 24 @@ -713,19 +725,6 @@ magic: critChanceBp: 500 - # Weights for each position (P1 to P8) - # Each inner vector corresponds to the weights for: - # Bolt, Shield, Boost, Feather, Ice, Chains, Haze, Dragon, Lightning, Wand, Gauge Boost, Team Boost - positionWeights: - - [5, 100, 10, 0, 70, 0, 0, 5, 0, 10, 0, 10] # P1 - - [30, 25, 45, 5, 15, 10, 30, 20, 5, 10, 0, 5 ] # P2 - - [35, 10, 35, 5, 10, 10, 35, 35, 5, 15, 0, 10] # P3 - - [35, 5, 35, 5, 10, 15, 30, 45, 5, 10, 0, 10] # P4 - - [25, 5, 45, 5, 5, 20, 30, 35, 5, 10, 0, 10] # P5 - - [40, 5, 45, 5, 5, 20, 30, 25, 5, 10, 0, 10] # P6 - - [20, 5, 55, 15, 5, 10, 30, 25, 10, 10, 0, 10] # P7 - - [10, 5, 70, 35, 5, 10, 15, 10, 10, 15, 0, 10] # P8 - statScalings: - basicType: 4 # WaterShield stat: agility diff --git a/src/libserver/registry/MagicRegistry.cpp b/src/libserver/registry/MagicRegistry.cpp index 5feae93f3..5869956c4 100644 --- a/src/libserver/registry/MagicRegistry.cpp +++ b/src/libserver/registry/MagicRegistry.cpp @@ -67,6 +67,10 @@ uint32_t ReadSlotInfo(const YAML::Node& section, Magic::SlotInfo& slot) slot.criticalByDarkFire = section["criticalByDarkFire"].as(); slot.attackRank = section["attackRank"].as(0); + // Crit items do not need the positional weights defined, base items carry that info + if (slot.type == slot.basicType) + slot.positionalWeights = section["positionalWeights"].as>(); + return slot.type; } @@ -80,16 +84,6 @@ void MagicRegistry::ReadConfig(const std::filesystem::path& configPath) if (not magicSection) throw std::runtime_error("Missing magic section"); - // Position weights - { - const auto positionWeightsSection = magicSection["positionWeights"]; - if (not positionWeightsSection) - throw std::runtime_error("Missing magic positionWeights section"); - - for (const auto& entry : positionWeightsSection) - _positionWeights.push_back(entry.as>()); - } - // Slot info { const auto slotSection = magicSection["slotInfo"]; @@ -108,7 +102,7 @@ void MagicRegistry::ReadConfig(const std::filesystem::path& configPath) } } - // Pre-build the pick pools so RandomMagicItem never has to filter at runtime. + // Pre-build the pick pools and positional weights so RandomMagicItem never has to filter at runtime. for (const auto& [type, slot] : _slotInfo) { if (slot.basicType != type) @@ -116,6 +110,26 @@ void MagicRegistry::ReadConfig(const std::filesystem::path& configPath) _teamPool.push_back(type); if (slot.teamMode == 0) _soloPool.push_back(type); + + // Only compile weights from base type + if (slot.type != slot.basicType) + continue; + + for (size_t i = 0; i < slot.positionalWeights.size(); ++i) + { + const auto& pair = std::make_pair( + slot.positionalWeights[i], + slot); + + // Add to team magic item weights since team is a superset of solo + _teamPositionWeights[i].emplace_back(pair); + + // Do not add to solo position weights if team item + if (slot.teamMode != 0) + continue; + + _soloPositionWeights[i].emplace_back(pair); + } } if (const auto regenSection = magicSection["regen"]) @@ -212,9 +226,14 @@ const Magic::StatScaling* MagicRegistry::GetStatScaling(uint32_t basicType) cons return it == _statScalings.cend() ? nullptr : &it->second; } -const std::vector& MagicRegistry::GetPositionWeights(uint32_t position) const +const std::vector>& MagicRegistry::GetSoloPositionWeights(uint32_t position) const +{ + return _soloPositionWeights.at(position); +} + +const std::vector>& MagicRegistry::GetTeamPositionWeights(uint32_t position) const { - return _positionWeights.at(position); + return _teamPositionWeights.at(position); } } // namespace server::registry diff --git a/src/server/race/RaceNetworkHandler.cpp b/src/server/race/RaceNetworkHandler.cpp index bca7493c9..0d14e3d07 100644 --- a/src/server/race/RaceNetworkHandler.cpp +++ b/src/server/race/RaceNetworkHandler.cpp @@ -64,14 +64,17 @@ uint32_t SelectMagicTypeByPosition( throw std::out_of_range("Position must be between 0 and 7"); // Get the weights for the specified position - const std::vector& weights = magicRegistry.GetPositionWeights(position); - auto weightsEndIter = weights.end(); - if (not isTeam) - // Exclude team item weights - weightsEndIter -= 3; + const auto& positionSlotInfoWeights = isTeam ? + magicRegistry.GetTeamPositionWeights(position) : + magicRegistry.GetSoloPositionWeights(position); + + // Keep reference to weights only for the discrete distribution + const auto& positionWeights = positionSlotInfoWeights | std::views::keys; // Create a discrete distribution based on the weights - std::discrete_distribution dist(weights.begin(), weightsEndIter); + std::discrete_distribution dist( + positionWeights.cbegin(), + positionWeights.cend()); // Random number generator static std::random_device rd; @@ -79,14 +82,8 @@ uint32_t SelectMagicTypeByPosition( // Select a random index based on the distribution // and map the discrete index to the corresponding magic item - const uint32_t selectedWeight = dist(gen); - - // Discrete weights to magic item id mapping example: - // TODO: sensitive to weights ordering - // 0: (0 + 1) * 2 = 2 (Bolt) - // 5: (5 + 1) * 2 = 12 (Chains) - // 8: (8 + 1) * 2 = 18 (Lightning) - return (selectedWeight + 1) * 2; + const uint32_t selectedIndex = dist(gen); + return positionSlotInfoWeights[selectedIndex].second.type; } registry::Magic::SlotInfo RandomMagicItem( From e2e2c7dc321474a5fb6e435480b58b1b4800d3b6 Mon Sep 17 00:00:00 2001 From: Serkan <14278530+SergeantSerk@users.noreply.github.com> Date: Sat, 27 Jun 2026 00:22:16 +0100 Subject: [PATCH 05/12] Pass SelectMagicTypeByPosition by const reference --- src/server/race/RaceNetworkHandler.cpp | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/server/race/RaceNetworkHandler.cpp b/src/server/race/RaceNetworkHandler.cpp index 0d14e3d07..1e9d1b933 100644 --- a/src/server/race/RaceNetworkHandler.cpp +++ b/src/server/race/RaceNetworkHandler.cpp @@ -54,7 +54,7 @@ uint32_t GetMountStatValue( } // Function to select a random item based on position weights -uint32_t SelectMagicTypeByPosition( +const registry::Magic::SlotInfo& SelectMagicTypeByPosition( const registry::MagicRegistry& magicRegistry, uint32_t position, bool isTeam) @@ -83,10 +83,10 @@ uint32_t SelectMagicTypeByPosition( // Select a random index based on the distribution // and map the discrete index to the corresponding magic item const uint32_t selectedIndex = dist(gen); - return positionSlotInfoWeights[selectedIndex].second.type; + return positionSlotInfoWeights[selectedIndex].second; } -registry::Magic::SlotInfo RandomMagicItem( +const registry::Magic::SlotInfo& RandomMagicItem( const registry::MagicRegistry& magicRegistry, tracker::RaceTracker& tracker, data::Uid racerUid) @@ -106,12 +106,11 @@ registry::Magic::SlotInfo RandomMagicItem( racerPosition++; } - const uint32_t positionMagicType = SelectMagicTypeByPosition( + const registry::Magic::SlotInfo& magicSlotInfo = SelectMagicTypeByPosition( magicRegistry, racerPosition, racer.team == protocol::TeamColor::Red or racer.team == protocol::TeamColor::Blue); - auto magicSlotInfo = magicRegistry.GetSlotInfo(positionMagicType); uint32_t critChanceBp = magicRegistry.GetBaseCritChanceBp(); if (magicSlotInfo.criticalType != 0) { @@ -123,9 +122,8 @@ registry::Magic::SlotInfo RandomMagicItem( } if ((rand() % 10000) < static_cast(critChanceBp)) - { - magicSlotInfo = magicRegistry.GetSlotInfo(magicSlotInfo.criticalType); - } + return magicRegistry.GetSlotInfo(magicSlotInfo.criticalType); + return magicSlotInfo; } From 9b1d727f4f0872c055c56c53c5b37f65efb43569 Mon Sep 17 00:00:00 2001 From: Serkan <14278530+SergeantSerk@users.noreply.github.com> Date: Sat, 27 Jun 2026 00:25:14 +0100 Subject: [PATCH 06/12] Name progress value for AcCmdUserRaceUpdatePos --- .../libserver/network/command/proto/RaceMessageDefinitions.hpp | 2 +- src/libserver/network/command/proto/RaceMessageDefinitions.cpp | 2 +- src/server/race/RaceNetworkHandler.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/libserver/network/command/proto/RaceMessageDefinitions.hpp b/include/libserver/network/command/proto/RaceMessageDefinitions.hpp index 59c5228c9..58ed8ca6d 100644 --- a/include/libserver/network/command/proto/RaceMessageDefinitions.hpp +++ b/include/libserver/network/command/proto/RaceMessageDefinitions.hpp @@ -1542,7 +1542,7 @@ struct AcCmdUserRaceUpdatePos //! 1 = In the air uint16_t member5{}; //! Race track progress - float member6{}; + float progress{}; //! Ticks since connected to race director? uint32_t member7{}; diff --git a/src/libserver/network/command/proto/RaceMessageDefinitions.cpp b/src/libserver/network/command/proto/RaceMessageDefinitions.cpp index 4ae581e2f..c34729e34 100644 --- a/src/libserver/network/command/proto/RaceMessageDefinitions.cpp +++ b/src/libserver/network/command/proto/RaceMessageDefinitions.cpp @@ -1105,7 +1105,7 @@ void AcCmdUserRaceUpdatePos::Read( stream.Read(command.member4) .Read(command.member5) - .Read(command.member6) + .Read(command.progress) .Read(command.member7); } diff --git a/src/server/race/RaceNetworkHandler.cpp b/src/server/race/RaceNetworkHandler.cpp index 1e9d1b933..fe8101de8 100644 --- a/src/server/race/RaceNetworkHandler.cpp +++ b/src/server/race/RaceNetworkHandler.cpp @@ -2161,7 +2161,7 @@ void RaceNetworkHandler::HandleRaceUserPos( // TODO: player position anticheat racer.position = command.position; - racer.progress = command.member6; + racer.progress = command.progress; } void RaceNetworkHandler::HandleChat( From 364213f899d0e31c73ea2f76f0ace978888a8155 Mon Sep 17 00:00:00 2001 From: Serkan <14278530+SergeantSerk@users.noreply.github.com> Date: Wed, 1 Jul 2026 12:24:00 +0100 Subject: [PATCH 07/12] Create MagicSystem --- CMakeLists.txt | 1 + include/server/race/MagicSystem.hpp | 54 ++++++++++++ src/server/race/MagicSystem.cpp | 116 +++++++++++++++++++++++++ src/server/race/RaceNetworkHandler.cpp | 96 +------------------- 4 files changed, 175 insertions(+), 92 deletions(-) create mode 100644 include/server/race/MagicSystem.hpp create mode 100644 src/server/race/MagicSystem.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 90b261e19..3e254a74a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -107,6 +107,7 @@ add_executable(alicia-server src/server/chat/AllChatDirector.cpp src/server/chat/PrivateChatDirector.cpp src/server/messenger/MessengerDirector.cpp + src/server/race/MagicSystem.cpp src/server/race/RaceInstance.cpp src/server/race/RaceNetworkHandler.cpp src/server/ranch/RanchDirector.cpp diff --git a/include/server/race/MagicSystem.hpp b/include/server/race/MagicSystem.hpp new file mode 100644 index 000000000..939cce60b --- /dev/null +++ b/include/server/race/MagicSystem.hpp @@ -0,0 +1,54 @@ +/** + * Alicia Server - dedicated server software + * Copyright (C) 2026 Story Of Alicia + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + **/ + +#ifndef MAGICSYSTEM_HPP +#define MAGICSYSTEM_HPP + +#include "server/tracker/RaceTracker.hpp" + +#include +#include + +#include + +namespace server::race +{ + +class MagicSystem +{ +public: + static uint32_t GetMountStatValue( + const tracker::RaceTracker::Racer::MountStatsSnapshot& stats, + registry::Magic::MountStat which); + + //! Function to select a random item based on position weights + static const registry::Magic::SlotInfo& SelectMagicTypeByPosition( + const registry::MagicRegistry& magicRegistry, + uint32_t position, + bool isTeam); + + static const registry::Magic::SlotInfo& RandomMagicItem( + const registry::MagicRegistry& magicRegistry, + tracker::RaceTracker& tracker, + data::Uid racerUid); +}; + +} // namespace server::race + +#endif // MAGICSYSTEM_HPP diff --git a/src/server/race/MagicSystem.cpp b/src/server/race/MagicSystem.cpp new file mode 100644 index 000000000..3c0b8503f --- /dev/null +++ b/src/server/race/MagicSystem.cpp @@ -0,0 +1,116 @@ +/** + * Alicia Server - dedicated server software + * Copyright (C) 2026 Story Of Alicia + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + **/ + +#include "server/race/MagicSystem.hpp" +#include "server/tracker/RaceTracker.hpp" + +#include +#include + +namespace server::race +{ + +uint32_t MagicSystem::GetMountStatValue( + const tracker::RaceTracker::Racer::MountStatsSnapshot& stats, + registry::Magic::MountStat which) +{ + switch (which) + { + case registry::Magic::MountStat::Agility: return stats.agility; + case registry::Magic::MountStat::Ambition: return stats.ambition; + case registry::Magic::MountStat::Rush: return stats.rush; + case registry::Magic::MountStat::Endurance: return stats.endurance; + case registry::Magic::MountStat::Courage: return stats.courage; + } + return 0; +} + +// Function to select a random item based on position weights +const registry::Magic::SlotInfo& MagicSystem::SelectMagicTypeByPosition( + const registry::MagicRegistry& magicRegistry, + uint32_t position, + bool isTeam) +{ + // Validate position + if (position > 7) + throw std::out_of_range("Position must be between 0 and 7"); + + // Get the weights for the specified position + const auto& positionSlotInfoWeights = isTeam ? magicRegistry.GetTeamPositionWeights(position) : magicRegistry.GetSoloPositionWeights(position); + + // Keep reference to weights only for the discrete distribution + const auto& positionWeights = positionSlotInfoWeights | std::views::keys; + + // Create a discrete distribution based on the weights + std::discrete_distribution dist( + positionWeights.cbegin(), + positionWeights.cend()); + + // Random number generator + static std::random_device rd; + static std::mt19937 gen(rd()); + + // Select a random index based on the distribution + // and map the discrete index to the corresponding magic item + const uint32_t selectedIndex = dist(gen); + return positionSlotInfoWeights[selectedIndex].second; +} + +const registry::Magic::SlotInfo& MagicSystem::RandomMagicItem( + const registry::MagicRegistry& magicRegistry, + tracker::RaceTracker& tracker, + data::Uid racerUid) +{ + const auto& racer = tracker.GetRacer(racerUid); + + // Get this racer's track progress + const float racerTrackProgress = racer.progress; + + // Determine the racer's position (0 = 1st place) + uint32_t racerPosition = 0; + for (const auto& [uid, instanceRacer] : tracker.GetRacers()) + { + if (uid == racerUid) + continue; + if (instanceRacer.progress > racerTrackProgress) + racerPosition++; + } + + const registry::Magic::SlotInfo& magicSlotInfo = SelectMagicTypeByPosition( + magicRegistry, + racerPosition, + racer.team == protocol::TeamColor::Red or racer.team == protocol::TeamColor::Blue); + + uint32_t critChanceBp = magicRegistry.GetBaseCritChanceBp(); + if (magicSlotInfo.criticalType != 0) + { + if (const auto* scaling = magicRegistry.GetStatScaling(magicSlotInfo.basicType)) + { + const uint32_t statValue = GetMountStatValue(racer.mountStats, scaling->stat); + critChanceBp += scaling->critStepBp * (statValue / 10u); + } + } + + if ((rand() % 10000) < static_cast(critChanceBp)) + return magicRegistry.GetSlotInfo(magicSlotInfo.criticalType); + + return magicSlotInfo; +} + +} // namespace server::race diff --git a/src/server/race/RaceNetworkHandler.cpp b/src/server/race/RaceNetworkHandler.cpp index fe8101de8..a728da077 100644 --- a/src/server/race/RaceNetworkHandler.cpp +++ b/src/server/race/RaceNetworkHandler.cpp @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. **/ +#include "server/race/MagicSystem.hpp" #include "server/race/RaceNetworkHandler.hpp" #include "server/ServerInstance.hpp" @@ -38,97 +39,8 @@ namespace std::random_device _randomDevice; -uint32_t GetMountStatValue( - const tracker::RaceTracker::Racer::MountStatsSnapshot& stats, - registry::Magic::MountStat which) -{ - switch (which) - { - case registry::Magic::MountStat::Agility: return stats.agility; - case registry::Magic::MountStat::Ambition: return stats.ambition; - case registry::Magic::MountStat::Rush: return stats.rush; - case registry::Magic::MountStat::Endurance: return stats.endurance; - case registry::Magic::MountStat::Courage: return stats.courage; - } - return 0; -} - -// Function to select a random item based on position weights -const registry::Magic::SlotInfo& SelectMagicTypeByPosition( - const registry::MagicRegistry& magicRegistry, - uint32_t position, - bool isTeam) -{ - // Validate position - if (position > 7) - throw std::out_of_range("Position must be between 0 and 7"); - - // Get the weights for the specified position - const auto& positionSlotInfoWeights = isTeam ? - magicRegistry.GetTeamPositionWeights(position) : - magicRegistry.GetSoloPositionWeights(position); - - // Keep reference to weights only for the discrete distribution - const auto& positionWeights = positionSlotInfoWeights | std::views::keys; - - // Create a discrete distribution based on the weights - std::discrete_distribution dist( - positionWeights.cbegin(), - positionWeights.cend()); - - // Random number generator - static std::random_device rd; - static std::mt19937 gen(rd()); - - // Select a random index based on the distribution - // and map the discrete index to the corresponding magic item - const uint32_t selectedIndex = dist(gen); - return positionSlotInfoWeights[selectedIndex].second; } -const registry::Magic::SlotInfo& RandomMagicItem( - const registry::MagicRegistry& magicRegistry, - tracker::RaceTracker& tracker, - data::Uid racerUid) -{ - const auto& racer = tracker.GetRacer(racerUid); - - // Get this racer's track progress - const float racerTrackProgress = racer.progress; - - // Determine the racer's position (0 = 1st place) - uint32_t racerPosition = 0; - for (const auto& [uid, instanceRacer] : tracker.GetRacers()) - { - if (uid == racerUid) - continue; - if (instanceRacer.progress > racerTrackProgress) - racerPosition++; - } - - const registry::Magic::SlotInfo& magicSlotInfo = SelectMagicTypeByPosition( - magicRegistry, - racerPosition, - racer.team == protocol::TeamColor::Red or racer.team == protocol::TeamColor::Blue); - - uint32_t critChanceBp = magicRegistry.GetBaseCritChanceBp(); - if (magicSlotInfo.criticalType != 0) - { - if (const auto* scaling = magicRegistry.GetStatScaling(magicSlotInfo.basicType)) - { - const uint32_t statValue = GetMountStatValue(racer.mountStats, scaling->stat); - critChanceBp += scaling->critStepBp * (statValue / 10u); - } - } - - if ((rand() % 10000) < static_cast(critChanceBp)) - return magicRegistry.GetSlotInfo(magicSlotInfo.criticalType); - - return magicSlotInfo; -} - -} // anon namespace - RaceNetworkHandler::RaceNetworkHandler(ServerInstance& serverInstance) : _serverInstance(serverInstance) , _commandServer(*this) @@ -2484,7 +2396,7 @@ void RaceNetworkHandler::HandleRequestMagicItem( return; } - const auto& magicItemSlotInfo = RandomMagicItem( + const auto& magicItemSlotInfo = race::MagicSystem::RandomMagicItem( _serverInstance.GetMagicRegistry(), tracker, clientContext.characterUid); @@ -3200,7 +3112,7 @@ uint32_t RaceNetworkHandler::ComputeEffectDurationMs( if (attackerRacerIter != racers.cend()) { - const uint32_t statValue = GetMountStatValue( + const uint32_t statValue = race::MagicSystem::GetMountStatValue( attackerRacerIter->second.mountStats, scaling->stat); @@ -3216,7 +3128,7 @@ uint32_t RaceNetworkHandler::ComputeEffectDurationMs( // Target-side reduction (e.g. IceWall shock mitigation), clamped to 100%. if (scaling->targetDurationReductionBp > 0) { - const uint32_t statValue = GetMountStatValue( + const uint32_t statValue = race::MagicSystem::GetMountStatValue( targetRacer.mountStats, scaling->stat); From 00a4f7ddc197e69373101851fcf176206195fa6c Mon Sep 17 00:00:00 2001 From: Serkan <14278530+SergeantSerk@users.noreply.github.com> Date: Fri, 3 Jul 2026 23:41:33 +0100 Subject: [PATCH 08/12] Calculate magic racer effective position --- src/server/race/MagicSystem.cpp | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/server/race/MagicSystem.cpp b/src/server/race/MagicSystem.cpp index 3c0b8503f..3fd8cb52c 100644 --- a/src/server/race/MagicSystem.cpp +++ b/src/server/race/MagicSystem.cpp @@ -20,6 +20,7 @@ #include "server/race/MagicSystem.hpp" #include "server/tracker/RaceTracker.hpp" +#include #include #include @@ -84,17 +85,36 @@ const registry::Magic::SlotInfo& MagicSystem::RandomMagicItem( // Determine the racer's position (0 = 1st place) uint32_t racerPosition = 0; - for (const auto& [uid, instanceRacer] : tracker.GetRacers()) + const auto& allRacers = tracker.GetRacers(); + size_t totalRacers = allRacers.size(); + + for (const auto& [uid, instanceRacer] : allRacers) { if (uid == racerUid) continue; + + // TODO: do we ignore disconnected racers too? + if (instanceRacer.progress > racerTrackProgress) racerPosition++; } + // Map the actual position to one of the 8 weight slots [0, 7] via linear interpolation. + // This ensures last place in a small race gets "last-place" item weights. + uint32_t effectivePosition = racerPosition; + if (totalRacers > 1) + { + effectivePosition = static_cast( + static_cast(racerPosition) * 7.0f / + static_cast(totalRacers - 1)); + } + + // Clamp effective position to [0, 7] for safety + effectivePosition = std::clamp(effectivePosition, 0u, 7u); + const registry::Magic::SlotInfo& magicSlotInfo = SelectMagicTypeByPosition( magicRegistry, - racerPosition, + effectivePosition, racer.team == protocol::TeamColor::Red or racer.team == protocol::TeamColor::Blue); uint32_t critChanceBp = magicRegistry.GetBaseCritChanceBp(); From 8d0b3ed7239f2d532fe180f79e8cd3a924bc49cc Mon Sep 17 00:00:00 2001 From: Xenii Date: Sun, 5 Jul 2026 18:52:34 +0200 Subject: [PATCH 09/12] update weights for booster and feather --- resources/config/game/magic.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/config/game/magic.yaml b/resources/config/game/magic.yaml index 23ec00122..dbd512695 100644 --- a/resources/config/game/magic.yaml +++ b/resources/config/game/magic.yaml @@ -150,7 +150,7 @@ magic: massEffect: 0 affectByCriticalAura: 1 criticalByDarkFire: 0 - positionalWeights: [10, 45, 35, 35, 45, 45, 55, 70] + positionalWeights: [0, 5, 10, 10, 45, 45, 55, 70] - type: 7 # Booster (Critical) basicType: 6 @@ -209,7 +209,7 @@ magic: massEffect: 0 affectByCriticalAura: 1 criticalByDarkFire: 0 - positionalWeights: [0, 5, 5, 5, 5, 5, 15, 35] + positionalWeights: [0, 0, 0, 0, 5, 5, 15, 35] - type: 9 # HotRodding (Critical) basicType: 8 From 5d3d9bfa006354baacc6fe5b8b43bc7f5c9ff561 Mon Sep 17 00:00:00 2001 From: Serkan <14278530+SergeantSerk@users.noreply.github.com> Date: Mon, 6 Jul 2026 11:30:56 +0100 Subject: [PATCH 10/12] Extract rng to anonymous namespace --- src/server/race/MagicSystem.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/server/race/MagicSystem.cpp b/src/server/race/MagicSystem.cpp index 3fd8cb52c..ea22c04b4 100644 --- a/src/server/race/MagicSystem.cpp +++ b/src/server/race/MagicSystem.cpp @@ -24,6 +24,14 @@ #include #include +namespace +{ + +std::random_device _randomDevice; +std::mt19937 mt(_randomDevice()); + +} // namespace + namespace server::race { @@ -63,13 +71,9 @@ const registry::Magic::SlotInfo& MagicSystem::SelectMagicTypeByPosition( positionWeights.cbegin(), positionWeights.cend()); - // Random number generator - static std::random_device rd; - static std::mt19937 gen(rd()); - // Select a random index based on the distribution // and map the discrete index to the corresponding magic item - const uint32_t selectedIndex = dist(gen); + const uint32_t selectedIndex = dist(mt); return positionSlotInfoWeights[selectedIndex].second; } From c1da457338c7e7f1a8d2e9087b42865ec15d1650 Mon Sep 17 00:00:00 2001 From: Serkan <14278530+SergeantSerk@users.noreply.github.com> Date: Mon, 6 Jul 2026 11:42:17 +0100 Subject: [PATCH 11/12] Racer world position + progress documentation --- include/server/tracker/RaceTracker.hpp | 7 +++++-- src/server/race/MagicSystem.cpp | 6 ++---- src/server/race/RaceInstance.cpp | 2 +- src/server/race/RaceNetworkHandler.cpp | 4 ++-- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/include/server/tracker/RaceTracker.hpp b/include/server/tracker/RaceTracker.hpp index c937712d1..3a0105e2a 100644 --- a/include/server/tracker/RaceTracker.hpp +++ b/include/server/tracker/RaceTracker.hpp @@ -72,12 +72,15 @@ class RaceTracker Oid oid{InvalidEntityOid}; State state{State::Disconnected}; Team team{Team::Solo}; - protocol::Vector3 position{}; + //! The racer's position in the world, as a vector. + protocol::Vector3 worldPosition{}; uint32_t starPointValue{}; uint32_t jumpComboValue{}; uint32_t courseTime{InvalidCourseTime}; std::optional magicItem{}; - float progress{}; + //! The racer's progress on the race track. + //! Normalised by the client to: 0.0f <= x <= 1.0f + float raceProgress{}; //! A set of tracked items in racer's proximity. std::unordered_set trackedDecks; diff --git a/src/server/race/MagicSystem.cpp b/src/server/race/MagicSystem.cpp index ea22c04b4..1e825cc30 100644 --- a/src/server/race/MagicSystem.cpp +++ b/src/server/race/MagicSystem.cpp @@ -84,9 +84,6 @@ const registry::Magic::SlotInfo& MagicSystem::RandomMagicItem( { const auto& racer = tracker.GetRacer(racerUid); - // Get this racer's track progress - const float racerTrackProgress = racer.progress; - // Determine the racer's position (0 = 1st place) uint32_t racerPosition = 0; const auto& allRacers = tracker.GetRacers(); @@ -99,7 +96,8 @@ const registry::Magic::SlotInfo& MagicSystem::RandomMagicItem( // TODO: do we ignore disconnected racers too? - if (instanceRacer.progress > racerTrackProgress) + // Check if instance racer is ahead of the racer requesting item + if (instanceRacer.raceProgress > racer.raceProgress) racerPosition++; } diff --git a/src/server/race/RaceInstance.cpp b/src/server/race/RaceInstance.cpp index 9ceddea69..aef871a46 100644 --- a/src/server/race/RaceInstance.cpp +++ b/src/server/race/RaceInstance.cpp @@ -529,7 +529,7 @@ void RaceInstance::TickItemSpawners() const protocol::Vector3& position, const uint32_t spawnStyle = 0) { - const auto distance = (racer.position - position).Length(); + const auto distance = (racer.worldPosition - position).Length(); const bool isInProximity = distance < ItemSpawnDistanceThreshold; const bool isAlreadyTracked = racer.trackedDecks.contains(oid); diff --git a/src/server/race/RaceNetworkHandler.cpp b/src/server/race/RaceNetworkHandler.cpp index a728da077..dbbe7c8b7 100644 --- a/src/server/race/RaceNetworkHandler.cpp +++ b/src/server/race/RaceNetworkHandler.cpp @@ -2072,8 +2072,8 @@ void RaceNetworkHandler::HandleRaceUserPos( // TODO: player position anticheat - racer.position = command.position; - racer.progress = command.progress; + racer.worldPosition = command.position; + racer.raceProgress = command.progress; } void RaceNetworkHandler::HandleChat( From 8b977fe8170b680b688a1ad343588b98e3693ff9 Mon Sep 17 00:00:00 2001 From: Xenii Date: Thu, 9 Jul 2026 20:04:03 +0200 Subject: [PATCH 12/12] add different weights --- resources/config/game/magic.yaml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/resources/config/game/magic.yaml b/resources/config/game/magic.yaml index dbd512695..9802f78f0 100644 --- a/resources/config/game/magic.yaml +++ b/resources/config/game/magic.yaml @@ -31,7 +31,7 @@ magic: affectByCriticalAura: 1 criticalByDarkFire: 1 attackRank: 2 - positionalWeights: [5, 30, 35, 35, 25, 40, 20, 10] + positionalWeights: [1, 45, 45, 45, 30, 20, 3, 3] - type: 3 # FireBall (Critical) basicType: 2 @@ -91,7 +91,7 @@ magic: massEffect: 0 affectByCriticalAura: 1 criticalByDarkFire: 0 - positionalWeights: [100, 25, 10, 5, 5, 5, 5, 5] + positionalWeights: [103, 10, 10, 5, 4, 3, 3, 3] - type: 5 # WaterShield (Critical) basicType: 4 @@ -150,7 +150,7 @@ magic: massEffect: 0 affectByCriticalAura: 1 criticalByDarkFire: 0 - positionalWeights: [0, 5, 10, 10, 45, 45, 55, 70] + positionalWeights: [0, 10, 15, 18, 45, 55, 105, 105] - type: 7 # Booster (Critical) basicType: 6 @@ -209,7 +209,7 @@ magic: massEffect: 0 affectByCriticalAura: 1 criticalByDarkFire: 0 - positionalWeights: [0, 0, 0, 0, 5, 5, 15, 35] + positionalWeights: [0, 0, 0, 0, 5, 20, 50, 50] - type: 9 # HotRodding (Critical) basicType: 8 @@ -269,7 +269,7 @@ magic: affectByCriticalAura: 1 criticalByDarkFire: 0 attackRank: 1 - positionalWeights: [70, 15, 10, 10, 5, 5, 5, 5] + positionalWeights: [89, 35, 20, 20, 10, 12, 3, 3] - type: 11 # IceWall (Critical) basicType: 10 @@ -329,7 +329,7 @@ magic: massEffect: 0 affectByCriticalAura: 1 criticalByDarkFire: 0 - positionalWeights: [0, 10, 10, 15, 20, 20, 10, 10] + positionalWeights: [0, 5, 20, 20, 30, 20, 3, 3] - type: 13 # JumpStun (Critical) basicType: 12 @@ -388,7 +388,7 @@ magic: massEffect: 0 affectByCriticalAura: 1 criticalByDarkFire: 0 - positionalWeights: [0, 30, 35, 30, 30, 30, 30, 15] + positionalWeights: [5, 31, 20, 20, 17, 22, 8, 8] - type: 15 # DarkFire (Critical) basicType: 14 @@ -448,7 +448,7 @@ magic: affectByCriticalAura: 1 criticalByDarkFire: 0 attackRank: 2 - positionalWeights: [5, 20, 35, 45, 35, 25, 25, 10] + positionalWeights: [1, 45, 45, 50, 35, 20, 3, 3] - type: 17 # Summon (Critical) basicType: 16 @@ -509,7 +509,7 @@ magic: affectByCriticalAura: 1 criticalByDarkFire: 1 attackRank: 3 - positionalWeights: [0, 5, 5, 5, 5, 5, 10, 10] + positionalWeights: [0, 3, 3, 3, 4, 4, 4, 4] - type: 19 # Lightning (Critical) basicType: 18 @@ -569,7 +569,7 @@ magic: massEffect: 0 affectByCriticalAura: 1 criticalByDarkFire: 0 - positionalWeights: [10, 10, 15, 10, 10, 10, 10, 15] + positionalWeights: [1, 6, 10, 7, 8, 8, 4, 4] - type: 21 # BufPower (Critical) basicType: 20 @@ -687,7 +687,7 @@ magic: massEffect: 0 affectByCriticalAura: 1 criticalByDarkFire: 0 - positionalWeights: [10, 5, 10, 10, 10, 10, 10, 10] + positionalWeights: [0, 10, 12, 12, 12, 16, 14, 14] - type: 25 # BufSpeed (Critical) basicType: 24