Exemple #1
0
        /// <summary>
        /// Moves the character to the "graveyard" so a respawn will be required on login.
        /// No penalty in items or "weakened" status effect.
        /// </summary>
        public static void DespawnCharacter(ICharacter character)
        {
            var publicState = character.GetPublicState <ICharacterPublicState>();

            if (publicState.IsDead)
            {
                return;
            }

            VehicleSystem.ServerCharacterExitCurrentVehicle(character, force: true);

            var privateState = PlayerCharacter.GetPrivateState(character);

            CharacterDamageTrackingSystem.ServerClearStats(character);
            privateState.IsDespawned = true;
            Api.Logger.Important("Character despawned", character);

            // we have to set the dead flag to stop game mechanics from working
            // but on the respawn player should not lose anything
            publicState.IsDead = true;
            // recreate physics (as dead/despawned character doesn't have any physics)
            character.ProtoCharacter.SharedCreatePhysics(character);

            TeleportDeadPlayerCharacterToGraveyard(character);

            privateState.LastDeathPosition = Vector2Ushort.Zero;
            privateState.LastDeathTime     = null;
        }
Exemple #2
0
        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));
            }
        }
Exemple #3
0
        public void ServerReduceHealth(double damage, IProtoGameObject damageSource)
        {
            if (damage <= 0)
            {
                return;
            }

            if (damageSource != null)
            {
                // it's important to register the damage source before the damage is applied
                // (to use it in case of the subsequent death)
                CharacterDamageTrackingSystem.ServerRegisterDamage(damage,
                                                                   (ICharacter)this.GameObject,
                                                                   new ServerDamageSourceEntry(damageSource));
            }

            var newHealth = this.HealthCurrent - damage;

            if (newHealth <= 0 &&
                ((ICharacter)this.GameObject).IsNpc &&
                damageSource is IProtoStatusEffect)
            {
                // Don't allow killing mob by a status effect.
                // This is a workaround to kill quests which cannot be finished
                // as the final damage is done by a status effect.
                // TODO: Should be removed when we enable the damage tracking for mobs damage.
                newHealth = float.Epsilon;
            }

            this.ServerSetHealthCurrent((float)newHealth);
        }
Exemple #4
0
        public string Execute([CurrentCharacterIfNull] ICharacter player)
        {
            var stats = player.GetPublicState <PlayerCharacterPublicState>()
                        .CurrentStatsExtended;

            // override death doesn't work here as the dead character has no physics and simple healing will not help
            //stats.ServerSetHealthCurrent(stats.HealthMax, overrideDeath: true);

            stats.ServerSetCurrentValuesToMaxValues();
            player.ServerRemoveAllStatusEffects(removeOnlyDebuffs: true);

            CharacterDamageTrackingSystem.ServerClearStats(player);

            string vehicleHealedMessage = null;
            var    vehicle = PlayerCharacter.GetPublicState(player).CurrentVehicle;

            if (!(vehicle is null))
            {
                var protoVehicle       = (IProtoVehicle)vehicle.ProtoGameObject;
                var vehiclePublicState = vehicle.GetPublicState <VehiclePublicState>();
                vehiclePublicState.StructurePointsCurrent = protoVehicle.SharedGetStructurePointsMax(vehicle);
                vehicleHealedMessage = $"Vehicle healed to {vehiclePublicState.StructurePointsCurrent} HP";
            }

            return(string.Format(
                       "{0} healed to {1} HP (all other stats are also restored, all debuff status effects were removed){2}.",
                       player,
                       stats.HealthCurrent,
                       vehicleHealedMessage != null
                    ? Environment.NewLine + vehicleHealedMessage
                    : string.Empty));
        }
        public void ServerReduceHealth(double damage, IGameObjectWithProto damageSource)
        {
            if (damage <= 0)
            {
                return;
            }

            if (this.HealthCurrent <= 0)
            {
                return;
            }

            if (damageSource != null)
            {
                // it's important to register the damage source before the damage is applied
                // (to use it in case of the subsequent death)
                CharacterDamageTrackingSystem.ServerRegisterDamage(damage,
                                                                   (ICharacter)this.GameObject,
                                                                   new ServerDamageSourceEntry(damageSource));
            }

            var newHealth = this.HealthCurrent - damage;

            if (newHealth <= 0 &&
                ((ICharacter)this.GameObject).IsNpc &&
                damageSource?.ProtoGameObject is IProtoStatusEffect)
            {
                var attackerCharacter = GetAttackerCharacter(damageSource, out _);
                if (attackerCharacter is null ||
                    attackerCharacter.IsNpc)
                {
                    // Don't allow killing mob by a status effect which is NOT added by a player character.
                    // This is a workaround to kill quests which cannot be finished
                    // when creature is killed by a status effect.
                    // TODO: Should be removed when we enable the damage tracking for mobs damage.
                    newHealth = float.Epsilon;
                }
            }

            this.ServerSetHealthCurrent((float)newHealth);

            if (newHealth <= 0)
            {
                var attackerCharacter = GetAttackerCharacter(damageSource, out var weaponSkill);
                ServerCharacterDeathMechanic.OnCharacterKilled(
                    attackerCharacter,
                    targetCharacter: (ICharacter)this.GameObject,
                    weaponSkill);
            }
        }
