mirror of
https://github.com/psforever/PSF-LoginServer.git
synced 2026-07-16 08:55:18 +00:00
events for mounting and dismounting of both passenger and cargo; id'ing the installation of an amenity (vehicle or facility); separation of kill/assist experience and support experience calculations; retention of kill record which allows for the calculation of menace
This commit is contained in:
parent
da6ea21147
commit
4b87526e4d
16 changed files with 367 additions and 140 deletions
|
|
@ -2473,7 +2473,11 @@ class SessionData(
|
|||
continent.id,
|
||||
LocalAction.RouterTelepadTransport(pguid, pguid, sguid, dguid)
|
||||
)
|
||||
player.LogActivity(VehicleDismountActivity(VehicleSource(router), PlayerSource(player)))
|
||||
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))
|
||||
} else {
|
||||
log.warn(s"UseRouterTelepadSystem: ${player.Name} can not teleport")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import net.psforever.objects.avatar.scoring.Kill
|
|||
import net.psforever.objects.serverobject.affinity.FactionAffinity
|
||||
import net.psforever.objects.sourcing.SourceEntry
|
||||
import net.psforever.objects.vital.{InGameActivity, InGameHistory}
|
||||
import net.psforever.objects.zones.exp.ExperienceCalculator
|
||||
import net.psforever.objects.zones.exp.{ExperienceCalculator, SupportExperienceCalculator}
|
||||
import net.psforever.util.Database._
|
||||
import net.psforever.persistence
|
||||
|
||||
|
|
@ -87,6 +87,7 @@ class ZoneActor(context: ActorContext[ZoneActor.Command], zone: Zone)
|
|||
private[this] val log = org.log4s.getLogger
|
||||
private val players: mutable.ListBuffer[Player] = mutable.ListBuffer()
|
||||
private val experience: ActorRef[ExperienceCalculator.Command] = context.spawnAnonymous(ExperienceCalculator(zone))
|
||||
private val supportExperience: ActorRef[SupportExperienceCalculator.Command] = context.spawnAnonymous(SupportExperienceCalculator(zone))
|
||||
|
||||
zone.actor = context.self
|
||||
zone.init(context.toClassic)
|
||||
|
|
@ -154,7 +155,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)
|
||||
supportExperience ! SupportExperienceCalculator.RewardOurSupporters(target, history, kill, bep)
|
||||
|
||||
case ZoneMapUpdate() =>
|
||||
zone.Buildings
|
||||
|
|
|
|||
|
|
@ -348,7 +348,7 @@ object MineDeployableControl {
|
|||
jumping = false,
|
||||
ExoSuitDefinition.Select(exosuit, faction),
|
||||
bep = 0,
|
||||
kills = Nil,
|
||||
progress = PlayerSource.Nobody.progress,
|
||||
UniquePlayer(charId, name, CharacterSex.Male, mine.Faction)
|
||||
)
|
||||
case None =>
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ final case class AmenitySource(
|
|||
health: Int,
|
||||
Orientation: Vector3,
|
||||
occupants: List[SourceEntry],
|
||||
installation: SourceEntry,
|
||||
hacked: Option[HackInfo],
|
||||
unique: UniqueAmenity
|
||||
) extends SourceWithHealthEntry {
|
||||
|
|
@ -54,6 +55,7 @@ object AmenitySource {
|
|||
health,
|
||||
obj.Orientation,
|
||||
Nil,
|
||||
SourceEntry(obj.Owner),
|
||||
hackData,
|
||||
sourcing.UniqueAmenity(obj.Zone.Number, obj.GUID, obj.Position)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package net.psforever.objects.sourcing
|
||||
|
||||
import net.psforever.objects.avatar.scoring.Life
|
||||
import net.psforever.objects.definition.{AvatarDefinition, ExoSuitDefinition}
|
||||
import net.psforever.objects.serverobject.affinity.FactionAffinity
|
||||
import net.psforever.objects.serverobject.mount.Mountable
|
||||
|
|
@ -28,7 +29,7 @@ final case class PlayerSource(
|
|||
jumping: Boolean,
|
||||
Modifiers: ResistanceProfile,
|
||||
bep: Long,
|
||||
kills: Seq[Any],
|
||||
progress: Life,
|
||||
unique: UniquePlayer
|
||||
) extends SourceWithHealthEntry {
|
||||
override def Name: String = unique.name
|
||||
|
|
@ -46,6 +47,7 @@ object PlayerSource {
|
|||
val exosuit = p.ExoSuit
|
||||
val faction = p.Faction
|
||||
val seatedEntity = mountableAndSeat(p)
|
||||
val avatar = p.avatar
|
||||
PlayerSource(
|
||||
p.Definition,
|
||||
exosuit,
|
||||
|
|
@ -58,8 +60,8 @@ object PlayerSource {
|
|||
p.Crouching,
|
||||
p.Jumping,
|
||||
ExoSuitDefinition.Select(exosuit, faction),
|
||||
p.avatar.bep,
|
||||
kills = Nil,
|
||||
avatar.bep,
|
||||
progress = avatar.scorecard.CurrentLife,
|
||||
UniquePlayer(p.CharId, p.Name, p.Sex, faction)
|
||||
)
|
||||
}
|
||||
|
|
@ -78,7 +80,7 @@ object PlayerSource {
|
|||
jumping = false,
|
||||
GlobalDefinitions.Standard,
|
||||
bep = 0L,
|
||||
kills = Nil,
|
||||
progress = tokenLife,
|
||||
UniquePlayer(0L, name, CharacterSex.Male, faction)
|
||||
)
|
||||
}
|
||||
|
|
@ -116,6 +118,7 @@ object PlayerSource {
|
|||
def inSeat(player: Player, source: SourceEntry, seatNumber: Int): PlayerSource = {
|
||||
val exosuit = player.ExoSuit
|
||||
val faction = player.Faction
|
||||
val avatar = player.avatar
|
||||
PlayerSource(
|
||||
player.Definition,
|
||||
exosuit,
|
||||
|
|
@ -128,8 +131,8 @@ object PlayerSource {
|
|||
player.Crouching,
|
||||
player.Jumping,
|
||||
ExoSuitDefinition.Select(exosuit, faction),
|
||||
player.avatar.bep,
|
||||
kills = Nil,
|
||||
avatar.bep,
|
||||
progress = tokenLife,
|
||||
UniquePlayer(player.CharId, player.Name, player.Sex, faction)
|
||||
)
|
||||
}
|
||||
|
|
@ -142,4 +145,6 @@ object PlayerSource {
|
|||
* the others first: this will be my guest-gift to you.”
|
||||
*/
|
||||
final val Nobody = PlayerSource("Nobody", PlanetSideEmpire.NEUTRAL, Vector3.Zero)
|
||||
|
||||
private val tokenLife: Life = Life()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,12 +5,9 @@ import akka.actor.{Actor, Cancellable}
|
|||
import net.psforever.actors.zone.ZoneActor
|
||||
import net.psforever.objects.zones.Zone
|
||||
import net.psforever.objects._
|
||||
import net.psforever.packet.game.{
|
||||
CargoMountPointStatusMessage,
|
||||
ObjectAttachMessage,
|
||||
ObjectDetachMessage,
|
||||
PlanetsideAttributeMessage
|
||||
}
|
||||
import net.psforever.objects.sourcing.VehicleSource
|
||||
import net.psforever.objects.vital.VehicleCargoMountActivity
|
||||
import net.psforever.packet.game.{CargoMountPointStatusMessage, ObjectAttachMessage, ObjectDetachMessage, PlanetsideAttributeMessage}
|
||||
import net.psforever.types.{BailType, CargoStatus, PlanetSideGUID, Vector3}
|
||||
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||
import net.psforever.services.Service
|
||||
|
|
@ -117,7 +114,7 @@ trait CarrierBehavior {
|
|||
case _ =>
|
||||
obj.CargoHold(mountPoint) match {
|
||||
case Some(hold) if hold.isOccupied && hold.occupant.get.GUID == cargo_guid =>
|
||||
hold.unmount(hold.occupant.get)
|
||||
CarrierBehavior.CargoDismountAction(obj, hold.occupant.get, hold, BailType.Normal)
|
||||
case _ => ;
|
||||
}
|
||||
false
|
||||
|
|
@ -213,10 +210,8 @@ object CarrierBehavior {
|
|||
if (distance <= 64) {
|
||||
//cargo vehicle is close enough to assume to be physically within the carrier's hold; mount it
|
||||
log.debug(s"HandleCheckCargoMounting: mounting cargo vehicle in carrier at distance of $distance")
|
||||
hold.mount(cargo)
|
||||
cargo.MountedIn = carrierGUID
|
||||
CargoMountAction(carrier, cargo, hold, carrierGUID)
|
||||
cargo.Velocity = None
|
||||
cargo.Actor ! CargoBehavior.EndCargoMounting(carrierGUID)
|
||||
zone.VehicleEvents ! VehicleServiceMessage(
|
||||
s"${cargo.Actor}",
|
||||
VehicleAction.SendResponse(PlanetSideGUID(0), PlanetsideAttributeMessage(cargoGUID, 0, cargo.Health))
|
||||
|
|
@ -358,9 +353,7 @@ object CarrierBehavior {
|
|||
//obviously, don't do this
|
||||
} else if (iteration > 40) {
|
||||
//cargo vehicle has spent too long not getting far enough away; restore the cargo's mount in the carrier hold
|
||||
hold.mount(cargo)
|
||||
cargo.MountedIn = carrierGUID
|
||||
cargo.Actor ! CargoBehavior.EndCargoMounting(carrierGUID)
|
||||
CargoMountAction(carrier, cargo, hold, carrierGUID)
|
||||
CargoMountBehaviorForAll(carrier, cargo, mountPoint)
|
||||
zone.actor ! ZoneActor.RemoveFromBlockMap(cargo)
|
||||
false
|
||||
|
|
@ -441,9 +434,10 @@ object CarrierBehavior {
|
|||
val zone = carrier.Zone
|
||||
carrier.CargoHolds.find({ case (_, hold) => hold.occupant.contains(cargo) }) match {
|
||||
case Some((mountPoint, hold)) =>
|
||||
cargo.MountedIn = None
|
||||
hold.unmount(
|
||||
CarrierBehavior.CargoDismountAction(
|
||||
carrier,
|
||||
cargo,
|
||||
hold,
|
||||
if (bailed) BailType.Bailed else if (kicked) BailType.Kicked else BailType.Normal
|
||||
)
|
||||
val driverOpt = cargo.Seats(0).occupant
|
||||
|
|
@ -653,4 +647,40 @@ object CarrierBehavior {
|
|||
)
|
||||
msgs
|
||||
}
|
||||
|
||||
/**
|
||||
* na
|
||||
* @param carrier the ferrying vehicle
|
||||
* @param cargo the ferried vehicle
|
||||
* @param hold na
|
||||
* @param carrierGuid the ferrying vehicle's unique identifier
|
||||
*/
|
||||
private def CargoMountAction(
|
||||
carrier: Vehicle,
|
||||
cargo: Vehicle,
|
||||
hold: Cargo,
|
||||
carrierGuid: PlanetSideGUID): Unit = {
|
||||
hold.mount(cargo)
|
||||
cargo.MountedIn = carrierGuid
|
||||
cargo.LogActivity(VehicleCargoMountActivity(VehicleSource(carrier), VehicleSource(cargo), carrier.Zone.Number))
|
||||
cargo.Actor ! CargoBehavior.EndCargoMounting(carrierGuid)
|
||||
}
|
||||
|
||||
/**
|
||||
* na
|
||||
* @param carrier the ferrying vehicle
|
||||
* @param cargo the ferried vehicle
|
||||
* @param hold na
|
||||
* @param bailType na
|
||||
*/
|
||||
private def CargoDismountAction(
|
||||
carrier: Vehicle,
|
||||
cargo: Vehicle,
|
||||
hold: Cargo,
|
||||
bailType: BailType.Value
|
||||
): Unit = {
|
||||
cargo.MountedIn = None
|
||||
hold.unmount(cargo, bailType)
|
||||
cargo.LogActivity(VehicleCargoMountActivity(VehicleSource(carrier), VehicleSource(cargo), carrier.Zone.Number))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -170,8 +170,8 @@ class BfrControl(vehicle: Vehicle)
|
|||
specialArmWeaponEquipManagement(item, slot, handiness)
|
||||
}
|
||||
|
||||
override def dismountCleanup(seatBeingDismounted: Int): Unit = {
|
||||
super.dismountCleanup(seatBeingDismounted)
|
||||
override def dismountCleanup(seatBeingDismounted: Int, player: Player): Unit = {
|
||||
super.dismountCleanup(seatBeingDismounted, player)
|
||||
if (!vehicle.Seats.values.exists(_.isOccupied)) {
|
||||
vehicle.Subsystems(VehicleSubsystemEntry.BattleframeShieldGenerator) match {
|
||||
case Some(subsys) =>
|
||||
|
|
|
|||
|
|
@ -34,9 +34,9 @@ class DeployingVehicleControl(vehicle: Vehicle)
|
|||
case msg : Deployment.TryUndeploy =>
|
||||
deployBehavior.apply(msg)
|
||||
|
||||
case msg @ Mountable.TryDismount(_, seat_num, _) =>
|
||||
case msg @ Mountable.TryDismount(player, seat_num, _) =>
|
||||
dismountBehavior.apply(msg)
|
||||
dismountCleanup(seat_num)
|
||||
dismountCleanup(seat_num, player)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -20,10 +20,10 @@ import net.psforever.objects.serverobject.hackable.GenericHackables
|
|||
import net.psforever.objects.serverobject.mount.{Mountable, MountableBehavior}
|
||||
import net.psforever.objects.serverobject.repair.RepairableVehicle
|
||||
import net.psforever.objects.serverobject.terminals.Terminal
|
||||
import net.psforever.objects.sourcing.{SourceEntry, VehicleSource}
|
||||
import net.psforever.objects.sourcing.{PlayerSource, SourceEntry, VehicleSource}
|
||||
import net.psforever.objects.vehicles._
|
||||
import net.psforever.objects.vital.interaction.{DamageInteraction, DamageResult}
|
||||
import net.psforever.objects.vital.{DamagingActivity, InGameActivity, ShieldCharge}
|
||||
import net.psforever.objects.vital.{DamagingActivity, InGameActivity, ShieldCharge, VehicleDismountActivity, VehicleMountActivity}
|
||||
import net.psforever.objects.vital.environment.EnvironmentReason
|
||||
import net.psforever.objects.vital.etc.SuicideReason
|
||||
import net.psforever.objects.zones._
|
||||
|
|
@ -128,9 +128,9 @@ class VehicleControl(vehicle: Vehicle)
|
|||
mountBehavior.apply(msg)
|
||||
mountCleanup(mount_point, player)
|
||||
|
||||
case msg @ Mountable.TryDismount(_, seat_num, _) =>
|
||||
case msg @ Mountable.TryDismount(player, seat_num, _) =>
|
||||
dismountBehavior.apply(msg)
|
||||
dismountCleanup(seat_num)
|
||||
dismountCleanup(seat_num, player)
|
||||
|
||||
case CommonMessages.ChargeShields(amount, motivator) =>
|
||||
chargeShields(amount, motivator.collect { case o: PlanetSideGameObject with FactionAffinity => SourceEntry(o) })
|
||||
|
|
@ -237,9 +237,9 @@ class VehicleControl(vehicle: Vehicle)
|
|||
|
||||
def commonDisabledBehavior: Receive = checkBehavior
|
||||
.orElse {
|
||||
case msg @ Mountable.TryDismount(_, seat_num, _) =>
|
||||
case msg @ Mountable.TryDismount(user, seat_num, _) =>
|
||||
dismountBehavior.apply(msg)
|
||||
dismountCleanup(seat_num)
|
||||
dismountCleanup(seat_num, user)
|
||||
|
||||
case Vehicle.Deconstruct(time) =>
|
||||
time match {
|
||||
|
|
@ -297,6 +297,8 @@ class VehicleControl(vehicle: Vehicle)
|
|||
val obj = MountableObject
|
||||
obj.PassengerInSeat(user) match {
|
||||
case Some(seatNumber) =>
|
||||
val vsrc = VehicleSource(vehicle)
|
||||
user.LogActivity(VehicleMountActivity(vsrc, PlayerSource.inSeat(user, vehicle, vsrc), vehicle.Zone.Number))
|
||||
//if the driver mount, change ownership if that is permissible for this vehicle
|
||||
if (seatNumber == 0 && !obj.OwnerName.contains(user.Name) && obj.Definition.CanBeOwned.nonEmpty) {
|
||||
//whatever vehicle was previously owned
|
||||
|
|
@ -325,7 +327,7 @@ class VehicleControl(vehicle: Vehicle)
|
|||
vehicle.DeploymentState == DriveState.Deployed || super.dismountTest(obj, seatNumber, user)
|
||||
}
|
||||
|
||||
def dismountCleanup(seatBeingDismounted: Int): Unit = {
|
||||
def dismountCleanup(seatBeingDismounted: Int, user: Player): Unit = {
|
||||
val obj = MountableObject
|
||||
// Reset velocity to zero when driver dismounts, to allow jacking/repair if vehicle was moving slightly before dismount
|
||||
if (!obj.Seats(0).isOccupied) {
|
||||
|
|
@ -340,6 +342,7 @@ class VehicleControl(vehicle: Vehicle)
|
|||
)
|
||||
}
|
||||
if (!obj.Seats(seatBeingDismounted).isOccupied) { //seat was vacated
|
||||
user.LogActivity(VehicleDismountActivity(VehicleSource(vehicle), PlayerSource(user), vehicle.Zone.Number))
|
||||
//we were only owning the vehicle while we sat in its driver seat
|
||||
val canBeOwned = obj.Definition.CanBeOwned
|
||||
if (canBeOwned.contains(false) && seatBeingDismounted == 0) {
|
||||
|
|
|
|||
|
|
@ -50,8 +50,30 @@ final case class ShieldCharge(amount: Int, cause: Option[SourceEntry])
|
|||
final case class TerminalUsedActivity(terminal: AmenitySource, transaction: TransactionType.Value)
|
||||
extends GeneralActivity
|
||||
|
||||
final case class VehicleDismountActivity(vehicle: VehicleSource, player: PlayerSource)
|
||||
extends GeneralActivity
|
||||
sealed trait VehicleMountChange extends GeneralActivity {
|
||||
def vehicle: VehicleSource
|
||||
def zoneNumber: Int
|
||||
}
|
||||
|
||||
sealed trait VehiclePassengerMountChange extends VehicleMountChange {
|
||||
def player: PlayerSource
|
||||
}
|
||||
|
||||
sealed trait VehicleCargoMountChange extends VehicleMountChange {
|
||||
def cargo: VehicleSource
|
||||
}
|
||||
|
||||
final case class VehicleMountActivity(vehicle: VehicleSource, player: PlayerSource, zoneNumber: Int)
|
||||
extends VehiclePassengerMountChange
|
||||
|
||||
final case class VehicleDismountActivity(vehicle: VehicleSource, player: PlayerSource, zoneNumber: Int)
|
||||
extends VehiclePassengerMountChange
|
||||
|
||||
final case class VehicleCargoMountActivity(vehicle: VehicleSource, cargo: VehicleSource, zoneNumber: Int)
|
||||
extends VehicleCargoMountChange
|
||||
|
||||
final case class VehicleCargoDismountActivity(vehicle: VehicleSource, cargo: VehicleSource, zoneNumber: Int)
|
||||
extends VehicleCargoMountChange
|
||||
|
||||
final case class Contribution(src: SourceUniqueness, entries: List[InGameActivity])
|
||||
extends GeneralActivity {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ package net.psforever.objects.zones.exp
|
|||
import akka.actor.typed.scaladsl.{AbstractBehavior, ActorContext, Behaviors}
|
||||
import akka.actor.typed.{Behavior, SupervisorStrategy}
|
||||
import net.psforever.objects.PlanetSideGameObject
|
||||
import net.psforever.objects.avatar.scoring.Kill
|
||||
import net.psforever.objects.serverobject.affinity.FactionAffinity
|
||||
import net.psforever.objects.sourcing.{PlayerSource, SourceEntry}
|
||||
import net.psforever.objects.vital.{InGameActivity, InGameHistory}
|
||||
|
|
@ -27,45 +26,6 @@ object ExperienceCalculator {
|
|||
RewardThisDeath(SourceEntry(obj), obj.LastDamage, obj.History)
|
||||
}
|
||||
}
|
||||
|
||||
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 calculateExperience(
|
||||
victim: PlayerSource,
|
||||
history: Iterable[InGameActivity]
|
||||
): Long = {
|
||||
val lifespan = (history.headOption, history.lastOption) match {
|
||||
case (Some(spawn), Some(death)) => death.time - spawn.time
|
||||
case _ => 0L
|
||||
}
|
||||
val wasEverAMax = Support.wasEverAMax(victim, history)
|
||||
val base = if (wasEverAMax) { //shamed
|
||||
250L
|
||||
} else if (victim.Seated || victim.kills.nonEmpty) {
|
||||
100L
|
||||
} else if (lifespan > 15000L) {
|
||||
50L
|
||||
} else {
|
||||
1L
|
||||
}
|
||||
if (base > 1) {
|
||||
//black ops modifier
|
||||
//TODO x10
|
||||
base
|
||||
} else {
|
||||
base
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed case class InheritedAssistEntry(assists: Iterable[ContributionStatsOutput]) {
|
||||
val time: Long = System.currentTimeMillis()
|
||||
}
|
||||
|
||||
class ExperienceCalculator(context: ActorContext[ExperienceCalculator.Command], zone: Zone)
|
||||
|
|
@ -78,9 +38,6 @@ class ExperienceCalculator(context: ActorContext[ExperienceCalculator.Command],
|
|||
case RewardThisDeath(victim: PlayerSource, lastDamage, history) =>
|
||||
KillAssists.rewardThisPlayerDeath(victim, lastDamage, history, zone.AvatarEvents)
|
||||
|
||||
case RewardOurSupporters(target: PlayerSource, history, kill, bep) =>
|
||||
KillContributions.rewardTheseSupporters(target, history, kill, bep, zone.AvatarEvents)
|
||||
|
||||
case _ => ()
|
||||
}
|
||||
Behaviors.same
|
||||
|
|
|
|||
|
|
@ -24,12 +24,7 @@ object KillAssists {
|
|||
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")
|
||||
Nil
|
||||
} else {
|
||||
history.slice(spawnIndex, endIndex)
|
||||
}
|
||||
|
|
@ -54,13 +49,57 @@ object KillAssists {
|
|||
.collect { case (dam, Some(adv)) => (dam, adv.attacker) }
|
||||
}
|
||||
|
||||
private[exp] def calculateExperience(
|
||||
killer: PlayerSource,
|
||||
victim: PlayerSource,
|
||||
history: Iterable[InGameActivity]
|
||||
): Long = {
|
||||
/**
|
||||
* "Menace" is a crude measurement of how much consistent destructive power a player has been demonstrating.
|
||||
* Within the last ten kills, the rate of the player's killing speed is measured.
|
||||
* The measurement - a "streak" in modern lingo - is transformed into the form of an `Integer` for simplicity.
|
||||
* @param player the player
|
||||
* @param mercy a time value that can be used to continue a missed streak
|
||||
* @return an `Integer` between 0 and 7
|
||||
*/
|
||||
private[exp] def calculateMenace(player: PlayerSource, mercy: Long = 2500L): Int = {
|
||||
val allKills = player.progress.kills.reverse
|
||||
val restBetweenKills = allKills.take(10) match {
|
||||
case firstKill :: kills if kills.size == 9 =>
|
||||
var xTime = firstKill.time.toDate.getTime
|
||||
kills.map { kill =>
|
||||
val time = kill.time.toDate.getTime
|
||||
val timeOut = time - xTime
|
||||
xTime = time
|
||||
timeOut
|
||||
}
|
||||
case _ =>
|
||||
Nil
|
||||
}
|
||||
//TODO the math here is not very meaningful
|
||||
math.floor(math.sqrt(
|
||||
math.max(0, takeWhileDelay(restBetweenKills, testValue = 20000L, mercy).size - 1) +
|
||||
math.max(0, takeWhileDelay(restBetweenKills, testValue = 10000L, mercy).size - 5) * 3 +
|
||||
math.max(0, takeWhileDelay(restBetweenKills, testValue = 5000L, mercy = 1000L).size - 4) * 5
|
||||
)).toInt
|
||||
}
|
||||
|
||||
private def takeWhileDelay(list: Iterable[Long], testValue: Long, mercy: Long = 2500L): Iterable[Long] = {
|
||||
var onGoingMercy: Long = mercy
|
||||
list.takeWhile { time =>
|
||||
if (time < testValue) {
|
||||
true
|
||||
} else if (time - onGoingMercy - 1 < testValue) {
|
||||
onGoingMercy = math.ceil(onGoingMercy * 0.65).toLong
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def calculateExperience(
|
||||
killer: PlayerSource,
|
||||
victim: PlayerSource,
|
||||
history: Iterable[InGameActivity]
|
||||
): Long = {
|
||||
//base value (the kill experience before modifiers)
|
||||
val base = ExperienceCalculator.calculateExperience(victim, history)
|
||||
val base = Support.baseExperience(victim, history)
|
||||
if (base > 1) {
|
||||
//battle rank disparity modifiers
|
||||
val battleRankDisparity = {
|
||||
|
|
@ -81,7 +120,9 @@ object KillAssists {
|
|||
math.floor(-0.15f * base - killerLevel + victimLevel).toLong
|
||||
}
|
||||
}
|
||||
math.max(1, base + battleRankDisparity)
|
||||
//menace modifiers
|
||||
val menace = 1f + calculateMenace(victim) * 0.14f
|
||||
math.max(1, (base + battleRankDisparity) * menace).toLong
|
||||
} else {
|
||||
base
|
||||
}
|
||||
|
|
@ -105,7 +146,7 @@ object KillAssists {
|
|||
|
||||
case _ =>
|
||||
val assists = collectKillAssistsForPlayer(victim, shortHistory, None)
|
||||
val fullBep = ExperienceCalculator.calculateExperience(victim, shortHistory)
|
||||
val fullBep = Support.baseExperience(victim, shortHistory)
|
||||
val hitSquad = assists.map {
|
||||
case ContributionStatsOutput(p, w, r) => (p, Assist(victim, w, r, (fullBep * r).toLong))
|
||||
}.toSeq
|
||||
|
|
|
|||
|
|
@ -2,13 +2,14 @@
|
|||
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.serverobject.hackable.Hackable.HackInfo
|
||||
import net.psforever.objects.sourcing.{AmenitySource, PlayerSource, SourceEntry, SourceUniqueness, VehicleSource}
|
||||
import net.psforever.objects.vital.{Contribution, InGameActivity, RevivingActivity, TerminalUsedActivity, VehicleDismountActivity}
|
||||
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.services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||
import net.psforever.types.PlanetSideEmpire
|
||||
import net.psforever.types.{PlanetSideEmpire, Vector3}
|
||||
|
||||
import scala.collection.mutable
|
||||
|
||||
|
|
@ -22,7 +23,10 @@ object KillContributions {
|
|||
medical_terminal,
|
||||
adv_med_terminal,
|
||||
crystals_health_a,
|
||||
crystals_health_b
|
||||
crystals_health_b,
|
||||
bfr_rearm_terminal,
|
||||
multivehicle_rearm_terminal,
|
||||
lodestar_repair_terminal
|
||||
).collect { _.ObjectId }
|
||||
}
|
||||
|
||||
|
|
@ -49,25 +53,34 @@ object KillContributions {
|
|||
)
|
||||
}
|
||||
//sort by applicable time periods, as long as the longer period is represented by activity
|
||||
val otherContributionCalculations = contributionScoringAndCulling(faction, kill, Seq(target.CharId), contributions, bep)(_, _)
|
||||
val finalContributions = if (longHistory.isEmpty) {
|
||||
val contributionProcess = new CombinedHealthAndArmorContributionProcess(faction, contributions, Nil)
|
||||
contributionProcess.submit(shortHistory)
|
||||
val contributionEntries = otherContributionCalculations(shortHistory, contributionProcess.output())
|
||||
contributionEntries
|
||||
.keys
|
||||
.map { composeContributionOutput(_, contributionEntries, contributionEntries, bep) }
|
||||
} else {
|
||||
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())
|
||||
(longContributionEntries.keys ++ shortContributionEntries.keys)
|
||||
longContributionEntries.remove(target.CharId)
|
||||
longContributionEntries.remove(kill.victim.CharId)
|
||||
shortContributionEntries.remove(target.CharId)
|
||||
shortContributionEntries.remove(kill.victim.CharId)
|
||||
(longContributionEntries ++ shortContributionEntries)
|
||||
.toSeq
|
||||
.distinct
|
||||
.map { composeContributionOutput(_, shortContributionEntries, longContributionEntries, bep) }
|
||||
.distinctBy(_._2.player.unique)
|
||||
.map { case (_, stats) =>
|
||||
composeContributionOutput(stats.player, shortContributionEntries, longContributionEntries, bep)
|
||||
}
|
||||
} else {
|
||||
val contributionProcess = new CombinedHealthAndArmorContributionProcess(faction, contributions, Nil)
|
||||
contributionProcess.submit(shortHistory)
|
||||
val contributionEntries = otherContributionCalculations(shortHistory, contributionProcess.output())
|
||||
contributionEntries.remove(target.CharId)
|
||||
contributionEntries.remove(kill.victim.CharId)
|
||||
contributionEntries
|
||||
.map { case (_, stats) =>
|
||||
composeContributionOutput(stats.player, contributionEntries, contributionEntries, bep)
|
||||
}
|
||||
}
|
||||
//take the output and transform that into contribution distribution data
|
||||
val victim = kill.victim
|
||||
|
|
@ -98,7 +111,6 @@ object KillContributions {
|
|||
private def contributionScoringAndCulling(
|
||||
faction: PlanetSideEmpire.Value,
|
||||
kill: Kill,
|
||||
excludeTargets: Seq[Long],
|
||||
contributions: Map[SourceUniqueness, List[InGameActivity]],
|
||||
bep: Long
|
||||
)
|
||||
|
|
@ -113,11 +125,11 @@ object KillContributions {
|
|||
contributionEntries.put(id, stat.copy(weapons = newWeaponStats))
|
||||
}
|
||||
contributeWithKillWhileMountedActivity(faction, kill, history, contributionEntries)
|
||||
contributeWithVehicleTransportActivity(history, contributionEntries)
|
||||
contributeWithRevivalActivity(history, contributionEntries)
|
||||
contributeWithVehicleTransportActivity(history, contributionEntries)
|
||||
contributeWithVehicleCargoTransportActivity(history, contributionEntries)
|
||||
contributeWithTerminalActivity(faction, history, contributions, contributionEntries)
|
||||
contributionEntries.remove(0)
|
||||
excludeTargets.foreach { contributionEntries.remove }
|
||||
contributionEntries
|
||||
}
|
||||
|
||||
|
|
@ -185,23 +197,99 @@ object KillContributions {
|
|||
history: List[InGameActivity],
|
||||
participants: mutable.LongMap[ContributionStats]
|
||||
): List[InGameActivity] = {
|
||||
val shortHistory = history.collect { case out: VehicleDismountActivity => out }
|
||||
shortHistory
|
||||
/*
|
||||
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
|
||||
*/
|
||||
val dismountActivity = history
|
||||
.collect {
|
||||
case inAndOut: VehiclePassengerMountChange
|
||||
if !inAndOut.vehicle.owner.contains(inAndOut.player) => inAndOut
|
||||
}
|
||||
.grouped(2)
|
||||
.collect {
|
||||
case List(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 distanceMoved = Vector3.DistanceSquared(in.vehicle.Position.xy, out.vehicle.Position.xy)
|
||||
val timeSpent = out.time - in.time
|
||||
timeSpent >= 210000 /* 3:30 */ ||
|
||||
(sameZone && (distanceMoved > 160000f || distanceMoved > 10000f && timeSpent >= 60000)) |
|
||||
(!sameZone && (distanceMoved > 10000f || timeSpent >= 120000))
|
||||
}) =>
|
||||
out
|
||||
}.toList
|
||||
//apply
|
||||
dismountActivity
|
||||
.groupBy { a => a.vehicle.owner }
|
||||
.foreach { case (Some(owner), dismountsFromVehicle) =>
|
||||
.collect { case (Some(owner), dismountsFromVehicle) =>
|
||||
val numberOfDismounts = dismountsFromVehicle.size
|
||||
contributeWithCombinedActivity(
|
||||
owner.CharId,
|
||||
dismountsFromVehicle.map { act => WeaponStats(act.vehicle.Definition.ObjectId, 1, 1, act.time, 15f) },
|
||||
dismountsFromVehicle.map { act => WeaponStats(act.vehicle.Definition.ObjectId, 0, numberOfDismounts, act.time, 15f) },
|
||||
dismountsFromVehicle.head.vehicle.owner.get,
|
||||
numberOfDismounts,
|
||||
numberOfDismounts,
|
||||
amount = 0,
|
||||
total = 0,
|
||||
numberOfDismounts,
|
||||
dismountsFromVehicle.maxBy(_.time).time,
|
||||
participants
|
||||
)
|
||||
}
|
||||
shortHistory
|
||||
dismountActivity
|
||||
}
|
||||
|
||||
private def contributeWithVehicleCargoTransportActivity(
|
||||
history: List[InGameActivity],
|
||||
participants: mutable.LongMap[ContributionStats]
|
||||
): List[InGameActivity] = {
|
||||
/*
|
||||
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
|
||||
*/
|
||||
val dismountActivity = history
|
||||
.collect {
|
||||
case inAndOut: VehicleCargoMountChange if inAndOut.vehicle.owner.nonEmpty => inAndOut
|
||||
}
|
||||
.grouped(2)
|
||||
.collect {
|
||||
case List(in: VehicleCargoMountActivity, out: VehicleCargoDismountActivity)
|
||||
if in.vehicle.unique == out.vehicle.unique &&
|
||||
out.vehicle.Faction == out.cargo.Faction &&
|
||||
(in.vehicle.Definition == GlobalDefinitions.router || {
|
||||
val distanceMoved = Vector3.DistanceSquared(in.vehicle.Position.xy, out.vehicle.Position.xy)
|
||||
val timeSpent = out.time - in.time
|
||||
timeSpent >= 210000 /* 3:30 */ || distanceMoved > 640000f
|
||||
}) =>
|
||||
out
|
||||
}.toList
|
||||
//apply
|
||||
dismountActivity
|
||||
.groupBy { a => a.vehicle.owner }
|
||||
.collect { case (Some(owner), dismountsFromVehicle) =>
|
||||
val numberOfDismounts = dismountsFromVehicle.size
|
||||
contributeWithCombinedActivity(
|
||||
owner.CharId,
|
||||
dismountsFromVehicle.map { act => WeaponStats(act.vehicle.Definition.ObjectId, 0, numberOfDismounts, act.time, 15f) },
|
||||
dismountsFromVehicle.head.vehicle.owner.get,
|
||||
amount = 0,
|
||||
total = 0,
|
||||
numberOfDismounts,
|
||||
dismountsFromVehicle.maxBy(_.time).time,
|
||||
participants
|
||||
)
|
||||
}
|
||||
dismountActivity
|
||||
}
|
||||
|
||||
private def contributeWithRevivalActivity(
|
||||
|
|
@ -251,11 +339,22 @@ object KillContributions {
|
|||
case (t, terminal, Some(info)) if terminal.Faction != faction =>
|
||||
participants.getOrElseUpdate(
|
||||
info.player.CharId,
|
||||
ContributionStats(info.player, Seq(WeaponStats(0, 1, 1, t.time, 10f)), 1, 1, 1, t.time)
|
||||
ContributionStats(info.player, Seq(WeaponStats(terminal.Definition.ObjectId, 0, 1, t.time, 10f)), 0, 0, 1, t.time)
|
||||
)
|
||||
t
|
||||
case (t, terminal, _) =>
|
||||
extractContributionsForEntityByUser(faction, terminal, contributions, participants)
|
||||
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)
|
||||
)
|
||||
}
|
||||
case _ =>
|
||||
extractContributionsForEntityByUser(faction, terminal, contributions, participants)
|
||||
}
|
||||
t
|
||||
}.toList
|
||||
}
|
||||
|
|
@ -317,32 +416,33 @@ object KillContributions {
|
|||
}
|
||||
|
||||
private def composeContributionOutput(
|
||||
charId: Long,
|
||||
player: PlayerSource,
|
||||
shortPeriod: mutable.LongMap[ContributionStats],
|
||||
longPeriod: mutable.LongMap[ContributionStats],
|
||||
bep: Long
|
||||
): (Long, ContributionStatsOutput) = {
|
||||
shortPeriod
|
||||
val charId = player.CharId
|
||||
longPeriod
|
||||
.get(charId)
|
||||
.collect {
|
||||
case entry =>
|
||||
val weapons = entry.weapons
|
||||
(
|
||||
entry.player,
|
||||
weapons.map { _.equipment_id },
|
||||
math.min(weapons.foldLeft(0f)(_ + _.contributions).toLong, bep)
|
||||
weapons.filter { _.amount == 0 }.map { _.equipment_id },
|
||||
(0.9f * math.min(weapons.foldLeft(0f)(_ + _.contributions), bep.toFloat)).toLong
|
||||
)
|
||||
}
|
||||
.orElse {
|
||||
longPeriod
|
||||
shortPeriod
|
||||
.get(charId)
|
||||
.collect {
|
||||
case entry =>
|
||||
val weapons = entry.weapons
|
||||
(
|
||||
entry.player,
|
||||
weapons.filter { _.amount == 0 }.map { _.equipment_id },
|
||||
(0.9f * math.min(weapons.foldLeft(0f)(_ + _.contributions), bep.toFloat)).toLong
|
||||
weapons.map { _.equipment_id },
|
||||
math.min(weapons.foldLeft(0f)(_ + _.contributions).toLong, bep)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -350,7 +450,7 @@ object KillContributions {
|
|||
Some((PlayerSource.Nobody, Seq(0), 0L))
|
||||
}
|
||||
.collect {
|
||||
case (player, weaponIds, experience) =>
|
||||
case (player, weaponIds, experience) if experience > 0 =>
|
||||
(charId, ContributionStatsOutput(player, weaponIds, experience.toFloat))
|
||||
}
|
||||
.get
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import net.psforever.objects.serverobject.terminals.Terminal
|
|||
import net.psforever.objects.sourcing.{AmenitySource, PlayerSource, SourceEntry}
|
||||
import net.psforever.objects.vital.{HealFromEquipment, InGameActivity, ReconstructionActivity, RepairFromEquipment, RepairFromExoSuitChange, RevivingActivity, SpawningActivity, SupportActivityCausedByAnother, TerminalUsedActivity}
|
||||
import net.psforever.objects.zones.Zone
|
||||
import net.psforever.objects.zones.exp.KillAssists.calculateMenace
|
||||
import net.psforever.types.{ExoSuitType, PlanetSideEmpire, TransactionType}
|
||||
import net.psforever.zones.Zones
|
||||
|
||||
|
|
@ -18,6 +19,33 @@ import scala.collection.mutable
|
|||
object Support {
|
||||
private type SupportActivity = InGameActivity with SupportActivityCausedByAnother
|
||||
|
||||
private[exp] def baseExperience(
|
||||
victim: PlayerSource,
|
||||
history: Iterable[InGameActivity]
|
||||
): Long = {
|
||||
val lifespan = (history.headOption, history.lastOption) match {
|
||||
case (Some(spawn), Some(death)) => death.time - spawn.time
|
||||
case _ => 0L
|
||||
}
|
||||
val wasEverAMax = Support.wasEverAMax(victim, history)
|
||||
val base = if (wasEverAMax) { //shamed
|
||||
250L
|
||||
} else if (victim.Seated || victim.progress.kills.nonEmpty) {
|
||||
100L
|
||||
} else if (lifespan > 15000L) {
|
||||
50L
|
||||
} else {
|
||||
1L
|
||||
}
|
||||
if (base > 1) {
|
||||
//black ops modifier
|
||||
//TODO x10
|
||||
base
|
||||
} else {
|
||||
base
|
||||
}
|
||||
}
|
||||
|
||||
private[exp] def onlyOriginalAssistEntries(
|
||||
first: mutable.LongMap[ContributionStatsOutput],
|
||||
second: mutable.LongMap[ContributionStatsOutput],
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
// Copyright (c) 2023 PSForever
|
||||
package net.psforever.objects.zones.exp
|
||||
|
||||
import akka.actor.typed.scaladsl.{AbstractBehavior, ActorContext, Behaviors}
|
||||
import akka.actor.typed.{Behavior, SupervisorStrategy}
|
||||
import net.psforever.objects.PlanetSideGameObject
|
||||
import net.psforever.objects.avatar.scoring.Kill
|
||||
import net.psforever.objects.serverobject.affinity.FactionAffinity
|
||||
import net.psforever.objects.sourcing.{PlayerSource, SourceEntry}
|
||||
import net.psforever.objects.vital.{InGameActivity, InGameHistory}
|
||||
import net.psforever.objects.zones.Zone
|
||||
|
||||
object SupportExperienceCalculator {
|
||||
def apply(zone: Zone): Behavior[Command] =
|
||||
Behaviors.supervise[Command] {
|
||||
Behaviors.setup(context => new SupportExperienceCalculator(context, zone))
|
||||
}.onFailure[Exception](SupervisorStrategy.restart)
|
||||
|
||||
sealed trait Command
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SupportExperienceCalculator(context: ActorContext[SupportExperienceCalculator.Command], zone: Zone)
|
||||
extends AbstractBehavior[SupportExperienceCalculator.Command](context) {
|
||||
|
||||
import SupportExperienceCalculator._
|
||||
|
||||
def onMessage(msg: Command): Behavior[Command] = {
|
||||
msg match {
|
||||
case RewardOurSupporters(target: PlayerSource, history, kill, bep) =>
|
||||
KillContributions.rewardTheseSupporters(target, history, kill, bep, zone.AvatarEvents)
|
||||
|
||||
case _ => ()
|
||||
}
|
||||
Behaviors.same
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
// Copyright (c) 2023 PSForever
|
||||
package net.psforever.objects.zones.exp
|
||||
|
||||
import net.psforever.objects.vital.InGameActivity
|
||||
|
||||
final case class VitalsHistoryException(
|
||||
head: InGameActivity, //InGameActivity might be more suitable?
|
||||
private val message: String = "",
|
||||
private val cause: Throwable = None.orNull
|
||||
) extends Exception(message, cause)
|
||||
Loading…
Add table
Add a link
Reference in a new issue