restored bep from ntu deposits into resource silos; updating statistics in the database regarding kills and related stats including weapons; updated history management; basic experience calculation changes

This commit is contained in:
Fate-JH 2023-08-28 19:41:41 -04:00
parent c869cdc8a0
commit d6a37e9421
26 changed files with 1177 additions and 688 deletions

View file

@ -1,6 +1,6 @@
/* changes to objects from V008__Scoring.sql */
ALTER TABLE killactivity
ADD COLUMN victim_mounted INT NO NULL DEFAULT 0;
ADD COLUMN victim_mounted INT NOT NULL DEFAULT 0;
DROP PROCEDURE IF EXISTS proc_sessionnumber_initAndOrIncrease;
@ -254,7 +254,7 @@ END;
$$ LANGUAGE plpgsql;
/* new objects */
CREATE OR REPLACE TABLE machinedestroyed (
CREATE TABLE IF NOT EXISTS machinedestroyed (
"index" SERIAL PRIMARY KEY NOT NULL,
"avatar_id" INT NOT NULL REFERENCES avatar (id),
"weapon_id" INT NOT NULL,
@ -269,29 +269,44 @@ CREATE OR REPLACE TABLE machinedestroyed (
"timestamp" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
/*
expectations:
heal player with medapp: no intermediate_type, implement_type is the medapp (no, yes)
revive player with medapp: intermediate_type is 121, implement_type is the medapp (yes, yes)
repair max with bank: no intermediate_type, implement_type is the bank (no, yes)
repair foo with gluegun: intermediate_type is the foo object type, implement_type is the gluegun (yes, yes)
bail from vehicle: intermediate_type is the vehicle object type, no implement_type (yes, no)
cargo from vehicle: intermediate_type is the vehicle object type, implement_type is the cargo vehicle object type (yes, yes)
damage from vehicle (collision): no intermediate_type, implement_type is vehicle object type (no, yes)
damage from vehicle (explosion): intermediate_type is vehicle object type, implement_type is intermediate_type (yes, yes)
always ignore target_id = user_id
*/
CREATE OR REPLACE TABLE supportactivity (
CREATE TABLE IF NOT EXISTS "assistactivity" (
"index" SERIAL PRIMARY KEY NOT NULL,
"victim_id" INT NOT NULL REFERENCES avatar (id),
"attacker_id" INT NOT NULL REFERENCES avatar (id),
"weapon_id" SMALLINT NOT NULL,
"zone_id" SMALLINT NOT NULL,
"px" INT NOT NULL,
"py" INT NOT NULL,
"pz" INT NOT NULL,
"exp" INT NOT NULL,
"timestamp" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS supportactivity (
"index" SERIAL PRIMARY KEY NOT NULL,
"user_id" INT NOT NULL REFERENCES avatar (id), -- player that provides the support
"target_id" INT NOT NULL REFERENCES avatar (id), -- benefactor of the support
"interaction_type" SMALLINT NOT NULL, -- description of support
"target_exosuit" SMALLINT NOT NULL, -- benefactor's exo-suit
"interaction_type" SMALLINT NOT NULL, -- classification of support
"intermediate_type" INT DEFAULT 0, -- through what medium user_id supports target_id
"implement_type" INT DEFAULT 0, -- tool utilized by user_id to support target_id, potentially via interaction with intermediate_type
"bep" INT NOT NULL,
"exp" INT NOT NULL,
"timestamp" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS respawnsession (
"avatar_id" INT NOT NULL REFERENCES avatar (id),
"session_id" INT NOT NULL,
"respawn_count" INT NOT NULL DEFAULT 0,
UNIQUE(avatar_id, session_id)
);
CREATE TABLE IF NOT EXISTS respawn (
"avatar_id" INT NOT NULL REFERENCES avatar (id),
"respawn_count" INT NOT NULL DEFAULT 0,
UNIQUE(avatar_id)
);
/*
Before inserting a value into the weaponstatsession table to session id -1,
correct the session id to the most current session id,
@ -333,3 +348,33 @@ ON weaponstatsession
FOR EACH ROW
WHEN (NEW.session_id = -1)
EXECUTE FUNCTION fn_weaponstatsession_beforeInsert();
/*
A kill assist activity causes a major update to weapon stats:
the weapon that was used in the activity has the kills count for the killer updated/increased.
*/
CREATE OR REPLACE FUNCTION fn_assistactivity_updateRelatedStats()
RETURNS TRIGGER
AS
$$
DECLARE killerSessionId Int;
DECLARE killerId Int;
DECLARE weaponId Int;
BEGIN
killerId := NEW.killer_id;
weaponId := NEW.weapon_id;
SELECT proc_sessionnumber_get(killerId, killerSessionId);
BEGIN
UPDATE weaponstatsession
SET assists = assists + 1
WHERE avatar_id = killerId AND session_id = killerSessionId AND weapon_id = weaponId;
END;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE TRIGGER psf_assistactivity_updateRelatedStats
AFTER INSERT
ON killactivity
FOR EACH ROW
EXECUTE FUNCTION fn_assistactivity_updateRelatedStats();

View file

@ -7,7 +7,7 @@ import akka.actor.typed.{ActorRef, Behavior, PostStop, SupervisorStrategy}
import java.util.concurrent.atomic.AtomicInteger
import net.psforever.actors.zone.ZoneActor
import net.psforever.objects.avatar.scoring.{Assist, Death, EquipmentStat, KDAStat, Kill}
import net.psforever.objects.avatar.scoring.{Assist, Death, EquipmentStat, KDAStat, Kill, SupportActivity}
import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.sourcing.VehicleSource
import net.psforever.objects.vital.InGameHistory
@ -843,6 +843,7 @@ object AvatarActor {
def avatarNoLongerLoggedIn(accountId: Long): Unit = {
import ctx._
import scala.concurrent.ExecutionContext.Implicits.global //linter says unused but compiler says otherwise
ctx.run(
query[persistence.Account]
.filter(_.id == lift(accountId))
@ -1681,16 +1682,24 @@ class AvatarActor(
updateToolDischarge(stats)
Behaviors.same
case UpdateKillsDeathsAssists(stat) =>
updateKillsDeathsAssists(stat)
case UpdateKillsDeathsAssists(stat: Kill) =>
updateKills(stat)
Behaviors.same
case UpdateKillsDeathsAssists(stat: Assist) =>
updateAssists(stat)
Behaviors.same
case UpdateKillsDeathsAssists(stat: Death) =>
updateDeaths(stat)
Behaviors.same
case UpdateKillsDeathsAssists(stat: SupportActivity) =>
updateSupport(stat)
Behaviors.same
case AwardBep(bep, ExperienceType.Support) =>
supportExperiencePool = supportExperiencePool + bep
avatar.scorecard.rate(bep)
if (supportExperienceTimer.isCancelled) {
resetSupportExperienceTimer(previousBep = 0, previousDelay = 0)
}
awardSupportExperience(bep, previousDelay = 0L)
Behaviors.same
case AwardBep(bep, modifier) =>
@ -1877,7 +1886,8 @@ class AvatarActor(
performAvatarLogin(avatarId, accountId, replyTo)
case _ =>
//refuse
//TODO refuse
//TODO refuse?
sessionActor ! SessionActor.Quit()
}
}
@ -3011,63 +3021,114 @@ class AvatarActor(
}
}
def updateKillsDeathsAssists(kdaStat: KDAStat): Unit = {
avatar.scorecard.rate(kdaStat)
val exp = kdaStat.experienceEarned
def awardSupportExperience(bep: Long, previousDelay: Long): Unit = {
supportExperiencePool = supportExperiencePool + bep
avatar.scorecard.rate(bep)
if (supportExperienceTimer.isCancelled) {
resetSupportExperienceTimer(previousBep = 0, previousDelay = 0)
}
}
def updateKills(killStat: Kill): Unit = {
val exp = killStat.experienceEarned
val (modifiedExp, msg) = updateExperienceAndType(killStat.experienceEarned)
avatar.scorecard.rate(killStat.copy(experienceEarned = modifiedExp))
val _session = session.get
val zone = _session.zone
val player = _session.player
val playerSource = PlayerSource(player)
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 _ => None
}) match {
case Some(mount: PlanetSideGameObject with FactionAffinity with InGameHistory with MountedWeapons) =>
player.HistoryAndContributions() ++ InGameHistory.ContributionFrom(mount).toList
case _ =>
player.HistoryAndContributions()
}
zone.actor ! ZoneActor.RewardOurSupporters(playerSource, historyTranscript, killStat, exp)
val target = killStat.info.targetAfter.asInstanceOf[PlayerSource]
val targetMounted = target.seatedIn.map { case (v: VehicleSource, seat) =>
val definition = v.Definition
definition.ObjectId * 10 + Vehicles.SeatPermissionGroup(definition, seat).map { _.id }.getOrElse(0)
}.getOrElse(0)
zones.exp.ToDatabase.reportKillBy(
avatar.id.toLong,
target.CharId,
target.ExoSuit.id,
targetMounted,
killStat.info.interaction.cause.attribution,
player.Zone.Number,
target.Position,
modifiedExp
)
if (exp > 0L) {
setBep(avatar.bep + exp, msg)
}
}
def updateDeaths(deathStat: Death): Unit = {
avatar.scorecard.rate(deathStat)
val _session = session.get
val zone = _session.zone
val player = _session.player
kdaStat match {
case kill: Kill =>
val playerSource = PlayerSource(player)
val historyTranscript = (kill.info.interaction.cause match {
case pr: ProjectileReason => pr.projectile.mounted_in.flatMap { a => zone.GUID(a._1) } //what fired the projectile
case _ => None
}) match {
case Some(mount: PlanetSideGameObject with FactionAffinity with InGameHistory with MountedWeapons) =>
player.HistoryAndContributions() ++ InGameHistory.ContributionFrom(mount).toList
case _ =>
player.HistoryAndContributions()
}
zone.actor ! ZoneActor.RewardOurSupporters(playerSource, historyTranscript, kill, exp)
val target = kill.info.targetAfter.asInstanceOf[PlayerSource]
val targetMounted = target.seatedIn.map { case (v: VehicleSource, seat) =>
val definition = v.Definition
definition.ObjectId * 10 + Vehicles.SeatPermissionGroup(definition, seat).map { _.id }.getOrElse(0)
}.getOrElse(0)
zones.exp.ToDatabase.reportKillBy(
avatar.id.toLong,
target.CharId,
target.ExoSuit.id,
targetMounted,
kill.info.interaction.cause.attribution,
player.Zone.Number,
target.Position,
kill.experienceEarned
)
case assist: Assist =>
val avatarId = avatar.id.toLong
assist.weapons.foreach {
zones.exp.ToDatabase.reportAssistKills(avatarId, _, assists = 1)
}
case _: Death =>
zone.AvatarEvents ! AvatarServiceMessage(
player.Name,
AvatarAction.SendResponse(
Service.defaultPlayerGUID,
AvatarStatisticsMessage(DeathStatistic(avatar.scorecard.Lives.size))
)
)
zone.AvatarEvents ! AvatarServiceMessage(
player.Name,
AvatarAction.SendResponse(
Service.defaultPlayerGUID,
AvatarStatisticsMessage(DeathStatistic(avatar.scorecard.Lives.size))
)
)
}
def updateAssists(assistStat: Assist): Unit = {
avatar.scorecard.rate(assistStat)
val exp = assistStat.experienceEarned
val _session = session.get
val avatarId = avatar.id.toLong
assistStat.weapons.foreach { wrapper =>
zones.exp.ToDatabase.reportKillAssistBy(
avatarId,
assistStat.victim.CharId,
wrapper.equipment,
_session.zone.Number,
assistStat.victim.Position,
exp
)
}
if (exp > 0L) {
val gameOpts = Config.app.game
val (msg, modifier): (ExperienceType, Float) = if (player.Carrying.contains(SpecialCarry.RabbitBall)) {
(ExperienceType.RabbitBall, 1.25f)
} else {
(ExperienceType.Normal, 1f)
}
setBep(avatar.bep + (exp * modifier * gameOpts.bepRate).toLong, msg)
awardSupportExperience(exp, previousDelay = 0L)
}
def updateSupport(supportStat: SupportActivity): Unit = {
val avatarId = avatar.id.toLong
val target = supportStat.target
val targetId = target.CharId
val targetExosuit = target.ExoSuit.id
val exp = supportStat.experienceEarned
supportStat.weapons.foreach { entry =>
zones.exp.ToDatabase.reportSupportBy(
avatarId,
targetId,
targetExosuit,
entry.value,
entry.intermediate,
entry.equipment,
exp
)
}
awardSupportExperience(exp, previousDelay = 0L)
}
def updateExperienceAndType(exp: Long): (Long, ExperienceType) = {
val _session = session.get
val player = _session.player
val gameOpts = Config.app.game
val (modifier, msg) = if (player.Carrying.contains(SpecialCarry.RabbitBall)) {
(1.25f, ExperienceType.RabbitBall)
} else {
(1f, ExperienceType.Normal)
}
((exp * modifier * gameOpts.bepRate).toLong, msg)
}
def updateToolDischarge(stats: EquipmentStat): Unit = {

View file

@ -282,7 +282,6 @@ class SessionMountHandlers(
def MountingAction(tplayer: Player, obj: PlanetSideGameObject with FactionAffinity with InGameHistory, seatNum: Int): Unit = {
val playerGuid: PlanetSideGUID = tplayer.GUID
val objGuid: PlanetSideGUID = obj.GUID
tplayer.ContributionFrom(obj)
sessionData.playerActionsToCancel()
avatarActor ! AvatarActor.DeactivateActiveImplants()
avatarActor ! AvatarActor.SuspendStaminaRegeneration(3.seconds)

View file

@ -3,6 +3,7 @@ package net.psforever.objects.avatar.scoring
import net.psforever.objects.sourcing.PlayerSource
import net.psforever.objects.vital.interaction.DamageResult
import net.psforever.objects.zones.exp.EquipmentUseContextWrapper
import org.joda.time.LocalDateTime
trait KDAStat {
@ -10,10 +11,29 @@ trait KDAStat {
val time: LocalDateTime = LocalDateTime.now()
}
final case class Kill(victim: PlayerSource, info: DamageResult, experienceEarned: Long) extends KDAStat
final case class Kill(
victim: PlayerSource,
info: DamageResult,
experienceEarned: Long
) extends KDAStat
final case class Assist(victim: PlayerSource, weapons: Seq[Int], damageInflictedPercentage: Float, experienceEarned: Long) extends KDAStat
final case class Assist(
victim: PlayerSource,
weapons: Seq[EquipmentUseContextWrapper],
damageInflictedPercentage: Float,
experienceEarned: Long
) extends KDAStat
final case class Death(assailant: Seq[PlayerSource], timeAlive: Long, bep: Long) extends KDAStat {
final case class Death(
assailant: Seq[PlayerSource],
timeAlive: Long,
bep: Long
) extends KDAStat {
def experienceEarned: Long = 0
}
final case class SupportActivity(
target: PlayerSource,
weapons: Seq[EquipmentUseContextWrapper],
experienceEarned: Long
) extends KDAStat

View file

@ -36,7 +36,7 @@ class ScoreCard() {
curr = curr.copy(assists = a +: curr.assists)
val faction = a.victim.Faction
a.weapons.foreach { wid =>
ScoreCard.updateStatisticsFor(assistStatistics, wid, faction)
ScoreCard.updateStatisticsFor(assistStatistics, wid.equipment, faction)
}
case d: Death =>
val expired = curr

View file

@ -7,8 +7,8 @@ import net.psforever.actors.zone.BuildingActor
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
import net.psforever.objects.serverobject.transfer.TransferBehavior
import net.psforever.objects.serverobject.structures.Building
import net.psforever.objects.{GlobalDefinitions, Ntu, NtuContainer, NtuStorageBehavior}
import net.psforever.types.PlanetSideEmpire
import net.psforever.objects.{GlobalDefinitions, Ntu, NtuContainer, NtuStorageBehavior, Vehicle}
import net.psforever.types.{ExperienceType, PlanetSideEmpire}
import net.psforever.services.Service
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
@ -181,6 +181,19 @@ class ResourceSiloControl(resourceSilo: ResourceSilo)
if (amount != 0) {
panelAnimationFunc(sender, amount)
panelAnimationFunc = SkipPanelAnimation
(src match {
case v: Vehicle => Some(v)
case _ => None
})
.map { v => (v, v.Owners) }
.collect { case (vehicle, Some(owner)) =>
//experience is reported as normal
val deposit: Long = 100L * math.floor(amount).toLong / math.floor(resourceSilo.MaxNtuCapacitor / 105f).toLong
vehicle.Zone.AvatarEvents ! AvatarServiceMessage(
owner.name,
AvatarAction.AwardBep(0, deposit, ExperienceType.Normal)
)
}
}
}
@ -192,6 +205,7 @@ class ResourceSiloControl(resourceSilo: ResourceSilo)
* @param trigger if positive, activate the animation;
* if negative or zero, disable the animation
*/
//noinspection ScalaUnusedSymbol
def PanelAnimation(source: ActorRef, trigger: Float): Unit = {
val currentlyHas = resourceSilo.NtuCapacitor
// do not let the trigger charge go to waste, but also do not let the silo be filled

View file

@ -9,12 +9,11 @@ import net.psforever.objects.serverobject.resourcesilo.ResourceSilo
import net.psforever.objects.serverobject.structures.WarpGate
import net.psforever.objects.serverobject.transfer.{TransferBehavior, TransferContainer}
import net.psforever.objects.{NtuContainer, _}
import net.psforever.types.{DriveState, ExperienceType}
import net.psforever.types.DriveState
import net.psforever.services.Service
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
import akka.actor.typed.scaladsl.adapter._
import net.psforever.objects.serverobject.transfer.TransferContainer.TransferMaterial
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._
@ -140,10 +139,6 @@ trait AntTransferBehavior extends TransferBehavior with NtuStorageBehavior {
chargeable.NtuCapacitor -= chargeToDeposit
UpdateNtuUI(chargeable)
Ntu.Grant(chargeable, chargeToDeposit)
vehicle.Zone.AvatarEvents ! AvatarServiceMessage(
vehicle.OwnerName.getOrElse(""),
AvatarAction.AwardBep(0, 100L, ExperienceType.Normal)
)
}
/** Stopping */

View file

@ -77,8 +77,8 @@ final case class VehicleCargoDismountActivity(vehicle: VehicleSource, cargo: Veh
final case class Contribution(src: SourceUniqueness, entries: List[InGameActivity])
extends GeneralActivity {
val start: Long = entries.last.time
val end: Long = entries.head.time
val start: Long = entries.headOption.map { _.time }.getOrElse(System.currentTimeMillis())
val end: Long = entries.lastOption.map { _.time }.getOrElse(start)
}
/* vitals history */
@ -261,17 +261,18 @@ trait InGameHistory {
mutable.HashMap[SourceUniqueness, Contribution]()
def ContributionFrom(target: PlanetSideGameObject with FactionAffinity with InGameHistory): Option[Contribution] = {
if (target ne this) {
InGameHistory.ContributionFrom(target) match {
case out @ Some(in @ Contribution(src, _)) =>
contributionInheritance.put(src, in)
out
if (target eq this) {
None
} else {
target.GetContribution() match {
case Some(in) =>
val contribution = Contribution(SourceEntry(target).unique, in)
contributionInheritance.put(contribution.src, contribution)
Some(contribution)
case None =>
contributionInheritance.remove(SourceEntry(target).unique)
None
}
} else {
None
}
}
@ -307,12 +308,11 @@ object InGameHistory {
unit: Option[PlanetSideGameObject with FactionAffinity with InGameHistory]
): Unit = {
val toUnitSource = unit.collect { case o: PlanetSideGameObject with FactionAffinity => SourceEntry(o) }
val event: GeneralActivity = if (obj.History.nonEmpty || obj.History.headOption.exists {
_.isInstanceOf[SpawningActivity]
}) {
ReconstructionActivity(ObjectSource(obj), zoneNumber, toUnitSource)
} else {
val event: GeneralActivity = if (obj.History.isEmpty) {
SpawningActivity(ObjectSource(obj), zoneNumber, toUnitSource)
} else {
ReconstructionActivity(ObjectSource(obj), zoneNumber, toUnitSource)
}
if (obj.History.lastOption match {
case Some(evt: SpawningActivity) => evt != event

View file

@ -0,0 +1,31 @@
// Copyright (c) 2023 PSForever
package net.psforever.objects.zones.exp
import enumeratum.values.IntEnumEntry
sealed abstract class EquipmentUseContextWrapper(val value: Int) extends IntEnumEntry {
def equipment: Int
def intermediate: Int = 0
}
final case class NoUse(equipment: Int) extends EquipmentUseContextWrapper(value = -1)
final case class DamageWith(equipment: Int) extends EquipmentUseContextWrapper(value = 0)
final case class Destroyed(equipment: Int) extends EquipmentUseContextWrapper(value = 1)
final case class ReviveAssist(equipment: Int) extends EquipmentUseContextWrapper(value = 4)
final case class AmenityDestroyed(equipment: Int) extends EquipmentUseContextWrapper(value = 10)
final case class DriverKilled(equipment: Int) extends EquipmentUseContextWrapper(value = 12)
final case class GunnerKilled(equipment: Int) extends EquipmentUseContextWrapper(value = 13)
final case class PassengerKilled(equipment: Int) extends EquipmentUseContextWrapper(value = 14)
final case class CargoDestroyed(equipment: Int) extends EquipmentUseContextWrapper(value = 15)
final case class DriverAssist(equipment: Int) extends EquipmentUseContextWrapper(value = 18)
final case class HealKillAssist(equipment: Int) extends EquipmentUseContextWrapper(value = 20)
final case class ReviveKillAssist(equipment: Int) extends EquipmentUseContextWrapper(value = 21)
final case class RepairKillAssist(equipment: Int, override val intermediate: Int) extends EquipmentUseContextWrapper(value = 22)
final case class AmsRespawnKillAssist(equipment: Int) extends EquipmentUseContextWrapper(value = 23)
final case class HotDropKillAssist(equipment: Int, override val intermediate: Int) extends EquipmentUseContextWrapper(value = 24)
final case class HackKillAssist(equipment: Int) extends EquipmentUseContextWrapper(value = 25)
final case class LodestarRearmKillAssist(equipment: Int) extends EquipmentUseContextWrapper(value = 26)
final case class AmsResupplyKillAssist(equipment: Int) extends EquipmentUseContextWrapper(value = 27)
final case class RouterKillAssist(equipment: Int) extends EquipmentUseContextWrapper(value = 28)

View file

@ -23,7 +23,7 @@ object KillAssists {
case damage: DamagingActivity => damage.data.targetAfter.asInstanceOf[PlayerSource].Health == 0
case _ => false
}
if (spawnIndex == -1 || endIndex == -1) {
if (spawnIndex == -1 || endIndex == -1 || spawnIndex > endIndex) {
Nil
} else {
history.slice(spawnIndex, endIndex)
@ -193,7 +193,7 @@ object KillAssists {
val assists = func(history, victim.Faction).filterNot { case (_, kda) => kda.amount <= 0 }
val total = assists.values.foldLeft(0f)(_ + _.total)
val output = assists.map { case (id, kda) =>
(id, ContributionStatsOutput(kda.player, kda.weapons.map { _.equipment_id }, kda.amount / total))
(id, ContributionStatsOutput(kda.player, kda.weapons.map { _.equipment }, kda.amount / total))
}
output.remove(victim.CharId)
output
@ -277,7 +277,8 @@ object KillAssists {
case Some(mod) =>
//previous attacker, just add to entry
val firstWeapon = mod.weapons.head
val weapons = if (firstWeapon.equipment_id == wepid) {
val newEntry = DamageWith(wepid)
val weapons = if (firstWeapon.equipment == newEntry) {
firstWeapon.copy(
amount = firstWeapon.amount + amount,
shots = firstWeapon.shots + 1,
@ -285,7 +286,7 @@ object KillAssists {
contributions = firstWeapon.contributions + percentage
) +: mod.weapons.tail
} else {
WeaponStats(wepid, amount, 1, time, percentage) +: mod.weapons
WeaponStats(newEntry, amount, 1, time, percentage) +: mod.weapons
}
mod.copy(
amount = mod.amount + amount,
@ -298,7 +299,7 @@ object KillAssists {
//new attacker, new entry
ContributionStats(
user,
Seq(WeaponStats(wepid, amount, 1, time, percentage)),
Seq(WeaponStats(DamageWith(wepid), amount, 1, time, percentage)),
amount,
amount,
1,

View file

@ -3,11 +3,12 @@ package net.psforever.objects.zones.exp
import akka.actor.ActorRef
import net.psforever.objects.GlobalDefinitions
import net.psforever.objects.avatar.scoring.{Assist, Kill}
import net.psforever.objects.avatar.scoring.{Kill, SupportActivity}
import net.psforever.objects.serverobject.hackable.Hackable.HackInfo
import net.psforever.objects.sourcing.{AmenitySource, PlayerSource, SourceEntry, SourceUniqueness, VehicleSource}
import net.psforever.objects.vital.{Contribution, InGameActivity, RevivingActivity, TerminalUsedActivity, VehicleCargoDismountActivity, VehicleCargoMountActivity, VehicleCargoMountChange, VehicleDismountActivity, VehicleMountActivity, VehiclePassengerMountChange}
import net.psforever.objects.vital.projectile.ProjectileReason
import net.psforever.objects.zones.exp.rec.{ArmorRecoveryExperienceContributionProcess, CombinedHealthAndArmorContributionProcess, MachineRecoveryExperienceContributionProcess}
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
import net.psforever.types.{PlanetSideEmpire, Vector3}
@ -20,15 +21,16 @@ object KillContributions {
bank,
nano_dispenser,
medicalapplicator,
order_terminal,
order_terminala,
order_terminalb,
medical_terminal,
adv_med_terminal,
crystals_health_a,
crystals_health_b,
bfr_rearm_terminal,
multivehicle_rearm_terminal,
lodestar_repair_terminal
).collect { _.ObjectId }
}
} //TODO currently includes things that are not typical items but are used for expressing contribution implements
private[exp] def rewardTheseSupporters(
target: PlayerSource,
@ -40,27 +42,27 @@ object KillContributions {
//setup
val killTime = kill.time.toDate.getTime
val faction = target.Faction
val limitedHistory = limitHistoryToThisLife(history.toList, killTime)
//divide by applicable time periods (long=10minutes, short=5minutes)
val shortPeriod = killTime - 300000L
val (contributions, (longHistory, shortHistory)) = {
val (contrib, onlyHistory) = limitedHistory.partition { _.isInstanceOf[Contribution] }
val (contrib, onlyHistory) = history.partition { _.isInstanceOf[Contribution] }
(
contrib
.collect { case Contribution(unique, entries) => (unique, entries) }
.toMap[SourceUniqueness, List[InGameActivity]],
onlyHistory.partition { _.time > shortPeriod }
limitHistoryToThisLife(onlyHistory.toList, killTime).partition { _.time > shortPeriod }
)
}
//sort by applicable time periods, as long as the longer period is represented by activity
val otherContributionCalculations = contributionScoringAndCulling(faction, kill, contributions, bep)(_, _)
val empty = mutable.ListBuffer[SourceUniqueness]()
val otherContributionCalculations = contributionScoringAndCulling(faction, kill, contributions, bep)(_, _, _)
val finalContributions = if (longHistory.nonEmpty && KillAssists.calculateMenace(target) > 2) {
val longContributionProcess = new CombinedHealthAndArmorContributionProcess(faction, contributions, Nil)
val shortContributionProcess = new CombinedHealthAndArmorContributionProcess(faction, contributions, Seq(longContributionProcess))
longContributionProcess.submit(longHistory)
shortContributionProcess.submit(shortHistory)
val longContributionEntries = otherContributionCalculations(longHistory, longContributionProcess.output())
val shortContributionEntries = otherContributionCalculations(shortHistory, shortContributionProcess.output())
val longContributionEntries = otherContributionCalculations(longHistory, longContributionProcess.output(), empty)
val shortContributionEntries = otherContributionCalculations(shortHistory, shortContributionProcess.output(), empty)
longContributionEntries.remove(target.CharId)
longContributionEntries.remove(kill.victim.CharId)
shortContributionEntries.remove(target.CharId)
@ -74,7 +76,7 @@ object KillContributions {
} else {
val contributionProcess = new CombinedHealthAndArmorContributionProcess(faction, contributions, Nil)
contributionProcess.submit(shortHistory)
val contributionEntries = otherContributionCalculations(shortHistory, contributionProcess.output())
val contributionEntries = otherContributionCalculations(shortHistory, contributionProcess.output(), empty)
contributionEntries.remove(target.CharId)
contributionEntries.remove(kill.victim.CharId)
contributionEntries
@ -87,7 +89,7 @@ object KillContributions {
finalContributions.foreach { case (charId, ContributionStatsOutput(player, weapons, exp)) =>
eventBus ! AvatarServiceMessage(
player.Name,
AvatarAction.UpdateKillsDeathsAssists(charId, Assist(victim, weapons, 1f, exp.toLong))
AvatarAction.UpdateKillsDeathsAssists(charId, SupportActivity(victim, weapons, exp.toLong))
)
}
}
@ -116,7 +118,8 @@ object KillContributions {
)
(
history: List[InGameActivity],
contributionEntries: mutable.LongMap[ContributionStats]
contributionEntries: mutable.LongMap[ContributionStats],
excludedTargets: mutable.ListBuffer[SourceUniqueness]
): mutable.LongMap[ContributionStats] = {
contributionEntries.map { case (id, stat) =>
val newWeaponStats = stat.weapons.map { weaponStat =>
@ -124,11 +127,11 @@ object KillContributions {
}
contributionEntries.put(id, stat.copy(weapons = newWeaponStats))
}
contributeWithKillWhileMountedActivity(faction, kill, history, contributionEntries)
contributeWithKillWhileMountedActivity(faction, kill, history, contributionEntries, excludedTargets)
contributeWithRevivalActivity(history, contributionEntries)
contributeWithVehicleTransportActivity(history, contributionEntries)
contributeWithVehicleCargoTransportActivity(history, contributionEntries)
contributeWithTerminalActivity(faction, history, contributions, contributionEntries)
contributeWithTerminalActivity(faction, history, contributions, contributionEntries, excludedTargets)
contributionEntries.remove(0)
contributionEntries
}
@ -137,7 +140,8 @@ object KillContributions {
faction: PlanetSideEmpire.Value,
kill: Kill,
history: List[InGameActivity],
participants: mutable.LongMap[ContributionStats]
participants: mutable.LongMap[ContributionStats],
excludedTargets: mutable.ListBuffer[SourceUniqueness]
): List[InGameActivity] = {
(kill
.info
@ -147,7 +151,7 @@ object KillContributions {
case _ => None
})
.collect {
case mount: VehicleSource =>
case mount: VehicleSource if !excludedTargets.contains(mount.unique) =>
//repairs
val contributions = history
.collect { case Contribution(unique, entries) if mount.unique == unique => (unique, entries) }
@ -155,7 +159,7 @@ object KillContributions {
contributions
.foreach {
case (_, localHistory) =>
val process = new ArmorRecoveryExperienceContributionProcess(faction, contributions)
val process = new ArmorRecoveryExperienceContributionProcess(faction, contributions, excludedTargets :+ mount.unique)
process.submit(localHistory)
cullContributorImplements(process.output()).foreach {
case (id, stat) =>
@ -179,14 +183,21 @@ object KillContributions {
}
}
//vehicle owner
extractContributionsForEntityByUser(faction, mount, contributions, participants)
extractContributionsForEntityByUser(faction, mount, contributions, participants, excludedTargets)
mount.owner
.collect {
case owner =>
val time = kill.time.toDate.getTime
participants.getOrElseUpdate(
owner.CharId,
ContributionStats(owner, Seq(WeaponStats(0, 1, 1, time, 10f)), 1, 1, 1, time)
ContributionStats(
owner,
Seq(WeaponStats(DriverAssist(mount.Definition.ObjectId), 1, 1, time, 10f)),
1,
1,
1,
time
)
)
}
}
@ -236,7 +247,15 @@ object KillContributions {
val numberOfDismounts = dismountsFromVehicle.size
contributeWithCombinedActivity(
owner.CharId,
dismountsFromVehicle.map { act => WeaponStats(act.vehicle.Definition.ObjectId, 0, numberOfDismounts, act.time, 15f) },
dismountsFromVehicle.map { act =>
val statContext = act.vehicle.Definition match {
case v @ GlobalDefinitions.router =>
RouterKillAssist(v.ObjectId)
case v =>
HotDropKillAssist(v.ObjectId, 0)
}
WeaponStats(statContext, 0, numberOfDismounts, act.time, 15f)
},
dismountsFromVehicle.head.vehicle.owner.get,
amount = 0,
total = 0,
@ -280,7 +299,15 @@ object KillContributions {
val numberOfDismounts = dismountsFromVehicle.size
contributeWithCombinedActivity(
owner.CharId,
dismountsFromVehicle.map { act => WeaponStats(act.vehicle.Definition.ObjectId, 0, numberOfDismounts, act.time, 15f) },
dismountsFromVehicle.map { act =>
WeaponStats(
HotDropKillAssist(act.vehicle.Definition.ObjectId, act.cargo.Definition.ObjectId),
0,
numberOfDismounts,
act.time,
15f
)
},
dismountsFromVehicle.head.vehicle.owner.get,
amount = 0,
total = 0,
@ -303,7 +330,9 @@ object KillContributions {
val numberOfRevives = revivesByThisPlayer.size
contributeWithCombinedActivity(
id,
revivesByThisPlayer.map { stat => WeaponStats(stat.equipment.ObjectId, 100, 1, stat.time, 25f) },
revivesByThisPlayer.map { stat =>
WeaponStats(ReviveKillAssist(stat.equipment.ObjectId), 100, 1, stat.time, 25f)
},
revivesByThisPlayer.head.user,
amount = 100 * numberOfRevives,
total = 100 * numberOfRevives,
@ -319,41 +348,83 @@ object KillContributions {
faction: PlanetSideEmpire.Value,
history: List[InGameActivity],
contributions: Map[SourceUniqueness, List[InGameActivity]],
participants: mutable.LongMap[ContributionStats]
participants: mutable.LongMap[ContributionStats],
excludedTargets: mutable.ListBuffer[SourceUniqueness]
): List[InGameActivity] = {
contributeWithTerminalActivity(
history.collect { case t: TerminalUsedActivity => (t, t.terminal, t.terminal.hacked) },
faction,
contributions,
participants
participants,
excludedTargets
)
}
private[exp] def contributeWithTerminalActivity(
data: Seq[(InGameActivity, AmenitySource, Option[HackInfo])],
faction: PlanetSideEmpire.Value,
contributions: Map[SourceUniqueness, List[InGameActivity]],
participants: mutable.LongMap[ContributionStats]
contributions: Map[SourceUniqueness, List[InGameActivity]],
participants: mutable.LongMap[ContributionStats],
excludedTargets: mutable.ListBuffer[SourceUniqueness]
): List[InGameActivity] = {
data.collect {
case (t, terminal, Some(info)) if terminal.Faction != faction =>
/*
if the terminal has been hacked,
and the original terminal does not align with our own faction,
then the support must be reported as a hack;
if we are the same faction as the terminal, then the hacked condition is irrelevant
*/
participants.getOrElseUpdate(
info.player.CharId,
ContributionStats(info.player, Seq(WeaponStats(terminal.Definition.ObjectId, 0, 1, t.time, 10f)), 0, 0, 1, t.time)
ContributionStats(
info.player,
Seq(WeaponStats(HackKillAssist(terminal.Definition.ObjectId), 0, 1, t.time, 10f)),
0,
0,
1,
t.time
)
)
t
case (t, terminal, _) =>
terminal.installation match {
val (equipmentUseContext, ownerOpt) = terminal.installation match {
case v: VehicleSource =>
v.owner.collect {
owner =>
participants.getOrElseUpdate(
owner.CharId,
ContributionStats(owner, Seq(WeaponStats(terminal.Definition.ObjectId, 0, 1, t.time, 10f)), 0, 0, 1, t.time)
)
terminal.Definition match {
case GlobalDefinitions.order_terminala =>
extractContributionsForEntityByUser(faction, v, contributions, participants, excludedTargets)
(AmsResupplyKillAssist(terminal.Definition.ObjectId), v.owner)
case GlobalDefinitions.order_terminalb =>
extractContributionsForEntityByUser(faction, v, contributions, participants, excludedTargets)
(AmsResupplyKillAssist(terminal.Definition.ObjectId), v.owner)
case GlobalDefinitions.lodestar_repair_terminal =>
extractContributionsForEntityByUser(faction, v, contributions, participants, excludedTargets)
(RepairKillAssist(terminal.Definition.ObjectId, v.Definition.ObjectId), v.owner)
case GlobalDefinitions.bfr_rearm_terminal =>
extractContributionsForEntityByUser(faction, v, contributions, participants, excludedTargets)
(LodestarRearmKillAssist(terminal.Definition.ObjectId), v.owner)
case GlobalDefinitions.multivehicle_rearm_terminal =>
extractContributionsForEntityByUser(faction, v, contributions, participants, excludedTargets)
(LodestarRearmKillAssist(terminal.Definition.ObjectId), v.owner)
case _ =>
(NoUse(0), None)
}
case _ =>
extractContributionsForEntityByUser(faction, terminal, contributions, participants)
(NoUse(0), None)
}
ownerOpt.collect {
owner =>
participants.getOrElseUpdate(
owner.CharId,
ContributionStats(
owner,
Seq(WeaponStats(equipmentUseContext, 0, 1, t.time, 10f)),
0,
0,
1,
t.time
)
)
}
t
}.toList
@ -363,22 +434,27 @@ object KillContributions {
faction: PlanetSideEmpire.Value,
target: SourceEntry,
contributions: Map[SourceUniqueness, List[InGameActivity]],
contributionsBy: mutable.LongMap[ContributionStats]
contributionsBy: mutable.LongMap[ContributionStats],
excludedTargets: mutable.ListBuffer[SourceUniqueness]
): List[InGameActivity] = {
val shortHistory = contributions.getOrElse(target.unique, Nil)
val process = new ArmorRecoveryExperienceContributionProcess(faction, contributions)
process.submit(shortHistory)
cullContributorImplements(process.output()).foreach {
case (id, stats) => contributionsBy.getOrElseUpdate(id, stats)
if (!excludedTargets.contains(target.unique)) {
val shortHistory = contributions.getOrElse(target.unique, Nil)
val process = new MachineRecoveryExperienceContributionProcess(faction, contributions, excludedTargets :+ target.unique)
process.submit(shortHistory)
cullContributorImplements(process.output()).foreach {
case (id, stats) => contributionsBy.getOrElseUpdate(id, stats)
}
shortHistory
} else {
Nil
}
shortHistory
}
private[exp] def cullContributorImplements(
input: mutable.LongMap[ContributionStats]
): mutable.LongMap[ContributionStats] = {
input.collect { case (id, entry) =>
(id, entry.copy(weapons = entry.weapons.filter { stats => KillContributions.RecoveryItems.contains(stats.equipment_id) }))
(id, entry.copy(weapons = entry.weapons.filter { stats => KillContributions.RecoveryItems.contains(stats.equipment.equipment) })) //TODO bad test; fix later
}.filter { case (_, entry) =>
entry.weapons.nonEmpty
}
@ -398,7 +474,7 @@ object KillContributions {
case Some(stats) =>
val oldWeapons = stats.weapons
val newWeapons = newStats.map { weapon =>
oldWeapons.find { _.equipment_id == weapon.equipment_id } match {
oldWeapons.find { _.equipment == weapon.equipment } match {
case Some(foundEntry) =>
weapon.copy(
amount = weapon.amount + foundEntry.amount,
@ -429,7 +505,7 @@ object KillContributions {
val weapons = entry.weapons
(
entry.player,
weapons.filter { _.amount == 0 }.map { _.equipment_id },
weapons.filter { _.amount == 0 }.map { _.equipment },
(0.9f * math.min(weapons.foldLeft(0f)(_ + _.contributions), bep.toFloat)).toLong
)
}
@ -441,13 +517,13 @@ object KillContributions {
val weapons = entry.weapons
(
entry.player,
weapons.map { _.equipment_id },
weapons.map { _.equipment },
math.min(weapons.foldLeft(0f)(_ + _.contributions).toLong, bep)
)
}
}
.orElse {
Some((PlayerSource.Nobody, Seq(0), 0L))
Some((PlayerSource.Nobody, Seq(), 0L))
}
.collect {
case (player, weaponIds, experience) if experience > 0 =>

View file

@ -1,449 +0,0 @@
// Copyright (c) 2023 PSForever
package net.psforever.objects.zones.exp
import net.psforever.objects.sourcing.{PlayerSource, SourceUniqueness}
import net.psforever.objects.vital.interaction.{Adversarial, DamageResult}
import net.psforever.objects.vital.{DamagingActivity, HealFromEquipment, HealFromTerminal, HealingActivity, InGameActivity, RepairFromEquipment, RepairFromTerminal, RepairingActivity, RevivingActivity, SupportActivityCausedByAnother}
import net.psforever.types.PlanetSideEmpire
import scala.collection.mutable
sealed trait RecoveryExperienceContribution {
def submit(history: List[InGameActivity]): Unit
def output(): mutable.LongMap[ContributionStats]
def clear(): Unit
}
object RecoveryExperienceContribution {
private[exp] def contributeWithDamagingActivity(
activity: DamagingActivity,
amount: Int,
damageParticipants: mutable.LongMap[PlayerSource],
recoveryParticipants: mutable.LongMap[ContributionStats],
damageOrder: Seq[(Long, Int)],
recoveryOrder: Seq[(Long, Int)]
): (Seq[(Long, Int)], Seq[(Long, Int)]) = {
//mark entries from the ordered recovery list to truncate
val data: DamageResult = activity.data
val time: Long = activity.time
var lastCharId: Long = 0L
var lastValue: Int = 0
var ramt: Int = amount
var rindex: Int = 0
val riter = recoveryOrder.iterator
while (riter.hasNext && ramt > 0) {
val (id, value) = riter.next()
if (value > 0) {
/*
if the amount on the previous recovery node is positive, reduce it by the damage value for that user's last used equipment
keep traversing recovery nodes, and lobbing them off, until the recovery amount is zero
if the user can not be found having an entry, skip the update but lob off the recovery progress node all the same
if the amount is zero, do not check any further recovery progress nodes
*/
recoveryParticipants
.get(id)
.foreach { entry =>
val weapons = entry.weapons
lastCharId = id
lastValue = value
if (value > ramt) {
//take from the value on the last-used equipment, at the front of the list
recoveryParticipants.put(
id,
entry.copy(
weapons = weapons.head.copy(amount = math.max(0, weapons.head.amount - ramt), time = time) +: weapons.tail,
amount = math.max(0, entry.amount - ramt),
time = time
)
)
ramt = 0
lastValue = lastValue - value
} else {
//take from the value on the last-used equipment, at the front of the list
//move that entry to the end of the list
recoveryParticipants.put(
id,
entry.copy(
weapons = weapons.tail :+ weapons.head.copy(amount = 0, time = time),
amount = math.max(0, entry.amount - ramt),
time = time
)
)
ramt = ramt - value
rindex += 1
lastValue = 0
}
}
rindex += 1
}
}
//damage order and damage contribution entry
val newDamageEntry = data
.adversarial
.collect { case Adversarial(p: PlayerSource, _, _) => (p, damageParticipants.get(p.CharId)) }
.collect {
case (player, Some(PlayerSource.Nobody)) =>
damageParticipants.put(player.CharId, player)
Some(player)
case (player, Some(_)) =>
damageParticipants.getOrElseUpdate(player.CharId, player)
Some(player)
}
.collect {
case Some(player) => (player.CharId, amount) //for damageOrder
}
.orElse {
Some((0L, amount)) //for damageOrder
}
//re-combine output list(s)
val leftovers = if (lastValue > 0) {
Seq((lastCharId, lastValue))
} else {
Nil
}
(newDamageEntry.toList ++ damageOrder, leftovers ++ recoveryOrder.slice(rindex, recoveryOrder.size) ++ recoveryOrder.take(rindex).map { case (id, _) => (id, 0) })
}
private[exp] def contributeWithRecoveryActivity(
user: PlayerSource,
wepid: Int,
faction: PlanetSideEmpire.Value,
amount: Int,
time: Long,
damageParticipants: mutable.LongMap[PlayerSource],
recoveryParticipants: mutable.LongMap[ContributionStats],
damageOrder: Seq[(Long, Int)],
recoveryOrder: Seq[(Long, Int)]
): (Seq[(Long, Int)], Seq[(Long, Int)]) = {
contributeWithRecoveryActivity(user, user.CharId, wepid, faction, amount, time, damageParticipants, recoveryParticipants, damageOrder, recoveryOrder)
}
private[exp] def contributeWithRecoveryActivity(
wepid: Int,
faction: PlanetSideEmpire.Value,
amount: Int,
time: Long,
damageParticipants: mutable.LongMap[PlayerSource],
recoveryParticipants: mutable.LongMap[ContributionStats],
damageOrder: Seq[(Long, Int)],
recoveryOrder: Seq[(Long, Int)]
): (Seq[(Long, Int)], Seq[(Long, Int)]) = {
contributeWithRecoveryActivity(PlayerSource.Nobody, charId = 0, wepid, faction, amount, time, damageParticipants, recoveryParticipants, damageOrder, recoveryOrder)
}
private[exp] def contributeWithRecoveryActivity(
user: PlayerSource,
charId: Long,
wepid: Int,
faction: PlanetSideEmpire.Value,
amount: Int,
time: Long,
damageParticipants: mutable.LongMap[PlayerSource],
recoveryParticipants: mutable.LongMap[ContributionStats],
damageOrder: Seq[(Long, Int)],
recoveryOrder: Seq[(Long, Int)]
): (Seq[(Long, Int)], Seq[(Long, Int)]) = {
//mark entries from the ordered damage list to truncate
val damageEntries = damageOrder.iterator
var amtToReduce: Int = amount
var amtToGain: Int = 0
var lastValue: Int = -1
var damageRemoveCount: Int = 0
var damageRemainder: Seq[(Long, Int)] = Nil
//keep reducing previous damage until recovery amount is depleted, or no more damage entries remain, or the last damage entry was depleted already
while (damageEntries.hasNext && amtToReduce > 0 && lastValue != 0) {
val (id, value) = damageEntries.next()
lastValue = value
if (value > 0) {
damageParticipants
.get(id)
.collect {
case player if player.Faction != faction =>
//if previous attacker was an enemy, the recovery counts towards contribution
if (value > amtToReduce) {
damageRemainder = Seq((id, value - amtToReduce))
amtToGain = amtToGain + amtToReduce
amtToReduce = 0
} else {
amtToGain = amtToGain + value
amtToReduce = amtToReduce - value
}
Some(player)
case player =>
//if the previous attacker was friendly fire, the recovery doesn't count towards contribution
if (value > amtToReduce) {
damageRemainder = Seq((id, value - amtToReduce))
amtToReduce = 0
} else {
amtToReduce = amtToReduce - value
}
Some(player)
}
.orElse {
//if we couldn't find an entry, just give the contribution to the user anyway
damageParticipants.put(id, PlayerSource.Nobody)
if (value > amtToReduce) {
damageRemainder = Seq((id, value - amtToReduce))
amtToGain = amtToGain + amtToReduce
amtToReduce = 0
} else {
amtToGain = amtToGain + value
amtToReduce = amtToReduce - value
}
None
}
//keep track of entries whose damage was depleted
damageRemoveCount += 1
}
}
amtToGain = amtToGain + amtToReduce //if early termination, gives leftovers as gain
if (amtToGain > 0) {
val newWeaponStats = WeaponStats(wepid, amtToGain, 1, time, 1f)
//try: add first contribution entry
//then: add accumulation of last weapon entry to contribution entry
//last: add new weapon entry to contribution entry
recoveryParticipants
.getOrElseUpdate(
charId,
ContributionStats(user, Seq(newWeaponStats), amtToGain, amtToGain, 1, time)
) match {
case entry if entry.weapons.size > 1 =>
if (entry.weapons.head.equipment_id == wepid) {
val head = entry.weapons.head
recoveryParticipants.put(
charId,
entry.copy(
weapons = head.copy(amount = head.amount + amtToGain, shots = head.shots + 1, time = time) +: entry.weapons.tail,
amount = entry.amount + amtToGain,
total = entry.total + amtToGain,
shots = entry.shots + 1,
time = time
)
)
} else {
recoveryParticipants.put(
charId,
entry.copy(
weapons = newWeaponStats +: entry.weapons,
amount = entry.amount + amtToGain,
total = entry.total + amtToGain,
shots = entry.shots + 1,
time = time
)
)
}
case _ => ()
//not technically possible
}
}
val newRecoveryEntry = if (amtToGain == 0) {
Seq((0L, amount))
} else if (amtToGain < amount) {
Seq((0L, amount - amtToGain), (charId, amtToGain))
} else {
Seq((charId, amount))
}
(
damageRemainder ++ damageOrder.drop(damageRemoveCount) ++ damageOrder.take(damageRemoveCount).map { case (id, _) => (id, 0) },
newRecoveryEntry ++ recoveryOrder
)
}
private[exp] def contributeWithSupportRecoveryActivity(
users: Seq[PlayerSource],
wepid: Int,
faction: PlanetSideEmpire.Value,
amount: Int,
time: Long,
participants: mutable.LongMap[ContributionStats],
damageOrder: Seq[(Long, Int)],
recoveryOrder: Seq[(Long, Int)]
): (Seq[(Long, Int)], Seq[(Long, Int)]) = {
var outputDamageOrder = damageOrder
var outputRecoveryOrder = recoveryOrder
val damageParticipants: mutable.LongMap[PlayerSource] = mutable.LongMap[PlayerSource]()
users.zip {
val numberOfUsers = users.size
val out = Array.fill(numberOfUsers)(numberOfUsers / amount)
(0 to numberOfUsers % amount).foreach {
out(_) += 1
}
out
}.foreach { case (user, subAmount) =>
val (a, b) = contributeWithRecoveryActivity(user, user.CharId, wepid, faction, subAmount, time, damageParticipants, participants, outputDamageOrder, outputRecoveryOrder)
outputDamageOrder = a
outputRecoveryOrder = b
}
(outputDamageOrder, outputRecoveryOrder)
}
}
//noinspection ScalaUnusedSymbol
private abstract class RecoveryExperienceContributionProcess(
faction : PlanetSideEmpire.Value,
contributions: Map[SourceUniqueness, List[InGameActivity]]
) extends RecoveryExperienceContribution {
protected var damageInOrder: Seq[(Long, Int)] = Seq[(Long, Int)]()
protected var recoveryInOrder: Seq[(Long, Int)] = Seq[(Long, Int)]()
protected val contributionsBy: mutable.LongMap[ContributionStats] = mutable.LongMap[ContributionStats]()
protected val participants: mutable.LongMap[ContributionStats] = mutable.LongMap[ContributionStats]()
protected val damageParticipants: mutable.LongMap[PlayerSource] = mutable.LongMap[PlayerSource]()
def submit(history: List[InGameActivity]): Unit
def output(): mutable.LongMap[ContributionStats] = {
val output = participants.map { a => a }
clear()
output
}
def clear(): Unit = {
damageInOrder = Nil
recoveryInOrder = Nil
contributionsBy.clear()
participants.clear()
damageParticipants.clear()
}
}
private class HealthRecoveryExperienceContributionProcess(
private val faction : PlanetSideEmpire.Value,
private val contributions: Map[SourceUniqueness, List[InGameActivity]]
) extends RecoveryExperienceContributionProcess(faction, contributions) {
def submit(history: List[InGameActivity]): Unit = {
history.foreach {
case d: DamagingActivity if d.health > 0 =>
val (damage, recovery) = RecoveryExperienceContribution.contributeWithDamagingActivity(d, d.health, damageParticipants, participants, damageInOrder, recoveryInOrder)
damageInOrder = damage
recoveryInOrder = recovery
case ht: HealFromTerminal =>
val time = ht.time
val users = KillContributions.contributeWithTerminalActivity(Seq((ht, ht.term, ht.term.hacked)), faction, contributions, contributionsBy)
.collect { case entry: SupportActivityCausedByAnother => entry }
.groupBy(_.user.unique)
.map(_._2.head.user)
.toSeq
val (damage, recovery) = RecoveryExperienceContribution.contributeWithSupportRecoveryActivity(users, ht.term.Definition.ObjectId, faction, ht.amount, time, participants, damageInOrder, recoveryInOrder)
damageInOrder = damage
recoveryInOrder = recovery
case h: HealFromEquipment =>
val (damage, recovery) = RecoveryExperienceContribution.contributeWithRecoveryActivity(h.user, h.equipment_def.ObjectId, faction, h.amount, h.time, damageParticipants, participants, damageInOrder, recoveryInOrder)
damageInOrder = damage
recoveryInOrder = recovery
case h: HealingActivity =>
val (damage, recovery) = RecoveryExperienceContribution.contributeWithRecoveryActivity(wepid = 0, faction, h.amount, h.time, damageParticipants, participants, damageInOrder, recoveryInOrder)
damageInOrder = damage
recoveryInOrder = recovery
case r: RevivingActivity =>
val (damage, recovery) = RecoveryExperienceContribution.contributeWithRecoveryActivity(r.equipment.ObjectId, faction, r.amount, r.time, damageParticipants, participants, damageInOrder, recoveryInOrder)
damageInOrder = damage
recoveryInOrder = recovery
case _ => ()
}
}
}
private class ArmorRecoveryExperienceContributionProcess(
private val faction : PlanetSideEmpire.Value,
private val contributions: Map[SourceUniqueness, List[InGameActivity]]
) extends RecoveryExperienceContributionProcess(faction, contributions) {
def submit(history: List[InGameActivity]): Unit = {
history.foreach {
case d: DamagingActivity if d.amount - d.health > 0 =>
val (damage, recovery) = RecoveryExperienceContribution.contributeWithDamagingActivity(d, d.amount - d.health, damageParticipants, participants, damageInOrder, recoveryInOrder)
damageInOrder = damage
recoveryInOrder = recovery
case rt: RepairFromTerminal =>
val time = rt.time
val users = KillContributions.contributeWithTerminalActivity(Seq((rt, rt.term, rt.term.hacked)), faction, contributions, contributionsBy)
.collect { case entry: SupportActivityCausedByAnother => entry }
.groupBy(_.user.unique)
.map(_._2.head.user)
.toSeq
val (damage, recovery) = RecoveryExperienceContribution.contributeWithSupportRecoveryActivity(users, rt.term.Definition.ObjectId, faction, rt.amount, time, participants, damageInOrder, recoveryInOrder)
damageInOrder = damage
recoveryInOrder = recovery
case r: RepairFromEquipment =>
val (damage, recovery) = RecoveryExperienceContribution.contributeWithRecoveryActivity(r.user, r.equipment_def.ObjectId, faction, r.amount, r.time, damageParticipants, participants, damageInOrder, recoveryInOrder)
damageInOrder = damage
recoveryInOrder = recovery
case r: RepairingActivity =>
val (damage, recovery) = RecoveryExperienceContribution.contributeWithRecoveryActivity(wepid = 0, faction, r.amount, r.time, damageParticipants, participants, damageInOrder, recoveryInOrder)
damageInOrder = damage
recoveryInOrder = recovery
case _ => ()
}
}
}
private class CombinedHealthAndArmorContributionProcess(
private val faction : PlanetSideEmpire.Value,
private val contributions: Map[SourceUniqueness, List[InGameActivity]],
otherSubmissions: Seq[RecoveryExperienceContribution]
) extends RecoveryExperienceContribution {
private val process: Seq[RecoveryExperienceContributionProcess] = Seq(
new HealthRecoveryExperienceContributionProcess(faction, contributions),
new ArmorRecoveryExperienceContributionProcess(faction, contributions)
)
def submit(history: List[InGameActivity]): Unit = {
for (elem <- process ++ otherSubmissions) { elem.submit(history) }
}
def output(): mutable.LongMap[ContributionStats] = {
val output = combineRecoveryContributions(
KillContributions.cullContributorImplements(process.head.output()),
KillContributions.cullContributorImplements(process(1).output())
)
clear()
output
}
def clear(): Unit = {
process.foreach ( _.clear() )
}
private def combineRecoveryContributions(
healthAssists: mutable.LongMap[ContributionStats],
armorAssists: mutable.LongMap[ContributionStats]
): mutable.LongMap[ContributionStats] = {
healthAssists
.map {
case out@(id, healthEntry) =>
armorAssists.get(id) match {
case Some(armorEntry) =>
//healthAssists && armorAssists
(id, healthEntry.copy(weapons = healthEntry.weapons ++ armorEntry.weapons))
case None =>
//healthAssists only
out
}
}
.addAll {
//armorAssists only
val healthKeys = healthAssists.keys.toSeq
armorAssists.filter { case (id, _) => !healthKeys.contains(id) }
}
.map {
case (id, entry) =>
val groupedWeapons = entry.weapons
.groupBy(_.equipment_id)
.map {
case (weaponId, weaponEntries) =>
val specificEntries = weaponEntries.filter(_.equipment_id == weaponId)
val amount = specificEntries.foldLeft(0)(_ + _.amount)
val shots = specificEntries.foldLeft(0)(_ + _.shots)
WeaponStats(weaponId, amount, shots, specificEntries.maxBy(_.time).time, 1f)
}
.toSeq
(id, ContributionStats(
player = entry.player,
weapons = groupedWeapons,
amount = entry.amount + entry.amount,
total = entry.total + entry.total,
shots = groupedWeapons.foldLeft(0)(_ + _.shots),
time = groupedWeapons.maxBy(_.time).time
))
}
}
}

View file

@ -4,14 +4,14 @@ package net.psforever.objects.zones.exp
import net.psforever.objects.sourcing.PlayerSource
sealed trait ItemUseStats {
def equipment_id: Int
def equipment: EquipmentUseContextWrapper
def shots: Int
def time: Long
def contributions: Float
}
private case class WeaponStats(
equipment_id: Int,
equipment: EquipmentUseContextWrapper,
amount: Int,
shots: Int,
time: Long,
@ -19,7 +19,7 @@ private case class WeaponStats(
) extends ItemUseStats
private case class EquipmentStats(
equipment_id: Int,
equipment: EquipmentUseContextWrapper,
shots: Int,
time: Long,
contributions: Float
@ -36,6 +36,6 @@ private[exp] case class ContributionStats(
sealed case class ContributionStatsOutput(
player: PlayerSource,
implements: Seq[Int],
implements: Seq[EquipmentUseContextWrapper],
percentage: Float
)

View file

@ -87,31 +87,11 @@ object Support {
second: ContributionStatsOutput
): ContributionStatsOutput = {
if (first.percentage < second.percentage)
second.copy(
implements = (second.implements ++ first.implements).distinct,
percentage = first.percentage + second.implements.size * multiplier
)
second.copy(implements = (second.implements ++ first.implements).distinct, percentage = first.percentage + second.implements.size * multiplier)
else
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 collectKillAssists(
// victim: SourceEntry,
// history: List[InGameActivity],
// func: (List[InGameActivity], PlanetSideEmpire.Value) => mutable.LongMap[ContributionStats]
// ): mutable.LongMap[ContributionStatsOutput] = {
// val healthAssists = func(history, victim.Faction).filterNot { case (_, kda) => kda.amount <= 0 }
// val qualifiedHealing = healthAssists.values.foldLeft(0)(_ + _.total)
// val healthAssistsOutput = healthAssists.map { case (id, kda) =>
// (id, ContributionStatsOutput(kda.player, kda.weapons.map { _.weapon_id }, kda.amount / qualifiedHealing))
// }
// healthAssistsOutput.remove(victim.CharId)
// healthAssistsOutput
// }
private[exp] def collectHealingSupportAssists(
target: SourceEntry,
time: JodaLocalDateTime,
@ -145,9 +125,12 @@ object Support {
history: List[InGameActivity]
): mutable.LongMap[ContributionStatsOutput] = {
collectSupportContributions(
target, time, history.collect { case repairs: RepairFromEquipment
target,
time,
history.collect { case repairs: RepairFromEquipment
if repairs.amount > 0 && repairs.user.unique != target.unique => (repairs, repairs.equipment_def.ObjectId)
}, mapContributionPointsByPercentage
},
mapContributionPointsByPercentage
)
}
@ -183,7 +166,7 @@ object Support {
if target.CharId != player.CharId && faction == player.Faction =>
//accessed a hacked terminal
termsUsed.put(guid.toLong, Some(terminal))
addTerminalContributionEntry(credit, player, Seq(GlobalDefinitions.remote_electronics_kit.ObjectId), percentage = 1f)
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
@ -193,7 +176,8 @@ object Support {
.History
.collect { case a: RepairFromEquipment =>
val user = a.user
addTerminalContributionEntry(credit, user, Seq(a.equipment_def.ObjectId), percentage = 0.5f)
//TODO might be wrong intermediate
addTerminalContributionEntry(credit, user, Seq(RepairKillAssist(a.equipment_def.ObjectId, target.Definition.ObjectId)), percentage = 0.5f)
}
case _ =>
//what is this?
@ -208,7 +192,7 @@ object Support {
private def addTerminalContributionEntry(
contributions: mutable.LongMap[ContributionStatsOutput],
player: PlayerSource,
implements: Seq[Int],
implements: Seq[EquipmentUseContextWrapper],
percentage: Float
): Unit = {
val charId = player.CharId
@ -216,10 +200,7 @@ object Support {
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
))
contributions.put(charId, entry.copy(implements = (entry.implements ++ implements).distinct, percentage = entry.percentage + 0.05f))
case None =>
contributions.put(charId, ContributionStatsOutput(player, implements, percentage))
}
@ -262,7 +243,7 @@ object Support {
))
case None =>
//the contribution percentage allocated will always be 1.0f and should be overwritten later
contributions.put(charId, ContributionStats(user, Seq(WeaponStats(defaultTool, amount, 1, h.time, 1f)), amount, amount, 1, h.time))
contributions.put(charId, ContributionStats(user, Seq(WeaponStats(NoUse(defaultTool), amount, 1, h.time, 1f)), amount, amount, 1, h.time))
}
}
contributions
@ -354,7 +335,7 @@ object Support {
0.5f
}
}
(charId, ContributionStatsOutput(user, contribution.weapons.map { _.equipment_id }, value))
(charId, ContributionStatsOutput(user, contribution.weapons.map { _.equipment }, value))
}
//noinspection ScalaUnusedSymbol
@ -366,7 +347,7 @@ object Support {
charId: Long,
contribution: ContributionStats,
): (Long, ContributionStatsOutput) = {
(charId, ContributionStatsOutput(contribution.player, contribution.weapons.map { _.equipment_id }, compareList.size.toFloat))
(charId, ContributionStatsOutput(contribution.player, contribution.weapons.map { _.equipment }, compareList.size.toFloat))
}
private[exp] def wasEverAMax(player: PlayerSource, history: Iterable[InGameActivity]): Boolean = {

View file

@ -14,6 +14,10 @@ import net.psforever.util.Database.ctx._
import scala.util.Success
object ToDatabase {
/**
* Insert an entry into the database's `killactivity` table.
* One player just died and some other player is at fault.
*/
def reportKillBy(
killerId: Long,
victimId: Long,
@ -40,6 +44,66 @@ object ToDatabase {
)
}
/**
* Insert an entry into the database's `assistactivity` table.
* One player just died and some other player tried to take credit.
* (They are actually an accomplice.)
*/
def reportKillAssistBy(
avatarId: Long,
victimId: Long,
weaponId: Int,
zoneId: Int,
position: Vector3,
exp: Long
): Unit = {
ctx.run(query[persistence.Assistactivity]
.insert(
_.killerId -> lift(avatarId),
_.victimId -> lift(victimId),
_.weaponId -> lift(weaponId),
_.zoneId -> lift(zoneId),
_.px -> lift((position.x * 1000).toInt),
_.py -> lift((position.y * 1000).toInt),
_.pz -> lift((position.z * 1000).toInt),
_.exp -> lift(exp)
)
)
}
/**
* Insert an entry into the database's `supportactivity` table.
* One player did something for some other player and
* that other player was able to kill a third player.
*/
def reportSupportBy(
user: Long,
target: Long,
exosuit: Int,
interaction: Int,
intermediate: Int,
implement: Int,
experience: Long
): Unit = {
ctx.run(query[persistence.Supportactivity]
.insert(
_.userId -> lift(user),
_.targetId -> lift(target),
_.targetExosuit -> lift(exosuit),
_.interactionType -> lift(interaction),
_.implementType -> lift(implement),
_.intermediateType -> lift(intermediate),
_.exp -> lift(experience)
)
)
}
/**
* Attempt to update the database's `weaponstatsession` table and,
* if no existing entries can be found,
* insert a new entry into the table.
* Shots fired.
*/
def reportToolDischarge(avatarId: Long, stats: EquipmentStat): Unit = {
val result = for {
res <- ctx.run(
@ -69,34 +133,11 @@ object ToDatabase {
}
}
def reportAssistKills(avatarId: Long, weaponId: Int, assists: Int): Unit = {
val result = for {
res <- ctx.run(
query[persistence.Weaponstatsession]
.filter(_.avatarId == lift(avatarId))
.filter(_.weaponId == lift(weaponId))
.update(
_.assists -> lift(assists)
)
)
} yield res
result.onComplete {
case Success(rowCount) if rowCount.longValue > 0 => ()
case _ =>
ctx.run(query[persistence.Weaponstatsession]
.insert(
_.avatarId -> lift(avatarId),
_.weaponId -> lift(weaponId),
_.assists -> lift(assists),
_.shotsFired -> lift(0),
_.shotsLanded -> lift(0),
_.kills -> lift(0),
_.sessionId -> lift(-1L)
)
)
}
}
/**
* Insert an entry into the database's `machinedestroyed` table.
* Just as stated, something that was not a player was destroyed.
* Valid entity types include: vehicles, amenities, and various turrets.
*/
def reportMachineDestruction(
avatarId: Long,
machine: VehicleSource,
@ -108,15 +149,15 @@ object ToDatabase {
import net.psforever.util.Database.ctx
import net.psforever.util.Database.ctx._
val normalFaction = machine.Faction.id
val hackedFaction = hackState.map { _.player.Faction }.getOrElse(normalFaction)
val hackedToFaction = hackState.map { _.player.Faction.id }.getOrElse(normalFaction)
val machinePosition = machine.Position
ctx.run(query[persistence.Machinedestroyedinstance]
ctx.run(query[persistence.Machinedestroyed]
.insert(
_.avatarId -> lift(avatarId),
_.weaponId -> lift(weaponId),
_.machineType -> lift(machine.Definition.ObjectId),
_.machineFaction -> lift(normalFaction),
_.hackedFaction -> lift(hackedFaction),
_.hackedFaction -> lift(hackedToFaction),
_.asCargo -> lift(isCargo),
_.zoneNum -> lift(zoneNumber),
_.px -> lift((machinePosition.x * 1000).toInt),

View file

@ -0,0 +1,84 @@
// Copyright (c) 2023 PSForever
package net.psforever.objects.zones.exp.rec
import net.psforever.objects.sourcing.SourceUniqueness
import net.psforever.objects.vital.{DamagingActivity, InGameActivity, RepairFromEquipment, RepairFromTerminal, RepairingActivity, SupportActivityCausedByAnother}
import net.psforever.objects.zones.exp.KillContributions
import net.psforever.types.PlanetSideEmpire
import scala.collection.mutable
class ArmorRecoveryExperienceContributionProcess(
private val faction : PlanetSideEmpire.Value,
private val contributions: Map[SourceUniqueness, List[InGameActivity]],
private val excludedTargets: mutable.ListBuffer[SourceUniqueness] = mutable.ListBuffer()
) extends RecoveryExperienceContributionProcess(faction, contributions) {
def submit(history: List[InGameActivity]): Unit = {
history.foreach {
case d: DamagingActivity if d.amount - d.health > 0 =>
val (damage, recovery) = RecoveryExperienceContribution.contributeWithDamagingActivity(
d,
d.amount - d.health,
damageParticipants,
participants,
damageInOrder,
recoveryInOrder
)
damageInOrder = damage
recoveryInOrder = recovery
case rt: RepairFromTerminal =>
val time = rt.time
val users = KillContributions.contributeWithTerminalActivity(
Seq((rt, rt.term, rt.term.hacked)),
faction,
contributions,
contributionsBy,
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 =>
val (damage, recovery) = RecoveryExperienceContribution.contributeWithRecoveryActivity(
r.user,
r.equipment_def.ObjectId,
faction,
r.amount,
r.time,
damageParticipants,
participants,
damageInOrder,
recoveryInOrder
)
damageInOrder = damage
recoveryInOrder = recovery
case r: RepairingActivity =>
val (damage, recovery) = RecoveryExperienceContribution.contributeWithRecoveryActivity(
wepid = 0,
faction,
r.amount,
r.time,
damageParticipants,
participants,
damageInOrder,
recoveryInOrder
)
damageInOrder = damage
recoveryInOrder = recovery
case _ => ()
}
}
}

View file

@ -0,0 +1,82 @@
// Copyright (c) 2023 PSForever
package net.psforever.objects.zones.exp.rec
import net.psforever.objects.sourcing.SourceUniqueness
import net.psforever.objects.vital.InGameActivity
import net.psforever.objects.zones.exp.{ContributionStats, KillContributions, WeaponStats}
import net.psforever.types.PlanetSideEmpire
import scala.collection.mutable
class CombinedHealthAndArmorContributionProcess(
private val faction : PlanetSideEmpire.Value,
private val contributions: Map[SourceUniqueness, List[InGameActivity]],
otherSubmissions: Seq[RecoveryExperienceContribution]
) extends RecoveryExperienceContribution {
private val excludedTargets: mutable.ListBuffer[SourceUniqueness] = mutable.ListBuffer()
private val process: Seq[RecoveryExperienceContributionProcess] = Seq(
new HealthRecoveryExperienceContributionProcess(faction, contributions, excludedTargets),
new ArmorRecoveryExperienceContributionProcess(faction, contributions, excludedTargets)
)
def submit(history: List[InGameActivity]): Unit = {
for (elem <- process ++ otherSubmissions) { elem.submit(history) }
}
def output(): mutable.LongMap[ContributionStats] = {
val output = combineRecoveryContributions(
KillContributions.cullContributorImplements(process.head.output()),
KillContributions.cullContributorImplements(process(1).output())
)
clear()
output
}
def clear(): Unit = {
process.foreach ( _.clear() )
}
private def combineRecoveryContributions(
healthAssists: mutable.LongMap[ContributionStats],
armorAssists: mutable.LongMap[ContributionStats]
): mutable.LongMap[ContributionStats] = {
healthAssists
.map {
case out@(id, healthEntry) =>
armorAssists.get(id) match {
case Some(armorEntry) =>
//healthAssists && armorAssists
(id, healthEntry.copy(weapons = healthEntry.weapons ++ armorEntry.weapons))
case None =>
//healthAssists only
out
}
}
.addAll {
//armorAssists only
val healthKeys = healthAssists.keys.toSeq
armorAssists.filter { case (id, _) => !healthKeys.contains(id) }
}
.map {
case (id, entry) =>
val groupedWeapons = entry.weapons
.groupBy(_.equipment)
.map {
case (weaponId, weaponEntries) =>
val specificEntries = weaponEntries.filter(_.equipment == weaponId)
val amount = specificEntries.foldLeft(0)(_ + _.amount)
val shots = specificEntries.foldLeft(0)(_ + _.shots)
WeaponStats(weaponId, amount, shots, specificEntries.maxBy(_.time).time, 1f)
}
.toSeq
(id, ContributionStats(
player = entry.player,
weapons = groupedWeapons,
amount = entry.amount + entry.amount,
total = entry.total + entry.total,
shots = groupedWeapons.foldLeft(0)(_ + _.shots),
time = groupedWeapons.maxBy(_.time).time
))
}
}
}

View file

@ -0,0 +1,97 @@
// Copyright (c) 2023 PSForever
package net.psforever.objects.zones.exp.rec
import net.psforever.objects.sourcing.SourceUniqueness
import net.psforever.objects.vital.{DamagingActivity, HealFromEquipment, HealFromTerminal, HealingActivity, InGameActivity, RevivingActivity, SupportActivityCausedByAnother}
import net.psforever.objects.zones.exp.KillContributions
import net.psforever.types.PlanetSideEmpire
import scala.collection.mutable
private class HealthRecoveryExperienceContributionProcess(
private val faction : PlanetSideEmpire.Value,
private val contributions: Map[SourceUniqueness, List[InGameActivity]],
private val excludedTargets: mutable.ListBuffer[SourceUniqueness] = mutable.ListBuffer()
) extends RecoveryExperienceContributionProcess(faction, contributions) {
def submit(history: List[InGameActivity]): Unit = {
history.foreach {
case d: DamagingActivity if d.health > 0 =>
val (damage, recovery) = RecoveryExperienceContribution.contributeWithDamagingActivity(
d,
d.health,
damageParticipants,
participants,
damageInOrder,
recoveryInOrder
)
damageInOrder = damage
recoveryInOrder = recovery
case ht: HealFromTerminal =>
val time = ht.time
val users = KillContributions.contributeWithTerminalActivity(
Seq((ht, ht.term, ht.term.hacked)),
faction,
contributions,
contributionsBy,
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 =>
val (damage, recovery) = RecoveryExperienceContribution.contributeWithRecoveryActivity(
h.user,
h.equipment_def.ObjectId,
faction,
h.amount,
h.time,
damageParticipants,
participants,
damageInOrder,
recoveryInOrder
)
damageInOrder = damage
recoveryInOrder = recovery
case h: HealingActivity =>
val (damage, recovery) = RecoveryExperienceContribution.contributeWithRecoveryActivity(
wepid = 0,
faction,
h.amount,
h.time,
damageParticipants,
participants,
damageInOrder,
recoveryInOrder
)
damageInOrder = damage
recoveryInOrder = recovery
case r: RevivingActivity =>
val (damage, recovery) = RecoveryExperienceContribution.contributeWithRecoveryActivity(
r.equipment.ObjectId,
faction,
r.amount,
r.time,
damageParticipants,
participants,
damageInOrder,
recoveryInOrder
)
damageInOrder = damage
recoveryInOrder = recovery
case _ => ()
}
}
}

View file

@ -0,0 +1,59 @@
// Copyright (c) 2023 PSForever
package net.psforever.objects.zones.exp.rec
import net.psforever.objects.sourcing.SourceUniqueness
import net.psforever.objects.vital.{DamagingActivity, InGameActivity, RepairFromEquipment, RepairingActivity}
import net.psforever.types.PlanetSideEmpire
import scala.collection.mutable
//noinspection ScalaUnusedSymbol
class MachineRecoveryExperienceContributionProcess(
private val faction : PlanetSideEmpire.Value,
private val contributions: Map[SourceUniqueness, List[InGameActivity]],
private val excludedTargets: mutable.ListBuffer[SourceUniqueness] = mutable.ListBuffer()
) extends RecoveryExperienceContributionProcess(faction, contributions) {
def submit(history: List[InGameActivity]): Unit = {
history.foreach {
case d: DamagingActivity if d.amount - d.health > 0 =>
val (damage, recovery) = RecoveryExperienceContribution.contributeWithDamagingActivity(
d,
d.amount - d.health,
damageParticipants,
participants,
damageInOrder,
recoveryInOrder
)
damageInOrder = damage
recoveryInOrder = recovery
case r: RepairFromEquipment =>
val (damage, recovery) = RecoveryExperienceContribution.contributeWithRecoveryActivity(
r.user,
r.equipment_def.ObjectId,
faction,
r.amount,
r.time,
damageParticipants,
participants,
damageInOrder,
recoveryInOrder
)
damageInOrder = damage
recoveryInOrder = recovery
case r: RepairingActivity =>
val (damage, recovery) = RecoveryExperienceContribution.contributeWithRecoveryActivity(
wepid = 0,
faction,
r.amount,
r.time,
damageParticipants,
participants,
damageInOrder,
recoveryInOrder
)
damageInOrder = damage
recoveryInOrder = recovery
case _ => ()
}
}
}

View file

@ -0,0 +1,280 @@
// Copyright (c) 2023 PSForever
package net.psforever.objects.zones.exp.rec
import net.psforever.objects.sourcing.PlayerSource
import net.psforever.objects.vital._
import net.psforever.objects.vital.interaction.{Adversarial, DamageResult}
import net.psforever.objects.zones.exp.{ContributionStats, HealKillAssist, WeaponStats}
import net.psforever.types.PlanetSideEmpire
import scala.collection.mutable
trait RecoveryExperienceContribution {
def submit(history: List[InGameActivity]): Unit
def output(): mutable.LongMap[ContributionStats]
def clear(): Unit
}
object RecoveryExperienceContribution {
private[exp] def contributeWithDamagingActivity(
activity: DamagingActivity,
amount: Int,
damageParticipants: mutable.LongMap[PlayerSource],
recoveryParticipants: mutable.LongMap[ContributionStats],
damageOrder: Seq[(Long, Int)],
recoveryOrder: Seq[(Long, Int)]
): (Seq[(Long, Int)], Seq[(Long, Int)]) = {
//mark entries from the ordered recovery list to truncate
val data: DamageResult = activity.data
val time: Long = activity.time
var lastCharId: Long = 0L
var lastValue: Int = 0
var ramt: Int = amount
var rindex: Int = 0
val riter = recoveryOrder.iterator
while (riter.hasNext && ramt > 0) {
val (id, value) = riter.next()
if (value > 0) {
/*
if the amount on the previous recovery node is positive, reduce it by the damage value for that user's last used equipment
keep traversing recovery nodes, and lobbing them off, until the recovery amount is zero
if the user can not be found having an entry, skip the update but lob off the recovery progress node all the same
if the amount is zero, do not check any further recovery progress nodes
*/
recoveryParticipants
.get(id)
.foreach { entry =>
val weapons = entry.weapons
lastCharId = id
lastValue = value
if (value > ramt) {
//take from the value on the last-used equipment, at the front of the list
recoveryParticipants.put(
id,
entry.copy(
weapons = weapons.head.copy(amount = math.max(0, weapons.head.amount - ramt), time = time) +: weapons.tail,
amount = math.max(0, entry.amount - ramt),
time = time
)
)
ramt = 0
lastValue = lastValue - value
} else {
//take from the value on the last-used equipment, at the front of the list
//move that entry to the end of the list
recoveryParticipants.put(
id,
entry.copy(
weapons = weapons.tail :+ weapons.head.copy(amount = 0, time = time),
amount = math.max(0, entry.amount - ramt),
time = time
)
)
ramt = ramt - value
rindex += 1
lastValue = 0
}
}
rindex += 1
}
}
//damage order and damage contribution entry
val newDamageEntry = data
.adversarial
.collect { case Adversarial(p: PlayerSource, _, _) => (p, damageParticipants.get(p.CharId)) }
.collect {
case (player, Some(PlayerSource.Nobody)) =>
damageParticipants.put(player.CharId, player)
Some(player)
case (player, Some(_)) =>
damageParticipants.getOrElseUpdate(player.CharId, player)
Some(player)
}
.collect {
case Some(player) => (player.CharId, amount) //for damageOrder
}
.orElse {
Some((0L, amount)) //for damageOrder
}
//re-combine output list(s)
val leftovers = if (lastValue > 0) {
Seq((lastCharId, lastValue))
} else {
Nil
}
(newDamageEntry.toList ++ damageOrder, leftovers ++ recoveryOrder.slice(rindex, recoveryOrder.size) ++ recoveryOrder.take(rindex).map { case (id, _) => (id, 0) })
}
private[exp] def contributeWithRecoveryActivity(
user: PlayerSource,
wepid: Int,
faction: PlanetSideEmpire.Value,
amount: Int,
time: Long,
damageParticipants: mutable.LongMap[PlayerSource],
recoveryParticipants: mutable.LongMap[ContributionStats],
damageOrder: Seq[(Long, Int)],
recoveryOrder: Seq[(Long, Int)]
): (Seq[(Long, Int)], Seq[(Long, Int)]) = {
contributeWithRecoveryActivity(user, user.CharId, wepid, faction, amount, time, damageParticipants, recoveryParticipants, damageOrder, recoveryOrder)
}
private[exp] def contributeWithRecoveryActivity(
wepid: Int,
faction: PlanetSideEmpire.Value,
amount: Int,
time: Long,
damageParticipants: mutable.LongMap[PlayerSource],
recoveryParticipants: mutable.LongMap[ContributionStats],
damageOrder: Seq[(Long, Int)],
recoveryOrder: Seq[(Long, Int)]
): (Seq[(Long, Int)], Seq[(Long, Int)]) = {
contributeWithRecoveryActivity(PlayerSource.Nobody, charId = 0, wepid, faction, amount, time, damageParticipants, recoveryParticipants, damageOrder, recoveryOrder)
}
private[exp] def contributeWithRecoveryActivity(
user: PlayerSource,
charId: Long,
wepid: Int,
faction: PlanetSideEmpire.Value,
amount: Int,
time: Long,
damageParticipants: mutable.LongMap[PlayerSource],
recoveryParticipants: mutable.LongMap[ContributionStats],
damageOrder: Seq[(Long, Int)],
recoveryOrder: Seq[(Long, Int)]
): (Seq[(Long, Int)], Seq[(Long, Int)]) = {
//mark entries from the ordered damage list to truncate
val damageEntries = damageOrder.iterator
var amtToReduce: Int = amount
var amtToGain: Int = 0
var lastValue: Int = -1
var damageRemoveCount: Int = 0
var damageRemainder: Seq[(Long, Int)] = Nil
//keep reducing previous damage until recovery amount is depleted, or no more damage entries remain, or the last damage entry was depleted already
while (damageEntries.hasNext && amtToReduce > 0 && lastValue != 0) {
val (id, value) = damageEntries.next()
lastValue = value
if (value > 0) {
damageParticipants
.get(id)
.collect {
case player if player.Faction != faction =>
//if previous attacker was an enemy, the recovery counts towards contribution
if (value > amtToReduce) {
damageRemainder = Seq((id, value - amtToReduce))
amtToGain = amtToGain + amtToReduce
amtToReduce = 0
} else {
amtToGain = amtToGain + value
amtToReduce = amtToReduce - value
}
Some(player)
case player =>
//if the previous attacker was friendly fire, the recovery doesn't count towards contribution
if (value > amtToReduce) {
damageRemainder = Seq((id, value - amtToReduce))
amtToReduce = 0
} else {
amtToReduce = amtToReduce - value
}
Some(player)
}
.orElse {
//if we couldn't find an entry, just give the contribution to the user anyway
damageParticipants.put(id, user)
if (value > amtToReduce) {
damageRemainder = Seq((id, value - amtToReduce))
amtToGain = amtToGain + amtToReduce
amtToReduce = 0
} else {
amtToGain = amtToGain + value
amtToReduce = amtToReduce - value
}
None
}
//keep track of entries whose damage was depleted
damageRemoveCount += 1
}
}
amtToGain = amtToGain + amtToReduce //if early termination, gives leftovers as gain
if (amtToGain > 0) {
val newWeaponStats = WeaponStats(HealKillAssist(wepid), amtToGain, 1, time, 1f)
//try: add first contribution entry
//then: add accumulation of last weapon entry to contribution entry
//last: add new weapon entry to contribution entry
recoveryParticipants
.getOrElseUpdate(
charId,
ContributionStats(user, Seq(newWeaponStats), amtToGain, amtToGain, 1, time)
) match {
case entry if entry.weapons.size > 1 =>
if (entry.weapons.head.equipment.equipment == wepid) {
val head = entry.weapons.head
recoveryParticipants.put(
charId,
entry.copy(
weapons = head.copy(amount = head.amount + amtToGain, shots = head.shots + 1, time = time) +: entry.weapons.tail,
amount = entry.amount + amtToGain,
total = entry.total + amtToGain,
shots = entry.shots + 1,
time = time
)
)
} else {
recoveryParticipants.put(
charId,
entry.copy(
weapons = newWeaponStats +: entry.weapons,
amount = entry.amount + amtToGain,
total = entry.total + amtToGain,
shots = entry.shots + 1,
time = time
)
)
}
case _ => ()
//not technically possible
}
}
val newRecoveryEntry = if (amtToGain == 0) {
Seq((0L, amount))
} else if (amtToGain < amount) {
Seq((0L, amount - amtToGain), (charId, amtToGain))
} else {
Seq((charId, amount))
}
(
damageRemainder ++ damageOrder.drop(damageRemoveCount) ++ damageOrder.take(damageRemoveCount).map { case (id, _) => (id, 0) },
newRecoveryEntry ++ recoveryOrder
)
}
private[exp] def contributeWithSupportRecoveryActivity(
users: Seq[PlayerSource],
wepid: Int,
faction: PlanetSideEmpire.Value,
amount: Int,
time: Long,
participants: mutable.LongMap[ContributionStats],
damageOrder: Seq[(Long, Int)],
recoveryOrder: Seq[(Long, Int)]
): (Seq[(Long, Int)], Seq[(Long, Int)]) = {
var outputDamageOrder = damageOrder
var outputRecoveryOrder = recoveryOrder
val damageParticipants: mutable.LongMap[PlayerSource] = mutable.LongMap[PlayerSource]()
users.zip {
val numberOfUsers = users.size
val out = Array.fill(numberOfUsers)(numberOfUsers / amount)
(0 to numberOfUsers % amount).foreach {
out(_) += 1
}
out
}.foreach { case (user, subAmount) =>
val (a, b) = contributeWithRecoveryActivity(user, user.CharId, wepid, faction, subAmount, time, damageParticipants, participants, outputDamageOrder, outputRecoveryOrder)
outputDamageOrder = a
outputRecoveryOrder = b
}
(outputDamageOrder, outputRecoveryOrder)
}
}

View file

@ -0,0 +1,37 @@
// Copyright (c) 2023 PSForever
package net.psforever.objects.zones.exp.rec
import net.psforever.objects.sourcing.{PlayerSource, SourceUniqueness}
import net.psforever.objects.vital.InGameActivity
import net.psforever.objects.zones.exp.ContributionStats
import net.psforever.types.PlanetSideEmpire
import scala.collection.mutable
//noinspection ScalaUnusedSymbol
abstract class RecoveryExperienceContributionProcess(
faction : PlanetSideEmpire.Value,
contributions: Map[SourceUniqueness, List[InGameActivity]]
) extends RecoveryExperienceContribution {
protected var damageInOrder: Seq[(Long, Int)] = Seq[(Long, Int)]()
protected var recoveryInOrder: Seq[(Long, Int)] = Seq[(Long, Int)]()
protected val contributionsBy: mutable.LongMap[ContributionStats] = mutable.LongMap[ContributionStats]()
protected val participants: mutable.LongMap[ContributionStats] = mutable.LongMap[ContributionStats]()
protected val damageParticipants: mutable.LongMap[PlayerSource] = mutable.LongMap[PlayerSource]()
def submit(history: List[InGameActivity]): Unit
def output(): mutable.LongMap[ContributionStats] = {
val output = participants.map { a => a }
clear()
output
}
def clear(): Unit = {
damageInOrder = Nil
recoveryInOrder = Nil
contributionsBy.clear()
participants.clear()
damageParticipants.clear()
}
}

View file

@ -0,0 +1,16 @@
package net.psforever.persistence
import org.joda.time.LocalDateTime
case class Assistactivity(
index: Int,
killerId: Long,
victimId: Long,
weaponId: Int,
zoneId: Int,
px: Int, //Position.x * 1000
py: Int, //Position.y * 1000
pz: Int, //Position.z * 1000
exp: Long,
timestamp: LocalDateTime = LocalDateTime.now()
)

View file

@ -1,6 +1,8 @@
// Copyright (c) 2022 PSForever
package net.psforever.persistence
import org.joda.time.LocalDateTime
case class Killactivity(
index: Int,
killerId: Long,
@ -12,5 +14,6 @@ case class Killactivity(
px: Int, //Position.x * 1000
py: Int, //Position.y * 1000
pz: Int, //Position.z * 1000
exp: Long
exp: Long,
timestamp: LocalDateTime = LocalDateTime.now()
)

View file

@ -3,7 +3,7 @@ package net.psforever.persistence
import org.joda.time.LocalDateTime
case class Machinedestroyedinstance(
case class Machinedestroyed(
index: Int,
avatarId: Long,
weaponId: Int,

View file

@ -0,0 +1,16 @@
// Copyright (c) 2022 PSForever
package net.psforever.persistence
import org.joda.time.LocalDateTime
case class Supportactivity(
index: Int,
userId: Long,
targetId: Long,
targetExosuit: Int,
interactionType: Int,
implementType: Int,
intermediateType: Int,
exp: Long,
timestamp: LocalDateTime = LocalDateTime.now()
)

View file

@ -64,16 +64,16 @@ object StatisticalCategory extends IntEnum[StatisticalCategory] {
Seq(Phantasm, ImplantTerminalMech, Droppod, SpitfireAA, SpitfireCloaked, SpitfireTurret, TankTraps) ++
driverOnlyVehicles ++ gunnerVehicles ++ mannedTurretElements ++ exosuitElements,
Seq(
// Chaingun12mm, Chaingun15mm, Cannon20mm, Deliverer20mm, DropshipL20mm, Cannon75mm, Lightning75mm, AdvancedMissileLauncherT, AMS, AnniversaryGun, AnniversaryGunA, AnniversaryGunB, ANT, Sunderer, ApcBallGunL, ApcBallGunR, ApcTr, ApcNc, ApcVs, ApcWeaponSystemA, ApcWeaponSystemB, ApcWeaponSystemC, ApcWeaponSystemCNc, ApcWeaponSystemCTr, ApcWeaponSystemCVs, ApcWeaponSystemD, ApcWeaponSystemDNc, ApcWeaponSystemDTr, ApcWeaponSystemDVs, Aphelion, AphelionArmorSiphon, AphelionFlight, AphelionGunner, AphelionImmolationCannon, AphelionLaser, AphelionNtuSiphon, AphelionPlasmaCloud, AphelionPlasmaRocketPod,
// AphelionPpa, AphelionStarfire, AuroraWeaponSystemA, AuroraWeaponSystemB, Battlewagon, BattlewagonWeaponSystemA, BattlewagonWeaponSystemB, BattlewagonWeaponSystemC, BattlewagonWeaponSystemD, Infantry, Raider, Beamer, BoltDriver, Boomer, Chainblade, ChaingunP, Colossus, ColossusArmorSiphon, ColossusBurster, ColossusChaingun, ColossusClusterBombPod, ColossusDual100mmCannons, ColossusFlight,
// ColossusGunner, ColossusNtuSiphon, ColossusTankCannon, Cycler, CyclerV2, CyclerV3, CyclerV4, Dropship, DropshipRearTurret, Dynomite, EnergyGunNc, EnergyGunTr, EnergyGunVs, Flail, FlailWeapon, Flamethrower,
// Flechette, FluxCannonThresher, Fluxpod, Forceblade, FragGrenade, Fury, FragmentationGrenade, FuryWeaponSystemA, GalaxyGunship, GalaxyGunshipCannon, GalaxyGunshipGun, GalaxyGunshipTailgun, Gauss, GaussCannon, GrenadeLauncherMarauder, HeMine, HeavyRailBeamMagrider, HeavySniper, Hellfire,
// Hunterseeker, Ilc9, Isp, JammerGrenade, Katana, Knife, Lancer, Lasher, Liberator, Liberator25mmCannon, LiberatorBombBay, LiberatorWeaponSystem, Lightgunship, LightgunshipWeapon20mm, LightgunshipWeaponRocket, LightgunshipWeaponSystem, Lightning, LightningWeaponSystem, Lodestar, Maelstrom, Magcutter, Magrider, PhalanxTurret,
// MedicalApplicator, MediumTransport, MediumTransportWeaponSystemA, MediumTransportWeaponSystemB, MineSweeper, MiniChaingun, Mosquito, NchevFalcon, NchevScattercannon, NchevSparrow, Oicw,
// OrbitalStrikeBig, OrbitalStrikeSmall, ParticleBeamMagrider, PelletGun, Peregrine, PeregrineArmorSiphon, PeregrineDualMachineGun, PeregrineDualRocketPods, PeregrineFlight, PeregrineGunner, PeregrineMechhammer, PeregrineNtuSiphon, PeregrineParticleCannon, PeregrineSparrow, PhalanxAvcombo, PhalanxFlakcombo, PhalanxSglHevgatcan, Phantasm, Phantasm12mmMachinegun, Phoenix, PlasmaGrenade, Prowler, ProwlerWeaponSystemA,
// ProwlerWeaponSystemB, Pulsar, PulsedParticleAccelerator, Punisher, QuadAssault, QuadAssaultWeaponSystem, QuadStealth, RShotgun, Radiator, Repeater, Rocklet, RotaryChaingunMosquito, Router, RouterTelepadDeployable, Scythe, SixShooter, Skyguard, SkyguardWeaponSystem,
// Spiker, SpitfireAA, SpitfireCloaked, SpitfireTurret, Striker, Suppressor, Switchblade, ThreeManHeavyBuggy, Thumper, Thunderer, ThundererWeaponSystemA, ThundererWeaponSystemB, TrhevBurster, TrhevDualcycler, TrhevPounder, TwoManAssaultBuggy, TwoManHeavyBuggy,
// TwoManHoverBuggy, Vanguard, VanguardWeapon150mm, VanguardWeapon20mm, VanguardWeaponSystem, VanuModule, VanuSentryTurretWeapon, VanuModuleBeam, VshevComet, VshevQuasar, VshevStarfire, Vulture, VultureBombBay, VultureNoseWeaponSystem, VultureTailCannon, Wasp, WaspWeaponSystem, Winchester
Chaingun12mm, Chaingun15mm, Cannon20mm, Deliverer20mm, DropshipL20mm, Cannon75mm, Lightning75mm, AdvancedMissileLauncherT, AMS, AnniversaryGun, AnniversaryGunA, AnniversaryGunB, ANT, Sunderer, ApcBallGunL, ApcBallGunR, ApcTr, ApcNc, ApcVs, ApcWeaponSystemA, ApcWeaponSystemB, ApcWeaponSystemC, ApcWeaponSystemCNc, ApcWeaponSystemCTr, ApcWeaponSystemCVs, ApcWeaponSystemD, ApcWeaponSystemDNc, ApcWeaponSystemDTr, ApcWeaponSystemDVs, Aphelion, AphelionArmorSiphon, AphelionFlight, AphelionGunner, AphelionImmolationCannon, AphelionLaser, AphelionNtuSiphon, AphelionPlasmaCloud, AphelionPlasmaRocketPod,
AphelionPpa, AphelionStarfire, AuroraWeaponSystemA, AuroraWeaponSystemB, Battlewagon, BattlewagonWeaponSystemA, BattlewagonWeaponSystemB, BattlewagonWeaponSystemC, BattlewagonWeaponSystemD, Infantry, Raider, Beamer, BoltDriver, Boomer, Chainblade, ChaingunP, Colossus, ColossusArmorSiphon, ColossusBurster, ColossusChaingun, ColossusClusterBombPod, ColossusDual100mmCannons, ColossusFlight,
ColossusGunner, ColossusNtuSiphon, ColossusTankCannon, Cycler, CyclerV2, CyclerV3, CyclerV4, Dropship, DropshipRearTurret, Dynomite, EnergyGunNc, EnergyGunTr, EnergyGunVs, Flail, FlailWeapon, Flamethrower,
Flechette, FluxCannonThresher, Fluxpod, Forceblade, FragGrenade, Fury, FragmentationGrenade, FuryWeaponSystemA, GalaxyGunship, GalaxyGunshipCannon, GalaxyGunshipGun, GalaxyGunshipTailgun, Gauss, GaussCannon, GrenadeLauncherMarauder, HeMine, HeavyRailBeamMagrider, HeavySniper, Hellfire,
Hunterseeker, Ilc9, Isp, JammerGrenade, Katana, Knife, Lancer, Lasher, Liberator, Liberator25mmCannon, LiberatorBombBay, LiberatorWeaponSystem, Lightgunship, LightgunshipWeapon20mm, LightgunshipWeaponRocket, LightgunshipWeaponSystem, Lightning, LightningWeaponSystem, Lodestar, Maelstrom, Magcutter, Magrider, PhalanxTurret,
MedicalApplicator, MediumTransport, MediumTransportWeaponSystemA, MediumTransportWeaponSystemB, MineSweeper, MiniChaingun, Mosquito, NchevFalcon, NchevScattercannon, NchevSparrow, Oicw,
OrbitalStrikeBig, OrbitalStrikeSmall, ParticleBeamMagrider, PelletGun, Peregrine, PeregrineArmorSiphon, PeregrineDualMachineGun, PeregrineDualRocketPods, PeregrineFlight, PeregrineGunner, PeregrineMechhammer, PeregrineNtuSiphon, PeregrineParticleCannon, PeregrineSparrow, PhalanxAvcombo, PhalanxFlakcombo, PhalanxSglHevgatcan, Phantasm, Phantasm12mmMachinegun, Phoenix, PlasmaGrenade, Prowler, ProwlerWeaponSystemA,
ProwlerWeaponSystemB, Pulsar, PulsedParticleAccelerator, Punisher, QuadAssault, QuadAssaultWeaponSystem, QuadStealth, RShotgun, Radiator, Repeater, Rocklet, RotaryChaingunMosquito, Router, RouterTelepadDeployable, Scythe, SixShooter, Skyguard, SkyguardWeaponSystem,
Spiker, SpitfireAA, SpitfireCloaked, SpitfireTurret, Striker, Suppressor, Switchblade, ThreeManHeavyBuggy, Thumper, Thunderer, ThundererWeaponSystemA, ThundererWeaponSystemB, TrhevBurster, TrhevDualcycler, TrhevPounder, TwoManAssaultBuggy, TwoManHeavyBuggy,
TwoManHoverBuggy, Vanguard, VanguardWeapon150mm, VanguardWeapon20mm, VanguardWeaponSystem, VanuModule, VanuSentryTurretWeapon, VanuModuleBeam, VshevComet, VshevQuasar, VshevStarfire, Vulture, VultureBombBay, VultureNoseWeaponSystem, VultureTailCannon, Wasp, WaspWeaponSystem, Winchester
),
Seq(Facilities, Redoubt, Tower, VanuControlPoint, VanuVehicleStation),
exosuitElements,