mirror of
https://github.com/psforever/PSF-LoginServer.git
synced 2026-07-16 08:55:18 +00:00
all rewarded support events are accounted for
This commit is contained in:
parent
d6a37e9421
commit
8bdb215d58
19 changed files with 1393 additions and 913 deletions
|
|
@ -363,7 +363,7 @@ DECLARE weaponId Int;
|
||||||
BEGIN
|
BEGIN
|
||||||
killerId := NEW.killer_id;
|
killerId := NEW.killer_id;
|
||||||
weaponId := NEW.weapon_id;
|
weaponId := NEW.weapon_id;
|
||||||
SELECT proc_sessionnumber_get(killerId, killerSessionId);
|
CALL proc_sessionnumber_get(killerId, killerSessionId);
|
||||||
BEGIN
|
BEGIN
|
||||||
UPDATE weaponstatsession
|
UPDATE weaponstatsession
|
||||||
SET assists = assists + 1
|
SET assists = assists + 1
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import akka.actor.typed.{ActorRef, Behavior, PostStop, SupervisorStrategy}
|
||||||
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
import net.psforever.actors.zone.ZoneActor
|
import net.psforever.actors.zone.ZoneActor
|
||||||
import net.psforever.objects.avatar.scoring.{Assist, Death, EquipmentStat, KDAStat, Kill, SupportActivity}
|
import net.psforever.objects.avatar.scoring.{Assist, Death, EquipmentStat, KDAStat, Kill, Life, SupportActivity}
|
||||||
import net.psforever.objects.serverobject.affinity.FactionAffinity
|
import net.psforever.objects.serverobject.affinity.FactionAffinity
|
||||||
import net.psforever.objects.sourcing.VehicleSource
|
import net.psforever.objects.sourcing.VehicleSource
|
||||||
import net.psforever.objects.vital.InGameHistory
|
import net.psforever.objects.vital.InGameHistory
|
||||||
|
|
@ -851,6 +851,14 @@ object AvatarActor {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def updateToolDischargeFor(avatarId: Long, lives: Seq[Life]): Unit = {
|
||||||
|
lives
|
||||||
|
.flatMap { _.equipmentStats }
|
||||||
|
.foreach { stat =>
|
||||||
|
zones.exp.ToDatabase.reportToolDischarge(avatarId, stat)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
def toAvatar(avatar: persistence.Avatar): Avatar = {
|
def toAvatar(avatar: persistence.Avatar): Avatar = {
|
||||||
val bep = avatar.bep
|
val bep = avatar.bep
|
||||||
val convertedCosmetics = if (BattleRank.showCosmetics(bep)) {
|
val convertedCosmetics = if (BattleRank.showCosmetics(bep)) {
|
||||||
|
|
@ -1083,6 +1091,7 @@ class AvatarActor(
|
||||||
.receiveSignal {
|
.receiveSignal {
|
||||||
case (_, PostStop) =>
|
case (_, PostStop) =>
|
||||||
AvatarActor.avatarNoLongerLoggedIn(account.id)
|
AvatarActor.avatarNoLongerLoggedIn(account.id)
|
||||||
|
AvatarActor.updateToolDischargeFor(avatar.id.toLong, avatar.scorecard.CurrentLife +: avatar.scorecard.Lives)
|
||||||
Behaviors.same
|
Behaviors.same
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1707,14 +1716,7 @@ class AvatarActor(
|
||||||
Behaviors.same
|
Behaviors.same
|
||||||
|
|
||||||
case SupportExperienceDeposit(bep, delayBy) =>
|
case SupportExperienceDeposit(bep, delayBy) =>
|
||||||
setBep(avatar.bep + bep, ExperienceType.Support)
|
actuallyAwardSupportExperience(bep, delayBy)
|
||||||
supportExperiencePool = supportExperiencePool - bep
|
|
||||||
if (supportExperiencePool > 0) {
|
|
||||||
resetSupportExperienceTimer(bep, delayBy)
|
|
||||||
} else {
|
|
||||||
supportExperienceTimer.cancel()
|
|
||||||
supportExperienceTimer = Default.Cancellable
|
|
||||||
}
|
|
||||||
Behaviors.same
|
Behaviors.same
|
||||||
|
|
||||||
case SetBep(bep) =>
|
case SetBep(bep) =>
|
||||||
|
|
@ -3022,10 +3024,23 @@ class AvatarActor(
|
||||||
}
|
}
|
||||||
|
|
||||||
def awardSupportExperience(bep: Long, previousDelay: Long): Unit = {
|
def awardSupportExperience(bep: Long, previousDelay: Long): Unit = {
|
||||||
supportExperiencePool = supportExperiencePool + bep
|
setBep(avatar.bep + bep, ExperienceType.Support) //todo simplify support testing
|
||||||
avatar.scorecard.rate(bep)
|
// supportExperiencePool = supportExperiencePool + bep
|
||||||
if (supportExperienceTimer.isCancelled) {
|
// avatar.scorecard.rate(bep)
|
||||||
resetSupportExperienceTimer(previousBep = 0, previousDelay = 0)
|
// if (supportExperienceTimer.isCancelled) {
|
||||||
|
// resetSupportExperienceTimer(previousBep = 0, previousDelay = 0)
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
def actuallyAwardSupportExperience(bep: Long, delayBy: Long): Unit = {
|
||||||
|
setBep(avatar.bep + bep, ExperienceType.Support)
|
||||||
|
supportExperiencePool = supportExperiencePool - bep
|
||||||
|
if (supportExperiencePool > 0) {
|
||||||
|
resetSupportExperienceTimer(bep, delayBy)
|
||||||
|
} else {
|
||||||
|
supportExperiencePool = 0
|
||||||
|
supportExperienceTimer.cancel()
|
||||||
|
supportExperienceTimer = Default.Cancellable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3037,13 +3052,14 @@ class AvatarActor(
|
||||||
val zone = _session.zone
|
val zone = _session.zone
|
||||||
val player = _session.player
|
val player = _session.player
|
||||||
val playerSource = PlayerSource(player)
|
val playerSource = PlayerSource(player)
|
||||||
val historyTranscript = (killStat.info.interaction.cause match {
|
val historyTranscript = {
|
||||||
|
(killStat.info.interaction.cause match {
|
||||||
case pr: ProjectileReason => pr.projectile.mounted_in.flatMap { a => zone.GUID(a._1) } //what fired the projectile
|
case pr: ProjectileReason => pr.projectile.mounted_in.flatMap { a => zone.GUID(a._1) } //what fired the projectile
|
||||||
case _ => None
|
case _ => None
|
||||||
}) match {
|
}).collect {
|
||||||
case Some(mount: PlanetSideGameObject with FactionAffinity with InGameHistory with MountedWeapons) =>
|
case mount: PlanetSideGameObject with FactionAffinity with InGameHistory with MountedWeapons =>
|
||||||
player.HistoryAndContributions() ++ InGameHistory.ContributionFrom(mount).toList
|
player.ContributionFrom(mount)
|
||||||
case _ =>
|
}
|
||||||
player.HistoryAndContributions()
|
player.HistoryAndContributions()
|
||||||
}
|
}
|
||||||
zone.actor ! ZoneActor.RewardOurSupporters(playerSource, historyTranscript, killStat, exp)
|
zone.actor ! ZoneActor.RewardOurSupporters(playerSource, historyTranscript, killStat, exp)
|
||||||
|
|
@ -3133,7 +3149,6 @@ class AvatarActor(
|
||||||
|
|
||||||
def updateToolDischarge(stats: EquipmentStat): Unit = {
|
def updateToolDischarge(stats: EquipmentStat): Unit = {
|
||||||
avatar.scorecard.rate(stats)
|
avatar.scorecard.rate(stats)
|
||||||
zones.exp.ToDatabase.reportToolDischarge(avatar.id.toLong, stats)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def createAvatar(
|
def createAvatar(
|
||||||
|
|
|
||||||
|
|
@ -95,6 +95,7 @@ class Vehicle(private val vehicleDef: VehicleDefinition)
|
||||||
interaction(new InteractWithRadiationCloudsSeatedInVehicle(obj = this, range = 20))
|
interaction(new InteractWithRadiationCloudsSeatedInVehicle(obj = this, range = 20))
|
||||||
|
|
||||||
private var faction: PlanetSideEmpire.Value = PlanetSideEmpire.NEUTRAL
|
private var faction: PlanetSideEmpire.Value = PlanetSideEmpire.NEUTRAL
|
||||||
|
private var previousFaction: PlanetSideEmpire.Value = PlanetSideEmpire.NEUTRAL
|
||||||
private var shields: Int = 0
|
private var shields: Int = 0
|
||||||
private var decal: Int = 0
|
private var decal: Int = 0
|
||||||
private var trunkAccess: Option[PlanetSideGUID] = None
|
private var trunkAccess: Option[PlanetSideGUID] = None
|
||||||
|
|
@ -128,14 +129,18 @@ class Vehicle(private val vehicleDef: VehicleDefinition)
|
||||||
}
|
}
|
||||||
|
|
||||||
def Faction: PlanetSideEmpire.Value = {
|
def Faction: PlanetSideEmpire.Value = {
|
||||||
this.faction
|
|
||||||
}
|
|
||||||
|
|
||||||
override def Faction_=(faction: PlanetSideEmpire.Value): PlanetSideEmpire.Value = {
|
|
||||||
this.faction = faction
|
|
||||||
faction
|
faction
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override def Faction_=(toFaction: PlanetSideEmpire.Value): PlanetSideEmpire.Value = {
|
||||||
|
//TODO in the future, this may create an issue when the vehicle is originally or is hacked from Black Ops
|
||||||
|
previousFaction = faction
|
||||||
|
faction = toFaction
|
||||||
|
toFaction
|
||||||
|
}
|
||||||
|
|
||||||
|
def PreviousFaction: PlanetSideEmpire.Value = previousFaction
|
||||||
|
|
||||||
/** How long it takes to jack the vehicle in seconds, based on the hacker's certification level */
|
/** How long it takes to jack the vehicle in seconds, based on the hacker's certification level */
|
||||||
def JackingDuration: Array[Int] = Definition.JackingDuration
|
def JackingDuration: Array[Int] = Definition.JackingDuration
|
||||||
|
|
||||||
|
|
@ -335,9 +340,9 @@ class Vehicle(private val vehicleDef: VehicleDefinition)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override def DeployTime = Definition.DeployTime
|
override def DeployTime: Int = Definition.DeployTime
|
||||||
|
|
||||||
override def UndeployTime = Definition.UndeployTime
|
override def UndeployTime: Int = Definition.UndeployTime
|
||||||
|
|
||||||
def Inventory: GridInventory = trunk
|
def Inventory: GridInventory = trunk
|
||||||
|
|
||||||
|
|
@ -495,7 +500,7 @@ class Vehicle(private val vehicleDef: VehicleDefinition)
|
||||||
|
|
||||||
def PreviousGatingManifest(): Option[VehicleManifest] = previousVehicleGatingManifest
|
def PreviousGatingManifest(): Option[VehicleManifest] = previousVehicleGatingManifest
|
||||||
|
|
||||||
def DamageModel = Definition.asInstanceOf[DamageResistanceModel]
|
def DamageModel: DamageResistanceModel = Definition.asInstanceOf[DamageResistanceModel]
|
||||||
|
|
||||||
override def BailProtection_=(protect: Boolean): Boolean = {
|
override def BailProtection_=(protect: Boolean): Boolean = {
|
||||||
!Definition.CanFly && super.BailProtection_=(protect)
|
!Definition.CanFly && super.BailProtection_=(protect)
|
||||||
|
|
|
||||||
|
|
@ -237,16 +237,14 @@ object Vehicles {
|
||||||
val zone = target.Zone
|
val zone = target.Zone
|
||||||
// Forcefully dismount any cargo
|
// Forcefully dismount any cargo
|
||||||
target.CargoHolds.foreach { case (_, cargoHold) =>
|
target.CargoHolds.foreach { case (_, cargoHold) =>
|
||||||
cargoHold.occupant match {
|
cargoHold.occupant.collect {
|
||||||
case Some(cargo: Vehicle) =>
|
cargo: Vehicle => cargo.Actor ! CargoBehavior.StartCargoDismounting(bailed = false)
|
||||||
cargo.Actor ! CargoBehavior.StartCargoDismounting(bailed = false)
|
|
||||||
case None => ;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Forcefully dismount all seated occupants from the vehicle
|
// Forcefully dismount all seated occupants from the vehicle
|
||||||
target.Seats.values.foreach(seat => {
|
target.Seats.values.foreach(seat => {
|
||||||
seat.occupant match {
|
seat.occupant.collect {
|
||||||
case Some(tplayer: Player) =>
|
tplayer: Player =>
|
||||||
seat.unmount(tplayer)
|
seat.unmount(tplayer)
|
||||||
tplayer.VehicleSeated = None
|
tplayer.VehicleSeated = None
|
||||||
if (tplayer.HasGUID) {
|
if (tplayer.HasGUID) {
|
||||||
|
|
@ -255,7 +253,6 @@ object Vehicles {
|
||||||
VehicleAction.KickPassenger(tplayer.GUID, 4, unk2 = false, target.GUID)
|
VehicleAction.KickPassenger(tplayer.GUID, 4, unk2 = false, target.GUID)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
case _ => ;
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
// If the vehicle can fly and is flying deconstruct it, and well played to whomever managed to hack a plane in mid air. I'm impressed.
|
// If the vehicle can fly and is flying deconstruct it, and well played to whomever managed to hack a plane in mid air. I'm impressed.
|
||||||
|
|
@ -264,24 +261,16 @@ object Vehicles {
|
||||||
target.Actor ! Vehicle.Deconstruct()
|
target.Actor ! Vehicle.Deconstruct()
|
||||||
} else { // Otherwise handle ownership transfer as normal
|
} else { // Otherwise handle ownership transfer as normal
|
||||||
// Remove ownership of our current vehicle, if we have one
|
// Remove ownership of our current vehicle, if we have one
|
||||||
hacker.avatar.vehicle match {
|
hacker.avatar.vehicle
|
||||||
case Some(guid: PlanetSideGUID) =>
|
.flatMap { guid => zone.GUID(guid) }
|
||||||
zone.GUID(guid) match {
|
.collect { case vehicle: Vehicle =>
|
||||||
case Some(vehicle: Vehicle) =>
|
|
||||||
Vehicles.Disown(hacker, vehicle)
|
Vehicles.Disown(hacker, vehicle)
|
||||||
case _ => ;
|
|
||||||
}
|
}
|
||||||
case _ => ;
|
|
||||||
}
|
|
||||||
target.OwnerGuid match {
|
|
||||||
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 {
|
target.OwnerGuid
|
||||||
case Some(tplayer: Player) =>
|
.flatMap { guid => zone.GUID(guid) }
|
||||||
|
.collect { case tplayer: Player =>
|
||||||
Vehicles.Disown(tplayer, target)
|
Vehicles.Disown(tplayer, target)
|
||||||
case _ => ; // Vehicle already has no owner
|
|
||||||
}
|
|
||||||
case _ => ;
|
|
||||||
}
|
}
|
||||||
// Now take ownership of the jacked vehicle
|
// Now take ownership of the jacked vehicle
|
||||||
target.Actor ! CommonMessages.Hack(hacker, target)
|
target.Actor ! CommonMessages.Hack(hacker, target)
|
||||||
|
|
@ -302,16 +291,15 @@ object Vehicles {
|
||||||
// If AMS is deployed, swap it to the new faction
|
// If AMS is deployed, swap it to the new faction
|
||||||
target.Definition match {
|
target.Definition match {
|
||||||
case GlobalDefinitions.router =>
|
case GlobalDefinitions.router =>
|
||||||
target.Utility(UtilityType.internal_router_telepad_deployable) match {
|
target.Utility(UtilityType.internal_router_telepad_deployable).collect {
|
||||||
case Some(util: Utility.InternalTelepad) =>
|
case util: Utility.InternalTelepad =>
|
||||||
//"power cycle"
|
//"power cycle"
|
||||||
util.Actor ! TelepadLike.Deactivate(util)
|
util.Actor ! TelepadLike.Deactivate(util)
|
||||||
util.Actor ! TelepadLike.Activate(util)
|
util.Actor ! TelepadLike.Activate(util)
|
||||||
case _ => ;
|
|
||||||
}
|
}
|
||||||
case GlobalDefinitions.ams if target.DeploymentState == DriveState.Deployed =>
|
case GlobalDefinitions.ams if target.DeploymentState == DriveState.Deployed =>
|
||||||
zone.VehicleEvents ! VehicleServiceMessage.AMSDeploymentChange(zone)
|
zone.VehicleEvents ! VehicleServiceMessage.AMSDeploymentChange(zone)
|
||||||
case _ => ;
|
case _ => ()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -412,6 +400,7 @@ object Vehicles {
|
||||||
*
|
*
|
||||||
* @param vehicle the vehicle
|
* @param vehicle the vehicle
|
||||||
*/
|
*/
|
||||||
|
//noinspection ScalaUnusedSymbol
|
||||||
def BeforeUnloadVehicle(vehicle: Vehicle, zone: Zone): Unit = {
|
def BeforeUnloadVehicle(vehicle: Vehicle, zone: Zone): Unit = {
|
||||||
vehicle.Definition match {
|
vehicle.Definition match {
|
||||||
case GlobalDefinitions.ams =>
|
case GlobalDefinitions.ams =>
|
||||||
|
|
@ -420,7 +409,7 @@ object Vehicles {
|
||||||
vehicle.Actor ! Deployment.TryUndeploy(DriveState.Undeploying)
|
vehicle.Actor ! Deployment.TryUndeploy(DriveState.Undeploying)
|
||||||
case GlobalDefinitions.router =>
|
case GlobalDefinitions.router =>
|
||||||
vehicle.Actor ! Deployment.TryUndeploy(DriveState.Undeploying)
|
vehicle.Actor ! Deployment.TryUndeploy(DriveState.Undeploying)
|
||||||
case _ => ;
|
case _ => ()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,10 @@ object PlayerSource {
|
||||||
}
|
}
|
||||||
|
|
||||||
def apply(name: String, faction: PlanetSideEmpire.Value, position: Vector3): PlayerSource = {
|
def apply(name: String, faction: PlanetSideEmpire.Value, position: Vector3): PlayerSource = {
|
||||||
|
this(UniquePlayer(0L, name, CharacterSex.Male, faction), position)
|
||||||
|
}
|
||||||
|
|
||||||
|
def apply(unique: UniquePlayer, position: Vector3): PlayerSource = {
|
||||||
new PlayerSource(
|
new PlayerSource(
|
||||||
GlobalDefinitions.avatar,
|
GlobalDefinitions.avatar,
|
||||||
ExoSuitType.Standard,
|
ExoSuitType.Standard,
|
||||||
|
|
@ -81,7 +85,7 @@ object PlayerSource {
|
||||||
GlobalDefinitions.Standard,
|
GlobalDefinitions.Standard,
|
||||||
bep = 0L,
|
bep = 0L,
|
||||||
progress = tokenLife,
|
progress = tokenLife,
|
||||||
UniquePlayer(0L, name, CharacterSex.Male, faction)
|
unique
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -137,6 +141,19 @@ object PlayerSource {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Produce a copy of a normal player source entity
|
||||||
|
* but the `seatedIn` field is overrode to point at the specified vehicle and seat number.<br>
|
||||||
|
* Don't think too much about it.
|
||||||
|
* @param player `SourceEntry` for a player
|
||||||
|
* @param source `SourceEntry` for the aforementioned mountable entity
|
||||||
|
* @param seatNumber the attributed seating index in which the player is mounted in `source`
|
||||||
|
* @return a `PlayerSource` entity
|
||||||
|
*/
|
||||||
|
def inSeat(player: PlayerSource, source: SourceEntry, seatNumber: Int): PlayerSource = {
|
||||||
|
player.copy(seatedIn = Some((source, seatNumber)))
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* "Nobody is my name: Nobody they call me –
|
* "Nobody is my name: Nobody they call me –
|
||||||
* my mother and my father and all my other companions”
|
* my mother and my father and all my other companions”
|
||||||
|
|
@ -146,5 +163,8 @@ object PlayerSource {
|
||||||
*/
|
*/
|
||||||
final val Nobody = PlayerSource("Nobody", PlanetSideEmpire.NEUTRAL, Vector3.Zero)
|
final val Nobody = PlayerSource("Nobody", PlanetSideEmpire.NEUTRAL, Vector3.Zero)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to dummy the statistics value for shallow player source entities.
|
||||||
|
*/
|
||||||
private val tokenLife: Life = Life()
|
private val tokenLife: Life = Life()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +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],
|
owner: Option[UniquePlayer],
|
||||||
occupants: List[SourceEntry],
|
occupants: List[SourceEntry],
|
||||||
Modifiers: ResistanceProfile,
|
Modifiers: ResistanceProfile,
|
||||||
unique: UniqueVehicle
|
unique: UniqueVehicle
|
||||||
|
|
@ -54,13 +54,15 @@ object VehicleSource {
|
||||||
obj.OriginalOwnerName.getOrElse("none")
|
obj.OriginalOwnerName.getOrElse("none")
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
vehicle.copy(occupants = {
|
//shallow information that references the existing source entry
|
||||||
obj.Seats.map { case (seatNumber, seat) =>
|
vehicle.copy(
|
||||||
|
owner = obj.Owners,
|
||||||
|
occupants = obj.Seats.map { case (seatNumber, seat) =>
|
||||||
seat.occupant match {
|
seat.occupant match {
|
||||||
case Some(p) => PlayerSource.inSeat(p, vehicle, seatNumber) //shallow
|
case Some(p) => PlayerSource.inSeat(p, vehicle, seatNumber)
|
||||||
case _ => PlayerSource.Nobody
|
case _ => PlayerSource.Nobody
|
||||||
}
|
}
|
||||||
}.toList
|
}.toList
|
||||||
})
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,14 +32,12 @@ trait CarrierBehavior {
|
||||||
cargoDismountTimer.cancel()
|
cargoDismountTimer.cancel()
|
||||||
val obj = CarrierObject
|
val obj = CarrierObject
|
||||||
val zone = obj.Zone
|
val zone = obj.Zone
|
||||||
zone.GUID(isMounting) match {
|
zone.GUID(isMounting).collect {
|
||||||
case Some(v : Vehicle) => v.Actor ! CargoBehavior.EndCargoMounting(obj.GUID)
|
case v : Vehicle => v.Actor ! CargoBehavior.EndCargoMounting(obj.GUID)
|
||||||
case _ => ;
|
|
||||||
}
|
}
|
||||||
isMounting = None
|
isMounting = None
|
||||||
zone.GUID(isDismounting) match {
|
zone.GUID(isDismounting).collect {
|
||||||
case Some(v : Vehicle) => v.Actor ! CargoBehavior.EndCargoDismounting(obj.GUID)
|
case v : Vehicle => v.Actor ! CargoBehavior.EndCargoDismounting(obj.GUID)
|
||||||
case _ => ;
|
|
||||||
}
|
}
|
||||||
isDismounting = None
|
isDismounting = None
|
||||||
}
|
}
|
||||||
|
|
@ -86,9 +84,8 @@ trait CarrierBehavior {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
obj.Zone.GUID(isMounting) match {
|
obj.Zone.GUID(isMounting).collect {
|
||||||
case Some(v: Vehicle) => v.Actor ! CargoBehavior.EndCargoMounting(obj.GUID)
|
case v: Vehicle => v.Actor ! CargoBehavior.EndCargoMounting(obj.GUID)
|
||||||
case _ => ;
|
|
||||||
}
|
}
|
||||||
isMounting = None
|
isMounting = None
|
||||||
}
|
}
|
||||||
|
|
@ -112,10 +109,9 @@ trait CarrierBehavior {
|
||||||
kicked = false
|
kicked = false
|
||||||
)
|
)
|
||||||
case _ =>
|
case _ =>
|
||||||
obj.CargoHold(mountPoint) match {
|
obj.CargoHold(mountPoint).collect {
|
||||||
case Some(hold) if hold.isOccupied && hold.occupant.get.GUID == cargo_guid =>
|
case hold if hold.isOccupied && hold.occupant.get.GUID == cargo_guid =>
|
||||||
CarrierBehavior.CargoDismountAction(obj, hold.occupant.get, hold, BailType.Normal)
|
CarrierBehavior.CargoDismountAction(obj, hold.occupant.get, hold, BailType.Normal)
|
||||||
case _ => ;
|
|
||||||
}
|
}
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
@ -132,17 +128,15 @@ trait CarrierBehavior {
|
||||||
CarrierBehavior.CheckCargoDismount(cargo_guid, mountPoint, iteration + 1, bailed)
|
CarrierBehavior.CheckCargoDismount(cargo_guid, mountPoint, iteration + 1, bailed)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
zone.GUID(isDismounting.getOrElse(cargo_guid)) match {
|
zone.GUID(isDismounting.getOrElse(cargo_guid)).collect {
|
||||||
case Some(cargo: Vehicle) =>
|
case cargo: Vehicle =>
|
||||||
cargo.Actor ! CargoBehavior.EndCargoDismounting(guid)
|
cargo.Actor ! CargoBehavior.EndCargoDismounting(guid)
|
||||||
case _ => ;
|
|
||||||
}
|
}
|
||||||
isDismounting = None
|
isDismounting = None
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
zone.GUID(isDismounting.getOrElse(cargo_guid)) match {
|
zone.GUID(isDismounting.getOrElse(cargo_guid)).collect {
|
||||||
case Some(cargo: Vehicle) => cargo.Actor ! CargoBehavior.EndCargoDismounting(guid)
|
case cargo: Vehicle => cargo.Actor ! CargoBehavior.EndCargoDismounting(guid)
|
||||||
case _ => ;
|
|
||||||
}
|
}
|
||||||
isDismounting = None
|
isDismounting = None
|
||||||
}
|
}
|
||||||
|
|
@ -526,7 +520,7 @@ object CarrierBehavior {
|
||||||
targetGUID: PlanetSideGUID
|
targetGUID: PlanetSideGUID
|
||||||
): Unit = {
|
): Unit = {
|
||||||
target match {
|
target match {
|
||||||
case Some(_: Vehicle) => ;
|
case Some(_: Vehicle) => ()
|
||||||
case Some(_) => log.error(s"$decorator target $targetGUID no longer identifies as a vehicle")
|
case Some(_) => log.error(s"$decorator target $targetGUID no longer identifies as a vehicle")
|
||||||
case None => log.error(s"$decorator target $targetGUID has gone missing")
|
case None => log.error(s"$decorator target $targetGUID has gone missing")
|
||||||
}
|
}
|
||||||
|
|
@ -662,7 +656,17 @@ object CarrierBehavior {
|
||||||
carrierGuid: PlanetSideGUID): Unit = {
|
carrierGuid: PlanetSideGUID): Unit = {
|
||||||
hold.mount(cargo)
|
hold.mount(cargo)
|
||||||
cargo.MountedIn = carrierGuid
|
cargo.MountedIn = carrierGuid
|
||||||
cargo.LogActivity(VehicleCargoMountActivity(VehicleSource(carrier), VehicleSource(cargo), carrier.Zone.Number))
|
val event = VehicleCargoMountActivity(VehicleSource(carrier), VehicleSource(cargo), carrier.Zone.Number)
|
||||||
|
cargo.LogActivity(event)
|
||||||
|
cargo.Seats
|
||||||
|
.filterNot(_._1 == 0) /*ignore driver*/
|
||||||
|
.values
|
||||||
|
.collect {
|
||||||
|
case seat if seat.isOccupied =>
|
||||||
|
seat.occupants.foreach { player =>
|
||||||
|
player.LogActivity(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
cargo.Actor ! CargoBehavior.EndCargoMounting(carrierGuid)
|
cargo.Actor ! CargoBehavior.EndCargoMounting(carrierGuid)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -681,6 +685,18 @@ object CarrierBehavior {
|
||||||
): Unit = {
|
): Unit = {
|
||||||
cargo.MountedIn = None
|
cargo.MountedIn = None
|
||||||
hold.unmount(cargo, bailType)
|
hold.unmount(cargo, bailType)
|
||||||
cargo.LogActivity(VehicleCargoMountActivity(VehicleSource(carrier), VehicleSource(cargo), carrier.Zone.Number))
|
val event = VehicleCargoMountActivity(VehicleSource(carrier), VehicleSource(cargo), carrier.Zone.Number)
|
||||||
|
cargo.LogActivity(event)
|
||||||
|
cargo.Seats
|
||||||
|
.filterNot(_._1 == 0) /*ignore driver*/
|
||||||
|
.values
|
||||||
|
.collect {
|
||||||
|
case seat if seat.isOccupied =>
|
||||||
|
seat.occupants.foreach { player =>
|
||||||
|
player.LogActivity(event)
|
||||||
|
player.ContributionFrom(cargo)
|
||||||
|
player.ContributionFrom(carrier)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ package net.psforever.objects.vital
|
||||||
import net.psforever.objects.PlanetSideGameObject
|
import net.psforever.objects.PlanetSideGameObject
|
||||||
import net.psforever.objects.definition.{EquipmentDefinition, KitDefinition, ToolDefinition}
|
import net.psforever.objects.definition.{EquipmentDefinition, KitDefinition, ToolDefinition}
|
||||||
import net.psforever.objects.serverobject.affinity.FactionAffinity
|
import net.psforever.objects.serverobject.affinity.FactionAffinity
|
||||||
import net.psforever.objects.sourcing.{AmenitySource, ObjectSource, PlayerSource, SourceEntry, SourceUniqueness, SourceWithHealthEntry, VehicleSource}
|
import net.psforever.objects.sourcing.{AmenitySource, PlayerSource, SourceEntry, SourceUniqueness, SourceWithHealthEntry, VehicleSource}
|
||||||
import net.psforever.objects.vital.environment.EnvironmentReason
|
import net.psforever.objects.vital.environment.EnvironmentReason
|
||||||
import net.psforever.objects.vital.etc.{ExplodingEntityReason, PainboxReason, SuicideReason}
|
import net.psforever.objects.vital.etc.{ExplodingEntityReason, PainboxReason, SuicideReason}
|
||||||
import net.psforever.objects.vital.interaction.{DamageInteraction, DamageResult}
|
import net.psforever.objects.vital.interaction.{DamageInteraction, DamageResult}
|
||||||
|
|
@ -20,7 +20,21 @@ import scala.collection.mutable
|
||||||
* Must keep track of the time (ms) the activity occurred.
|
* Must keep track of the time (ms) the activity occurred.
|
||||||
*/
|
*/
|
||||||
trait InGameActivity {
|
trait InGameActivity {
|
||||||
val time: Long = System.currentTimeMillis()
|
private var _time: Long = System.currentTimeMillis()
|
||||||
|
|
||||||
|
def time: Long = _time
|
||||||
|
}
|
||||||
|
|
||||||
|
object InGameActivity {
|
||||||
|
def ShareTime(benefactor: InGameActivity, donor: InGameActivity): InGameActivity = {
|
||||||
|
benefactor._time = donor.time
|
||||||
|
benefactor
|
||||||
|
}
|
||||||
|
|
||||||
|
def SetTime(benefactor: InGameActivity, time: Long): InGameActivity = {
|
||||||
|
benefactor._time = time
|
||||||
|
benefactor
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* normal history */
|
/* normal history */
|
||||||
|
|
@ -66,14 +80,22 @@ sealed trait VehicleCargoMountChange extends VehicleMountChange {
|
||||||
final case class VehicleMountActivity(vehicle: VehicleSource, player: PlayerSource, zoneNumber: Int)
|
final case class VehicleMountActivity(vehicle: VehicleSource, player: PlayerSource, zoneNumber: Int)
|
||||||
extends VehiclePassengerMountChange
|
extends VehiclePassengerMountChange
|
||||||
|
|
||||||
final case class VehicleDismountActivity(vehicle: VehicleSource, player: PlayerSource, zoneNumber: Int)
|
final case class VehicleDismountActivity(
|
||||||
extends VehiclePassengerMountChange
|
vehicle: VehicleSource,
|
||||||
|
player: PlayerSource,
|
||||||
|
zoneNumber: Int,
|
||||||
|
pairedEvent: Option[VehicleMountActivity] = None
|
||||||
|
) extends VehiclePassengerMountChange
|
||||||
|
|
||||||
final case class VehicleCargoMountActivity(vehicle: VehicleSource, cargo: VehicleSource, zoneNumber: Int)
|
final case class VehicleCargoMountActivity(vehicle: VehicleSource, cargo: VehicleSource, zoneNumber: Int)
|
||||||
extends VehicleCargoMountChange
|
extends VehicleCargoMountChange
|
||||||
|
|
||||||
final case class VehicleCargoDismountActivity(vehicle: VehicleSource, cargo: VehicleSource, zoneNumber: Int)
|
final case class VehicleCargoDismountActivity(
|
||||||
extends VehicleCargoMountChange
|
vehicle: VehicleSource,
|
||||||
|
cargo: VehicleSource,
|
||||||
|
zoneNumber: Int,
|
||||||
|
pairedEvent: Option[VehicleCargoMountActivity] = None
|
||||||
|
) extends VehicleCargoMountChange
|
||||||
|
|
||||||
final case class Contribution(src: SourceUniqueness, entries: List[InGameActivity])
|
final case class Contribution(src: SourceUniqueness, entries: List[InGameActivity])
|
||||||
extends GeneralActivity {
|
extends GeneralActivity {
|
||||||
|
|
@ -202,11 +224,34 @@ trait InGameHistory {
|
||||||
/**
|
/**
|
||||||
* An in-game event must be recorded.
|
* An in-game event must be recorded.
|
||||||
* Add new entry to the list (for recent activity).
|
* Add new entry to the list (for recent activity).
|
||||||
|
* Special handling must be conducted for certain events.
|
||||||
* @param action the fully-informed entry
|
* @param action the fully-informed entry
|
||||||
* @return the list of previous changes to this entity
|
* @return the list of previous changes to this entity
|
||||||
*/
|
*/
|
||||||
def LogActivity(action: Option[InGameActivity]): List[InGameActivity] = {
|
def LogActivity(action: Option[InGameActivity]): List[InGameActivity] = {
|
||||||
action match {
|
action match {
|
||||||
|
case Some(act: VehicleDismountActivity) =>
|
||||||
|
history
|
||||||
|
.findLast(_.isInstanceOf[VehicleMountActivity])
|
||||||
|
.collect {
|
||||||
|
case event: VehicleMountActivity if event.vehicle.unique == act.vehicle.unique =>
|
||||||
|
history = history :+ InGameActivity.ShareTime(act.copy(pairedEvent = Some(event)), act)
|
||||||
|
}
|
||||||
|
.orElse {
|
||||||
|
history = history :+ act
|
||||||
|
None
|
||||||
|
}
|
||||||
|
case Some(act: VehicleCargoDismountActivity) =>
|
||||||
|
history
|
||||||
|
.findLast(_.isInstanceOf[VehicleCargoMountActivity])
|
||||||
|
.collect {
|
||||||
|
case event: VehicleCargoMountActivity if event.vehicle.unique == act.vehicle.unique =>
|
||||||
|
history = history :+ InGameActivity.ShareTime(act.copy(pairedEvent = Some(event)), act)
|
||||||
|
}
|
||||||
|
.orElse {
|
||||||
|
history = history :+ act
|
||||||
|
None
|
||||||
|
}
|
||||||
case Some(act) =>
|
case Some(act) =>
|
||||||
history = history :+ act
|
history = history :+ act
|
||||||
case None => ()
|
case None => ()
|
||||||
|
|
@ -264,13 +309,18 @@ trait InGameHistory {
|
||||||
if (target eq this) {
|
if (target eq this) {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
target.GetContribution() match {
|
val uniqueTarget = SourceEntry(target).unique
|
||||||
case Some(in) =>
|
(target.GetContribution(), contributionInheritance.get(uniqueTarget)) match {
|
||||||
val contribution = Contribution(SourceEntry(target).unique, in)
|
case (Some(in), Some(curr)) =>
|
||||||
contributionInheritance.put(contribution.src, contribution)
|
val end = curr.end
|
||||||
|
val contribution = Contribution(uniqueTarget, curr.entries ++ in.filter(_.time > end))
|
||||||
|
contributionInheritance.put(uniqueTarget, contribution)
|
||||||
Some(contribution)
|
Some(contribution)
|
||||||
case None =>
|
case (Some(in), _) =>
|
||||||
contributionInheritance.remove(SourceEntry(target).unique)
|
val contribution = Contribution(uniqueTarget, in)
|
||||||
|
contributionInheritance.put(uniqueTarget, contribution)
|
||||||
|
Some(contribution)
|
||||||
|
case (None, _) =>
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -310,9 +360,9 @@ object InGameHistory {
|
||||||
val toUnitSource = unit.collect { case o: PlanetSideGameObject with FactionAffinity => SourceEntry(o) }
|
val toUnitSource = unit.collect { case o: PlanetSideGameObject with FactionAffinity => SourceEntry(o) }
|
||||||
|
|
||||||
val event: GeneralActivity = if (obj.History.isEmpty) {
|
val event: GeneralActivity = if (obj.History.isEmpty) {
|
||||||
SpawningActivity(ObjectSource(obj), zoneNumber, toUnitSource)
|
SpawningActivity(SourceEntry(obj), zoneNumber, toUnitSource)
|
||||||
} else {
|
} else {
|
||||||
ReconstructionActivity(ObjectSource(obj), zoneNumber, toUnitSource)
|
ReconstructionActivity(SourceEntry(obj), zoneNumber, toUnitSource)
|
||||||
}
|
}
|
||||||
if (obj.History.lastOption match {
|
if (obj.History.lastOption match {
|
||||||
case Some(evt: SpawningActivity) => evt != event
|
case Some(evt: SpawningActivity) => evt != event
|
||||||
|
|
|
||||||
|
|
@ -5,27 +5,34 @@ import enumeratum.values.IntEnumEntry
|
||||||
|
|
||||||
sealed abstract class EquipmentUseContextWrapper(val value: Int) extends IntEnumEntry {
|
sealed abstract class EquipmentUseContextWrapper(val value: Int) extends IntEnumEntry {
|
||||||
def equipment: Int
|
def equipment: Int
|
||||||
|
def intermediate: Int
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed abstract class NoIntermediateUseContextWrapper(override val value: Int)
|
||||||
|
extends EquipmentUseContextWrapper(value) {
|
||||||
def intermediate: Int = 0
|
def intermediate: Int = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
final case class NoUse(equipment: Int) extends EquipmentUseContextWrapper(value = -1)
|
final case class NoUse() extends NoIntermediateUseContextWrapper(value = -1) {
|
||||||
|
def equipment: Int = 0
|
||||||
|
}
|
||||||
|
|
||||||
final case class DamageWith(equipment: Int) extends EquipmentUseContextWrapper(value = 0)
|
final case class DamageWith(equipment: Int) extends NoIntermediateUseContextWrapper(value = 0)
|
||||||
|
|
||||||
final case class Destroyed(equipment: Int) extends EquipmentUseContextWrapper(value = 1)
|
final case class Destroyed(equipment: Int) extends NoIntermediateUseContextWrapper(value = 1)
|
||||||
final case class ReviveAssist(equipment: Int) extends EquipmentUseContextWrapper(value = 4)
|
final case class ReviveAssist(equipment: Int) extends NoIntermediateUseContextWrapper(value = 4)
|
||||||
final case class AmenityDestroyed(equipment: Int) extends EquipmentUseContextWrapper(value = 10)
|
final case class AmenityDestroyed(equipment: Int, intermediate: Int) extends EquipmentUseContextWrapper(value = 10)
|
||||||
final case class DriverKilled(equipment: Int) extends EquipmentUseContextWrapper(value = 12)
|
final case class DriverKilled(equipment: Int) extends NoIntermediateUseContextWrapper(value = 12)
|
||||||
final case class GunnerKilled(equipment: Int) extends EquipmentUseContextWrapper(value = 13)
|
final case class GunnerKilled(equipment: Int) extends NoIntermediateUseContextWrapper(value = 13)
|
||||||
final case class PassengerKilled(equipment: Int) extends EquipmentUseContextWrapper(value = 14)
|
final case class PassengerKilled(equipment: Int) extends NoIntermediateUseContextWrapper(value = 14)
|
||||||
final case class CargoDestroyed(equipment: Int) extends EquipmentUseContextWrapper(value = 15)
|
final case class CargoDestroyed(equipment: Int, intermediate: Int) extends EquipmentUseContextWrapper(value = 15)
|
||||||
final case class DriverAssist(equipment: Int) extends EquipmentUseContextWrapper(value = 18)
|
final case class DriverAssist(equipment: Int) extends NoIntermediateUseContextWrapper(value = 18)
|
||||||
final case class HealKillAssist(equipment: Int) extends EquipmentUseContextWrapper(value = 20)
|
final case class HealKillAssist(equipment: Int) extends NoIntermediateUseContextWrapper(value = 20)
|
||||||
final case class ReviveKillAssist(equipment: Int) extends EquipmentUseContextWrapper(value = 21)
|
final case class ReviveKillAssist(equipment: Int) extends NoIntermediateUseContextWrapper(value = 21)
|
||||||
final case class RepairKillAssist(equipment: Int, override val intermediate: Int) extends EquipmentUseContextWrapper(value = 22)
|
final case class RepairKillAssist(equipment: Int, intermediate: Int) extends EquipmentUseContextWrapper(value = 22)
|
||||||
final case class AmsRespawnKillAssist(equipment: Int) extends EquipmentUseContextWrapper(value = 23)
|
final case class AmsRespawnKillAssist(equipment: Int, intermediate: Int) extends EquipmentUseContextWrapper(value = 23)
|
||||||
final case class HotDropKillAssist(equipment: Int, override val intermediate: Int) extends EquipmentUseContextWrapper(value = 24)
|
final case class HotDropKillAssist(equipment: Int, intermediate: Int) extends EquipmentUseContextWrapper(value = 24)
|
||||||
final case class HackKillAssist(equipment: Int) extends EquipmentUseContextWrapper(value = 25)
|
final case class HackKillAssist(equipment: Int, intermediate: Int) extends EquipmentUseContextWrapper(value = 25)
|
||||||
final case class LodestarRearmKillAssist(equipment: Int) extends EquipmentUseContextWrapper(value = 26)
|
final case class LodestarRearmKillAssist(equipment: Int) extends NoIntermediateUseContextWrapper(value = 26)
|
||||||
final case class AmsResupplyKillAssist(equipment: Int) extends EquipmentUseContextWrapper(value = 27)
|
final case class AmsResupplyKillAssist(equipment: Int) extends NoIntermediateUseContextWrapper(value = 27)
|
||||||
final case class RouterKillAssist(equipment: Int) extends EquipmentUseContextWrapper(value = 28)
|
final case class RouterKillAssist(equipment: Int) extends NoIntermediateUseContextWrapper(value = 28)
|
||||||
|
|
|
||||||
|
|
@ -2,17 +2,110 @@
|
||||||
package net.psforever.objects.zones.exp
|
package net.psforever.objects.zones.exp
|
||||||
|
|
||||||
import akka.actor.ActorRef
|
import akka.actor.ActorRef
|
||||||
import net.psforever.objects.avatar.scoring.{Assist, Death, Kill}
|
import net.psforever.objects.avatar.scoring.{Assist, Death, KDAStat, 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, SpawningActivity}
|
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
|
||||||
|
|
||||||
|
import scala.annotation.tailrec
|
||||||
import scala.collection.mutable
|
import scala.collection.mutable
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
|
|
||||||
|
/**
|
||||||
|
* One player will interact using any number of weapons they possess
|
||||||
|
* that will affect a different player - the target.
|
||||||
|
* A kill is counted as the last interaction that affects a target so as to drop their health to zero.
|
||||||
|
* An assist is counted as every other interaction that affects the target up until the kill interaction
|
||||||
|
* in a similar way to the kill interaction.
|
||||||
|
* @see `ContributionStats`
|
||||||
|
* @see `ContributionStatsOutput`
|
||||||
|
* @see `DamagingActivity`
|
||||||
|
* @see `HealingActivity`
|
||||||
|
* @see `InGameActivity`
|
||||||
|
* @see `InGameHistory`
|
||||||
|
* @see `PlayerSource`
|
||||||
|
* @see `RepairingActivity`
|
||||||
|
* @see `SourceEntry`
|
||||||
|
*/
|
||||||
object KillAssists {
|
object KillAssists {
|
||||||
|
/**
|
||||||
|
* Primary landing point for calculating the rewards given for player death.
|
||||||
|
* Rewards in the form of "battle experience points" are given:
|
||||||
|
* to the player held responsible for the other player's death - the killer;
|
||||||
|
* all players whose efforts managed to deal damage to the player who died prior to the killer - assists.
|
||||||
|
* @param victim player that died
|
||||||
|
* @param lastDamage purported as the in-game activity that resulted in the player dying
|
||||||
|
* @param history chronology of activity the game considers noteworthy;
|
||||||
|
* `lastDamage` should be within this chronology
|
||||||
|
* @param eventBus where to send the results of the experience determination(s)
|
||||||
|
* @see `ActorRef`
|
||||||
|
* @see `AvatarAction.UpdateKillsDeathsAssists`
|
||||||
|
* @see `AvatarServiceMessage`
|
||||||
|
* @see `DamageResult`
|
||||||
|
* @see `rewardThisPlayerDeath`
|
||||||
|
*/
|
||||||
|
private[exp] def rewardThisPlayerDeath(
|
||||||
|
victim: PlayerSource,
|
||||||
|
lastDamage: Option[DamageResult],
|
||||||
|
history: Iterable[InGameActivity],
|
||||||
|
eventBus: ActorRef
|
||||||
|
): Unit = {
|
||||||
|
rewardThisPlayerDeath(victim, lastDamage, history).foreach { case (p, kda) =>
|
||||||
|
eventBus ! AvatarServiceMessage(p.Name, AvatarAction.UpdateKillsDeathsAssists(p.CharId, kda))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Primary innards of the functionality of calculating the rewards given for player death.
|
||||||
|
* @param victim player that died
|
||||||
|
* @param lastDamage purported as the in-game activity that resulted in the player dying
|
||||||
|
* @param history chronology of activity the game considers noteworthy;
|
||||||
|
* `lastDamage` should be within this chronology
|
||||||
|
* @return na
|
||||||
|
* @see `Assist`
|
||||||
|
* @see `calculateExperience`
|
||||||
|
* @see `collectKillAssistsForPlayer`
|
||||||
|
* @see `DamageResult`
|
||||||
|
* @see `Death`
|
||||||
|
* @see `KDAStat`
|
||||||
|
* @see `limitHistoryToThisLife`
|
||||||
|
* @see `Support.baseExperience`
|
||||||
|
*/
|
||||||
|
private def rewardThisPlayerDeath(
|
||||||
|
victim: PlayerSource,
|
||||||
|
lastDamage: Option[DamageResult],
|
||||||
|
history: Iterable[InGameActivity],
|
||||||
|
): Seq[(PlayerSource, KDAStat)] = {
|
||||||
|
val shortHistory = limitHistoryToThisLife(history.toList)
|
||||||
|
determineKiller(lastDamage, shortHistory) match {
|
||||||
|
case Some((result, killer: PlayerSource)) =>
|
||||||
|
val assists = collectKillAssistsForPlayer(victim, shortHistory, Some(killer))
|
||||||
|
val fullBep = calculateExperience(killer, victim, shortHistory)
|
||||||
|
val hitSquad = (killer, Kill(victim, result, fullBep)) +: assists.map {
|
||||||
|
case ContributionStatsOutput(p, w, r) => (p, Assist(victim, w, r, (fullBep * r).toLong))
|
||||||
|
}.toSeq
|
||||||
|
(victim, Death(hitSquad.map { _._1 }, shortHistory.last.time - shortHistory.head.time, fullBep)) +: hitSquad
|
||||||
|
|
||||||
|
case _ =>
|
||||||
|
val assists = collectKillAssistsForPlayer(victim, shortHistory, None)
|
||||||
|
val fullBep = Support.baseExperience(victim, shortHistory)
|
||||||
|
val hitSquad = assists.map {
|
||||||
|
case ContributionStatsOutput(p, w, r) => (p, Assist(victim, w, r, (fullBep * r).toLong))
|
||||||
|
}.toSeq
|
||||||
|
(victim, Death(hitSquad.map { _._1 }, shortHistory.last.time - shortHistory.head.time, fullBep)) +: hitSquad
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Limit the chronology of in-game activity between an starting activity and a concluding activity for a player character.
|
||||||
|
* The starting activity is signalled by one or two particular events.
|
||||||
|
* The concluding activity is a condition of one of many common events.
|
||||||
|
* All of the activities logged in between count.
|
||||||
|
* @param history chronology of activity the game considers noteworthy
|
||||||
|
* @return chronology of activity the game considers noteworthy, but truncated
|
||||||
|
*/
|
||||||
private def limitHistoryToThisLife(history: List[InGameActivity]): List[InGameActivity] = {
|
private def limitHistoryToThisLife(history: List[InGameActivity]): List[InGameActivity] = {
|
||||||
val spawnIndex = history.lastIndexWhere {
|
val spawnIndex = history.lastIndexWhere {
|
||||||
case _: SpawningActivity => true
|
case _: SpawningActivity => true
|
||||||
|
|
@ -30,6 +123,14 @@ object KillAssists {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine the player who is the origin/owner of the bullet that reduced health to zero.
|
||||||
|
* @param lastDamageActivity damage result that purports the player who is the killer
|
||||||
|
* @param history chronology of activity the game considers noteworthy;
|
||||||
|
* referenced in the case that the suggested `DamageResult` is not suitable to determine a player
|
||||||
|
* @return player associated
|
||||||
|
* @see `limitHistoryToThisLife`
|
||||||
|
*/
|
||||||
private[exp] def determineKiller(
|
private[exp] def determineKiller(
|
||||||
lastDamageActivity: Option[DamageResult],
|
lastDamageActivity: Option[DamageResult],
|
||||||
history: List[InGameActivity]
|
history: List[InGameActivity]
|
||||||
|
|
@ -37,16 +138,18 @@ object KillAssists {
|
||||||
val now = System.currentTimeMillis()
|
val now = System.currentTimeMillis()
|
||||||
val compareTimeMillis = 10.seconds.toMillis
|
val compareTimeMillis = 10.seconds.toMillis
|
||||||
lastDamageActivity
|
lastDamageActivity
|
||||||
.collect { case dam if now - dam.interaction.hitTime < compareTimeMillis => dam }
|
.collect { case dam
|
||||||
.flatMap { dam => Some(dam, dam.adversarial) }
|
if now - dam.interaction.hitTime < compareTimeMillis && dam.adversarial.nonEmpty =>
|
||||||
|
(dam, dam.adversarial.get.attacker)
|
||||||
|
}
|
||||||
.orElse {
|
.orElse {
|
||||||
history.collect { case damage: DamagingActivity
|
limitHistoryToThisLife(history)
|
||||||
if now - damage.time < compareTimeMillis && damage.data.adversarial.nonEmpty =>
|
.lastOption
|
||||||
damage.data
|
.collect { case dam: DamagingActivity =>
|
||||||
|
val res = dam.data
|
||||||
|
(res, res.adversarial.get.attacker)
|
||||||
}
|
}
|
||||||
.flatMap { dam => Some(dam, dam.adversarial) }.lastOption
|
|
||||||
}
|
}
|
||||||
.collect { case (dam, Some(adv)) => (dam, adv.attacker) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -55,37 +158,140 @@ object KillAssists {
|
||||||
* The measurement - a "streak" in modern lingo - is transformed into the form of an `Integer` for simplicity.
|
* The measurement - a "streak" in modern lingo - is transformed into the form of an `Integer` for simplicity.
|
||||||
* @param player the player
|
* @param player the player
|
||||||
* @param mercy a time value that can be used to continue a missed streak
|
* @param mercy a time value that can be used to continue a missed streak
|
||||||
* @return an `Integer` between 0 and 7
|
* @return an integer between 0 and 7;
|
||||||
|
* 0 is no kills,
|
||||||
|
* 1 is some kills,
|
||||||
|
* 2-7 is a menace score;
|
||||||
|
* there is no particular meaning behind different menace scores ascribed by this function
|
||||||
|
* @see `qualifiedTimeDifferences`
|
||||||
|
* @see `takeWhileLess`
|
||||||
*/
|
*/
|
||||||
private[exp] def calculateMenace(player: PlayerSource, mercy: Long = 2500L): Int = {
|
private[exp] def calculateMenace(player: PlayerSource, mercy: Long = 5000L): Int = {
|
||||||
val allKills = player.progress.kills.reverse
|
val maxDelayDiff: Long = 45000L
|
||||||
val restBetweenKills = allKills.take(10) match {
|
val minDelayDiff: Long = 20000L
|
||||||
case firstKill :: kills if kills.size == 9 =>
|
val allKills = player.progress.kills
|
||||||
var xTime = firstKill.time.toDate.getTime
|
//the very first kill must have been within the max delay (but does not count towards menace)
|
||||||
kills.map { kill =>
|
if (allKills.headOption.exists { System.currentTimeMillis() - _.time.toDate.getTime < maxDelayDiff}) {
|
||||||
val time = kill.time.toDate.getTime
|
allKills match {
|
||||||
val timeOut = time - xTime
|
case _ :: kills if kills.size > 3 =>
|
||||||
xTime = time
|
val (continuations, restsBetweenKills) =
|
||||||
timeOut
|
qualifiedTimeDifferences(
|
||||||
}
|
kills.map(_.time.toDate.getTime).iterator,
|
||||||
case _ =>
|
maxValidDiffCount = 10,
|
||||||
Nil
|
maxDelayDiff,
|
||||||
}
|
minDelayDiff
|
||||||
//TODO the math here is not very meaningful
|
)
|
||||||
|
.partition(_ > minDelayDiff)
|
||||||
|
math.max(
|
||||||
|
1,
|
||||||
math.floor(math.sqrt(
|
math.floor(math.sqrt(
|
||||||
math.max(0, takeWhileDelay(restBetweenKills, testValue = 20000L, mercy).size - 1) +
|
math.max(0, takeWhileLess(restsBetweenKills, testValue = 20000L, mercy).size - 1) + /*max=8*/
|
||||||
math.max(0, takeWhileDelay(restBetweenKills, testValue = 10000L, mercy).size - 5) * 3 +
|
math.max(0, takeWhileLess(restsBetweenKills, testValue = 10000L, mercy).size - 5) * 3 + /*max=12*/
|
||||||
math.max(0, takeWhileDelay(restBetweenKills, testValue = 5000L, mercy = 1000L).size - 4) * 5
|
math.max(0, takeWhileLess(restsBetweenKills, testValue = 5000L, mercy = 1000L).size - 4) * 7 /*max=35*/
|
||||||
)).toInt
|
) - continuations.size)
|
||||||
|
).toInt
|
||||||
|
case _ =>
|
||||||
|
1
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private def takeWhileDelay(list: Iterable[Long], testValue: Long, mercy: Long = 2500L): Iterable[Long] = {
|
/**
|
||||||
|
* Take a list of times
|
||||||
|
* and produce a list of delays between those entries less than a maximum time delay.
|
||||||
|
* These are considered "qualifying".
|
||||||
|
* Count a certain number of time delays that fall within a minimum threshold
|
||||||
|
* and stop when that minimum count is achieved.
|
||||||
|
* These are considered "valid".
|
||||||
|
* The final product should be a new list of the successive delays from the first list
|
||||||
|
* containing both qualified and valid entries,
|
||||||
|
* stopping at either the first unqualified delay or the last valid delay or at exhaustion of the original list.
|
||||||
|
* @param iter unfiltered list of times (ms)
|
||||||
|
* @param maxValidDiffCount maximum number of valid entries in the final list of time differences;
|
||||||
|
* see `validTimeEntryCount`
|
||||||
|
* @param maxDiff exclusive amount of time allowed between qualifying entries;
|
||||||
|
* include any time difference within this delay;
|
||||||
|
* these entries are "qualifying" but are not "valid"
|
||||||
|
* @param minDiff inclusive amount of time difference allowed between valid entries;
|
||||||
|
* include time differences in this delay
|
||||||
|
* these entries are "valid" and should increment the counter `validTimeEntryCount`
|
||||||
|
* @return list of qualifying time differences (ms)
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
Parameters governed by recursion:
|
||||||
|
@param diffList ongoing list of qualifying time differences (ms)
|
||||||
|
@param diffExtensionList accumulation of entries greater than the `minTimeEntryDiff`
|
||||||
|
but less that the `minTimeEntryDiff`;
|
||||||
|
holds qualifying time differences
|
||||||
|
that will be included before the next valid time difference
|
||||||
|
@param validDiffCount currently number of valid time entries in the qualified time list;
|
||||||
|
see `maxValidTimeEntryCount`
|
||||||
|
@param previousTime previous qualifying entry time;
|
||||||
|
by default, current time (ms)
|
||||||
|
*/
|
||||||
|
@tailrec
|
||||||
|
private def qualifiedTimeDifferences(
|
||||||
|
iter: Iterator[Long],
|
||||||
|
maxValidDiffCount: Int,
|
||||||
|
maxDiff: Long,
|
||||||
|
minDiff: Long,
|
||||||
|
diffList: Seq[Long] = Nil,
|
||||||
|
diffExtensionList: Seq[Long] = Nil,
|
||||||
|
validDiffCount: Int = 0,
|
||||||
|
previousTime: Long = System.currentTimeMillis()
|
||||||
|
): Iterable[Long] = {
|
||||||
|
if (iter.hasNext && validDiffCount < maxValidDiffCount) {
|
||||||
|
val nextTime = iter.next()
|
||||||
|
val delay = previousTime - nextTime
|
||||||
|
if (delay < maxDiff) {
|
||||||
|
if (delay <= minDiff) {
|
||||||
|
qualifiedTimeDifferences(
|
||||||
|
iter,
|
||||||
|
maxValidDiffCount,
|
||||||
|
maxDiff,
|
||||||
|
minDiff,
|
||||||
|
diffList ++ (diffExtensionList :+ delay),
|
||||||
|
Nil,
|
||||||
|
validDiffCount + 1,
|
||||||
|
nextTime
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
qualifiedTimeDifferences(
|
||||||
|
iter,
|
||||||
|
maxValidDiffCount,
|
||||||
|
maxDiff,
|
||||||
|
minDiff,
|
||||||
|
diffList,
|
||||||
|
diffExtensionList :+ delay,
|
||||||
|
validDiffCount,
|
||||||
|
nextTime
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
diffList
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
diffList
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* From a list of values, isolate all values less than than a test value.
|
||||||
|
* @param list list of values
|
||||||
|
* @param testValue test value that all valid values must be less than
|
||||||
|
* @param mercy initial mercy value that values may be tested for being less than the test value
|
||||||
|
* @return list of values less than the test value, including mercy
|
||||||
|
*/
|
||||||
|
private def takeWhileLess(list: Iterable[Long], testValue: Long, mercy: Long): Iterable[Long] = {
|
||||||
var onGoingMercy: Long = mercy
|
var onGoingMercy: Long = mercy
|
||||||
list.takeWhile { time =>
|
list.filter { value =>
|
||||||
if (time < testValue) {
|
if (value < testValue) {
|
||||||
true
|
true
|
||||||
} else if (time - onGoingMercy - 1 < testValue) {
|
} else if (value - onGoingMercy < testValue) {
|
||||||
onGoingMercy = math.ceil(onGoingMercy * 0.65).toLong
|
//mercy is reduced every time it is utilized to find a valid value
|
||||||
|
onGoingMercy = math.ceil(onGoingMercy * 0.8f).toLong
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
|
|
@ -93,6 +299,15 @@ object KillAssists {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modify a base experience value to consider additional reasons for points.
|
||||||
|
* @param killer player that delivers the interaction that reduces health to zero
|
||||||
|
* @param victim player to which the final interaction has reduced health to zero
|
||||||
|
* @param history chronology of activity the game considers noteworthy
|
||||||
|
* @return the value of the kill in what the game called "battle experience points"
|
||||||
|
* @see `BattleRank.withExperience`
|
||||||
|
* @see `Support.baseExperience`
|
||||||
|
*/
|
||||||
private def calculateExperience(
|
private def calculateExperience(
|
||||||
killer: PlayerSource,
|
killer: PlayerSource,
|
||||||
victim: PlayerSource,
|
victim: PlayerSource,
|
||||||
|
|
@ -128,35 +343,20 @@ object KillAssists {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private[exp] def rewardThisPlayerDeath(
|
/**
|
||||||
victim: PlayerSource,
|
* Evaluate chronologic in-game activity within a scope of history and
|
||||||
lastDamage: Option[DamageResult],
|
* isolate the interactions that lead to one player dying.
|
||||||
history: Iterable[InGameActivity],
|
* Factor in interactions that would have the dying player attempt to resist death, if only for a short while longer.
|
||||||
eventBus: ActorRef
|
* @param victim player to which the final interaction has reduced health to zero
|
||||||
): Unit = {
|
* @param history chronology of activity the game considers noteworthy
|
||||||
val shortHistory = limitHistoryToThisLife(history.toList)
|
* @param killerOpt optional player that delivers the interaction that reduces the `victim's` health to zero
|
||||||
val everyone = determineKiller(lastDamage, shortHistory) match {
|
* @return summary of the interaction in terms of players, equipment activity, and experience
|
||||||
case Some((result, killer: PlayerSource)) =>
|
* @see `armorDamageContributors`
|
||||||
val assists = collectKillAssistsForPlayer(victim, shortHistory, Some(killer))
|
* @see `collectKillAssists`
|
||||||
val fullBep = calculateExperience(killer, victim, shortHistory)
|
* @see `healthDamageContributors`
|
||||||
val hitSquad = (killer, Kill(victim, result, fullBep)) +: assists.map {
|
* @see `Support.allocateContributors`
|
||||||
case ContributionStatsOutput(p, w, r) => (p, Assist(victim, w, r, (fullBep * r).toLong))
|
* @see `Support.onlyOriginalAssistEntries`
|
||||||
}.toSeq
|
*/
|
||||||
(victim, Death(hitSquad.map { _._1 }, shortHistory.last.time - shortHistory.head.time, fullBep)) +: hitSquad
|
|
||||||
|
|
||||||
case _ =>
|
|
||||||
val assists = collectKillAssistsForPlayer(victim, shortHistory, None)
|
|
||||||
val fullBep = Support.baseExperience(victim, shortHistory)
|
|
||||||
val hitSquad = assists.map {
|
|
||||||
case ContributionStatsOutput(p, w, r) => (p, Assist(victim, w, r, (fullBep * r).toLong))
|
|
||||||
}.toSeq
|
|
||||||
(victim, Death(hitSquad.map { _._1 }, shortHistory.last.time - shortHistory.head.time, fullBep)) +: hitSquad
|
|
||||||
}
|
|
||||||
everyone.foreach { case (p, kda) =>
|
|
||||||
eventBus ! AvatarServiceMessage(p.Name, AvatarAction.UpdateKillsDeathsAssists(p.CharId, kda))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private def collectKillAssistsForPlayer(
|
private def collectKillAssistsForPlayer(
|
||||||
victim: PlayerSource,
|
victim: PlayerSource,
|
||||||
history: List[InGameActivity],
|
history: List[InGameActivity],
|
||||||
|
|
@ -170,7 +370,7 @@ object KillAssists {
|
||||||
healthAssists.remove(0L)
|
healthAssists.remove(0L)
|
||||||
healthAssists.remove(victim.CharId)
|
healthAssists.remove(victim.CharId)
|
||||||
killerOpt.map { killer => healthAssists.remove(killer.CharId) }
|
killerOpt.map { killer => healthAssists.remove(killer.CharId) }
|
||||||
if (Support.wasEverAMax(victim, history)) { //a cardinal sin
|
if (Support.wasEverAMax(victim, history)) {
|
||||||
val armorAssists = collectKillAssists(
|
val armorAssists = collectKillAssists(
|
||||||
victim,
|
victim,
|
||||||
history,
|
history,
|
||||||
|
|
@ -185,6 +385,14 @@ object KillAssists {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Analyze history based on a discriminating function and format the output.
|
||||||
|
* @param victim player to which the final interaction has reduced health to zero
|
||||||
|
* @param history chronology of activity the game considers noteworthy
|
||||||
|
* @param func mechanism for discerning particular interactions and building a narrative around their history;
|
||||||
|
* tallies all activity by a certain player using certain equipment
|
||||||
|
* @return summary of the interaction in terms of players, equipment activity, and experience
|
||||||
|
*/
|
||||||
private def collectKillAssists(
|
private def collectKillAssists(
|
||||||
victim: SourceEntry,
|
victim: SourceEntry,
|
||||||
history: List[InGameActivity],
|
history: List[InGameActivity],
|
||||||
|
|
@ -199,14 +407,28 @@ object KillAssists {
|
||||||
output
|
output
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In relation to a target player's health,
|
||||||
|
* build a secondary chronology of how the health value is affected per interaction and
|
||||||
|
* maintain a quantitative record of that activity in relation to the other players and their equipment.
|
||||||
|
* @param history chronology of activity the game considers noteworthy
|
||||||
|
* @param faction empire to target
|
||||||
|
* @param participants quantitative record of activity in relation to the other players and their equipment
|
||||||
|
* @return chronology of how the health value is affected per interaction
|
||||||
|
* @see `contributeWithDamagingActivity`
|
||||||
|
* @see `contributeWithRecoveryActivity`
|
||||||
|
* @see `RevivingActivity`
|
||||||
|
*/
|
||||||
private def healthDamageContributors(
|
private def healthDamageContributors(
|
||||||
history: List[InGameActivity],
|
history: List[InGameActivity],
|
||||||
faction: PlanetSideEmpire.Value,
|
faction: PlanetSideEmpire.Value,
|
||||||
participants: mutable.LongMap[ContributionStats]
|
participants: mutable.LongMap[ContributionStats]
|
||||||
): Seq[(Long, Int)] = {
|
): Seq[(Long, Int)] = {
|
||||||
/** damage as it is measured in order (with heal-countered damage eliminated)<br>
|
/*
|
||||||
* key - character identifier,
|
damage as it is measured in order (with heal-countered damage eliminated)<br>
|
||||||
* value - current damage contribution */
|
key - character identifier,
|
||||||
|
value - current damage contribution
|
||||||
|
*/
|
||||||
var inOrder: Seq[(Long, Int)] = Seq[(Long, Int)]()
|
var inOrder: Seq[(Long, Int)] = Seq[(Long, Int)]()
|
||||||
history.foreach {
|
history.foreach {
|
||||||
case d: DamagingActivity if d.health > 0 =>
|
case d: DamagingActivity if d.health > 0 =>
|
||||||
|
|
@ -220,14 +442,27 @@ object KillAssists {
|
||||||
inOrder
|
inOrder
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In relation to a target player's armor,
|
||||||
|
* build a secondary chronology of how the armor value is affected per interaction and
|
||||||
|
* maintain a quantitative record of that activity in relation to the other players and their equipment.
|
||||||
|
* @param history chronology of activity the game considers noteworthy
|
||||||
|
* @param faction empire to target
|
||||||
|
* @param participants quantitative record of activity in relation to the other players and their equipment
|
||||||
|
* @return chronology of how the armor value is affected per interaction
|
||||||
|
* @see `contributeWithDamagingActivity`
|
||||||
|
* @see `contributeWithRecoveryActivity`
|
||||||
|
*/
|
||||||
private def armorDamageContributors(
|
private def armorDamageContributors(
|
||||||
history: List[InGameActivity],
|
history: List[InGameActivity],
|
||||||
faction: PlanetSideEmpire.Value,
|
faction: PlanetSideEmpire.Value,
|
||||||
participants: mutable.LongMap[ContributionStats]
|
participants: mutable.LongMap[ContributionStats]
|
||||||
): Seq[(Long, Int)] = {
|
): Seq[(Long, Int)] = {
|
||||||
/** damage as it is measured in order (with heal-countered damage eliminated)<br>
|
/*
|
||||||
* key - character identifier,
|
damage as it is measured in order (with heal-countered damage eliminated)<br>
|
||||||
* value - current damage contribution */
|
key - character identifier,
|
||||||
|
value - current damage contribution
|
||||||
|
*/
|
||||||
var inOrder: Seq[(Long, Int)] = Seq[(Long, Int)]()
|
var inOrder: Seq[(Long, Int)] = Seq[(Long, Int)]()
|
||||||
history.foreach {
|
history.foreach {
|
||||||
case d: DamagingActivity if d.amount - d.health > 0 =>
|
case d: DamagingActivity if d.amount - d.health > 0 =>
|
||||||
|
|
@ -239,6 +474,15 @@ object KillAssists {
|
||||||
inOrder
|
inOrder
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Analyze damaging activity for quantitative records.
|
||||||
|
* @param activity a particular in-game activity that negative affects a player's health
|
||||||
|
* @param faction empire to target
|
||||||
|
* @param amount value
|
||||||
|
* @param participants quantitative record of activity in relation to the other players and their equipment
|
||||||
|
* @param order chronology of how the armor value is affected per interaction
|
||||||
|
* @return chronology of how the armor value is affected per interaction
|
||||||
|
*/
|
||||||
private def contributeWithDamagingActivity(
|
private def contributeWithDamagingActivity(
|
||||||
activity: DamagingActivity,
|
activity: DamagingActivity,
|
||||||
faction: PlanetSideEmpire.Value,
|
faction: PlanetSideEmpire.Value,
|
||||||
|
|
@ -259,6 +503,16 @@ object KillAssists {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Analyze damaging activity for quantitative records.
|
||||||
|
* @param userOpt optional player for the quantitative record
|
||||||
|
* @param wepid weapon for the quantitative record
|
||||||
|
* @param faction empire to target
|
||||||
|
* @param amount value
|
||||||
|
* @param participants quantitative record of activity in relation to the other players and their equipment
|
||||||
|
* @param order chronology of how the armor value is affected per interaction
|
||||||
|
* @return chronology of how the armor value is affected per interaction
|
||||||
|
*/
|
||||||
private[exp] def contributeWithDamagingActivity(
|
private[exp] def contributeWithDamagingActivity(
|
||||||
userOpt: Option[PlayerSource],
|
userOpt: Option[PlayerSource],
|
||||||
wepid: Int,
|
wepid: Int,
|
||||||
|
|
@ -331,6 +585,13 @@ object KillAssists {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Analyze recovery activity for quantitative records.
|
||||||
|
* @param amount value
|
||||||
|
* @param participants quantitative record of activity in relation to the other players and their equipment
|
||||||
|
* @param order chronology of how the armor value is affected per interaction
|
||||||
|
* @return chronology of how the armor value is affected per interaction
|
||||||
|
*/
|
||||||
private[exp] def contributeWithRecoveryActivity(
|
private[exp] def contributeWithRecoveryActivity(
|
||||||
amount: Int,
|
amount: Int,
|
||||||
participants: mutable.LongMap[ContributionStats],
|
participants: mutable.LongMap[ContributionStats],
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,23 +1,23 @@
|
||||||
// Copyright (c) 2023 PSForever
|
// Copyright (c) 2023 PSForever
|
||||||
package net.psforever.objects.zones.exp
|
package net.psforever.objects.zones.exp
|
||||||
|
|
||||||
import net.psforever.objects.GlobalDefinitions
|
import net.psforever.objects.sourcing.PlayerSource
|
||||||
|
import net.psforever.objects.vital.{InGameActivity, ReconstructionActivity, RepairFromExoSuitChange, SpawningActivity}
|
||||||
import java.util.Date
|
import net.psforever.types.{ExoSuitType, PlanetSideEmpire}
|
||||||
import org.joda.time.{Instant, LocalDateTime => JodaLocalDateTime}
|
|
||||||
import net.psforever.objects.serverobject.hackable.Hackable
|
|
||||||
import net.psforever.objects.serverobject.terminals.Terminal
|
|
||||||
import net.psforever.objects.sourcing.{AmenitySource, PlayerSource, SourceEntry}
|
|
||||||
import net.psforever.objects.vital.{HealFromEquipment, InGameActivity, ReconstructionActivity, RepairFromEquipment, RepairFromExoSuitChange, RevivingActivity, SpawningActivity, SupportActivityCausedByAnother, TerminalUsedActivity}
|
|
||||||
import net.psforever.objects.zones.Zone
|
|
||||||
import net.psforever.types.{ExoSuitType, PlanetSideEmpire, TransactionType}
|
|
||||||
import net.psforever.zones.Zones
|
|
||||||
|
|
||||||
import scala.collection.mutable
|
import scala.collection.mutable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Functions to assist experience calculation and history manipulation and analysis.
|
||||||
|
*/
|
||||||
object Support {
|
object Support {
|
||||||
private type SupportActivity = InGameActivity with SupportActivityCausedByAnother
|
/**
|
||||||
|
* Calculate a base experience value to consider additional reasons for points.
|
||||||
|
* @param victim player to which a final interaction has reduced health to zero
|
||||||
|
* @param history chronology of activity the game considers noteworthy
|
||||||
|
* @return the value of the kill in what the game called "battle experience points"
|
||||||
|
* @see `Support.wasEverAMax`
|
||||||
|
*/
|
||||||
private[exp] def baseExperience(
|
private[exp] def baseExperience(
|
||||||
victim: PlayerSource,
|
victim: PlayerSource,
|
||||||
history: Iterable[InGameActivity]
|
history: Iterable[InGameActivity]
|
||||||
|
|
@ -26,8 +26,7 @@ object Support {
|
||||||
case (Some(spawn), Some(death)) => death.time - spawn.time
|
case (Some(spawn), Some(death)) => death.time - spawn.time
|
||||||
case _ => 0L
|
case _ => 0L
|
||||||
}
|
}
|
||||||
val wasEverAMax = Support.wasEverAMax(victim, history)
|
val base = if (Support.wasEverAMax(victim, history)) {
|
||||||
val base = if (wasEverAMax) { //shamed
|
|
||||||
250L
|
250L
|
||||||
} else if (victim.Seated || victim.progress.kills.nonEmpty) {
|
} else if (victim.Seated || victim.progress.kills.nonEmpty) {
|
||||||
100L
|
100L
|
||||||
|
|
@ -45,6 +44,16 @@ object Support {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Combine two quantitative records into one, maintaining only the original entries.
|
||||||
|
* @param first one quantitative record
|
||||||
|
* @param second another quantitative record
|
||||||
|
* @param combiner mechanism for determining how to combine quantitative records;
|
||||||
|
* defaults to an additive combiner with a small multiplier value
|
||||||
|
* @return the combined quantitative records
|
||||||
|
* @see `defaultAdditiveOutputCombiner`
|
||||||
|
* @see `onlyOriginalAssistEntriesIterable`
|
||||||
|
*/
|
||||||
private[exp] def onlyOriginalAssistEntries(
|
private[exp] def onlyOriginalAssistEntries(
|
||||||
first: mutable.LongMap[ContributionStatsOutput],
|
first: mutable.LongMap[ContributionStatsOutput],
|
||||||
second: mutable.LongMap[ContributionStatsOutput],
|
second: mutable.LongMap[ContributionStatsOutput],
|
||||||
|
|
@ -54,6 +63,15 @@ object Support {
|
||||||
onlyOriginalAssistEntriesIterable(first.values, second.values, combiner)
|
onlyOriginalAssistEntriesIterable(first.values, second.values, combiner)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Combine two quantitative records into one, maintaining only the original entries.
|
||||||
|
* @param first one quantitative record
|
||||||
|
* @param second another quantitative record
|
||||||
|
* @param combiner mechanism for determining how to combine quantitative records;
|
||||||
|
* defaults to an additive combiner with a small multiplier value
|
||||||
|
* @return the combined quantitative records
|
||||||
|
* @see `defaultAdditiveOutputCombiner`
|
||||||
|
*/
|
||||||
private[exp] def onlyOriginalAssistEntriesIterable(
|
private[exp] def onlyOriginalAssistEntriesIterable(
|
||||||
first: Iterable[ContributionStatsOutput],
|
first: Iterable[ContributionStatsOutput],
|
||||||
second: Iterable[ContributionStatsOutput],
|
second: Iterable[ContributionStatsOutput],
|
||||||
|
|
@ -79,6 +97,13 @@ object Support {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Combine two quantitative records into one, maintaining only the original entries.
|
||||||
|
* @param multiplier adjust the combined
|
||||||
|
* @param first one quantitative record
|
||||||
|
* @param second another quantitative record
|
||||||
|
* @return the combined quantitative records
|
||||||
|
*/
|
||||||
private def defaultAdditiveOutputCombiner(
|
private def defaultAdditiveOutputCombiner(
|
||||||
multiplier: Float
|
multiplier: Float
|
||||||
)
|
)
|
||||||
|
|
@ -92,206 +117,43 @@ object Support {
|
||||||
first.copy(implements = (first.implements ++ second.implements).distinct, percentage = second.percentage + second.implements.size * multiplier)
|
first.copy(implements = (first.implements ++ second.implements).distinct, percentage = second.percentage + second.implements.size * multiplier)
|
||||||
}
|
}
|
||||||
|
|
||||||
private[exp] def collectHealingSupportAssists(
|
/**
|
||||||
target: SourceEntry,
|
* Take two sequences of equipment statistics
|
||||||
time: JodaLocalDateTime,
|
* and combine both lists where overlap of the same equipment use is added together per field.
|
||||||
history: List[InGameActivity]
|
* If one sequence comtains more elements of the same type of equipment use,
|
||||||
): mutable.LongMap[ContributionStatsOutput] = {
|
* the additional entries may become lost.
|
||||||
//normal heals
|
* @param first statistics in relation to equipment
|
||||||
val healInfo = collectSupportContributions(
|
* @param second statistics in relation to equipment
|
||||||
target, time, history.collect { case heals: HealFromEquipment
|
* @return statistics in relation to equipment
|
||||||
if heals.amount > 0 && heals.user.unique != target.unique => (heals, heals.equipment_def.ObjectId)
|
*/
|
||||||
}, mapContributionPointsByPercentage
|
private[exp] def combineWeaponStats(
|
||||||
)
|
first: Seq[WeaponStats],
|
||||||
//revivals (count all revivals performed by the player of the last one, if any)
|
second: Seq[WeaponStats]
|
||||||
val reviveInfo = collectSupportContributions(
|
): Seq[WeaponStats] = {
|
||||||
target, time, history.collect { case revive: RevivingActivity
|
val (firstInSecond, firstAlone) = first.partition(firstStat => second.exists(_.equipment == firstStat.equipment))
|
||||||
if revive.user.unique != target.unique => (revive, revive.equipment.ObjectId)
|
val (secondInFirst, secondAlone) = second.partition(secondStat => firstInSecond.exists(_.equipment == secondStat.equipment))
|
||||||
}.lastOption.toSeq, mapContributionPointsByCount
|
val combined = firstInSecond.flatMap { firstStat =>
|
||||||
)
|
secondInFirst
|
||||||
//combine and output
|
.filter(_.equipment == firstStat.equipment)
|
||||||
reviveInfo.foreach { case (reviverId, contribution) =>
|
.map { secondStat =>
|
||||||
val result = healInfo.get(reviverId)
|
firstStat.copy(
|
||||||
val percentage = result.map { a => a.percentage }.getOrElse { contribution.percentage }
|
shots = firstStat.shots + secondStat.shots,
|
||||||
val implements = result.map { a => (a.implements ++ contribution.implements).distinct }.getOrElse { contribution.implements }
|
amount = firstStat.amount + secondStat.amount,
|
||||||
healInfo.put(reviverId, ContributionStatsOutput(contribution.player, implements, percentage))
|
contributions = firstStat.contributions + secondStat.contributions,
|
||||||
}
|
time = math.max(firstStat.time, secondStat.time)
|
||||||
healInfo
|
|
||||||
}
|
|
||||||
|
|
||||||
private[exp] def collectRepairingSupportAssists(
|
|
||||||
target: SourceEntry,
|
|
||||||
time: JodaLocalDateTime,
|
|
||||||
history: List[InGameActivity]
|
|
||||||
): mutable.LongMap[ContributionStatsOutput] = {
|
|
||||||
collectSupportContributions(
|
|
||||||
target,
|
|
||||||
time,
|
|
||||||
history.collect { case repairs: RepairFromEquipment
|
|
||||||
if repairs.amount > 0 && repairs.user.unique != target.unique => (repairs, repairs.equipment_def.ObjectId)
|
|
||||||
},
|
|
||||||
mapContributionPointsByPercentage
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private[exp] def collectTerminalSupportAssists(
|
|
||||||
target: SourceEntry,
|
|
||||||
history: List[InGameActivity]
|
|
||||||
): Iterable[ContributionStatsOutput] = {
|
|
||||||
val delay: Long = 300000L
|
|
||||||
val curr = System.currentTimeMillis()
|
|
||||||
var zone: Zone = Zone.Nowhere
|
|
||||||
val termsUsed: mutable.LongMap[Option[AmenitySource]] = mutable.LongMap[Option[AmenitySource]]()
|
|
||||||
val credit: mutable.LongMap[ContributionStatsOutput] = mutable.LongMap[ContributionStatsOutput]()
|
|
||||||
val faction = target.Faction
|
|
||||||
val terminalUseHistory = history.filter {
|
|
||||||
case _: SpawningActivity => true
|
|
||||||
case entry: TerminalUsedActivity => curr - entry.time < delay
|
|
||||||
case _ => false
|
|
||||||
}
|
}
|
||||||
terminalUseHistory.collect {
|
firstAlone ++ secondAlone ++ combined
|
||||||
case entry: SpawningActivity =>
|
|
||||||
zone = Zones.zones(entry.zoneNumber)
|
|
||||||
case entry: TerminalUsedActivity
|
|
||||||
if !termsUsed.contains(entry.terminal.unique.guid.guid.toLong) =>
|
|
||||||
if (
|
|
||||||
entry.transaction == TransactionType.Loadout ||
|
|
||||||
entry.transaction == TransactionType.Buy ||
|
|
||||||
entry.transaction == TransactionType.Learn
|
|
||||||
) {
|
|
||||||
val terminal = entry.terminal
|
|
||||||
val guid = terminal.unique.guid.guid
|
|
||||||
terminal.hacked.collect {
|
|
||||||
case Hackable.HackInfo(player, _, _, _)
|
|
||||||
if target.CharId != player.CharId && faction == player.Faction =>
|
|
||||||
//accessed a hacked terminal
|
|
||||||
termsUsed.put(guid.toLong, Some(terminal))
|
|
||||||
addTerminalContributionEntry(credit, player, Seq(HackKillAssist(GlobalDefinitions.remote_electronics_kit.ObjectId)), percentage = 1f)
|
|
||||||
case _
|
|
||||||
if terminal.Faction == faction =>
|
|
||||||
//accessed a faction-friendly terminal; check log for repair history
|
|
||||||
termsUsed.put(guid.toLong, Some(terminal))
|
|
||||||
zone.GUID(guid)
|
|
||||||
.asInstanceOf[Terminal]
|
|
||||||
.History
|
|
||||||
.collect { case a: RepairFromEquipment =>
|
|
||||||
val user = a.user
|
|
||||||
//TODO might be wrong intermediate
|
|
||||||
addTerminalContributionEntry(credit, user, Seq(RepairKillAssist(a.equipment_def.ObjectId, target.Definition.ObjectId)), percentage = 0.5f)
|
|
||||||
}
|
|
||||||
case _ =>
|
|
||||||
//what is this?
|
|
||||||
termsUsed.put(guid.toLong, None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
credit.remove(target.CharId)
|
|
||||||
credit.values
|
|
||||||
}
|
|
||||||
|
|
||||||
private def addTerminalContributionEntry(
|
|
||||||
contributions: mutable.LongMap[ContributionStatsOutput],
|
|
||||||
player: PlayerSource,
|
|
||||||
implements: Seq[EquipmentUseContextWrapper],
|
|
||||||
percentage: Float
|
|
||||||
): Unit = {
|
|
||||||
val charId = player.CharId
|
|
||||||
contributions.get(charId) match {
|
|
||||||
case Some(entry) if percentage > entry.percentage =>
|
|
||||||
contributions.put(charId, entry.copy(percentage = percentage))
|
|
||||||
case Some(entry) =>
|
|
||||||
contributions.put(charId, entry.copy(implements = (entry.implements ++ implements).distinct, percentage = entry.percentage + 0.05f))
|
|
||||||
case None =>
|
|
||||||
contributions.put(charId, ContributionStatsOutput(player, implements, percentage))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private[exp] def findTimeApplicableActivities(
|
|
||||||
activity: Seq[SupportActivity],
|
|
||||||
killLastTime: JodaLocalDateTime,
|
|
||||||
timeOffset: Int //s
|
|
||||||
): Seq[SupportActivity] = {
|
|
||||||
val dateTimeConverter: Date=>JodaLocalDateTime = JodaLocalDateTime.fromDateFields
|
|
||||||
val milliToInstant: Long=>org.joda.time.Instant = Instant.ofEpochMilli
|
|
||||||
val targetTime = killLastTime.minusSeconds(timeOffset)
|
|
||||||
//find all activities that occurred after the kill time after the offset is considered
|
|
||||||
activity
|
|
||||||
.filter { a =>
|
|
||||||
val activityTime = dateTimeConverter(milliToInstant(a.time).toDate)
|
|
||||||
targetTime.isBefore(activityTime) || targetTime.equals(activityTime)
|
|
||||||
}
|
|
||||||
.sortBy(_.time)(Ordering.Long.reverse)
|
|
||||||
}
|
|
||||||
|
|
||||||
private def combineTimeApplicableActivitiesForContribution(
|
|
||||||
longTerm: Seq[SupportActivity],
|
|
||||||
shortTerm: Seq[SupportActivity],
|
|
||||||
defaultTool: Int
|
|
||||||
): mutable.LongMap[ContributionStats] = {
|
|
||||||
val contributions: mutable.LongMap[ContributionStats] = mutable.LongMap[ContributionStats]()
|
|
||||||
(longTerm ++ shortTerm).foreach { h =>
|
|
||||||
val amount = h.amount
|
|
||||||
val user = h.user
|
|
||||||
val charId = user.CharId
|
|
||||||
contributions.get(charId) match {
|
|
||||||
case Some(entry) =>
|
|
||||||
contributions.update(charId, entry.copy(
|
|
||||||
amount = entry.amount + amount,
|
|
||||||
total = entry.total + amount,
|
|
||||||
shots = entry.shots + 1,
|
|
||||||
time = math.max(entry.time, h.time)
|
|
||||||
))
|
|
||||||
case None =>
|
|
||||||
//the contribution percentage allocated will always be 1.0f and should be overwritten later
|
|
||||||
contributions.put(charId, ContributionStats(user, Seq(WeaponStats(NoUse(defaultTool), amount, 1, h.time, 1f)), amount, amount, 1, h.time))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
contributions
|
|
||||||
}
|
|
||||||
|
|
||||||
private def collectSupportContributions(
|
|
||||||
target: SourceEntry,
|
|
||||||
time: JodaLocalDateTime,
|
|
||||||
pairs: Seq[(InGameActivity, Int)],
|
|
||||||
contributionPointsMap: (Float,Iterable[SupportActivity])=>(Long,ContributionStats)=>(Long,ContributionStatsOutput)
|
|
||||||
): mutable.LongMap[ContributionStatsOutput] = {
|
|
||||||
val (activity, tools) = pairs.unzip
|
|
||||||
//TODO this is not correct, but it will do for now
|
|
||||||
if (tools.isEmpty) {
|
|
||||||
mutable.LongMap[ContributionStatsOutput]()
|
|
||||||
} else {
|
|
||||||
val distinctTools = tools.distinct
|
|
||||||
if (distinctTools.size == 1) {
|
|
||||||
compileTimedSupportContributions(target, time, activity, distinctTools.head, contributionPointsMap)
|
|
||||||
} else {
|
|
||||||
var output = Iterable[ContributionStatsOutput]()
|
|
||||||
distinctTools.foreach { implement =>
|
|
||||||
output = onlyOriginalAssistEntriesIterable(
|
|
||||||
output,
|
|
||||||
compileTimedSupportContributions(target, time, activity, implement, contributionPointsMap).values
|
|
||||||
)
|
|
||||||
}
|
|
||||||
mutable.LongMap[ContributionStatsOutput]().addAll(output.map { entry => (entry.player.CharId, entry) })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private def compileTimedSupportContributions(
|
|
||||||
target: SourceEntry,
|
|
||||||
timeLimit: JodaLocalDateTime,
|
|
||||||
activity: Seq[InGameActivity],
|
|
||||||
defaultImplement: Int,
|
|
||||||
contributionPointsMap: (Float,Iterable[SupportActivity])=>(Long,ContributionStats)=>(Long,ContributionStatsOutput)
|
|
||||||
): mutable.LongMap[ContributionStatsOutput] = {
|
|
||||||
val activityByAnother = activity.collect { case theActivity: SupportActivity => theActivity }
|
|
||||||
val longTermActivity = findTimeApplicableActivities(activityByAnother, timeLimit, timeOffset = 600)
|
|
||||||
val shortTermActivity = findTimeApplicableActivities(activityByAnother, timeLimit, timeOffset = 300)
|
|
||||||
val contributions = combineTimeApplicableActivitiesForContribution(longTermActivity, shortTermActivity, defaultImplement)
|
|
||||||
contributions.remove(target.CharId)
|
|
||||||
val mapFunc = contributionPointsMap(contributions.values.foldLeft(0)(_ + _.total).toFloat, shortTermActivity)
|
|
||||||
contributions.map { case (a, b) => mapFunc(a, b) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run a function against history, targeting a certain faction.
|
||||||
|
* @param tallyFunc the history analysis function
|
||||||
|
* @param history chronology of activity the game considers noteworthy
|
||||||
|
* @param faction empire to target
|
||||||
|
* @return quantitative record of activity in relation to the other players and their equipment
|
||||||
|
*/
|
||||||
private[exp] def allocateContributors(
|
private[exp] def allocateContributors(
|
||||||
tallyFunc: (List[InGameActivity], PlanetSideEmpire.Value, mutable.LongMap[ContributionStats]) => Any
|
tallyFunc: (List[InGameActivity], PlanetSideEmpire.Value, mutable.LongMap[ContributionStats]) => Any
|
||||||
)
|
)
|
||||||
|
|
@ -299,57 +161,24 @@ object Support {
|
||||||
history: List[InGameActivity],
|
history: List[InGameActivity],
|
||||||
faction: PlanetSideEmpire.Value
|
faction: PlanetSideEmpire.Value
|
||||||
): mutable.LongMap[ContributionStats] = {
|
): mutable.LongMap[ContributionStats] = {
|
||||||
/** players who have contributed to this death, and how much they have contributed<br>
|
/*
|
||||||
* key - character identifier,
|
players who have contributed to this death, and how much they have contributed<br>
|
||||||
* value - (player, damage, total damage, number of shots) */
|
key - character identifier,
|
||||||
|
value - (player, damage, total damage, number of shots)
|
||||||
|
*/
|
||||||
val participants: mutable.LongMap[ContributionStats] = mutable.LongMap[ContributionStats]()
|
val participants: mutable.LongMap[ContributionStats] = mutable.LongMap[ContributionStats]()
|
||||||
tallyFunc(history, faction, participants)
|
tallyFunc(history, faction, participants)
|
||||||
participants
|
participants
|
||||||
}
|
}
|
||||||
|
|
||||||
private def mapContributionPointsByPercentage(
|
/**
|
||||||
total: Float,
|
* You better not fail this purity test.
|
||||||
compareList: Iterable[SupportActivity]
|
* @param player player being tested
|
||||||
)
|
* @param history chronology of activity the game considers noteworthy;
|
||||||
(
|
* allegedly associated with this player
|
||||||
charId: Long,
|
* @return `true`, if the player has ever committed a great shame;
|
||||||
contribution: ContributionStats,
|
* `false`, otherwise ... and it better be
|
||||||
): (Long, ContributionStatsOutput) = {
|
*/
|
||||||
val user = contribution.player
|
|
||||||
val unique = user.unique
|
|
||||||
val points = contribution.amount
|
|
||||||
val value = if (points < 75) {
|
|
||||||
//a small contribution means the lower time limit
|
|
||||||
if (compareList.exists { a => a.user.unique == unique }) {
|
|
||||||
math.max(0.2f, points / total)
|
|
||||||
} else {
|
|
||||||
0f
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
//large contribution is always okay
|
|
||||||
if (points > 299) {
|
|
||||||
1.0f
|
|
||||||
} else if (points > 100) {
|
|
||||||
0.75f
|
|
||||||
} else {
|
|
||||||
0.5f
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(charId, ContributionStatsOutput(user, contribution.weapons.map { _.equipment }, value))
|
|
||||||
}
|
|
||||||
|
|
||||||
//noinspection ScalaUnusedSymbol
|
|
||||||
private def mapContributionPointsByCount(
|
|
||||||
total: Float,
|
|
||||||
compareList: Iterable[SupportActivity]
|
|
||||||
)
|
|
||||||
(
|
|
||||||
charId: Long,
|
|
||||||
contribution: ContributionStats,
|
|
||||||
): (Long, ContributionStatsOutput) = {
|
|
||||||
(charId, ContributionStatsOutput(contribution.player, contribution.weapons.map { _.equipment }, compareList.size.toFloat))
|
|
||||||
}
|
|
||||||
|
|
||||||
private[exp] def wasEverAMax(player: PlayerSource, history: Iterable[InGameActivity]): Boolean = {
|
private[exp] def wasEverAMax(player: PlayerSource, history: Iterable[InGameActivity]): Boolean = {
|
||||||
player.ExoSuit == ExoSuitType.MAX || history.exists {
|
player.ExoSuit == ExoSuitType.MAX || history.exists {
|
||||||
case SpawningActivity(p: PlayerSource, _, _) => p.ExoSuit == ExoSuitType.MAX
|
case SpawningActivity(p: PlayerSource, _, _) => p.ExoSuit == ExoSuitType.MAX
|
||||||
|
|
|
||||||
|
|
@ -105,20 +105,6 @@ object ToDatabase {
|
||||||
* Shots fired.
|
* Shots fired.
|
||||||
*/
|
*/
|
||||||
def reportToolDischarge(avatarId: Long, stats: EquipmentStat): Unit = {
|
def reportToolDischarge(avatarId: Long, stats: EquipmentStat): Unit = {
|
||||||
val result = for {
|
|
||||||
res <- ctx.run(
|
|
||||||
query[persistence.Weaponstatsession]
|
|
||||||
.filter(_.avatarId == lift(avatarId))
|
|
||||||
.filter(_.weaponId == lift(stats.objectId))
|
|
||||||
.update(
|
|
||||||
_.shotsFired -> lift(stats.shotsFired),
|
|
||||||
_.shotsLanded -> lift(stats.shotsLanded)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
} yield res
|
|
||||||
result.onComplete {
|
|
||||||
case Success(rowCount) if rowCount.longValue > 0 => ()
|
|
||||||
case _ =>
|
|
||||||
ctx.run(query[persistence.Weaponstatsession]
|
ctx.run(query[persistence.Weaponstatsession]
|
||||||
.insert(
|
.insert(
|
||||||
_.avatarId -> lift(avatarId),
|
_.avatarId -> lift(avatarId),
|
||||||
|
|
@ -131,7 +117,6 @@ object ToDatabase {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Insert an entry into the database's `machinedestroyed` table.
|
* Insert an entry into the database's `machinedestroyed` table.
|
||||||
|
|
|
||||||
|
|
@ -2,16 +2,12 @@
|
||||||
package net.psforever.objects.zones.exp.rec
|
package net.psforever.objects.zones.exp.rec
|
||||||
|
|
||||||
import net.psforever.objects.sourcing.SourceUniqueness
|
import net.psforever.objects.sourcing.SourceUniqueness
|
||||||
import net.psforever.objects.vital.{DamagingActivity, InGameActivity, RepairFromEquipment, RepairFromTerminal, RepairingActivity, SupportActivityCausedByAnother}
|
import net.psforever.objects.vital.{DamagingActivity, InGameActivity, RepairFromEquipment, RepairingActivity}
|
||||||
import net.psforever.objects.zones.exp.KillContributions
|
|
||||||
import net.psforever.types.PlanetSideEmpire
|
import net.psforever.types.PlanetSideEmpire
|
||||||
|
|
||||||
import scala.collection.mutable
|
|
||||||
|
|
||||||
class ArmorRecoveryExperienceContributionProcess(
|
class ArmorRecoveryExperienceContributionProcess(
|
||||||
private val faction : PlanetSideEmpire.Value,
|
private val faction : PlanetSideEmpire.Value,
|
||||||
private val contributions: Map[SourceUniqueness, List[InGameActivity]],
|
private val contributions: Map[SourceUniqueness, List[InGameActivity]]
|
||||||
private val excludedTargets: mutable.ListBuffer[SourceUniqueness] = mutable.ListBuffer()
|
|
||||||
) extends RecoveryExperienceContributionProcess(faction, contributions) {
|
) extends RecoveryExperienceContributionProcess(faction, contributions) {
|
||||||
def submit(history: List[InGameActivity]): Unit = {
|
def submit(history: List[InGameActivity]): Unit = {
|
||||||
history.foreach {
|
history.foreach {
|
||||||
|
|
@ -26,31 +22,6 @@ class ArmorRecoveryExperienceContributionProcess(
|
||||||
)
|
)
|
||||||
damageInOrder = damage
|
damageInOrder = damage
|
||||||
recoveryInOrder = recovery
|
recoveryInOrder = recovery
|
||||||
case rt: RepairFromTerminal =>
|
|
||||||
val time = rt.time
|
|
||||||
val users = KillContributions.contributeWithTerminalActivity(
|
|
||||||
Seq((rt, rt.term, rt.term.hacked)),
|
|
||||||
faction,
|
|
||||||
contributions,
|
|
||||||
contributionsBy,
|
|
||||||
excludedTargets
|
|
||||||
)
|
|
||||||
.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 =>
|
case r: RepairFromEquipment =>
|
||||||
val (damage, recovery) = RecoveryExperienceContribution.contributeWithRecoveryActivity(
|
val (damage, recovery) = RecoveryExperienceContribution.contributeWithRecoveryActivity(
|
||||||
r.user,
|
r.user,
|
||||||
|
|
|
||||||
|
|
@ -13,10 +13,9 @@ class CombinedHealthAndArmorContributionProcess(
|
||||||
private val contributions: Map[SourceUniqueness, List[InGameActivity]],
|
private val contributions: Map[SourceUniqueness, List[InGameActivity]],
|
||||||
otherSubmissions: Seq[RecoveryExperienceContribution]
|
otherSubmissions: Seq[RecoveryExperienceContribution]
|
||||||
) extends RecoveryExperienceContribution {
|
) extends RecoveryExperienceContribution {
|
||||||
private val excludedTargets: mutable.ListBuffer[SourceUniqueness] = mutable.ListBuffer()
|
|
||||||
private val process: Seq[RecoveryExperienceContributionProcess] = Seq(
|
private val process: Seq[RecoveryExperienceContributionProcess] = Seq(
|
||||||
new HealthRecoveryExperienceContributionProcess(faction, contributions, excludedTargets),
|
new HealthRecoveryExperienceContributionProcess(faction, contributions),
|
||||||
new ArmorRecoveryExperienceContributionProcess(faction, contributions, excludedTargets)
|
new ArmorRecoveryExperienceContributionProcess(faction, contributions)
|
||||||
)
|
)
|
||||||
|
|
||||||
def submit(history: List[InGameActivity]): Unit = {
|
def submit(history: List[InGameActivity]): Unit = {
|
||||||
|
|
|
||||||
|
|
@ -2,16 +2,12 @@
|
||||||
package net.psforever.objects.zones.exp.rec
|
package net.psforever.objects.zones.exp.rec
|
||||||
|
|
||||||
import net.psforever.objects.sourcing.SourceUniqueness
|
import net.psforever.objects.sourcing.SourceUniqueness
|
||||||
import net.psforever.objects.vital.{DamagingActivity, HealFromEquipment, HealFromTerminal, HealingActivity, InGameActivity, RevivingActivity, SupportActivityCausedByAnother}
|
import net.psforever.objects.vital.{DamagingActivity, HealFromEquipment, HealingActivity, InGameActivity, RevivingActivity}
|
||||||
import net.psforever.objects.zones.exp.KillContributions
|
|
||||||
import net.psforever.types.PlanetSideEmpire
|
import net.psforever.types.PlanetSideEmpire
|
||||||
|
|
||||||
import scala.collection.mutable
|
|
||||||
|
|
||||||
private class HealthRecoveryExperienceContributionProcess(
|
private class HealthRecoveryExperienceContributionProcess(
|
||||||
private val faction : PlanetSideEmpire.Value,
|
private val faction : PlanetSideEmpire.Value,
|
||||||
private val contributions: Map[SourceUniqueness, List[InGameActivity]],
|
private val contributions: Map[SourceUniqueness, List[InGameActivity]]
|
||||||
private val excludedTargets: mutable.ListBuffer[SourceUniqueness] = mutable.ListBuffer()
|
|
||||||
) extends RecoveryExperienceContributionProcess(faction, contributions) {
|
) extends RecoveryExperienceContributionProcess(faction, contributions) {
|
||||||
def submit(history: List[InGameActivity]): Unit = {
|
def submit(history: List[InGameActivity]): Unit = {
|
||||||
history.foreach {
|
history.foreach {
|
||||||
|
|
@ -26,31 +22,6 @@ private class HealthRecoveryExperienceContributionProcess(
|
||||||
)
|
)
|
||||||
damageInOrder = damage
|
damageInOrder = damage
|
||||||
recoveryInOrder = recovery
|
recoveryInOrder = recovery
|
||||||
case ht: HealFromTerminal =>
|
|
||||||
val time = ht.time
|
|
||||||
val users = KillContributions.contributeWithTerminalActivity(
|
|
||||||
Seq((ht, ht.term, ht.term.hacked)),
|
|
||||||
faction,
|
|
||||||
contributions,
|
|
||||||
contributionsBy,
|
|
||||||
excludedTargets
|
|
||||||
)
|
|
||||||
.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 =>
|
case h: HealFromEquipment =>
|
||||||
val (damage, recovery) = RecoveryExperienceContribution.contributeWithRecoveryActivity(
|
val (damage, recovery) = RecoveryExperienceContribution.contributeWithRecoveryActivity(
|
||||||
h.user,
|
h.user,
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package net.psforever.objects.zones.exp.rec
|
||||||
|
|
||||||
import net.psforever.objects.sourcing.SourceUniqueness
|
import net.psforever.objects.sourcing.SourceUniqueness
|
||||||
import net.psforever.objects.vital.{DamagingActivity, InGameActivity, RepairFromEquipment, RepairingActivity}
|
import net.psforever.objects.vital.{DamagingActivity, InGameActivity, RepairFromEquipment, RepairingActivity}
|
||||||
|
import net.psforever.objects.zones.exp.ContributionStats
|
||||||
import net.psforever.types.PlanetSideEmpire
|
import net.psforever.types.PlanetSideEmpire
|
||||||
|
|
||||||
import scala.collection.mutable
|
import scala.collection.mutable
|
||||||
|
|
@ -15,10 +16,10 @@ class MachineRecoveryExperienceContributionProcess(
|
||||||
) extends RecoveryExperienceContributionProcess(faction, contributions) {
|
) extends RecoveryExperienceContributionProcess(faction, contributions) {
|
||||||
def submit(history: List[InGameActivity]): Unit = {
|
def submit(history: List[InGameActivity]): Unit = {
|
||||||
history.foreach {
|
history.foreach {
|
||||||
case d: DamagingActivity if d.amount - d.health > 0 =>
|
case d: DamagingActivity if d.amount == d.health =>
|
||||||
val (damage, recovery) = RecoveryExperienceContribution.contributeWithDamagingActivity(
|
val (damage, recovery) = RecoveryExperienceContribution.contributeWithDamagingActivity(
|
||||||
d,
|
d,
|
||||||
d.amount - d.health,
|
d.health,
|
||||||
damageParticipants,
|
damageParticipants,
|
||||||
participants,
|
participants,
|
||||||
damageInOrder,
|
damageInOrder,
|
||||||
|
|
@ -26,7 +27,7 @@ class MachineRecoveryExperienceContributionProcess(
|
||||||
)
|
)
|
||||||
damageInOrder = damage
|
damageInOrder = damage
|
||||||
recoveryInOrder = recovery
|
recoveryInOrder = recovery
|
||||||
case r: RepairFromEquipment =>
|
case r: RepairFromEquipment if !excludedTargets.contains(r.user.unique) =>
|
||||||
val (damage, recovery) = RecoveryExperienceContribution.contributeWithRecoveryActivity(
|
val (damage, recovery) = RecoveryExperienceContribution.contributeWithRecoveryActivity(
|
||||||
r.user,
|
r.user,
|
||||||
r.equipment_def.ObjectId,
|
r.equipment_def.ObjectId,
|
||||||
|
|
@ -56,4 +57,23 @@ class MachineRecoveryExperienceContributionProcess(
|
||||||
case _ => ()
|
case _ => ()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override def output(): mutable.LongMap[ContributionStats] = {
|
||||||
|
super.output().map { case (id, stats) =>
|
||||||
|
val weps = stats.weapons
|
||||||
|
.groupBy(_.equipment)
|
||||||
|
.map { case (wrapper, entries) =>
|
||||||
|
val size = entries.size
|
||||||
|
val newTime = entries.maxBy(_.time).time
|
||||||
|
entries.head.copy(
|
||||||
|
shots = size,
|
||||||
|
amount = entries.foldLeft(0)(_ + _.amount),
|
||||||
|
contributions = (10 + size).toFloat,
|
||||||
|
time = newTime
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.toSeq
|
||||||
|
(id, stats.copy(weapons = weps))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -262,6 +262,7 @@ object RecoveryExperienceContribution {
|
||||||
): (Seq[(Long, Int)], Seq[(Long, Int)]) = {
|
): (Seq[(Long, Int)], Seq[(Long, Int)]) = {
|
||||||
var outputDamageOrder = damageOrder
|
var outputDamageOrder = damageOrder
|
||||||
var outputRecoveryOrder = recoveryOrder
|
var outputRecoveryOrder = recoveryOrder
|
||||||
|
if (users.nonEmpty) {
|
||||||
val damageParticipants: mutable.LongMap[PlayerSource] = mutable.LongMap[PlayerSource]()
|
val damageParticipants: mutable.LongMap[PlayerSource] = mutable.LongMap[PlayerSource]()
|
||||||
users.zip {
|
users.zip {
|
||||||
val numberOfUsers = users.size
|
val numberOfUsers = users.size
|
||||||
|
|
@ -275,6 +276,7 @@ object RecoveryExperienceContribution {
|
||||||
outputDamageOrder = a
|
outputDamageOrder = a
|
||||||
outputRecoveryOrder = b
|
outputRecoveryOrder = b
|
||||||
}
|
}
|
||||||
|
}
|
||||||
(outputDamageOrder, outputRecoveryOrder)
|
(outputDamageOrder, outputRecoveryOrder)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue