From da6ea211477fcef6235438bcd6236b9efba82c65 Mon Sep 17 00:00:00 2001 From: Fate-JH Date: Thu, 15 Jun 2023 09:31:51 -0400 Subject: [PATCH] extensivwe changes to the way OwnableByPlayer manages owner user information due to uniqueness, that changes a lot of vehicle and deployable code; fleshing out experience calculation procedure for future testing --- .../actors/session/AvatarActor.scala | 15 +- .../actors/session/support/SessionData.scala | 11 +- .../support/SessionTerminalHandlers.scala | 6 +- .../session/support/ZoningOperations.scala | 10 +- .../psforever/objects/BoomerDeployable.scala | 4 +- .../net/psforever/objects/Deployables.scala | 5 +- .../psforever/objects/OwnableByPlayer.scala | 70 +- .../scala/net/psforever/objects/Player.scala | 12 +- .../net/psforever/objects/SpecialEmp.scala | 9 +- .../psforever/objects/TelepadDeployable.scala | 3 +- .../scala/net/psforever/objects/Vehicle.scala | 4 +- .../net/psforever/objects/Vehicles.scala | 6 +- .../objects/avatar/PlayerControl.scala | 13 +- .../objects/ce/DeployableBehavior.scala | 20 +- .../BattleFrameFlightConverter.scala | 2 +- .../BattleFrameRoboticsConverter.scala | 2 +- .../converter/DroppodConverter.scala | 2 +- .../converter/FieldTurretConverter.scala | 2 +- .../converter/ShieldGeneratorConverter.scala | 2 +- .../converter/SmallDeployableConverter.scala | 2 +- .../converter/SmallTurretConverter.scala | 2 +- .../definition/converter/TRAPConverter.scala | 2 +- .../TelepadDeployableConverter.scala | 2 +- .../converter/VehicleConverter.scala | 2 +- .../pad/VehicleSpawnControl.scala | 119 +-- .../objects/sourcing/VehicleSource.scala | 2 + .../vehicles/control/VehicleControl.scala | 6 +- .../objects/vital/InGameHistory.scala | 114 +-- .../objects/zones/exp/KillAssists.scala | 30 +- .../objects/zones/exp/KillContributions.scala | 750 +++++++----------- .../objects/zones/exp/KillDeathAssists2.scala | 295 ------- .../exp/RecoveryExperienceContribution.scala | 449 +++++++++++ .../psforever/objects/zones/exp/Stats.scala | 34 +- .../psforever/objects/zones/exp/Support.scala | 28 +- src/test/scala/objects/ConverterTest.scala | 8 +- src/test/scala/objects/DeployableTest.scala | 27 +- .../scala/objects/VehicleControlTest.scala | 8 +- src/test/scala/objects/VehicleTest.scala | 24 +- 38 files changed, 1089 insertions(+), 1013 deletions(-) delete mode 100644 src/main/scala/net/psforever/objects/zones/exp/KillDeathAssists2.scala create mode 100644 src/main/scala/net/psforever/objects/zones/exp/RecoveryExperienceContribution.scala 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 {