protected override void ServerOnStaticObjectZeroStructurePoints( WeaponFinalCache weaponCache, ICharacter byCharacter, IWorldObject targetObject) { var tilePosition = targetObject.TilePosition; base.ServerOnStaticObjectZeroStructurePoints(weaponCache, byCharacter, targetObject); ServerWorld.CreateStaticWorldObject <ObjectMineralPragmiumSourceExplosion>(tilePosition); }
public virtual void ServerOnDeath(ICharacter character) { this.ServerSendDeathSoundEvent(character); ServerWorld.DestroyObject(character); var position = character.Position; var tilePosition = position.ToVector2Ushort(); var rotationAngleRad = character.GetPublicState <ICharacterPublicState>().AppliedInput.RotationAngleRad; var isLeftOrientation = ClientCharacterAnimationHelper.IsLeftHalfOfCircle( angleDeg: rotationAngleRad * MathConstants.RadToDeg); var isFlippedHorizontally = !isLeftOrientation; var objectCorpse = ServerWorld.CreateStaticWorldObject <ObjectCorpse>(tilePosition); ObjectCorpse.ServerSetupCorpse(objectCorpse, (IProtoCharacterMob)character.ProtoCharacter, (Vector2F)(position - tilePosition.ToVector2D()), isFlippedHorizontally: isFlippedHorizontally); }
public static void ServerCreateBossLoot( Vector2Ushort bossPosition, IProtoCharacterMob protoCharacterBoss, ServerBossDamageTracker damageTracker, double bossDifficultyCoef, IProtoStaticWorldObject lootObjectProto, int lootObjectsDefaultCount, double lootObjectsRadius, double learningPointsBonusPerLootObject, 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: FactionSystem.SharedGetClanTag(w.Character))) .ToArray(); Instance.CallClient( ServerCharacters.EnumerateAllPlayerCharacters(onlyOnline: true), _ => _.ClientRemote_VictoryAnnouncement(protoCharacterBoss, winnerNamesWithClanTags)); foreach (var entry in winnerEntries) { // provide bonus LP entry.Character.SharedGetTechnologies() .ServerAddLearningPoints( learningPointsBonusPerLootObject * entry.LootCount, allowModifyingByStatsAndRates: false); } 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)); Api.SafeInvoke( () => BossDefeated?.Invoke(protoCharacterBoss, bossPosition, winnerEntries)); 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)(bossPosition.X + distance * Math.Cos(angle)), (ushort)(bossPosition.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); } }