PSF-LoginServer/src/main/scala/net/psforever/objects/Player.scala
Fate-JH e9dbd5f259 importing controlled implementation changes from original exp-for-kda branch; assist kill experience rewarded
importing controlled implementation changes from original exp-for-kda branch; code for contributions from prior activity, but will be adjusting to new contribution methods

kill contributions should work; even if they don't, need to put this away for now

extensivwe changes to the way OwnableByPlayer manages owner user information due to uniqueness, that changes a lot of vehicle and deployable code; fleshing out experience calculation procedure for future testing

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

support experience accumulates and is given to the user in gradual provisions

rewarding facility capture through cep; not fully tested yet; math sucks

sort of cep to bep consditions for squad facility capture; bep deposit for ntu silo activity

early reivision for v010; recording ongoing shots fired and landed

restored bep from ntu deposits into resource silos; updating statistics in the database regarding kills and related stats including weapons; updated history management; basic experience calculation changes

all rewarded support events are accounted for

command experience calculations upon facility capture or resecure

corrected database migrations

most of the code for the play or progress system

statistics window updates for exosuits to report kills; killing an llu runner gives cep; moving play or progress functionality to a bang command rather than piggybacking setbr; bep is no longer too high by error
2023-10-17 14:06:21 -04:00

655 lines
19 KiB
Scala

