diff --git a/src/main/resources/application.conf b/src/main/resources/application.conf
index 5d444811e..806a23ff1 100644
--- a/src/main/resources/application.conf
+++ b/src/main/resources/application.conf
@@ -408,8 +408,14 @@ game {
base = 25
}
{
- name = "router"
+ name = "router-driver"
base = 15
+ shots-multiplier = 1.0
+ }
+ {
+ name = "telepad-use"
+ base = 20
+ shots-multiplier = 1.0
}
{
name = "hotdrop"
diff --git a/src/main/scala/net/psforever/actors/session/support/GeneralOperations.scala b/src/main/scala/net/psforever/actors/session/support/GeneralOperations.scala
index 2d0532395..e39619e13 100644
--- a/src/main/scala/net/psforever/actors/session/support/GeneralOperations.scala
+++ b/src/main/scala/net/psforever/actors/session/support/GeneralOperations.scala
@@ -12,7 +12,7 @@ import net.psforever.objects.serverobject.terminals.capture.CaptureTerminal
import net.psforever.objects.serverobject.terminals.{MatrixTerminalDefinition, Terminal}
import net.psforever.objects.serverobject.tube.SpawnTube
import net.psforever.objects.serverobject.turret.FacilityTurret
-import net.psforever.objects.sourcing.{PlayerSource, VehicleSource}
+import net.psforever.objects.sourcing.{DeployableSource, PlayerSource, VehicleSource}
import net.psforever.objects.vehicles.Utility.InternalTelepad
import net.psforever.objects.zones.blockmap.BlockMapEntity
import net.psforever.services.RemoverActor
@@ -1372,11 +1372,8 @@ class GeneralOperations(
continent.id,
LocalAction.RouterTelepadTransport(pguid, pguid, sguid, dguid)
)
- val vSource = VehicleSource(router)
- val zoneNumber = continent.Number
- player.LogActivity(VehicleMountActivity(vSource, PlayerSource(player), zoneNumber))
player.Position = dest.Position
- player.LogActivity(VehicleDismountActivity(vSource, PlayerSource(player), zoneNumber))
+ player.LogActivity(TelepadUseActivity(VehicleSource(router), DeployableSource(remoteTelepad), PlayerSource(player)))
} else {
log.warn(s"UseRouterTelepadSystem: ${player.Name} can not teleport")
}
diff --git a/src/main/scala/net/psforever/objects/vital/InGameHistory.scala b/src/main/scala/net/psforever/objects/vital/InGameHistory.scala
index 359cfe2f8..70f8b4193 100644
--- a/src/main/scala/net/psforever/objects/vital/InGameHistory.scala
+++ b/src/main/scala/net/psforever/objects/vital/InGameHistory.scala
@@ -4,7 +4,7 @@ package net.psforever.objects.vital
import net.psforever.objects.PlanetSideGameObject
import net.psforever.objects.definition.{EquipmentDefinition, KitDefinition, ToolDefinition}
import net.psforever.objects.serverobject.affinity.FactionAffinity
-import net.psforever.objects.sourcing.{AmenitySource, PlayerSource, SourceEntry, SourceUniqueness, SourceWithHealthEntry, VehicleSource}
+import net.psforever.objects.sourcing.{AmenitySource, DeployableSource, PlayerSource, SourceEntry, SourceUniqueness, SourceWithHealthEntry, VehicleSource}
import net.psforever.objects.vital.environment.EnvironmentReason
import net.psforever.objects.vital.etc.{ExplodingEntityReason, PainboxReason, SuicideReason}
import net.psforever.objects.vital.interaction.{DamageInteraction, DamageResult}
@@ -82,6 +82,9 @@ final case class ShieldCharge(amount: Int, cause: Option[SourceEntry])
final case class TerminalUsedActivity(terminal: AmenitySource, transaction: TransactionType.Value)
extends GeneralActivity
+final case class TelepadUseActivity(router: VehicleSource, telepad: DeployableSource, player: PlayerSource)
+ extends GeneralActivity
+
sealed trait VehicleMountChange extends GeneralActivity {
def vehicle: VehicleSource
def zoneNumber: Int
@@ -248,7 +251,7 @@ trait InGameHistory {
*/
def LogActivity(action: Option[InGameActivity]): List[InGameActivity] = {
action match {
- case Some(act: VehicleDismountActivity) =>
+ case Some(act: VehicleDismountActivity) if act.pairedEvent.isEmpty =>
history
.findLast(_.isInstanceOf[VehicleMountActivity])
.collect {
@@ -259,6 +262,8 @@ trait InGameHistory {
history = history :+ act
None
}
+ case Some(act: VehicleDismountActivity) =>
+ history = history :+ act
case Some(act: VehicleCargoDismountActivity) =>
history
.findLast(_.isInstanceOf[VehicleCargoMountActivity])
diff --git a/src/main/scala/net/psforever/objects/zones/exp/KillContributions.scala b/src/main/scala/net/psforever/objects/zones/exp/KillContributions.scala
index 410492f90..5a58f8b82 100644
--- a/src/main/scala/net/psforever/objects/zones/exp/KillContributions.scala
+++ b/src/main/scala/net/psforever/objects/zones/exp/KillContributions.scala
@@ -5,7 +5,7 @@ import akka.actor.ActorRef
import net.psforever.objects.GlobalDefinitions
import net.psforever.objects.avatar.scoring.{Kill, SupportActivity}
import net.psforever.objects.sourcing.{BuildingSource, PlayerSource, SourceEntry, SourceUniqueness, TurretSource, VehicleSource}
-import net.psforever.objects.vital.{Contribution, HealFromTerminal, InGameActivity, RepairFromTerminal, RevivingActivity, TerminalUsedActivity, VehicleCargoDismountActivity, VehicleCargoMountActivity, VehicleDismountActivity, VehicleMountActivity}
+import net.psforever.objects.vital.{Contribution, HealFromTerminal, InGameActivity, RepairFromTerminal, RevivingActivity, TelepadUseActivity, TerminalUsedActivity, VehicleCargoDismountActivity, VehicleCargoMountActivity, VehicleDismountActivity, VehicleMountActivity}
import net.psforever.objects.vital.projectile.ProjectileReason
import net.psforever.objects.zones.exp.rec.{CombinedHealthAndArmorContributionProcess, MachineRecoveryExperienceContributionProcess}
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
@@ -55,6 +55,11 @@ object KillContributions {
/** cached for empty collection returns; please do not add anything to it */
private val emptyMap: mutable.LongMap[ContributionStats] = mutable.LongMap.empty[ContributionStats]
+ /** cached for use with telepad deployable activities, from the perspective of the router */
+ private val routerKillAssist = RouterKillAssist(GlobalDefinitions.router.ObjectId)
+ /** cached for use with telepad deployable activities */
+ private val routerTelepadKillAssist = RouterKillAssist(GlobalDefinitions.router_telepad_deployable.ObjectId)
+
/**
* Primary landing point for calculating the rewards given for helping one player kill another player.
* Rewards in the form of "support experience points" are given
@@ -263,6 +268,7 @@ object KillContributions {
): mutable.LongMap[ContributionStats] = {
contributeWithRevivalActivity(history, existingParticipants)
contributeWithTerminalActivity(history, faction, contributions, excludedTargets, existingParticipants)
+ contributeWithRouterTelepadActivity(kill, history, faction, contributions, excludedTargets, existingParticipants)
contributeWithVehicleTransportActivity(kill, history, faction, contributions, excludedTargets, existingParticipants)
contributeWithVehicleCargoTransportActivity(kill, history, faction, contributions, excludedTargets, existingParticipants)
contributeWithKillWhileMountedActivity(kill, faction, contributions, excludedTargets, existingParticipants)
@@ -371,10 +377,17 @@ object KillContributions {
/*
collect the dismount activity of all vehicles from which this player is not the owner
make certain all dismount activity can be paired with a mounting activity
- certain other qualifications of the prior mounting must be met before the support bonus applies
+ other qualifications of the prior mounting must be met before the support bonus applies
*/
+ val killerOpt = kill.info.adversarial
+ .map(_.attacker)
+ .collect { case p: PlayerSource => p }
val dismountActivity = history
.collect {
+ /*
+ the player should not get credit from being the vehicle owner in matters of transportation
+ there are considerations of time and distance traveled before the kill as well
+ */
case out: VehicleDismountActivity
if !out.vehicle.owner.contains(out.player.unique) && out.pairedEvent.nonEmpty => (out.pairedEvent.get, out)
}
@@ -382,29 +395,30 @@ object KillContributions {
case (in: VehicleMountActivity, out: VehicleDismountActivity)
if in.vehicle.unique == out.vehicle.unique &&
out.vehicle.Faction == out.player.Faction &&
- (in.vehicle.Definition == GlobalDefinitions.router || {
- val inTime = in.time
- val outTime = out.time
- out.player.progress.kills.exists { death =>
- val deathTime = death.info.interaction.hitTime
- inTime < deathTime && deathTime <= outTime
- }
- } || {
- val sameZone = in.zoneNumber == out.zoneNumber
- val distanceTransported = Vector3.DistanceSquared(in.vehicle.Position.xy, out.vehicle.Position.xy)
- val distanceMoved = {
- val killLocation = kill.info.adversarial
- .collect { adversarial => adversarial.attacker.Position.xy }
- .getOrElse(Vector3.Zero)
- Vector3.DistanceSquared(killLocation, out.player.Position.xy)
- }
- val timeSpent = out.time - in.time
- distanceMoved < 5625f /* 75m */ &&
- (timeSpent >= 210000L /* 3:30 */ ||
- (sameZone && (distanceTransported > 160000f /* 400m */ ||
- distanceTransported > 10000f /* 100m */ && timeSpent >= 60000L /* 1:00m */)) ||
- (!sameZone && (distanceTransported > 10000f /* 100m */ || timeSpent >= 120000L /* 2:00 */ )))
- }) =>
+ /*
+ considerations of time and distance transported before the kill
+ */
+ ({
+ val inTime = in.time
+ val outTime = out.time
+ out.player.progress.kills.exists { death =>
+ val deathTime = death.info.interaction.hitTime
+ inTime < deathTime && deathTime <= outTime
+ }
+ } || {
+ val sameZone = in.zoneNumber == out.zoneNumber
+ val distanceTransported = Vector3.DistanceSquared(in.vehicle.Position.xy, out.vehicle.Position.xy)
+ val distanceMoved = {
+ val killLocation = killerOpt.map(_.Position.xy).getOrElse(Vector3.Zero)
+ Vector3.DistanceSquared(killLocation, out.player.Position.xy)
+ }
+ val timeSpent = out.time - in.time
+ distanceMoved < 5625f /* 75m */ &&
+ (timeSpent >= 210000L /* 3:30 */ ||
+ (sameZone && (distanceTransported > 160000f /* 400m */ ||
+ distanceTransported > 10000f /* 100m */ && timeSpent >= 60000L /* 1:00m */)) ||
+ (!sameZone && (distanceTransported > 10000f /* 100m */ || timeSpent >= 120000L /* 2:00 */ )))
+ }) =>
out
}
//apply
@@ -412,25 +426,20 @@ object KillContributions {
.groupBy { _.vehicle }
.collect { case (mount, dismountsFromVehicle) if mount.owner.nonEmpty =>
val promotedOwner = PlayerSource(mount.owner.get, mount.Position)
- val (equipmentUseContext, equipmentUseEvent) = mount.Definition match {
- case v @ GlobalDefinitions.router =>
- (RouterKillAssist(v.ObjectId), "router")
- case v =>
- (HotDropKillAssist(v.ObjectId, 0), "hotdrop")
- }
val size = dismountsFromVehicle.size
val time = dismountsFromVehicle.maxBy(_.time).time
- val weaponStat = Support.calculateSupportExperience(
- equipmentUseEvent,
- WeaponStats(equipmentUseContext, size, size, time, 1f)
- )
- combineStatsInto(
- out,
- (
- promotedOwner.CharId,
- ContributionStats(promotedOwner, Seq(weaponStat), size, size, size, time)
- )
- )
+ List((HotDropKillAssist(mount.Definition.ObjectId, 0), "hotdrop", promotedOwner))
+ .foreach {
+ case (equipmentUseContext, equipmentUseEvent, eventOwner) =>
+ val weaponStat = Support.calculateSupportExperience(
+ equipmentUseEvent,
+ WeaponStats(equipmentUseContext, size, size, time, 1f)
+ )
+ combineStatsInto(
+ out,
+ (eventOwner.CharId, ContributionStats(eventOwner, Seq(weaponStat), size, size, size, time))
+ )
+ }
contributions.get(mount.unique).collect {
case list =>
val mountHistory = dismountsFromVehicle
@@ -554,6 +563,86 @@ object KillContributions {
}
}
+ /**
+ * Gather and reward use of a telepad deployable in performing a kill.
+ * There are two ways to account for telepad deployable use,
+ * i.e., traveling through s telepad deployable or using the internal telepad system of a Router:
+ * the user that places the telepad deployable unit,
+ * and the user that owns the Router.
+ * na
+ * @param kill the in-game event that maintains information about the other player's death
+ * @param faction empire to target
+ * @param contributions mapping between external entities
+ * the target has interacted with in the form of in-game activity
+ * and history related to the time period in which the interaction ocurred
+ * @param excludedTargets if a potential target is listed here already, skip processing it
+ * @param out quantitative record of activity in relation to the other players and their equipment
+ * @see `combineStatsInto`
+ * @see `extractContributionsForMachineByTarget`
+ */
+ private def contributeWithRouterTelepadActivity(
+ kill: Kill,
+ history: List[InGameActivity],
+ faction: PlanetSideEmpire.Value,
+ contributions: Map[SourceUniqueness, List[InGameActivity]],
+ excludedTargets: mutable.ListBuffer[SourceUniqueness],
+ out: mutable.LongMap[ContributionStats]
+ ): Unit = {
+ /*
+ collect the use of all router telepads from which this player is not the owner (deployer) of the telepad
+ */
+ val killer = kill.info.adversarial
+ .map(_.attacker)
+ .collect { case p: PlayerSource => p }
+ .getOrElse(PlayerSource.Nobody)
+ history
+ .collect {
+ case event: TelepadUseActivity if !event.player.Name.equals(event.telepad.OwnerName) =>
+ event
+ }
+ .groupBy(_.telepad.unique)
+ .flatMap {
+ case (_, telepadEvents) =>
+ val size = telepadEvents.size
+ val time = telepadEvents.maxBy(_.time).time
+ val firstEvent = telepadEvents.head
+ val telepadOwner = firstEvent.telepad.owner.asInstanceOf[PlayerSource]
+ val mount = firstEvent.router
+ contributions.get(mount.unique).collect {
+ case list =>
+ val mountHistory = telepadEvents
+ .flatMap { event =>
+ val eventTime = event.time
+ val startTime = eventTime - Config.app.game.experience.longContributionTime
+ limitHistoryToThisLife(list, eventTime, startTime)
+ }
+ .distinctBy(_.time)
+ combineStatsInto(
+ out,
+ extractContributionsForMachineByTarget(mount, faction, mountHistory, contributions, excludedTargets, eventOutputType="support-repair")
+ )
+ }
+ telepadEvents
+ .flatMap(_.router.owner)
+ .distinct
+ .filterNot(owner => owner == killer.unique || owner == telepadOwner.unique)
+ .map(p => (WeaponStats(routerKillAssist, size, size, time, 1f), "router-driver", PlayerSource(p, Vector3.Zero))) :+
+ (WeaponStats(routerTelepadKillAssist, size, size, time, 1f), "telepad-use", telepadOwner)
+ }
+ .foreach {
+ case (equipmentUseContext, equipmentUseEventId, eventOwner) =>
+ val size = equipmentUseContext.amount
+ val weaponStat = Support.calculateSupportExperience(equipmentUseEventId, equipmentUseContext)
+ combineStatsInto(
+ out,
+ (
+ eventOwner.CharId,
+ ContributionStats(eventOwner, Seq(weaponStat), size, size, size, equipmentUseContext.time)
+ )
+ )
+ }
+ }
+
/**
* Gather and reward specific in-game equipment use activity.
* na
diff --git a/src/main/scala/net/psforever/objects/zones/exp/Support.scala b/src/main/scala/net/psforever/objects/zones/exp/Support.scala
index 1b0551b50..0233426ca 100644
--- a/src/main/scala/net/psforever/objects/zones/exp/Support.scala
+++ b/src/main/scala/net/psforever/objects/zones/exp/Support.scala
@@ -676,12 +676,14 @@ object Support {
val shots = weaponStat.shots
val shotsMax = event.shotsMax
val shotsMultiplier = event.shotsMultiplier
- if (shotsMultiplier > 0f && shots < event.shotsCutoff) {
- val modifiedShotsReward: Float =
- shotsMultiplier * math.log(math.min(shotsMax, shots).toDouble + 2d).toFloat
- val modifiedAmountReward: Float =
- event.amountMultiplier * weaponStat.amount.toFloat
- event.base.toFloat + modifiedShotsReward + modifiedAmountReward
+ if (shots < event.shotsCutoff) {
+ if (shotsMultiplier > 0f) {
+ val modifiedShotsReward: Float = shotsMultiplier * math.log(math.min(shotsMax, shots).toDouble + 2d).toFloat
+ val modifiedAmountReward: Float = event.amountMultiplier * weaponStat.amount.toFloat
+ event.base.toFloat + modifiedShotsReward + modifiedAmountReward
+ } else {
+ event.base.toFloat
+ }
} else {
0f
}