Exemple #6
0
        public void ServerReduceHealth(double damage, IGameObjectWithProto damageSource)
        {
            if (damage <= 0)
            {
                return;
            }

            if (this.HealthCurrent <= 0)
            {
                return;
            }

            var damagedCharacter = (ICharacter)this.GameObject;

            if (damageSource is not null)
            {
                // it's important to register the damage source before the damage is applied
                // (to use it in case of the subsequent death)
                CharacterDamageTrackingSystem.ServerRegisterDamage(damage,
                                                                   damagedCharacter,
                                                                   new ServerDamageSourceEntry(damageSource));
            }

            var newHealth = this.HealthCurrent - damage;

            if (newHealth <= 0 &&
                damagedCharacter.IsNpc &&
                damageSource?.ProtoGameObject is IProtoStatusEffect)
            {
                var attackerCharacter = GetAttackerCharacter(damageSource, out _);
                if (attackerCharacter is null ||
                    attackerCharacter.IsNpc)
                {
                    // don't allow killing a mob by a status effect which is NOT added by a player character
                    newHealth = float.Epsilon;
                }
            }

            this.ServerSetHealthCurrent((float)newHealth);

            if (newHealth <= 0)
            {
                var attackerCharacter = GetAttackerCharacter(damageSource, out var weaponSkill);
                ServerCharacterDeathMechanic.OnCharacterKilled(
                    attackerCharacter,
                    targetCharacter: damagedCharacter,
                    weaponSkill);
            }
        }
Exemple #7
0
        /// <summary>
        /// Moves the character to service area so a respawn will be required on login.
        /// There will be no penalties (such as loot drop or "weakened" status effect).
        /// </summary>
        public static void DespawnCharacter(ICharacter character)
        {
            var privateState = PlayerCharacter.GetPrivateState(character);

            if (privateState.IsDespawned)
            {
                return;
            }

            VehicleSystem.ServerCharacterExitCurrentVehicle(character, force: true);
            CharacterDamageTrackingSystem.ServerClearStats(character);

            ServerTeleportPlayerCharacterToServiceArea(character);

            privateState.LastDeathPosition = Vector2Ushort.Zero;
            privateState.LastDeathTime     = null;
        }
        public string Execute([CurrentCharacterIfNull] ICharacter player)
        {
            var stats = player.GetPublicState <PlayerCharacterPublicState>()
                        .CurrentStatsExtended;

            // override death doesn't work here as the dead character has no physics and simple healing will not help
            //stats.ServerSetHealthCurrent(stats.HealthMax, overrideDeath: true);

            stats.ServerSetCurrentValuesToMaxValues();
            player.ServerRemoveAllStatusEffects(removeOnlyDebuffs: true);

            CharacterDamageTrackingSystem.ServerClearStats(player);

            return(string.Format(
                       "{0} healed to {1} HP (all other stats are also restored, all debuff status effects were removed).",
                       player,
                       stats.HealthCurrent));
        }
