From cc2089b513c7403a4c44e74828d1d6d1c3872b09 Mon Sep 17 00:00:00 2001 From: Fate-JH Date: Mon, 13 Nov 2023 02:24:36 -0500 Subject: [PATCH] make a better guess on the wonership of a facility during a capture/resecure; during a neutral capture, an 'owning side' will be decalred based on effort; changed the calculation of kill experience to simplify the modifiers and reduce the potential to have 0 bep --- .../converter/CaptureFlagConverter.scala | 2 +- .../serverobject/hackable/Hackable.scala | 13 ++-- .../serverobject/structures/Building.scala | 2 +- .../FacilityHackParticipation.scala | 2 + .../participation/NoParticipation.scala | 3 + .../participation/ParticipationLogic.scala | 3 + .../terminals/capture/CaptureTerminal.scala | 2 +- .../objects/zones/exp/KillAssists.scala | 36 ++++------ .../local/support/HackCaptureActor.scala | 67 +++++++++++++++++-- 9 files changed, 95 insertions(+), 35 deletions(-) diff --git a/src/main/scala/net/psforever/objects/definition/converter/CaptureFlagConverter.scala b/src/main/scala/net/psforever/objects/definition/converter/CaptureFlagConverter.scala index bdd7d490..fd44b028 100644 --- a/src/main/scala/net/psforever/objects/definition/converter/CaptureFlagConverter.scala +++ b/src/main/scala/net/psforever/objects/definition/converter/CaptureFlagConverter.scala @@ -14,7 +14,7 @@ class CaptureFlagConverter extends ObjectCreateConverter[CaptureFlag]() { override def ConstructorData(obj : CaptureFlag) : Try[CaptureFlagData] = { val hackInfo = obj.Owner.asInstanceOf[Building].CaptureTerminal.get.HackedBy match { case Some(hackInfo) => hackInfo - case _ => Hackable.HackInfo(PlayerSource("", PlanetSideEmpire.NEUTRAL, Vector3.Zero), PlanetSideGUID(0), 0L, 0L) + case _ => Hackable.HackInfo(PlayerSource("", PlanetSideEmpire.NEUTRAL, Vector3.Zero), PlanetSideGUID(0), 0L, 0L, obj.Faction) } val millisecondsRemaining = math.max(0, hackInfo.hackStartTime + hackInfo.hackDuration - System.currentTimeMillis()) diff --git a/src/main/scala/net/psforever/objects/serverobject/hackable/Hackable.scala b/src/main/scala/net/psforever/objects/serverobject/hackable/Hackable.scala index a494f2d8..7ac01e45 100644 --- a/src/main/scala/net/psforever/objects/serverobject/hackable/Hackable.scala +++ b/src/main/scala/net/psforever/objects/serverobject/hackable/Hackable.scala @@ -25,14 +25,14 @@ trait Hackable { def HackedBy_=(agent: Option[Player]): Option[HackInfo] = { (hackedBy, agent) match { case (None, Some(actor)) => - hackedBy = Some(HackInfo(PlayerSource(actor), actor.GUID, System.currentTimeMillis(), 0L)) + hackedBy = Some(HackInfo(PlayerSource(actor), actor.GUID, System.currentTimeMillis(), 0L, Faction)) case (Some(info), Some(actor)) => if (actor.Faction == this.Faction) { //hack cleared hackedBy = None } else if (actor.Faction != info.hackerFaction) { //override the hack state with a new hack state if the new user has different faction affiliation - hackedBy = Some(HackInfo(PlayerSource(actor), actor.GUID, System.currentTimeMillis(), 0L)) + hackedBy = Some(HackInfo(PlayerSource(actor), actor.GUID, System.currentTimeMillis(), 0L, Faction)) } case (_, None) => hackedBy = None @@ -73,14 +73,15 @@ trait Hackable { object Hackable { final case class HackInfo( player: PlayerSource, - hackerGUID: PlanetSideGUID, - hackStartTime: Long, - hackDuration: Long + hackerGUID: PlanetSideGUID, + hackStartTime: Long, + hackDuration: Long, + originalFaction: PlanetSideEmpire.Value ) { def hackerName: String = player.Name def hackerFaction: PlanetSideEmpire.Value = player.Faction def hackerPos: Vector3 = player.Position - def Duration(time: Long): HackInfo = HackInfo(player, hackerGUID, hackStartTime, time) + def Duration(time: Long): HackInfo = HackInfo(player, hackerGUID, hackStartTime, time, originalFaction) } } diff --git a/src/main/scala/net/psforever/objects/serverobject/structures/Building.scala b/src/main/scala/net/psforever/objects/serverobject/structures/Building.scala index df9aa66c..a80c79ec 100644 --- a/src/main/scala/net/psforever/objects/serverobject/structures/Building.scala +++ b/src/main/scala/net/psforever/objects/serverobject/structures/Building.scala @@ -182,7 +182,7 @@ class Building( val (hacking, hackingFaction, hackTime): (Boolean, PlanetSideEmpire.Value, Long) = CaptureTerminal match { case Some(obj: CaptureTerminal with Hackable) => obj.HackedBy match { - case Some(Hackable.HackInfo(p, _, start, length)) => + case Some(Hackable.HackInfo(p, _, start, length, _)) => val hack_time_remaining_ms = math.max(0, start + length - System.currentTimeMillis()) (true, p.Faction, hack_time_remaining_ms) case _ => diff --git a/src/main/scala/net/psforever/objects/serverobject/structures/participation/FacilityHackParticipation.scala b/src/main/scala/net/psforever/objects/serverobject/structures/participation/FacilityHackParticipation.scala index 72c527e5..7d0569f8 100644 --- a/src/main/scala/net/psforever/objects/serverobject/structures/participation/FacilityHackParticipation.scala +++ b/src/main/scala/net/psforever/objects/serverobject/structures/participation/FacilityHackParticipation.scala @@ -18,6 +18,8 @@ trait FacilityHackParticipation extends ParticipationLogic { protected var playerContribution: mutable.LongMap[(Player, Int, Long)] = mutable.LongMap[(Player, Int, Long)]() protected var playerPopulationOverTime: Seq[Map[PlanetSideEmpire.Value, Int]] = Seq[Map[PlanetSideEmpire.Value, Int]]() + def PlayerContributionRaw: Map[Long, (Player, Int, Long)] = playerContribution.toMap + def PlayerContribution(timeDelay: Long): Map[Long, Float] = { playerContribution .collect { diff --git a/src/main/scala/net/psforever/objects/serverobject/structures/participation/NoParticipation.scala b/src/main/scala/net/psforever/objects/serverobject/structures/participation/NoParticipation.scala index 2d758368..2e48d871 100644 --- a/src/main/scala/net/psforever/objects/serverobject/structures/participation/NoParticipation.scala +++ b/src/main/scala/net/psforever/objects/serverobject/structures/participation/NoParticipation.scala @@ -1,6 +1,7 @@ // Copyright (c) 2023 PSForever package net.psforever.objects.serverobject.structures.participation +import net.psforever.objects.Player import net.psforever.objects.serverobject.structures.Building import net.psforever.objects.sourcing.PlayerSource import net.psforever.types.PlanetSideEmpire @@ -16,5 +17,7 @@ case object NoParticipation extends ParticipationLogic { completionTime: Long, isResecured: Boolean ): Unit = { /* nothing here */ } + override def PlayerContributionRaw: Map[Long, (Player, Int, Long)] = Map.empty[Long, (Player, Int, Long)] + override def PlayerContribution(timeDelay: Long): Map[Long, Float] = Map.empty[Long, Float] } diff --git a/src/main/scala/net/psforever/objects/serverobject/structures/participation/ParticipationLogic.scala b/src/main/scala/net/psforever/objects/serverobject/structures/participation/ParticipationLogic.scala index 8e91e344..d42a8d22 100644 --- a/src/main/scala/net/psforever/objects/serverobject/structures/participation/ParticipationLogic.scala +++ b/src/main/scala/net/psforever/objects/serverobject/structures/participation/ParticipationLogic.scala @@ -1,6 +1,7 @@ // Copyright (c) 2023 PSForever package net.psforever.objects.serverobject.structures.participation +import net.psforever.objects.Player import net.psforever.objects.serverobject.structures.Building import net.psforever.objects.sourcing.PlayerSource import net.psforever.types.PlanetSideEmpire @@ -31,5 +32,7 @@ trait ParticipationLogic { isResecured: Boolean ): Unit + def PlayerContributionRaw: Map[Long, (Player, Int, Long)] + def PlayerContribution(timeDelay: Long = 600): Map[Long, Float] } diff --git a/src/main/scala/net/psforever/objects/serverobject/terminals/capture/CaptureTerminal.scala b/src/main/scala/net/psforever/objects/serverobject/terminals/capture/CaptureTerminal.scala index 3ad5847f..8cd54ec5 100644 --- a/src/main/scala/net/psforever/objects/serverobject/terminals/capture/CaptureTerminal.scala +++ b/src/main/scala/net/psforever/objects/serverobject/terminals/capture/CaptureTerminal.scala @@ -12,7 +12,7 @@ class CaptureTerminal(private val idef: CaptureTerminalDefinition) extends Ameni override def toString: String = { val guid = if (HasGUID) { - s" ${Continent}-${GUID.guid}" + s" $Continent-${GUID.guid}" } else { "" } diff --git a/src/main/scala/net/psforever/objects/zones/exp/KillAssists.scala b/src/main/scala/net/psforever/objects/zones/exp/KillAssists.scala index 8b15262b..6c71e2ff 100644 --- a/src/main/scala/net/psforever/objects/zones/exp/KillAssists.scala +++ b/src/main/scala/net/psforever/objects/zones/exp/KillAssists.scala @@ -320,33 +320,25 @@ object KillAssists { 0L } else if (base > 1) { //include battle rank disparity modifier - val battleRankDisparity = { + val battleRankDisparity: Long = { import net.psforever.objects.avatar.BattleRank val killerLevel = BattleRank.withExperience(killer.bep).value val victimLevel = BattleRank.withExperience(victim.bep).value - if (victimLevel > killerLevel || killerLevel - victimLevel < 6) { - if (killerLevel < 7) { - 6 * victimLevel + 10 - } else if (killerLevel < 12) { - (12 - killerLevel) * victimLevel + 10 - } else if (killerLevel < 25) { - 25 + victimLevel - killerLevel - } else { - 25 - } + val victimMinusKiller = victimLevel - killerLevel + if (victimMinusKiller > -1) { + victimMinusKiller * 10 + victimLevel } else { - math.floor(-0.15f * base - killerLevel + victimLevel).toLong + val bothLevels = killerLevel + victimLevel + val pointFive = (base.toFloat * 0.25f).toInt + -1 * (if (bothLevels >= base) { + pointFive + } else { + math.min(bothLevels, pointFive) + }) } - } - val baseWithDisparity: Long = base + battleRankDisparity - val killCount: Long = victim.progress.kills.size - if (battleRankDisparity > 0) { - //include menace modifier - val pureMenace = calculateMenace(victim) - baseWithDisparity + (killCount * (1f + pureMenace.toFloat / 10f)).toLong - } else { - math.max(baseWithDisparity, killCount) - } + }.toLong + //include menace modifier + base + battleRankDisparity + (victim.progress.kills.size.toFloat * (1f + calculateMenace(victim).toFloat / 10f)).toLong } else { base } diff --git a/src/main/scala/net/psforever/services/local/support/HackCaptureActor.scala b/src/main/scala/net/psforever/services/local/support/HackCaptureActor.scala index 9c8b9f64..5947beff 100644 --- a/src/main/scala/net/psforever/services/local/support/HackCaptureActor.scala +++ b/src/main/scala/net/psforever/services/local/support/HackCaptureActor.scala @@ -78,7 +78,14 @@ class HackCaptureActor extends Actor { // Timed hack finished (or neutral LLU base with no neighbour as timed hack), capture the base val hackTime = terminal.Definition.FacilityHackTime.toMillis HackCompleted(terminal, hackedByFaction) - building.Participation.RewardFacilityCapture(terminal.Faction, hackedByFaction, hacker, hackTime, hackTime, isResecured = false) + building.Participation.RewardFacilityCapture( + HackCaptureActor.GetDefendingFaction(terminal, building, hackedByFaction), + hackedByFaction, + hacker, + hackTime, + hackTime, + isResecured = false + ) } } // If there's hacked objects left in the list restart the timer with the shortest hack time left @@ -95,7 +102,14 @@ class HackCaptureActor extends Actor { case flag: CaptureFlag => target.Zone.LocalEvents ! CaptureFlagManager.Lost(flag, CaptureFlagLostReasonEnum.Resecured) } NotifyHackStateChange(target, isResecured = true) - building.Participation.RewardFacilityCapture(target.Faction, faction, hacker, target.Definition.FacilityHackTime.toMillis, System.currentTimeMillis() - results.head.hack_timestamp, isResecured = true) + building.Participation.RewardFacilityCapture( + target.Faction, + faction, + hacker, + target.Definition.FacilityHackTime.toMillis, + System.currentTimeMillis() - results.head.hack_timestamp, + isResecured = true + ) // Restart the timer in case the object we just removed was the next one scheduled RestartTimer() @@ -111,7 +125,14 @@ class HackCaptureActor extends Actor { val hackedByFaction = hackInfo.hackerFaction hackedObjects = hackedObjects.filterNot(x => x == entry) HackCompleted(terminal, hackedByFaction) - building.Participation.RewardFacilityCapture(terminal.Faction, hacker.Faction, hacker, terminal.Definition.FacilityHackTime.toMillis, System.currentTimeMillis() - entry.hack_timestamp, isResecured = false) + building.Participation.RewardFacilityCapture( + HackCaptureActor.GetDefendingFaction(terminal, building, hackedByFaction), + hackedByFaction, + hacker, + terminal.Definition.FacilityHackTime.toMillis, + System.currentTimeMillis() - entry.hack_timestamp, + isResecured = false + ) entry.target.Actor ! CommonMessages.ClearHack() flag.Zone.LocalEvents ! CaptureFlagManager.Captured(flag) // If there's hacked objects left in the list restart the timer with the shortest hack time left @@ -247,6 +268,44 @@ object HackCaptureActor { hack_timestamp: Long ) + def GetDefendingFaction( + terminal: CaptureTerminal, + building: Building, + excludeThisFaction: PlanetSideEmpire.Value + ): PlanetSideEmpire.Value = { + terminal.HackedBy + .map { _.originalFaction } + .orElse { Some(terminal.Faction) } + .collect { + case PlanetSideEmpire.NEUTRAL => + val factionEfforts = building.Participation + .PlayerContributionRaw + .values + .foldLeft(Array.fill(4)(0L))({ case (a, b) => + val (player, duration, _) = b + val index = player.Faction.id + a.update(index, a(index) + duration.toLong) + a + }) + factionEfforts.update(excludeThisFaction.id, Long.MinValue) + factionEfforts + .indices + .maxByOption(factionEfforts) + .map { index => PlanetSideEmpire(index) } + .getOrElse { + val test = PlanetSideEmpire(0) + if (excludeThisFaction == test) { + PlanetSideEmpire(1) + } else { + test + } + } + case faction => + faction + } + .get + } + def GetHackingFaction(terminal: CaptureTerminal): Option[PlanetSideEmpire.Value] = { terminal.HackedBy.map { a => a.player.Faction } } @@ -255,7 +314,7 @@ object HackCaptureActor { terminal.HackedBy match { case _ if isResecured => 17039360L - case Some(Hackable.HackInfo(p, _, start, length)) => + case Some(Hackable.HackInfo(p, _, start, length, _)) => // See PlanetSideAttributeMessage #20 documentation for an explanation of how the timer is calculated val hackTimeRemainingMS = math.max(0, start + length - System.currentTimeMillis()) val startNum = p.Faction match {