private static void ServerLoadSystem() { ServerPrivateChatRoomsCache.Clear(); var database = Server.Database; if (!database.TryGet(nameof(ChatSystem), DatabaseKeyGlobalChatRoomHolder, out sharedGlobalChatRoomHolder) || !IsValidChatRoomHolder(sharedGlobalChatRoomHolder)) { sharedGlobalChatRoomHolder = ServerCreateChatRoom(new ChatRoomGlobal()); database.Set(nameof(ChatSystem), DatabaseKeyGlobalChatRoomHolder, sharedGlobalChatRoomHolder); } if (!database.TryGet(nameof(ChatSystem), DatabaseKeyTradeChatRoomHolder, out sharedTradeChatRoomHolder) || !IsValidChatRoomHolder(sharedTradeChatRoomHolder)) { sharedTradeChatRoomHolder = ServerCreateChatRoom(new ChatRoomTrade()); database.Set(nameof(ChatSystem), DatabaseKeyTradeChatRoomHolder, sharedTradeChatRoomHolder); } if (!database.TryGet(nameof(ChatSystem), DatabaseKeyLocalChatRoomHolder, out sharedLocalChatRoomHolder)) { sharedLocalChatRoomHolder = ServerCreateChatRoom(new ChatRoomLocal()); database.Set(nameof(ChatSystem), DatabaseKeyLocalChatRoomHolder, sharedLocalChatRoomHolder); } // right now it's not possible to enumerate all the existing chat rooms as this is a bootstrapper // schedule a delayed initialization ServerTimersSystem.AddAction( 0.1, () => { foreach (var chatRoomHolder in ServerWorld .GetGameObjectsOfProto <ILogicObject, ChatRoomHolder>() .ToList()) { if (SharedGetChatRoom(chatRoomHolder) is not ChatRoomPrivate privateChatRoom) { continue; } var characterA = Server.Characters.GetPlayerCharacter(privateChatRoom.CharacterA); var characterB = Server.Characters.GetPlayerCharacter(privateChatRoom.CharacterB); if (characterA is null || characterB is null) { // incorrect private chat room ServerWorld.DestroyObject(chatRoomHolder); continue; } ServerAddPrivateChatRoomToCharacterCache(characterA, chatRoomHolder); ServerAddPrivateChatRoomToCharacterCache(characterB, chatRoomHolder); }
private void ServerExplodeAt(WeaponFinalCache weaponCache, Vector2D endPosition, bool isHit) { var character = weaponCache.Character; var protoWeapon = (IProtoItemWeaponRanged)weaponCache.ProtoWeapon; var shotSourcePosition = WeaponSystemClientDisplay.SharedCalculateWeaponShotWorldPositon( character, protoWeapon, character.ProtoCharacter, character.Position, hasTrace: true); if (isHit) { // offset end position a bit towards the source position // this way the explosion will definitely happen outside the hit object endPosition -= 0.1 * (endPosition - shotSourcePosition).Normalized; } var timeToHit = WeaponSystemClientDisplay.SharedCalculateTimeToHit( protoWeapon.FireTracePreset ?? this.FireTracePreset, shotSourcePosition, endPosition); // We're using a check here similar to the one in WeaponSystem. // Ensure that player can hit objects only on the same height level // and can fire through over the pits (the cliffs of the lower heights). var anyCliffIsAnObstacle = character.Tile.Height != Server.World.GetTile(endPosition.ToVector2Ushort()).Height; if (!WeaponSystem.SharedHasTileObstacle( character.Position, character.Tile.Height, endPosition, character.PhysicsBody.PhysicsSpace, anyCliffIsAnObstacle: anyCliffIsAnObstacle)) { ServerTimersSystem.AddAction( timeToHit, () => { ExplosionHelper.ServerExplode( character: character, protoExplosive: this, protoWeapon: protoWeapon, explosionPreset: this.ExplosionPreset, epicenterPosition: endPosition, damageDescriptionCharacters: this.DamageDescription, physicsSpace: Server.World.GetPhysicsSpace(), executeExplosionCallback: this.ServerExecuteExplosion); }); } // notify other characters about the explosion using var charactersObserving = Api.Shared.GetTempList <ICharacter>(); var explosionEventRadius = Math.Max(protoWeapon.SoundPresetWeaponDistance.max, this.FireRangeMax) + this.DamageRadius; Server.World.GetCharactersInRadius(endPosition.ToVector2Ushort(), charactersObserving, radius: (byte)Math.Min(byte.MaxValue, explosionEventRadius), onlyPlayers: true); charactersObserving.Remove(character); this.CallClient(charactersObserving.AsList(), _ => _.ClientRemote_OnExplosion(protoWeapon, shotSourcePosition, endPosition)); }
public static void ServerExplode( [CanBeNull] ICharacter character, [CanBeNull] IProtoObjectExplosive protoObjectExplosive, ExplosionPreset explosionPreset, Vector2D epicenterPosition, DamageDescription damageDescriptionCharacters, IPhysicsSpace physicsSpace, ExecuteExplosionDelegate executeExplosionCallback) { ValidateIsServer(); // schedule explosion charred ground spawning ServerTimersSystem.AddAction( delaySeconds: explosionPreset.SpriteAnimationDuration * 0.5, () => { var tilePosition = (Vector2Ushort)epicenterPosition; // remove existing charred ground objects at the same tile foreach (var staticWorldObject in Shared.WrapInTempList( Server.World.GetTile(tilePosition).StaticObjects)) { if (staticWorldObject.ProtoStaticWorldObject is ObjectCharredGround) { Server.World.DestroyObject(staticWorldObject); } } // spawn charred ground var objectCharredGround = Server.World .CreateStaticWorldObject <ObjectCharredGround>(tilePosition); var objectCharredGroundOffset = epicenterPosition - tilePosition.ToVector2D(); if (objectCharredGroundOffset != Vector2D.Zero) { ObjectCharredGround.ServerSetWorldOffset(objectCharredGround, (Vector2F)objectCharredGroundOffset); } }); // schedule explosion damage ServerTimersSystem.AddAction( delaySeconds: explosionPreset.ServerDamageApplyDelay, () => { // prepare weapon caches var characterFinalStatsCache = character?.SharedGetFinalStatsCache() ?? FinalStatsCache.Empty; var weaponFinalCache = new WeaponFinalCache(character, characterFinalStatsCache, weapon: null, protoWeapon: null, protoObjectExplosive: protoObjectExplosive, damageDescription: damageDescriptionCharacters); // execute explosion executeExplosionCallback( positionEpicenter: epicenterPosition, physicsSpace: physicsSpace, weaponFinalCache: weaponFinalCache); }); }
public static void ServerExplode( [CanBeNull] ICharacter character, [CanBeNull] IProtoExplosive protoExplosive, [CanBeNull] IProtoItemWeapon protoWeapon, ExplosionPreset explosionPreset, Vector2D epicenterPosition, DamageDescription damageDescriptionCharacters, IPhysicsSpace physicsSpace, ExecuteExplosionDelegate executeExplosionCallback) { ValidateIsServer(); // schedule explosion charred ground spawning var protoObjectCharredGround = explosionPreset.ProtoObjectCharredGround; if (protoObjectCharredGround is not null) { ServerTimersSystem.AddAction( delaySeconds: explosionPreset.SpriteAnimationDuration * 0.5, () => { var tilePosition = (Vector2Ushort)(epicenterPosition - protoObjectCharredGround.Layout.Center); var canSpawnCharredGround = true; var tile = Server.World.GetTile(tilePosition); if (tile.ProtoTile.Kind != TileKind.Solid || tile.EightNeighborTiles.Any(t => t.ProtoTile.Kind != TileKind.Solid)) { // allow charred ground only on solid ground canSpawnCharredGround = false; } if (canSpawnCharredGround) { // remove existing charred ground objects at the same tile foreach (var staticWorldObject in Shared.WrapInTempList( tile.StaticObjects) .EnumerateAndDispose()) { switch (staticWorldObject.ProtoStaticWorldObject) { case ProtoObjectCharredGround _: Server.World.DestroyObject(staticWorldObject); break; case IProtoObjectDeposit _: // don't show charred ground over resource deposits (it looks wrong) canSpawnCharredGround = false; break; } } } if (canSpawnCharredGround && PveSystem.ServerIsPvE) { var bounds = protoObjectCharredGround.Layout.Bounds; if (LandClaimSystem.SharedIsLandClaimedByAnyone( new RectangleInt(tilePosition, bounds.Size + (1, 1)))) { // ensure that it's not possible to create charred ground in a land claim area in PvE canSpawnCharredGround = false; } } if (canSpawnCharredGround) { // spawn charred ground var objectCharredGround = Server.World.CreateStaticWorldObject(protoObjectCharredGround, tilePosition); var objectCharredGroundOffset = epicenterPosition - tilePosition.ToVector2D(); if (objectCharredGroundOffset != Vector2D.Zero) { ProtoObjectCharredGround.ServerSetWorldOffset(objectCharredGround, (Vector2F)objectCharredGroundOffset); } } }); } // schedule explosion damage ServerTimersSystem.AddAction( delaySeconds: explosionPreset.ServerDamageApplyDelay, () => { // prepare weapon caches var characterFinalStatsCache = character?.SharedGetFinalStatsCache() ?? FinalStatsCache.Empty; var weaponFinalCache = new WeaponFinalCache(character, characterFinalStatsCache, weapon: null, protoWeapon: protoWeapon, protoAmmo: null, damageDescription: damageDescriptionCharacters, protoExplosive: protoExplosive); // execute explosion executeExplosionCallback( positionEpicenter: epicenterPosition, physicsSpace: physicsSpace, weaponFinalCache: weaponFinalCache); }); }
private static void ServerAddMember( ICharacter character, ILogicObject faction, FactionMemberRole role) { Api.Assert(!character.IsNpc, "NPC cannot join a faction"); if (!SharedIsValidRole(role)) { throw new Exception("Invalid role: " + role); } var currentFaction = ServerGetFaction(character); if (currentFaction == faction) { // already in faction return; } if (currentFaction is not null) { throw new Exception($"Player already has a faction: {character} in {faction}"); } // faction members cannot have a newbie protection NewbieProtectionSystem.ServerDisableNewbieProtection(character); var members = ServerGetFactionMembersEditable(faction); var factionPublicState = Faction.GetPublicState(faction); var maxMembers = FactionConstants.SharedGetFactionMembersMax(factionPublicState.Kind); if (members.Count >= maxMembers) { throw new Exception("Faction size exceeded - max " + maxMembers); } if (role == FactionMemberRole.Leader) { foreach (var otherMember in members) { if (otherMember.Role == FactionMemberRole.Leader) { throw new Exception("Faction can have only a single leader"); } } } members.Add(new FactionMemberEntry(character.Name, role)); ServerCharacterFactionDictionary[character] = faction; PlayerCharacter.GetPublicState(character).ClanTag = factionPublicState.ClanTag; factionPublicState.PlayersNumberCurrent++; Logger.Important($"Player joined faction: {character} in {faction} - role: {role}", character); ServerInvitations.RemoveAllInvitationsFor(character); Api.SafeInvoke( () => ServerCharacterJoinedOrLeftFaction?.Invoke(character, faction, isJoined: true)); // add this with some delay to prevent from the bug when the player name listed twice due to the late delta-replication ServerTimersSystem.AddAction(delaySeconds: 0.1, () => ServerSendCurrentFaction(character)); }
/// <summary> /// This method will expand the container capacity to max and schedule its trimming. /// </summary> private static void ServerExpandContainerAndScheduleProcessing(IItemsContainer itemsContainer) { ServerItems.SetSlotsCount(itemsContainer, slotsCount: byte.MaxValue); ServerTimersSystem.AddAction(0, () => ServerTrimSlotsNumber(itemsContainer)); }
public static void ServerAddMark(IStaticWorldObject staticWorldObject, double serverSpawnTime) { Api.ValidateIsServer(); ushort searchAreaCircleRadius = 0; var searchAreaCirclePosition = Vector2Ushort.Zero; var timeToClaimRemains = SharedCalculateTimeToClaimLimitRemovalSeconds(serverSpawnTime); var biome = staticWorldObject.OccupiedTile.ProtoTile; var position = SharedGetObjectCenterPosition(staticWorldObject); if (IsResourceDepositCoordinatesHiddenUntilCapturePossible && timeToClaimRemains > 0) { var stopwatch = Stopwatch.StartNew(); searchAreaCircleRadius = DepositSearchAreaCircleRadius; try { if (!ServerSearchAreaHelper.GenerateSearchArea(position, biome, searchAreaCircleRadius, out searchAreaCirclePosition, maxAttempts: 100)) { Logger.Warning( "Unable to calculate an approximate search area for the resource deposit location, will use the center area: " + staticWorldObject); searchAreaCirclePosition = position; } } finally { Logger.Important( $"Calculating a resource deposit search area took {stopwatch.ElapsedMilliseconds}ms (for {staticWorldObject} in {biome.ShortId} biome)"); } // hide position position = Vector2Ushort.Zero; } sharedResourceMarksList.Add( new WorldMapResourceMark(staticWorldObject.Id, position, staticWorldObject.ProtoStaticWorldObject, serverSpawnTime, biome: biome, searchAreaCirclePosition: searchAreaCirclePosition, searchAreaCircleRadius: searchAreaCircleRadius)); if (!IsResourceDepositCoordinatesHiddenUntilCapturePossible) { return; } if (timeToClaimRemains <= 0) { return; } ServerTimersSystem.AddAction( timeToClaimRemains + 1, () => { if (staticWorldObject.IsDestroyed) { return; } Logger.Important("It's possible to capture the resource deposit now, adding a mark on the map: " + staticWorldObject); ServerRemoveMark(staticWorldObject); // add on the next frame (give to for the network replication system) ServerTimersSystem.AddAction( 0.1, () => { if (staticWorldObject.IsDestroyed) { return; } ServerAddMark(staticWorldObject, serverSpawnTime); }); }); }
private void UpdateDisplayedTimeNoTimer() { if (this.publicState.HasHarvest) { this.HarvestInTimePercent = 100; this.HarvestInTimeText = null; // not used } else { // update harvest time var fraction = ServerTimersSystem.SharedGetTimeRemainingFraction( this.nextHarvestOrSpoilTime, this.totalHarvestDuration, out var timeRemainingSeconds); this.HarvestInTimeText = ClientTimeFormatHelper.FormatTimeDuration(Math.Max(0, timeRemainingSeconds)); this.HarvestInTimePercent = (float)(100 * fraction); } if (this.publicState.IsSpoiled) { this.SpoiledInTimePercent = 100; this.SpoiledInTimeText = null; // not used this.IsSpoiling = true; } else if (this.publicState.HasHarvest) { // update rotten time var fraction = ServerTimersSystem.SharedGetTimeRemainingFraction( this.nextHarvestOrSpoilTime, this.protoPlant.TimeToHarvestSpoilTotalSeconds / FarmingConstants.SharedFarmPlantsSpoilSpeedMultiplier, out var timeRemainingSeconds); this.SpoiledInTimeText = ClientTimeFormatHelper.FormatTimeDuration(Math.Max(0, timeRemainingSeconds)); this.SpoiledInTimePercent = (float)(100 * fraction); this.IsSpoiling = true; } else { this.IsSpoiling = false; } if (this.VisibilityWatered == Visibility.Visible) { // update watering time var totalDuration = this.wateringDuration; if (totalDuration < double.MaxValue) { var fraction = ServerTimersSystem.SharedGetTimeRemainingFraction( this.wateringEndsTime, totalDuration, out var timeRemainingSeconds); this.WateringEndsTimeText = ClientTimeFormatHelper.FormatTimeDuration(timeRemainingSeconds); this.WateringEndsTimePercent = (float)(100 * fraction); } else { this.WateringEndsTimeText = TitlePermanent; this.WateringEndsTimePercent = 100; } } else { this.WateringEndsTimePercent = 0; } }
public override void ServerOnDeath(ICharacter character) { this.ServerSendDeathSoundEvent(character); ServerTimersSystem.AddAction( delaySeconds: 3, () => { var bossPosition = character.Position; using var tempListPlayersNearby = Api.Shared.GetTempList <ICharacter>(); Server.World.GetScopedByPlayers(character, tempListPlayersNearby); foreach (var player in tempListPlayersNearby.AsList()) { if (player.Position.DistanceSquaredTo(bossPosition) <= VictoryLearningPointsBonusMaxDistance * VictoryLearningPointsBonusMaxDistance) { player.SharedGetTechnologies() .ServerAddLearningPoints(VictoryLearningPointsBonusToEachAlivePlayer, allowModifyingByStat: false); } } // explode var protoExplosion = Api.GetProtoEntity <ObjectPragmiumQueenDeathExplosion>(); Server.World.CreateStaticWorldObject(protoExplosion, (bossPosition - protoExplosion.Layout.Center) .ToVector2Ushort()); var privateState = GetPrivateState(character); var damageTracker = privateState.DamageTracker; // spawn loot and minions on death ServerTimersSystem.AddAction( delaySeconds: protoExplosion.ExplosionDelay.TotalSeconds + protoExplosion.ExplosionPreset.ServerDamageApplyDelay * 1.01, () => { try { ServerBossLootSystem.ServerCreateBossLoot( epicenterPosition: bossPosition.ToVector2Ushort(), protoCharacterBoss: this, damageTracker: damageTracker, bossDifficultyCoef: ServerBossDifficultyCoef, lootObjectProto: ProtoLootObjectLazy.Value, lootObjectsDefaultCount: DeathSpawnLootObjectsDefaultCount, lootObjectsRadius: DeathSpawnLootObjectsRadius, maxLootWinners: MaxLootWinners); } finally { ServerBossLootSystem.ServerSpawnBossMinionsOnDeath( epicenterPosition: bossPosition.ToVector2Ushort(), bossDifficultyCoef: ServerBossDifficultyCoef, minionProto: ProtoMinionObjectDeathLazy.Value, minionsDefaultCount: DeathSpawnMinionsDefaultCount, minionsRadius: DeathSpawnMinionsRadius); } }); // destroy the character object after the explosion ServerTimersSystem.AddAction( delaySeconds: protoExplosion.ExplosionDelay.TotalSeconds + 0.5, () => Server.World.DestroyObject(character)); }); }
protected override void ServerUpdate(ServerUpdateData data) { var item = data.GameObject; var privateState = data.PrivateState; var character = item.Container.OwnerAsCharacter; if (!IsItemSelectedByPlayer(character, item)) { // not a selected player item this.ServerSetUpdateRate(item, isRare: true); return; } privateState.ServerTimeToPing -= data.DeltaTime; if (privateState.ServerTimeToPing > 0) { return; } privateState.ServerTimeToPing = ServerScanInterval; if (!CharacterEnergySystem.ServerDeductEnergyCharge( character, requiredEnergyAmount: this.EnergyConsumptionPerSecond * ServerScanInterval)) { // no power this.CallClient(character, _ => _.ClientRemote_NoPower()); return; } ItemDurabilitySystem.ServerModifyDurability( item, delta: -(int)Math.Round(this.DurabilityDecreasePerSecond * ServerScanInterval)); if (item.IsDestroyed) { // zero durability reached return; } // update signal strength using var tempSignalStrength = Api.Shared.GetTempList <byte>(); this.ServerCalculateStrengthToTheClosestPragmiumSpires(character, tempSignalStrength.AsList(), MaxNumberOfPongsPerScan); var previousSignalStrength = -1; foreach (var signalStrength in tempSignalStrength.AsList()) { if (signalStrength == previousSignalStrength) { // don't send multiple pongs for the signals of the same strength continue; } previousSignalStrength = signalStrength; var serverTimeToPong = SharedCalculateTimeToPong(signalStrength); ServerTimersSystem.AddAction( serverTimeToPong, () => { var currentCharacter = item.Container.OwnerAsCharacter; if (IsItemSelectedByPlayer(currentCharacter, item)) { this.CallClient(currentCharacter, _ => _.ClientRemote_OnSignal(item, PragmiumSensorSignalKind.Pong)); } }); //Logger.Dev(string.Format("Pragmium scanner signal: {0} strength. Time to send pong: {1} ms.", // signalStrength, // (int)(serverTimeToPong * 1000))); } this.CallClient(character, _ => _.ClientRemote_OnSignal(item, PragmiumSensorSignalKind.Ping)); bool IsItemSelectedByPlayer(ICharacter c, IItem i) => c is not null && ReferenceEquals(i, c.SharedGetPlayerSelectedHotbarItem()); }