/// <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; }
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 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); }
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); } }
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); } }
/// <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)); }
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); } }
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); }
/// <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()); } }