diff --git a/src/main/scala/net/psforever/actors/session/AvatarActor.scala b/src/main/scala/net/psforever/actors/session/AvatarActor.scala
index e06299a48..19ec2e8bb 100644
--- a/src/main/scala/net/psforever/actors/session/AvatarActor.scala
+++ b/src/main/scala/net/psforever/actors/session/AvatarActor.scala
@@ -10,6 +10,7 @@ import net.psforever.actors.zone.ZoneActor
import net.psforever.objects.avatar.scoring.{Assist, Death, EquipmentStat, KDAStat, Kill}
import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.vital.InGameHistory
+import net.psforever.objects.vehicles.MountedWeapons
import org.joda.time.{LocalDateTime, Seconds}
import scala.collection.mutable
@@ -2949,14 +2950,16 @@ class AvatarActor(
kdaStat match {
case kill: Kill =>
val playerSource = PlayerSource(player)
- (kill.info.interaction.cause match {
- case pr: ProjectileReason => pr.projectile.mounted_in.map { a => zone.GUID(a._1) }
+ val historyTranscript = (kill.info.interaction.cause match {
+ case pr: ProjectileReason => pr.projectile.mounted_in.flatMap { a => zone.GUID(a._1) } //what fired the projectile
case _ => None
- }).collect {
- case Some(mount: PlanetSideGameObject with FactionAffinity with InGameHistory) =>
- player.ContributionFrom(mount)
+ }) match {
+ case Some(mount: PlanetSideGameObject with FactionAffinity with InGameHistory with MountedWeapons) =>
+ player.HistoryAndContributions() ++ InGameHistory.ContributionFrom(mount)
+ case None =>
+ player.HistoryAndContributions()
}
- zone.actor ! ZoneActor.RewardOurSupporters(playerSource, player.HistoryAndContributions(), kill, exp)
+ zone.actor ! ZoneActor.RewardOurSupporters(playerSource, historyTranscript, kill, exp)
case _: Assist =>
()
case _: Death =>
diff --git a/src/main/scala/net/psforever/actors/session/support/SessionData.scala b/src/main/scala/net/psforever/actors/session/support/SessionData.scala
index 2aa1feb42..51fa82a6e 100644
--- a/src/main/scala/net/psforever/actors/session/support/SessionData.scala
+++ b/src/main/scala/net/psforever/actors/session/support/SessionData.scala
@@ -3,7 +3,7 @@ package net.psforever.actors.session.support
import akka.actor.typed.scaladsl.adapter._
import akka.actor.{ActorContext, ActorRef, Cancellable, typed}
-import net.psforever.objects.sourcing.{PlayerSource, SourceEntry}
+import net.psforever.objects.sourcing.{PlayerSource, SourceEntry, VehicleSource}
import net.psforever.objects.zones.blockmap.{SectorGroup, SectorPopulation}
import scala.collection.mutable
@@ -411,10 +411,10 @@ class SessionData(
/* line 2: vehicle is not mounted in anything or, if it is, its seats are empty */
if (
(session.account.gm ||
- (player.avatar.vehicle.contains(objectGuid) && vehicle.Owner.contains(player.GUID)) ||
+ (player.avatar.vehicle.contains(objectGuid) && vehicle.OwnerGuid.contains(player.GUID)) ||
(player.Faction == vehicle.Faction &&
(vehicle.Definition.CanBeOwned.nonEmpty &&
- (vehicle.Owner.isEmpty || continent.GUID(vehicle.Owner.get).isEmpty) || vehicle.Destroyed))) &&
+ (vehicle.OwnerGuid.isEmpty || continent.GUID(vehicle.OwnerGuid.get).isEmpty) || vehicle.Destroyed))) &&
(vehicle.MountedIn.isEmpty || !vehicle.Seats.values.exists(_.isOccupied))
) {
vehicle.Actor ! Vehicle.Deconstruct()
@@ -442,7 +442,7 @@ class SessionData(
}
case Some(obj: Deployable) =>
- if (session.account.gm || obj.Owner.isEmpty || obj.Owner.contains(player.GUID) || obj.Destroyed) {
+ if (session.account.gm || obj.OwnerGuid.isEmpty || obj.OwnerGuid.contains(player.GUID) || obj.Destroyed) {
obj.Actor ! Deployable.Deconstruct()
} else {
log.warn(s"RequestDestroy: ${player.Name} must own the deployable in order to deconstruct it")
@@ -1377,7 +1377,7 @@ class SessionData(
//access to trunk
if (
obj.AccessingTrunk.isEmpty &&
- (!obj.PermissionGroup(AccessPermissionGroup.Trunk.id).contains(VehicleLockState.Locked) || obj.Owner
+ (!obj.PermissionGroup(AccessPermissionGroup.Trunk.id).contains(VehicleLockState.Locked) || obj.OwnerGuid
.contains(player.GUID))
) {
log.info(s"${player.Name} is looking in the ${obj.Definition.Name}'s trunk")
@@ -2473,6 +2473,7 @@ class SessionData(
continent.id,
LocalAction.RouterTelepadTransport(pguid, pguid, sguid, dguid)
)
+ player.LogActivity(VehicleDismountActivity(VehicleSource(router), PlayerSource(player)))
} else {
log.warn(s"UseRouterTelepadSystem: ${player.Name} can not teleport")
}
diff --git a/src/main/scala/net/psforever/actors/session/support/SessionTerminalHandlers.scala b/src/main/scala/net/psforever/actors/session/support/SessionTerminalHandlers.scala
index de2845420..f797bdb14 100644
--- a/src/main/scala/net/psforever/actors/session/support/SessionTerminalHandlers.scala
+++ b/src/main/scala/net/psforever/actors/session/support/SessionTerminalHandlers.scala
@@ -2,6 +2,9 @@
package net.psforever.actors.session.support
import akka.actor.{ActorContext, typed}
+import net.psforever.objects.sourcing.AmenitySource
+import net.psforever.objects.vital.TerminalUsedActivity
+
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
//
@@ -68,8 +71,8 @@ class SessionTerminalHandlers(
order match {
case Terminal.BuyEquipment(item)
if tplayer.avatar.purchaseCooldown(item.Definition).nonEmpty =>
- lastTerminalOrderFulfillment = true
sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, success = false))
+ lastTerminalOrderFulfillment = true
case Terminal.BuyEquipment(item) =>
avatarActor ! AvatarActor.UpdatePurchaseTime(item.Definition)
@@ -141,6 +144,7 @@ class SessionTerminalHandlers(
if (GlobalDefinitions.isBattleFrameVehicle(vehicle.Definition)) {
sendResponse(UnuseItemMessage(player.GUID, msg.terminal_guid))
}
+ player.LogActivity(TerminalUsedActivity(AmenitySource(term), msg.transaction_type))
}.orElse {
log.error(
s"${tplayer.Name} wanted to spawn a vehicle, but there was no spawn pad associated with terminal ${msg.terminal_guid} to accept it"
diff --git a/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala b/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala
index bb913edbc..b88ae7316 100644
--- a/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala
+++ b/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala
@@ -258,7 +258,7 @@ class ZoningOperations(
obj.GUID,
Deployable.Icon(obj.Definition.Item),
obj.Position,
- obj.Owner.getOrElse(PlanetSideGUID(0))
+ obj.OwnerGuid.getOrElse(PlanetSideGUID(0))
)
sendResponse(DeployableObjectsInfoMessage(DeploymentAction.Build, deployInfo))
})
@@ -2824,7 +2824,7 @@ class ZoningOperations(
continent.DeployableList
.filter(_.OwnerName.contains(name))
.foreach(obj => {
- obj.Owner = guid
+ obj.OwnerGuid = guid
drawDeloyableIcon(obj)
})
drawDeloyableIcon = DontRedrawIcons
@@ -2832,7 +2832,7 @@ class ZoningOperations(
//assert or transfer vehicle ownership
continent.GUID(player.avatar.vehicle) match {
case Some(vehicle: Vehicle) if vehicle.OwnerName.contains(tplayer.Name) =>
- vehicle.Owner = guid
+ vehicle.OwnerGuid = guid
continent.VehicleEvents ! VehicleServiceMessage(
s"${tplayer.Faction}",
VehicleAction.Ownership(guid, vehicle.GUID)
@@ -2907,7 +2907,7 @@ class ZoningOperations(
val effortBy = nextSpawnPoint
.collect { case sp: SpawnTube => (sp, continent.GUID(sp.Owner.GUID)) }
.collect {
- case (_, Some(v: Vehicle)) => continent.GUID(v.Owner)
+ case (_, Some(v: Vehicle)) => continent.GUID(v.OwnerGuid)
case (sp, Some(_: Building)) => Some(sp)
}
.collect { case Some(thing: PlanetSideGameObject with FactionAffinity) => Some(SourceEntry(thing)) }
@@ -2962,7 +2962,7 @@ class ZoningOperations(
obj.GUID,
Deployable.Icon(obj.Definition.Item),
obj.Position,
- obj.Owner.getOrElse(PlanetSideGUID(0))
+ obj.OwnerGuid.getOrElse(PlanetSideGUID(0))
)
sendResponse(DeployableObjectsInfoMessage(DeploymentAction.Build, deployInfo))
}
diff --git a/src/main/scala/net/psforever/objects/BoomerDeployable.scala b/src/main/scala/net/psforever/objects/BoomerDeployable.scala
index 0490c2303..33fb76008 100644
--- a/src/main/scala/net/psforever/objects/BoomerDeployable.scala
+++ b/src/main/scala/net/psforever/objects/BoomerDeployable.scala
@@ -72,7 +72,9 @@ class BoomerDeployableControl(mine: BoomerDeployable)
override def loseOwnership(faction: PlanetSideEmpire.Value): Unit = {
super.loseOwnership(PlanetSideEmpire.NEUTRAL)
- mine.OwnerName = None
+ val guid = mine.OwnerGuid
+ mine.AssignOwnership(None)
+ mine.OwnerGuid = guid
}
override def gainOwnership(player: Player): Unit = {
diff --git a/src/main/scala/net/psforever/objects/Deployables.scala b/src/main/scala/net/psforever/objects/Deployables.scala
index 085eaef4b..826b38761 100644
--- a/src/main/scala/net/psforever/objects/Deployables.scala
+++ b/src/main/scala/net/psforever/objects/Deployables.scala
@@ -89,8 +89,7 @@ object Deployables {
.foreach { p =>
p.Actor ! Player.LoseDeployable(target)
}
- target.Owner = None
- target.OwnerName = None
+ target.AssignOwnership(None)
}
events ! LocalServiceMessage(
s"${target.Faction}",
@@ -119,7 +118,7 @@ object Deployables {
.collect {
case Some(obj: Deployable) =>
obj.Actor ! Deployable.Ownership(None)
- obj.Owner = None //fast-forward the effect
+ obj.OwnerGuid = None //fast-forward the effect
obj
}
}
diff --git a/src/main/scala/net/psforever/objects/OwnableByPlayer.scala b/src/main/scala/net/psforever/objects/OwnableByPlayer.scala
index 5892ef75e..a83c908e4 100644
--- a/src/main/scala/net/psforever/objects/OwnableByPlayer.scala
+++ b/src/main/scala/net/psforever/objects/OwnableByPlayer.scala
@@ -1,45 +1,33 @@
// Copyright (c) 2019 PSForever
package net.psforever.objects
+import net.psforever.objects.sourcing.{PlayerSource, UniquePlayer}
import net.psforever.types.PlanetSideGUID
trait OwnableByPlayer {
- private var owner: Option[PlanetSideGUID] = None
- private var ownerName: Option[String] = None
- private var originalOwnerName: Option[String] = None
+ private var owner: Option[UniquePlayer] = None
+ private var ownerGuid: Option[PlanetSideGUID] = None
+ private var originalOwnerName: Option[String] = None
- def Owner: Option[PlanetSideGUID] = owner
+ def Owners: Option[UniquePlayer] = owner
- def Owner_=(owner: PlanetSideGUID): Option[PlanetSideGUID] = Owner_=(Some(owner))
+ def OwnerGuid: Option[PlanetSideGUID] = ownerGuid
- def Owner_=(owner: Player): Option[PlanetSideGUID] = Owner_=(Some(owner.GUID))
+ def OwnerGuid_=(owner: PlanetSideGUID): Option[PlanetSideGUID] = OwnerGuid_=(Some(owner))
- def Owner_=(owner: Option[PlanetSideGUID]): Option[PlanetSideGUID] = {
+ def OwnerGuid_=(owner: Player): Option[PlanetSideGUID] = OwnerGuid_=(Some(owner.GUID))
+
+ def OwnerGuid_=(owner: Option[PlanetSideGUID]): Option[PlanetSideGUID] = {
owner match {
case Some(_) =>
- this.owner = owner
+ ownerGuid = owner
case None =>
- this.owner = None
+ ownerGuid = None
}
- Owner
+ OwnerGuid
}
- def OwnerName: Option[String] = ownerName
-
- def OwnerName_=(owner: String): Option[String] = OwnerName_=(Some(owner))
-
- def OwnerName_=(owner: Player): Option[String] = OwnerName_=(Some(owner.Name))
-
- def OwnerName_=(owner: Option[String]): Option[String] = {
- owner match {
- case Some(_) =>
- ownerName = owner
- originalOwnerName = originalOwnerName.orElse(owner)
- case None =>
- ownerName = None
- }
- OwnerName
- }
+ def OwnerName: Option[String] = owner.map { _.name }
def OriginalOwnerName: Option[String] = originalOwnerName
@@ -56,14 +44,30 @@ trait OwnableByPlayer {
* @return na
*/
def AssignOwnership(playerOpt: Option[Player]): OwnableByPlayer = {
- playerOpt match {
- case Some(player) =>
- Owner = player
- OwnerName = player
- case None =>
- Owner = None
- OwnerName = None
+ (originalOwnerName, playerOpt) match {
+ case (None, Some(player)) =>
+ owner = Some(PlayerSource(player).unique)
+ originalOwnerName = originalOwnerName.orElse { Some(player.Name) }
+ OwnerGuid = player
+ case (_, Some(player)) =>
+ owner = Some(PlayerSource(player).unique)
+ OwnerGuid = player
+ case (_, None) =>
+ owner = None
+ OwnerGuid = None
}
this
}
+
+ /**
+ * na
+ * @param ownable na
+ * @return na
+ */
+ def AssignOwnership(ownable: OwnableByPlayer): OwnableByPlayer = {
+ owner = ownable.owner
+ originalOwnerName = originalOwnerName.orElse { ownable.originalOwnerName }
+ OwnerGuid = ownable.OwnerGuid
+ this
+ }
}
diff --git a/src/main/scala/net/psforever/objects/Player.scala b/src/main/scala/net/psforever/objects/Player.scala
index 18efd8417..963b10357 100644
--- a/src/main/scala/net/psforever/objects/Player.scala
+++ b/src/main/scala/net/psforever/objects/Player.scala
@@ -557,16 +557,16 @@ class Player(var avatar: Avatar)
def DamageModel: DamageResistanceModel = exosuit.asInstanceOf[DamageResistanceModel]
- def canEqual(other: Any): Boolean = other.isInstanceOf[Player]
-
- override def GetContributionDuringPeriod(ending: Long, duration: Long): List[InGameActivity] = {
- val start = ending - duration
+ override def GetContributionDuringPeriod(list: List[InGameActivity], duration: Long): List[InGameActivity] = {
+ val earliestEndTime = System.currentTimeMillis() - duration
History.collect {
- case heal: HealFromEquipment if heal.time <= ending && heal.time > start => heal
- case repair: RepairFromEquipment if repair.time <= ending && repair.time > start => repair
+ case heal: HealFromEquipment if heal.amount > 0 && heal.time > earliestEndTime => heal
+ case repair: RepairFromEquipment if repair.amount > 0 && repair.time > earliestEndTime => repair
}
}
+ def canEqual(other: Any): Boolean = other.isInstanceOf[Player]
+
override def equals(other: Any): Boolean =
other match {
case that: Player =>
diff --git a/src/main/scala/net/psforever/objects/SpecialEmp.scala b/src/main/scala/net/psforever/objects/SpecialEmp.scala
index 32a59853f..17da3b464 100644
--- a/src/main/scala/net/psforever/objects/SpecialEmp.scala
+++ b/src/main/scala/net/psforever/objects/SpecialEmp.scala
@@ -112,11 +112,10 @@ object SpecialEmp {
faction: PlanetSideEmpire.Value
): (PlanetSideGameObject, PlanetSideGameObject, Float) => Boolean = {
distanceCheck(new PlanetSideServerObject with OwnableByPlayer {
- Owner = Some(owner.GUID)
- OwnerName = owner match {
- case p: Player => p.Name
- case o: OwnableByPlayer => o.OwnerName.getOrElse("")
- case _ => ""
+ owner match {
+ case p: Player => AssignOwnership(p)
+ case o: OwnableByPlayer => AssignOwnership(o)
+ case _ => OwnerGuid_=(Some(owner.GUID))
}
Position = position
def Faction = faction
diff --git a/src/main/scala/net/psforever/objects/TelepadDeployable.scala b/src/main/scala/net/psforever/objects/TelepadDeployable.scala
index 1a00dba95..a0cfa954a 100644
--- a/src/main/scala/net/psforever/objects/TelepadDeployable.scala
+++ b/src/main/scala/net/psforever/objects/TelepadDeployable.scala
@@ -103,8 +103,7 @@ class TelepadDeployableControl(tpad: TelepadDeployable)
override def startOwnerlessDecay(): Unit = {
//telepads do not decay when they become ownerless
//telepad decay is tied to their lifecycle with routers
- tpad.Owner = None
- tpad.OwnerName = None
+ tpad.AssignOwnership(None)
}
override def finalizeDeployable(callback: ActorRef): Unit = {
diff --git a/src/main/scala/net/psforever/objects/Vehicle.scala b/src/main/scala/net/psforever/objects/Vehicle.scala
index b2e09fcf9..b8e4eb039 100644
--- a/src/main/scala/net/psforever/objects/Vehicle.scala
+++ b/src/main/scala/net/psforever/objects/Vehicle.scala
@@ -476,7 +476,7 @@ class Vehicle(private val vehicleDef: VehicleDefinition)
if (trunkAccess.isEmpty || trunkAccess.contains(player.GUID)) {
groupPermissions(3) match {
case VehicleLockState.Locked => //only the owner
- Owner.isEmpty || (Owner.isDefined && player.GUID == Owner.get)
+ OwnerGuid.isEmpty || (OwnerGuid.isDefined && player.GUID == OwnerGuid.get)
case VehicleLockState.Group => //anyone in the owner's squad or platoon
faction == player.Faction //TODO this is not correct
case VehicleLockState.Empire => //anyone of the owner's faction
@@ -681,6 +681,6 @@ object Vehicle {
*/
def toString(obj: Vehicle): String = {
val occupancy = obj.Seats.values.count(seat => seat.isOccupied)
- s"${obj.Definition.Name}, owned by ${obj.Owner}: (${obj.Health}/${obj.MaxHealth})(${obj.Shields}/${obj.MaxShields}) ($occupancy)"
+ s"${obj.Definition.Name}, owned by ${obj.OwnerGuid}: (${obj.Health}/${obj.MaxHealth})(${obj.Shields}/${obj.MaxShields}) ($occupancy)"
}
}
diff --git a/src/main/scala/net/psforever/objects/Vehicles.scala b/src/main/scala/net/psforever/objects/Vehicles.scala
index 22ea1221e..5d2f3a61e 100644
--- a/src/main/scala/net/psforever/objects/Vehicles.scala
+++ b/src/main/scala/net/psforever/objects/Vehicles.scala
@@ -61,7 +61,7 @@ object Vehicles {
* `None`, otherwise
*/
def Disown(guid: PlanetSideGUID, vehicle: Vehicle): Option[Vehicle] =
- vehicle.Zone.GUID(vehicle.Owner) match {
+ vehicle.Zone.GUID(vehicle.OwnerGuid) match {
case Some(player: Player) =>
if (player.avatar.vehicle.contains(guid)) {
player.avatar.vehicle = None
@@ -127,7 +127,7 @@ object Vehicles {
*/
def Disown(player: Player, vehicle: Vehicle): Option[Vehicle] = {
val pguid = player.GUID
- if (vehicle.Owner.contains(pguid)) {
+ if (vehicle.OwnerGuid.contains(pguid)) {
vehicle.AssignOwnership(None)
//vehicle.Zone.VehicleEvents ! VehicleServiceMessage(player.Name, VehicleAction.Ownership(pguid, PlanetSideGUID(0)))
val vguid = vehicle.GUID
@@ -272,7 +272,7 @@ object Vehicles {
}
case _ => ;
}
- target.Owner match {
+ target.OwnerGuid match {
case Some(previousOwnerGuid: PlanetSideGUID) =>
// Remove ownership of the vehicle from the previous player
zone.GUID(previousOwnerGuid) match {
diff --git a/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala b/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala
index bb2dfc6bf..c76bab3e9 100644
--- a/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala
+++ b/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala
@@ -33,7 +33,7 @@ import net.psforever.objects.locker.LockerContainerControl
import net.psforever.objects.serverobject.environment._
import net.psforever.objects.serverobject.repair.Repairable
import net.psforever.objects.serverobject.shuttle.OrbitalShuttlePad
-import net.psforever.objects.sourcing.PlayerSource
+import net.psforever.objects.sourcing.{AmenitySource, PlayerSource}
import net.psforever.objects.vital.collision.CollisionReason
import net.psforever.objects.vital.environment.EnvironmentReason
import net.psforever.objects.vital.etc.{PainboxReason, SuicideReason}
@@ -356,6 +356,12 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
}
case Terminal.TerminalMessage(_, msg, order) =>
+ lazy val terminalUsedAction = {
+ player.Zone.GUID(msg.terminal_guid).collect {
+ case t: Terminal =>
+ player.LogActivity(TerminalUsedActivity(AmenitySource(t), msg.transaction_type))
+ }
+ }
order match {
case Terminal.BuyExosuit(exosuit, subtype) =>
val result = setExoSuit(exosuit, subtype)
@@ -366,6 +372,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
player.Name,
AvatarAction.TerminalOrderResult(msg.terminal_guid, msg.transaction_type, result)
)
+ terminalUsedAction
case Terminal.InfantryLoadout(exosuit, subtype, holsters, inventory) =>
log.info(s"${player.Name} wants to change equipment loadout to their option #${msg.unk1 + 1}")
@@ -508,7 +515,9 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
player.Name,
AvatarAction.TerminalOrderResult(msg.terminal_guid, msg.transaction_type, result=true)
)
- case _ => assert(assertion=false, msg.toString)
+ terminalUsedAction
+ case _ =>
+ assert(assertion=false, msg.toString)
}
case Zone.Ground.ItemOnGround(item, _, _) =>
diff --git a/src/main/scala/net/psforever/objects/ce/DeployableBehavior.scala b/src/main/scala/net/psforever/objects/ce/DeployableBehavior.scala
index dba3e149a..b8ff9be0c 100644
--- a/src/main/scala/net/psforever/objects/ce/DeployableBehavior.scala
+++ b/src/main/scala/net/psforever/objects/ce/DeployableBehavior.scala
@@ -66,16 +66,16 @@ trait DeployableBehavior {
finalizeDeployable(callback)
case Deployable.Ownership(None)
- if DeployableObject.Owner.nonEmpty =>
+ if DeployableObject.OwnerGuid.nonEmpty =>
val obj = DeployableObject
if (constructed.contains(true)) {
loseOwnership(obj.Faction)
} else {
- obj.Owner = None
+ obj.OwnerGuid = None
}
case Deployable.Ownership(Some(player))
- if !DeployableObject.Destroyed && DeployableObject.Owner.isEmpty =>
+ if !DeployableObject.Destroyed && DeployableObject.OwnerGuid.isEmpty =>
if (constructed.contains(true)) {
gainOwnership(player)
} else {
@@ -132,12 +132,12 @@ trait DeployableBehavior {
def startOwnerlessDecay(): Unit = {
val obj = DeployableObject
- if (obj.Owner.nonEmpty && decay.isCancelled) {
+ if (obj.OwnerGuid.nonEmpty && decay.isCancelled) {
//without an owner, this deployable should begin to decay and will deconstruct later
import scala.concurrent.ExecutionContext.Implicits.global
decay = context.system.scheduler.scheduleOnce(Deployable.decay, self, Deployable.Deconstruct())
}
- obj.Owner = None //OwnerName should remain set
+ obj.OwnerGuid = None //OwnerName should remain set
}
/**
@@ -163,7 +163,7 @@ trait DeployableBehavior {
val guid = obj.GUID
val zone = obj.Zone
val originalFaction = obj.Faction
- val info = DeployableInfo(guid, DeployableIcon.Boomer, obj.Position, obj.Owner.get)
+ val info = DeployableInfo(guid, DeployableIcon.Boomer, obj.Position, obj.OwnerGuid.get)
if (originalFaction != toFaction) {
obj.Faction = toFaction
val localEvents = zone.LocalEvents
@@ -199,7 +199,7 @@ trait DeployableBehavior {
zone.LocalEvents ! LocalServiceMessage(
zone.id,
LocalAction.TriggerEffectLocation(
- obj.Owner.getOrElse(Service.defaultPlayerGUID),
+ obj.OwnerGuid.getOrElse(Service.defaultPlayerGUID),
"spawn_object_effect",
obj.Position,
obj.Orientation
@@ -234,7 +234,7 @@ trait DeployableBehavior {
val obj = DeployableObject
val zone = obj.Zone
val localEvents = zone.LocalEvents
- val owner = obj.Owner.getOrElse(Service.defaultPlayerGUID)
+ val owner = obj.OwnerGuid.getOrElse(Service.defaultPlayerGUID)
obj.OwnerName match {
case Some(_) =>
case None =>
@@ -306,7 +306,9 @@ trait DeployableBehavior {
if (!obj.Destroyed) {
Deployables.AnnounceDestroyDeployable(obj)
}
- obj.OwnerName = None
+ val guid = obj.OwnerGuid
+ obj.AssignOwnership(None)
+ obj.OwnerGuid = guid
}
}
diff --git a/src/main/scala/net/psforever/objects/definition/converter/BattleFrameFlightConverter.scala b/src/main/scala/net/psforever/objects/definition/converter/BattleFrameFlightConverter.scala
index 0d652db9e..7ed207187 100644
--- a/src/main/scala/net/psforever/objects/definition/converter/BattleFrameFlightConverter.scala
+++ b/src/main/scala/net/psforever/objects/definition/converter/BattleFrameFlightConverter.scala
@@ -28,7 +28,7 @@ class BattleFrameFlightConverter extends ObjectCreateConverter[Vehicle]() {
jammered = false,
v4 = None,
v5 = None,
- obj.Owner match {
+ obj.OwnerGuid match {
case Some(owner) => owner
case None => PlanetSideGUID(0)
}
diff --git a/src/main/scala/net/psforever/objects/definition/converter/BattleFrameRoboticsConverter.scala b/src/main/scala/net/psforever/objects/definition/converter/BattleFrameRoboticsConverter.scala
index 1c51b1afb..411ff6a79 100644
--- a/src/main/scala/net/psforever/objects/definition/converter/BattleFrameRoboticsConverter.scala
+++ b/src/main/scala/net/psforever/objects/definition/converter/BattleFrameRoboticsConverter.scala
@@ -28,7 +28,7 @@ class BattleFrameRoboticsConverter extends ObjectCreateConverter[Vehicle]() {
jammered = obj.Jammed,
v4 = None,
v5 = None,
- obj.Owner match {
+ obj.OwnerGuid match {
case Some(owner) => owner
case None => PlanetSideGUID(0)
}
diff --git a/src/main/scala/net/psforever/objects/definition/converter/DroppodConverter.scala b/src/main/scala/net/psforever/objects/definition/converter/DroppodConverter.scala
index 53a04ee47..7585759d0 100644
--- a/src/main/scala/net/psforever/objects/definition/converter/DroppodConverter.scala
+++ b/src/main/scala/net/psforever/objects/definition/converter/DroppodConverter.scala
@@ -27,7 +27,7 @@ class DroppodConverter extends ObjectCreateConverter[Vehicle]() {
jammered = obj.Jammed,
v4 = Some(false),
v5 = None,
- obj.Owner match {
+ obj.OwnerGuid match {
case Some(owner) => owner
case None => PlanetSideGUID(0)
}
diff --git a/src/main/scala/net/psforever/objects/definition/converter/FieldTurretConverter.scala b/src/main/scala/net/psforever/objects/definition/converter/FieldTurretConverter.scala
index 1379c6060..ca0426512 100644
--- a/src/main/scala/net/psforever/objects/definition/converter/FieldTurretConverter.scala
+++ b/src/main/scala/net/psforever/objects/definition/converter/FieldTurretConverter.scala
@@ -26,7 +26,7 @@ class FieldTurretConverter extends ObjectCreateConverter[TurretDeployable]() {
jammered = obj.Jammed,
Some(false),
None,
- obj.Owner match {
+ obj.OwnerGuid match {
case Some(owner) => owner
case None => PlanetSideGUID(0)
}
diff --git a/src/main/scala/net/psforever/objects/definition/converter/ShieldGeneratorConverter.scala b/src/main/scala/net/psforever/objects/definition/converter/ShieldGeneratorConverter.scala
index 74536ffbb..938b01c02 100644
--- a/src/main/scala/net/psforever/objects/definition/converter/ShieldGeneratorConverter.scala
+++ b/src/main/scala/net/psforever/objects/definition/converter/ShieldGeneratorConverter.scala
@@ -24,7 +24,7 @@ class ShieldGeneratorConverter extends ObjectCreateConverter[ShieldGeneratorDepl
jammered = obj.Jammed,
None,
None,
- obj.Owner match {
+ obj.OwnerGuid match {
case Some(owner) => owner
case None => PlanetSideGUID(0)
}
diff --git a/src/main/scala/net/psforever/objects/definition/converter/SmallDeployableConverter.scala b/src/main/scala/net/psforever/objects/definition/converter/SmallDeployableConverter.scala
index 264beb5bb..b7b430737 100644
--- a/src/main/scala/net/psforever/objects/definition/converter/SmallDeployableConverter.scala
+++ b/src/main/scala/net/psforever/objects/definition/converter/SmallDeployableConverter.scala
@@ -25,7 +25,7 @@ class SmallDeployableConverter extends ObjectCreateConverter[Deployable]() {
},
Some(false),
None,
- obj.Owner match {
+ obj.OwnerGuid match {
case Some(owner) => owner
case None => PlanetSideGUID(0)
}
diff --git a/src/main/scala/net/psforever/objects/definition/converter/SmallTurretConverter.scala b/src/main/scala/net/psforever/objects/definition/converter/SmallTurretConverter.scala
index 48892b8f6..13ee2745f 100644
--- a/src/main/scala/net/psforever/objects/definition/converter/SmallTurretConverter.scala
+++ b/src/main/scala/net/psforever/objects/definition/converter/SmallTurretConverter.scala
@@ -26,7 +26,7 @@ class SmallTurretConverter extends ObjectCreateConverter[TurretDeployable]() {
jammered = obj.Jammed,
Some(true),
None,
- obj.Owner match {
+ obj.OwnerGuid match {
case Some(owner) => owner
case None => PlanetSideGUID(0)
}
diff --git a/src/main/scala/net/psforever/objects/definition/converter/TRAPConverter.scala b/src/main/scala/net/psforever/objects/definition/converter/TRAPConverter.scala
index 1e85bb946..621fde9a3 100644
--- a/src/main/scala/net/psforever/objects/definition/converter/TRAPConverter.scala
+++ b/src/main/scala/net/psforever/objects/definition/converter/TRAPConverter.scala
@@ -24,7 +24,7 @@ class TRAPConverter extends ObjectCreateConverter[TrapDeployable]() {
false,
Some(true),
None,
- obj.Owner match {
+ obj.OwnerGuid match {
case Some(owner) => owner
case None => PlanetSideGUID(0)
}
diff --git a/src/main/scala/net/psforever/objects/definition/converter/TelepadDeployableConverter.scala b/src/main/scala/net/psforever/objects/definition/converter/TelepadDeployableConverter.scala
index 65fd33ba0..f6e88d47f 100644
--- a/src/main/scala/net/psforever/objects/definition/converter/TelepadDeployableConverter.scala
+++ b/src/main/scala/net/psforever/objects/definition/converter/TelepadDeployableConverter.scala
@@ -28,7 +28,7 @@ class TelepadDeployableConverter extends ObjectCreateConverter[TelepadDeployable
false,
None,
Some(router.guid),
- obj.Owner.getOrElse(PlanetSideGUID(0))
+ obj.OwnerGuid.getOrElse(PlanetSideGUID(0))
),
unk1 = 87,
unk2 = 12
diff --git a/src/main/scala/net/psforever/objects/definition/converter/VehicleConverter.scala b/src/main/scala/net/psforever/objects/definition/converter/VehicleConverter.scala
index 6ce558c2e..b363fcee4 100644
--- a/src/main/scala/net/psforever/objects/definition/converter/VehicleConverter.scala
+++ b/src/main/scala/net/psforever/objects/definition/converter/VehicleConverter.scala
@@ -27,7 +27,7 @@ class VehicleConverter extends ObjectCreateConverter[Vehicle]() {
jammered = obj.Jammed,
v4 = Some(false),
v5 = None,
- obj.Owner match {
+ obj.OwnerGuid match {
case Some(owner) => owner
case None => PlanetSideGUID(0)
}
diff --git a/src/main/scala/net/psforever/objects/serverobject/pad/VehicleSpawnControl.scala b/src/main/scala/net/psforever/objects/serverobject/pad/VehicleSpawnControl.scala
index bc0526a86..0fa40b9e8 100644
--- a/src/main/scala/net/psforever/objects/serverobject/pad/VehicleSpawnControl.scala
+++ b/src/main/scala/net/psforever/objects/serverobject/pad/VehicleSpawnControl.scala
@@ -1,15 +1,17 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.pad
-import akka.actor.{Cancellable, Props}
+import akka.actor.{ActorRef, Cancellable, OneForOneStrategy, Props}
import net.psforever.objects.avatar.SpecialCarry
import net.psforever.objects.entity.WorldEntity
import net.psforever.objects.guid.{GUIDTask, TaskWorkflow}
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
import net.psforever.objects.serverobject.pad.process.{VehicleSpawnControlBase, VehicleSpawnControlConcealPlayer}
+import net.psforever.objects.sourcing.AmenitySource
+import net.psforever.objects.vital.TerminalUsedActivity
import net.psforever.objects.zones.{Zone, ZoneAware, Zoning}
import net.psforever.objects.{Default, PlanetSideGameObject, Player, Vehicle}
-import net.psforever.types.Vector3
+import net.psforever.types.{PlanetSideGUID, TransactionType, Vector3}
import scala.annotation.tailrec
import scala.concurrent.ExecutionContext.Implicits.global
@@ -38,38 +40,39 @@ class VehicleSpawnControl(pad: VehicleSpawnPad)
with FactionAffinityBehavior.Check {
/** a reminder sent to future customers */
- var periodicReminder: Cancellable = Default.Cancellable
+ private var periodicReminder: Cancellable = Default.Cancellable
/** repeatedly test whether queued orders are valid */
- var queueManagement: Cancellable = Default.Cancellable
+ private var queueManagement: Cancellable = Default.Cancellable
/** a list of vehicle orders that have been submitted for this spawn pad */
- var orders: List[VehicleSpawnPad.VehicleOrder] = List.empty[VehicleSpawnPad.VehicleOrder]
+ private var orders: List[VehicleSpawnPad.VehicleOrder] = List.empty[VehicleSpawnPad.VehicleOrder]
/** the current vehicle order being acted upon;
* used as a guard condition to control order processing rate
*/
- var trackedOrder: Option[VehicleSpawnControl.Order] = None
+ private var trackedOrder: Option[VehicleSpawnControl.Order] = None
/** how to process either the first order or every subsequent order */
- var handleOrderFunc: VehicleSpawnPad.VehicleOrder => Unit = NewTasking
+ private var handleOrderFunc: VehicleSpawnPad.VehicleOrder => Unit = NewTasking
def LogId = ""
/**
* The first chained action of the vehicle spawning process.
*/
- val concealPlayer =
+ private val concealPlayer: ActorRef =
context.actorOf(Props(classOf[VehicleSpawnControlConcealPlayer], pad), s"${context.parent.path.name}-conceal")
def FactionObject: FactionAffinity = pad
import akka.actor.SupervisorStrategy._
- override val supervisorStrategy = {
- import akka.actor.OneForOneStrategy
+
+ override val supervisorStrategy: OneForOneStrategy = {
OneForOneStrategy(maxNrOfRetries = 10, withinTimeRange = 10 seconds) {
- case _: akka.actor.ActorKilledException => Restart
- case _ => Resume
+ case _ =>
+ log.warn(s"vehicle spawn pad restarted${trackedOrder.map { o => s"; an unfulfilled order for ${o.driver.Name} will be expunged" }.getOrElse("")}")
+ Restart
}
}
@@ -85,21 +88,18 @@ class VehicleSpawnControl(pad: VehicleSpawnPad)
try {
handleOrderFunc(msg)
} catch {
- case _: AssertionError => ; //ehhh
- case e: Exception => //something unexpected
- e.printStackTrace()
+ case _: AssertionError => () //ehhh
+ case e: Exception => e.printStackTrace() //something unexpected
}
case VehicleSpawnControl.ProcessControl.OrderCancelled =>
- trackedOrder match {
- case Some(entry)
- if sender() == concealPlayer =>
+ trackedOrder.collect {
+ case entry if sender() == concealPlayer =>
CancelOrder(
entry,
VehicleSpawnControl.validateOrderCredentials(pad, entry.driver, entry.vehicle)
.orElse(Some("@SVCP_RemovedFromVehicleQueue_Generic"))
)
- case _ => ;
}
trackedOrder = None //guard off
SelectOrder()
@@ -120,37 +120,40 @@ class VehicleSpawnControl(pad: VehicleSpawnPad)
During this time, a periodic message about the spawn pad being blocked will be broadcast to the order queue.
*/
case VehicleSpawnControl.ProcessControl.Reminder =>
- trackedOrder match {
- case Some(entry) =>
- if (periodicReminder.isCancelled) {
- trace(s"the pad has become blocked by a ${entry.vehicle.Definition.Name} in its current order")
- periodicReminder = context.system.scheduler.scheduleWithFixedDelay(
- VehicleSpawnControl.periodicReminderTestDelay,
- VehicleSpawnControl.periodicReminderTestDelay,
- self,
- VehicleSpawnControl.ProcessControl.Reminder
- )
- } else {
- BlockedReminder(entry, orders)
- }
- case None => ;
+ trackedOrder
+ .collect {
+ case entry =>
+ if (periodicReminder.isCancelled) {
+ trace(s"the pad has become blocked by a ${entry.vehicle.Definition.Name} in its current order")
+ periodicReminder = context.system.scheduler.scheduleWithFixedDelay(
+ VehicleSpawnControl.periodicReminderTestDelay,
+ VehicleSpawnControl.periodicReminderTestDelay,
+ self,
+ VehicleSpawnControl.ProcessControl.Reminder
+ )
+ } else {
+ BlockedReminder(entry, orders)
+ }
+ trackedOrder
+ }
+ .orElse {
periodicReminder.cancel()
- }
+ None
+ }
case VehicleSpawnControl.ProcessControl.Flush =>
periodicReminder.cancel()
orders.foreach { CancelOrder(_, Some("@SVCP_RemovedFromVehicleQueue_Generic")) }
orders = Nil
- trackedOrder match {
- case Some(entry) => CancelOrder(entry, Some("@SVCP_RemovedFromVehicleQueue_Generic"))
- case None => ;
+ trackedOrder.foreach {
+ entry => CancelOrder(entry, Some("@SVCP_RemovedFromVehicleQueue_Generic"))
}
trackedOrder = None
handleOrderFunc = NewTasking
pad.Zone.VehicleEvents ! VehicleSpawnPad.ResetSpawnPad(pad) //cautious animation reset
- concealPlayer ! akka.actor.Kill //should cause the actor to restart, which will abort any trapped messages
+ self ! akka.actor.Kill //should cause the actor to restart, which will abort any trapped messages
- case _ => ;
+ case _ => ()
}
/**
@@ -158,7 +161,7 @@ class VehicleSpawnControl(pad: VehicleSpawnPad)
* All orders accepted in the meantime will be queued and a note about priority will be issued.
* @param order the order being accepted
*/
- def NewTasking(order: VehicleSpawnPad.VehicleOrder): Unit = {
+ private def NewTasking(order: VehicleSpawnPad.VehicleOrder): Unit = {
handleOrderFunc = QueuedTasking
ProcessOrder(Some(order))
}
@@ -168,7 +171,7 @@ class VehicleSpawnControl(pad: VehicleSpawnPad)
* all orders accepted in the meantime will be queued and a note about priority will be issued.
* @param order the order being accepted
*/
- def QueuedTasking(order: VehicleSpawnPad.VehicleOrder): Unit = {
+ private def QueuedTasking(order: VehicleSpawnPad.VehicleOrder): Unit = {
val name = order.player.Name
if (trackedOrder match {
case Some(tracked) =>
@@ -219,14 +222,14 @@ class VehicleSpawnControl(pad: VehicleSpawnPad)
/**
* Select the next available queued order and begin processing it.
*/
- def SelectOrder(): Unit = ProcessOrder(SelectFirstOrder())
+ private def SelectOrder(): Unit = ProcessOrder(SelectFirstOrder())
/**
* Select the next-available queued order if there is no current order being fulfilled.
* If the queue has been exhausted, set functionality to prepare to accept the next order as a "first order."
* @return the next-available order
*/
- def SelectFirstOrder(): Option[VehicleSpawnPad.VehicleOrder] = {
+ private def SelectFirstOrder(): Option[VehicleSpawnPad.VehicleOrder] = {
trackedOrder match {
case None =>
val (completeOrder, remainingOrders): (Option[VehicleSpawnPad.VehicleOrder], List[VehicleSpawnPad.VehicleOrder]) =
@@ -255,14 +258,12 @@ class VehicleSpawnControl(pad: VehicleSpawnPad)
* @param order the order being accepted;
* `None`, if no order found or submitted
*/
- def ProcessOrder(order: Option[VehicleSpawnPad.VehicleOrder]): Unit = {
+ private def ProcessOrder(order: Option[VehicleSpawnPad.VehicleOrder]): Unit = {
periodicReminder.cancel()
- order match {
- case Some(_order) =>
+ order.collect {
+ case VehicleSpawnPad.VehicleOrder(driver, vehicle, terminal) =>
val size = orders.size + 1
- val driver = _order.player
val name = driver.Name
- val vehicle = _order.vehicle
val newOrder = VehicleSpawnControl.Order(driver, vehicle)
recursiveOrderReminder(orders.iterator, size)
trace(s"processing next order - a ${vehicle.Definition.Name} for $name")
@@ -273,7 +274,7 @@ class VehicleSpawnControl(pad: VehicleSpawnPad)
)
trackedOrder = Some(newOrder) //guard on
context.system.scheduler.scheduleOnce(2000 milliseconds, concealPlayer, newOrder)
- case None => ;
+ driver.LogActivity(TerminalUsedActivity(AmenitySource(terminal), TransactionType.Buy))
}
}
@@ -282,7 +283,7 @@ class VehicleSpawnControl(pad: VehicleSpawnPad)
* either start a periodic examination of those credentials until the queue has been emptied or
* cancel a running periodic examination if the queue is already empty.
*/
- def queueManagementTask(): Unit = {
+ private def queueManagementTask(): Unit = {
if (orders.nonEmpty) {
orders = orderCredentialsCheck(orders).toList
if (queueManagement.isCancelled) {
@@ -306,7 +307,7 @@ class VehicleSpawnControl(pad: VehicleSpawnPad)
* @param recipients the original list of orders
* @return the list of still-acceptable orders
*/
- def orderCredentialsCheck(recipients: Iterable[VehicleSpawnPad.VehicleOrder]): Iterable[VehicleSpawnPad.VehicleOrder] = {
+ private def orderCredentialsCheck(recipients: Iterable[VehicleSpawnPad.VehicleOrder]): Iterable[VehicleSpawnPad.VehicleOrder] = {
recipients
.map { order =>
(order, VehicleSpawnControl.validateOrderCredentials(order.terminal, order.player, order.vehicle))
@@ -328,10 +329,10 @@ class VehicleSpawnControl(pad: VehicleSpawnPad)
* @param blockedOrder the previous order whose vehicle is blocking the spawn pad from operating
* @param recipients all of the other customers who will be receiving the message
*/
- def BlockedReminder(blockedOrder: VehicleSpawnControl.Order, recipients: Seq[VehicleSpawnPad.VehicleOrder]): Unit = {
+ private def BlockedReminder(blockedOrder: VehicleSpawnControl.Order, recipients: Seq[VehicleSpawnPad.VehicleOrder]): Unit = {
val user = blockedOrder.vehicle
.Seats(0).occupant
- .orElse(pad.Zone.GUID(blockedOrder.vehicle.Owner))
+ .orElse(pad.Zone.GUID(blockedOrder.vehicle.OwnerGuid))
.orElse(pad.Zone.GUID(blockedOrder.DriverGUID))
val relevantRecipients: Iterator[VehicleSpawnPad.VehicleOrder] = user match {
case Some(p: Player) if !p.HasGUID =>
@@ -358,14 +359,14 @@ class VehicleSpawnControl(pad: VehicleSpawnPad)
* Cancel this vehicle order and inform the person who made it, if possible.
* @param entry the order being cancelled
*/
- def CancelOrder(entry: VehicleSpawnControl.Order, msg: Option[String]): Unit = {
+ private def CancelOrder(entry: VehicleSpawnControl.Order, msg: Option[String]): Unit = {
CancelOrder(entry.vehicle, entry.driver, msg)
}
/**
* Cancel this vehicle order and inform the person who made it, if possible.
* @param entry the order being cancelled
*/
- def CancelOrder(entry: VehicleSpawnPad.VehicleOrder, msg: Option[String]): Unit = {
+ private def CancelOrder(entry: VehicleSpawnPad.VehicleOrder, msg: Option[String]): Unit = {
CancelOrder(entry.vehicle, entry.player, msg)
}
/**
@@ -373,7 +374,7 @@ class VehicleSpawnControl(pad: VehicleSpawnPad)
* @param vehicle the vehicle from the order being cancelled
* @param player the player who would driver the vehicle from the order being cancelled
*/
- def CancelOrder(vehicle: Vehicle, player: Player, msg: Option[String]): Unit = {
+ private def CancelOrder(vehicle: Vehicle, player: Player, msg: Option[String]): Unit = {
if (vehicle.Seats.values.count(_.isOccupied) == 0) {
VehicleSpawnControl.DisposeSpawnedVehicle(vehicle, player, pad.Zone)
pad.Zone.VehicleEvents ! VehicleSpawnPad.PeriodicReminder(player.Name, VehicleSpawnPad.Reminders.Cancelled, msg)
@@ -436,8 +437,8 @@ object VehicleSpawnControl {
final case class Order(driver: Player, vehicle: Vehicle) {
assert(driver.HasGUID, s"when ordering a vehicle, the prospective driver ${driver.Name} does not have a GUID")
assert(vehicle.HasGUID, s"when ordering a vehicle, the ${vehicle.Definition.Name} does not have a GUID")
- val DriverGUID = driver.GUID
- val time = System.currentTimeMillis()
+ val DriverGUID: PlanetSideGUID = driver.GUID
+ val time: Long = System.currentTimeMillis()
}
/**
@@ -502,7 +503,7 @@ object VehicleSpawnControl {
* @param player the player who would own the vehicle being disposed
* @param zone the zone in which the vehicle is registered (should be located)
*/
- def DisposeSpawnedVehicle(vehicle: Vehicle, player: Player, zone: Zone): Unit = {
+ private def DisposeSpawnedVehicle(vehicle: Vehicle, player: Player, zone: Zone): Unit = {
DisposeVehicle(vehicle, zone)
zone.VehicleEvents ! VehicleSpawnPad.RevealPlayer(player.GUID)
}
diff --git a/src/main/scala/net/psforever/objects/sourcing/VehicleSource.scala b/src/main/scala/net/psforever/objects/sourcing/VehicleSource.scala
index fc02bdf1c..f3d4d8c46 100644
--- a/src/main/scala/net/psforever/objects/sourcing/VehicleSource.scala
+++ b/src/main/scala/net/psforever/objects/sourcing/VehicleSource.scala
@@ -17,6 +17,7 @@ final case class VehicleSource(
Orientation: Vector3,
Velocity: Option[Vector3],
deployed: DriveState.Value,
+ owner: Option[PlayerSource],
occupants: List[SourceEntry],
Modifiers: ResistanceProfile,
unique: UniqueVehicle
@@ -42,6 +43,7 @@ object VehicleSource {
obj.Orientation,
obj.Velocity,
obj.DeploymentState,
+ None,
Nil,
obj.Definition.asInstanceOf[ResistanceProfile],
UniqueVehicle(
diff --git a/src/main/scala/net/psforever/objects/vehicles/control/VehicleControl.scala b/src/main/scala/net/psforever/objects/vehicles/control/VehicleControl.scala
index d79b75f8a..ae24739b1 100644
--- a/src/main/scala/net/psforever/objects/vehicles/control/VehicleControl.scala
+++ b/src/main/scala/net/psforever/objects/vehicles/control/VehicleControl.scala
@@ -286,7 +286,7 @@ class VehicleControl(vehicle: Vehicle)
val seatGroup = vehicle.SeatPermissionGroup(seatNumber).getOrElse(AccessPermissionGroup.Passenger)
val permission = vehicle.PermissionGroup(seatGroup.id).getOrElse(VehicleLockState.Empire)
(if (seatGroup == AccessPermissionGroup.Driver) {
- vehicle.Owner.contains(user.GUID) || vehicle.Owner.isEmpty || permission != VehicleLockState.Locked
+ vehicle.OwnerGuid.contains(user.GUID) || vehicle.OwnerGuid.isEmpty || permission != VehicleLockState.Locked
} else {
permission != VehicleLockState.Locked
}) &&
@@ -348,7 +348,7 @@ class VehicleControl(vehicle: Vehicle)
//are we already decaying? are we unowned? is no one seated anywhere?
if (!decaying &&
obj.Definition.undergoesDecay &&
- obj.Owner.isEmpty &&
+ obj.OwnerGuid.isEmpty &&
obj.Seats.values.forall(!_.isOccupied)) {
decaying = true
decayTimer = context.system.scheduler.scheduleOnce(
@@ -418,7 +418,7 @@ class VehicleControl(vehicle: Vehicle)
Vehicles.Disown(obj.GUID, obj)
if (!decaying &&
obj.Definition.undergoesDecay &&
- obj.Owner.isEmpty &&
+ obj.OwnerGuid.isEmpty &&
obj.Seats.values.forall(!_.isOccupied)) {
decaying = true
decayTimer = context.system.scheduler.scheduleOnce(
diff --git a/src/main/scala/net/psforever/objects/vital/InGameHistory.scala b/src/main/scala/net/psforever/objects/vital/InGameHistory.scala
index d4836b221..be18371a8 100644
--- a/src/main/scala/net/psforever/objects/vital/InGameHistory.scala
+++ b/src/main/scala/net/psforever/objects/vital/InGameHistory.scala
@@ -50,9 +50,13 @@ final case class ShieldCharge(amount: Int, cause: Option[SourceEntry])
final case class TerminalUsedActivity(terminal: AmenitySource, transaction: TransactionType.Value)
extends GeneralActivity
-final case class Contribution(unique: SourceEntry, entries: List[InGameActivity])
+final case class VehicleDismountActivity(vehicle: VehicleSource, player: PlayerSource)
+ extends GeneralActivity
+
+final case class Contribution(src: SourceUniqueness, entries: List[InGameActivity])
extends GeneralActivity {
- override val time: Long = entries.maxBy(_.time).time
+ val start: Long = entries.last.time
+ val end: Long = entries.head.time
}
/* vitals history */
@@ -183,6 +187,9 @@ trait InGameHistory {
action match {
case Some(act) =>
history = history :+ act
+ if (IsContributionEvent(act)) {
+ previousContributionInheritance = None
+ }
case None => ()
}
history
@@ -206,7 +213,7 @@ trait InGameHistory {
LogActivity(DamageFromPainbox(result))
case _: EnvironmentReason =>
LogActivity(DamageFromEnvironment(result))
- case _ => ;
+ case _ =>
LogActivity(DamageFrom(result))
if(result.adversarial.nonEmpty) {
lastDamage = Some(result)
@@ -227,70 +234,79 @@ trait InGameHistory {
}
}
- private val contributionInheritance: mutable.HashMap[SourceUniqueness, Seq[Contribution]] =
- mutable.HashMap[SourceUniqueness, Seq[Contribution]]()
+ private var previousContributionInheritance: Option[List[InGameActivity]] = None
- def ContributionFrom(target: PlanetSideGameObject with FactionAffinity with InGameHistory): Boolean = {
+ private val contributionInheritance: mutable.HashMap[SourceUniqueness, Contribution] =
+ mutable.HashMap[SourceUniqueness, Contribution]()
+
+ def ContributionFrom(target: PlanetSideGameObject with FactionAffinity with InGameHistory): Option[Contribution] = {
if (target ne this) {
- val events = target.GetContribution()
- val nonEmpty = events.nonEmpty
- if (nonEmpty) {
- val source = SourceEntry(target)
- contributionInheritance.get(source.unique) match {
- case Some(previousContributions) =>
- val uniqueEvents = for {
- curr <- events
- if !previousContributions.filter(_ == curr).exists(_.time == curr.time)
- } yield curr
- contributionInheritance.put(source.unique, previousContributions :+ Contribution(source, uniqueEvents))
- case None =>
- contributionInheritance.put(source.unique, Seq(Contribution(source, events)))
- }
+ InGameHistory.ContributionFrom(target) match {
+ case out @ Some(in @ Contribution(src, _)) =>
+ contributionInheritance.put(src, in)
+ out
+ case None =>
+ contributionInheritance.remove(SourceEntry(target).unique)
+ None
}
- nonEmpty
} else {
- false
+ None
}
}
- def RemoveContributionFrom(target: PlanetSideGameObject with FactionAffinity with InGameHistory): Iterable[Contribution] = {
- contributionInheritance.remove(SourceEntry(target).unique).getOrElse(Nil)
+ def GetContribution(): Option[List[InGameActivity]] = {
+ previousContributionInheritance
+ .collect {
+ case out @ events if events.head.time > System.currentTimeMillis() - 600000L =>
+ Some(out)
+ case events =>
+ val newEvents = GetContributionDuringPeriod(events, duration = 600000L)
+ if (newEvents.isEmpty) {
+ previousContributionInheritance = None
+ None
+ } else {
+ previousContributionInheritance = Some(newEvents)
+ Some(newEvents)
+ }
+ }
+ .flatten
+ .orElse {
+ val events = GetContributionDuringPeriod(History, duration = 600000L)
+ if (events.nonEmpty) {
+ previousContributionInheritance = Some(events)
+ Some(events)
+ } else {
+ None
+ }
+ }
}
- def GetContribution(): List[InGameActivity] = {
- GetContributionDuringPeriod(System.currentTimeMillis(), duration = 600000)
+ def GetContributionDuringPeriod(list: List[InGameActivity], duration: Long): List[InGameActivity] = {
+ val earliestEndTime = System.currentTimeMillis() - duration
+ list.collect {
+ case event: DamagingActivity if event.health > 0 && event.time > earliestEndTime => event
+ case event: RepairingActivity if event.amount > 0 && event.time > earliestEndTime => event
+ }
}
- def GetContribution(ending: Long): List[InGameActivity] = {
- GetContributionDuringPeriod(ending, duration = 600000)
- }
-
- def GetContributionDuringPeriod(ending: Long, duration: Long): List[InGameActivity] = {
- val start = ending - duration
- History.collect { case repair: RepairFromEquipment
- if repair.time <= ending && repair.time > start => repair
+ def IsContributionEvent(event: InGameActivity): Boolean = {
+ event match {
+ case _: RepairingActivity => true
+ case _: DamagingActivity => true
+ case _ => false
}
}
def HistoryAndContributions(): List[InGameActivity] = {
- val ending = System.currentTimeMillis()
- val start = ending - 600000
- contributionInheritance.foreach { case (obj, list) =>
- val filtered = list.filter { event => event.time <= ending && event.time > start }
- if (filtered.isEmpty) {
- contributionInheritance.remove(obj)
- } else if (filtered.size != list.size) {
- contributionInheritance.update(obj, filtered)
- }
- }
- val contributions = contributionInheritance.flatMap { case (_, list) => list }
- (History ++ contributions).sortBy(_.time)
+ History ++ contributionInheritance.values.toList
}
def ClearHistory(): List[InGameActivity] = {
lastDamage = None
val out = history
history = List.empty
+ previousContributionInheritance = None
+ contributionInheritance.clear()
out
}
}
@@ -318,4 +334,10 @@ object InGameHistory {
unit.foreach { o => obj.ContributionFrom(o) }
}
}
+
+ def ContributionFrom(target: PlanetSideGameObject with FactionAffinity with InGameHistory): Option[Contribution] = {
+ target
+ .GetContribution()
+ .collect { case events => Contribution(SourceEntry(target).unique, events) }
+ }
}
diff --git a/src/main/scala/net/psforever/objects/zones/exp/KillAssists.scala b/src/main/scala/net/psforever/objects/zones/exp/KillAssists.scala
index 7410cbd6d..bd8ea8d71 100644
--- a/src/main/scala/net/psforever/objects/zones/exp/KillAssists.scala
+++ b/src/main/scala/net/psforever/objects/zones/exp/KillAssists.scala
@@ -5,7 +5,7 @@ import akka.actor.ActorRef
import net.psforever.objects.avatar.scoring.{Assist, Death, Kill}
import net.psforever.objects.sourcing.{PlayerSource, SourceEntry}
import net.psforever.objects.vital.interaction.{Adversarial, DamageResult}
-import net.psforever.objects.vital.{DamagingActivity, HealingActivity, InGameActivity, RepairingActivity, RevivingActivity}
+import net.psforever.objects.vital.{DamagingActivity, HealingActivity, InGameActivity, RepairingActivity, RevivingActivity, SpawningActivity}
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
import net.psforever.types.PlanetSideEmpire
@@ -13,6 +13,28 @@ import scala.collection.mutable
import scala.concurrent.duration._
object KillAssists {
+ private def limitHistoryToThisLife(history: List[InGameActivity]): List[InGameActivity] = {
+ val spawnIndex = history.lastIndexWhere {
+ case _: SpawningActivity => true
+ case _: RevivingActivity => true
+ case _ => false
+ }
+ val endIndex = history.lastIndexWhere {
+ case damage: DamagingActivity => damage.data.targetAfter.asInstanceOf[PlayerSource].Health == 0
+ case _ => false
+ }
+ if (spawnIndex == -1 || endIndex == -1) {
+ Nil //throw VitalsHistoryException(history.head, "vitals history does not contain expected conditions")
+ // } else
+ // if (spawnIndex == -1) {
+ // Nil //throw VitalsHistoryException(history.head, "vitals history does not contain initial spawn conditions")
+ // } else if (endIndex == -1) {
+ // Nil //throw VitalsHistoryException(history.last, "vitals history does not contain end of life conditions")
+ } else {
+ history.slice(spawnIndex, endIndex)
+ }
+ }
+
private[exp] def determineKiller(
lastDamageActivity: Option[DamageResult],
history: List[InGameActivity]
@@ -71,7 +93,7 @@ object KillAssists {
history: Iterable[InGameActivity],
eventBus: ActorRef
): Unit = {
- val shortHistory = Support.limitHistoryToThisLife(history.toList)
+ val shortHistory = limitHistoryToThisLife(history.toList)
val everyone = determineKiller(lastDamage, shortHistory) match {
case Some((result, killer: PlayerSource)) =>
val assists = collectKillAssistsForPlayer(victim, shortHistory, Some(killer))
@@ -130,7 +152,7 @@ object KillAssists {
val assists = func(history, victim.Faction).filterNot { case (_, kda) => kda.amount <= 0 }
val total = assists.values.foldLeft(0f)(_ + _.total)
val output = assists.map { case (id, kda) =>
- (id, ContributionStatsOutput(kda.player, kda.weapons.map { _.weapon_id }, kda.amount / total))
+ (id, ContributionStatsOutput(kda.player, kda.weapons.map { _.equipment_id }, kda.amount / total))
}
output.remove(victim.CharId)
output
@@ -214,7 +236,7 @@ object KillAssists {
case Some(mod) =>
//previous attacker, just add to entry
val firstWeapon = mod.weapons.head
- val weapons = if (firstWeapon.weapon_id == wepid) {
+ val weapons = if (firstWeapon.equipment_id == wepid) {
firstWeapon.copy(
amount = firstWeapon.amount + amount,
shots = firstWeapon.shots + 1,
diff --git a/src/main/scala/net/psforever/objects/zones/exp/KillContributions.scala b/src/main/scala/net/psforever/objects/zones/exp/KillContributions.scala
index a546793e7..b02ace185 100644
--- a/src/main/scala/net/psforever/objects/zones/exp/KillContributions.scala
+++ b/src/main/scala/net/psforever/objects/zones/exp/KillContributions.scala
@@ -2,29 +2,28 @@
package net.psforever.objects.zones.exp
import akka.actor.ActorRef
-
-import java.util.Date
import net.psforever.objects.avatar.scoring.{Assist, Kill}
-import net.psforever.objects.sourcing.{PlayerSource, SourceUniqueness}
-import net.psforever.objects.vital.{Contribution, DamagingActivity, HealFromEquipment, HealFromTerminal, HealingActivity, InGameActivity, RepairFromEquipment, RepairFromExoSuitChange, RepairFromTerminal, RepairingActivity, RevivingActivity, SupportActivityCausedByAnother}
-import net.psforever.objects.vital.interaction.Adversarial
+import net.psforever.objects.serverobject.hackable.Hackable.HackInfo
+import net.psforever.objects.sourcing.{AmenitySource, PlayerSource, SourceEntry, SourceUniqueness, VehicleSource}
+import net.psforever.objects.vital.{Contribution, InGameActivity, RevivingActivity, TerminalUsedActivity, VehicleDismountActivity}
+import net.psforever.objects.vital.projectile.ProjectileReason
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
import net.psforever.types.PlanetSideEmpire
-import org.joda.time.Instant
-import org.joda.time.{LocalDateTime => JodaLocalDateTime}
import scala.collection.mutable
object KillContributions {
- private lazy val recoveryItems: Seq[Int] = {
+ final val RecoveryItems: Seq[Int] = {
import net.psforever.objects.GlobalDefinitions._
Seq(
- bank.ObjectId,
- nano_dispenser.ObjectId,
- medicalapplicator.ObjectId,
- medical_terminal.ObjectId,
- adv_med_terminal.ObjectId
- )
+ bank,
+ nano_dispenser,
+ medicalapplicator,
+ medical_terminal,
+ adv_med_terminal,
+ crystals_health_a,
+ crystals_health_b
+ ).collect { _.ObjectId }
}
private[exp] def rewardTheseSupporters(
@@ -34,465 +33,326 @@ object KillContributions {
bep: Long,
eventBus: ActorRef
): Unit = {
- val killTime = kill.time
+ //setup
+ val killTime = kill.time.toDate.getTime
val faction = target.Faction
- val shortHistory = Support.limitHistoryToThisLife(history.toList)
- //direct heal assists and repair assists
- /*
- can be simplified by ignoring resolved damage and countered healing
- and just extracting the HealingActivity and DamagingActivity events,
- but the contribution percentage calculations will need to be adjusted
- */
- val healthAssists = cullContributorImplements(allocateContributors(healthRecoveryContributors)(shortHistory, faction))
- val armorAssists = cullContributorImplements(allocateContributors(armorRecoveryContributors)(shortHistory, faction))
- //combined
- val allAssists = healthAssists.map { case out @ (id, healthEntry) =>
- armorAssists.get(id) match {
- case Some(armorEntry) =>
- (id, healthEntry.copy(weapons = healthEntry.weapons ++ armorEntry.weapons))
- case None =>
- out
- }
- }
- //revival
- contributeWithRevivalActivity(shortHistory, allAssists)
- allAssists.remove(0)
- allAssists.remove(target.CharId)
+ val limitedHistory = limitHistoryToThisLife(history.toList, killTime)
//divide by applicable time periods (long=10minutes, short=5minutes)
- val longEvents = findTimeApplicableActivities(allAssists.values.toSeq, killTime, timeOffset = 600)
- val shortEvents = findTimeApplicableActivities(longEvents, killTime, timeOffset = 300)
- val victim = kill.victim
- //convert to output format
- longEvents
- .map { stats =>
- val (_, ContributionStatsOutput(p, w, r)) = mapContributionPointsByPercentage(total = 100f, shortEvents)(stats.player.CharId, stats)
- (p, Assist(victim, w, r, (bep * r).toLong))
- }
- .foreach { case (player, kda) =>
- eventBus ! AvatarServiceMessage(player.Name, AvatarAction.UpdateKillsDeathsAssists(player.CharId, kda))
- }
- }
-
- private def allocateContributors(
- tallyFunc: (List[InGameActivity], PlanetSideEmpire.Value, mutable.LongMap[ContributionStats]) => Seq[(Long, Int)]
- )
- (
- history: List[InGameActivity],
- faction: PlanetSideEmpire.Value
- ): mutable.LongMap[ContributionStats] = {
- /** players who have contributed to this death, and how much they have contributed
- * key - character identifier,
- * value - (player, damage, total damage, number of shots) */
- val participants: mutable.LongMap[ContributionStats] = mutable.LongMap[ContributionStats]()
- tallyFunc(history, faction, participants)
- participants
- }
-
- private def cullContributorImplements(
- input: mutable.LongMap[ContributionStats]
- ): mutable.LongMap[ContributionStats] = {
- input.collect { case (id, entry) =>
- (id, entry.copy(weapons = entry.weapons.filter { stats => recoveryItems.contains(stats.weapon_id) }))
- }.filter { case (_, entry) =>
- entry.weapons.nonEmpty
+ val shortPeriod = killTime - 300000L
+ val (contributions, (longHistory, shortHistory)) = {
+ val (contrib, onlyHistory) = limitedHistory.partition { _.isInstanceOf[Contribution] }
+ (
+ contrib
+ .collect { case Contribution(unique, entries) => (unique, entries) }
+ .toMap[SourceUniqueness, List[InGameActivity]],
+ onlyHistory.partition { _.time > shortPeriod }
+ )
}
- }
-
- private def healthRecoveryContributors(
- history: List[InGameActivity],
- faction: PlanetSideEmpire.Value,
- participants: mutable.LongMap[ContributionStats]
- ): Seq[(Long, Int)] = {
- /** damage as it is measured in order (with heal-countered damage eliminated)
- * key - character identifier,
- * value - current damage contribution */
- var damageInOrder: Seq[(Long, Int)] = Seq[(Long, Int)]()
- var recoveryInOrder: Seq[(Long, Int)] = Seq[(Long, Int)]()
- val contributions = history
- .collect { case Contribution(unique, entries) => (unique.unique, entries) }
- .toMap[SourceUniqueness, List[InGameActivity]]
- val contributionsBy: mutable.LongMap[List[InGameActivity]] =
- mutable.LongMap[List[InGameActivity]]()
- history.foreach {
- case d: DamagingActivity if d.health > 0 =>
- val (damage, recovery) = contributeWithDamagingActivity(d, faction, d.health, participants, damageInOrder, recoveryInOrder)
- damageInOrder = damage
- recoveryInOrder = recovery
- case h: HealFromEquipment =>
- val (damage, recovery) = contributeWithRecoveryActivity(h.user, h.equipment_def.ObjectId, faction, h.amount, h.time, participants, damageInOrder, recoveryInOrder)
- damageInOrder = damage
- recoveryInOrder = recovery
- case ht: HealFromTerminal =>
- val time = ht.time
- val users = cacheContributionsForEntityByUser(ht.term.unique, contributions, contributionsBy)
- .collect { case entry: SupportActivityCausedByAnother
- if entry.time <= time && entry.time > time - 300000 => entry //short support activity time
- }
- .groupBy(_.user.unique)
- .map(_._2.head.user)
- .toSeq
- val (damage, recovery) = contributeWithSupportRecoveryActivity(users, ht.term.Definition.ObjectId, faction, ht.amount, time, participants, damageInOrder, recoveryInOrder)
- damageInOrder = damage
- recoveryInOrder = recovery
- case h: HealingActivity =>
- val (damage, recovery) = contributeWithRecoveryActivity(wepid = 0, faction, h.amount, h.time, participants, damageInOrder, recoveryInOrder)
- damageInOrder = damage
- recoveryInOrder = recovery
- case r: RevivingActivity =>
- val (damage, recovery) = contributeWithRecoveryActivity(r.equipment.ObjectId, faction, r.amount, r.time, participants, damageInOrder, recoveryInOrder)
- damageInOrder = damage
- recoveryInOrder = recovery
- case _ => ()
- }
- recoveryInOrder
- }
-
- private def armorRecoveryContributors(
- history: List[InGameActivity],
- faction: PlanetSideEmpire.Value,
- participants: mutable.LongMap[ContributionStats]
- ): Seq[(Long, Int)] = {
- /** damage as it is measured in order (with heal-countered damage eliminated)
- * key - character identifier,
- * value - current damage contribution */
- var damageInOrder: Seq[(Long, Int)] = Seq[(Long, Int)]()
- var recoveryInOrder: Seq[(Long, Int)] = Seq[(Long, Int)]()
- val contributions = history
- .collect { case Contribution(unique, entries) => (unique.unique, entries) }
- .toMap[SourceUniqueness, List[InGameActivity]]
- val contributionsBy: mutable.LongMap[List[InGameActivity]] =
- mutable.LongMap[List[InGameActivity]]()
- history.foreach {
- case d: DamagingActivity if d.amount - d.health > 0 =>
- val (damage, recovery) = contributeWithDamagingActivity(d, faction, d.amount - d.health, participants, damageInOrder, recoveryInOrder)
- damageInOrder = damage
- recoveryInOrder = recovery
- case r: RepairFromEquipment =>
- val (damage, recovery) = contributeWithRecoveryActivity(r.user, r.equipment_def.ObjectId, faction, r.amount, r.time, participants, damageInOrder, recoveryInOrder)
- damageInOrder = damage
- recoveryInOrder = recovery
- case rt: RepairFromTerminal => ()
- val time = rt.time
- val users = cacheContributionsForEntityByUser(rt.term.unique, contributions, contributionsBy)
- .collect { case entry: SupportActivityCausedByAnother
- if entry.time <= time && entry.time > time - 300000 => entry //short support activity time
- }
- .groupBy(_.user.unique)
- .map(_._2.head.user)
- .toSeq
- val (damage, recovery) = contributeWithSupportRecoveryActivity(users, rt.term.Definition.ObjectId, faction, rt.amount, time, participants, damageInOrder, recoveryInOrder)
- damageInOrder = damage
- recoveryInOrder = recovery
- case rxc: RepairFromExoSuitChange =>
- //TODO use cacheContributionsForEntityByUser; what out what order terminal was used
- val (damage, recovery) = contributeWithRecoveryActivity(wepid = 0, faction, rxc.amount, rxc.time, participants, damageInOrder, recoveryInOrder)
- damageInOrder = damage
- recoveryInOrder = recovery
- case r: RepairingActivity =>
- val (damage, recovery) = contributeWithRecoveryActivity(wepid = 0, faction, r.amount, r.time, participants, damageInOrder,recoveryInOrder)
- damageInOrder = damage
- recoveryInOrder = recovery
- case _ => ()
- }
- recoveryInOrder
- }
-
- def cacheContributionsForEntityByUser(
- target: SourceUniqueness,
- contributions: Map[SourceUniqueness, List[InGameActivity]],
- contributionsBy: mutable.LongMap[List[InGameActivity]]
- ): List[InGameActivity] = {
- contributions.get(target) match {
- case Some(list) if list.nonEmpty =>
- list
- .collect { case r: RepairFromEquipment => r }
- .groupBy(_.user.unique)
- .flatMap { case (_, activities) =>
- val charId = activities.head.user.CharId
- contributionsBy.get(charId) match {
- case Some(outList) =>
- outList
- case None =>
- contributionsBy.put(charId, activities).getOrElse(Nil)
- }
- }.toList
- case _ =>
- Nil
- }
- }
-
- private def contributeWithDamagingActivity(
- activity: DamagingActivity,
- faction: PlanetSideEmpire.Value,
- amount: Int,
- participants: mutable.LongMap[ContributionStats],
- damageOrder: Seq[(Long, Int)],
- recoveryOrder: Seq[(Long, Int)]
- ): (Seq[(Long, Int)], Seq[(Long, Int)]) = {
- //mark entries from the recovery list to truncate
- val data = activity.data
- val time = activity.time
- val playerOpt = data.adversarial.collect { case Adversarial(p: PlayerSource, _, _) => p }
- var lastCharId = 0L
- var lastValue = 0
- var ramt = amount
- var rindex = -1
- val riter = recoveryOrder.iterator
- while (riter.hasNext && ramt > 0) {
- val (id, value) = riter.next()
- if (value > 0) {
- val entry = participants(id)
- val weapons = entry.weapons
- lastCharId = id
- lastValue = value
- if (value > ramt) {
- participants.put(
- id,
- entry.copy(
- weapons = weapons.head.copy(amount = weapons.head.amount - ramt, time = time) +: weapons.tail,
- amount = entry.total - ramt,
- time = time
- )
- )
- ramt = 0
- lastValue = lastValue - value
- } else {
- participants.put(
- id,
- entry.copy(
- weapons = weapons.tail :+ weapons.head.copy(amount = 0, time = time),
- amount = entry.total - ramt,
- time = time
- )
- )
- ramt = ramt - value
- rindex += 1
- lastValue = 0
- }
- }
- rindex += 1
- }
- //damage calculations
- val newDamageOrder = KillAssists.contributeWithDamagingActivity(
- playerOpt,
- data.interaction.cause.attribution,
- faction,
- amount,
- time,
- participants,
- damageOrder
- )
- //re-combine output list(s)
- val leftovers = if (lastValue > 0) {
- Seq((lastCharId, lastValue))
+ //sort by applicable time periods, as long as the longer period is represented by activity
+ val otherContributionCalculations = contributionScoringAndCulling(faction, kill, Seq(target.CharId), contributions, bep)(_, _)
+ val finalContributions = if (longHistory.isEmpty) {
+ val contributionProcess = new CombinedHealthAndArmorContributionProcess(faction, contributions, Nil)
+ contributionProcess.submit(shortHistory)
+ val contributionEntries = otherContributionCalculations(shortHistory, contributionProcess.output())
+ contributionEntries
+ .keys
+ .map { composeContributionOutput(_, contributionEntries, contributionEntries, bep) }
} else {
- Nil
+ val longContributionProcess = new CombinedHealthAndArmorContributionProcess(faction, contributions, Nil)
+ val shortContributionProcess = new CombinedHealthAndArmorContributionProcess(faction, contributions, Seq(longContributionProcess))
+ longContributionProcess.submit(longHistory)
+ shortContributionProcess.submit(shortHistory)
+ val longContributionEntries = otherContributionCalculations(longHistory, longContributionProcess.output())
+ val shortContributionEntries = otherContributionCalculations(shortHistory, shortContributionProcess.output())
+ (longContributionEntries.keys ++ shortContributionEntries.keys)
+ .toSeq
+ .distinct
+ .map { composeContributionOutput(_, shortContributionEntries, longContributionEntries, bep) }
+ }
+ //take the output and transform that into contribution distribution data
+ val victim = kill.victim
+ finalContributions.foreach { case (charId, ContributionStatsOutput(player, weapons, exp)) =>
+ eventBus ! AvatarServiceMessage(
+ player.Name,
+ AvatarAction.UpdateKillsDeathsAssists(charId, Assist(victim, weapons, 1f, exp.toLong))
+ )
}
- (newDamageOrder, leftovers ++ recoveryOrder.slice(rindex, recoveryOrder.size) ++ recoveryOrder.take(rindex).map { case (id, _) => (id, 0) })
}
- private def contributeWithRecoveryActivity(
- user: PlayerSource,
- wepid: Int,
- faction: PlanetSideEmpire.Value,
- amount: Int,
- time: Long,
- participants: mutable.LongMap[ContributionStats],
- damageOrder: Seq[(Long, Int)],
- recoveryOrder: Seq[(Long, Int)]
- ): (Seq[(Long, Int)], Seq[(Long, Int)]) = {
- contributeWithRecoveryActivity(user, user.CharId, wepid, faction, amount, time, participants, damageOrder, recoveryOrder)
+ /**
+ * Only historical activity that falls within the valid period matters.
+ * Unlike an expected case where the history would be bound by being spawned and being killed, respectively,
+ * this imposes only the long contribution time limit on events since the latest entry;
+ * and, it may stop some time after the otherwise closest activity for being spawned.
+ * @param history the original history
+ * @param eventTime from which time to start counting backwards
+ * @return the potentially truncated history
+ */
+ private def limitHistoryToThisLife(history: List[InGameActivity], eventTime: Long): List[InGameActivity] = {
+ val longLimit: Long = eventTime - 600000L
+ history.collect {
+ case event if event.time < eventTime && event.time >= longLimit => event
+ }
}
- private def contributeWithRecoveryActivity(
- wepid: Int,
- faction: PlanetSideEmpire.Value,
- amount: Int,
- time: Long,
- participants: mutable.LongMap[ContributionStats],
- damageOrder: Seq[(Long, Int)],
- recoveryOrder: Seq[(Long, Int)]
- ): (Seq[(Long, Int)], Seq[(Long, Int)]) = {
- contributeWithRecoveryActivity(PlayerSource.Nobody, charId = 0, wepid, faction, amount, time, participants, damageOrder, recoveryOrder)
+ private def contributionScoringAndCulling(
+ faction: PlanetSideEmpire.Value,
+ kill: Kill,
+ excludeTargets: Seq[Long],
+ contributions: Map[SourceUniqueness, List[InGameActivity]],
+ bep: Long
+ )
+ (
+ history: List[InGameActivity],
+ contributionEntries: mutable.LongMap[ContributionStats]
+ ): mutable.LongMap[ContributionStats] = {
+ contributionEntries.map { case (id, stat) =>
+ val newWeaponStats = stat.weapons.map { weaponStat =>
+ weaponStat.copy(contributions = (weaponStat.shots + (bep * weaponStat.amount / stat.total)).toFloat)
+ }
+ contributionEntries.put(id, stat.copy(weapons = newWeaponStats))
+ }
+ contributeWithKillWhileMountedActivity(faction, kill, history, contributionEntries)
+ contributeWithVehicleTransportActivity(history, contributionEntries)
+ contributeWithRevivalActivity(history, contributionEntries)
+ contributeWithTerminalActivity(faction, history, contributions, contributionEntries)
+ contributionEntries.remove(0)
+ excludeTargets.foreach { contributionEntries.remove }
+ contributionEntries
}
- private def contributeWithRecoveryActivity(
- user: PlayerSource,
- charId: Long,
- wepid: Int,
- faction: PlanetSideEmpire.Value,
- amount: Int,
- time: Long,
- participants: mutable.LongMap[ContributionStats],
- damageOrder: Seq[(Long, Int)],
- recoveryOrder: Seq[(Long, Int)]
- ): (Seq[(Long, Int)], Seq[(Long, Int)]) = {
- //recovery calculations
- val newOrder = KillAssists.contributeWithRecoveryActivity(amount, participants, damageOrder)
- //detect potential instances of and remove approximate value for friendly-fire recovery (that doesn't count)
- val qualityRecoveryAmount = {
- val (entries, count) = {
- val value = damageOrder.size - newOrder.size
- if (value > 0) {
- if (newOrder.head == damageOrder.lift(value).getOrElse(newOrder.head)) {
- (damageOrder.take(value), value)
- } else {
- (damageOrder.take(value + 1), value + 1)
- }
- } else {
- if (newOrder.headOption != damageOrder.headOption) {
- (damageOrder.headOption.toList, 1)
- } else {
- (Nil, 1)
- }
- }
+ private def contributeWithKillWhileMountedActivity(
+ faction: PlanetSideEmpire.Value,
+ kill: Kill,
+ history: List[InGameActivity],
+ participants: mutable.LongMap[ContributionStats]
+ ): List[InGameActivity] = {
+ (kill
+ .info
+ .interaction
+ .cause match {
+ case p: ProjectileReason => p.projectile.mounted_in.map { _._2 }
+ case _ => None
+ })
+ .collect {
+ case mount: VehicleSource =>
+ //repairs
+ val contributions = history
+ .collect { case Contribution(unique, entries) if mount.unique == unique => (unique, entries) }
+ .toMap[SourceUniqueness, List[InGameActivity]]
+ contributions
+ .foreach {
+ case (_, localHistory) =>
+ val process = new ArmorRecoveryExperienceContributionProcess(faction, contributions)
+ process.submit(localHistory)
+ cullContributorImplements(process.output()).foreach {
+ case (id, stat) =>
+ val killExperience = kill.experienceEarned
+ val newWeapons = stat.weapons.map { weapon =>
+ val shots = weapon.shots
+ val amount = weapon.amount
+ val exp = math.min(0.5d * killExperience, shots + math.ceil(math.log(amount))).toFloat
+ weapon.copy(contributions = exp)
+ }.toList
+ contributeWithCombinedActivity(
+ id,
+ newWeapons,
+ stat.player,
+ stat.amount,
+ stat.total,
+ stat.shots,
+ stat.time,
+ participants
+ )
+ }
+ }
+ //vehicle owner
+ extractContributionsForEntityByUser(faction, mount, contributions, participants)
+ mount.owner
+ .collect {
+ case owner =>
+ val time = kill.time.toDate.getTime
+ participants.getOrElseUpdate(
+ owner.CharId,
+ ContributionStats(owner, Seq(WeaponStats(0, 1, 1, time, 10f)), 1, 1, 1, time)
+ )
+ }
}
- if (entries.nonEmpty) {
- //approximate the value as a percentage
- amount * entries.count { case (id, _) => participants.get(id).collect { _.player.Faction != faction }.getOrElse(false) } / count
- } else {
- amount
- }
- }
- if (qualityRecoveryAmount > 0) {
- val updatedEntry = participants.get(charId) match {
- case Some(entry) =>
- //existing entry
- entry.copy(
- weapons = WeaponStats(wepid, qualityRecoveryAmount, 1, time, 1f) +: entry.weapons,
- amount = entry.amount + qualityRecoveryAmount,
- total = entry.total + qualityRecoveryAmount,
- time = time
- )
- case None =>
- //new entry
- ContributionStats(user, Seq(WeaponStats(wepid, qualityRecoveryAmount, 1, time, 1f)), qualityRecoveryAmount, qualityRecoveryAmount, 1, time)
- }
- participants.put(charId, updatedEntry)
- }
- //re-combine output list(s) (bridge entry for output recovery list)
- (newOrder, (charId, amount) +: recoveryOrder)
+ history
}
- private def contributeWithSupportRecoveryActivity(
- users: Seq[PlayerSource],
- wepid: Int,
- faction: PlanetSideEmpire.Value,
- amount: Int,
- time: Long,
- participants: mutable.LongMap[ContributionStats],
- damageOrder: Seq[(Long, Int)],
- recoveryOrder: Seq[(Long, Int)]
- ): (Seq[(Long, Int)], Seq[(Long, Int)]) = {
- var outputDamageOrder = damageOrder
- var outputRecoveryOrder = recoveryOrder
- users.zip {
- val numberOfUsers = users.size
- val out = Array.fill(numberOfUsers)(numberOfUsers / amount)
- (0 to numberOfUsers % amount).foreach {
- out(_) += 1
+ private def contributeWithVehicleTransportActivity(
+ history: List[InGameActivity],
+ participants: mutable.LongMap[ContributionStats]
+ ): List[InGameActivity] = {
+ val shortHistory = history.collect { case out: VehicleDismountActivity => out }
+ shortHistory
+ .groupBy { a => a.vehicle.owner }
+ .foreach { case (Some(owner), dismountsFromVehicle) =>
+ val numberOfDismounts = dismountsFromVehicle.size
+ contributeWithCombinedActivity(
+ owner.CharId,
+ dismountsFromVehicle.map { act => WeaponStats(act.vehicle.Definition.ObjectId, 1, 1, act.time, 15f) },
+ dismountsFromVehicle.head.vehicle.owner.get,
+ numberOfDismounts,
+ numberOfDismounts,
+ numberOfDismounts,
+ dismountsFromVehicle.maxBy(_.time).time,
+ participants
+ )
}
- out
- }.foreach { case (user, subAmount) =>
- val (a, b) = contributeWithRecoveryActivity(user, user.CharId, wepid, faction, subAmount, time, participants, outputDamageOrder, outputRecoveryOrder)
- outputDamageOrder = a
- outputRecoveryOrder = b
- }
- (outputDamageOrder, outputRecoveryOrder)
+ shortHistory
}
private def contributeWithRevivalActivity(
history: List[InGameActivity],
participants: mutable.LongMap[ContributionStats]
- ): Unit = {
- history
- .collect { case rev: RevivingActivity => rev }
+ ): List[InGameActivity] = {
+ val shortHistory = history.collect { case rev: RevivingActivity => rev }
+ shortHistory
.groupBy(_.user.CharId)
.foreach { case (id, revivesByThisPlayer) =>
val numberOfRevives = revivesByThisPlayer.size
- val latestTime = revivesByThisPlayer.maxBy(_.time).time
- val newStats = revivesByThisPlayer.map { stat => WeaponStats(stat.equipment.ObjectId, 100, 1, stat.time, 0.25f) }
- val newStat = WeaponStats(revivesByThisPlayer.head.equipment.ObjectId, 100 * numberOfRevives, numberOfRevives, latestTime, 0.25f)
- participants.get(id) match {
- case Some(stats) =>
- participants.put(id, stats.copy(weapons = stats.weapons ++ newStats))
- case None =>
- participants.put(id, ContributionStats(
- revivesByThisPlayer.head.user,
- newStats,
- newStat.amount,
- newStat.shots,
- revivesByThisPlayer.size,
- latestTime
- ))
- }
+ contributeWithCombinedActivity(
+ id,
+ revivesByThisPlayer.map { stat => WeaponStats(stat.equipment.ObjectId, 100, 1, stat.time, 25f) },
+ revivesByThisPlayer.head.user,
+ amount = 100 * numberOfRevives,
+ total = 100 * numberOfRevives,
+ numberOfRevives,
+ revivesByThisPlayer.maxBy(_.time).time,
+ participants
+ )
}
+ shortHistory
}
- private def findTimeApplicableActivities(
- activity: Seq[ContributionStats],
- killLastTime: JodaLocalDateTime,
- timeOffset: Int //s
- ): Seq[ContributionStats] = {
- val dateTimeConverter: Date=>JodaLocalDateTime = JodaLocalDateTime.fromDateFields
- val milliToInstant: Long=>org.joda.time.Instant = Instant.ofEpochMilli
- val targetTime = killLastTime.minusSeconds(timeOffset)
- //find all activities that occurred before the kill time but after the offset
- activity.collect { entry =>
- val weapons = entry.weapons.filter { stat =>
- val activityTime = dateTimeConverter(milliToInstant(stat.time).toDate)
- (activityTime.isBefore(killLastTime) || activityTime.equals(killLastTime)) && targetTime.isAfter(activityTime)
- }
- .groupBy(_.weapon_id)
- .collect { case (id, stats) =>
- WeaponStats(
- id,
- stats.foldLeft(0)(_ + _.amount),
- stats.foldLeft(0)(_ + _.shots),
- stats.maxBy(_.time).time,
- stats.foldLeft(0f)(_ + _.contributions)
- )
- }
- if (weapons.nonEmpty) {
- val totalMod = entry.total / entry.amount.toFloat
- val recoveryAmount = weapons.foldLeft(0)(_ + _.amount)
- val recoveryShots = weapons.foldLeft(0)(_ + _.shots)
- val recoveryTime = weapons.map { _.time }.max
- Some(ContributionStats(entry.player, weapons.toSeq, recoveryAmount, (recoveryAmount * totalMod).toInt, recoveryShots, recoveryTime))
- } else {
- None
- }
- }.flatten
+ private def contributeWithTerminalActivity(
+ faction: PlanetSideEmpire.Value,
+ history: List[InGameActivity],
+ contributions: Map[SourceUniqueness, List[InGameActivity]],
+ participants: mutable.LongMap[ContributionStats]
+ ): List[InGameActivity] = {
+ contributeWithTerminalActivity(
+ history.collect { case t: TerminalUsedActivity => (t, t.terminal, t.terminal.hacked) },
+ faction,
+ contributions,
+ participants
+ )
}
- private def mapContributionPointsByPercentage(
- total: Float,
- compareList: Iterable[ContributionStats]
- )
- (
- charId: Long,
- contribution: ContributionStats,
- ): (Long, ContributionStatsOutput) = {
- val user = contribution.player
- val unique = user.unique
- val points = contribution.amount
- val value = if (points < 75) {
- //a small contribution means the lower time limit
- if (compareList.exists { a => a.player.unique == unique }) {
- math.max(0.2f, points / total)
- } else {
- 0f
- }
- } else {
- //large contribution is always okay
- if (points > 299) {
- 1.0f
- } else if (points > 100) {
- 0.75f
- } else {
- 0.5f
- }
+ private[exp] def contributeWithTerminalActivity(
+ data: Seq[(InGameActivity, AmenitySource, Option[HackInfo])],
+ faction: PlanetSideEmpire.Value,
+ contributions: Map[SourceUniqueness, List[InGameActivity]],
+ participants: mutable.LongMap[ContributionStats]
+ ): List[InGameActivity] = {
+ data.collect {
+ case (t, terminal, Some(info)) if terminal.Faction != faction =>
+ participants.getOrElseUpdate(
+ info.player.CharId,
+ ContributionStats(info.player, Seq(WeaponStats(0, 1, 1, t.time, 10f)), 1, 1, 1, t.time)
+ )
+ t
+ case (t, terminal, _) =>
+ extractContributionsForEntityByUser(faction, terminal, contributions, participants)
+ t
+ }.toList
+ }
+
+ private def extractContributionsForEntityByUser(
+ faction: PlanetSideEmpire.Value,
+ target: SourceEntry,
+ contributions: Map[SourceUniqueness, List[InGameActivity]],
+ contributionsBy: mutable.LongMap[ContributionStats]
+ ): List[InGameActivity] = {
+ val shortHistory = contributions.getOrElse(target.unique, Nil)
+ val process = new ArmorRecoveryExperienceContributionProcess(faction, contributions)
+ process.submit(shortHistory)
+ cullContributorImplements(process.output()).foreach {
+ case (id, stats) => contributionsBy.getOrElseUpdate(id, stats)
}
- (charId, ContributionStatsOutput(user, contribution.weapons.map { _.weapon_id }, value))
+ shortHistory
+ }
+
+ private[exp] def cullContributorImplements(
+ input: mutable.LongMap[ContributionStats]
+ ): mutable.LongMap[ContributionStats] = {
+ input.collect { case (id, entry) =>
+ (id, entry.copy(weapons = entry.weapons.filter { stats => KillContributions.RecoveryItems.contains(stats.equipment_id) }))
+ }.filter { case (_, entry) =>
+ entry.weapons.nonEmpty
+ }
+ }
+
+ private def contributeWithCombinedActivity(
+ id: Long,
+ newStats: List[WeaponStats],
+ user: PlayerSource,
+ amount: Int,
+ total: Int,
+ shots: Int,
+ time: Long,
+ participants: mutable.LongMap[ContributionStats]
+ ): Unit = {
+ participants.get(id) match {
+ case Some(stats) =>
+ val oldWeapons = stats.weapons
+ val newWeapons = newStats.map { weapon =>
+ oldWeapons.find { _.equipment_id == weapon.equipment_id } match {
+ case Some(foundEntry) =>
+ weapon.copy(
+ amount = weapon.amount + foundEntry.amount,
+ shots = weapon.shots + foundEntry.shots,
+ contributions = weapon.contributions + foundEntry.contributions
+ )
+ case None =>
+ weapon
+ }
+ }
+ participants.put(id, stats.copy(weapons = newWeapons))
+ case None =>
+ participants.put(id, ContributionStats(user, newStats, amount, total, shots, time))
+ }
+ }
+
+ private def composeContributionOutput(
+ charId: Long,
+ shortPeriod: mutable.LongMap[ContributionStats],
+ longPeriod: mutable.LongMap[ContributionStats],
+ bep: Long
+ ): (Long, ContributionStatsOutput) = {
+ shortPeriod
+ .get(charId)
+ .collect {
+ case entry =>
+ val weapons = entry.weapons
+ (
+ entry.player,
+ weapons.map { _.equipment_id },
+ math.min(weapons.foldLeft(0f)(_ + _.contributions).toLong, bep)
+ )
+ }
+ .orElse {
+ longPeriod
+ .get(charId)
+ .collect {
+ case entry =>
+ val weapons = entry.weapons
+ (
+ entry.player,
+ weapons.filter { _.amount == 0 }.map { _.equipment_id },
+ (0.9f * math.min(weapons.foldLeft(0f)(_ + _.contributions), bep.toFloat)).toLong
+ )
+ }
+ }
+ .orElse {
+ Some((PlayerSource.Nobody, Seq(0), 0L))
+ }
+ .collect {
+ case (player, weaponIds, experience) =>
+ (charId, ContributionStatsOutput(player, weaponIds, experience.toFloat))
+ }
+ .get
}
}
diff --git a/src/main/scala/net/psforever/objects/zones/exp/KillDeathAssists2.scala b/src/main/scala/net/psforever/objects/zones/exp/KillDeathAssists2.scala
deleted file mode 100644
index 4b2b4ad9f..000000000
--- a/src/main/scala/net/psforever/objects/zones/exp/KillDeathAssists2.scala
+++ /dev/null
@@ -1,295 +0,0 @@
-// Copyright (c) NOT VERSIONED PSForever
-package net.psforever.objects.zones.exp
-
-import net.psforever.objects.definition.WithShields
-import net.psforever.objects.sourcing.{PlayerSource, SourceEntry, SourceWithHealthEntry, SourceWithShieldsEntry}
-import net.psforever.objects.vital.{DamagingActivity, InGameActivity, RepairingActivity, ShieldCharge, SpawningActivity}
-import net.psforever.objects.vital.interaction.Adversarial
-import net.psforever.types.PlanetSideEmpire
-
-import scala.collection.mutable
-
-object KillDeathAssists2 {
- private def collectKillAssists(
- victim: SourceEntry,
- history: List[InGameActivity],
- func: (List[InGameActivity], PlanetSideEmpire.Value) => mutable.LongMap[ContributionStats]
- ): mutable.LongMap[ContributionStatsOutput] = {
- val assists = func(history, victim.Faction).filterNot { case (_, kda) => kda.amount <= 0 }
- val total = assists.values.foldLeft(0f)(_ + _.total)
- val output = assists.map { case (id, kda) =>
- (id, ContributionStatsOutput(kda.player, kda.weapons.map { _.weapon_id }, kda.amount / total))
- }
- output.remove(victim.CharId)
- output
- }
-
- private[exp] def collectAssistsForEntity(
- target: SourceWithHealthEntry,
- history: List[InGameActivity],
- killerOpt: Option[PlayerSource]
- ): Iterable[ContributionStatsOutput] = {
- val (_, maxShields) = maximumEntityHealthAndShields(history)
- val healthAssists = collectKillAssists(
- target,
- history,
- Support.allocateContributors(entityHealthDamageContributors)
- )
- if (maxShields > 0) {
- val shieldAssists = collectShieldAssists(target.asInstanceOf[SourceWithShieldsEntry], history)
- killerOpt.collect {
- case killer =>
- healthAssists.remove(killer.CharId)
- shieldAssists.remove(killer.CharId)
- }
- Support.onlyOriginalAssistEntries(healthAssists, shieldAssists)
- } else {
- killerOpt.collect {
- case killer =>
- healthAssists.remove(killer.CharId)
- }
- healthAssists.values
- }
- }
-
- private[exp] def collectShieldAssists(
- victim: SourceWithShieldsEntry,
- history: List[InGameActivity],
- ): mutable.LongMap[ContributionStatsOutput] = {
- val initialShieldAssists = Support.allocateContributors(entityShieldDamageContributors)(history, victim.Faction)
- .filterNot { case (_, kda) => kda.amount <= 0 }
- val fullFloatShield = initialShieldAssists.values.foldLeft(0)(_ + _.total).toFloat
- val shieldAssists = initialShieldAssists.map { case (id, kda) =>
- (id, ContributionStatsOutput(kda.player, kda.weapons.map { _.weapon_id }, math.min(kda.total / fullFloatShield, 0.4f)))
- }
- shieldAssists.remove(victim.CharId)
- shieldAssists
- }
-
- private def entityShieldDamageContributors(
- history: List[InGameActivity],
- faction: PlanetSideEmpire.Value,
- participants: mutable.LongMap[ContributionStats]
- ): Seq[(Long, Int)] = {
- /** damage as it is measured in order (with heal-countered damage eliminated)
- * key - character identifier,
- * value - current damage contribution */
- var inOrder: Seq[(Long, Int)] = Seq[(Long, Int)]()
- history.tail.foreach {
- case d: DamagingActivity if d.amount - d.health > 0 =>
- inOrder = contributeWithDamagingActivity(d, faction, d.amount - d.health, participants, inOrder)
- case v: ShieldCharge if v.amount > 0 =>
- inOrder = contributeWithRecoveryActivity(v.amount, participants, inOrder)
- case _ => ()
- }
- inOrder
- }
-
- private def maximumEntityHealthAndShields(history: List[InGameActivity]): (Int, Int) = {
- var (health, shields) = (0, 0)
- history.collect {
- case SpawningActivity(target: SourceWithHealthEntry with SourceWithShieldsEntry, _, _) =>
- health = math.max(target.Definition.MaxHealth, health)
- shields = math.max(target.Definition.asInstanceOf[WithShields].MaxShields, shields)
- case SpawningActivity(target: SourceWithHealthEntry, _, _) =>
- health = math.max(target.Definition.MaxHealth, health)
- case d: DamagingActivity =>
- d.data.targetBefore match {
- case target: SourceWithHealthEntry with SourceWithShieldsEntry =>
- health = math.max(target.Definition.MaxHealth, health)
- shields = math.max(target.Definition.asInstanceOf[WithShields].MaxShields, shields)
- case target: SourceWithHealthEntry =>
- health = math.max(target.health, health)
- case _ => ()
- }
- }
- (health, shields)
- }
-
- // private def initialEntityHealthAndShields(history: List[InGameActivity]): (Int, Int, PlanetSideEmpire.Value) = {
- // history.collectFirst {
- // case SpawningActivity(target: SourceWithHealthEntry with SourceWithShieldsEntry, _, _) =>
- // (target.health, target.shields, target.Faction)
- // case SpawningActivity(target: SourceWithHealthEntry, _, _) =>
- // (target.health, 0, target.Faction)
- // case d: DamagingActivity =>
- // d.data.targetBefore match {
- // case target: SourceWithHealthEntry with SourceWithShieldsEntry =>
- // (target.health, target.shields, target.Faction)
- // case target: SourceWithHealthEntry =>
- // (target.health, 0, target.Faction)
- // case _ =>
- // (0, 0, PlanetSideEmpire.NEUTRAL)
- // }
- // }.getOrElse((0, 0, PlanetSideEmpire.NEUTRAL))
- // }
-
- private def armorDamageContributors(
- history: List[InGameActivity],
- faction: PlanetSideEmpire.Value,
- participants: mutable.LongMap[ContributionStats]
- ): Seq[(Long, Int)] = {
- /** damage as it is measured in order (with heal-countered damage eliminated)
- * key - character identifier,
- * value - current damage contribution */
- var inOrder: Seq[(Long, Int)] = Seq[(Long, Int)]()
- history.foreach {
- case d: DamagingActivity if d.amount - d.health > 0 =>
- inOrder = contributeWithDamagingActivity(d, faction, d.amount - d.health, participants, inOrder)
- case r: RepairingActivity =>
- inOrder = contributeWithRecoveryActivity(r.amount, participants, inOrder)
- case _ => ()
- }
- inOrder
- }
-
- private[exp] def contributeWithDamagingActivity(
- activity: DamagingActivity,
- faction: PlanetSideEmpire.Value,
- amount: Int,
- participants: mutable.LongMap[ContributionStats],
- order: Seq[(Long, Int)]
- ): Seq[(Long, Int)] = {
- val data = activity.data
- val playerOpt = data.adversarial.collect { case Adversarial(p: PlayerSource, _,_) => p }
- contributeWithDamagingActivity(
- playerOpt,
- data.interaction.cause.attribution,
- faction,
- amount,
- activity.time,
- participants,
- order
- )
- }
-
- private[exp] def contributeWithDamagingActivity(
- userOpt: Option[PlayerSource],
- wepid: Int,
- faction: PlanetSideEmpire.Value,
- amount: Int,
- time: Long,
- participants: mutable.LongMap[ContributionStats],
- order: Seq[(Long, Int)]
- ): Seq[(Long, Int)] = {
- userOpt match {
- case Some(user)
- if user.Faction != faction =>
- val whoId = user.CharId
- val percentage = amount / user.Definition.MaxHealth.toFloat
- val updatedEntry = participants.get(whoId) match {
- case Some(mod) =>
- //previous attacker, just add to entry
- val firstWeapon = mod.weapons.head
- val weapons = if (firstWeapon.weapon_id == wepid) {
- firstWeapon.copy(
- amount = firstWeapon.amount + amount,
- shots = firstWeapon.shots + 1,
- time = time,
- contributions = firstWeapon.contributions + percentage
- ) +: mod.weapons.tail
- } else {
- WeaponStats(wepid, amount, 1, time, percentage) +: mod.weapons
- }
- mod.copy(
- amount = mod.amount + amount,
- weapons = weapons,
- total = mod.total + amount,
- shots = mod.shots + 1,
- time = time
- )
- case None =>
- //new attacker, new entry
- ContributionStats(
- user,
- Seq(WeaponStats(wepid, amount, 1, time, percentage)),
- amount,
- amount,
- 1,
- time
- )
- }
- participants.put(whoId, updatedEntry)
- order.indexWhere({ case (id, _) => id == whoId }) match {
- case 0 =>
- //ongoing attack by same player
- val entry = order.head
- (entry._1, entry._2 + amount) +: order.tail
- case _ =>
- //different player than immediate prior attacker
- (whoId, amount) +: order
- }
- case _ =>
- //damage that does not lead to contribution
- order.headOption match {
- case Some((id, dam)) =>
- if (id == 0L) {
- (0L, dam + amount) +: order.tail //pool
- } else {
- (0L, amount) +: order //new
- }
- case None =>
- order
- }
- }
- }
-
- private def entityHealthDamageContributors(
- history: List[InGameActivity],
- faction: PlanetSideEmpire.Value,
- participants: mutable.LongMap[ContributionStats]
- ): Seq[(Long, Int)] = {
- /** damage as it is measured in order (with heal-countered damage eliminated)
- * key - character identifier,
- * value - current damage contribution */
- var inOrder: Seq[(Long, Int)] = Seq[(Long, Int)]()
- history.collect {
- case d: DamagingActivity if d.health > 0 =>
- inOrder = contributeWithDamagingActivity(d, faction, d.health, participants, inOrder)
-
- case r: RepairingActivity if r.amount > 0 =>
- inOrder = contributeWithRecoveryActivity(r.amount, participants, inOrder)
- }
- inOrder
- }
-
- private[exp] def contributeWithRecoveryActivity(
- amount: Int,
- participants: mutable.LongMap[ContributionStats],
- order: Seq[(Long, Int)]
- ): Seq[(Long, Int)] = {
- var amt = amount
- var count = 0
- var newOrder: Seq[(Long, Int)] = Nil
- order.takeWhile { entry =>
- val (id, total) = entry
- if (id > 0 && total > 0) {
- val part = participants(id)
- if (amount > total) {
- //drop this entry
- participants.put(id, part.copy(amount = 0, weapons = Nil)) //just in case
- amt = amt - total
- } else {
- //edit around the inclusion of this entry
- val newTotal = total - amt
- val trimmedWeapons = {
- var index = -1
- var weaponSum = 0
- val pweapons = part.weapons
- while (weaponSum < amt) {
- index += 1
- weaponSum = weaponSum + pweapons(index).amount
- }
- pweapons(index).copy(amount = weaponSum - amt) +: pweapons.slice(index+1, pweapons.size)
- }
- newOrder = (id, newTotal) +: newOrder
- participants.put(id, part.copy(amount = part.amount - amount, weapons = trimmedWeapons))
- amt = 0
- }
- }
- count += 1
- amt > 0
- }
- newOrder ++ order.drop(count)
- }
-}
diff --git a/src/main/scala/net/psforever/objects/zones/exp/RecoveryExperienceContribution.scala b/src/main/scala/net/psforever/objects/zones/exp/RecoveryExperienceContribution.scala
new file mode 100644
index 000000000..bae389793
--- /dev/null
+++ b/src/main/scala/net/psforever/objects/zones/exp/RecoveryExperienceContribution.scala
@@ -0,0 +1,449 @@
+// Copyright (c) 2023 PSForever
+package net.psforever.objects.zones.exp
+
+import net.psforever.objects.sourcing.{PlayerSource, SourceUniqueness}
+import net.psforever.objects.vital.interaction.{Adversarial, DamageResult}
+import net.psforever.objects.vital.{DamagingActivity, HealFromEquipment, HealFromTerminal, HealingActivity, InGameActivity, RepairFromEquipment, RepairFromTerminal, RepairingActivity, RevivingActivity, SupportActivityCausedByAnother}
+import net.psforever.types.PlanetSideEmpire
+
+import scala.collection.mutable
+
+sealed trait RecoveryExperienceContribution {
+ def submit(history: List[InGameActivity]): Unit
+ def output(): mutable.LongMap[ContributionStats]
+ def clear(): Unit
+}
+
+object RecoveryExperienceContribution {
+ private[exp] def contributeWithDamagingActivity(
+ activity: DamagingActivity,
+ amount: Int,
+ damageParticipants: mutable.LongMap[PlayerSource],
+ recoveryParticipants: mutable.LongMap[ContributionStats],
+ damageOrder: Seq[(Long, Int)],
+ recoveryOrder: Seq[(Long, Int)]
+ ): (Seq[(Long, Int)], Seq[(Long, Int)]) = {
+ //mark entries from the ordered recovery list to truncate
+ val data: DamageResult = activity.data
+ val time: Long = activity.time
+ var lastCharId: Long = 0L
+ var lastValue: Int = 0
+ var ramt: Int = amount
+ var rindex: Int = 0
+ val riter = recoveryOrder.iterator
+ while (riter.hasNext && ramt > 0) {
+ val (id, value) = riter.next()
+ if (value > 0) {
+ /*
+ if the amount on the previous recovery node is positive, reduce it by the damage value for that user's last used equipment
+ keep traversing recovery nodes, and lobbing them off, until the recovery amount is zero
+ if the user can not be found having an entry, skip the update but lob off the recovery progress node all the same
+ if the amount is zero, do not check any further recovery progress nodes
+ */
+ recoveryParticipants
+ .get(id)
+ .foreach { entry =>
+ val weapons = entry.weapons
+ lastCharId = id
+ lastValue = value
+ if (value > ramt) {
+ //take from the value on the last-used equipment, at the front of the list
+ recoveryParticipants.put(
+ id,
+ entry.copy(
+ weapons = weapons.head.copy(amount = math.max(0, weapons.head.amount - ramt), time = time) +: weapons.tail,
+ amount = math.max(0, entry.amount - ramt),
+ time = time
+ )
+ )
+ ramt = 0
+ lastValue = lastValue - value
+ } else {
+ //take from the value on the last-used equipment, at the front of the list
+ //move that entry to the end of the list
+ recoveryParticipants.put(
+ id,
+ entry.copy(
+ weapons = weapons.tail :+ weapons.head.copy(amount = 0, time = time),
+ amount = math.max(0, entry.amount - ramt),
+ time = time
+ )
+ )
+ ramt = ramt - value
+ rindex += 1
+ lastValue = 0
+ }
+ }
+ rindex += 1
+ }
+ }
+ //damage order and damage contribution entry
+ val newDamageEntry = data
+ .adversarial
+ .collect { case Adversarial(p: PlayerSource, _, _) => (p, damageParticipants.get(p.CharId)) }
+ .collect {
+ case (player, Some(PlayerSource.Nobody)) =>
+ damageParticipants.put(player.CharId, player)
+ Some(player)
+ case (player, Some(_)) =>
+ damageParticipants.getOrElseUpdate(player.CharId, player)
+ Some(player)
+ }
+ .collect {
+ case Some(player) => (player.CharId, amount) //for damageOrder
+ }
+ .orElse {
+ Some((0L, amount)) //for damageOrder
+ }
+ //re-combine output list(s)
+ val leftovers = if (lastValue > 0) {
+ Seq((lastCharId, lastValue))
+ } else {
+ Nil
+ }
+ (newDamageEntry.toList ++ damageOrder, leftovers ++ recoveryOrder.slice(rindex, recoveryOrder.size) ++ recoveryOrder.take(rindex).map { case (id, _) => (id, 0) })
+ }
+
+ private[exp] def contributeWithRecoveryActivity(
+ user: PlayerSource,
+ wepid: Int,
+ faction: PlanetSideEmpire.Value,
+ amount: Int,
+ time: Long,
+ damageParticipants: mutable.LongMap[PlayerSource],
+ recoveryParticipants: mutable.LongMap[ContributionStats],
+ damageOrder: Seq[(Long, Int)],
+ recoveryOrder: Seq[(Long, Int)]
+ ): (Seq[(Long, Int)], Seq[(Long, Int)]) = {
+ contributeWithRecoveryActivity(user, user.CharId, wepid, faction, amount, time, damageParticipants, recoveryParticipants, damageOrder, recoveryOrder)
+ }
+
+ private[exp] def contributeWithRecoveryActivity(
+ wepid: Int,
+ faction: PlanetSideEmpire.Value,
+ amount: Int,
+ time: Long,
+ damageParticipants: mutable.LongMap[PlayerSource],
+ recoveryParticipants: mutable.LongMap[ContributionStats],
+ damageOrder: Seq[(Long, Int)],
+ recoveryOrder: Seq[(Long, Int)]
+ ): (Seq[(Long, Int)], Seq[(Long, Int)]) = {
+ contributeWithRecoveryActivity(PlayerSource.Nobody, charId = 0, wepid, faction, amount, time, damageParticipants, recoveryParticipants, damageOrder, recoveryOrder)
+ }
+
+ private[exp] def contributeWithRecoveryActivity(
+ user: PlayerSource,
+ charId: Long,
+ wepid: Int,
+ faction: PlanetSideEmpire.Value,
+ amount: Int,
+ time: Long,
+ damageParticipants: mutable.LongMap[PlayerSource],
+ recoveryParticipants: mutable.LongMap[ContributionStats],
+ damageOrder: Seq[(Long, Int)],
+ recoveryOrder: Seq[(Long, Int)]
+ ): (Seq[(Long, Int)], Seq[(Long, Int)]) = {
+ //mark entries from the ordered damage list to truncate
+ val damageEntries = damageOrder.iterator
+ var amtToReduce: Int = amount
+ var amtToGain: Int = 0
+ var lastValue: Int = -1
+ var damageRemoveCount: Int = 0
+ var damageRemainder: Seq[(Long, Int)] = Nil
+ //keep reducing previous damage until recovery amount is depleted, or no more damage entries remain, or the last damage entry was depleted already
+ while (damageEntries.hasNext && amtToReduce > 0 && lastValue != 0) {
+ val (id, value) = damageEntries.next()
+ lastValue = value
+ if (value > 0) {
+ damageParticipants
+ .get(id)
+ .collect {
+ case player if player.Faction != faction =>
+ //if previous attacker was an enemy, the recovery counts towards contribution
+ if (value > amtToReduce) {
+ damageRemainder = Seq((id, value - amtToReduce))
+ amtToGain = amtToGain + amtToReduce
+ amtToReduce = 0
+ } else {
+ amtToGain = amtToGain + value
+ amtToReduce = amtToReduce - value
+ }
+ Some(player)
+ case player =>
+ //if the previous attacker was friendly fire, the recovery doesn't count towards contribution
+ if (value > amtToReduce) {
+ damageRemainder = Seq((id, value - amtToReduce))
+ amtToReduce = 0
+ } else {
+ amtToReduce = amtToReduce - value
+ }
+ Some(player)
+ }
+ .orElse {
+ //if we couldn't find an entry, just give the contribution to the user anyway
+ damageParticipants.put(id, PlayerSource.Nobody)
+ if (value > amtToReduce) {
+ damageRemainder = Seq((id, value - amtToReduce))
+ amtToGain = amtToGain + amtToReduce
+ amtToReduce = 0
+ } else {
+ amtToGain = amtToGain + value
+ amtToReduce = amtToReduce - value
+ }
+ None
+ }
+ //keep track of entries whose damage was depleted
+ damageRemoveCount += 1
+ }
+ }
+ amtToGain = amtToGain + amtToReduce //if early termination, gives leftovers as gain
+ if (amtToGain > 0) {
+ val newWeaponStats = WeaponStats(wepid, amtToGain, 1, time, 1f)
+ //try: add first contribution entry
+ //then: add accumulation of last weapon entry to contribution entry
+ //last: add new weapon entry to contribution entry
+ recoveryParticipants
+ .getOrElseUpdate(
+ charId,
+ ContributionStats(user, Seq(newWeaponStats), amtToGain, amtToGain, 1, time)
+ ) match {
+ case entry if entry.weapons.size > 1 =>
+ if (entry.weapons.head.equipment_id == wepid) {
+ val head = entry.weapons.head
+ recoveryParticipants.put(
+ charId,
+ entry.copy(
+ weapons = head.copy(amount = head.amount + amtToGain, shots = head.shots + 1, time = time) +: entry.weapons.tail,
+ amount = entry.amount + amtToGain,
+ total = entry.total + amtToGain,
+ shots = entry.shots + 1,
+ time = time
+ )
+ )
+ } else {
+ recoveryParticipants.put(
+ charId,
+ entry.copy(
+ weapons = newWeaponStats +: entry.weapons,
+ amount = entry.amount + amtToGain,
+ total = entry.total + amtToGain,
+ shots = entry.shots + 1,
+ time = time
+ )
+ )
+ }
+ case _ => ()
+ //not technically possible
+ }
+ }
+ val newRecoveryEntry = if (amtToGain == 0) {
+ Seq((0L, amount))
+ } else if (amtToGain < amount) {
+ Seq((0L, amount - amtToGain), (charId, amtToGain))
+ } else {
+ Seq((charId, amount))
+ }
+ (
+ damageRemainder ++ damageOrder.drop(damageRemoveCount) ++ damageOrder.take(damageRemoveCount).map { case (id, _) => (id, 0) },
+ newRecoveryEntry ++ recoveryOrder
+ )
+ }
+
+ private[exp] def contributeWithSupportRecoveryActivity(
+ users: Seq[PlayerSource],
+ wepid: Int,
+ faction: PlanetSideEmpire.Value,
+ amount: Int,
+ time: Long,
+ participants: mutable.LongMap[ContributionStats],
+ damageOrder: Seq[(Long, Int)],
+ recoveryOrder: Seq[(Long, Int)]
+ ): (Seq[(Long, Int)], Seq[(Long, Int)]) = {
+ var outputDamageOrder = damageOrder
+ var outputRecoveryOrder = recoveryOrder
+ val damageParticipants: mutable.LongMap[PlayerSource] = mutable.LongMap[PlayerSource]()
+ users.zip {
+ val numberOfUsers = users.size
+ val out = Array.fill(numberOfUsers)(numberOfUsers / amount)
+ (0 to numberOfUsers % amount).foreach {
+ out(_) += 1
+ }
+ out
+ }.foreach { case (user, subAmount) =>
+ val (a, b) = contributeWithRecoveryActivity(user, user.CharId, wepid, faction, subAmount, time, damageParticipants, participants, outputDamageOrder, outputRecoveryOrder)
+ outputDamageOrder = a
+ outputRecoveryOrder = b
+ }
+ (outputDamageOrder, outputRecoveryOrder)
+ }
+}
+
+//noinspection ScalaUnusedSymbol
+private abstract class RecoveryExperienceContributionProcess(
+ faction : PlanetSideEmpire.Value,
+ contributions: Map[SourceUniqueness, List[InGameActivity]]
+ ) extends RecoveryExperienceContribution {
+ protected var damageInOrder: Seq[(Long, Int)] = Seq[(Long, Int)]()
+ protected var recoveryInOrder: Seq[(Long, Int)] = Seq[(Long, Int)]()
+ protected val contributionsBy: mutable.LongMap[ContributionStats] = mutable.LongMap[ContributionStats]()
+ protected val participants: mutable.LongMap[ContributionStats] = mutable.LongMap[ContributionStats]()
+ protected val damageParticipants: mutable.LongMap[PlayerSource] = mutable.LongMap[PlayerSource]()
+
+ def submit(history: List[InGameActivity]): Unit
+
+ def output(): mutable.LongMap[ContributionStats] = {
+ val output = participants.map { a => a }
+ clear()
+ output
+ }
+
+ def clear(): Unit = {
+ damageInOrder = Nil
+ recoveryInOrder = Nil
+ contributionsBy.clear()
+ participants.clear()
+ damageParticipants.clear()
+ }
+}
+
+private class HealthRecoveryExperienceContributionProcess(
+ private val faction : PlanetSideEmpire.Value,
+ private val contributions: Map[SourceUniqueness, List[InGameActivity]]
+ ) extends RecoveryExperienceContributionProcess(faction, contributions) {
+ def submit(history: List[InGameActivity]): Unit = {
+ history.foreach {
+ case d: DamagingActivity if d.health > 0 =>
+ val (damage, recovery) = RecoveryExperienceContribution.contributeWithDamagingActivity(d, d.health, damageParticipants, participants, damageInOrder, recoveryInOrder)
+ damageInOrder = damage
+ recoveryInOrder = recovery
+ case ht: HealFromTerminal =>
+ val time = ht.time
+ val users = KillContributions.contributeWithTerminalActivity(Seq((ht, ht.term, ht.term.hacked)), faction, contributions, contributionsBy)
+ .collect { case entry: SupportActivityCausedByAnother => entry }
+ .groupBy(_.user.unique)
+ .map(_._2.head.user)
+ .toSeq
+ val (damage, recovery) = RecoveryExperienceContribution.contributeWithSupportRecoveryActivity(users, ht.term.Definition.ObjectId, faction, ht.amount, time, participants, damageInOrder, recoveryInOrder)
+ damageInOrder = damage
+ recoveryInOrder = recovery
+ case h: HealFromEquipment =>
+ val (damage, recovery) = RecoveryExperienceContribution.contributeWithRecoveryActivity(h.user, h.equipment_def.ObjectId, faction, h.amount, h.time, damageParticipants, participants, damageInOrder, recoveryInOrder)
+ damageInOrder = damage
+ recoveryInOrder = recovery
+ case h: HealingActivity =>
+ val (damage, recovery) = RecoveryExperienceContribution.contributeWithRecoveryActivity(wepid = 0, faction, h.amount, h.time, damageParticipants, participants, damageInOrder, recoveryInOrder)
+ damageInOrder = damage
+ recoveryInOrder = recovery
+ case r: RevivingActivity =>
+ val (damage, recovery) = RecoveryExperienceContribution.contributeWithRecoveryActivity(r.equipment.ObjectId, faction, r.amount, r.time, damageParticipants, participants, damageInOrder, recoveryInOrder)
+ damageInOrder = damage
+ recoveryInOrder = recovery
+ case _ => ()
+ }
+ }
+}
+
+private class ArmorRecoveryExperienceContributionProcess(
+ private val faction : PlanetSideEmpire.Value,
+ private val contributions: Map[SourceUniqueness, List[InGameActivity]]
+ ) extends RecoveryExperienceContributionProcess(faction, contributions) {
+ def submit(history: List[InGameActivity]): Unit = {
+ history.foreach {
+ case d: DamagingActivity if d.amount - d.health > 0 =>
+ val (damage, recovery) = RecoveryExperienceContribution.contributeWithDamagingActivity(d, d.amount - d.health, damageParticipants, participants, damageInOrder, recoveryInOrder)
+ damageInOrder = damage
+ recoveryInOrder = recovery
+ case rt: RepairFromTerminal =>
+ val time = rt.time
+ val users = KillContributions.contributeWithTerminalActivity(Seq((rt, rt.term, rt.term.hacked)), faction, contributions, contributionsBy)
+ .collect { case entry: SupportActivityCausedByAnother => entry }
+ .groupBy(_.user.unique)
+ .map(_._2.head.user)
+ .toSeq
+ val (damage, recovery) = RecoveryExperienceContribution.contributeWithSupportRecoveryActivity(users, rt.term.Definition.ObjectId, faction, rt.amount, time, participants, damageInOrder, recoveryInOrder)
+ damageInOrder = damage
+ recoveryInOrder = recovery
+ case r: RepairFromEquipment =>
+ val (damage, recovery) = RecoveryExperienceContribution.contributeWithRecoveryActivity(r.user, r.equipment_def.ObjectId, faction, r.amount, r.time, damageParticipants, participants, damageInOrder, recoveryInOrder)
+ damageInOrder = damage
+ recoveryInOrder = recovery
+ case r: RepairingActivity =>
+ val (damage, recovery) = RecoveryExperienceContribution.contributeWithRecoveryActivity(wepid = 0, faction, r.amount, r.time, damageParticipants, participants, damageInOrder, recoveryInOrder)
+ damageInOrder = damage
+ recoveryInOrder = recovery
+ case _ => ()
+ }
+ }
+}
+
+private class CombinedHealthAndArmorContributionProcess(
+ private val faction : PlanetSideEmpire.Value,
+ private val contributions: Map[SourceUniqueness, List[InGameActivity]],
+ otherSubmissions: Seq[RecoveryExperienceContribution]
+ ) extends RecoveryExperienceContribution {
+ private val process: Seq[RecoveryExperienceContributionProcess] = Seq(
+ new HealthRecoveryExperienceContributionProcess(faction, contributions),
+ new ArmorRecoveryExperienceContributionProcess(faction, contributions)
+ )
+
+ def submit(history: List[InGameActivity]): Unit = {
+ for (elem <- process ++ otherSubmissions) { elem.submit(history) }
+ }
+
+ def output(): mutable.LongMap[ContributionStats] = {
+ val output = combineRecoveryContributions(
+ KillContributions.cullContributorImplements(process.head.output()),
+ KillContributions.cullContributorImplements(process(1).output())
+ )
+ clear()
+ output
+ }
+
+ def clear(): Unit = {
+ process.foreach ( _.clear() )
+ }
+
+ private def combineRecoveryContributions(
+ healthAssists: mutable.LongMap[ContributionStats],
+ armorAssists: mutable.LongMap[ContributionStats]
+ ): mutable.LongMap[ContributionStats] = {
+ healthAssists
+ .map {
+ case out@(id, healthEntry) =>
+ armorAssists.get(id) match {
+ case Some(armorEntry) =>
+ //healthAssists && armorAssists
+ (id, healthEntry.copy(weapons = healthEntry.weapons ++ armorEntry.weapons))
+ case None =>
+ //healthAssists only
+ out
+ }
+ }
+ .addAll {
+ //armorAssists only
+ val healthKeys = healthAssists.keys.toSeq
+ armorAssists.filter { case (id, _) => !healthKeys.contains(id) }
+ }
+ .map {
+ case (id, entry) =>
+ val groupedWeapons = entry.weapons
+ .groupBy(_.equipment_id)
+ .map {
+ case (weaponId, weaponEntries) =>
+ val specificEntries = weaponEntries.filter(_.equipment_id == weaponId)
+ val amount = specificEntries.foldLeft(0)(_ + _.amount)
+ val shots = specificEntries.foldLeft(0)(_ + _.shots)
+ WeaponStats(weaponId, amount, shots, specificEntries.maxBy(_.time).time, 1f)
+ }
+ .toSeq
+ (id, ContributionStats(
+ player = entry.player,
+ weapons = groupedWeapons,
+ amount = entry.amount + entry.amount,
+ total = entry.total + entry.total,
+ shots = groupedWeapons.foldLeft(0)(_ + _.shots),
+ time = groupedWeapons.maxBy(_.time).time
+ ))
+ }
+ }
+}
diff --git a/src/main/scala/net/psforever/objects/zones/exp/Stats.scala b/src/main/scala/net/psforever/objects/zones/exp/Stats.scala
index 103b5110e..758939a47 100644
--- a/src/main/scala/net/psforever/objects/zones/exp/Stats.scala
+++ b/src/main/scala/net/psforever/objects/zones/exp/Stats.scala
@@ -3,22 +3,36 @@ package net.psforever.objects.zones.exp
import net.psforever.objects.sourcing.PlayerSource
+sealed trait ItemUseStats {
+ def equipment_id: Int
+ def shots: Int
+ def time: Long
+ def contributions: Float
+}
+
private case class WeaponStats(
- weapon_id: Int,
+ equipment_id: Int,
amount: Int,
shots: Int,
time: Long,
contributions: Float
- )
+ ) extends ItemUseStats
-private case class ContributionStats(
- player: PlayerSource,
- weapons: Seq[WeaponStats],
- amount: Int,
- total: Int,
- shots: Int,
- time: Long
- )
+private case class EquipmentStats(
+ equipment_id: Int,
+ shots: Int,
+ time: Long,
+ contributions: Float
+ ) extends ItemUseStats
+
+private[exp] case class ContributionStats(
+ player: PlayerSource,
+ weapons: Seq[WeaponStats],
+ amount: Int,
+ total: Int,
+ shots: Int,
+ time: Long
+ )
sealed case class ContributionStatsOutput(
player: PlayerSource,
diff --git a/src/main/scala/net/psforever/objects/zones/exp/Support.scala b/src/main/scala/net/psforever/objects/zones/exp/Support.scala
index 4d518a504..a5334b102 100644
--- a/src/main/scala/net/psforever/objects/zones/exp/Support.scala
+++ b/src/main/scala/net/psforever/objects/zones/exp/Support.scala
@@ -8,7 +8,7 @@ import org.joda.time.{Instant, LocalDateTime => JodaLocalDateTime}
import net.psforever.objects.serverobject.hackable.Hackable
import net.psforever.objects.serverobject.terminals.Terminal
import net.psforever.objects.sourcing.{AmenitySource, PlayerSource, SourceEntry}
-import net.psforever.objects.vital.{DamagingActivity, HealFromEquipment, InGameActivity, ReconstructionActivity, RepairFromEquipment, RepairFromExoSuitChange, RevivingActivity, SpawningActivity, SupportActivityCausedByAnother, TerminalUsedActivity}
+import net.psforever.objects.vital.{HealFromEquipment, InGameActivity, ReconstructionActivity, RepairFromEquipment, RepairFromExoSuitChange, RevivingActivity, SpawningActivity, SupportActivityCausedByAnother, TerminalUsedActivity}
import net.psforever.objects.zones.Zone
import net.psforever.types.{ExoSuitType, PlanetSideEmpire, TransactionType}
import net.psforever.zones.Zones
@@ -18,28 +18,6 @@ import scala.collection.mutable
object Support {
private type SupportActivity = InGameActivity with SupportActivityCausedByAnother
- private[exp] def limitHistoryToThisLife(history: List[InGameActivity]): List[InGameActivity] = {
- val spawnIndex = history.lastIndexWhere {
- case _: SpawningActivity => true
- case _: RevivingActivity => true
- case _ => false
- }
- val endIndex = history.lastIndexWhere {
- case damage: DamagingActivity => damage.data.targetAfter.asInstanceOf[PlayerSource].Health == 0
- case _ => false
- }
- if (spawnIndex == -1 || endIndex == -1) {
- Nil //throw VitalsHistoryException(history.head, "vitals history does not contain expected conditions")
- // } else
- // if (spawnIndex == -1) {
- // Nil //throw VitalsHistoryException(history.head, "vitals history does not contain initial spawn conditions")
- // } else if (endIndex == -1) {
- // Nil //throw VitalsHistoryException(history.last, "vitals history does not contain end of life conditions")
- } else {
- history.slice(spawnIndex, endIndex)
- }
- }
-
private[exp] def onlyOriginalAssistEntries(
first: mutable.LongMap[ContributionStatsOutput],
second: mutable.LongMap[ContributionStatsOutput],
@@ -349,7 +327,7 @@ object Support {
0.5f
}
}
- (charId, ContributionStatsOutput(user, contribution.weapons.map { _.weapon_id }, value))
+ (charId, ContributionStatsOutput(user, contribution.weapons.map { _.equipment_id }, value))
}
//noinspection ScalaUnusedSymbol
@@ -361,7 +339,7 @@ object Support {
charId: Long,
contribution: ContributionStats,
): (Long, ContributionStatsOutput) = {
- (charId, ContributionStatsOutput(contribution.player, contribution.weapons.map { _.weapon_id }, compareList.size.toFloat))
+ (charId, ContributionStatsOutput(contribution.player, contribution.weapons.map { _.equipment_id }, compareList.size.toFloat))
}
private[exp] def wasEverAMax(player: PlayerSource, history: Iterable[InGameActivity]): Boolean = {
diff --git a/src/test/scala/objects/ConverterTest.scala b/src/test/scala/objects/ConverterTest.scala
index 3e5aab963..d20476c9d 100644
--- a/src/test/scala/objects/ConverterTest.scala
+++ b/src/test/scala/objects/ConverterTest.scala
@@ -564,7 +564,7 @@ class ConverterTest extends Specification {
obj.Faction = PlanetSideEmpire.TR
obj.GUID = PlanetSideGUID(90)
obj.Router = PlanetSideGUID(1001)
- obj.Owner = PlanetSideGUID(5001)
+ obj.OwnerGuid = PlanetSideGUID(5001)
obj.Health = 1
obj.Definition.Packet.ConstructorData(obj) match {
case Success(pkt) =>
@@ -596,7 +596,7 @@ class ConverterTest extends Specification {
obj.Faction = PlanetSideEmpire.TR
obj.GUID = PlanetSideGUID(90)
obj.Router = PlanetSideGUID(1001)
- obj.Owner = PlanetSideGUID(5001)
+ obj.OwnerGuid = PlanetSideGUID(5001)
obj.Health = 0
obj.Definition.Packet.ConstructorData(obj) match {
case Success(pkt) =>
@@ -628,7 +628,7 @@ class ConverterTest extends Specification {
obj.Faction = PlanetSideEmpire.TR
obj.GUID = PlanetSideGUID(90)
//obj.Router = PlanetSideGUID(1001)
- obj.Owner = PlanetSideGUID(5001)
+ obj.OwnerGuid = PlanetSideGUID(5001)
obj.Health = 1
obj.Definition.Packet.ConstructorData(obj).isFailure mustEqual true
@@ -641,7 +641,7 @@ class ConverterTest extends Specification {
obj.Faction = PlanetSideEmpire.TR
obj.GUID = PlanetSideGUID(90)
obj.Router = PlanetSideGUID(1001)
- obj.Owner = PlanetSideGUID(5001)
+ obj.OwnerGuid = PlanetSideGUID(5001)
obj.Health = 1
obj.Definition.Packet.DetailedConstructorData(obj).isFailure mustEqual true
}
diff --git a/src/test/scala/objects/DeployableTest.scala b/src/test/scala/objects/DeployableTest.scala
index 3e918dd08..d8a276367 100644
--- a/src/test/scala/objects/DeployableTest.scala
+++ b/src/test/scala/objects/DeployableTest.scala
@@ -34,16 +34,17 @@ class DeployableTest extends Specification {
"Deployable" should {
"know its owner by GUID" in {
val obj = new ExplosiveDeployable(GlobalDefinitions.he_mine)
- obj.Owner.isEmpty mustEqual true
- obj.Owner = PlanetSideGUID(10)
- obj.Owner.contains(PlanetSideGUID(10)) mustEqual true
+ obj.OwnerGuid.isEmpty mustEqual true
+ obj.OwnerGuid = PlanetSideGUID(10)
+ obj.OwnerGuid.contains(PlanetSideGUID(10)) mustEqual true
}
"know its owner by GUID" in {
- val obj = new ExplosiveDeployable(GlobalDefinitions.he_mine)
- obj.OwnerName.isEmpty mustEqual true
- obj.OwnerName = "TestCharacter"
- obj.OwnerName.contains("TestCharacter") mustEqual true
+// val obj = new ExplosiveDeployable(GlobalDefinitions.he_mine)
+// obj.OwnerName.isEmpty mustEqual true
+// obj.OwnerName = "TestCharacter"
+// obj.OwnerName.contains("TestCharacter") mustEqual true
+ ko
}
"know its faction allegiance" in {
@@ -339,8 +340,8 @@ class ExplosiveDeployableJammerTest extends ActorTest {
guid.register(player2, 4)
guid.register(weapon, 5)
j_mine.Zone = zone
- j_mine.Owner = player2
- j_mine.OwnerName = player2.Name
+ j_mine.OwnerGuid = player2
+ //j_mine.OwnerName = player2.Name
j_mine.Faction = PlanetSideEmpire.NC
j_mine.Actor = system.actorOf(Props(classOf[MineDeployableControl], j_mine), "j-mine-control")
@@ -422,8 +423,8 @@ class ExplosiveDeployableJammerExplodeTest extends ActorTest {
guid.register(player2, 4)
guid.register(weapon, 5)
h_mine.Zone = zone
- h_mine.Owner = player2
- h_mine.OwnerName = player2.Name
+ h_mine.OwnerGuid = player2
+ //h_mine.OwnerName = player2.Name
h_mine.Faction = PlanetSideEmpire.NC
h_mine.Actor = system.actorOf(Props(classOf[MineDeployableControl], h_mine), "h-mine-control")
zone.blockMap.addTo(player1)
@@ -528,8 +529,8 @@ class ExplosiveDeployableDestructionTest extends ActorTest {
guid.register(player2, 4)
guid.register(weapon, 5)
h_mine.Zone = zone
- h_mine.Owner = player2
- h_mine.OwnerName = player2.Name
+ h_mine.OwnerGuid = player2
+ //h_mine.OwnerName = player2.Name
h_mine.Faction = PlanetSideEmpire.NC
h_mine.Actor = system.actorOf(Props(classOf[MineDeployableControl], h_mine), "h-mine-control")
diff --git a/src/test/scala/objects/VehicleControlTest.scala b/src/test/scala/objects/VehicleControlTest.scala
index e65bb2d11..5b2bb09b4 100644
--- a/src/test/scala/objects/VehicleControlTest.scala
+++ b/src/test/scala/objects/VehicleControlTest.scala
@@ -310,7 +310,7 @@ class VehicleControlMountingBlockedExosuitTest extends ActorTest {
// Reset to allow further driver mount mounting tests
vehicle.Actor.tell(Mountable.TryDismount(player1, 0), probe.ref)
probe.receiveOne(500 milliseconds) //discard
- vehicle.Owner = None //ensure
+ vehicle.OwnerGuid = None //ensure
vehicle.OwnerName = None //ensure
vehicle.Actor.tell(Mountable.TryMount(player3, 1), probe.ref) // Agile in driver mount allowing all except MAX
VehicleControlTest.checkCanMount(probe, "Agile in driver mount allowing all except MAX")
@@ -369,7 +369,7 @@ class VehicleControlMountingDriverSeatTest extends ActorTest {
"allow players to sit in the driver mount, even if it is locked, if the vehicle is unowned" in {
assert(vehicle.PermissionGroup(0).contains(VehicleLockState.Locked)) //driver group -> locked
assert(vehicle.Seats(0).occupant.isEmpty)
- assert(vehicle.Owner.isEmpty)
+ assert(vehicle.OwnerGuid.isEmpty)
vehicle.Actor.tell(Mountable.TryMount(player1, 1), probe.ref)
VehicleControlTest.checkCanMount(probe, "")
assert(vehicle.Seats(0).occupant.nonEmpty)
@@ -397,7 +397,7 @@ class VehicleControlMountingOwnedLockedDriverSeatTest extends ActorTest {
"block players that are not the current owner from sitting in the driver mount (locked)" in {
assert(vehicle.PermissionGroup(0).contains(VehicleLockState.Locked)) //driver group -> locked
assert(vehicle.Seats(0).occupant.isEmpty)
- vehicle.Owner = player1.GUID
+ vehicle.OwnerGuid = player1.GUID
vehicle.Actor.tell(Mountable.TryMount(player1, 1), probe.ref)
VehicleControlTest.checkCanMount(probe, "")
@@ -434,7 +434,7 @@ class VehicleControlMountingOwnedUnlockedDriverSeatTest extends ActorTest {
vehicle.PermissionGroup(0, 3) //passenger group -> empire
assert(vehicle.PermissionGroup(0).contains(VehicleLockState.Empire)) //driver group -> empire
assert(vehicle.Seats(0).occupant.isEmpty)
- vehicle.Owner = player1.GUID //owner set
+ vehicle.OwnerGuid = player1.GUID //owner set
vehicle.Actor.tell(Mountable.TryMount(player1, 1), probe.ref)
VehicleControlTest.checkCanMount(probe, "")
diff --git a/src/test/scala/objects/VehicleTest.scala b/src/test/scala/objects/VehicleTest.scala
index e850ce911..1d0d7ea1e 100644
--- a/src/test/scala/objects/VehicleTest.scala
+++ b/src/test/scala/objects/VehicleTest.scala
@@ -121,7 +121,7 @@ class VehicleTest extends Specification {
"construct (detailed)" in {
val fury_vehicle = Vehicle(GlobalDefinitions.fury)
- fury_vehicle.Owner.isEmpty mustEqual true
+ fury_vehicle.OwnerGuid.isEmpty mustEqual true
fury_vehicle.Seats.size mustEqual 1
fury_vehicle.Seats(0).definition.restriction mustEqual NoMax
fury_vehicle.Seats(0).isOccupied mustEqual false
@@ -148,30 +148,30 @@ class VehicleTest extends Specification {
"can be owned by a player" in {
val fury_vehicle = Vehicle(GlobalDefinitions.fury)
- fury_vehicle.Owner.isDefined mustEqual false
+ fury_vehicle.OwnerGuid.isDefined mustEqual false
val player1 = Player(avatar1)
player1.GUID = PlanetSideGUID(1)
- fury_vehicle.Owner = player1
- fury_vehicle.Owner.isDefined mustEqual true
- fury_vehicle.Owner.contains(PlanetSideGUID(1)) mustEqual true
+ fury_vehicle.OwnerGuid = player1
+ fury_vehicle.OwnerGuid.isDefined mustEqual true
+ fury_vehicle.OwnerGuid.contains(PlanetSideGUID(1)) mustEqual true
}
"ownership depends on who last was granted it" in {
val fury_vehicle = Vehicle(GlobalDefinitions.fury)
- fury_vehicle.Owner.isDefined mustEqual false
+ fury_vehicle.OwnerGuid.isDefined mustEqual false
val player1 = Player(avatar1)
player1.GUID = PlanetSideGUID(1)
- fury_vehicle.Owner = player1
- fury_vehicle.Owner.isDefined mustEqual true
- fury_vehicle.Owner.contains(PlanetSideGUID(1)) mustEqual true
+ fury_vehicle.OwnerGuid = player1
+ fury_vehicle.OwnerGuid.isDefined mustEqual true
+ fury_vehicle.OwnerGuid.contains(PlanetSideGUID(1)) mustEqual true
val player2 = Player(avatar2)
player2.GUID = PlanetSideGUID(2)
- fury_vehicle.Owner = player2
- fury_vehicle.Owner.isDefined mustEqual true
- fury_vehicle.Owner.contains(PlanetSideGUID(2)) mustEqual true
+ fury_vehicle.OwnerGuid = player2
+ fury_vehicle.OwnerGuid.isDefined mustEqual true
+ fury_vehicle.OwnerGuid.contains(PlanetSideGUID(2)) mustEqual true
}
"can use mount point to get mount number" in {