using ProjectM.Simulation; using Unity.Burst; using Unity.Collections; using Unity.Entities; using Unity.NetCode; namespace ProjectM.Server { /// /// Server-authoritative ability-damage upgrade (handles RPCs). Resolves /// the sender's player (SourceConnection -> NetworkId -> GhostOwner) and, if the global ledger affords the /// Aether cost, withdraws it IN-PLACE and grows a single damage on the player /// (replace-by-SourceId so the [InternalBufferCapacity(8)] buffer stays bounded — repeated upgrades grow one /// row's percent rather than appending). StatRecomputeSystem folds it into EffectiveAbilityStats.Damage on /// both worlds. Plain server SimulationSystemGroup (not predicted → applied once). /// [BurstCompile] [WorldSystemFilter(WorldSystemFilterFlags.ServerSimulation)] public partial struct AbilityUpgradeSystem : ISystem { const uint UpgradeSourceId = 0x00A0E711u; // distinct sentinel so the upgrade modifier is found + grown const float TierStep = 0.25f; // +25% damage per tier const int CostAmount = 20; // Aether per tier [BurstCompile] public void OnCreate(ref SystemState state) { state.RequireForUpdate(); var builder = new EntityQueryBuilder(Allocator.Temp) .WithAll(); state.RequireForUpdate(state.GetEntityQuery(builder)); } [BurstCompile] public void OnUpdate(ref SystemState state) { var ledger = SystemAPI.GetBuffer(SystemAPI.GetSingletonEntity()); var playerByConn = new NativeHashMap(8, Allocator.Temp); foreach (var (owner, entity) in SystemAPI.Query>().WithAll().WithEntityAccess()) playerByConn[owner.ValueRO.NetworkId] = entity; var ecb = new EntityCommandBuffer(Allocator.Temp); foreach (var (receive, requestEntity) in SystemAPI.Query>().WithAll().WithEntityAccess()) { var conn = receive.ValueRO.SourceConnection; if (SystemAPI.HasComponent(conn) && playerByConn.TryGetValue(SystemAPI.GetComponent(conn).Value, out var player)) { int have = 0; for (int i = 0; i < ledger.Length; i++) if (ledger[i].ItemId == ResourceId.Aether) { have = ledger[i].Count; break; } if (have >= CostAmount) { StorageMath.Withdraw(ledger, ResourceId.Aether, CostAmount); var mods = SystemAPI.GetBuffer(player); bool grown = false; for (int i = 0; i < mods.Length; i++) { if (mods[i].SourceId == UpgradeSourceId && mods[i].Target == (byte)StatTarget.Damage) { var m = mods[i]; m.Value += TierStep; mods[i] = m; grown = true; break; } } if (!grown) mods.Add(new StatModifier { Target = (byte)StatTarget.Damage, Op = (byte)ModOp.PercentAdd, Value = TierStep, SourceId = UpgradeSourceId, }); } } ecb.DestroyEntity(requestEntity); } ecb.Playback(state.EntityManager); playerByConn.Dispose(); } } }