mirror of
https://github.com/psforever/PSF-LoginServer.git
synced 2026-07-16 08:55:18 +00:00
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
This commit is contained in:
parent
45d2ea544c
commit
da6ea21147
38 changed files with 1089 additions and 1013 deletions
|
|
@ -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.avatar.scoring.{Assist, Death, EquipmentStat, KDAStat, Kill}
|
||||||
import net.psforever.objects.serverobject.affinity.FactionAffinity
|
import net.psforever.objects.serverobject.affinity.FactionAffinity
|
||||||
import net.psforever.objects.vital.InGameHistory
|
import net.psforever.objects.vital.InGameHistory
|
||||||
|
import net.psforever.objects.vehicles.MountedWeapons
|
||||||
import org.joda.time.{LocalDateTime, Seconds}
|
import org.joda.time.{LocalDateTime, Seconds}
|
||||||
|
|
||||||
import scala.collection.mutable
|
import scala.collection.mutable
|
||||||
|
|
@ -2949,14 +2950,16 @@ class AvatarActor(
|
||||||
kdaStat match {
|
kdaStat match {
|
||||||
case kill: Kill =>
|
case kill: Kill =>
|
||||||
val playerSource = PlayerSource(player)
|
val playerSource = PlayerSource(player)
|
||||||
(kill.info.interaction.cause match {
|
val historyTranscript = (kill.info.interaction.cause match {
|
||||||
case pr: ProjectileReason => pr.projectile.mounted_in.map { a => zone.GUID(a._1) }
|
case pr: ProjectileReason => pr.projectile.mounted_in.flatMap { a => zone.GUID(a._1) } //what fired the projectile
|
||||||
case _ => None
|
case _ => None
|
||||||
}).collect {
|
}) match {
|
||||||
case Some(mount: PlanetSideGameObject with FactionAffinity with InGameHistory) =>
|
case Some(mount: PlanetSideGameObject with FactionAffinity with InGameHistory with MountedWeapons) =>
|
||||||
player.ContributionFrom(mount)
|
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 _: Assist =>
|
||||||
()
|
()
|
||||||
case _: Death =>
|
case _: Death =>
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ package net.psforever.actors.session.support
|
||||||
|
|
||||||
import akka.actor.typed.scaladsl.adapter._
|
import akka.actor.typed.scaladsl.adapter._
|
||||||
import akka.actor.{ActorContext, ActorRef, Cancellable, typed}
|
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 net.psforever.objects.zones.blockmap.{SectorGroup, SectorPopulation}
|
||||||
|
|
||||||
import scala.collection.mutable
|
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 */
|
/* line 2: vehicle is not mounted in anything or, if it is, its seats are empty */
|
||||||
if (
|
if (
|
||||||
(session.account.gm ||
|
(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 &&
|
(player.Faction == vehicle.Faction &&
|
||||||
(vehicle.Definition.CanBeOwned.nonEmpty &&
|
(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.MountedIn.isEmpty || !vehicle.Seats.values.exists(_.isOccupied))
|
||||||
) {
|
) {
|
||||||
vehicle.Actor ! Vehicle.Deconstruct()
|
vehicle.Actor ! Vehicle.Deconstruct()
|
||||||
|
|
@ -442,7 +442,7 @@ class SessionData(
|
||||||
}
|
}
|
||||||
|
|
||||||
case Some(obj: Deployable) =>
|
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()
|
obj.Actor ! Deployable.Deconstruct()
|
||||||
} else {
|
} else {
|
||||||
log.warn(s"RequestDestroy: ${player.Name} must own the deployable in order to deconstruct it")
|
log.warn(s"RequestDestroy: ${player.Name} must own the deployable in order to deconstruct it")
|
||||||
|
|
@ -1377,7 +1377,7 @@ class SessionData(
|
||||||
//access to trunk
|
//access to trunk
|
||||||
if (
|
if (
|
||||||
obj.AccessingTrunk.isEmpty &&
|
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))
|
.contains(player.GUID))
|
||||||
) {
|
) {
|
||||||
log.info(s"${player.Name} is looking in the ${obj.Definition.Name}'s trunk")
|
log.info(s"${player.Name} is looking in the ${obj.Definition.Name}'s trunk")
|
||||||
|
|
@ -2473,6 +2473,7 @@ class SessionData(
|
||||||
continent.id,
|
continent.id,
|
||||||
LocalAction.RouterTelepadTransport(pguid, pguid, sguid, dguid)
|
LocalAction.RouterTelepadTransport(pguid, pguid, sguid, dguid)
|
||||||
)
|
)
|
||||||
|
player.LogActivity(VehicleDismountActivity(VehicleSource(router), PlayerSource(player)))
|
||||||
} else {
|
} else {
|
||||||
log.warn(s"UseRouterTelepadSystem: ${player.Name} can not teleport")
|
log.warn(s"UseRouterTelepadSystem: ${player.Name} can not teleport")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,9 @@
|
||||||
package net.psforever.actors.session.support
|
package net.psforever.actors.session.support
|
||||||
|
|
||||||
import akka.actor.{ActorContext, typed}
|
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.ExecutionContext.Implicits.global
|
||||||
import scala.concurrent.Future
|
import scala.concurrent.Future
|
||||||
//
|
//
|
||||||
|
|
@ -68,8 +71,8 @@ class SessionTerminalHandlers(
|
||||||
order match {
|
order match {
|
||||||
case Terminal.BuyEquipment(item)
|
case Terminal.BuyEquipment(item)
|
||||||
if tplayer.avatar.purchaseCooldown(item.Definition).nonEmpty =>
|
if tplayer.avatar.purchaseCooldown(item.Definition).nonEmpty =>
|
||||||
lastTerminalOrderFulfillment = true
|
|
||||||
sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, success = false))
|
sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, success = false))
|
||||||
|
lastTerminalOrderFulfillment = true
|
||||||
|
|
||||||
case Terminal.BuyEquipment(item) =>
|
case Terminal.BuyEquipment(item) =>
|
||||||
avatarActor ! AvatarActor.UpdatePurchaseTime(item.Definition)
|
avatarActor ! AvatarActor.UpdatePurchaseTime(item.Definition)
|
||||||
|
|
@ -141,6 +144,7 @@ class SessionTerminalHandlers(
|
||||||
if (GlobalDefinitions.isBattleFrameVehicle(vehicle.Definition)) {
|
if (GlobalDefinitions.isBattleFrameVehicle(vehicle.Definition)) {
|
||||||
sendResponse(UnuseItemMessage(player.GUID, msg.terminal_guid))
|
sendResponse(UnuseItemMessage(player.GUID, msg.terminal_guid))
|
||||||
}
|
}
|
||||||
|
player.LogActivity(TerminalUsedActivity(AmenitySource(term), msg.transaction_type))
|
||||||
}.orElse {
|
}.orElse {
|
||||||
log.error(
|
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"
|
s"${tplayer.Name} wanted to spawn a vehicle, but there was no spawn pad associated with terminal ${msg.terminal_guid} to accept it"
|
||||||
|
|
|
||||||
|
|
@ -258,7 +258,7 @@ class ZoningOperations(
|
||||||
obj.GUID,
|
obj.GUID,
|
||||||
Deployable.Icon(obj.Definition.Item),
|
Deployable.Icon(obj.Definition.Item),
|
||||||
obj.Position,
|
obj.Position,
|
||||||
obj.Owner.getOrElse(PlanetSideGUID(0))
|
obj.OwnerGuid.getOrElse(PlanetSideGUID(0))
|
||||||
)
|
)
|
||||||
sendResponse(DeployableObjectsInfoMessage(DeploymentAction.Build, deployInfo))
|
sendResponse(DeployableObjectsInfoMessage(DeploymentAction.Build, deployInfo))
|
||||||
})
|
})
|
||||||
|
|
@ -2824,7 +2824,7 @@ class ZoningOperations(
|
||||||
continent.DeployableList
|
continent.DeployableList
|
||||||
.filter(_.OwnerName.contains(name))
|
.filter(_.OwnerName.contains(name))
|
||||||
.foreach(obj => {
|
.foreach(obj => {
|
||||||
obj.Owner = guid
|
obj.OwnerGuid = guid
|
||||||
drawDeloyableIcon(obj)
|
drawDeloyableIcon(obj)
|
||||||
})
|
})
|
||||||
drawDeloyableIcon = DontRedrawIcons
|
drawDeloyableIcon = DontRedrawIcons
|
||||||
|
|
@ -2832,7 +2832,7 @@ class ZoningOperations(
|
||||||
//assert or transfer vehicle ownership
|
//assert or transfer vehicle ownership
|
||||||
continent.GUID(player.avatar.vehicle) match {
|
continent.GUID(player.avatar.vehicle) match {
|
||||||
case Some(vehicle: Vehicle) if vehicle.OwnerName.contains(tplayer.Name) =>
|
case Some(vehicle: Vehicle) if vehicle.OwnerName.contains(tplayer.Name) =>
|
||||||
vehicle.Owner = guid
|
vehicle.OwnerGuid = guid
|
||||||
continent.VehicleEvents ! VehicleServiceMessage(
|
continent.VehicleEvents ! VehicleServiceMessage(
|
||||||
s"${tplayer.Faction}",
|
s"${tplayer.Faction}",
|
||||||
VehicleAction.Ownership(guid, vehicle.GUID)
|
VehicleAction.Ownership(guid, vehicle.GUID)
|
||||||
|
|
@ -2907,7 +2907,7 @@ class ZoningOperations(
|
||||||
val effortBy = nextSpawnPoint
|
val effortBy = nextSpawnPoint
|
||||||
.collect { case sp: SpawnTube => (sp, continent.GUID(sp.Owner.GUID)) }
|
.collect { case sp: SpawnTube => (sp, continent.GUID(sp.Owner.GUID)) }
|
||||||
.collect {
|
.collect {
|
||||||
case (_, Some(v: Vehicle)) => continent.GUID(v.Owner)
|
case (_, Some(v: Vehicle)) => continent.GUID(v.OwnerGuid)
|
||||||
case (sp, Some(_: Building)) => Some(sp)
|
case (sp, Some(_: Building)) => Some(sp)
|
||||||
}
|
}
|
||||||
.collect { case Some(thing: PlanetSideGameObject with FactionAffinity) => Some(SourceEntry(thing)) }
|
.collect { case Some(thing: PlanetSideGameObject with FactionAffinity) => Some(SourceEntry(thing)) }
|
||||||
|
|
@ -2962,7 +2962,7 @@ class ZoningOperations(
|
||||||
obj.GUID,
|
obj.GUID,
|
||||||
Deployable.Icon(obj.Definition.Item),
|
Deployable.Icon(obj.Definition.Item),
|
||||||
obj.Position,
|
obj.Position,
|
||||||
obj.Owner.getOrElse(PlanetSideGUID(0))
|
obj.OwnerGuid.getOrElse(PlanetSideGUID(0))
|
||||||
)
|
)
|
||||||
sendResponse(DeployableObjectsInfoMessage(DeploymentAction.Build, deployInfo))
|
sendResponse(DeployableObjectsInfoMessage(DeploymentAction.Build, deployInfo))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,9 @@ class BoomerDeployableControl(mine: BoomerDeployable)
|
||||||
|
|
||||||
override def loseOwnership(faction: PlanetSideEmpire.Value): Unit = {
|
override def loseOwnership(faction: PlanetSideEmpire.Value): Unit = {
|
||||||
super.loseOwnership(PlanetSideEmpire.NEUTRAL)
|
super.loseOwnership(PlanetSideEmpire.NEUTRAL)
|
||||||
mine.OwnerName = None
|
val guid = mine.OwnerGuid
|
||||||
|
mine.AssignOwnership(None)
|
||||||
|
mine.OwnerGuid = guid
|
||||||
}
|
}
|
||||||
|
|
||||||
override def gainOwnership(player: Player): Unit = {
|
override def gainOwnership(player: Player): Unit = {
|
||||||
|
|
|
||||||
|
|
@ -89,8 +89,7 @@ object Deployables {
|
||||||
.foreach { p =>
|
.foreach { p =>
|
||||||
p.Actor ! Player.LoseDeployable(target)
|
p.Actor ! Player.LoseDeployable(target)
|
||||||
}
|
}
|
||||||
target.Owner = None
|
target.AssignOwnership(None)
|
||||||
target.OwnerName = None
|
|
||||||
}
|
}
|
||||||
events ! LocalServiceMessage(
|
events ! LocalServiceMessage(
|
||||||
s"${target.Faction}",
|
s"${target.Faction}",
|
||||||
|
|
@ -119,7 +118,7 @@ object Deployables {
|
||||||
.collect {
|
.collect {
|
||||||
case Some(obj: Deployable) =>
|
case Some(obj: Deployable) =>
|
||||||
obj.Actor ! Deployable.Ownership(None)
|
obj.Actor ! Deployable.Ownership(None)
|
||||||
obj.Owner = None //fast-forward the effect
|
obj.OwnerGuid = None //fast-forward the effect
|
||||||
obj
|
obj
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,45 +1,33 @@
|
||||||
// Copyright (c) 2019 PSForever
|
// Copyright (c) 2019 PSForever
|
||||||
package net.psforever.objects
|
package net.psforever.objects
|
||||||
|
|
||||||
|
import net.psforever.objects.sourcing.{PlayerSource, UniquePlayer}
|
||||||
import net.psforever.types.PlanetSideGUID
|
import net.psforever.types.PlanetSideGUID
|
||||||
|
|
||||||
trait OwnableByPlayer {
|
trait OwnableByPlayer {
|
||||||
private var owner: Option[PlanetSideGUID] = None
|
private var owner: Option[UniquePlayer] = None
|
||||||
private var ownerName: Option[String] = None
|
private var ownerGuid: Option[PlanetSideGUID] = None
|
||||||
private var originalOwnerName: Option[String] = 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 {
|
owner match {
|
||||||
case Some(_) =>
|
case Some(_) =>
|
||||||
this.owner = owner
|
ownerGuid = owner
|
||||||
case None =>
|
case None =>
|
||||||
this.owner = None
|
ownerGuid = None
|
||||||
}
|
}
|
||||||
Owner
|
OwnerGuid
|
||||||
}
|
}
|
||||||
|
|
||||||
def OwnerName: Option[String] = ownerName
|
def OwnerName: Option[String] = owner.map { _.name }
|
||||||
|
|
||||||
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 OriginalOwnerName: Option[String] = originalOwnerName
|
def OriginalOwnerName: Option[String] = originalOwnerName
|
||||||
|
|
||||||
|
|
@ -56,14 +44,30 @@ trait OwnableByPlayer {
|
||||||
* @return na
|
* @return na
|
||||||
*/
|
*/
|
||||||
def AssignOwnership(playerOpt: Option[Player]): OwnableByPlayer = {
|
def AssignOwnership(playerOpt: Option[Player]): OwnableByPlayer = {
|
||||||
playerOpt match {
|
(originalOwnerName, playerOpt) match {
|
||||||
case Some(player) =>
|
case (None, Some(player)) =>
|
||||||
Owner = player
|
owner = Some(PlayerSource(player).unique)
|
||||||
OwnerName = player
|
originalOwnerName = originalOwnerName.orElse { Some(player.Name) }
|
||||||
case None =>
|
OwnerGuid = player
|
||||||
Owner = None
|
case (_, Some(player)) =>
|
||||||
OwnerName = None
|
owner = Some(PlayerSource(player).unique)
|
||||||
|
OwnerGuid = player
|
||||||
|
case (_, None) =>
|
||||||
|
owner = None
|
||||||
|
OwnerGuid = None
|
||||||
}
|
}
|
||||||
this
|
this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* na
|
||||||
|
* @param ownable na
|
||||||
|
* @return na
|
||||||
|
*/
|
||||||
|
def AssignOwnership(ownable: OwnableByPlayer): OwnableByPlayer = {
|
||||||
|
owner = ownable.owner
|
||||||
|
originalOwnerName = originalOwnerName.orElse { ownable.originalOwnerName }
|
||||||
|
OwnerGuid = ownable.OwnerGuid
|
||||||
|
this
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -557,16 +557,16 @@ class Player(var avatar: Avatar)
|
||||||
|
|
||||||
def DamageModel: DamageResistanceModel = exosuit.asInstanceOf[DamageResistanceModel]
|
def DamageModel: DamageResistanceModel = exosuit.asInstanceOf[DamageResistanceModel]
|
||||||
|
|
||||||
def canEqual(other: Any): Boolean = other.isInstanceOf[Player]
|
override def GetContributionDuringPeriod(list: List[InGameActivity], duration: Long): List[InGameActivity] = {
|
||||||
|
val earliestEndTime = System.currentTimeMillis() - duration
|
||||||
override def GetContributionDuringPeriod(ending: Long, duration: Long): List[InGameActivity] = {
|
|
||||||
val start = ending - duration
|
|
||||||
History.collect {
|
History.collect {
|
||||||
case heal: HealFromEquipment if heal.time <= ending && heal.time > start => heal
|
case heal: HealFromEquipment if heal.amount > 0 && heal.time > earliestEndTime => heal
|
||||||
case repair: RepairFromEquipment if repair.time <= ending && repair.time > start => repair
|
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 =
|
override def equals(other: Any): Boolean =
|
||||||
other match {
|
other match {
|
||||||
case that: Player =>
|
case that: Player =>
|
||||||
|
|
|
||||||
|
|
@ -112,11 +112,10 @@ object SpecialEmp {
|
||||||
faction: PlanetSideEmpire.Value
|
faction: PlanetSideEmpire.Value
|
||||||
): (PlanetSideGameObject, PlanetSideGameObject, Float) => Boolean = {
|
): (PlanetSideGameObject, PlanetSideGameObject, Float) => Boolean = {
|
||||||
distanceCheck(new PlanetSideServerObject with OwnableByPlayer {
|
distanceCheck(new PlanetSideServerObject with OwnableByPlayer {
|
||||||
Owner = Some(owner.GUID)
|
owner match {
|
||||||
OwnerName = owner match {
|
case p: Player => AssignOwnership(p)
|
||||||
case p: Player => p.Name
|
case o: OwnableByPlayer => AssignOwnership(o)
|
||||||
case o: OwnableByPlayer => o.OwnerName.getOrElse("")
|
case _ => OwnerGuid_=(Some(owner.GUID))
|
||||||
case _ => ""
|
|
||||||
}
|
}
|
||||||
Position = position
|
Position = position
|
||||||
def Faction = faction
|
def Faction = faction
|
||||||
|
|
|
||||||
|
|
@ -103,8 +103,7 @@ class TelepadDeployableControl(tpad: TelepadDeployable)
|
||||||
override def startOwnerlessDecay(): Unit = {
|
override def startOwnerlessDecay(): Unit = {
|
||||||
//telepads do not decay when they become ownerless
|
//telepads do not decay when they become ownerless
|
||||||
//telepad decay is tied to their lifecycle with routers
|
//telepad decay is tied to their lifecycle with routers
|
||||||
tpad.Owner = None
|
tpad.AssignOwnership(None)
|
||||||
tpad.OwnerName = None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override def finalizeDeployable(callback: ActorRef): Unit = {
|
override def finalizeDeployable(callback: ActorRef): Unit = {
|
||||||
|
|
|
||||||
|
|
@ -476,7 +476,7 @@ class Vehicle(private val vehicleDef: VehicleDefinition)
|
||||||
if (trunkAccess.isEmpty || trunkAccess.contains(player.GUID)) {
|
if (trunkAccess.isEmpty || trunkAccess.contains(player.GUID)) {
|
||||||
groupPermissions(3) match {
|
groupPermissions(3) match {
|
||||||
case VehicleLockState.Locked => //only the owner
|
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
|
case VehicleLockState.Group => //anyone in the owner's squad or platoon
|
||||||
faction == player.Faction //TODO this is not correct
|
faction == player.Faction //TODO this is not correct
|
||||||
case VehicleLockState.Empire => //anyone of the owner's faction
|
case VehicleLockState.Empire => //anyone of the owner's faction
|
||||||
|
|
@ -681,6 +681,6 @@ object Vehicle {
|
||||||
*/
|
*/
|
||||||
def toString(obj: Vehicle): String = {
|
def toString(obj: Vehicle): String = {
|
||||||
val occupancy = obj.Seats.values.count(seat => seat.isOccupied)
|
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)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@ object Vehicles {
|
||||||
* `None`, otherwise
|
* `None`, otherwise
|
||||||
*/
|
*/
|
||||||
def Disown(guid: PlanetSideGUID, vehicle: Vehicle): Option[Vehicle] =
|
def Disown(guid: PlanetSideGUID, vehicle: Vehicle): Option[Vehicle] =
|
||||||
vehicle.Zone.GUID(vehicle.Owner) match {
|
vehicle.Zone.GUID(vehicle.OwnerGuid) match {
|
||||||
case Some(player: Player) =>
|
case Some(player: Player) =>
|
||||||
if (player.avatar.vehicle.contains(guid)) {
|
if (player.avatar.vehicle.contains(guid)) {
|
||||||
player.avatar.vehicle = None
|
player.avatar.vehicle = None
|
||||||
|
|
@ -127,7 +127,7 @@ object Vehicles {
|
||||||
*/
|
*/
|
||||||
def Disown(player: Player, vehicle: Vehicle): Option[Vehicle] = {
|
def Disown(player: Player, vehicle: Vehicle): Option[Vehicle] = {
|
||||||
val pguid = player.GUID
|
val pguid = player.GUID
|
||||||
if (vehicle.Owner.contains(pguid)) {
|
if (vehicle.OwnerGuid.contains(pguid)) {
|
||||||
vehicle.AssignOwnership(None)
|
vehicle.AssignOwnership(None)
|
||||||
//vehicle.Zone.VehicleEvents ! VehicleServiceMessage(player.Name, VehicleAction.Ownership(pguid, PlanetSideGUID(0)))
|
//vehicle.Zone.VehicleEvents ! VehicleServiceMessage(player.Name, VehicleAction.Ownership(pguid, PlanetSideGUID(0)))
|
||||||
val vguid = vehicle.GUID
|
val vguid = vehicle.GUID
|
||||||
|
|
@ -272,7 +272,7 @@ object Vehicles {
|
||||||
}
|
}
|
||||||
case _ => ;
|
case _ => ;
|
||||||
}
|
}
|
||||||
target.Owner match {
|
target.OwnerGuid match {
|
||||||
case Some(previousOwnerGuid: PlanetSideGUID) =>
|
case Some(previousOwnerGuid: PlanetSideGUID) =>
|
||||||
// Remove ownership of the vehicle from the previous player
|
// Remove ownership of the vehicle from the previous player
|
||||||
zone.GUID(previousOwnerGuid) match {
|
zone.GUID(previousOwnerGuid) match {
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ import net.psforever.objects.locker.LockerContainerControl
|
||||||
import net.psforever.objects.serverobject.environment._
|
import net.psforever.objects.serverobject.environment._
|
||||||
import net.psforever.objects.serverobject.repair.Repairable
|
import net.psforever.objects.serverobject.repair.Repairable
|
||||||
import net.psforever.objects.serverobject.shuttle.OrbitalShuttlePad
|
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.collision.CollisionReason
|
||||||
import net.psforever.objects.vital.environment.EnvironmentReason
|
import net.psforever.objects.vital.environment.EnvironmentReason
|
||||||
import net.psforever.objects.vital.etc.{PainboxReason, SuicideReason}
|
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) =>
|
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 {
|
order match {
|
||||||
case Terminal.BuyExosuit(exosuit, subtype) =>
|
case Terminal.BuyExosuit(exosuit, subtype) =>
|
||||||
val result = setExoSuit(exosuit, subtype)
|
val result = setExoSuit(exosuit, subtype)
|
||||||
|
|
@ -366,6 +372,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
|
||||||
player.Name,
|
player.Name,
|
||||||
AvatarAction.TerminalOrderResult(msg.terminal_guid, msg.transaction_type, result)
|
AvatarAction.TerminalOrderResult(msg.terminal_guid, msg.transaction_type, result)
|
||||||
)
|
)
|
||||||
|
terminalUsedAction
|
||||||
|
|
||||||
case Terminal.InfantryLoadout(exosuit, subtype, holsters, inventory) =>
|
case Terminal.InfantryLoadout(exosuit, subtype, holsters, inventory) =>
|
||||||
log.info(s"${player.Name} wants to change equipment loadout to their option #${msg.unk1 + 1}")
|
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,
|
player.Name,
|
||||||
AvatarAction.TerminalOrderResult(msg.terminal_guid, msg.transaction_type, result=true)
|
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, _, _) =>
|
case Zone.Ground.ItemOnGround(item, _, _) =>
|
||||||
|
|
|
||||||
|
|
@ -66,16 +66,16 @@ trait DeployableBehavior {
|
||||||
finalizeDeployable(callback)
|
finalizeDeployable(callback)
|
||||||
|
|
||||||
case Deployable.Ownership(None)
|
case Deployable.Ownership(None)
|
||||||
if DeployableObject.Owner.nonEmpty =>
|
if DeployableObject.OwnerGuid.nonEmpty =>
|
||||||
val obj = DeployableObject
|
val obj = DeployableObject
|
||||||
if (constructed.contains(true)) {
|
if (constructed.contains(true)) {
|
||||||
loseOwnership(obj.Faction)
|
loseOwnership(obj.Faction)
|
||||||
} else {
|
} else {
|
||||||
obj.Owner = None
|
obj.OwnerGuid = None
|
||||||
}
|
}
|
||||||
|
|
||||||
case Deployable.Ownership(Some(player))
|
case Deployable.Ownership(Some(player))
|
||||||
if !DeployableObject.Destroyed && DeployableObject.Owner.isEmpty =>
|
if !DeployableObject.Destroyed && DeployableObject.OwnerGuid.isEmpty =>
|
||||||
if (constructed.contains(true)) {
|
if (constructed.contains(true)) {
|
||||||
gainOwnership(player)
|
gainOwnership(player)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -132,12 +132,12 @@ trait DeployableBehavior {
|
||||||
|
|
||||||
def startOwnerlessDecay(): Unit = {
|
def startOwnerlessDecay(): Unit = {
|
||||||
val obj = DeployableObject
|
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
|
//without an owner, this deployable should begin to decay and will deconstruct later
|
||||||
import scala.concurrent.ExecutionContext.Implicits.global
|
import scala.concurrent.ExecutionContext.Implicits.global
|
||||||
decay = context.system.scheduler.scheduleOnce(Deployable.decay, self, Deployable.Deconstruct())
|
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 guid = obj.GUID
|
||||||
val zone = obj.Zone
|
val zone = obj.Zone
|
||||||
val originalFaction = obj.Faction
|
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) {
|
if (originalFaction != toFaction) {
|
||||||
obj.Faction = toFaction
|
obj.Faction = toFaction
|
||||||
val localEvents = zone.LocalEvents
|
val localEvents = zone.LocalEvents
|
||||||
|
|
@ -199,7 +199,7 @@ trait DeployableBehavior {
|
||||||
zone.LocalEvents ! LocalServiceMessage(
|
zone.LocalEvents ! LocalServiceMessage(
|
||||||
zone.id,
|
zone.id,
|
||||||
LocalAction.TriggerEffectLocation(
|
LocalAction.TriggerEffectLocation(
|
||||||
obj.Owner.getOrElse(Service.defaultPlayerGUID),
|
obj.OwnerGuid.getOrElse(Service.defaultPlayerGUID),
|
||||||
"spawn_object_effect",
|
"spawn_object_effect",
|
||||||
obj.Position,
|
obj.Position,
|
||||||
obj.Orientation
|
obj.Orientation
|
||||||
|
|
@ -234,7 +234,7 @@ trait DeployableBehavior {
|
||||||
val obj = DeployableObject
|
val obj = DeployableObject
|
||||||
val zone = obj.Zone
|
val zone = obj.Zone
|
||||||
val localEvents = zone.LocalEvents
|
val localEvents = zone.LocalEvents
|
||||||
val owner = obj.Owner.getOrElse(Service.defaultPlayerGUID)
|
val owner = obj.OwnerGuid.getOrElse(Service.defaultPlayerGUID)
|
||||||
obj.OwnerName match {
|
obj.OwnerName match {
|
||||||
case Some(_) =>
|
case Some(_) =>
|
||||||
case None =>
|
case None =>
|
||||||
|
|
@ -306,7 +306,9 @@ trait DeployableBehavior {
|
||||||
if (!obj.Destroyed) {
|
if (!obj.Destroyed) {
|
||||||
Deployables.AnnounceDestroyDeployable(obj)
|
Deployables.AnnounceDestroyDeployable(obj)
|
||||||
}
|
}
|
||||||
obj.OwnerName = None
|
val guid = obj.OwnerGuid
|
||||||
|
obj.AssignOwnership(None)
|
||||||
|
obj.OwnerGuid = guid
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ class BattleFrameFlightConverter extends ObjectCreateConverter[Vehicle]() {
|
||||||
jammered = false,
|
jammered = false,
|
||||||
v4 = None,
|
v4 = None,
|
||||||
v5 = None,
|
v5 = None,
|
||||||
obj.Owner match {
|
obj.OwnerGuid match {
|
||||||
case Some(owner) => owner
|
case Some(owner) => owner
|
||||||
case None => PlanetSideGUID(0)
|
case None => PlanetSideGUID(0)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ class BattleFrameRoboticsConverter extends ObjectCreateConverter[Vehicle]() {
|
||||||
jammered = obj.Jammed,
|
jammered = obj.Jammed,
|
||||||
v4 = None,
|
v4 = None,
|
||||||
v5 = None,
|
v5 = None,
|
||||||
obj.Owner match {
|
obj.OwnerGuid match {
|
||||||
case Some(owner) => owner
|
case Some(owner) => owner
|
||||||
case None => PlanetSideGUID(0)
|
case None => PlanetSideGUID(0)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ class DroppodConverter extends ObjectCreateConverter[Vehicle]() {
|
||||||
jammered = obj.Jammed,
|
jammered = obj.Jammed,
|
||||||
v4 = Some(false),
|
v4 = Some(false),
|
||||||
v5 = None,
|
v5 = None,
|
||||||
obj.Owner match {
|
obj.OwnerGuid match {
|
||||||
case Some(owner) => owner
|
case Some(owner) => owner
|
||||||
case None => PlanetSideGUID(0)
|
case None => PlanetSideGUID(0)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ class FieldTurretConverter extends ObjectCreateConverter[TurretDeployable]() {
|
||||||
jammered = obj.Jammed,
|
jammered = obj.Jammed,
|
||||||
Some(false),
|
Some(false),
|
||||||
None,
|
None,
|
||||||
obj.Owner match {
|
obj.OwnerGuid match {
|
||||||
case Some(owner) => owner
|
case Some(owner) => owner
|
||||||
case None => PlanetSideGUID(0)
|
case None => PlanetSideGUID(0)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ class ShieldGeneratorConverter extends ObjectCreateConverter[ShieldGeneratorDepl
|
||||||
jammered = obj.Jammed,
|
jammered = obj.Jammed,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
obj.Owner match {
|
obj.OwnerGuid match {
|
||||||
case Some(owner) => owner
|
case Some(owner) => owner
|
||||||
case None => PlanetSideGUID(0)
|
case None => PlanetSideGUID(0)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ class SmallDeployableConverter extends ObjectCreateConverter[Deployable]() {
|
||||||
},
|
},
|
||||||
Some(false),
|
Some(false),
|
||||||
None,
|
None,
|
||||||
obj.Owner match {
|
obj.OwnerGuid match {
|
||||||
case Some(owner) => owner
|
case Some(owner) => owner
|
||||||
case None => PlanetSideGUID(0)
|
case None => PlanetSideGUID(0)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ class SmallTurretConverter extends ObjectCreateConverter[TurretDeployable]() {
|
||||||
jammered = obj.Jammed,
|
jammered = obj.Jammed,
|
||||||
Some(true),
|
Some(true),
|
||||||
None,
|
None,
|
||||||
obj.Owner match {
|
obj.OwnerGuid match {
|
||||||
case Some(owner) => owner
|
case Some(owner) => owner
|
||||||
case None => PlanetSideGUID(0)
|
case None => PlanetSideGUID(0)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ class TRAPConverter extends ObjectCreateConverter[TrapDeployable]() {
|
||||||
false,
|
false,
|
||||||
Some(true),
|
Some(true),
|
||||||
None,
|
None,
|
||||||
obj.Owner match {
|
obj.OwnerGuid match {
|
||||||
case Some(owner) => owner
|
case Some(owner) => owner
|
||||||
case None => PlanetSideGUID(0)
|
case None => PlanetSideGUID(0)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ class TelepadDeployableConverter extends ObjectCreateConverter[TelepadDeployable
|
||||||
false,
|
false,
|
||||||
None,
|
None,
|
||||||
Some(router.guid),
|
Some(router.guid),
|
||||||
obj.Owner.getOrElse(PlanetSideGUID(0))
|
obj.OwnerGuid.getOrElse(PlanetSideGUID(0))
|
||||||
),
|
),
|
||||||
unk1 = 87,
|
unk1 = 87,
|
||||||
unk2 = 12
|
unk2 = 12
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ class VehicleConverter extends ObjectCreateConverter[Vehicle]() {
|
||||||
jammered = obj.Jammed,
|
jammered = obj.Jammed,
|
||||||
v4 = Some(false),
|
v4 = Some(false),
|
||||||
v5 = None,
|
v5 = None,
|
||||||
obj.Owner match {
|
obj.OwnerGuid match {
|
||||||
case Some(owner) => owner
|
case Some(owner) => owner
|
||||||
case None => PlanetSideGUID(0)
|
case None => PlanetSideGUID(0)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,17 @@
|
||||||
// Copyright (c) 2017 PSForever
|
// Copyright (c) 2017 PSForever
|
||||||
package net.psforever.objects.serverobject.pad
|
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.avatar.SpecialCarry
|
||||||
import net.psforever.objects.entity.WorldEntity
|
import net.psforever.objects.entity.WorldEntity
|
||||||
import net.psforever.objects.guid.{GUIDTask, TaskWorkflow}
|
import net.psforever.objects.guid.{GUIDTask, TaskWorkflow}
|
||||||
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
|
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
|
||||||
import net.psforever.objects.serverobject.pad.process.{VehicleSpawnControlBase, VehicleSpawnControlConcealPlayer}
|
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.zones.{Zone, ZoneAware, Zoning}
|
||||||
import net.psforever.objects.{Default, PlanetSideGameObject, Player, Vehicle}
|
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.annotation.tailrec
|
||||||
import scala.concurrent.ExecutionContext.Implicits.global
|
import scala.concurrent.ExecutionContext.Implicits.global
|
||||||
|
|
@ -38,38 +40,39 @@ class VehicleSpawnControl(pad: VehicleSpawnPad)
|
||||||
with FactionAffinityBehavior.Check {
|
with FactionAffinityBehavior.Check {
|
||||||
|
|
||||||
/** a reminder sent to future customers */
|
/** a reminder sent to future customers */
|
||||||
var periodicReminder: Cancellable = Default.Cancellable
|
private var periodicReminder: Cancellable = Default.Cancellable
|
||||||
|
|
||||||
/** repeatedly test whether queued orders are valid */
|
/** 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 */
|
/** 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;
|
/** the current vehicle order being acted upon;
|
||||||
* used as a guard condition to control order processing rate
|
* 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 */
|
/** 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 = ""
|
def LogId = ""
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The first chained action of the vehicle spawning process.
|
* 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")
|
context.actorOf(Props(classOf[VehicleSpawnControlConcealPlayer], pad), s"${context.parent.path.name}-conceal")
|
||||||
|
|
||||||
def FactionObject: FactionAffinity = pad
|
def FactionObject: FactionAffinity = pad
|
||||||
|
|
||||||
import akka.actor.SupervisorStrategy._
|
import akka.actor.SupervisorStrategy._
|
||||||
override val supervisorStrategy = {
|
|
||||||
import akka.actor.OneForOneStrategy
|
override val supervisorStrategy: OneForOneStrategy = {
|
||||||
OneForOneStrategy(maxNrOfRetries = 10, withinTimeRange = 10 seconds) {
|
OneForOneStrategy(maxNrOfRetries = 10, withinTimeRange = 10 seconds) {
|
||||||
case _: akka.actor.ActorKilledException => Restart
|
case _ =>
|
||||||
case _ => Resume
|
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 {
|
try {
|
||||||
handleOrderFunc(msg)
|
handleOrderFunc(msg)
|
||||||
} catch {
|
} catch {
|
||||||
case _: AssertionError => ; //ehhh
|
case _: AssertionError => () //ehhh
|
||||||
case e: Exception => //something unexpected
|
case e: Exception => e.printStackTrace() //something unexpected
|
||||||
e.printStackTrace()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case VehicleSpawnControl.ProcessControl.OrderCancelled =>
|
case VehicleSpawnControl.ProcessControl.OrderCancelled =>
|
||||||
trackedOrder match {
|
trackedOrder.collect {
|
||||||
case Some(entry)
|
case entry if sender() == concealPlayer =>
|
||||||
if sender() == concealPlayer =>
|
|
||||||
CancelOrder(
|
CancelOrder(
|
||||||
entry,
|
entry,
|
||||||
VehicleSpawnControl.validateOrderCredentials(pad, entry.driver, entry.vehicle)
|
VehicleSpawnControl.validateOrderCredentials(pad, entry.driver, entry.vehicle)
|
||||||
.orElse(Some("@SVCP_RemovedFromVehicleQueue_Generic"))
|
.orElse(Some("@SVCP_RemovedFromVehicleQueue_Generic"))
|
||||||
)
|
)
|
||||||
case _ => ;
|
|
||||||
}
|
}
|
||||||
trackedOrder = None //guard off
|
trackedOrder = None //guard off
|
||||||
SelectOrder()
|
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.
|
During this time, a periodic message about the spawn pad being blocked will be broadcast to the order queue.
|
||||||
*/
|
*/
|
||||||
case VehicleSpawnControl.ProcessControl.Reminder =>
|
case VehicleSpawnControl.ProcessControl.Reminder =>
|
||||||
trackedOrder match {
|
trackedOrder
|
||||||
case Some(entry) =>
|
.collect {
|
||||||
if (periodicReminder.isCancelled) {
|
case entry =>
|
||||||
trace(s"the pad has become blocked by a ${entry.vehicle.Definition.Name} in its current order")
|
if (periodicReminder.isCancelled) {
|
||||||
periodicReminder = context.system.scheduler.scheduleWithFixedDelay(
|
trace(s"the pad has become blocked by a ${entry.vehicle.Definition.Name} in its current order")
|
||||||
VehicleSpawnControl.periodicReminderTestDelay,
|
periodicReminder = context.system.scheduler.scheduleWithFixedDelay(
|
||||||
VehicleSpawnControl.periodicReminderTestDelay,
|
VehicleSpawnControl.periodicReminderTestDelay,
|
||||||
self,
|
VehicleSpawnControl.periodicReminderTestDelay,
|
||||||
VehicleSpawnControl.ProcessControl.Reminder
|
self,
|
||||||
)
|
VehicleSpawnControl.ProcessControl.Reminder
|
||||||
} else {
|
)
|
||||||
BlockedReminder(entry, orders)
|
} else {
|
||||||
}
|
BlockedReminder(entry, orders)
|
||||||
case None => ;
|
}
|
||||||
|
trackedOrder
|
||||||
|
}
|
||||||
|
.orElse {
|
||||||
periodicReminder.cancel()
|
periodicReminder.cancel()
|
||||||
}
|
None
|
||||||
|
}
|
||||||
|
|
||||||
case VehicleSpawnControl.ProcessControl.Flush =>
|
case VehicleSpawnControl.ProcessControl.Flush =>
|
||||||
periodicReminder.cancel()
|
periodicReminder.cancel()
|
||||||
orders.foreach { CancelOrder(_, Some("@SVCP_RemovedFromVehicleQueue_Generic")) }
|
orders.foreach { CancelOrder(_, Some("@SVCP_RemovedFromVehicleQueue_Generic")) }
|
||||||
orders = Nil
|
orders = Nil
|
||||||
trackedOrder match {
|
trackedOrder.foreach {
|
||||||
case Some(entry) => CancelOrder(entry, Some("@SVCP_RemovedFromVehicleQueue_Generic"))
|
entry => CancelOrder(entry, Some("@SVCP_RemovedFromVehicleQueue_Generic"))
|
||||||
case None => ;
|
|
||||||
}
|
}
|
||||||
trackedOrder = None
|
trackedOrder = None
|
||||||
handleOrderFunc = NewTasking
|
handleOrderFunc = NewTasking
|
||||||
pad.Zone.VehicleEvents ! VehicleSpawnPad.ResetSpawnPad(pad) //cautious animation reset
|
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.
|
* All orders accepted in the meantime will be queued and a note about priority will be issued.
|
||||||
* @param order the order being accepted
|
* @param order the order being accepted
|
||||||
*/
|
*/
|
||||||
def NewTasking(order: VehicleSpawnPad.VehicleOrder): Unit = {
|
private def NewTasking(order: VehicleSpawnPad.VehicleOrder): Unit = {
|
||||||
handleOrderFunc = QueuedTasking
|
handleOrderFunc = QueuedTasking
|
||||||
ProcessOrder(Some(order))
|
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.
|
* all orders accepted in the meantime will be queued and a note about priority will be issued.
|
||||||
* @param order the order being accepted
|
* @param order the order being accepted
|
||||||
*/
|
*/
|
||||||
def QueuedTasking(order: VehicleSpawnPad.VehicleOrder): Unit = {
|
private def QueuedTasking(order: VehicleSpawnPad.VehicleOrder): Unit = {
|
||||||
val name = order.player.Name
|
val name = order.player.Name
|
||||||
if (trackedOrder match {
|
if (trackedOrder match {
|
||||||
case Some(tracked) =>
|
case Some(tracked) =>
|
||||||
|
|
@ -219,14 +222,14 @@ class VehicleSpawnControl(pad: VehicleSpawnPad)
|
||||||
/**
|
/**
|
||||||
* Select the next available queued order and begin processing it.
|
* 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.
|
* 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."
|
* If the queue has been exhausted, set functionality to prepare to accept the next order as a "first order."
|
||||||
* @return the next-available order
|
* @return the next-available order
|
||||||
*/
|
*/
|
||||||
def SelectFirstOrder(): Option[VehicleSpawnPad.VehicleOrder] = {
|
private def SelectFirstOrder(): Option[VehicleSpawnPad.VehicleOrder] = {
|
||||||
trackedOrder match {
|
trackedOrder match {
|
||||||
case None =>
|
case None =>
|
||||||
val (completeOrder, remainingOrders): (Option[VehicleSpawnPad.VehicleOrder], List[VehicleSpawnPad.VehicleOrder]) =
|
val (completeOrder, remainingOrders): (Option[VehicleSpawnPad.VehicleOrder], List[VehicleSpawnPad.VehicleOrder]) =
|
||||||
|
|
@ -255,14 +258,12 @@ class VehicleSpawnControl(pad: VehicleSpawnPad)
|
||||||
* @param order the order being accepted;
|
* @param order the order being accepted;
|
||||||
* `None`, if no order found or submitted
|
* `None`, if no order found or submitted
|
||||||
*/
|
*/
|
||||||
def ProcessOrder(order: Option[VehicleSpawnPad.VehicleOrder]): Unit = {
|
private def ProcessOrder(order: Option[VehicleSpawnPad.VehicleOrder]): Unit = {
|
||||||
periodicReminder.cancel()
|
periodicReminder.cancel()
|
||||||
order match {
|
order.collect {
|
||||||
case Some(_order) =>
|
case VehicleSpawnPad.VehicleOrder(driver, vehicle, terminal) =>
|
||||||
val size = orders.size + 1
|
val size = orders.size + 1
|
||||||
val driver = _order.player
|
|
||||||
val name = driver.Name
|
val name = driver.Name
|
||||||
val vehicle = _order.vehicle
|
|
||||||
val newOrder = VehicleSpawnControl.Order(driver, vehicle)
|
val newOrder = VehicleSpawnControl.Order(driver, vehicle)
|
||||||
recursiveOrderReminder(orders.iterator, size)
|
recursiveOrderReminder(orders.iterator, size)
|
||||||
trace(s"processing next order - a ${vehicle.Definition.Name} for $name")
|
trace(s"processing next order - a ${vehicle.Definition.Name} for $name")
|
||||||
|
|
@ -273,7 +274,7 @@ class VehicleSpawnControl(pad: VehicleSpawnPad)
|
||||||
)
|
)
|
||||||
trackedOrder = Some(newOrder) //guard on
|
trackedOrder = Some(newOrder) //guard on
|
||||||
context.system.scheduler.scheduleOnce(2000 milliseconds, concealPlayer, newOrder)
|
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
|
* 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.
|
* cancel a running periodic examination if the queue is already empty.
|
||||||
*/
|
*/
|
||||||
def queueManagementTask(): Unit = {
|
private def queueManagementTask(): Unit = {
|
||||||
if (orders.nonEmpty) {
|
if (orders.nonEmpty) {
|
||||||
orders = orderCredentialsCheck(orders).toList
|
orders = orderCredentialsCheck(orders).toList
|
||||||
if (queueManagement.isCancelled) {
|
if (queueManagement.isCancelled) {
|
||||||
|
|
@ -306,7 +307,7 @@ class VehicleSpawnControl(pad: VehicleSpawnPad)
|
||||||
* @param recipients the original list of orders
|
* @param recipients the original list of orders
|
||||||
* @return the list of still-acceptable 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
|
recipients
|
||||||
.map { order =>
|
.map { order =>
|
||||||
(order, VehicleSpawnControl.validateOrderCredentials(order.terminal, order.player, order.vehicle))
|
(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 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
|
* @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
|
val user = blockedOrder.vehicle
|
||||||
.Seats(0).occupant
|
.Seats(0).occupant
|
||||||
.orElse(pad.Zone.GUID(blockedOrder.vehicle.Owner))
|
.orElse(pad.Zone.GUID(blockedOrder.vehicle.OwnerGuid))
|
||||||
.orElse(pad.Zone.GUID(blockedOrder.DriverGUID))
|
.orElse(pad.Zone.GUID(blockedOrder.DriverGUID))
|
||||||
val relevantRecipients: Iterator[VehicleSpawnPad.VehicleOrder] = user match {
|
val relevantRecipients: Iterator[VehicleSpawnPad.VehicleOrder] = user match {
|
||||||
case Some(p: Player) if !p.HasGUID =>
|
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.
|
* Cancel this vehicle order and inform the person who made it, if possible.
|
||||||
* @param entry the order being cancelled
|
* @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)
|
CancelOrder(entry.vehicle, entry.driver, msg)
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Cancel this vehicle order and inform the person who made it, if possible.
|
* Cancel this vehicle order and inform the person who made it, if possible.
|
||||||
* @param entry the order being cancelled
|
* @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)
|
CancelOrder(entry.vehicle, entry.player, msg)
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
|
|
@ -373,7 +374,7 @@ class VehicleSpawnControl(pad: VehicleSpawnPad)
|
||||||
* @param vehicle the vehicle from the order being cancelled
|
* @param vehicle the vehicle from the order being cancelled
|
||||||
* @param player the player who would driver 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) {
|
if (vehicle.Seats.values.count(_.isOccupied) == 0) {
|
||||||
VehicleSpawnControl.DisposeSpawnedVehicle(vehicle, player, pad.Zone)
|
VehicleSpawnControl.DisposeSpawnedVehicle(vehicle, player, pad.Zone)
|
||||||
pad.Zone.VehicleEvents ! VehicleSpawnPad.PeriodicReminder(player.Name, VehicleSpawnPad.Reminders.Cancelled, msg)
|
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) {
|
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(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")
|
assert(vehicle.HasGUID, s"when ordering a vehicle, the ${vehicle.Definition.Name} does not have a GUID")
|
||||||
val DriverGUID = driver.GUID
|
val DriverGUID: PlanetSideGUID = driver.GUID
|
||||||
val time = System.currentTimeMillis()
|
val time: Long = System.currentTimeMillis()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -502,7 +503,7 @@ object VehicleSpawnControl {
|
||||||
* @param player the player who would own the vehicle being disposed
|
* @param player the player who would own the vehicle being disposed
|
||||||
* @param zone the zone in which the vehicle is registered (should be located)
|
* @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)
|
DisposeVehicle(vehicle, zone)
|
||||||
zone.VehicleEvents ! VehicleSpawnPad.RevealPlayer(player.GUID)
|
zone.VehicleEvents ! VehicleSpawnPad.RevealPlayer(player.GUID)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ final case class VehicleSource(
|
||||||
Orientation: Vector3,
|
Orientation: Vector3,
|
||||||
Velocity: Option[Vector3],
|
Velocity: Option[Vector3],
|
||||||
deployed: DriveState.Value,
|
deployed: DriveState.Value,
|
||||||
|
owner: Option[PlayerSource],
|
||||||
occupants: List[SourceEntry],
|
occupants: List[SourceEntry],
|
||||||
Modifiers: ResistanceProfile,
|
Modifiers: ResistanceProfile,
|
||||||
unique: UniqueVehicle
|
unique: UniqueVehicle
|
||||||
|
|
@ -42,6 +43,7 @@ object VehicleSource {
|
||||||
obj.Orientation,
|
obj.Orientation,
|
||||||
obj.Velocity,
|
obj.Velocity,
|
||||||
obj.DeploymentState,
|
obj.DeploymentState,
|
||||||
|
None,
|
||||||
Nil,
|
Nil,
|
||||||
obj.Definition.asInstanceOf[ResistanceProfile],
|
obj.Definition.asInstanceOf[ResistanceProfile],
|
||||||
UniqueVehicle(
|
UniqueVehicle(
|
||||||
|
|
|
||||||
|
|
@ -286,7 +286,7 @@ class VehicleControl(vehicle: Vehicle)
|
||||||
val seatGroup = vehicle.SeatPermissionGroup(seatNumber).getOrElse(AccessPermissionGroup.Passenger)
|
val seatGroup = vehicle.SeatPermissionGroup(seatNumber).getOrElse(AccessPermissionGroup.Passenger)
|
||||||
val permission = vehicle.PermissionGroup(seatGroup.id).getOrElse(VehicleLockState.Empire)
|
val permission = vehicle.PermissionGroup(seatGroup.id).getOrElse(VehicleLockState.Empire)
|
||||||
(if (seatGroup == AccessPermissionGroup.Driver) {
|
(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 {
|
} else {
|
||||||
permission != VehicleLockState.Locked
|
permission != VehicleLockState.Locked
|
||||||
}) &&
|
}) &&
|
||||||
|
|
@ -348,7 +348,7 @@ class VehicleControl(vehicle: Vehicle)
|
||||||
//are we already decaying? are we unowned? is no one seated anywhere?
|
//are we already decaying? are we unowned? is no one seated anywhere?
|
||||||
if (!decaying &&
|
if (!decaying &&
|
||||||
obj.Definition.undergoesDecay &&
|
obj.Definition.undergoesDecay &&
|
||||||
obj.Owner.isEmpty &&
|
obj.OwnerGuid.isEmpty &&
|
||||||
obj.Seats.values.forall(!_.isOccupied)) {
|
obj.Seats.values.forall(!_.isOccupied)) {
|
||||||
decaying = true
|
decaying = true
|
||||||
decayTimer = context.system.scheduler.scheduleOnce(
|
decayTimer = context.system.scheduler.scheduleOnce(
|
||||||
|
|
@ -418,7 +418,7 @@ class VehicleControl(vehicle: Vehicle)
|
||||||
Vehicles.Disown(obj.GUID, obj)
|
Vehicles.Disown(obj.GUID, obj)
|
||||||
if (!decaying &&
|
if (!decaying &&
|
||||||
obj.Definition.undergoesDecay &&
|
obj.Definition.undergoesDecay &&
|
||||||
obj.Owner.isEmpty &&
|
obj.OwnerGuid.isEmpty &&
|
||||||
obj.Seats.values.forall(!_.isOccupied)) {
|
obj.Seats.values.forall(!_.isOccupied)) {
|
||||||
decaying = true
|
decaying = true
|
||||||
decayTimer = context.system.scheduler.scheduleOnce(
|
decayTimer = context.system.scheduler.scheduleOnce(
|
||||||
|
|
|
||||||
|
|
@ -50,9 +50,13 @@ final case class ShieldCharge(amount: Int, cause: Option[SourceEntry])
|
||||||
final case class TerminalUsedActivity(terminal: AmenitySource, transaction: TransactionType.Value)
|
final case class TerminalUsedActivity(terminal: AmenitySource, transaction: TransactionType.Value)
|
||||||
extends GeneralActivity
|
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 {
|
extends GeneralActivity {
|
||||||
override val time: Long = entries.maxBy(_.time).time
|
val start: Long = entries.last.time
|
||||||
|
val end: Long = entries.head.time
|
||||||
}
|
}
|
||||||
|
|
||||||
/* vitals history */
|
/* vitals history */
|
||||||
|
|
@ -183,6 +187,9 @@ trait InGameHistory {
|
||||||
action match {
|
action match {
|
||||||
case Some(act) =>
|
case Some(act) =>
|
||||||
history = history :+ act
|
history = history :+ act
|
||||||
|
if (IsContributionEvent(act)) {
|
||||||
|
previousContributionInheritance = None
|
||||||
|
}
|
||||||
case None => ()
|
case None => ()
|
||||||
}
|
}
|
||||||
history
|
history
|
||||||
|
|
@ -206,7 +213,7 @@ trait InGameHistory {
|
||||||
LogActivity(DamageFromPainbox(result))
|
LogActivity(DamageFromPainbox(result))
|
||||||
case _: EnvironmentReason =>
|
case _: EnvironmentReason =>
|
||||||
LogActivity(DamageFromEnvironment(result))
|
LogActivity(DamageFromEnvironment(result))
|
||||||
case _ => ;
|
case _ =>
|
||||||
LogActivity(DamageFrom(result))
|
LogActivity(DamageFrom(result))
|
||||||
if(result.adversarial.nonEmpty) {
|
if(result.adversarial.nonEmpty) {
|
||||||
lastDamage = Some(result)
|
lastDamage = Some(result)
|
||||||
|
|
@ -227,70 +234,79 @@ trait InGameHistory {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val contributionInheritance: mutable.HashMap[SourceUniqueness, Seq[Contribution]] =
|
private var previousContributionInheritance: Option[List[InGameActivity]] = None
|
||||||
mutable.HashMap[SourceUniqueness, Seq[Contribution]]()
|
|
||||||
|
|
||||||
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) {
|
if (target ne this) {
|
||||||
val events = target.GetContribution()
|
InGameHistory.ContributionFrom(target) match {
|
||||||
val nonEmpty = events.nonEmpty
|
case out @ Some(in @ Contribution(src, _)) =>
|
||||||
if (nonEmpty) {
|
contributionInheritance.put(src, in)
|
||||||
val source = SourceEntry(target)
|
out
|
||||||
contributionInheritance.get(source.unique) match {
|
case None =>
|
||||||
case Some(previousContributions) =>
|
contributionInheritance.remove(SourceEntry(target).unique)
|
||||||
val uniqueEvents = for {
|
None
|
||||||
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)))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
nonEmpty
|
|
||||||
} else {
|
} else {
|
||||||
false
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def RemoveContributionFrom(target: PlanetSideGameObject with FactionAffinity with InGameHistory): Iterable[Contribution] = {
|
def GetContribution(): Option[List[InGameActivity]] = {
|
||||||
contributionInheritance.remove(SourceEntry(target).unique).getOrElse(Nil)
|
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] = {
|
def GetContributionDuringPeriod(list: List[InGameActivity], duration: Long): List[InGameActivity] = {
|
||||||
GetContributionDuringPeriod(System.currentTimeMillis(), duration = 600000)
|
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] = {
|
def IsContributionEvent(event: InGameActivity): Boolean = {
|
||||||
GetContributionDuringPeriod(ending, duration = 600000)
|
event match {
|
||||||
}
|
case _: RepairingActivity => true
|
||||||
|
case _: DamagingActivity => true
|
||||||
def GetContributionDuringPeriod(ending: Long, duration: Long): List[InGameActivity] = {
|
case _ => false
|
||||||
val start = ending - duration
|
|
||||||
History.collect { case repair: RepairFromEquipment
|
|
||||||
if repair.time <= ending && repair.time > start => repair
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def HistoryAndContributions(): List[InGameActivity] = {
|
def HistoryAndContributions(): List[InGameActivity] = {
|
||||||
val ending = System.currentTimeMillis()
|
History ++ contributionInheritance.values.toList
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def ClearHistory(): List[InGameActivity] = {
|
def ClearHistory(): List[InGameActivity] = {
|
||||||
lastDamage = None
|
lastDamage = None
|
||||||
val out = history
|
val out = history
|
||||||
history = List.empty
|
history = List.empty
|
||||||
|
previousContributionInheritance = None
|
||||||
|
contributionInheritance.clear()
|
||||||
out
|
out
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -318,4 +334,10 @@ object InGameHistory {
|
||||||
unit.foreach { o => obj.ContributionFrom(o) }
|
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) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import akka.actor.ActorRef
|
||||||
import net.psforever.objects.avatar.scoring.{Assist, Death, Kill}
|
import net.psforever.objects.avatar.scoring.{Assist, Death, Kill}
|
||||||
import net.psforever.objects.sourcing.{PlayerSource, SourceEntry}
|
import net.psforever.objects.sourcing.{PlayerSource, SourceEntry}
|
||||||
import net.psforever.objects.vital.interaction.{Adversarial, DamageResult}
|
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.services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||||
import net.psforever.types.PlanetSideEmpire
|
import net.psforever.types.PlanetSideEmpire
|
||||||
|
|
||||||
|
|
@ -13,6 +13,28 @@ import scala.collection.mutable
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
|
|
||||||
object KillAssists {
|
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(
|
private[exp] def determineKiller(
|
||||||
lastDamageActivity: Option[DamageResult],
|
lastDamageActivity: Option[DamageResult],
|
||||||
history: List[InGameActivity]
|
history: List[InGameActivity]
|
||||||
|
|
@ -71,7 +93,7 @@ object KillAssists {
|
||||||
history: Iterable[InGameActivity],
|
history: Iterable[InGameActivity],
|
||||||
eventBus: ActorRef
|
eventBus: ActorRef
|
||||||
): Unit = {
|
): Unit = {
|
||||||
val shortHistory = Support.limitHistoryToThisLife(history.toList)
|
val shortHistory = limitHistoryToThisLife(history.toList)
|
||||||
val everyone = determineKiller(lastDamage, shortHistory) match {
|
val everyone = determineKiller(lastDamage, shortHistory) match {
|
||||||
case Some((result, killer: PlayerSource)) =>
|
case Some((result, killer: PlayerSource)) =>
|
||||||
val assists = collectKillAssistsForPlayer(victim, shortHistory, Some(killer))
|
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 assists = func(history, victim.Faction).filterNot { case (_, kda) => kda.amount <= 0 }
|
||||||
val total = assists.values.foldLeft(0f)(_ + _.total)
|
val total = assists.values.foldLeft(0f)(_ + _.total)
|
||||||
val output = assists.map { case (id, kda) =>
|
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.remove(victim.CharId)
|
||||||
output
|
output
|
||||||
|
|
@ -214,7 +236,7 @@ object KillAssists {
|
||||||
case Some(mod) =>
|
case Some(mod) =>
|
||||||
//previous attacker, just add to entry
|
//previous attacker, just add to entry
|
||||||
val firstWeapon = mod.weapons.head
|
val firstWeapon = mod.weapons.head
|
||||||
val weapons = if (firstWeapon.weapon_id == wepid) {
|
val weapons = if (firstWeapon.equipment_id == wepid) {
|
||||||
firstWeapon.copy(
|
firstWeapon.copy(
|
||||||
amount = firstWeapon.amount + amount,
|
amount = firstWeapon.amount + amount,
|
||||||
shots = firstWeapon.shots + 1,
|
shots = firstWeapon.shots + 1,
|
||||||
|
|
|
||||||
|
|
@ -2,29 +2,28 @@
|
||||||
package net.psforever.objects.zones.exp
|
package net.psforever.objects.zones.exp
|
||||||
|
|
||||||
import akka.actor.ActorRef
|
import akka.actor.ActorRef
|
||||||
|
|
||||||
import java.util.Date
|
|
||||||
import net.psforever.objects.avatar.scoring.{Assist, Kill}
|
import net.psforever.objects.avatar.scoring.{Assist, Kill}
|
||||||
import net.psforever.objects.sourcing.{PlayerSource, SourceUniqueness}
|
import net.psforever.objects.serverobject.hackable.Hackable.HackInfo
|
||||||
import net.psforever.objects.vital.{Contribution, DamagingActivity, HealFromEquipment, HealFromTerminal, HealingActivity, InGameActivity, RepairFromEquipment, RepairFromExoSuitChange, RepairFromTerminal, RepairingActivity, RevivingActivity, SupportActivityCausedByAnother}
|
import net.psforever.objects.sourcing.{AmenitySource, PlayerSource, SourceEntry, SourceUniqueness, VehicleSource}
|
||||||
import net.psforever.objects.vital.interaction.Adversarial
|
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.services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||||
import net.psforever.types.PlanetSideEmpire
|
import net.psforever.types.PlanetSideEmpire
|
||||||
import org.joda.time.Instant
|
|
||||||
import org.joda.time.{LocalDateTime => JodaLocalDateTime}
|
|
||||||
|
|
||||||
import scala.collection.mutable
|
import scala.collection.mutable
|
||||||
|
|
||||||
object KillContributions {
|
object KillContributions {
|
||||||
private lazy val recoveryItems: Seq[Int] = {
|
final val RecoveryItems: Seq[Int] = {
|
||||||
import net.psforever.objects.GlobalDefinitions._
|
import net.psforever.objects.GlobalDefinitions._
|
||||||
Seq(
|
Seq(
|
||||||
bank.ObjectId,
|
bank,
|
||||||
nano_dispenser.ObjectId,
|
nano_dispenser,
|
||||||
medicalapplicator.ObjectId,
|
medicalapplicator,
|
||||||
medical_terminal.ObjectId,
|
medical_terminal,
|
||||||
adv_med_terminal.ObjectId
|
adv_med_terminal,
|
||||||
)
|
crystals_health_a,
|
||||||
|
crystals_health_b
|
||||||
|
).collect { _.ObjectId }
|
||||||
}
|
}
|
||||||
|
|
||||||
private[exp] def rewardTheseSupporters(
|
private[exp] def rewardTheseSupporters(
|
||||||
|
|
@ -34,465 +33,326 @@ object KillContributions {
|
||||||
bep: Long,
|
bep: Long,
|
||||||
eventBus: ActorRef
|
eventBus: ActorRef
|
||||||
): Unit = {
|
): Unit = {
|
||||||
val killTime = kill.time
|
//setup
|
||||||
|
val killTime = kill.time.toDate.getTime
|
||||||
val faction = target.Faction
|
val faction = target.Faction
|
||||||
val shortHistory = Support.limitHistoryToThisLife(history.toList)
|
val limitedHistory = limitHistoryToThisLife(history.toList, killTime)
|
||||||
//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)
|
|
||||||
//divide by applicable time periods (long=10minutes, short=5minutes)
|
//divide by applicable time periods (long=10minutes, short=5minutes)
|
||||||
val longEvents = findTimeApplicableActivities(allAssists.values.toSeq, killTime, timeOffset = 600)
|
val shortPeriod = killTime - 300000L
|
||||||
val shortEvents = findTimeApplicableActivities(longEvents, killTime, timeOffset = 300)
|
val (contributions, (longHistory, shortHistory)) = {
|
||||||
val victim = kill.victim
|
val (contrib, onlyHistory) = limitedHistory.partition { _.isInstanceOf[Contribution] }
|
||||||
//convert to output format
|
(
|
||||||
longEvents
|
contrib
|
||||||
.map { stats =>
|
.collect { case Contribution(unique, entries) => (unique, entries) }
|
||||||
val (_, ContributionStatsOutput(p, w, r)) = mapContributionPointsByPercentage(total = 100f, shortEvents)(stats.player.CharId, stats)
|
.toMap[SourceUniqueness, List[InGameActivity]],
|
||||||
(p, Assist(victim, w, r, (bep * r).toLong))
|
onlyHistory.partition { _.time > shortPeriod }
|
||||||
}
|
)
|
||||||
.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<br>
|
|
||||||
* 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
|
|
||||||
}
|
}
|
||||||
}
|
//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)(_, _)
|
||||||
private def healthRecoveryContributors(
|
val finalContributions = if (longHistory.isEmpty) {
|
||||||
history: List[InGameActivity],
|
val contributionProcess = new CombinedHealthAndArmorContributionProcess(faction, contributions, Nil)
|
||||||
faction: PlanetSideEmpire.Value,
|
contributionProcess.submit(shortHistory)
|
||||||
participants: mutable.LongMap[ContributionStats]
|
val contributionEntries = otherContributionCalculations(shortHistory, contributionProcess.output())
|
||||||
): Seq[(Long, Int)] = {
|
contributionEntries
|
||||||
/** damage as it is measured in order (with heal-countered damage eliminated)<br>
|
.keys
|
||||||
* key - character identifier,
|
.map { composeContributionOutput(_, contributionEntries, contributionEntries, bep) }
|
||||||
* 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)<br>
|
|
||||||
* 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))
|
|
||||||
} else {
|
} 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,
|
* Only historical activity that falls within the valid period matters.<br>
|
||||||
wepid: Int,
|
* Unlike an expected case where the history would be bound by being spawned and being killed, respectively,
|
||||||
faction: PlanetSideEmpire.Value,
|
* this imposes only the long contribution time limit on events since the latest entry;
|
||||||
amount: Int,
|
* and, it may stop some time after the otherwise closest activity for being spawned.
|
||||||
time: Long,
|
* @param history the original history
|
||||||
participants: mutable.LongMap[ContributionStats],
|
* @param eventTime from which time to start counting backwards
|
||||||
damageOrder: Seq[(Long, Int)],
|
* @return the potentially truncated history
|
||||||
recoveryOrder: Seq[(Long, Int)]
|
*/
|
||||||
): (Seq[(Long, Int)], Seq[(Long, Int)]) = {
|
private def limitHistoryToThisLife(history: List[InGameActivity], eventTime: Long): List[InGameActivity] = {
|
||||||
contributeWithRecoveryActivity(user, user.CharId, wepid, faction, amount, time, participants, damageOrder, recoveryOrder)
|
val longLimit: Long = eventTime - 600000L
|
||||||
|
history.collect {
|
||||||
|
case event if event.time < eventTime && event.time >= longLimit => event
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private def contributeWithRecoveryActivity(
|
private def contributionScoringAndCulling(
|
||||||
wepid: Int,
|
faction: PlanetSideEmpire.Value,
|
||||||
faction: PlanetSideEmpire.Value,
|
kill: Kill,
|
||||||
amount: Int,
|
excludeTargets: Seq[Long],
|
||||||
time: Long,
|
contributions: Map[SourceUniqueness, List[InGameActivity]],
|
||||||
participants: mutable.LongMap[ContributionStats],
|
bep: Long
|
||||||
damageOrder: Seq[(Long, Int)],
|
)
|
||||||
recoveryOrder: Seq[(Long, Int)]
|
(
|
||||||
): (Seq[(Long, Int)], Seq[(Long, Int)]) = {
|
history: List[InGameActivity],
|
||||||
contributeWithRecoveryActivity(PlayerSource.Nobody, charId = 0, wepid, faction, amount, time, participants, damageOrder, recoveryOrder)
|
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(
|
private def contributeWithKillWhileMountedActivity(
|
||||||
user: PlayerSource,
|
faction: PlanetSideEmpire.Value,
|
||||||
charId: Long,
|
kill: Kill,
|
||||||
wepid: Int,
|
history: List[InGameActivity],
|
||||||
faction: PlanetSideEmpire.Value,
|
participants: mutable.LongMap[ContributionStats]
|
||||||
amount: Int,
|
): List[InGameActivity] = {
|
||||||
time: Long,
|
(kill
|
||||||
participants: mutable.LongMap[ContributionStats],
|
.info
|
||||||
damageOrder: Seq[(Long, Int)],
|
.interaction
|
||||||
recoveryOrder: Seq[(Long, Int)]
|
.cause match {
|
||||||
): (Seq[(Long, Int)], Seq[(Long, Int)]) = {
|
case p: ProjectileReason => p.projectile.mounted_in.map { _._2 }
|
||||||
//recovery calculations
|
case _ => None
|
||||||
val newOrder = KillAssists.contributeWithRecoveryActivity(amount, participants, damageOrder)
|
})
|
||||||
//detect potential instances of and remove approximate value for friendly-fire recovery (that doesn't count)
|
.collect {
|
||||||
val qualityRecoveryAmount = {
|
case mount: VehicleSource =>
|
||||||
val (entries, count) = {
|
//repairs
|
||||||
val value = damageOrder.size - newOrder.size
|
val contributions = history
|
||||||
if (value > 0) {
|
.collect { case Contribution(unique, entries) if mount.unique == unique => (unique, entries) }
|
||||||
if (newOrder.head == damageOrder.lift(value).getOrElse(newOrder.head)) {
|
.toMap[SourceUniqueness, List[InGameActivity]]
|
||||||
(damageOrder.take(value), value)
|
contributions
|
||||||
} else {
|
.foreach {
|
||||||
(damageOrder.take(value + 1), value + 1)
|
case (_, localHistory) =>
|
||||||
}
|
val process = new ArmorRecoveryExperienceContributionProcess(faction, contributions)
|
||||||
} else {
|
process.submit(localHistory)
|
||||||
if (newOrder.headOption != damageOrder.headOption) {
|
cullContributorImplements(process.output()).foreach {
|
||||||
(damageOrder.headOption.toList, 1)
|
case (id, stat) =>
|
||||||
} else {
|
val killExperience = kill.experienceEarned
|
||||||
(Nil, 1)
|
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) {
|
history
|
||||||
//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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private def contributeWithSupportRecoveryActivity(
|
private def contributeWithVehicleTransportActivity(
|
||||||
users: Seq[PlayerSource],
|
history: List[InGameActivity],
|
||||||
wepid: Int,
|
participants: mutable.LongMap[ContributionStats]
|
||||||
faction: PlanetSideEmpire.Value,
|
): List[InGameActivity] = {
|
||||||
amount: Int,
|
val shortHistory = history.collect { case out: VehicleDismountActivity => out }
|
||||||
time: Long,
|
shortHistory
|
||||||
participants: mutable.LongMap[ContributionStats],
|
.groupBy { a => a.vehicle.owner }
|
||||||
damageOrder: Seq[(Long, Int)],
|
.foreach { case (Some(owner), dismountsFromVehicle) =>
|
||||||
recoveryOrder: Seq[(Long, Int)]
|
val numberOfDismounts = dismountsFromVehicle.size
|
||||||
): (Seq[(Long, Int)], Seq[(Long, Int)]) = {
|
contributeWithCombinedActivity(
|
||||||
var outputDamageOrder = damageOrder
|
owner.CharId,
|
||||||
var outputRecoveryOrder = recoveryOrder
|
dismountsFromVehicle.map { act => WeaponStats(act.vehicle.Definition.ObjectId, 1, 1, act.time, 15f) },
|
||||||
users.zip {
|
dismountsFromVehicle.head.vehicle.owner.get,
|
||||||
val numberOfUsers = users.size
|
numberOfDismounts,
|
||||||
val out = Array.fill(numberOfUsers)(numberOfUsers / amount)
|
numberOfDismounts,
|
||||||
(0 to numberOfUsers % amount).foreach {
|
numberOfDismounts,
|
||||||
out(_) += 1
|
dismountsFromVehicle.maxBy(_.time).time,
|
||||||
|
participants
|
||||||
|
)
|
||||||
}
|
}
|
||||||
out
|
shortHistory
|
||||||
}.foreach { case (user, subAmount) =>
|
|
||||||
val (a, b) = contributeWithRecoveryActivity(user, user.CharId, wepid, faction, subAmount, time, participants, outputDamageOrder, outputRecoveryOrder)
|
|
||||||
outputDamageOrder = a
|
|
||||||
outputRecoveryOrder = b
|
|
||||||
}
|
|
||||||
(outputDamageOrder, outputRecoveryOrder)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private def contributeWithRevivalActivity(
|
private def contributeWithRevivalActivity(
|
||||||
history: List[InGameActivity],
|
history: List[InGameActivity],
|
||||||
participants: mutable.LongMap[ContributionStats]
|
participants: mutable.LongMap[ContributionStats]
|
||||||
): Unit = {
|
): List[InGameActivity] = {
|
||||||
history
|
val shortHistory = history.collect { case rev: RevivingActivity => rev }
|
||||||
.collect { case rev: RevivingActivity => rev }
|
shortHistory
|
||||||
.groupBy(_.user.CharId)
|
.groupBy(_.user.CharId)
|
||||||
.foreach { case (id, revivesByThisPlayer) =>
|
.foreach { case (id, revivesByThisPlayer) =>
|
||||||
val numberOfRevives = revivesByThisPlayer.size
|
val numberOfRevives = revivesByThisPlayer.size
|
||||||
val latestTime = revivesByThisPlayer.maxBy(_.time).time
|
contributeWithCombinedActivity(
|
||||||
val newStats = revivesByThisPlayer.map { stat => WeaponStats(stat.equipment.ObjectId, 100, 1, stat.time, 0.25f) }
|
id,
|
||||||
val newStat = WeaponStats(revivesByThisPlayer.head.equipment.ObjectId, 100 * numberOfRevives, numberOfRevives, latestTime, 0.25f)
|
revivesByThisPlayer.map { stat => WeaponStats(stat.equipment.ObjectId, 100, 1, stat.time, 25f) },
|
||||||
participants.get(id) match {
|
revivesByThisPlayer.head.user,
|
||||||
case Some(stats) =>
|
amount = 100 * numberOfRevives,
|
||||||
participants.put(id, stats.copy(weapons = stats.weapons ++ newStats))
|
total = 100 * numberOfRevives,
|
||||||
case None =>
|
numberOfRevives,
|
||||||
participants.put(id, ContributionStats(
|
revivesByThisPlayer.maxBy(_.time).time,
|
||||||
revivesByThisPlayer.head.user,
|
participants
|
||||||
newStats,
|
)
|
||||||
newStat.amount,
|
|
||||||
newStat.shots,
|
|
||||||
revivesByThisPlayer.size,
|
|
||||||
latestTime
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
shortHistory
|
||||||
}
|
}
|
||||||
|
|
||||||
private def findTimeApplicableActivities(
|
private def contributeWithTerminalActivity(
|
||||||
activity: Seq[ContributionStats],
|
faction: PlanetSideEmpire.Value,
|
||||||
killLastTime: JodaLocalDateTime,
|
history: List[InGameActivity],
|
||||||
timeOffset: Int //s
|
contributions: Map[SourceUniqueness, List[InGameActivity]],
|
||||||
): Seq[ContributionStats] = {
|
participants: mutable.LongMap[ContributionStats]
|
||||||
val dateTimeConverter: Date=>JodaLocalDateTime = JodaLocalDateTime.fromDateFields
|
): List[InGameActivity] = {
|
||||||
val milliToInstant: Long=>org.joda.time.Instant = Instant.ofEpochMilli
|
contributeWithTerminalActivity(
|
||||||
val targetTime = killLastTime.minusSeconds(timeOffset)
|
history.collect { case t: TerminalUsedActivity => (t, t.terminal, t.terminal.hacked) },
|
||||||
//find all activities that occurred before the kill time but after the offset
|
faction,
|
||||||
activity.collect { entry =>
|
contributions,
|
||||||
val weapons = entry.weapons.filter { stat =>
|
participants
|
||||||
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 mapContributionPointsByPercentage(
|
private[exp] def contributeWithTerminalActivity(
|
||||||
total: Float,
|
data: Seq[(InGameActivity, AmenitySource, Option[HackInfo])],
|
||||||
compareList: Iterable[ContributionStats]
|
faction: PlanetSideEmpire.Value,
|
||||||
)
|
contributions: Map[SourceUniqueness, List[InGameActivity]],
|
||||||
(
|
participants: mutable.LongMap[ContributionStats]
|
||||||
charId: Long,
|
): List[InGameActivity] = {
|
||||||
contribution: ContributionStats,
|
data.collect {
|
||||||
): (Long, ContributionStatsOutput) = {
|
case (t, terminal, Some(info)) if terminal.Faction != faction =>
|
||||||
val user = contribution.player
|
participants.getOrElseUpdate(
|
||||||
val unique = user.unique
|
info.player.CharId,
|
||||||
val points = contribution.amount
|
ContributionStats(info.player, Seq(WeaponStats(0, 1, 1, t.time, 10f)), 1, 1, 1, t.time)
|
||||||
val value = if (points < 75) {
|
)
|
||||||
//a small contribution means the lower time limit
|
t
|
||||||
if (compareList.exists { a => a.player.unique == unique }) {
|
case (t, terminal, _) =>
|
||||||
math.max(0.2f, points / total)
|
extractContributionsForEntityByUser(faction, terminal, contributions, participants)
|
||||||
} else {
|
t
|
||||||
0f
|
}.toList
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
//large contribution is always okay
|
private def extractContributionsForEntityByUser(
|
||||||
if (points > 299) {
|
faction: PlanetSideEmpire.Value,
|
||||||
1.0f
|
target: SourceEntry,
|
||||||
} else if (points > 100) {
|
contributions: Map[SourceUniqueness, List[InGameActivity]],
|
||||||
0.75f
|
contributionsBy: mutable.LongMap[ContributionStats]
|
||||||
} else {
|
): List[InGameActivity] = {
|
||||||
0.5f
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)<br>
|
|
||||||
* 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)<br>
|
|
||||||
* 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)<br>
|
|
||||||
* 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,22 +3,36 @@ package net.psforever.objects.zones.exp
|
||||||
|
|
||||||
import net.psforever.objects.sourcing.PlayerSource
|
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(
|
private case class WeaponStats(
|
||||||
weapon_id: Int,
|
equipment_id: Int,
|
||||||
amount: Int,
|
amount: Int,
|
||||||
shots: Int,
|
shots: Int,
|
||||||
time: Long,
|
time: Long,
|
||||||
contributions: Float
|
contributions: Float
|
||||||
)
|
) extends ItemUseStats
|
||||||
|
|
||||||
private case class ContributionStats(
|
private case class EquipmentStats(
|
||||||
player: PlayerSource,
|
equipment_id: Int,
|
||||||
weapons: Seq[WeaponStats],
|
shots: Int,
|
||||||
amount: Int,
|
time: Long,
|
||||||
total: Int,
|
contributions: Float
|
||||||
shots: Int,
|
) extends ItemUseStats
|
||||||
time: Long
|
|
||||||
)
|
private[exp] case class ContributionStats(
|
||||||
|
player: PlayerSource,
|
||||||
|
weapons: Seq[WeaponStats],
|
||||||
|
amount: Int,
|
||||||
|
total: Int,
|
||||||
|
shots: Int,
|
||||||
|
time: Long
|
||||||
|
)
|
||||||
|
|
||||||
sealed case class ContributionStatsOutput(
|
sealed case class ContributionStatsOutput(
|
||||||
player: PlayerSource,
|
player: PlayerSource,
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import org.joda.time.{Instant, LocalDateTime => JodaLocalDateTime}
|
||||||
import net.psforever.objects.serverobject.hackable.Hackable
|
import net.psforever.objects.serverobject.hackable.Hackable
|
||||||
import net.psforever.objects.serverobject.terminals.Terminal
|
import net.psforever.objects.serverobject.terminals.Terminal
|
||||||
import net.psforever.objects.sourcing.{AmenitySource, PlayerSource, SourceEntry}
|
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.objects.zones.Zone
|
||||||
import net.psforever.types.{ExoSuitType, PlanetSideEmpire, TransactionType}
|
import net.psforever.types.{ExoSuitType, PlanetSideEmpire, TransactionType}
|
||||||
import net.psforever.zones.Zones
|
import net.psforever.zones.Zones
|
||||||
|
|
@ -18,28 +18,6 @@ import scala.collection.mutable
|
||||||
object Support {
|
object Support {
|
||||||
private type SupportActivity = InGameActivity with SupportActivityCausedByAnother
|
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(
|
private[exp] def onlyOriginalAssistEntries(
|
||||||
first: mutable.LongMap[ContributionStatsOutput],
|
first: mutable.LongMap[ContributionStatsOutput],
|
||||||
second: mutable.LongMap[ContributionStatsOutput],
|
second: mutable.LongMap[ContributionStatsOutput],
|
||||||
|
|
@ -349,7 +327,7 @@ object Support {
|
||||||
0.5f
|
0.5f
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(charId, ContributionStatsOutput(user, contribution.weapons.map { _.weapon_id }, value))
|
(charId, ContributionStatsOutput(user, contribution.weapons.map { _.equipment_id }, value))
|
||||||
}
|
}
|
||||||
|
|
||||||
//noinspection ScalaUnusedSymbol
|
//noinspection ScalaUnusedSymbol
|
||||||
|
|
@ -361,7 +339,7 @@ object Support {
|
||||||
charId: Long,
|
charId: Long,
|
||||||
contribution: ContributionStats,
|
contribution: ContributionStats,
|
||||||
): (Long, ContributionStatsOutput) = {
|
): (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 = {
|
private[exp] def wasEverAMax(player: PlayerSource, history: Iterable[InGameActivity]): Boolean = {
|
||||||
|
|
|
||||||
|
|
@ -564,7 +564,7 @@ class ConverterTest extends Specification {
|
||||||
obj.Faction = PlanetSideEmpire.TR
|
obj.Faction = PlanetSideEmpire.TR
|
||||||
obj.GUID = PlanetSideGUID(90)
|
obj.GUID = PlanetSideGUID(90)
|
||||||
obj.Router = PlanetSideGUID(1001)
|
obj.Router = PlanetSideGUID(1001)
|
||||||
obj.Owner = PlanetSideGUID(5001)
|
obj.OwnerGuid = PlanetSideGUID(5001)
|
||||||
obj.Health = 1
|
obj.Health = 1
|
||||||
obj.Definition.Packet.ConstructorData(obj) match {
|
obj.Definition.Packet.ConstructorData(obj) match {
|
||||||
case Success(pkt) =>
|
case Success(pkt) =>
|
||||||
|
|
@ -596,7 +596,7 @@ class ConverterTest extends Specification {
|
||||||
obj.Faction = PlanetSideEmpire.TR
|
obj.Faction = PlanetSideEmpire.TR
|
||||||
obj.GUID = PlanetSideGUID(90)
|
obj.GUID = PlanetSideGUID(90)
|
||||||
obj.Router = PlanetSideGUID(1001)
|
obj.Router = PlanetSideGUID(1001)
|
||||||
obj.Owner = PlanetSideGUID(5001)
|
obj.OwnerGuid = PlanetSideGUID(5001)
|
||||||
obj.Health = 0
|
obj.Health = 0
|
||||||
obj.Definition.Packet.ConstructorData(obj) match {
|
obj.Definition.Packet.ConstructorData(obj) match {
|
||||||
case Success(pkt) =>
|
case Success(pkt) =>
|
||||||
|
|
@ -628,7 +628,7 @@ class ConverterTest extends Specification {
|
||||||
obj.Faction = PlanetSideEmpire.TR
|
obj.Faction = PlanetSideEmpire.TR
|
||||||
obj.GUID = PlanetSideGUID(90)
|
obj.GUID = PlanetSideGUID(90)
|
||||||
//obj.Router = PlanetSideGUID(1001)
|
//obj.Router = PlanetSideGUID(1001)
|
||||||
obj.Owner = PlanetSideGUID(5001)
|
obj.OwnerGuid = PlanetSideGUID(5001)
|
||||||
obj.Health = 1
|
obj.Health = 1
|
||||||
obj.Definition.Packet.ConstructorData(obj).isFailure mustEqual true
|
obj.Definition.Packet.ConstructorData(obj).isFailure mustEqual true
|
||||||
|
|
||||||
|
|
@ -641,7 +641,7 @@ class ConverterTest extends Specification {
|
||||||
obj.Faction = PlanetSideEmpire.TR
|
obj.Faction = PlanetSideEmpire.TR
|
||||||
obj.GUID = PlanetSideGUID(90)
|
obj.GUID = PlanetSideGUID(90)
|
||||||
obj.Router = PlanetSideGUID(1001)
|
obj.Router = PlanetSideGUID(1001)
|
||||||
obj.Owner = PlanetSideGUID(5001)
|
obj.OwnerGuid = PlanetSideGUID(5001)
|
||||||
obj.Health = 1
|
obj.Health = 1
|
||||||
obj.Definition.Packet.DetailedConstructorData(obj).isFailure mustEqual true
|
obj.Definition.Packet.DetailedConstructorData(obj).isFailure mustEqual true
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,16 +34,17 @@ class DeployableTest extends Specification {
|
||||||
"Deployable" should {
|
"Deployable" should {
|
||||||
"know its owner by GUID" in {
|
"know its owner by GUID" in {
|
||||||
val obj = new ExplosiveDeployable(GlobalDefinitions.he_mine)
|
val obj = new ExplosiveDeployable(GlobalDefinitions.he_mine)
|
||||||
obj.Owner.isEmpty mustEqual true
|
obj.OwnerGuid.isEmpty mustEqual true
|
||||||
obj.Owner = PlanetSideGUID(10)
|
obj.OwnerGuid = PlanetSideGUID(10)
|
||||||
obj.Owner.contains(PlanetSideGUID(10)) mustEqual true
|
obj.OwnerGuid.contains(PlanetSideGUID(10)) mustEqual true
|
||||||
}
|
}
|
||||||
|
|
||||||
"know its owner by GUID" in {
|
"know its owner by GUID" in {
|
||||||
val obj = new ExplosiveDeployable(GlobalDefinitions.he_mine)
|
// val obj = new ExplosiveDeployable(GlobalDefinitions.he_mine)
|
||||||
obj.OwnerName.isEmpty mustEqual true
|
// obj.OwnerName.isEmpty mustEqual true
|
||||||
obj.OwnerName = "TestCharacter"
|
// obj.OwnerName = "TestCharacter"
|
||||||
obj.OwnerName.contains("TestCharacter") mustEqual true
|
// obj.OwnerName.contains("TestCharacter") mustEqual true
|
||||||
|
ko
|
||||||
}
|
}
|
||||||
|
|
||||||
"know its faction allegiance" in {
|
"know its faction allegiance" in {
|
||||||
|
|
@ -339,8 +340,8 @@ class ExplosiveDeployableJammerTest extends ActorTest {
|
||||||
guid.register(player2, 4)
|
guid.register(player2, 4)
|
||||||
guid.register(weapon, 5)
|
guid.register(weapon, 5)
|
||||||
j_mine.Zone = zone
|
j_mine.Zone = zone
|
||||||
j_mine.Owner = player2
|
j_mine.OwnerGuid = player2
|
||||||
j_mine.OwnerName = player2.Name
|
//j_mine.OwnerName = player2.Name
|
||||||
j_mine.Faction = PlanetSideEmpire.NC
|
j_mine.Faction = PlanetSideEmpire.NC
|
||||||
j_mine.Actor = system.actorOf(Props(classOf[MineDeployableControl], j_mine), "j-mine-control")
|
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(player2, 4)
|
||||||
guid.register(weapon, 5)
|
guid.register(weapon, 5)
|
||||||
h_mine.Zone = zone
|
h_mine.Zone = zone
|
||||||
h_mine.Owner = player2
|
h_mine.OwnerGuid = player2
|
||||||
h_mine.OwnerName = player2.Name
|
//h_mine.OwnerName = player2.Name
|
||||||
h_mine.Faction = PlanetSideEmpire.NC
|
h_mine.Faction = PlanetSideEmpire.NC
|
||||||
h_mine.Actor = system.actorOf(Props(classOf[MineDeployableControl], h_mine), "h-mine-control")
|
h_mine.Actor = system.actorOf(Props(classOf[MineDeployableControl], h_mine), "h-mine-control")
|
||||||
zone.blockMap.addTo(player1)
|
zone.blockMap.addTo(player1)
|
||||||
|
|
@ -528,8 +529,8 @@ class ExplosiveDeployableDestructionTest extends ActorTest {
|
||||||
guid.register(player2, 4)
|
guid.register(player2, 4)
|
||||||
guid.register(weapon, 5)
|
guid.register(weapon, 5)
|
||||||
h_mine.Zone = zone
|
h_mine.Zone = zone
|
||||||
h_mine.Owner = player2
|
h_mine.OwnerGuid = player2
|
||||||
h_mine.OwnerName = player2.Name
|
//h_mine.OwnerName = player2.Name
|
||||||
h_mine.Faction = PlanetSideEmpire.NC
|
h_mine.Faction = PlanetSideEmpire.NC
|
||||||
h_mine.Actor = system.actorOf(Props(classOf[MineDeployableControl], h_mine), "h-mine-control")
|
h_mine.Actor = system.actorOf(Props(classOf[MineDeployableControl], h_mine), "h-mine-control")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -310,7 +310,7 @@ class VehicleControlMountingBlockedExosuitTest extends ActorTest {
|
||||||
// Reset to allow further driver mount mounting tests
|
// Reset to allow further driver mount mounting tests
|
||||||
vehicle.Actor.tell(Mountable.TryDismount(player1, 0), probe.ref)
|
vehicle.Actor.tell(Mountable.TryDismount(player1, 0), probe.ref)
|
||||||
probe.receiveOne(500 milliseconds) //discard
|
probe.receiveOne(500 milliseconds) //discard
|
||||||
vehicle.Owner = None //ensure
|
vehicle.OwnerGuid = None //ensure
|
||||||
vehicle.OwnerName = None //ensure
|
vehicle.OwnerName = None //ensure
|
||||||
vehicle.Actor.tell(Mountable.TryMount(player3, 1), probe.ref) // Agile in driver mount allowing all except MAX
|
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")
|
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 {
|
"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.PermissionGroup(0).contains(VehicleLockState.Locked)) //driver group -> locked
|
||||||
assert(vehicle.Seats(0).occupant.isEmpty)
|
assert(vehicle.Seats(0).occupant.isEmpty)
|
||||||
assert(vehicle.Owner.isEmpty)
|
assert(vehicle.OwnerGuid.isEmpty)
|
||||||
vehicle.Actor.tell(Mountable.TryMount(player1, 1), probe.ref)
|
vehicle.Actor.tell(Mountable.TryMount(player1, 1), probe.ref)
|
||||||
VehicleControlTest.checkCanMount(probe, "")
|
VehicleControlTest.checkCanMount(probe, "")
|
||||||
assert(vehicle.Seats(0).occupant.nonEmpty)
|
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 {
|
"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.PermissionGroup(0).contains(VehicleLockState.Locked)) //driver group -> locked
|
||||||
assert(vehicle.Seats(0).occupant.isEmpty)
|
assert(vehicle.Seats(0).occupant.isEmpty)
|
||||||
vehicle.Owner = player1.GUID
|
vehicle.OwnerGuid = player1.GUID
|
||||||
|
|
||||||
vehicle.Actor.tell(Mountable.TryMount(player1, 1), probe.ref)
|
vehicle.Actor.tell(Mountable.TryMount(player1, 1), probe.ref)
|
||||||
VehicleControlTest.checkCanMount(probe, "")
|
VehicleControlTest.checkCanMount(probe, "")
|
||||||
|
|
@ -434,7 +434,7 @@ class VehicleControlMountingOwnedUnlockedDriverSeatTest extends ActorTest {
|
||||||
vehicle.PermissionGroup(0, 3) //passenger group -> empire
|
vehicle.PermissionGroup(0, 3) //passenger group -> empire
|
||||||
assert(vehicle.PermissionGroup(0).contains(VehicleLockState.Empire)) //driver group -> empire
|
assert(vehicle.PermissionGroup(0).contains(VehicleLockState.Empire)) //driver group -> empire
|
||||||
assert(vehicle.Seats(0).occupant.isEmpty)
|
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)
|
vehicle.Actor.tell(Mountable.TryMount(player1, 1), probe.ref)
|
||||||
VehicleControlTest.checkCanMount(probe, "")
|
VehicleControlTest.checkCanMount(probe, "")
|
||||||
|
|
|
||||||
|
|
@ -121,7 +121,7 @@ class VehicleTest extends Specification {
|
||||||
|
|
||||||
"construct (detailed)" in {
|
"construct (detailed)" in {
|
||||||
val fury_vehicle = Vehicle(GlobalDefinitions.fury)
|
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.size mustEqual 1
|
||||||
fury_vehicle.Seats(0).definition.restriction mustEqual NoMax
|
fury_vehicle.Seats(0).definition.restriction mustEqual NoMax
|
||||||
fury_vehicle.Seats(0).isOccupied mustEqual false
|
fury_vehicle.Seats(0).isOccupied mustEqual false
|
||||||
|
|
@ -148,30 +148,30 @@ class VehicleTest extends Specification {
|
||||||
|
|
||||||
"can be owned by a player" in {
|
"can be owned by a player" in {
|
||||||
val fury_vehicle = Vehicle(GlobalDefinitions.fury)
|
val fury_vehicle = Vehicle(GlobalDefinitions.fury)
|
||||||
fury_vehicle.Owner.isDefined mustEqual false
|
fury_vehicle.OwnerGuid.isDefined mustEqual false
|
||||||
|
|
||||||
val player1 = Player(avatar1)
|
val player1 = Player(avatar1)
|
||||||
player1.GUID = PlanetSideGUID(1)
|
player1.GUID = PlanetSideGUID(1)
|
||||||
fury_vehicle.Owner = player1
|
fury_vehicle.OwnerGuid = player1
|
||||||
fury_vehicle.Owner.isDefined mustEqual true
|
fury_vehicle.OwnerGuid.isDefined mustEqual true
|
||||||
fury_vehicle.Owner.contains(PlanetSideGUID(1)) mustEqual true
|
fury_vehicle.OwnerGuid.contains(PlanetSideGUID(1)) mustEqual true
|
||||||
}
|
}
|
||||||
|
|
||||||
"ownership depends on who last was granted it" in {
|
"ownership depends on who last was granted it" in {
|
||||||
val fury_vehicle = Vehicle(GlobalDefinitions.fury)
|
val fury_vehicle = Vehicle(GlobalDefinitions.fury)
|
||||||
fury_vehicle.Owner.isDefined mustEqual false
|
fury_vehicle.OwnerGuid.isDefined mustEqual false
|
||||||
|
|
||||||
val player1 = Player(avatar1)
|
val player1 = Player(avatar1)
|
||||||
player1.GUID = PlanetSideGUID(1)
|
player1.GUID = PlanetSideGUID(1)
|
||||||
fury_vehicle.Owner = player1
|
fury_vehicle.OwnerGuid = player1
|
||||||
fury_vehicle.Owner.isDefined mustEqual true
|
fury_vehicle.OwnerGuid.isDefined mustEqual true
|
||||||
fury_vehicle.Owner.contains(PlanetSideGUID(1)) mustEqual true
|
fury_vehicle.OwnerGuid.contains(PlanetSideGUID(1)) mustEqual true
|
||||||
|
|
||||||
val player2 = Player(avatar2)
|
val player2 = Player(avatar2)
|
||||||
player2.GUID = PlanetSideGUID(2)
|
player2.GUID = PlanetSideGUID(2)
|
||||||
fury_vehicle.Owner = player2
|
fury_vehicle.OwnerGuid = player2
|
||||||
fury_vehicle.Owner.isDefined mustEqual true
|
fury_vehicle.OwnerGuid.isDefined mustEqual true
|
||||||
fury_vehicle.Owner.contains(PlanetSideGUID(2)) mustEqual true
|
fury_vehicle.OwnerGuid.contains(PlanetSideGUID(2)) mustEqual true
|
||||||
}
|
}
|
||||||
|
|
||||||
"can use mount point to get mount number" in {
|
"can use mount point to get mount number" in {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue