public static void ServerCreateBossLoot( Vector2Ushort epicenterPosition, IProtoCharacterMob protoCharacterBoss, ServerBossDamageTracker damageTracker, double bossDifficultyCoef, IProtoStaticWorldObject lootObjectProto, int lootObjectsDefaultCount, double lootObjectsRadius, int maxLootWinners) { var approximatedTotalLootCountToSpawn = (int)Math.Ceiling(lootObjectsDefaultCount * bossDifficultyCoef); if (approximatedTotalLootCountToSpawn < 2) { approximatedTotalLootCountToSpawn = 2; } var allCharactersByDamage = damageTracker.GetDamageByCharacter(); var winners = ServerSelectWinnersByParticipation( ServerSelectWinnersByDamage(allCharactersByDamage, maxLootWinners)); var winnerEntries = winners .Select( c => new WinnerEntry( c.Character, damagePercent: (byte)Math.Max( 1, Math.Round(c.Score * 100, MidpointRounding.AwayFromZero)), lootCount: CalculateLootCountForScore(c.Score))) .ToList(); if (PveSystem.ServerIsPvE) { ServerNotifyCharactersNotEligibleForReward( protoCharacterBoss, allCharactersByDamage.Select(c => c.Character) .Except(winners.Select(c => c.Character)) .ToList()); ServerNotifyCharactersEligibleForReward( protoCharacterBoss, winnerEntries, totalLootCount: (ushort)winnerEntries.Sum(e => (int)e.LootCount)); } ServerSpawnLoot(); // send victory announcement notification var winnerNamesWithClanTags = winnerEntries .Select(w => (Name: w.Character.Name, ClanTag: PlayerCharacter.GetPublicState(w.Character).ClanTag)) .ToArray(); Instance.CallClient( ServerCharacters.EnumerateAllPlayerCharacters(onlyOnline: true), _ => _.ClientRemote_VictoryAnnouncement(protoCharacterBoss, winnerNamesWithClanTags)); Api.Logger.Important( protoCharacterBoss.ShortId + " boss defeated." + Environment.NewLine + "Damage by player:" + Environment.NewLine + allCharactersByDamage.Select(p => $" * {p.Character}: {p.Damage:F0}") .GetJoinedString(Environment.NewLine) + Environment.NewLine + "Player participation score: (only for selected winners)" + Environment.NewLine + winners.Select(p => $" * {p.Character}: {(p.Score * 100):F1}%") .GetJoinedString(Environment.NewLine)); byte CalculateLootCountForScore(double score) { var result = Math.Ceiling(approximatedTotalLootCountToSpawn * score); if (result < 1) { return(1); } return((byte)Math.Min(byte.MaxValue, result)); } void ServerSpawnLoot() { foreach (var winnerEntry in winnerEntries) { if (!ServerTrySpawnLootForWinner(winnerEntry.Character, winnerEntry.LootCount)) { // spawn attempts failure as logged inside the method, // abort further spawning return; } } } bool ServerTrySpawnLootForWinner(ICharacter forCharacter, double countToSpawnRemains) { var attemptsRemains = 2000; while (countToSpawnRemains > 0) { attemptsRemains--; if (attemptsRemains <= 0) { // attempts exceeded Api.Logger.Error( "Cannot spawn all the loot for boss - number of attempts exceeded. Cont to spawn for winner remains: " + countToSpawnRemains); return(false); } // calculate random distance from the explosion epicenter var distance = RandomHelper.Range(2, lootObjectsRadius); // ensure we spawn more objects closer to the epicenter var spawnProbability = 1 - (distance / lootObjectsRadius); spawnProbability = Math.Pow(spawnProbability, 1.25); if (!RandomHelper.RollWithProbability(spawnProbability)) { // random skip continue; } var angle = RandomHelper.NextDouble() * MathConstants.DoublePI; var spawnPosition = new Vector2Ushort( (ushort)(epicenterPosition.X + distance * Math.Cos(angle)), (ushort)(epicenterPosition.Y + distance * Math.Sin(angle))); if (ServerTrySpawnLootObject(spawnPosition, forCharacter)) { // spawned successfully! countToSpawnRemains--; } } return(true); } bool ServerTrySpawnLootObject(Vector2Ushort spawnPosition, ICharacter forCharacter) { if (!lootObjectProto.CheckTileRequirements(spawnPosition, character: null, logErrors: false)) { return(false); } var lootObject = ServerWorld.CreateStaticWorldObject(lootObjectProto, spawnPosition); if (lootObject is null) { return(false); } // mark the loot object for this player (works only in PvE) WorldObjectClaimSystem.ServerTryClaim(lootObject, forCharacter, WorldObjectClaimDuration.BossLoot, claimForPartyMembers: false); return(true); } }
public static void ServerSpawnMinions( ICharacter characterBoss, Vector2D characterBossCenterPosition, IProtoCharacterMob protoMinion, List <ICharacter> minionsList, double spawnCheckDistanceSqr, ServerBossDamageTracker bossDamageTracker, double minionsPerPlayer, int minionsTotalMin, int minionsTotalMax, int?minionsSpawnPerIterationLimit, double baseMinionsNumber, double spawnNoObstaclesCircleRadius, double spawnDistanceMin, double spawnDistanceMax) { // calculate how many minions required var minionsRequired = baseMinionsNumber; using var tempListCharacters = Api.Shared.GetTempList <ICharacter>(); Api.Server.World.GetScopedByPlayers(characterBoss, tempListCharacters); foreach (var player in tempListCharacters.AsList()) { if (player.Position.DistanceSquaredTo(characterBossCenterPosition) <= spawnCheckDistanceSqr) { minionsRequired += minionsPerPlayer; } } minionsRequired = MathHelper.Clamp(minionsRequired, minionsTotalMin, minionsTotalMax); if (minionsSpawnPerIterationLimit.HasValue) { minionsRequired = Math.Min(minionsRequired, minionsSpawnPerIterationLimit.Value); } ServerProcessMinions(characterBoss, protoMinion, minionsList, out var spawnedMinionsCount, despawnDistanceSqr: spawnCheckDistanceSqr); //Logger.Dev($"Minions required: {minionsRequired} minions have: {minionsHave}"); minionsRequired -= spawnedMinionsCount; if (minionsRequired <= 0) { return; } // spawn minions var attemptsRemains = 300; var physicsSpace = characterBoss.PhysicsBody.PhysicsSpace; while (minionsRequired > 0) { attemptsRemains--; if (attemptsRemains <= 0) { // attempts exceeded return; } var spawnDistance = spawnDistanceMin + RandomHelper.NextDouble() * (spawnDistanceMax - spawnDistanceMin); var angle = RandomHelper.NextDouble() * MathConstants.DoublePI; var spawnPosition = new Vector2Ushort( (ushort)(characterBossCenterPosition.X + spawnDistance * Math.Cos(angle)), (ushort)(characterBossCenterPosition.Y + spawnDistance * Math.Sin(angle))); if (ServerTrySpawnMinion(spawnPosition) is { } spawnedMinion) { // spawned successfully! minionsRequired--; minionsList.Add(spawnedMinion); } } ICharacter ServerTrySpawnMinion(Vector2Ushort spawnPosition) { var worldPosition = spawnPosition.ToVector2D(); if (physicsSpace.TestCircle(worldPosition, spawnNoObstaclesCircleRadius, CollisionGroups.Default, sendDebugEvent: true).EnumerateAndDispose().Any()) { // obstacles nearby return(null); } var spawnedCharacter = Api.Server.Characters.SpawnCharacter(protoMinion, worldPosition); if (spawnedCharacter is null) { return(null); } // write this boss' damage tracker into the minion character // so any damage dealt to it will be counted in the winners ranking var privateState = spawnedCharacter.GetPrivateState <ICharacterPrivateStateWithBossDamageTracker>(); privateState.DamageTracker = bossDamageTracker; // start spawn animation if (spawnedCharacter.ProtoGameObject is IProtoCharacterMob protoCharacterMob) { protoCharacterMob.ServerSetSpawnState(spawnedCharacter, MobSpawnState.Spawning); } return(spawnedCharacter); } }