// Copyright (c) 2017 PSForever
package net.psforever.objects
import net.psforever.objects.avatar.{Avatar, LoadoutManager, SpecialCarry}
import net.psforever.objects.ballistics.InteractWithRadiationClouds
import net.psforever.objects.ce.{Deployable, InteractWithMines}
import net.psforever.objects.definition.{AvatarDefinition, ExoSuitDefinition, SpecialExoSuitDefinition}
import net.psforever.objects.equipment.{Equipment, EquipmentSize, EquipmentSlot, JammableUnit}
import net.psforever.objects.inventory.{Container, GridInventory, InventoryItem}
import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.serverobject.aura.AuraContainer
import net.psforever.objects.serverobject.environment.InteractWithEnvironment
import net.psforever.objects.serverobject.mount.MountableEntity
import net.psforever.objects.vital.resistance.ResistanceProfile
import net.psforever.objects.vital.{HealFromEquipment, InGameActivity, RepairFromEquipment, Vitality}
import net.psforever.objects.vital.damage.DamageProfile
import net.psforever.objects.vital.interaction.DamageInteraction
import net.psforever.objects.vital.resolution.DamageResistanceModel
import net.psforever.objects.zones.blockmap.{BlockMapEntity, SectorPopulation}
import net.psforever.objects.zones.{InteractsWithZone, ZoneAware, Zoning}
import net.psforever.types.{PlanetSideGUID, _}
import scala.annotation.tailrec
import scala.util.{Success, Try}
class Player(var avatar: Avatar)
extends PlanetSideServerObject
with BlockMapEntity
with InteractsWithZone
with FactionAffinity
with Vitality
with ResistanceProfile
with Container
with JammableUnit
with ZoneAware
with AuraContainer
with MountableEntity {
interaction(new InteractWithEnvironment())
interaction(new InteractWithMinesUnlessSpectating(obj = this, range = 10))
interaction(new InteractWithRadiationClouds(range = 10f, Some(this)))
private var backpack: Boolean = false
private var released: Boolean = false
private var armor: Int = 0
private var capacitor: Float = 0f
private var capacitorState: CapacitorStateType.Value = CapacitorStateType.Idle
private var capacitorLastUsedMillis: Long = 0
private var capacitorLastChargedMillis: Long = 0
private var exosuit: ExoSuitDefinition = GlobalDefinitions.Standard
private val freeHand: EquipmentSlot = new OffhandEquipmentSlot(EquipmentSize.Inventory)
private val holsters: Array[EquipmentSlot] = Array.fill[EquipmentSlot](5)(new EquipmentSlot)
private val inventory: GridInventory = GridInventory()
private var drawnSlot: Int = Player.HandsDownSlot
private var lastDrawnSlot: Int = Player.HandsDownSlot
private var backpackAccess: Option[PlanetSideGUID] = None
private var carrying: Option[SpecialCarry] = None
private var facingYawUpper: Float = 0f
private var crouching: Boolean = false
private var jumping: Boolean = false
private var cloaked: Boolean = false
private var afk: Boolean = false
private var zoning: Zoning.Method = Zoning.Method.None
private var vehicleSeated: Option[PlanetSideGUID] = None
Continent = "home2" //the zone id
var spectator: Boolean = false
var silenced: Boolean = false
var death_by: Int = 0
var lastShotSeq_time: Int = -1
/** From PlanetsideAttributeMessage */
var PlanetsideAttribute: Array[Long] = Array.ofDim(120)
val squadLoadouts = new LoadoutManager(10)
var resistArmMotion: (Player,Int)=>Boolean = Player.neverRestrict
//init
Health = 0 //player health is artificially managed as a part of their lifecycle; start entity as dead
Destroyed = true //see isAlive
Player.SuitSetup(this, exosuit)
def Definition: AvatarDefinition = avatar.definition
def CharId: Long = avatar.id
def Name: String = avatar.name
def Faction: PlanetSideEmpire.Value = avatar.faction
def Sex: CharacterSex = avatar.sex
def Head: Int = avatar.head
def Voice: CharacterVoice.Value = avatar.voice
def isAlive: Boolean = !Destroyed
def isBackpack: Boolean = backpack
def Spawn(): Boolean = {
if (!isAlive && !isBackpack) {
Destroyed = false
Health = Definition.DefaultHealth
Armor = MaxArmor
Capacitor = 0
avatar.scorecard.respawn()
released = false
}
isAlive
}
def Die: Boolean = {
Destroyed = true
Health = 0
false
}
def Revive: Boolean = {
Destroyed = false
Health = Definition.DefaultHealth
avatar.scorecard.revive()
released = false
true
}
def Release: Boolean = {
if (!released) {
released = true
backpack = !isAlive
}
true
}
def isReleased: Boolean = released
def Armor: Int = armor
def Armor_=(assignArmor: Int): Int = {
armor = math.min(math.max(0, assignArmor), MaxArmor)
Armor
}
def MaxArmor: Int = exosuit.MaxArmor
def Capacitor: Float = capacitor
def Capacitor_=(value: Float): Float = {
val newValue = math.min(math.max(0, value), ExoSuitDef.MaxCapacitor.toFloat)
if (newValue < capacitor) {
capacitorLastUsedMillis = System.currentTimeMillis()
capacitorLastChargedMillis = 0
} else if (newValue > capacitor && newValue < ExoSuitDef.MaxCapacitor) {
capacitorLastChargedMillis = System.currentTimeMillis()
capacitorLastUsedMillis = 0
} else if (newValue > capacitor && newValue == ExoSuitDef.MaxCapacitor) {
capacitorLastChargedMillis = 0
capacitorLastUsedMillis = 0
capacitorState = CapacitorStateType.Idle
}
capacitor = newValue
capacitor
}
def CapacitorState: CapacitorStateType.Value = capacitorState
def CapacitorState_=(value: CapacitorStateType.Value): CapacitorStateType.Value = {
value match {
case CapacitorStateType.Charging => capacitorLastChargedMillis = System.currentTimeMillis()
case CapacitorStateType.Discharging => capacitorLastUsedMillis = System.currentTimeMillis()
case _ => ;
}
capacitorState = value
capacitorState
}
def CapacitorLastUsedMillis: Long = capacitorLastUsedMillis
def CapacitorLastChargedMillis: Long = capacitorLastChargedMillis
def VisibleSlots: Set[Int] =
if (exosuit.SuitType == ExoSuitType.MAX) {
Set(0)
} else {
(0 to 4).filterNot(index => holsters(index).Size == EquipmentSize.Blocked).toSet
}
override def Slot(slot: Int): EquipmentSlot = {
if (inventory.Offset <= slot && slot <= inventory.LastIndex) {
inventory.Slot(slot)
} else if (slot > -1 && slot < 5) {
holsters(slot)
} else if (slot == 5) {
avatar.fifthSlot()
} else if (slot == Player.FreeHandSlot) {
freeHand
} else {
OffhandEquipmentSlot.BlockedSlot
}
}
def Holsters(): Array[EquipmentSlot] = holsters
/**
* Transform the holster equipment slots
* into a list of the kind of item wrapper found in an inventory.
* @see `GridInventory`
* @see `InventoryItem`
* @return a list of items that would be found in a proper inventory
*/
def HolsterItems(): List[InventoryItem] = holsters
.zipWithIndex
.collect {
case (slot: EquipmentSlot, index: Int) =>
slot.Equipment match {
case Some(item) => Some(InventoryItem(item, index))
case None => None
}
}.flatten.toList
def Inventory: GridInventory = inventory
override def Fit(obj: Equipment): Option[Int] = {
recursiveHolsterFit(holsters.iterator, obj.Size) match {
case Some(index) =>
Some(index)
case None =>
inventory.Fit(obj.Definition.Tile) match {
case Some(index) =>
Some(index)
case None =>
if (freeHand.Equipment.isDefined) { None }
else { Some(Player.FreeHandSlot) }
}
}
}
@tailrec private def recursiveHolsterFit(
iter: Iterator[EquipmentSlot],
objSize: EquipmentSize.Value,
index: Int = 0
): Option[Int] = {
if (!iter.hasNext) {
None
} else {
val slot = iter.next()
if (slot.Equipment.isEmpty && slot.Size.equals(objSize)) {
Some(index)
} else {
recursiveHolsterFit(iter, objSize, index + 1)
}
}
}
def FreeHand: EquipmentSlot = freeHand
def FreeHand_=(item: Option[Equipment]): Option[Equipment] = {
if (freeHand.Equipment.isEmpty || item.isEmpty) {
freeHand.Equipment = item
}
FreeHand.Equipment
}
override def Find(guid: PlanetSideGUID): Option[Int] = {
findInHolsters(holsters.iterator, guid)
.orElse(inventory.Find(guid)) match {
case Some(index) =>
Some(index)
case None =>
if (freeHand.Equipment.isDefined && freeHand.Equipment.get.GUID == guid) {
Some(Player.FreeHandSlot)
} else {
None
}
}
}
@tailrec private def findInHolsters(
iter: Iterator[EquipmentSlot],
guid: PlanetSideGUID,
index: Int = 0
): Option[Int] = {
if (!iter.hasNext) {
None
} else {
val slot = iter.next()
if (slot.Equipment match { case Some(o) => o.GUID == guid; case _ => false }) {
Some(index)
} else {
findInHolsters(iter, guid, index + 1)
}
}
}
override def Collisions(dest: Int, width: Int, height: Int): Try[List[InventoryItem]] = {
if (-1 < dest && dest < 5) {
holsters(dest).Equipment match {
case Some(item) =>
Success(List(InventoryItem(item, dest)))
case None =>
Success(List())
}
} else if (dest == Player.FreeHandSlot) {
freeHand.Equipment match {
case Some(item) =>
Success(List(InventoryItem(item, dest)))
case None =>
Success(List())
}
} else {
super.Collisions(dest, width, height)
}
}
def ResistArmMotion(func: (Player,Int)=>Boolean): Unit = {
resistArmMotion = func
}
def TestArmMotion(): Boolean = {
resistArmMotion(this, drawnSlot)
}
def TestArmMotion(slot: Int): Boolean = {
resistArmMotion(this, slot)
}
def DrawnSlot: Int = drawnSlot
def DrawnSlot_=(slot: Int): Int = {
if (slot != drawnSlot) {
if (slot == Player.HandsDownSlot) {
drawnSlot = slot
} else if (VisibleSlots.contains(slot) && holsters(slot).Equipment.isDefined) {
drawnSlot = slot
lastDrawnSlot = slot
}
}
DrawnSlot
}
def LastDrawnSlot: Int = lastDrawnSlot
def ExoSuit: ExoSuitType.Value = exosuit.SuitType
def ExoSuitDef: ExoSuitDefinition = exosuit
def ExoSuit_=(suit: ExoSuitType.Value): Unit = {
val eSuit = ExoSuitDefinition.Select(suit, Faction)
exosuit = eSuit
Player.SuitSetup(this, eSuit)
ChangeSpecialAbility()
}
def Subtract: DamageProfile = exosuit.Subtract
def ResistanceDirectHit: Int = exosuit.ResistanceDirectHit
def ResistanceSplash: Int = exosuit.ResistanceSplash
def ResistanceAggravated: Int = exosuit.ResistanceAggravated
def RadiationShielding: Float = exosuit.RadiationShielding
def FacingYawUpper: Float = facingYawUpper
def FacingYawUpper_=(facing: Float): Float = {
facingYawUpper = facing
FacingYawUpper
}
def Crouching: Boolean = crouching
def Crouching_=(crouched: Boolean): Boolean = {
crouching = crouched
Crouching
}
def Jumping: Boolean = jumping
def Jumping_=(jumped: Boolean): Boolean = {
jumping = jumped
Jumping
}
def Cloaked: Boolean = cloaked
def Cloaked_=(isCloaked: Boolean): Boolean = {
cloaked = isCloaked
Cloaked
}
def AwayFromKeyboard: Boolean = afk
def AwayFromKeyboard_=(away: Boolean): Boolean = {
afk = away
AwayFromKeyboard
}
private var usingSpecial: SpecialExoSuitDefinition.Mode.Value => SpecialExoSuitDefinition.Mode.Value =
DefaultUsingSpecial
private var gettingSpecial: () => SpecialExoSuitDefinition.Mode.Value = DefaultGettingSpecial
private def ChangeSpecialAbility(): Unit = {
if (ExoSuit == ExoSuitType.MAX) {
gettingSpecial = MAXGettingSpecial
usingSpecial = Faction match {
case PlanetSideEmpire.TR => UsingAnchorsOrOverdrive
case PlanetSideEmpire.NC => UsingShield
case _ => DefaultUsingSpecial
}
} else {
usingSpecial = DefaultUsingSpecial
gettingSpecial = DefaultGettingSpecial
}
}
def UsingSpecial: SpecialExoSuitDefinition.Mode.Value = { gettingSpecial() }
def UsingSpecial_=(state: SpecialExoSuitDefinition.Mode.Value): SpecialExoSuitDefinition.Mode.Value =
usingSpecial(state)
//noinspection ScalaUnusedSymbol
private def DefaultUsingSpecial(state: SpecialExoSuitDefinition.Mode.Value): SpecialExoSuitDefinition.Mode.Value =
SpecialExoSuitDefinition.Mode.Normal
private def UsingAnchorsOrOverdrive(
state: SpecialExoSuitDefinition.Mode.Value
): SpecialExoSuitDefinition.Mode.Value = {
import SpecialExoSuitDefinition.Mode._
val curr = UsingSpecial
val next = if (curr == Normal) {
if (state == Anchored || state == Overdrive) {
state
} else {
Normal
}
} else if (state == Normal) {
Normal
} else {
curr
}
MAXUsingSpecial(next)
}
private def UsingShield(state: SpecialExoSuitDefinition.Mode.Value): SpecialExoSuitDefinition.Mode.Value = {
import SpecialExoSuitDefinition.Mode._
val curr = UsingSpecial
val next = if (curr == Normal) {
if (state == Shielded) {
state
} else {
Normal
}
} else if (state == Normal) {
Normal
} else {
curr
}
MAXUsingSpecial(next)
}
private def DefaultGettingSpecial(): SpecialExoSuitDefinition.Mode.Value = SpecialExoSuitDefinition.Mode.Normal
private def MAXUsingSpecial(state: SpecialExoSuitDefinition.Mode.Value): SpecialExoSuitDefinition.Mode.Value =
exosuit match {
case obj: SpecialExoSuitDefinition =>
obj.UsingSpecial = state
case _ =>
SpecialExoSuitDefinition.Mode.Normal
}
private def MAXGettingSpecial(): SpecialExoSuitDefinition.Mode.Value =
exosuit match {
case obj: SpecialExoSuitDefinition =>
obj.UsingSpecial
case _ =>
SpecialExoSuitDefinition.Mode.Normal
}
def isAnchored: Boolean =
ExoSuit == ExoSuitType.MAX && Faction == PlanetSideEmpire.TR && UsingSpecial == SpecialExoSuitDefinition.Mode.Anchored
def isOverdrived: Boolean =
ExoSuit == ExoSuitType.MAX && Faction == PlanetSideEmpire.TR && UsingSpecial == SpecialExoSuitDefinition.Mode.Overdrive
def isShielded: Boolean =
ExoSuit == ExoSuitType.MAX && Faction == PlanetSideEmpire.NC && UsingSpecial == SpecialExoSuitDefinition.Mode.Shielded
def AccessingBackpack: Option[PlanetSideGUID] = backpackAccess
def AccessingBackpack_=(guid: PlanetSideGUID): Option[PlanetSideGUID] = {
AccessingBackpack = Some(guid)
}
/**
* Change which player has access to the backpack of this player.
* A player may only access to the backpack of a dead released player, and only if no one else has access at the moment.
* @param guid the player who wishes to access the backpack
* @return the player who is currently allowed to access the backpack
*/
def AccessingBackpack_=(guid: Option[PlanetSideGUID]): Option[PlanetSideGUID] = {
guid match {
case None =>
backpackAccess = None
case Some(player) =>
if (isBackpack && backpackAccess.isEmpty) {
backpackAccess = Some(player)
}
}
AccessingBackpack
}
/**
* Can the other `player` access the contents of this `Player`'s backpack?
* @param player a player attempting to access this backpack
* @return `true`, if the `player` is permitted access; `false`, otherwise
*/
def CanAccessBackpack(player: Player): Boolean = {
isBackpack && (backpackAccess.isEmpty || backpackAccess.contains(player.GUID))
}
def VehicleSeated: Option[PlanetSideGUID] = vehicleSeated
def VehicleSeated_=(guid: PlanetSideGUID): Option[PlanetSideGUID] = VehicleSeated_=(Some(guid))
def VehicleSeated_=(guid: Option[PlanetSideGUID]): Option[PlanetSideGUID] = {
vehicleSeated = guid
VehicleSeated
}
def Carrying: Option[SpecialCarry] = carrying
//noinspection ScalaUnusedSymbol
def Carrying_=(item: SpecialCarry): Option[SpecialCarry] = {
Carrying_=(Some(item))
}
//noinspection ScalaUnusedSymbol
def Carrying_=(item: Option[SpecialCarry]): Option[SpecialCarry] = {
carrying = item
Carrying
}
def ZoningRequest: Zoning.Method = zoning
def ZoningRequest_=(request: Zoning.Method): Zoning.Method = {
zoning = request
ZoningRequest
}
override def CanDamage: Boolean = {
death_by < 1 && super.CanDamage
}
def DamageModel: DamageResistanceModel = exosuit.asInstanceOf[DamageResistanceModel]
override def GetContributionDuringPeriod(list: List[InGameActivity], duration: Long): List[InGameActivity] = {
val earliestEndTime = System.currentTimeMillis() - duration
History.collect {
case heal: HealFromEquipment if heal.amount > 0 && heal.time > earliestEndTime => heal
case repair: RepairFromEquipment if repair.amount > 0 && repair.time > earliestEndTime => repair
}
}
def canEqual(other: Any): Boolean = other.isInstanceOf[Player]
override def equals(other: Any): Boolean =
other match {
case that: Player =>
(that canEqual this) &&
avatar == that.avatar
case _ =>
false
}
override def hashCode(): Int = {
avatar.hashCode()
}
override def toString: String = {
val guid = if (HasGUID) {
s" $Continent-${GUID.guid}"
} else {
""
}
s"${avatar.name}$guid ${avatar.faction} H: $Health/$MaxHealth A: $Armor/$MaxArmor"
}
}
object Player {
final val LockerSlot: Int = 5
final val FreeHandSlot: Int = 250
final val HandsDownSlot: Int = 255
final case class BuildDeployable(obj: Deployable, withTool: ConstructionItem)
final case class LoseDeployable(obj: Deployable)
final case class Die(reason: Option[DamageInteraction])
object Die {
def apply(): Die = Die(None)
def apply(reason: DamageInteraction): Die = {
Die(Some(reason))
}
}
def apply(core: Avatar): Player = {
new Player(core)
}
private def SuitSetup(player: Player, eSuit: ExoSuitDefinition): Unit = {
//inventory
player.Inventory.Clear()
player.Inventory.Resize(eSuit.InventoryScale.Width, eSuit.InventoryScale.Height)
player.Inventory.Offset = eSuit.InventoryOffset
//holsters
(0 until 5).foreach(index => { player.Slot(index).Size = eSuit.Holster(index) })
}
def Respawn(player: Player): Player = {
if (player.Release) {
val obj = new Player(player.avatar)
obj.Continent = player.Continent
obj.avatar.scorecard.respawn()
obj
} else {
player
}
}
//noinspection ScalaUnusedSymbol
def neverRestrict(player: Player, slot: Int): Boolean = {
false
}
}
private class InteractWithMinesUnlessSpectating(
private val obj: Player,
override val range: Float
) extends InteractWithMines(range) {
override def interaction(sector: SectorPopulation, target: InteractsWithZone): Unit = {
if (!obj.spectator) {
super.interaction(sector, target)
}
}
}