public static TaskKill Require( IProtoCharacterMob protoCreature, ushort count = 1, string description = null) { return(new TaskKill(protoCreature, count, description)); }
public static void ServerProcessMinions( ICharacter characterBoss, IProtoCharacterMob protoMinion, List <ICharacter> minionsList, out int spawnedMinionsCount, double despawnDistanceSqr) { var bossPosition = characterBoss.Position; spawnedMinionsCount = 0; for (var index = minionsList.Count - 1; index >= 0; index--) { var minion = minionsList[index]; if (minion.GetPublicState <CharacterMobPublicState>().IsDead) { //Logger.Dev("The minion is dead: " + minion); minionsList.RemoveAt(index); continue; } if (minion.Position.DistanceSquaredTo(bossPosition) >= despawnDistanceSqr) { //Logger.Dev("The minion is too far, despawn it: " + minion); protoMinion.ServerSetSpawnState(minion, MobSpawnState.Despawning); minionsList.RemoveAt(index); continue; } spawnedMinionsCount++; } }
private static void ServerNotifyCharactersNotEligibleForReward( IProtoCharacterMob protoCharacterBoss, List <ICharacter> list) { Instance.CallClient(list, _ => _.ClientRemote_BossRewardNotAvailable( protoCharacterBoss)); }
public string Execute(IProtoCharacterMob protoMob, ushort x, ushort y) { var worldOffset = ServerWorldService.WorldBounds.Offset; x += worldOffset.X; y += worldOffset.Y; return(ServerSpawn(protoMob, (x, y))); }
private TaskKill( IProtoCharacterMob protoCharacterMob, ushort count, string description) : base(count, description) { this.ProtoCharacterMob = protoCharacterMob; }
private static int GetLevel(IProtoCharacterMob character) { if (character is null) { return(1); } if (character.IsBoss && Api.IsServer && (SharedLocalServerHelper.IsLocalServer || Api.IsEditor)) { return(1); } return(RandomLevels[RandomHelper.Next(0, RandomLevels.Count)]); }
public string Execute( IProtoCharacterMob protoMob, byte count = 1, [CurrentCharacterIfNull] ICharacter nearPlayer = null) { var result = new StringBuilder(); for (var i = 0; i < count; i++) { result.AppendLine(ServerSpawn(protoMob, nearPlayer.Position)); } return(result.ToString()); }
private static void ServerNotifyCharactersEligibleForReward( IProtoCharacterMob protoCharacterBoss, List <WinnerEntry> list, ushort totalLootCount) { foreach (var entry in list) { Instance.CallClient(entry.Character, _ => _.ClientRemote_BossRewardAvailable( protoCharacterBoss, entry.DamagePercent, entry.LootCount, totalLootCount)); } }
private static string ServerSpawn(IProtoCharacterMob protoMob, Vector2D position) { position = FindClosestPosition(position); var character = Server.Characters.SpawnCharacter( protoMob, position); if (character == null) { throw new Exception("Cannot spawn character."); } return($"{character} spawned at {position}."); }
public override float StructurePointsMax => 9001; // non-damageable public static void ServerSetupCorpse( IStaticWorldObject objectCorpse, IProtoCharacterMob protoCharacterMob, Vector2F tileOffset, bool isFlippedHorizontally) { var publicState = GetPublicState(objectCorpse); publicState.ProtoCharacterMob = protoCharacterMob; publicState.TileOffset = tileOffset; publicState.IsFlippedHorizontally = isFlippedHorizontally; publicState.DeathTime = Server.Game.FrameTime; // re-initialize the object physics // (it's required because the physics should use a proper tile offset we've just set) objectCorpse.ProtoStaticWorldObject.SharedCreatePhysics(objectCorpse); }
/// <summary> /// Server spawn callback for mob. /// </summary> /// <param name="trigger">Trigger leading to this spawn.</param> /// <param name="zone">Server zone instance.</param> /// <param name="protoMob">Prototype of character mob object to spawn.</param> /// <param name="tilePosition">Position to try spawn at.</param> protected virtual IGameObjectWithProto ServerSpawnMob( IProtoTrigger trigger, IServerZone zone, IProtoCharacterMob protoMob, Vector2Ushort tilePosition) { var worldPosition = tilePosition.ToVector2D(); if (!ServerCharacterSpawnHelper.IsPositionValidForCharacterSpawn(worldPosition, isPlayer: false)) { // position is not valid for spawning return(null); } return(Server.Characters.SpawnCharacter( protoMob, worldPosition)); }
private static string ServerSpawn(IProtoCharacterMob protoMob, Vector2D desiredPosition) { var position = FindClosestPosition(desiredPosition); if (!position.HasValue) { return("No empty position available nearby."); } var character = Server.Characters.SpawnCharacter( protoMob, position.Value); if (character is null) { throw new Exception("Cannot spawn character."); } return($"{character} spawned at {position.Value}."); }
public override float StructurePointsMax => 0; // non-damageable public static void ServerSetupCorpse( IStaticWorldObject objectCorpse, uint forDeadCharacterId, IProtoCharacterMob protoCharacterMob, Vector2F tileOffset, bool isFlippedHorizontally) { var publicState = GetPublicState(objectCorpse); publicState.ProtoCharacterMob = protoCharacterMob; publicState.TileOffset = tileOffset; publicState.IsFlippedHorizontally = isFlippedHorizontally; publicState.DeathTime = Server.Game.FrameTime; publicState.DeadCharacterId = forDeadCharacterId; // re-initialize the object physics // (it's required because the physics should use // a proper tile offset and CorpseInteractionAreaScale from the mob prototype) objectCorpse.ProtoStaticWorldObject.SharedCreatePhysics(objectCorpse); }
public string Execute( IProtoCharacterMob protoMob, ushort x, ushort y, byte count = 1) { var worldOffset = ServerWorldService.WorldBounds.Offset; x += worldOffset.X; y += worldOffset.Y; var result = new StringBuilder(); for (var i = 0; i < count; i++) { result.AppendLine(ServerSpawn(protoMob, (x, y))); } return(result.ToString()); }
public static void SetLevel(IProtoCharacterMob protoCharacter, ICharacter character, CharacterMobPublicState publicState, CharacterMobPrivateState privateState) { if (publicState is null) { return; } int level; //check the zone int levelByZone = GetLevelByZone(character.TilePosition); if (levelByZone != 0) { level = levelByZone; } else { //check if there is a parent mob var list = character.PhysicsBody.PhysicsSpace.TestCircle(character.TilePosition.ToVector2D(), 10.0, CollisionGroups.Default).AsList() .Where(t => t.PhysicsBody.AssociatedWorldObject is not null) .Where(t => t.PhysicsBody.AssociatedWorldObject.ProtoWorldObject is IProtoCharacterBoss || t.PhysicsBody.AssociatedWorldObject.ProtoWorldObject is IProtoCharacterSmallBoss).ToList(); level = GetLevel(protoCharacter); if (list.Count > 0) { var mobPublicState = list[0].PhysicsBody.AssociatedWorldObject.GetPublicState <CharacterMobPublicState>(); if (mobPublicState is not null && level > mobPublicState.Level) { level = mobPublicState.Level; } } } publicState.Level = level; }
private void BossDefeatedHandler( IProtoCharacterMob protoCharacterBoss, Vector2Ushort bossPosition, List <ServerBossLootSystem.WinnerEntry> winnerEntries) { if (protoCharacterBoss != this.ProtoCharacterMob) { // different boss type return; } foreach (var winner in winnerEntries) { var activeContext = this.GetActiveContext(winner.Character, out var state); if (activeContext is null) { continue; } state.SetCountCurrent(state.CountCurrent + 1, this.RequiredCount); activeContext.Refresh(); } }
private static void ServerBossDefeatedHandler( IProtoCharacterMob protoCharacterBoss, Vector2Ushort bossPosition, List <ServerBossLootSystem.WinnerEntry> winnerEntries) { var lootByFaction = winnerEntries.GroupBy(e => FactionSystem.SharedGetClanTag(e.Character)) .Where(g => !string.IsNullOrEmpty(g.Key)) .ToDictionary(g => g.Key, g => g.Sum(l => l.LootCount)); foreach (var pair in lootByFaction) { var clanTag = pair.Key; var faction = FactionSystem.ServerGetFactionByClanTag(clanTag); if (faction is null) { Api.Logger.Error("Should be impossible - no faction for clan tag: " + clanTag); continue; } var factionPrivateState = Faction.GetPrivateState(faction); factionPrivateState.ServerMetricBossScore += (ulong)pair.Value; /*Logger.Dev("Boss metric updated: " + factionPrivateState.ServerMetricBossScore);*/ } }
public ProtoCharacterMobViewModel([NotNull] IProtoCharacterMob creature) : base(creature) { }
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); } }
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); } }
public void ServerOnMobKilled(IProtoCharacterMob protoMob) { Api.ValidateIsServer(); AddIfNotContains(protoMob, this.ListMobs); }
public string Execute(IProtoCharacterMob protoMob, [CurrentCharacterIfNull] ICharacter nearPlayer = null) { return(ServerSpawn(protoMob, nearPlayer.Position)); }