diff --git a/common/src/main/scala/net/psforever/objects/Player.scala b/common/src/main/scala/net/psforever/objects/Player.scala index 9e21aee5..17671a60 100644 --- a/common/src/main/scala/net/psforever/objects/Player.scala +++ b/common/src/main/scala/net/psforever/objects/Player.scala @@ -67,6 +67,7 @@ class Player(private val core : Avatar) extends PlanetSideServerObject var lastShotSeq_time : Int = -1 /** From PlanetsideAttributeMessage */ var PlanetsideAttribute : Array[Long] = Array.ofDim(120) + var skipStaminaRegenForTurns : Int = 0 Player.SuitSetup(this, exosuit) @@ -639,6 +640,8 @@ object Player { final val FreeHandSlot : Int = 250 final val HandsDownSlot : Int = 255 + final case class Die() + def apply(core : Avatar) : Player = { new Player(core) } diff --git a/common/src/main/scala/net/psforever/objects/TurretDeployable.scala b/common/src/main/scala/net/psforever/objects/TurretDeployable.scala index dd1f30e5..f7c379f3 100644 --- a/common/src/main/scala/net/psforever/objects/TurretDeployable.scala +++ b/common/src/main/scala/net/psforever/objects/TurretDeployable.scala @@ -162,9 +162,8 @@ object TurretControl { seat.isOccupied && seat.Occupant.get.isAlive }).foreach(seat => { val tplayer = seat.Occupant.get - val tplayerGUID = tplayer.GUID - zone.AvatarEvents ! AvatarServiceMessage(tplayer.Name, AvatarAction.KilledWhileInVehicle(tplayerGUID)) - zone.AvatarEvents ! AvatarServiceMessage(continentId, AvatarAction.ObjectDelete(tplayerGUID, tplayerGUID)) //dead player still sees self + tplayer.History(lastShot) + tplayer.Actor ! Player.Die() }) //vehicle wreckage has no weapons target.Weapons.values diff --git a/common/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala b/common/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala index 6071b018..8dad02b9 100644 --- a/common/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala +++ b/common/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala @@ -1,15 +1,234 @@ -// Copyright (c) 2019 PSForever +// Copyright (c) 2020 PSForever package net.psforever.objects.avatar import akka.actor.Actor import net.psforever.objects.Player +import net.psforever.objects.ballistics.{PlayerSource, ResolvedProjectile, SourceEntry} +import net.psforever.objects.equipment.{JammableBehavior, JammableUnit} +import net.psforever.objects.vital.{PlayerSuicide, Vitality} +import net.psforever.objects.zones.Zone +import net.psforever.packet.game._ +import net.psforever.types.{ExoSuitType, PlanetSideGUID} +import services.Service +import services.avatar.{AvatarAction, AvatarServiceMessage} + +import scala.concurrent.duration._ /** * na; * stub for future development */ -class PlayerControl(player : Player) extends Actor { - def receive : Receive = { +class PlayerControl(player : Player) extends Actor + with JammableBehavior { + def JammableObject = player + + def receive : Receive = jammableBehavior.orElse { + case Player.Die() => + PlayerControl.HandleDestructionAwareness(player, player.GUID, None) + + case Vitality.Damage(resolution_function) => + if(player.isAlive) { + val originalHealth = player.Health + val originalArmor = player.Armor + val originalCapacitor = player.Capacitor.toInt + val cause = resolution_function(player) + val health = player.Health + val armor = player.Armor + val capacitor = player.Capacitor.toInt + val damageToHealth = originalHealth - health + val damageToArmor = originalArmor - armor + val damageToCapacitor = originalCapacitor - capacitor + PlayerControl.HandleDamageResolution(player, cause, damageToHealth, damageToArmor, damageToCapacitor) + if(damageToHealth != 0 || damageToArmor != 0 || damageToCapacitor != 0) { + org.log4s.getLogger("DamageResolution") + .info(s"${player.Name}-infantry: BEFORE=$originalHealth/$originalArmor/$originalCapacitor, AFTER=$health/$armor/$capacitor, CHANGE=$damageToHealth/$damageToArmor/$damageToCapacitor") + } + } + case _ => ; + } + + /** + * Start the jammered buzzing. + * Although, as a rule, the jammering sound effect should last as long as the jammering status, + * Infantry seem to hear the sound for a bit longer than the effect. + * @see `JammableBehavior.StartJammeredSound` + * @param target an object that can be affected by the jammered status + * @param dur the duration of the timer, in milliseconds; + * by default, 30000 + */ + override def StartJammeredSound(target : Any, dur : Int) : Unit = target match { + case obj : Player if !jammedSound => + obj.Zone.AvatarEvents ! AvatarServiceMessage(obj.Zone.Id, AvatarAction.PlanetsideAttributeToAll(obj.GUID, 27, 1)) + super.StartJammeredSound(obj, 3000) + case _ => ; + } + + /** + * Perform a variety of tasks to indicate being jammered. + * Deactivate implants (should also uninitialize them), + * delay stamina regeneration for a certain number of turns, + * and set the jammered status on specific holstered equipment. + * @see `JammableBehavior.StartJammeredStatus` + * @param target an object that can be affected by the jammered status + * @param dur the duration of the timer, in milliseconds + */ + override def StartJammeredStatus(target : Any, dur : Int) : Unit = target match { + case obj : Player => + //TODO these features + obj.Zone.AvatarEvents ! AvatarServiceMessage(obj.Zone.Id, AvatarAction.DeactivateImplantSlot(obj.GUID, 1)) + obj.Zone.AvatarEvents ! AvatarServiceMessage(obj.Zone.Id, AvatarAction.DeactivateImplantSlot(obj.GUID, 2)) + obj.skipStaminaRegenForTurns = math.max(obj.skipStaminaRegenForTurns, 10) + super.StartJammeredStatus(target, dur) + case _ => ; + } + + /** + * Stop the jammered buzzing. + * @see `JammableBehavior.CancelJammeredSound` + * @param target an object that can be affected by the jammered status + */ + override def CancelJammeredSound(target : Any) : Unit = target match { + case obj : Player if jammedSound => + obj.Zone.AvatarEvents ! AvatarServiceMessage(obj.Zone.Id, AvatarAction.PlanetsideAttributeToAll(obj.GUID, 27, 0)) + super.CancelJammeredSound(obj) case _ => ; } } + +object PlayerControl { + /** + * na + * @param target na + */ + def HandleDamageResolution(target : Player, cause : ResolvedProjectile, damageToHealth : Int, damageToArmor : Int, damageToCapacitor : Int) : Unit = { + val targetGUID = target.GUID + val playerGUID = target.Zone.LivePlayers.find { p => cause.projectile.owner.Name.equals(p.Name) } match { + case Some(player) => player.GUID + case _ => PlanetSideGUID(0) + } + if(target.Health > 0) { + //activity on map + if(damageToHealth + damageToArmor > 0) { + target.Zone.Activity ! Zone.HotSpot.Activity(cause.target, cause.projectile.owner, cause.hit_pos) + //alert damage source + HandleDamageAwareness(target, playerGUID, cause) + } + if(cause.projectile.profile.JammerProjectile) { + target.Actor ! JammableUnit.Jammered(cause) + } + } + else { + HandleDestructionAwareness(target, playerGUID, Some(cause)) + } + if(damageToHealth > 0) { + target.Zone.AvatarEvents ! AvatarServiceMessage(target.Zone.Id, AvatarAction.PlanetsideAttributeToAll(targetGUID, 0, target.Health)) + } + if(damageToArmor > 0) { + target.Zone.AvatarEvents ! AvatarServiceMessage(target.Zone.Id, AvatarAction.PlanetsideAttributeToAll(targetGUID, 4, target.Armor)) + } + if(damageToCapacitor > 0) { + target.Zone.AvatarEvents ! AvatarServiceMessage(target.Name, AvatarAction.PlanetsideAttributeSelf(targetGUID, 7, target.Capacitor.toLong)) + } + } + + /** + * na + * @param target na + * @param attribution na + * @param lastShot na + */ + def HandleDamageAwareness(target : Player, attribution : PlanetSideGUID, lastShot : ResolvedProjectile) : Unit = { + val owner = lastShot.projectile.owner + owner match { + case pSource : PlayerSource => + target.Zone.LivePlayers.find(_.Name == pSource.Name) match { + case Some(tplayer) => + target.Zone.AvatarEvents ! AvatarServiceMessage( + target.Name, + AvatarAction.HitHint(tplayer.GUID, target.GUID) + ) + case None => ; + } + case vSource : SourceEntry => + target.Zone.AvatarEvents ! AvatarServiceMessage( + target.Name, + AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(10, vSource.Position)) + ) + case _ => ; + } + } + + /** + * The player has lost all his vitality and must be killed.
+ *
+ * Shift directly into a state of being dead on the client by setting health to zero points, + * whereupon the player will perform a dramatic death animation. + * Stamina is also set to zero points. + * If the player was in a vehicle at the time of demise, special conditions apply and + * the model must be manipulated so it behaves correctly. + * Do not move or completely destroy the `Player` object as its coordinates of death will be important.
+ *
+ * A maximum revive waiting timer is started. + * When this timer reaches zero, the avatar will attempt to spawn back on its faction-specific sanctuary continent. + * @param target na + * @param attribution na + * @param lastShot na + */ + def HandleDestructionAwareness(target : Player, attribution : PlanetSideGUID, lastShot : Option[ResolvedProjectile]) : Unit = { + val player_guid = target.GUID + val pos = target.Position + val respawnTimer = 300000 //milliseconds + val zone = target.Zone + val events = zone.AvatarEvents + val nameChannel = target.Name + val zoneChannel = zone.Id + target.Die + target.Actor ! JammableUnit.ClearJammeredSound() + target.Actor ! JammableUnit.ClearJammeredStatus() + events ! AvatarServiceMessage(nameChannel, AvatarAction.Killed(player_guid)) //align client interface fields with state + if(target.VehicleSeated.nonEmpty) { + //make player invisible (if not, the cadaver sticks out the side in a seated position) + events ! AvatarServiceMessage(nameChannel, AvatarAction.PlanetsideAttributeToAll(player_guid, 29, 1)) + //only the dead player should "see" their own body, so that the death camera has something to focus on + events ! AvatarServiceMessage(zoneChannel, AvatarAction.ObjectDelete(player_guid, player_guid)) + } + events ! AvatarServiceMessage(zoneChannel, AvatarAction.PlanetsideAttributeToAll(player_guid, 0, 0)) //health + events ! AvatarServiceMessage(nameChannel, AvatarAction.PlanetsideAttributeToAll(player_guid, 2, 0)) //stamina + events ! AvatarServiceMessage(zoneChannel, AvatarAction.PlanetsideAttributeToAll(player_guid, 4, target.Armor)) //armor + if(target.ExoSuit == ExoSuitType.MAX) { + events ! AvatarServiceMessage(nameChannel, AvatarAction.PlanetsideAttributeToAll(player_guid, 7, 0)) // capacitor + } + events ! AvatarServiceMessage( + nameChannel, + AvatarAction.SendResponse(Service.defaultPlayerGUID, DestroyMessage(player_guid, player_guid, Service.defaultPlayerGUID, pos)) //how many players get this message? + ) + events ! AvatarServiceMessage( + nameChannel, + AvatarAction.SendResponse(Service.defaultPlayerGUID, AvatarDeadStateMessage(DeadState.Dead, respawnTimer, respawnTimer, pos, target.Faction, true)) + ) + //TODO other methods of death? + val pentry = PlayerSource(target) + (target.History.find({p => p.isInstanceOf[PlayerSuicide]}) match { + case Some(PlayerSuicide(_)) => + None + case _ => + lastShot.orElse { target.LastShot } match { + case out @ Some(shot) => + if(System.nanoTime - shot.hit_time < (10 seconds).toNanos) { + out + } + else { + None //suicide + } + case None => + None //suicide + } + }) match { + case Some(shot) => + zone.Activity ! Zone.HotSpot.Activity(pentry, shot.projectile.owner, shot.hit_pos) + events ! AvatarServiceMessage(zoneChannel, AvatarAction.DestroyDisplay(shot.projectile.owner, pentry, shot.projectile.attribute_to)) + case None => + events ! AvatarServiceMessage(zoneChannel, AvatarAction.DestroyDisplay(pentry, pentry, 0)) + } + } +} diff --git a/common/src/main/scala/net/psforever/objects/equipment/JammingUnit.scala b/common/src/main/scala/net/psforever/objects/equipment/JammingUnit.scala index 874474a0..aeb045d1 100644 --- a/common/src/main/scala/net/psforever/objects/equipment/JammingUnit.scala +++ b/common/src/main/scala/net/psforever/objects/equipment/JammingUnit.scala @@ -148,7 +148,7 @@ trait JammableBehavior { jammedSound = true import scala.concurrent.ExecutionContext.Implicits.global jammeredSoundTimer.cancel - jammeredSoundTimer = context.system.scheduler.scheduleOnce(dur seconds, self, JammableUnit.ClearJammeredSound()) + jammeredSoundTimer = context.system.scheduler.scheduleOnce(dur milliseconds, self, JammableUnit.ClearJammeredSound()) } } diff --git a/common/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretControl.scala b/common/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretControl.scala index b65cd6fa..0b737390 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretControl.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretControl.scala @@ -2,6 +2,7 @@ package net.psforever.objects.serverobject.turret import akka.actor.Actor +import net.psforever.objects.Player import net.psforever.objects.ballistics.ResolvedProjectile import net.psforever.objects.equipment.{JammableMountedWeapons, JammableUnit} import net.psforever.objects.serverobject.mount.{Mountable, MountableBehavior} @@ -130,9 +131,8 @@ object FacilityTurretControl { seat.isOccupied && seat.Occupant.get.isAlive }).foreach(seat => { val tplayer = seat.Occupant.get - val tplayerGUID = tplayer.GUID - zone.AvatarEvents ! AvatarServiceMessage(tplayer.Name, AvatarAction.KilledWhileInVehicle(tplayerGUID)) - zone.AvatarEvents ! AvatarServiceMessage(zoneId, AvatarAction.ObjectDelete(tplayerGUID, tplayerGUID)) //dead player still sees self + tplayer.History(lastShot) + tplayer.Actor ! Player.Die() }) //turret wreckage has no weapons // target.Weapons.values diff --git a/common/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala b/common/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala index be7b3ebb..1b0800f0 100644 --- a/common/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala +++ b/common/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala @@ -2,7 +2,7 @@ package net.psforever.objects.vehicles import akka.actor.{Actor, ActorRef} -import net.psforever.objects.{GlobalDefinitions, Vehicle} +import net.psforever.objects.{GlobalDefinitions, Player, Vehicle} import net.psforever.objects.ballistics.{ResolvedProjectile, VehicleSource} import net.psforever.objects.equipment.{JammableMountedWeapons, JammableUnit} import net.psforever.objects.serverobject.mount.{Mountable, MountableBehavior} @@ -226,9 +226,8 @@ object VehicleControl { seat.isOccupied && seat.Occupant.get.isAlive }).foreach(seat => { val tplayer = seat.Occupant.get - val tplayerGUID = tplayer.GUID - zone.AvatarEvents ! AvatarServiceMessage(tplayer.Name, AvatarAction.KilledWhileInVehicle(tplayerGUID)) - zone.AvatarEvents ! AvatarServiceMessage(continentId, AvatarAction.ObjectDelete(tplayerGUID, tplayerGUID)) //dead player still sees self + tplayer.History(lastShot) + tplayer.Actor ! Player.Die() }) //vehicle wreckage has no weapons target.Weapons.values diff --git a/common/src/main/scala/services/avatar/AvatarService.scala b/common/src/main/scala/services/avatar/AvatarService.scala index 9af81e4a..9799d8c7 100644 --- a/common/src/main/scala/services/avatar/AvatarService.scala +++ b/common/src/main/scala/services/avatar/AvatarService.scala @@ -70,10 +70,10 @@ class AvatarService(zone : Zone) extends Actor { AvatarEvents.publish( AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.EnvironmentalDamage(player_guid, source_guid, amount)) ) - case AvatarAction.Damage(player_guid, target, resolution_function) => + case AvatarAction.DeactivateImplantSlot(player_guid, slot) => AvatarEvents.publish( - AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.DamageResolution(target, resolution_function)) - ) + AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.DeactivateImplantSlot(slot)) + ) case AvatarAction.DeployItem(player_guid, item) => val definition = item.Definition val objectData = definition.Packet.ConstructorData(item).get @@ -119,9 +119,9 @@ class AvatarService(zone : Zone) extends Actor { AvatarEvents.publish( AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.HitHint(source_guid)) ) - case AvatarAction.KilledWhileInVehicle(player_guid) => + case AvatarAction.Killed(player_guid) => AvatarEvents.publish( - AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.KilledWhileInVehicle()) + AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.Killed()) ) case AvatarAction.LoadPlayer(player_guid, object_id, target_guid, cdata, pdata) => val pkt = pdata match { diff --git a/common/src/main/scala/services/avatar/AvatarServiceMessage.scala b/common/src/main/scala/services/avatar/AvatarServiceMessage.scala index 24026e68..f6156a25 100644 --- a/common/src/main/scala/services/avatar/AvatarServiceMessage.scala +++ b/common/src/main/scala/services/avatar/AvatarServiceMessage.scala @@ -6,7 +6,6 @@ import net.psforever.objects.ballistics.{Projectile, SourceEntry} import net.psforever.objects.ce.Deployable import net.psforever.objects.equipment.Equipment import net.psforever.objects.inventory.Container -import net.psforever.objects.vital.resolution.ResolutionCalculations import net.psforever.objects.zones.Zone import net.psforever.packet.PlanetSideGamePacket import net.psforever.packet.game.objectcreate.{ConstructorData, ObjectCreateMessageParent} @@ -31,15 +30,15 @@ object AvatarAction { final case class ChangeFireState_Stop(player_guid : PlanetSideGUID, weapon_guid : PlanetSideGUID) extends Action final case class ConcealPlayer(player_guid : PlanetSideGUID) extends Action final case class EnvironmentalDamage(player_guid : PlanetSideGUID, source_guid : PlanetSideGUID, amount: Int) extends Action - final case class Damage(player_guid : PlanetSideGUID, target : Player, resolution_function : ResolutionCalculations.Output) extends Action final case class DeployItem(player_guid : PlanetSideGUID, item : PlanetSideGameObject with Deployable) extends Action + final case class DeactivateImplantSlot(player_guid : PlanetSideGUID, slot : Int) extends Action final case class Destroy(victim : PlanetSideGUID, killer : PlanetSideGUID, weapon : PlanetSideGUID, pos : Vector3) extends Action final case class DestroyDisplay(killer : SourceEntry, victim : SourceEntry, method : Int, unk : Int = 121) extends Action final case class DropItem(player_guid : PlanetSideGUID, item : Equipment, zone : Zone) extends Action final case class EquipmentInHand(player_guid : PlanetSideGUID, target_guid : PlanetSideGUID, slot : Int, item : Equipment) extends Action final case class GenericObjectAction(player_guid : PlanetSideGUID, object_guid : PlanetSideGUID, action_code : Int) extends Action final case class HitHint(source_guid : PlanetSideGUID, player_guid : PlanetSideGUID) extends Action - final case class KilledWhileInVehicle(player_guid : PlanetSideGUID) extends Action + final case class Killed(player_guid : PlanetSideGUID) extends Action final case class LoadPlayer(player_guid : PlanetSideGUID, object_id : Int, target_guid : PlanetSideGUID, cdata : ConstructorData, pdata : Option[ObjectCreateMessageParent]) extends Action final case class LoadProjectile(player_guid : PlanetSideGUID, object_id : Int, projectile_guid : PlanetSideGUID, cdata : ConstructorData) extends Action final case class ObjectDelete(player_guid : PlanetSideGUID, item_guid : PlanetSideGUID, unk : Int = 0) extends Action diff --git a/common/src/main/scala/services/avatar/AvatarServiceResponse.scala b/common/src/main/scala/services/avatar/AvatarServiceResponse.scala index d38815e4..84fd846a 100644 --- a/common/src/main/scala/services/avatar/AvatarServiceResponse.scala +++ b/common/src/main/scala/services/avatar/AvatarServiceResponse.scala @@ -4,7 +4,6 @@ package services.avatar import net.psforever.objects.Player import net.psforever.objects.ballistics.{Projectile, SourceEntry} import net.psforever.objects.equipment.Equipment -import net.psforever.objects.vital.resolution.ResolutionCalculations import net.psforever.packet.PlanetSideGamePacket import net.psforever.packet.game.objectcreate.ConstructorData import net.psforever.packet.game.ObjectCreateMessage @@ -26,14 +25,14 @@ object AvatarResponse { final case class ChangeFireState_Stop(weapon_guid : PlanetSideGUID) extends Response final case class ConcealPlayer() extends Response final case class EnvironmentalDamage(target : PlanetSideGUID, source_guid : PlanetSideGUID, amount : Int) extends Response - final case class DamageResolution(target : Player, resolution_function : ResolutionCalculations.Output) extends Response + final case class DeactivateImplantSlot(slot : Int) extends Response final case class Destroy(victim : PlanetSideGUID, killer : PlanetSideGUID, weapon : PlanetSideGUID, pos : Vector3) extends Response final case class DestroyDisplay(killer : SourceEntry, victim : SourceEntry, method : Int, unk : Int) extends Response final case class DropItem(pkt : ObjectCreateMessage) extends Response final case class EquipmentInHand(pkt : ObjectCreateMessage) extends Response final case class GenericObjectAction(object_guid : PlanetSideGUID, action_code : Int) extends Response final case class HitHint(source_guid : PlanetSideGUID) extends Response - final case class KilledWhileInVehicle() extends Response + final case class Killed() extends Response final case class LoadPlayer(pkt : ObjectCreateMessage) extends Response final case class LoadProjectile(pkt : ObjectCreateMessage) extends Response final case class ObjectDelete(item_guid : PlanetSideGUID, unk : Int) extends Response diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index 7fe05cf9..fb549bf2 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -77,8 +77,7 @@ import services.support.SupportActor import scala.collection.mutable class WorldSessionActor extends Actor - with MDCContextAware - with JammableBehavior { + with MDCContextAware { import WorldSessionActor._ private[this] val log = org.log4s.getLogger @@ -164,7 +163,6 @@ class WorldSessionActor extends Actor var timeDL : Long = 0 var timeSurge : Long = 0 - var skipStaminaRegenForTurns : Int = 0 lazy val unsignedIntMaxValue : Long = Int.MaxValue.toLong * 2L + 1L var serverTime : Long = 0 @@ -332,7 +330,7 @@ class WorldSessionActor extends Actor context.stop(self) } - def Started : Receive = jammableBehavior.orElse { + def Started : Receive = { case ServiceManager.LookupResult("accountIntermediary", endpoint) => accountIntermediary = endpoint log.info("ID: " + sessionId + " Got account intermediary service " + endpoint) @@ -1361,67 +1359,16 @@ class WorldSessionActor extends Actor continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(target, 0, player.Health)) damageLog.info(s"${player.Name}-infantry: BEFORE=$originalHealth/$armor/$capacitor, AFTER=${player.Health}/$armor/$capacitor, CHANGE=$amount/0/0") if(player.Health == 0 && player.isAlive) { - KillPlayer(player) + player.Actor ! Player.Die() } } - case AvatarResponse.DamageResolution(target, resolution_function) => - if(player.isAlive) { - val originalHealth = player.Health - val originalArmor = player.Armor - val originalCapacitor = player.Capacitor.toInt - val cause = resolution_function(target) - val health = player.Health - val armor = player.Armor - val capacitor = player.Capacitor.toInt - val damageToHealth = originalHealth - health - val damageToArmor = originalArmor - armor - val damageToCapacitor = originalCapacitor - capacitor - if(damageToHealth != 0 || damageToArmor != 0 || damageToCapacitor != 0) { - damageLog.info(s"${player.Name}-infantry: BEFORE=$originalHealth/$originalArmor/$originalCapacitor, AFTER=$health/$armor/$capacitor, CHANGE=$damageToHealth/$damageToArmor/$damageToCapacitor") - val playerGUID = player.GUID - if(damageToHealth > 0) { - sendResponse(PlanetsideAttributeMessage(playerGUID, 0, health)) - continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(playerGUID, 0, health)) - } - if(damageToArmor > 0) { - sendResponse(PlanetsideAttributeMessage(playerGUID, 4, armor)) - continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(playerGUID, 4, armor)) - } - if(damageToCapacitor > 0) { - sendResponse(PlanetsideAttributeMessage(playerGUID, 7, capacitor)) - } - sendResponse(PlanetsideAttributeMessage(playerGUID, 0, health)) - sendResponse(PlanetsideAttributeMessage(playerGUID, 4, armor)) - continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(playerGUID, 0, health)) - continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(playerGUID, 4, armor)) - if(health == 0 && player.isAlive) { - KillPlayer(player) - } - else { - //damage cause -> recent damage source -> killing blow? - cause match { - case data : ResolvedProjectile => - val owner = data.projectile.owner - owner match { - case pSource : PlayerSource => - continent.LivePlayers.find(_.Name == pSource.Name) match { - case Some(tplayer) => - sendResponse(HitHint(tplayer.GUID, player.GUID)) - case None => ; - } - case vSource : SourceEntry => - sendResponse(DamageWithPositionMessage(damageToHealth + damageToArmor, vSource.Position)) - case _ => ; - } - continent.Activity ! Zone.HotSpot.Activity(owner, data.target, target.Position) - case _ => ; - } - } - } - if(target.isAlive && cause.projectile.profile.JammerProjectile) { - self ! JammableUnit.Jammered(cause) - } + case AvatarResponse.DeactivateImplantSlot(slot) => + //temporary solution until implants are finalized + slot match { + case 1 => DeactivateImplantDarkLight() + case 2 => DeactivateImplantSurge() + case _ => ; } case AvatarResponse.Destroy(victim, killer, weapon, pos) => @@ -1451,33 +1398,25 @@ class WorldSessionActor extends Actor sendResponse(HitHint(source_guid, guid)) } - case AvatarResponse.KilledWhileInVehicle() => - if(player.isAlive && player.VehicleSeated.nonEmpty) { - (continent.GUID(player.VehicleSeated) match { - case Some(obj : Vehicle) => - if(obj.Health == 0) Some(obj) - else None - case Some(obj : TurretDeployable) => - if(obj.Health == 0) Some(obj) - else None - case Some(obj : FacilityTurret) => - if(obj.Health == 1) Some(obj) //TODO proper turret death at 0 health - else None - case _ => - None - }) match { - case Some(obj : PlanetSideGameObject with Vitality) => - obj.LastShot match { - case Some(cause) => - player.History(cause) - KillPlayer(player) - case None => ; - } - case _ => - log.warn(s"${player.Name} was seated in a vehicle and should have been killed, but was not; suicidal fallback") - Suicide(player) - } + case AvatarResponse.Killed() => + val respawnTimer = 300000 //milliseconds + ToggleMaxSpecialState(enable = false) + deadState = DeadState.Dead + timeDL = 0 + timeSurge = 0 + continent.GUID(player.VehicleSeated) match { + case Some(obj : Vehicle) => + TotalDriverVehicleControl(obj) + UnAccessContents(obj) + case _ => ; } + PlayerActionsToCancel() + if(shotsWhileDead > 0) { + log.warn(s"KillPlayer/SHOTS_WHILE_DEAD: client of ${avatar.name} fired $shotsWhileDead rounds while character was dead on server") + shotsWhileDead = 0 + } + import scala.concurrent.ExecutionContext.Implicits.global + reviveTimer = context.system.scheduler.scheduleOnce(respawnTimer milliseconds, cluster, Zone.Lattice.RequestSpawnPoint(Zones.SanctuaryZoneNumber(player.Faction), player, 7)) case AvatarResponse.LoadPlayer(pkt) => if(tplayer_guid != guid) { @@ -3959,14 +3898,14 @@ class WorldSessionActor extends Actor } } } - if(skipStaminaRegenForTurns > 0) { + if(player.skipStaminaRegenForTurns > 0) { //do not renew stamina for a while - skipStaminaRegenForTurns -= 1 + player.skipStaminaRegenForTurns -= 1 player.Stamina > 0 } else if(player.Stamina == 0 && hadStaminaBefore) { //if the player lost all stamina this turn (had stamina at the start), do not renew stamina for a while - skipStaminaRegenForTurns = 4 + player.skipStaminaRegenForTurns = 4 player.Stamina > 0 } else if(isMovingPlus || player.Stamina == player.MaxStamina) { @@ -4579,7 +4518,7 @@ class WorldSessionActor extends Actor case msg @ AvatarJumpMessage(state) => //log.info("AvatarJump: " + msg) player.Stamina = player.Stamina - 10 - skipStaminaRegenForTurns = 5 + player.skipStaminaRegenForTurns = math.max(player.skipStaminaRegenForTurns, 5) sendResponse(PlanetsideAttributeMessage(player.GUID, 2, player.Stamina)) case msg @ ZipLineMessage(player_guid,origin_side,action,id,pos) => @@ -5533,15 +5472,6 @@ class WorldSessionActor extends Actor case _ => ; } - case msg @ WeaponJammedMessage(weapon_guid) => - FindWeapon match { - case Some(tool : Tool) => - log.info(s"WeaponJammed: ${tool.Definition.Name}@${weapon_guid.guid}") - //TODO - case _ => - log.info(s"WeaponJammed: ${weapon_guid.guid}") - } - case msg @ WeaponFireMessage(seq_time, weapon_guid, projectile_guid, shot_origin, unk1, unk2, unk3, unk4, unk5, unk6, unk7) => log.info(s"WeaponFire: $msg") if(player.isShielded) { @@ -7881,80 +7811,7 @@ class WorldSessionActor extends Actor */ def Suicide(tplayer : Player) : Unit = { tplayer.History(PlayerSuicide(PlayerSource(tplayer))) - KillPlayer(tplayer) - } - - /** - * The player has lost all his vitality and must be killed.
- *
- * Shift directly into a state of being dead on the client by setting health to zero points, - * whereupon the player will perform a dramatic death animation. - * Stamina is also set to zero points. - * If the player was in a vehicle at the time of demise, special conditions apply and - * the model must be manipulated so it behaves correctly. - * Do not move or completely destroy the `Player` object as its coordinates of death will be important.
- *
- * A maximum revive waiting timer is started. - * When this timer reaches zero, the avatar will attempt to spawn back on its faction-specific sanctuary continent. - * @param tplayer the player to be killed - */ - def KillPlayer(tplayer : Player) : Unit = { - val player_guid = tplayer.GUID - val pos = tplayer.Position - val respawnTimer = 300000 //milliseconds - ToggleMaxSpecialState(enable = false) - tplayer.Die - deadState = DeadState.Dead - timeDL = 0 - timeSurge = 0 - sendResponse(PlanetsideAttributeMessage(player_guid, 0, 0)) - sendResponse(PlanetsideAttributeMessage(player_guid, 2, 0)) - continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(player_guid, 0, 0)) - sendResponse(DestroyMessage(player_guid, player_guid, PlanetSideGUID(0), pos)) //how many players get this message? - sendResponse(AvatarDeadStateMessage(DeadState.Dead, respawnTimer, respawnTimer, pos, player.Faction, true)) - if(tplayer.VehicleSeated.nonEmpty) { - continent.GUID(tplayer.VehicleSeated.get) match { - case Some(obj : Vehicle) => - TotalDriverVehicleControl(obj) - UnAccessContents(obj) - case _ => ; - } - //make player invisible (if not, the cadaver sticks out the side in a seated position) - sendResponse(PlanetsideAttributeMessage(player_guid, 29, 1)) - continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(player_guid, 29, 1)) - } - PlayerActionsToCancel() - //TODO other methods of death? - val pentry = PlayerSource(tplayer) - (tplayer.History.find({p => p.isInstanceOf[PlayerSuicide]}) match { - case Some(PlayerSuicide(_)) => - None - case _ => - tplayer.LastShot match { - case Some(shot) => - if(System.nanoTime - shot.hit_time < (10 seconds).toNanos) { - Some(shot) - } - else { - None //suicide - } - case None => - None //suicide - } - }) match { - case Some(shot) => - continent.Activity ! Zone.HotSpot.Activity(pentry, shot.projectile.owner, shot.hit_pos) - continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.DestroyDisplay(shot.projectile.owner, pentry, shot.projectile.attribute_to)) - case None => - continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.DestroyDisplay(pentry, pentry, 0)) - } - if(shotsWhileDead > 0) { - log.warn(s"KillPlayer/SHOTS_WHILE_DEAD: client of ${avatar.name} fired $shotsWhileDead rounds while character was dead on server") - shotsWhileDead = 0 - } - - import scala.concurrent.ExecutionContext.Implicits.global - reviveTimer = context.system.scheduler.scheduleOnce(respawnTimer milliseconds, cluster, Zone.Lattice.RequestSpawnPoint(Zones.SanctuaryZoneNumber(tplayer.Faction), tplayer, 7)) + tplayer.Actor ! Player.Die() } /** @@ -7968,12 +7825,10 @@ class WorldSessionActor extends Actor * This is not a complete list but, for the purpose of enforcement, some pointers will be documented here. */ def PlayerActionsToCancel() : Unit = { - CancelJammeredSound(player) - CancelJammeredStatus(player) progressBarUpdate.cancel progressBarValue = None lastTerminalOrderFulfillment = true - skipStaminaRegenForTurns = 0 + player.skipStaminaRegenForTurns = 0 accessedContainer match { case Some(obj : Vehicle) => if(obj.AccessingTrunk.contains(player.GUID)) { @@ -8681,6 +8536,8 @@ class WorldSessionActor extends Actor * using the information reconstructed from a `Resolvedprojectile` * and affect the `target` in a synchronized manner. * The active `target` and the target of the `ResolvedProjectile` do not have be the same. + * While the "tell" for being able to sustain damage is an entity of type `Vitality`, + * only specific `Vitality` entity types are being screened for sustaining damage. * @see `DamageResistanceModel`
* `Vitality` * @param target a valid game object that is known to the server @@ -8689,10 +8546,7 @@ class WorldSessionActor extends Actor def HandleDealingDamage(target : PlanetSideGameObject with Vitality, data : ResolvedProjectile) : Unit = { val func = data.damage_model.Calculate(data) target match { - case obj : Player => - //damage is synchronized on the target player's `WSA` (results distributed from there) - continent.AvatarEvents ! AvatarServiceMessage(obj.Name, AvatarAction.Damage(player.GUID, obj, func)) - + case obj : Player => obj.Actor ! Vitality.Damage(func) case obj : Vehicle => obj.Actor ! Vitality.Damage(func) case obj : FacilityTurret => obj.Actor ! Vitality.Damage(func) case obj : ComplexDeployable => obj.Actor ! Vitality.Damage(func) @@ -10969,7 +10823,7 @@ class WorldSessionActor extends Actor * This method is intended to support only the current Live server implants. */ def DeactivateImplantDarkLight() : Unit = { - if(avatar.Implants(0).Active) { + if(avatar.Implants(0).Active && avatar.Implants(0).Implant == ImplantType.DarklightVision) { avatar.Implants(0).Active = false continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(player.GUID, 28, avatar.Implant(0).id * 2)) sendResponse(AvatarImplantMessage(PlanetSideGUID(player.GUID.guid), ImplantAction.Activation, 0, 0)) @@ -10982,7 +10836,7 @@ class WorldSessionActor extends Actor * This method is intended to support only the current Live server implants. */ def DeactivateImplantSurge() : Unit = { - if(avatar.Implants(1).Active) { + if(avatar.Implants(1).Active && avatar.Implants(0).Implant == ImplantType.Surge) { avatar.Implants(1).Active = false continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(player.GUID, 28, avatar.Implant(1).id * 2)) sendResponse(AvatarImplantMessage(PlanetSideGUID(player.GUID.guid), ImplantAction.Activation, 1, 0)) @@ -10990,53 +10844,6 @@ class WorldSessionActor extends Actor } } - /** - * Start the jammered buzzing. - * Although, as a rule, the jammering sound effect should last as long as the jammering status, - * Infantry seem to hear the sound for a bit longer than the effect. - * @see `JammableHevaior.StartJammeredSound` - * @param target an object that can be affected by the jammered status - * @param dur the duration of the timer, in milliseconds; - * by default, 30000 - */ - override def StartJammeredSound(target : Any, dur : Int) : Unit = target match { - case obj : Player if !jammedSound => - sendResponse(PlanetsideAttributeMessage(obj.GUID, 27, 1)) - continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(obj.GUID, 27, 1)) - super.StartJammeredSound(obj, 3000) - case _ => ; - } - - /** - * Perform a variety of tasks to indicate being jammered. - * Deactivate implants (should also uninitialize them), - * delay stamina regeneration for a certain number of turns, - * and set the jammered status on specific holstered equipment. - * @see `JammableHevaior.StartJammeredStatus` - * @param target an object that can be affected by the jammered status - * @param dur the duration of the timer, in milliseconds - */ - override def StartJammeredStatus(target : Any, dur : Int) : Unit = target match { - case obj : Player => - DeactivateImplants() - skipStaminaRegenForTurns = 10 - super.StartJammeredStatus(target, dur) - case _ => ; - } - - /** - * Stop the jammered buzzing. - * @see `JammableHevaior.CancelJammeredSound` - * @param target an object that can be affected by the jammered status - */ - override def CancelJammeredSound(target : Any) : Unit = target match { - case obj : Player if jammedSound => - sendResponse(PlanetsideAttributeMessage(obj.GUID, 27, 0)) - continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(obj.GUID, 27, 0)) - super.CancelJammeredSound(obj) - case _ => ; - } - def failWithError(error : String) = { log.error(error) sendResponse(ConnectionClose())