mirror of
https://github.com/psforever/PSF-LoginServer.git
synced 2026-07-16 08:55:18 +00:00
importing controlled implementation changes from original exp-for-kda branch; assist kill experience rewarded
This commit is contained in:
parent
2f787a467f
commit
6774edf5b6
11 changed files with 403 additions and 49 deletions
|
|
@ -6,7 +6,7 @@ import akka.actor.typed.scaladsl.{ActorContext, Behaviors, StashBuffer}
|
|||
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.{Death, EquipmentStat, KDAStat, Kill}
|
||||
import net.psforever.objects.avatar.scoring.{Assist, Death, EquipmentStat, KDAStat, Kill}
|
||||
import org.joda.time.{LocalDateTime, Seconds}
|
||||
import scala.collection.mutable
|
||||
import scala.concurrent.{ExecutionContextExecutor, Future, Promise}
|
||||
|
|
@ -2953,6 +2953,8 @@ class AvatarActor(
|
|||
zone.actor ! ZoneActor.RewardOurSupporters(playerSource, obj.History, kill, exp)
|
||||
}
|
||||
zone.actor ! ZoneActor.RewardOurSupporters(playerSource, player.History, kill, exp)
|
||||
case _: Assist =>
|
||||
()
|
||||
case _: Death =>
|
||||
zone.AvatarEvents ! AvatarServiceMessage(
|
||||
player.Name,
|
||||
|
|
|
|||
|
|
@ -398,6 +398,9 @@ class SessionAvatarHandlers(
|
|||
case AvatarResponse.UpdateKillsDeathsAssists(_, kda) =>
|
||||
avatarActor ! AvatarActor.UpdateKillsDeathsAssists(kda)
|
||||
|
||||
case AvatarResponse.AwardSupportBep(_, bep) =>
|
||||
avatarActor ! AvatarActor.AwardBep(bep, ExperienceType.Support)
|
||||
|
||||
case AvatarResponse.SendResponse(msg) =>
|
||||
sendResponse(msg)
|
||||
|
||||
|
|
|
|||
|
|
@ -154,7 +154,7 @@ class ZoneActor(context: ActorContext[ZoneActor.Command], zone: Zone)
|
|||
experience ! ExperienceCalculator.RewardThisDeath(entity)
|
||||
|
||||
case RewardOurSupporters(target, history, kill, bep) =>
|
||||
()
|
||||
experience ! ExperienceCalculator.RewardOurSupporters(target, history, kill, bep)
|
||||
|
||||
case ZoneMapUpdate() =>
|
||||
zone.Buildings
|
||||
|
|
@ -162,7 +162,6 @@ class ZoneActor(context: ActorContext[ZoneActor.Command], zone: Zone)
|
|||
.values
|
||||
.foreach(_.Actor ! BuildingActor.MapUpdate())
|
||||
}
|
||||
|
||||
this
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ import net.psforever.objects.equipment.EquipmentSlot
|
|||
import net.psforever.objects.guid.{GUIDTask, TaskWorkflow}
|
||||
import net.psforever.objects.inventory.InventoryItem
|
||||
import net.psforever.objects.loadouts.InfantryLoadout
|
||||
import net.psforever.objects.sourcing.PlayerSource
|
||||
import net.psforever.objects.vital.RevivingActivity
|
||||
import net.psforever.objects.zones.Zone
|
||||
import net.psforever.packet.game._
|
||||
import net.psforever.types.{ChatMessageType, ExoSuitType, Vector3}
|
||||
|
|
@ -64,7 +66,7 @@ object Players {
|
|||
val name = target.Name
|
||||
val medicName = medic.Name
|
||||
log.info(s"$medicName had revived $name")
|
||||
//target.History(PlayerRespawn(PlayerSource(target), target.Zone, target.Position, Some(PlayerSource(medic))))
|
||||
target.LogActivity(RevivingActivity(PlayerSource(target), PlayerSource(medic), target.MaxHealth, item.Definition))
|
||||
val magazine = item.Discharge(Some(25))
|
||||
target.Zone.AvatarEvents ! AvatarServiceMessage(
|
||||
medicName,
|
||||
|
|
|
|||
|
|
@ -28,13 +28,25 @@ trait InGameActivity {
|
|||
*/
|
||||
trait GeneralActivity extends InGameActivity
|
||||
|
||||
final case class SpawningActivity(src: SourceEntry, zoneNumber: Int, unit: Option[SourceEntry]) extends GeneralActivity
|
||||
trait SupportActivityCausedByAnother {
|
||||
def user: PlayerSource
|
||||
def amount: Int
|
||||
}
|
||||
|
||||
final case class ReconstructionActivity(src: SourceEntry, zoneNumber: Int, unit: Option[SourceEntry]) extends GeneralActivity
|
||||
final case class SpawningActivity(src: SourceEntry, zoneNumber: Int, unit: Option[SourceEntry])
|
||||
extends GeneralActivity
|
||||
|
||||
final case class ShieldCharge(amount: Int, cause: Option[SourceEntry]) extends GeneralActivity
|
||||
final case class ReconstructionActivity(src: SourceEntry, zoneNumber: Int, unit: Option[SourceEntry])
|
||||
extends GeneralActivity
|
||||
|
||||
final case class TerminalUsedActivity(terminal: AmenitySource, transaction: TransactionType.Value) extends GeneralActivity
|
||||
final case class RevivingActivity(target: SourceEntry, user: PlayerSource, amount: Int, equipment: EquipmentDefinition)
|
||||
extends GeneralActivity with SupportActivityCausedByAnother
|
||||
|
||||
final case class ShieldCharge(amount: Int, cause: Option[SourceEntry])
|
||||
extends GeneralActivity
|
||||
|
||||
final case class TerminalUsedActivity(terminal: AmenitySource, transaction: TransactionType.Value)
|
||||
extends GeneralActivity
|
||||
|
||||
/* vitals history */
|
||||
|
||||
|
|
@ -76,7 +88,7 @@ final case class HealFromKit(kit_def: KitDefinition, amount: Int)
|
|||
extends HealingActivity
|
||||
|
||||
final case class HealFromEquipment(user: PlayerSource, equipment_def: EquipmentDefinition, amount: Int)
|
||||
extends HealingActivity
|
||||
extends HealingActivity with SupportActivityCausedByAnother
|
||||
|
||||
final case class HealFromTerm(term: AmenitySource, amount: Int)
|
||||
extends HealingActivity
|
||||
|
|
@ -91,7 +103,7 @@ final case class RepairFromKit(kit_def: KitDefinition, amount: Int)
|
|||
extends RepairingActivity()
|
||||
|
||||
final case class RepairFromEquipment(user: PlayerSource, equipment_def: EquipmentDefinition, amount: Int)
|
||||
extends RepairingActivity
|
||||
extends RepairingActivity with SupportActivityCausedByAnother
|
||||
|
||||
final case class RepairFromTerm(term: AmenitySource, amount: Int) extends RepairingActivity
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import net.psforever.objects.PlanetSideGameObject
|
|||
import net.psforever.objects.avatar.scoring.{Assist, Death, Kill}
|
||||
import net.psforever.objects.serverobject.affinity.FactionAffinity
|
||||
import net.psforever.objects.sourcing.{PlayerSource, SourceEntry}
|
||||
import net.psforever.objects.vital.{DamagingActivity, HealingActivity, InGameActivity, InGameHistory, ReconstructionActivity, RepairFromExoSuitChange, RepairingActivity, SpawningActivity}
|
||||
import net.psforever.objects.vital.{DamagingActivity, HealingActivity, InGameActivity, InGameHistory, ReconstructionActivity, RepairFromExoSuitChange, RepairingActivity, RevivingActivity, SpawningActivity}
|
||||
import net.psforever.objects.vital.interaction.{Adversarial, DamageResult}
|
||||
import net.psforever.objects.zones.Zone
|
||||
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||
|
|
@ -33,6 +33,36 @@ object ExperienceCalculator {
|
|||
}
|
||||
}
|
||||
|
||||
final case class RewardOurSupporters(target: SourceEntry, history: Iterable[InGameActivity], kill: Kill, bep: Long) extends Command
|
||||
|
||||
object RewardOurSupporters {
|
||||
def apply(obj: PlanetSideGameObject with FactionAffinity with InGameHistory, kill: Kill): RewardOurSupporters = {
|
||||
RewardOurSupporters(SourceEntry(obj), obj.History, kill, kill.experienceEarned)
|
||||
}
|
||||
}
|
||||
|
||||
def limitHistoryToThisLife(history: List[InGameActivity]): List[InGameActivity] = {
|
||||
val spawnIndex = history.lastIndexWhere {
|
||||
case _: SpawningActivity => true
|
||||
case _: RevivingActivity => true
|
||||
case _ => false
|
||||
}
|
||||
val endIndex = history.lastIndexWhere {
|
||||
case damage: DamagingActivity => damage.data.targetAfter.asInstanceOf[PlayerSource].Health == 0
|
||||
case _ => false
|
||||
}
|
||||
if (spawnIndex == -1 || endIndex == -1) {
|
||||
Nil //throw VitalsHistoryException(history.head, "vitals history does not contain expected conditions")
|
||||
// } else
|
||||
// if (spawnIndex == -1) {
|
||||
// Nil //throw VitalsHistoryException(history.head, "vitals history does not contain initial spawn conditions")
|
||||
// } else if (endIndex == -1) {
|
||||
// Nil //throw VitalsHistoryException(history.last, "vitals history does not contain end of life conditions")
|
||||
} else {
|
||||
history.slice(spawnIndex, endIndex)
|
||||
}
|
||||
}
|
||||
|
||||
def calculateExperience(
|
||||
victim: PlayerSource,
|
||||
history: Iterable[InGameActivity]
|
||||
|
|
@ -64,6 +94,38 @@ object ExperienceCalculator {
|
|||
base
|
||||
}
|
||||
}
|
||||
|
||||
def onlyOriginalAssistEntries(
|
||||
first: mutable.LongMap[ContributionStatsOutput],
|
||||
second: mutable.LongMap[ContributionStatsOutput]
|
||||
): Iterable[ContributionStatsOutput] = {
|
||||
onlyOriginalAssistEntriesIterable(first.values, second.values)
|
||||
}
|
||||
|
||||
def onlyOriginalAssistEntriesIterable(
|
||||
first: Iterable[ContributionStatsOutput],
|
||||
second: Iterable[ContributionStatsOutput]
|
||||
): Iterable[ContributionStatsOutput] = {
|
||||
if (second.isEmpty) {
|
||||
first
|
||||
} else if (first.isEmpty) {
|
||||
second
|
||||
} else {
|
||||
//overlap discriminated by percentage
|
||||
val shared: mutable.LongMap[ContributionStatsOutput] = mutable.LongMap[ContributionStatsOutput]()
|
||||
for {
|
||||
h @ ContributionStatsOutput(hid, hwep, hkda) <- first
|
||||
a @ ContributionStatsOutput(aid, awep, akda) <- second
|
||||
(id, out) = if (hkda < akda)
|
||||
(aid.CharId, a.copy(implements = (a.implements ++ hwep).distinct))
|
||||
else
|
||||
(hid.CharId, h.copy(implements = (h.implements ++ awep).distinct))
|
||||
if hid == aid && shared.put(id, out).isEmpty
|
||||
} yield ()
|
||||
val sharedKeys = shared.keys
|
||||
(first ++ second).filterNot { case ContributionStatsOutput(id, _, _) => sharedKeys.exists(_ == id.CharId) } ++ shared.values
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ExperienceCalculator(context: ActorContext[ExperienceCalculator.Command], zone: Zone)
|
||||
|
|
@ -74,13 +136,10 @@ class ExperienceCalculator(context: ActorContext[ExperienceCalculator.Command],
|
|||
def onMessage(msg: Command): Behavior[Command] = {
|
||||
msg match {
|
||||
case RewardThisDeath(victim: PlayerSource, lastDamage, history) =>
|
||||
rewardThisPlayerDeath(
|
||||
victim,
|
||||
lastDamage,
|
||||
limitHistoryToThisLife(history.toList)
|
||||
)
|
||||
case _ =>
|
||||
()
|
||||
rewardThisPlayerDeath(victim, lastDamage, history)
|
||||
case RewardOurSupporters(target, history, kill, bep) =>
|
||||
rewardTheseSupporters(target, history.toList, kill, bep)
|
||||
case _ => ()
|
||||
}
|
||||
Behaviors.same
|
||||
}
|
||||
|
|
@ -88,24 +147,25 @@ class ExperienceCalculator(context: ActorContext[ExperienceCalculator.Command],
|
|||
def rewardThisPlayerDeath(
|
||||
victim: PlayerSource,
|
||||
lastDamage: Option[DamageResult],
|
||||
history: List[InGameActivity]
|
||||
history: Iterable[InGameActivity]
|
||||
): Unit = {
|
||||
val everyone = determineKiller(lastDamage, history) match {
|
||||
val shortHistory = ExperienceCalculator.limitHistoryToThisLife(history.toList)
|
||||
val everyone = determineKiller(lastDamage, shortHistory) match {
|
||||
case Some((result, killer: PlayerSource)) =>
|
||||
val assists = collectAssistsForPlayer(victim, history, Some(killer))
|
||||
val fullBep = KillDeathAssists.calculateExperience(killer, victim, history)
|
||||
val assists = collectAssistsForPlayer(victim, shortHistory, Some(killer))
|
||||
val fullBep = KillDeathAssists.calculateExperience(killer, victim, shortHistory)
|
||||
val hitSquad = (killer, Kill(victim, result, fullBep)) +: assists.map {
|
||||
case ContributionStatsOutput(p, w, r) => (p, Assist(victim, w, r, (fullBep * r).toLong))
|
||||
}.toSeq
|
||||
(victim, Death(hitSquad.map { _._1 }, history.last.time - history.head.time, fullBep)) +: hitSquad
|
||||
(victim, Death(hitSquad.map { _._1 }, shortHistory.last.time - shortHistory.head.time, fullBep)) +: hitSquad
|
||||
|
||||
case _ =>
|
||||
val assists = collectAssistsForPlayer(victim, history, None)
|
||||
val fullBep = ExperienceCalculator.calculateExperience(victim, history)
|
||||
val assists = collectAssistsForPlayer(victim, shortHistory, None)
|
||||
val fullBep = ExperienceCalculator.calculateExperience(victim, shortHistory)
|
||||
val hitSquad = assists.map {
|
||||
case ContributionStatsOutput(p, w, r) => (p, Assist(victim, w, r, (fullBep * r).toLong))
|
||||
}.toSeq
|
||||
(victim, Death(hitSquad.map { _._1 }, history.last.time - history.head.time, fullBep)) +: hitSquad
|
||||
(victim, Death(hitSquad.map { _._1 }, shortHistory.last.time - shortHistory.head.time, fullBep)) +: hitSquad
|
||||
}
|
||||
val events = zone.AvatarEvents
|
||||
everyone.foreach { case (p, kda) =>
|
||||
|
|
@ -113,27 +173,6 @@ class ExperienceCalculator(context: ActorContext[ExperienceCalculator.Command],
|
|||
}
|
||||
}
|
||||
|
||||
def limitHistoryToThisLife(history: List[InGameActivity]): List[InGameActivity] = {
|
||||
val spawnIndex = history.indexWhere {
|
||||
case SpawningActivity(_, _, _) => true
|
||||
case _ => false
|
||||
}
|
||||
val endIndex = history.lastIndexWhere {
|
||||
case damage: DamagingActivity => damage.data.targetAfter.asInstanceOf[PlayerSource].Health == 0
|
||||
case _ => false
|
||||
}
|
||||
if (spawnIndex == -1 || endIndex == -1) {
|
||||
Nil //throw VitalsHistoryException(history.head, "vitals history does not contain expected conditions")
|
||||
// } else
|
||||
// if (spawnIndex == -1) {
|
||||
// Nil //throw VitalsHistoryException(history.head, "vitals history does not contain initial spawn conditions")
|
||||
// } else if (endIndex == -1) {
|
||||
// Nil //throw VitalsHistoryException(history.last, "vitals history does not contain end of life conditions")
|
||||
} else {
|
||||
history.slice(spawnIndex, endIndex)
|
||||
}
|
||||
}
|
||||
|
||||
def determineKiller(lastDamageActivity: Option[DamageResult], history: List[InGameActivity]): Option[(DamageResult, SourceEntry)] = {
|
||||
val now = System.currentTimeMillis()
|
||||
val compareTimeMillis = 10.seconds.toMillis
|
||||
|
|
@ -254,7 +293,7 @@ class ExperienceCalculator(context: ActorContext[ExperienceCalculator.Command],
|
|||
mod.copy(
|
||||
amount = mod.amount + amount,
|
||||
weapons = weapons,
|
||||
totalDamage = mod.totalDamage + amount,
|
||||
total = mod.total + amount,
|
||||
shots = mod.shots + 1,
|
||||
time = activity.time
|
||||
)
|
||||
|
|
@ -333,4 +372,30 @@ class ExperienceCalculator(context: ActorContext[ExperienceCalculator.Command],
|
|||
}
|
||||
newOrder ++ order.drop(count)
|
||||
}
|
||||
|
||||
private def rewardTheseSupporters(
|
||||
target: SourceEntry,
|
||||
history: List[InGameActivity],
|
||||
kill: Kill,
|
||||
bep: Long
|
||||
): Unit = {
|
||||
val time = kill.time
|
||||
val normalAssists = ExperienceCalculator.onlyOriginalAssistEntries(
|
||||
Support.collectHealingSupportAssists(target, time, history),
|
||||
Support.collectRepairingSupportAssists(target, time, history)
|
||||
)
|
||||
// retainedSupportAssists.get(target.unique) match {
|
||||
// case Some(support) =>
|
||||
// ExperienceCalculator.onlyOriginalAssistEntriesIterable(normalAssists, support.assists)
|
||||
// case None =>
|
||||
// normalAssists
|
||||
// }
|
||||
val events = zone.AvatarEvents
|
||||
normalAssists.foreach { case ContributionStatsOutput(p, _, ratio) =>
|
||||
events ! AvatarServiceMessage(p.Name, AvatarAction.AwardSupportBep(p.CharId, (ratio * bep).toLong))
|
||||
}
|
||||
Support.collectTerminalSupportAssists(target, history).foreach { case ContributionStatsOutput(p, _, reward) =>
|
||||
events ! AvatarServiceMessage(p.Name, AvatarAction.AwardSupportBep(p.CharId, reward.toLong))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ private case class ContributionStats(
|
|||
player: PlayerSource,
|
||||
weapons: Seq[WeaponStats],
|
||||
amount: Int,
|
||||
totalDamage: Int,
|
||||
total: Int,
|
||||
shots: Int,
|
||||
time: Long
|
||||
)
|
||||
|
|
|
|||
260
src/main/scala/net/psforever/objects/zones/exp/Support.scala
Normal file
260
src/main/scala/net/psforever/objects/zones/exp/Support.scala
Normal file
|
|
@ -0,0 +1,260 @@
|
|||
// Copyright (c) 2023 PSForever
|
||||
package net.psforever.objects.zones.exp
|
||||
|
||||
import net.psforever.objects.GlobalDefinitions
|
||||
|
||||
import java.util.Date
|
||||
import org.joda.time.{Instant, LocalDateTime => JodaLocalDateTime}
|
||||
import net.psforever.objects.serverobject.hackable.Hackable
|
||||
import net.psforever.objects.serverobject.terminals.Terminal
|
||||
import net.psforever.objects.sourcing.{AmenitySource, PlayerSource, SourceEntry}
|
||||
import net.psforever.objects.vital.{HealFromEquipment, InGameActivity, RepairFromEquipment, RevivingActivity, SpawningActivity, SupportActivityCausedByAnother, TerminalUsedActivity}
|
||||
import net.psforever.objects.zones.Zone
|
||||
import net.psforever.types.TransactionType
|
||||
import net.psforever.zones.Zones
|
||||
|
||||
import scala.collection.mutable
|
||||
|
||||
object Support {
|
||||
private type SupportActivity = InGameActivity with SupportActivityCausedByAnother
|
||||
|
||||
private[exp] def collectHealingSupportAssists(
|
||||
target: SourceEntry,
|
||||
time: JodaLocalDateTime,
|
||||
history: Iterable[InGameActivity]
|
||||
): mutable.LongMap[ContributionStatsOutput] = {
|
||||
//normal heals
|
||||
val healInfo = collectSupportContributions(
|
||||
target, time, ExperienceCalculator.limitHistoryToThisLife(history.toList).collect { case heals: HealFromEquipment
|
||||
if heals.amount > 0 && heals.user.unique != target.unique => (heals, heals.equipment_def.ObjectId)
|
||||
}, mapContributionPointsByPercentage
|
||||
)
|
||||
//revivals (count all revivals performed by the player of the last one, if any)
|
||||
val reviveInfo = collectSupportContributions(
|
||||
target, time, history.collect { case revive: RevivingActivity
|
||||
if revive.user.unique != target.unique => (revive, revive.equipment.ObjectId)
|
||||
}.lastOption.toSeq, mapContributionPointsByCount
|
||||
)
|
||||
//combine and output
|
||||
reviveInfo.foreach { case (reviverId, contribution) =>
|
||||
val result = healInfo.get(reviverId)
|
||||
val percentage = result.map { a => a.percentage }.getOrElse { contribution.percentage }
|
||||
val implements = result.map { a => (a.implements ++ contribution.implements).distinct }.getOrElse { contribution.implements }
|
||||
healInfo.put(reviverId, ContributionStatsOutput(contribution.player, implements, percentage))
|
||||
}
|
||||
healInfo
|
||||
}
|
||||
|
||||
private[exp] def collectRepairingSupportAssists(
|
||||
target: SourceEntry,
|
||||
time: JodaLocalDateTime,
|
||||
history: Iterable[InGameActivity]
|
||||
): mutable.LongMap[ContributionStatsOutput] = {
|
||||
collectSupportContributions(
|
||||
target, time, ExperienceCalculator.limitHistoryToThisLife(history.toList).collect { case repairs: RepairFromEquipment
|
||||
if repairs.amount > 0 && repairs.user.unique != target.unique => (repairs, repairs.equipment_def.ObjectId)
|
||||
}, mapContributionPointsByPercentage
|
||||
)
|
||||
}
|
||||
|
||||
private[exp] def collectTerminalSupportAssists(
|
||||
target: SourceEntry,
|
||||
history: List[InGameActivity]
|
||||
): Iterable[ContributionStatsOutput] = {
|
||||
val delay: Long = 300000L
|
||||
val curr = System.currentTimeMillis()
|
||||
var zone: Zone = Zone.Nowhere
|
||||
val termsUsed: mutable.LongMap[Option[AmenitySource]] = mutable.LongMap[Option[AmenitySource]]()
|
||||
val credit: mutable.LongMap[ContributionStatsOutput] = mutable.LongMap[ContributionStatsOutput]()
|
||||
val faction = target.Faction
|
||||
val terminalUseHistory = history.filter {
|
||||
case _: SpawningActivity => true
|
||||
case entry: TerminalUsedActivity => curr - entry.time < delay
|
||||
case _ => false
|
||||
}
|
||||
terminalUseHistory.collect {
|
||||
case entry: SpawningActivity =>
|
||||
zone = Zones.zones(entry.zoneNumber)
|
||||
case entry: TerminalUsedActivity
|
||||
if !termsUsed.contains(entry.terminal.unique.guid.guid.toLong) =>
|
||||
if (
|
||||
entry.transaction == TransactionType.Loadout ||
|
||||
entry.transaction == TransactionType.Buy ||
|
||||
entry.transaction == TransactionType.Learn
|
||||
) {
|
||||
val terminal = entry.terminal
|
||||
val guid = terminal.unique.guid.guid
|
||||
terminal.hacked.collect {
|
||||
case Hackable.HackInfo(player, _, _, _)
|
||||
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)
|
||||
case _
|
||||
if terminal.Faction == faction =>
|
||||
//accessed a faction-friendly terminal; check log for repair history
|
||||
termsUsed.put(guid.toLong, Some(terminal))
|
||||
zone.GUID(guid)
|
||||
.asInstanceOf[Terminal]
|
||||
.History
|
||||
.collect { case a: RepairFromEquipment =>
|
||||
val user = a.user
|
||||
addTerminalContributionEntry(credit, user, Seq(a.equipment_def.ObjectId), percentage = 0.5f)
|
||||
}
|
||||
case _ =>
|
||||
//what is this?
|
||||
termsUsed.put(guid.toLong, None)
|
||||
}
|
||||
}
|
||||
}
|
||||
credit.remove(target.CharId)
|
||||
credit.values
|
||||
}
|
||||
|
||||
private def addTerminalContributionEntry(
|
||||
contributions: mutable.LongMap[ContributionStatsOutput],
|
||||
player: PlayerSource,
|
||||
implements: Seq[Int],
|
||||
percentage: Float
|
||||
): Unit = {
|
||||
val charId = player.CharId
|
||||
contributions.get(charId) match {
|
||||
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
|
||||
))
|
||||
case None =>
|
||||
contributions.put(charId, ContributionStatsOutput(player, implements, percentage))
|
||||
}
|
||||
}
|
||||
|
||||
private def findTimeApplicableActivities(
|
||||
activity: Seq[SupportActivity],
|
||||
killLastTime: JodaLocalDateTime,
|
||||
timeOffset: Int //s
|
||||
): Seq[SupportActivity] = {
|
||||
val dateTimeConverter: Date=>JodaLocalDateTime = JodaLocalDateTime.fromDateFields
|
||||
val milliToInstant: Long=>org.joda.time.Instant = Instant.ofEpochMilli
|
||||
val targetTime = killLastTime.minusSeconds(timeOffset)
|
||||
//find all activities that occurred after the kill time after the offset is considered
|
||||
activity
|
||||
.filter { a =>
|
||||
val activityTime = dateTimeConverter(milliToInstant(a.time).toDate)
|
||||
targetTime.isBefore(activityTime) || targetTime.equals(activityTime)
|
||||
}
|
||||
.sortBy(_.time)(Ordering.Long.reverse)
|
||||
}
|
||||
|
||||
private def allocateSupportAssists(
|
||||
longTerm: Seq[SupportActivity],
|
||||
shortTerm: Seq[SupportActivity],
|
||||
defaultTool: Int
|
||||
): mutable.LongMap[ContributionStats] = {
|
||||
val contributions: mutable.LongMap[ContributionStats] = mutable.LongMap[ContributionStats]()
|
||||
(longTerm ++ shortTerm).foreach { h =>
|
||||
val amount = h.amount
|
||||
val user = h.user
|
||||
val charId = user.CharId
|
||||
contributions.get(charId) match {
|
||||
case Some(entry) =>
|
||||
contributions.update(charId, entry.copy(
|
||||
amount = entry.amount + amount,
|
||||
total = entry.total + amount,
|
||||
shots = entry.shots + 1,
|
||||
time = math.max(entry.time, h.time)
|
||||
))
|
||||
case None =>
|
||||
contributions.put(charId, ContributionStats(user, Seq(WeaponStats(defaultTool, amount, 1, h.time)), amount, amount, 1, h.time))
|
||||
}
|
||||
}
|
||||
contributions
|
||||
}
|
||||
|
||||
private def collectSupportContributions(
|
||||
target: SourceEntry,
|
||||
time: JodaLocalDateTime,
|
||||
pairs: Seq[(InGameActivity, Int)],
|
||||
contributionPointsMap: (Float,Iterable[SupportActivity])=>(Long,ContributionStats)=>(Long,ContributionStatsOutput)
|
||||
): mutable.LongMap[ContributionStatsOutput] = {
|
||||
val (activity, tools) = pairs.unzip
|
||||
//TODO this is not correct, but it will do for now
|
||||
if (tools.isEmpty) {
|
||||
mutable.LongMap[ContributionStatsOutput]()
|
||||
} else if (tools.distinct.size == 1) {
|
||||
collectSupportAssists(target, time, activity, tools.head, contributionPointsMap)
|
||||
} else {
|
||||
var output = Iterable[ContributionStatsOutput]()
|
||||
tools.groupBy(identity).keys.foreach { implement =>
|
||||
output = ExperienceCalculator.onlyOriginalAssistEntriesIterable(
|
||||
output,
|
||||
collectSupportAssists(target, time, activity, implement, contributionPointsMap).values
|
||||
)
|
||||
}
|
||||
mutable.LongMap[ContributionStatsOutput]().addAll(output.map { entry => (entry.player.CharId, entry) })
|
||||
}
|
||||
}
|
||||
|
||||
private def collectSupportAssists(
|
||||
target: SourceEntry,
|
||||
killLastTime: JodaLocalDateTime,
|
||||
activity: Seq[InGameActivity],
|
||||
defaultImplement: Int,
|
||||
contributionPointsMap: (Float,Iterable[SupportActivity])=>(Long,ContributionStats)=>(Long,ContributionStatsOutput)
|
||||
): mutable.LongMap[ContributionStatsOutput] = {
|
||||
val activityByAnother = activity.collect {
|
||||
case activity: SupportActivity => activity
|
||||
}
|
||||
val longTermActivity = findTimeApplicableActivities(activityByAnother, killLastTime, timeOffset = 600)
|
||||
val shortTermActivity = findTimeApplicableActivities(activityByAnother, killLastTime, timeOffset = 300)
|
||||
val contributions = allocateSupportAssists(longTermActivity, shortTermActivity, defaultImplement)
|
||||
contributions.remove(target.CharId)
|
||||
val mapFunc = contributionPointsMap(contributions.values.foldLeft(0)(_ + _.total).toFloat, shortTermActivity)
|
||||
contributions.map { case (a, b) => mapFunc(a, b) }
|
||||
}
|
||||
|
||||
private def mapContributionPointsByPercentage(
|
||||
total: Float,
|
||||
compareList: Iterable[SupportActivity]
|
||||
)
|
||||
(
|
||||
charId: Long,
|
||||
contribution: ContributionStats,
|
||||
): (Long, ContributionStatsOutput) = {
|
||||
val user = contribution.player
|
||||
val unique = user.unique
|
||||
val points = contribution.amount
|
||||
val value = if (points < 75) {
|
||||
//a small contribution means the lower time limit
|
||||
if (compareList.exists { a => a.user.unique == unique }) {
|
||||
math.max(0.2f, points / total)
|
||||
} else {
|
||||
0f
|
||||
}
|
||||
} else {
|
||||
//large contribution is always okay
|
||||
if (points > 299) {
|
||||
1.0f
|
||||
} else if (points > 100) {
|
||||
0.75f
|
||||
} else {
|
||||
0.5f
|
||||
}
|
||||
}
|
||||
(charId, ContributionStatsOutput(user, contribution.weapons.map { _.weapon_id }, value))
|
||||
}
|
||||
|
||||
//noinspection ScalaUnusedSymbol
|
||||
private def mapContributionPointsByCount(
|
||||
total: Float,
|
||||
compareList: Iterable[SupportActivity]
|
||||
)
|
||||
(
|
||||
charId: Long,
|
||||
contribution: ContributionStats,
|
||||
): (Long, ContributionStatsOutput) = {
|
||||
(charId, ContributionStatsOutput(contribution.player, contribution.weapons.map { _.weapon_id }, compareList.size.toFloat))
|
||||
}
|
||||
}
|
||||
|
|
@ -438,6 +438,15 @@ class AvatarService(zone: Zone) extends Actor {
|
|||
)
|
||||
)
|
||||
|
||||
case AvatarAction.AwardSupportBep(charId, bep) =>
|
||||
AvatarEvents.publish(
|
||||
AvatarServiceResponse(
|
||||
s"/$forChannel/Avatar",
|
||||
Service.defaultPlayerGUID,
|
||||
AvatarResponse.AwardSupportBep(charId, bep)
|
||||
)
|
||||
)
|
||||
|
||||
case _ => ;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -156,6 +156,7 @@ object AvatarAction {
|
|||
final case class KitNotUsed(kit_guid: PlanetSideGUID, msg: String) extends Action
|
||||
|
||||
final case class UpdateKillsDeathsAssists(charId: Long, kda: KDAStat) extends Action
|
||||
final case class AwardSupportBep(charId: Long, bep: Long) extends Action
|
||||
|
||||
final case class TeardownConnection() extends Action
|
||||
// final case class PlayerStateShift(killer : PlanetSideGUID, victim : PlanetSideGUID) extends Action
|
||||
|
|
|
|||
|
|
@ -127,4 +127,5 @@ object AvatarResponse {
|
|||
final case class KitNotUsed(kit_guid: PlanetSideGUID, msg: String) extends Response
|
||||
|
||||
final case class UpdateKillsDeathsAssists(charId: Long, kda: KDAStat) extends Response
|
||||
final case class AwardSupportBep(charId: Long, bep: Long) extends Response
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue