mirror of
https://github.com/psforever/PSF-LoginServer.git
synced 2026-07-16 08:55:18 +00:00
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:
parent
c869cdc8a0
commit
d6a37e9421
26 changed files with 1177 additions and 688 deletions
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 =>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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 _ => ()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 _ => ()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 _ => ()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
)
|
||||
|
|
@ -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()
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
@ -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()
|
||||
)
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue