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 }