public static void OnCharacterDeath(ICharacter deadCharacter) { var publicState = deadCharacter.GetPublicState <ICharacterPublicState>(); if (!publicState.IsDead) { publicState.CurrentStats.ServerSetHealthCurrent(0); return; } // recreate physics (as dead character doesn't have any physics) deadCharacter.ProtoCharacter.SharedCreatePhysics(deadCharacter); Action <ICharacter> onCharacterDeath; if (deadCharacter.ProtoCharacter is IProtoCharacterMob protoCharacterMob) { onCharacterDeath = CharacterDeath; if (onCharacterDeath is not null) { Api.SafeInvoke(() => onCharacterDeath(deadCharacter)); } protoCharacterMob.ServerOnDeath(deadCharacter); return; } // player character death // remember the death position (useful for the respawn) var privateState = PlayerCharacter.GetPrivateState(deadCharacter); privateState.LastDeathPosition = deadCharacter.TilePosition; privateState.LastDeathTime = Api.Server.Game.FrameTime; ServerTimersSystem.AddAction(delaySeconds: PlayerTeleportToGraveyardDelaySeconds, () => TeleportDeadPlayerCharacterToGraveyard(deadCharacter)); var isPvPdeath = CharacterDamageTrackingSystem.ServerGetPvPdamagePercent(deadCharacter) >= 0.5; // register death (required even if the player is not a newbie) NewbieProtectionSystem.ServerRegisterDeath(deadCharacter, isPvPdeath, out var shouldSufferDeathConsequences); if (shouldSufferDeathConsequences) { DropPlayerLoot(deadCharacter); } else { Api.Logger.Important("Player character is dead - newbie PvP case, no loot drop or other consequences", deadCharacter); } onCharacterDeath = CharacterDeath; if (onCharacterDeath is not null) { Api.SafeInvoke(() => onCharacterDeath(deadCharacter)); } }
public override bool SharedCanInteract(ICharacter character, IStaticWorldObject worldObject, bool writeToLog) { if (!base.SharedCanInteract(character, worldObject, writeToLog)) { return(false); } if (GetPublicState(worldObject).OwnerName == character.Name) { return(true); } if (NewbieProtectionSystem.SharedIsNewbie(character)) { // newbie cannot pickup other players' loot if (writeToLog) { NewbieProtectionSystem.SharedShowNewbieCannotDamageOtherPlayersOrLootBags(character, isLootBag: true); } return(false); } // non-newbie character can pickup players' loot // please note this validation has an override for derived ObjectPlayerLootContainerProtected return(true); }
public static async void ClientInvitationAccept(string clanTag) { if (NewbieProtectionSystem.ClientIsNewbie) { NewbieProtectionSystem.ClientNotifyNewbieCannotPerformAction(null); return; } if (ClientCheckIsUnderJoinCooldown(showErrorNotification: true)) { return; } var timeRemains = await Instance.CallServer( _ => _.ServerRemote_GetCooldownRemainsToJoinReturnToFaction(clanTag)); if (timeRemains > 0) { var factionEmblem = await ClientFactionEmblemTextureProvider.GetEmblemTextureAsync(clanTag, useCache : true); NotificationSystem.ClientShowNotification( title: CoreStrings.Faction_ErrorUnderJoinCooldown, // TODO: consider using a separate text constant here message: string.Format(CoreStrings.ShieldProtection_CooldownRemains_Format, ClientTimeFormatHelper.FormatTimeDuration(timeRemains)), NotificationColor.Bad, icon: factionEmblem); return; } DialogWindow.ShowDialog( title: CoreStrings.Faction_Join, text: string.Format(CoreStrings.Faction_DialogJoinConfirmation_Message_Format, @$ "\[{clanTag}\]")
public override bool SharedCanInteract(ICharacter character, IStaticWorldObject worldObject, bool writeToLog) { if (!base.SharedCanInteract(character, worldObject, writeToLog)) { return(false); } var ownerName = GetPublicState(worldObject).OwnerName; if (ownerName == character.Name) { return(true); } if (PveSystem.SharedIsPve(false)) { if (IsClient && PartySystem.ClientIsPartyMember(ownerName) || (IsServer && PartySystem.ServerIsSameParty(Server.Characters.GetPlayerCharacter(ownerName), character))) { // in PvE party members can pickup items of their party members } else { // other players in PvE cannot pickup player's loot if (writeToLog && IsClient) { PveSystem.ClientShowNotificationActionForbidden(); } return(false); } } if (NewbieProtectionSystem.SharedIsNewbie(character)) { // newbie cannot pickup other players' loot if (writeToLog) { NewbieProtectionSystem.SharedShowNewbieCannotDamageOtherPlayersOrLootBags(character, isLootBag: true); } return(false); } // non-newbie character can pickup players' loot // please note this validation has an override for derived ObjectPlayerLootContainerProtected return(true); }
protected override double SharedCalculateDamageByWeapon( WeaponFinalCache weaponCache, double damagePreMultiplier, IStaticWorldObject targetObject, out double obstacleBlockDamageCoef) { var serverTime = IsServer ? Server.Game.FrameTime : Client.CurrentGame.ServerFrameTimeApproximated; if (serverTime < GetPublicState(targetObject).CooldownUntilServerTime) { // too hot for mining - no damage to it if (IsClient && weaponCache.ProtoWeapon is IProtoItemToolMining) { NotificationSystem.ClientShowNotification(CoreStrings.Meteorite_CooldownMessage_TooHotForMining, color: NotificationColor.Bad, icon: this.Icon); } if (IsServer && weaponCache.ProtoWeapon is IProtoItemWeaponMelee && !weaponCache.Character.IsNpc) { weaponCache.Character.ServerAddStatusEffect <StatusEffectHeat>(intensity: 0.5); } obstacleBlockDamageCoef = this.ObstacleBlockDamageCoef; return(0); } // meteorite cooldown finished if (NewbieProtectionSystem.SharedIsNewbie(weaponCache.Character)) { // don't allow mining meteorite while under newbie protection if (IsClient) { NewbieProtectionSystem.ClientNotifyNewbieCannotPerformAction(this); } obstacleBlockDamageCoef = 0; return(0); } return(base.SharedCalculateDamageByWeapon(weaponCache, damagePreMultiplier, targetObject, out obstacleBlockDamageCoef)); }
public override bool SharedCanInteract(ICharacter character, IStaticWorldObject worldObject, bool writeToLog) { // don't use the base implementation as it will not work in PvE // (action forbidden if player doesn't have access to the land claim) if (character.GetPublicState <ICharacterPublicState>().IsDead) { return(false); } if (!NewbieProtectionSystem.SharedValidateInteractionIsNotForbidden(character, worldObject, writeToLog)) { return(false); } return(this.SharedIsInsideCharacterInteractionArea(character, worldObject, writeToLog)); }
public static void ClientApplicantSubmitApplication(string clanTag) { if (NewbieProtectionSystem.ClientIsNewbie) { NewbieProtectionSystem.ClientNotifyNewbieCannotPerformAction(null); return; } if (ClientCheckIsUnderJoinCooldown(showErrorNotification: true)) { return; } DialogWindow.ShowDialog( title: CoreStrings.Faction_SubmitApplication, string.Format(CoreStrings.Faction_DialogSubmitApplication_Message_Format, @$ "\[{clanTag}\]")
public string Execute(bool isEnabled, [CurrentCharacterIfNull] ICharacter character) { if (isEnabled) { NewbieProtectionSystem.ServerRegisterNewbie(character); } else { NewbieProtectionSystem.ServerDisableNewbieProtection(character); } return(character + " is now " + (NewbieProtectionSystem.SharedIsNewbie(character) ? "under newbie protection." : "without newbie protection.")); }
protected override void ServerInitializeCharacterFirstTime(ServerInitializeData data) { base.ServerInitializeCharacterFirstTime(data); var character = data.GameObject; var publicState = data.PublicState; if (!Api.IsEditor) { NewbieProtectionSystem.ServerRegisterNewbie(character); publicState.IsMale = 1 == RandomHelper.Next(0, maxValueExclusive: 2); // male/female ratio: 50/50 publicState.FaceStyle = SharedCharacterFaceStylesProvider .GetForGender(publicState.IsMale) .GenerateRandomFace(); } else // if Editor { publicState.IsMale = true; publicState.FaceStyle = SharedCharacterFaceStylesProvider .GetForGender(publicState.IsMale) .GetDefaultFaceInEditor(); } ServerPlayerSpawnManager.ServerAddTorchItemIfNoItems(character); if (!Api.IsEditor) { return; } // the game is run as Editor // auto pwn in editor mode ConsoleCommandsSystem.SharedGetCommand <ConsoleAdminPwn>().Execute(player: character); // add all the skills ConsoleCommandsSystem.SharedGetCommand <ConsoleSkillsSetAll>().Execute(player: character); // add all the technologies ConsoleCommandsSystem.SharedGetCommand <ConsoleTechAddAll>().Execute(player: character); this.ServerRebuildFinalCacheIfNeeded(data.PrivateState, publicState); // add all the quests (and complete them) ConsoleCommandsSystem.SharedGetCommand <ConsoleQuestCompleteAll>().Execute(player: character); }
public virtual bool SharedCanInteract(ICharacter character, TWorldObject worldObject, bool writeToLog) { if (!this.IsInteractableObject) { return(false); } if (character.GetPublicState <ICharacterPublicState>().IsDead || IsServer && !character.ServerIsOnline) { return(false); } if (worldObject is IStaticWorldObject staticWorldObject) { if (PveSystem.SharedIsPve(clientLogErrorIfDataIsNotYetAvailable: false)) { if (!PveSystem.SharedValidateInteractionIsNotForbidden(character, staticWorldObject, writeToLog)) { // action forbidden by PvE system return(false); } } else // PvP servers have newbie protection system { if (!NewbieProtectionSystem.SharedValidateInteractionIsNotForbidden( character, staticWorldObject, writeToLog)) { // action forbidden by newbie protection system return(false); } } } return(this.SharedIsInsideCharacterInteractionArea(character, worldObject, writeToLog)); }
public override bool SharedCanInteract(ICharacter character, IStaticWorldObject worldObject, bool writeToLog) { if (!base.SharedCanInteract(character, worldObject, writeToLog)) { return(false); } if (NewbieProtectionSystem.SharedIsNewbie(character)) { // don't allow hacking while under newbie protection if (writeToLog && IsClient) { NewbieProtectionSystem.ClientNotifyNewbieCannotPerformAction(this); } return(false); } return(true); }
private static IItemsContainer ServerTryCreateLootContainerAtPosition( Vector2Ushort tilePosition, ICharacter character, bool writeWarningsToLog = true) { IStaticWorldObject objectLootContainer = null; var protoLootContainer = NewbieProtectionSystem.SharedIsNewbie(character) ? Api.GetProtoEntity <ObjectPlayerLootContainerProtected>() : Api.GetProtoEntity <ObjectPlayerLootContainer>(); if (protoLootContainer.CheckTileRequirements(tilePosition, character: null, logErrors: false)) { Logger.Important("Creating loot container at " + tilePosition); objectLootContainer = Server.World.CreateStaticWorldObject(protoLootContainer, tilePosition); } if (objectLootContainer is null) { // cannot create loot container if (writeWarningsToLog) { Logger.Warning( $"Cannot create loot container at {tilePosition} - tile contains something preventing it."); } return(null); } var lootPrivateState = GetPrivateState(objectLootContainer); lootPrivateState.Owner = character; ServerSetDefaultDestroyTimeout(lootPrivateState); GetPublicState(objectLootContainer).OwnerName = character.Name; var characterPrivateState = PlayerCharacter.GetPrivateState(character); characterPrivateState.DroppedLootLocations.Add( new DroppedLootInfo(tilePosition, lootPrivateState.DestroyAtTime)); return(lootPrivateState.ItemsContainer); }
protected override double SharedCalculateDamageByWeapon( WeaponFinalCache weaponCache, double damagePreMultiplier, IStaticWorldObject targetObject, out double obstacleBlockDamageCoef) { if (NewbieProtectionSystem.SharedIsNewbie(weaponCache.Character)) { // don't allow mining a boss loot while under newbie protection if (IsClient) { NewbieProtectionSystem.ClientNotifyNewbieCannotPerformAction(this); } obstacleBlockDamageCoef = 0; return(0); } return(base.SharedCalculateDamageByWeapon(weaponCache, damagePreMultiplier, targetObject, out obstacleBlockDamageCoef)); }
protected override void ServerInitializeCharacterFirstTime(ServerInitializeData data) { base.ServerInitializeCharacterFirstTime(data); var character = data.GameObject; var publicState = data.PublicState; if (!Api.IsEditor) { NewbieProtectionSystem.ServerRegisterNewbie(character); } publicState.IsMale = true; publicState.FaceStyle = SharedCharacterFaceStylesProvider .GetForGender(publicState.IsMale) .GenerateRandomFace(); ServerPlayerSpawnManager.ServerAddTorchItemIfNoItems(character); if (!Api.IsEditor) { return; } // the game is run as Editor // auto pwn in editor mode GetProtoEntity <ConsoleAdminPwn>().Execute(player: character); // add all the skills GetProtoEntity <ConsoleSkillsSetAll>().Execute(player: character); // add all the technologies GetProtoEntity <ConsoleTechAddAll>().Execute(player: character); this.ServerRebuildFinalCacheIfNeeded(data.PrivateState, publicState); // add all the quests (and complete them) GetProtoEntity <ConsoleQuestCompleteAll>().Execute(player: character); }
private static void ServerCharacterDeathHandler(ICharacter deadCharacter) { if (deadCharacter.IsNpc) { return; } var statistics = PlayerCharacter.GetPrivateState(deadCharacter).Statistics; statistics.Deaths++; Logger.Important($"{deadCharacter} died. Total deaths: {statistics.Deaths}"); if (PveSystem.ServerIsPvE) { return; } using var tempKilledBy = ServerGetKilledByPlayerCharacters(deadCharacter); if (tempKilledBy is null || tempKilledBy.Count == 0) { return; } // increase kills counter only for the first killer (with the highest damage ratio) IncreasePvpKillsCounter(killer: tempKilledBy.AsList()[0].Killer, deadCharacter); if (NewbieProtectionSystem.SharedIsNewbie(deadCharacter)) { Logger.Important( $"{deadCharacter} was killed while under newbie protection, PvP score mechanic doesn't apply"); return; } var originalPvpScore = statistics.PvpScore; if (originalPvpScore <= 0) { // cannot take PvP score Logger.Important($"{deadCharacter} was killed but has PvP score 0 so no score is lost"); return; } double pvpScoreTaken; if (originalPvpScore <= 1) { pvpScoreTaken = 1; } else { pvpScoreTaken = MathHelper.Clamp(Math.Floor(originalPvpScore * PvpScoreLossOnDeathFraction), min: 1, max: originalPvpScore); } statistics.PvpScore = Math.Max(0, originalPvpScore - pvpScoreTaken); if (statistics.PvpScore == 0) { statistics.ServerPvpScoreNextRecoveryTime = Server.Game.FrameTime + PvpScoreRestoreDuration; } Logger.Important( string.Format( "{0} was killed in PvP and lost PvP score: {1}Previous score: {2:0.##}{1}New score: {3:0.##}", deadCharacter, Environment.NewLine, originalPvpScore, statistics.PvpScore)); ServerDistributePvpScore(tempKilledBy.AsList(), pvpScoreTaken, deadCharacter); }
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)); }
public void SharedValidatePlacement( ICharacter character, Vector2Ushort targetPosition, bool logErrors, out bool canPlace, out bool isTooFar) { if (NewbieProtectionSystem.SharedIsNewbie(character)) { if (logErrors) { Logger.Warning("Newbie cannot plant bombs"); NewbieProtectionSystem.SharedNotifyNewbieCannotPerformAction(character, this); } canPlace = false; isTooFar = false; return; } // check if there is a direct line of sight // check that there are no other objects on the way between them (defined by default layer) var physicsSpace = character.PhysicsBody.PhysicsSpace; var characterCenter = character.Position + character.PhysicsBody.CenterOffset; var worldObjectCenter = targetPosition.ToVector2D() + (0.5, 0.5); // local method for testing if there is an obstacle from current to the specified position bool TestHasObstacle(Vector2D toPosition) { using var obstaclesOnTheWay = physicsSpace.TestLine( characterCenter, toPosition, CollisionGroup.GetDefault(), sendDebugEvent: false); foreach (var test in obstaclesOnTheWay.AsList()) { var testPhysicsBody = test.PhysicsBody; if (testPhysicsBody.AssociatedProtoTile != null) { // obstacle tile on the way return(true); } var testWorldObject = testPhysicsBody.AssociatedWorldObject; if (testWorldObject == character) { // not an obstacle - it's the character or world object itself continue; } switch (testWorldObject.ProtoWorldObject) { case IProtoObjectDeposit _: // allow deposits case ObjectWallDestroyed _: // allow destroyed walls continue; } // obstacle object on the way return(true); } // no obstacles return(false); } // let's test by casting rays from character center to the center of the planted bomb if (TestHasObstacle(worldObjectCenter)) { // has obstacle if (logErrors) { if (IsClient) { this.ClientShowCannotPlaceObstaclesOnTheWayNotification(); } else { Logger.Warning($"{character} cannot place {this} - obstacles on the way"); this.CallClient(character, _ => _.ClientRemote_CannotPlaceObstacles()); } } canPlace = false; isTooFar = false; return; } if (!this.ObjectExplosiveProto.CheckTileRequirements(targetPosition, character, logErrors)) { // explosive static object placement requirements failed canPlace = false; isTooFar = false; return; } // validate distance to the character if (this.SharedIsTooFarToPlace(character, targetPosition, logErrors)) { canPlace = true; isTooFar = true; return; } canPlace = true; isTooFar = false; }
public virtual bool SharedCanInteract(ICharacter character, TWorldObject worldObject, bool writeToLog) { if (!this.IsInteractableObject) { return(false); } try { this.VerifyGameObject(worldObject); } catch (Exception ex) { Logger.Warning($"Interaction check failed: {ex.GetType().FullName}: {ex.Message}"); return(false); } if (character.GetPublicState <ICharacterPublicState>().IsDead || IsServer && !character.ServerIsOnline || ((IsServer || character.IsCurrentClientCharacter) && PlayerCharacter.GetPrivateState(character).IsDespawned)) { if (writeToLog) { Logger.Warning( $"Character cannot interact with {worldObject} - character is offline/despawned/dead.", character); } return(false); } if (worldObject is IStaticWorldObject staticWorldObject) { if (PveSystem.SharedIsPve(clientLogErrorIfDataIsNotYetAvailable: false)) { if (!PveSystem.SharedValidateInteractionIsNotForbidden(character, staticWorldObject, writeToLog)) { // action forbidden by PvE system return(false); } } else // PvP servers have newbie protection system { if (!NewbieProtectionSystem.SharedValidateInteractionIsNotForbidden( character, staticWorldObject, writeToLog)) { // action forbidden by newbie protection system return(false); } } } return(this.SharedIsInsideCharacterInteractionArea(character, worldObject, writeToLog)); }
public virtual bool SharedOnDamage( WeaponFinalCache weaponCache, IStaticWorldObject targetObject, double damagePreMultiplier, out double obstacleBlockDamageCoef, out double damageApplied) { var objectPublicState = GetPublicState(targetObject); var previousStructurePoints = objectPublicState.StructurePointsCurrent; if (previousStructurePoints <= 0f) { // already destroyed static world object obstacleBlockDamageCoef = 0; damageApplied = 0; return(false); } var serverDamage = this.SharedCalculateDamageByWeapon( weaponCache, damagePreMultiplier, targetObject, out obstacleBlockDamageCoef); if (serverDamage < 0) { Logger.Warning( $"Server damage less than 0 and this is suspicious. {this} calculated damage: {serverDamage:0.###}"); serverDamage = 0; } if (IsClient) { // simply call these methods to display a client notification only! // they are not used for anything else here // to calculate damage they're used in WeaponDamageSystem.ServerCalculateTotalDamage method. RaidingProtectionSystem.SharedCanRaid(targetObject, showClientNotification: true); LandClaimShieldProtectionSystem.SharedCanRaid(targetObject, showClientNotification: true); PveSystem.SharedIsAllowStaticObjectDamage(weaponCache.Character, targetObject, showClientNotification: true); NewbieProtectionSystem.SharedIsAllowStructureDamage(weaponCache.Character, targetObject, showClientNotification: true); damageApplied = 0; return(true); } if (serverDamage <= 0) { // no damage applied damageApplied = 0; return(true); } // apply damage damageApplied = serverDamage; var newStructurePoints = (float)(previousStructurePoints - serverDamage); if (newStructurePoints < 0) { newStructurePoints = 0; } Logger.Info( $"Damage applied to {targetObject} by {weaponCache.Character}:\n{serverDamage} dmg, current structure points {newStructurePoints}/{this.StructurePointsMax}, {weaponCache.Weapon}"); objectPublicState.StructurePointsCurrent = newStructurePoints; try { this.ServerOnStaticObjectDamageApplied( weaponCache, targetObject, previousStructurePoints, newStructurePoints); } catch (Exception ex) { Logger.Exception(ex, $"Problem on processing {nameof(this.ServerOnStaticObjectDamageApplied)}()"); } if (newStructurePoints <= 0f) { this.ServerOnStaticObjectZeroStructurePoints(weaponCache, weaponCache.Character, targetObject); } return(true); }
public void SharedValidatePlacement( ICharacter character, Vector2Ushort targetPosition, bool logErrors, out bool canPlace, out bool isTooFar, out object errorCodeOrMessage) { if (NewbieProtectionSystem.SharedIsNewbie(character)) { if (logErrors) { NewbieProtectionSystem.SharedNotifyNewbieCannotPerformAction(character, this); } canPlace = false; isTooFar = false; errorCodeOrMessage = null; return; } // check whether somebody nearby is already placing a bomb there var tempCharactersNearby = Api.Shared.GetTempList <ICharacter>(); if (IsServer) { Server.World.GetScopedByPlayers(character, tempCharactersNearby); } else { Client.Characters.GetKnownPlayerCharacters(tempCharactersNearby); } foreach (var otherCharacter in tempCharactersNearby.AsList()) { if (otherCharacter != character && otherCharacter.IsInitialized && PlayerCharacter.GetPublicState(otherCharacter).CurrentPublicActionState is ItemExplosiveActionPublicState explosiveActionState && explosiveActionState.TargetPosition == targetPosition) { // someone is already planting a bomb here canPlace = false; isTooFar = false; errorCodeOrMessage = null; return; } } // check if there is a direct line of sight // check that there are no other objects on the way between them (defined by default layer) var physicsSpace = character.PhysicsBody.PhysicsSpace; var characterCenter = character.Position + character.PhysicsBody.CenterOffset; var worldObjectCenter = targetPosition.ToVector2D() + (0.5, 0.5); // local method for testing if there is an obstacle from current to the specified position bool TestHasObstacle(Vector2D toPosition) { using var obstaclesInTheWay = physicsSpace.TestLine( characterCenter, toPosition, CollisionGroup.Default, sendDebugEvent: false); foreach (var test in obstaclesInTheWay.AsList()) { var testPhysicsBody = test.PhysicsBody; if (testPhysicsBody.AssociatedProtoTile is not null) { // obstacle tile on the way return(true); } var testWorldObject = testPhysicsBody.AssociatedWorldObject; if (testWorldObject is null) { // some barrier on the way return(true); } if (testWorldObject == character) { // not an obstacle - it's the character or world object itself continue; } switch (testWorldObject.ProtoWorldObject) { case IProtoObjectDeposit: // allow deposits case ObjectWallDestroyed: // allow destroyed walls continue; } // obstacle object on the way return(true); } // no obstacles return(false); } if (!this.ObjectExplosiveProto.CheckTileRequirements(targetPosition, character, out errorCodeOrMessage, logErrors)) { // explosive static object placement requirements failed canPlace = false; isTooFar = false; return; } // let's check whether there are any obstacles by casting rays // from character's center to the center of the planted bomb if (TestHasObstacle(worldObjectCenter)) { // has obstacle if (logErrors) { if (IsClient) { this.ClientShowCannotPlaceObstaclesInTheWayNotification(); } else { Logger.Warning($"{character} cannot place {this} - obstacles in the way"); this.CallClient(character, _ => _.ClientRemote_CannotPlaceObstacles()); } } canPlace = false; isTooFar = false; errorCodeOrMessage = CoreStrings.Notification_ObstaclesOnTheWay; return; } // validate distance to the character if (this.SharedIsTooFarToPlace(character, targetPosition, logErrors)) { canPlace = true; isTooFar = true; return; } canPlace = true; isTooFar = false; }
public override bool SharedCanInteract(ICharacter character, IStaticWorldObject worldObject, bool writeToLog) { return(PveSystem.SharedValidateInteractionIsNotForbidden(character, worldObject, writeToLog) && NewbieProtectionSystem.SharedValidateInteractionIsNotForbidden(character, worldObject, writeToLog) && this.SharedIsInsideCharacterInteractionArea(character, worldObject, writeToLog)); }
public override bool SharedOnDamage( WeaponFinalCache weaponCache, IWorldObject targetObject, double damagePreMultiplier, double damagePostMultiplier, out double obstacleBlockDamageCoef, out double damageApplied) { var byCharacter = weaponCache.Character; if (NewbieProtectionSystem.SharedIsNewbie(byCharacter)) { // don't allow attacking a boss while under newbie protection if (IsClient) { NewbieProtectionSystem.ClientNotifyNewbieCannotPerformAction(this); } obstacleBlockDamageCoef = 0; damageApplied = 0; return(false); } if (IsServer) { // apply the difficulty coefficient damagePostMultiplier /= ServerBossDifficultyCoef; if (weaponCache.ProtoExplosive != null) { // the boss is massive so it will take x2 damage from explosives (such as grenades) damagePostMultiplier *= 2; } } var result = base.SharedOnDamage(weaponCache, targetObject, damagePreMultiplier, damagePostMultiplier, out obstacleBlockDamageCoef, out damageApplied); if (IsServer && result && byCharacter != null && !byCharacter.IsNpc) { var privateState = GetPrivateState((ICharacter)targetObject); // record the damage dealt by player privateState.DamageTracker.RegisterDamage(byCharacter, damageApplied); if (damageApplied > 1 / ServerBossDifficultyCoef) { // record the last time a significant damage is dealt privateState.LastDamageTime = Server.Game.FrameTime; } } return(result); }
public static double ServerCalculateTotalDamage( WeaponFinalCache weaponCache, IWorldObject targetObject, FinalStatsCache targetFinalStatsCache, double damagePreMultiplier, bool clampDefenseTo1) { if (targetObject is IStaticWorldObject staticWorldObject && (!RaidingProtectionSystem.SharedCanRaid(staticWorldObject, showClientNotification: false) || !LandClaimShieldProtectionSystem.SharedCanRaid(staticWorldObject, showClientNotification: false) || !PveSystem.SharedIsAllowStaticObjectDamage(weaponCache.Character, staticWorldObject, showClientNotification: false) || !NewbieProtectionSystem.SharedIsAllowStructureDamage(weaponCache.Character, staticWorldObject, showClientNotification: false))) { return(0); } if (targetObject.ProtoGameObject is IProtoVehicle && !PveSystem.SharedIsAllowVehicleDamage(weaponCache, (IDynamicWorldObject)targetObject, showClientNotification: false)) { return(0); } if (weaponCache.ProtoExplosive is not null && targetObject is IStaticWorldObject targetStaticWorldObject) { // special case - apply the explosive damage to static object return(ServerCalculateTotalDamageByExplosive(weaponCache.Character, weaponCache.ProtoExplosive, targetStaticWorldObject, damagePreMultiplier)); } if (ServerIsRestrictedPvPDamage(weaponCache, targetObject, out var isPvPcase, out var isFriendlyFireCase)) { return(0); } var damageValue = damagePreMultiplier * weaponCache.DamageValue; var invertedArmorPiercingCoef = weaponCache.InvertedArmorPiercingCoef; var totalDamage = 0d; // calculate total damage by summing all the damage components foreach (var damageDistribution in weaponCache.DamageDistributions) { var defenseStatName = SharedGetDefenseStatName(damageDistribution.DamageType); var defenseFraction = targetFinalStatsCache[defenseStatName]; defenseFraction = MathHelper.Clamp(defenseFraction, 0, clampDefenseTo1 ? 1 : double.MaxValue); totalDamage += ServerCalculateDamageComponent( damageValue, invertedArmorPiercingCoef, damageDistribution, defenseFraction); } // multiply on final multiplier (usually used for expanding projectiles) totalDamage *= weaponCache.FinalDamageMultiplier; var damagingCharacter = weaponCache.Character; if (isPvPcase) { // apply PvP damage multiplier totalDamage *= WeaponConstants.DamagePvpMultiplier; } else if (damagingCharacter is not null && !damagingCharacter.IsNpc && targetObject.ProtoGameObject is IProtoCharacterMob protoCharacterMob && !protoCharacterMob.IsBoss) { // apply PvE damage multiplier totalDamage *= WeaponConstants.DamagePveMultiplier; }
public static bool SharedOnDamageToCharacter( ICharacter targetCharacter, WeaponFinalCache weaponCache, double damageMultiplier, out double damageApplied) { var targetPublicState = targetCharacter.GetPublicState <ICharacterPublicState>(); var targetCurrentStats = targetPublicState.CurrentStats; if (targetCurrentStats.HealthCurrent <= 0) { // target character is dead, cannot apply damage to it damageApplied = 0; return(false); } { if (!targetCharacter.IsNpc && weaponCache.Character is ICharacter damagingCharacter && NewbieProtectionSystem.SharedIsNewbie(damagingCharacter)) { // no damage from newbie damageApplied = 0; if (Api.IsClient) { // display message to newbie NewbieProtectionSystem.ClientShowNewbieCannotDamageOtherPlayersOrLootBags(isLootBag: false); } // but the hit is registered so it's not possible to shoot through a character return(true); } } if (Api.IsClient) { // we don't simulate the damage on the client side damageApplied = 0; if (weaponCache.Character is ICharacter damagingCharacter) { // potentially a PvP case PveSystem.ClientShowDuelModeRequiredNotificationIfNecessary( damagingCharacter, targetCharacter); } return(true); } var attackerCharacter = weaponCache.Character; if (!(attackerCharacter is null) && attackerCharacter.IsNpc && targetCharacter.IsNpc) { // no creature-to-creature damage damageApplied = 0; return(false); } // calculate and apply damage on server var targetFinalStatsCache = targetCharacter.GetPrivateState <BaseCharacterPrivateState>() .FinalStatsCache; var totalDamage = ServerCalculateTotalDamage( weaponCache, targetCharacter, targetFinalStatsCache, damageMultiplier, clampDefenseTo1: true); if (totalDamage <= 0) { // damage suppressed damageApplied = 0; return(true); } // Clamp the max receivable damage to x5 from the max health. // This will help in case when the too much damage is dealt (mega-bomb!) // to ensure the equipment will not receive excessive damaged. totalDamage = Math.Min(totalDamage, 5 * targetCurrentStats.HealthMax); // apply damage if (!(attackerCharacter is null)) { targetCurrentStats.ServerReduceHealth(totalDamage, damageSource: attackerCharacter); }