Exemple #9
0
        private static void ServerCharacterDeathHandler(ICharacter character)
        {
            if (character.IsNpc)
            {
                return;
            }

            // Mutation status effect is added upon death if EITHER one of the two conditions are met:
            // player had 15% or more radiation poisoning when they died
            // OR they had 15% or more damage received from radiation when they died.
            if (ServerIsDeathFromRadiation())
            {
                Logger.Info("Character died from radiation, adding mutation", character);
                character.ServerAddStatusEffect <StatusEffectMutation>(intensity: 1.0);
            }

            bool ServerIsDeathFromRadiation()
            {
                if (character.SharedGetStatusEffectIntensity <StatusEffectRadiationPoisoning>()
                    > 0.15)
                {
                    return(true);
                }

                var damageSources = CharacterDamageTrackingSystem.ServerGetDamageSourcesLatest(character);

                if (damageSources is null)
                {
                    return(false);
                }

                foreach (var damageSource in damageSources)
                {
                    if (damageSource.ProtoEntity is StatusEffectRadiationPoisoning &&
                        damageSource.Fraction > 0.15)
                    {
                        return(true);
                    }
                }

                return(false);
            }
        }
Exemple #10
0
        public void ServerReduceHealth(double damage, IGameObjectWithProto damageSource)
        {
            if (damage <= 0)
            {
                return;
            }

            if (damageSource != null)
            {
                // it's important to register the damage source before the damage is applied
                // (to use it in case of the subsequent death)
                CharacterDamageTrackingSystem.ServerRegisterDamage(damage,
                                                                   (ICharacter)this.GameObject,
                                                                   new ServerDamageSourceEntry(damageSource));
            }

            var newHealth = this.HealthCurrent - damage;

            this.ServerSetHealthCurrent((float)newHealth);
        }
Exemple #11
0
        /// <summary>
        /// Check whether it was a player character killed and check its damage sources history.
        /// If it was killed by a friendly faction member, punish that member by reducing its karma
        /// and kick if it's too low.
        /// Please note: party members could kill each other without consequences even if they're in the same faction.
        /// </summary>
        private static void ServerCharacterDeathHandler(ICharacter killedCharacter)
        {
            if (killedCharacter.IsNpc)
            {
                return;
            }

            // get damage sources for the past 12.5 seconds (2.5 chunks)
            var damageSources = CharacterDamageTrackingSystem.ServerGetDamageSourcesAfterTime(
                killedCharacter,
                afterTime: Server.Game.FrameTime
                - ServerCharacterDamageSourcesStats.ChunkDuration * 2.5);

            if (damageSources is null)
            {
                return;
            }

            var killedCharacterFaction = FactionSystem.ServerGetFaction(killedCharacter);

            if (killedCharacterFaction is null)
            {
                // a non-faction player character was killed - no karma system action
                return;
            }

            foreach (var entry in damageSources)
            {
                if (entry.ProtoEntity is not PlayerCharacter)
                {
                    continue;
                }

                var attackerCharacter = Server.Characters.GetPlayerCharacter(entry.Name);
                if (attackerCharacter is null)
                {
                    // should not be possible
                    continue;
                }

                ProcessAttackerCharacter(attackerCharacter, damageFraction: entry.Fraction);
            }

            void ProcessAttackerCharacter(ICharacter attackerCharacter, double damageFraction)
            {
                // found a player character that has damaged the (now dead) player character recently
                if (PartySystem.ServerIsSameParty(killedCharacter, attackerCharacter))
                {
                    // damaging your own party members is fine
                    return;
                }

                var attackerCharacterFaction = FactionSystem.ServerGetFaction(attackerCharacter);

                if (attackerCharacterFaction is null ||
                    Faction.GetPublicState(attackerCharacterFaction).Kind != FactionKind.Public)
                {
                    // attacking character is not a member of a public faction - no karma system action
                    return;
                }

                if (!ReferenceEquals(killedCharacterFaction, attackerCharacterFaction) &&
                    (FactionSystem.ServerGetFactionDiplomacyStatus(killedCharacterFaction,
                                                                   attackerCharacterFaction)
                     != FactionDiplomacyStatus.Ally))
                {
                    // the killer is not a member of the same faction, and not an ally faction's member
                    return;
                }

                // a friendly player character has been killed, apply the punishment
                var attackerCharacterPrivateState = PlayerCharacter.GetPrivateState(attackerCharacter);
                var karmaPenalty = KarmaPenaltyPerKill * Math.Min(1, damageFraction * 2.0);

                attackerCharacterPrivateState.ServerFactionKarma -= karmaPenalty;
                Logger.Info(
                    string.Format(
                        "{0} karma decreased by {1:0.##} to {2:0.##} - due to {3:0.##}% recent damage to killed character {4}",
                        attackerCharacter,
                        karmaPenalty,
                        attackerCharacterPrivateState.ServerFactionKarma,
                        Math.Round(damageFraction * 100),
                        killedCharacter));

                // notify about the decreased karma
                Instance.CallClient(attackerCharacter,
                                    _ => _.ClientRemote_OnKarmaDecreased());

                if (attackerCharacterPrivateState.ServerFactionKarma > KarmaFactionKickThreshold)
                {
                    // didn't reach the kick threshold
                    return;
                }

                // that's enough! Bye-bye, killer!
                if (FactionSystem.ServerGetRole(attackerCharacter) == FactionMemberRole.Leader)
                {
                    if (FactionSystem.ServerGetFactionMembersReadOnly(attackerCharacterFaction).Count == 1)
                    {
                        // we cannot kick a faction leader as it's the only member in a faction
                        // but it also means that it didn't kill a faction member but an ally faction's member
                        // just drop the alliance
                        Logger.Important(
                            string.Format(
                                "Leader of the public faction [{0}] with just a single member ({1}) killed a member ({2}) of an ally faction [{3}] and reached a too low karma level - break the alliance",
                                FactionSystem.SharedGetClanTag(attackerCharacterFaction),
                                attackerCharacter,
                                killedCharacter,
                                FactionSystem.SharedGetClanTag(killedCharacterFaction)));

                        FactionSystem.ServerSetFactionDiplomacyStatusNeutral(killedCharacterFaction,
                                                                             attackerCharacterFaction);
                        // reset the karma a bit - restore an equivalent of a half of the karma penalty for killing
                        attackerCharacterPrivateState.ServerFactionKarma += KarmaPenaltyPerKill / 2.0;
                        return;
                    }

                    // pass the faction ownership to a different faction member and kick the killer character
                    var newLeaderName = FactionSystem.ServerGetFactionMembersReadOnly(attackerCharacterFaction)
                                        .FirstOrDefault(m => m.Role != FactionMemberRole.Member &&
                                                        m.Role != FactionMemberRole.Leader)
                                        .Name;

                    if (string.IsNullOrEmpty(newLeaderName))
                    {
                        newLeaderName = FactionSystem.ServerGetFactionMembersReadOnly(attackerCharacterFaction)
                                        .First(m => m.Role != FactionMemberRole.Leader)
                                        .Name;
                    }

                    Logger.Important(
                        string.Format(
                            "Leader of the public faction [{0}] - {1} - killed a friendly player ({2}) and reached a too low karma level - ownership of the faction will be passed to {3}. Player {1} will be kicked from the faction.",
                            FactionSystem.SharedGetClanTag(attackerCharacterFaction),
                            attackerCharacter,
                            killedCharacter,
                            newLeaderName));

                    // It may case a warning message on the client side
                    // as the faction leader is later removed from the faction
                    // but it expects to receive the updated faction information.
                    FactionSystem.ServerTransferFactionOwnership(attackerCharacter, newLeaderName);
                }

                Logger.Important(
                    string.Format(
                        "Member of a public faction [{0}] - {1} - killed a friendly player ({2} from [{3}]) and reached a too low karma level. Player {1} will be kicked from the faction.",
                        FactionSystem.SharedGetClanTag(attackerCharacterFaction),
                        attackerCharacter,
                        killedCharacter,
                        FactionSystem.SharedGetClanTag(killedCharacterFaction)));

                FactionSystem.ServerRemoveMemberFromCurrentFaction(attackerCharacter);

                Instance.CallClient(attackerCharacter,
                                    _ => _.ClientRemote_OnKickedFromTheFactionDueToLowKarma());
            }
        }