mirror of
https://github.com/psforever/PSF-LoginServer.git
synced 2026-01-20 02:54:46 +00:00
Destroy and repair (#346)
* bog-standard order_terminal amenities now take damage up to the point of destruction and can be repaired from destruction to functional to the point of being fully repaired; this is mostly proof fo concept * restored proper destruction to FacilityTurrets; extended proper rrepairs to FacilityTurrets; co-opted terminal hacking into TerminalControl; started to expand on hacking protocol, but chose restraint * changes made thus that a clear Definition hierarchy is established; all of this is in line with making future changes to repair/destroy variables, and making generic the repair code * all meaningful facility amenities take damage and can be repaired; spawn tubes can be destroyed and the base will properly lose spawns (and show it on the map); some hack logic has been redistributed into the appropriate control objects, following in the wake of repair/damage logic * deployables are repairable; the TRAP has been converted into a ComplexDeployable; changed the nature of the Repairable traits * player bank repair and medapp heal has been moved out from WSA into PlayerControl * overhaul of Progress callback system and the inclusion of player revival as a Progress activity * begun relocating functionality for hacking outside of WSA; set up behavoir mixin for cargo operations, in order to move vehicle hack function, but did not yet integrate * integration of the actor behavior mixin for vehicle cargo operations to support the integration of vehicle hacking finalization * establishing inheritance/override potential of Damageable activity; Generator and SpawnTube map behavior behavior (currently inactive) * ImplantTerminalMech objects now have a 'with-coordinates' constructor and a deprecated 'no-coordinates' constructor; implants mechs and interfaces are now damageable and repairable, and their damage state can also block mounting * generators are destroyed and repaired properly, and even explode, killing a radius-worth of players * destroy and repair pass on deployables, except for explosive types * Damageable pass; client synchronization pass * helpful comments * some tests for damageable and repairable; refined output and repaired existing tests * enabled friendly fire check and recovery * handled friendly fire against allied mines; moved jammer code to common damageable behavior * tweaks to damageability, infantry heal and repair, and sensor and explosive animations * animations; framework for future vitals events; closing database connections * adding some deployable tests; fixing a bunch of other tests; History is back * testing for basic Damageable functions; removing a log message * finicky animation stuff * event messages to the Generator to represent health changes * damage against BFR's is now only used against mythical creatures * test fix
This commit is contained in:
parent
840006dca8
commit
c2f6baf551
|
|
@ -2,10 +2,10 @@
|
|||
package net.psforever.objects
|
||||
|
||||
import akka.actor.ActorRef
|
||||
import scala.concurrent.duration._
|
||||
|
||||
import scala.concurrent.duration._
|
||||
import net.psforever.objects.ce.{Deployable, DeployedItem}
|
||||
import net.psforever.objects.serverobject.PlanetSideServerObject
|
||||
import net.psforever.objects.vehicles.{Utility, UtilityType}
|
||||
import net.psforever.objects.zones.Zone
|
||||
import net.psforever.packet.game.{DeployableInfo, DeploymentAction}
|
||||
import net.psforever.types.PlanetSideGUID
|
||||
|
|
@ -13,6 +13,8 @@ import services.RemoverActor
|
|||
import services.local.{LocalAction, LocalServiceMessage}
|
||||
|
||||
object Deployables {
|
||||
private val log = org.log4s.getLogger("Deployables")
|
||||
|
||||
object Make {
|
||||
def apply(item : DeployedItem.Value) : ()=>PlanetSideGameObject with Deployable = cemap(item)
|
||||
|
||||
|
|
@ -58,7 +60,7 @@ object Deployables {
|
|||
* @param time length of time that the deployable is allowed to exist in the game world;
|
||||
* `None` indicates the normal un-owned existence time (180 seconds)
|
||||
*/
|
||||
def AnnounceDestroyDeployable(target : PlanetSideServerObject with Deployable, time : Option[FiniteDuration]) : Unit = {
|
||||
def AnnounceDestroyDeployable(target : PlanetSideGameObject with Deployable, time : Option[FiniteDuration]) : Unit = {
|
||||
val zone = target.Zone
|
||||
target.OwnerName match {
|
||||
case Some(owner) =>
|
||||
|
|
@ -102,4 +104,23 @@ object Deployables {
|
|||
})
|
||||
boomers ++ deployables
|
||||
}
|
||||
|
||||
def RemoveTelepad(vehicle: Vehicle) : Unit = {
|
||||
val zone = vehicle.Zone
|
||||
(vehicle.Utility(UtilityType.internal_router_telepad_deployable) match {
|
||||
case Some(util : Utility.InternalTelepad) =>
|
||||
val telepad = util.Telepad
|
||||
util.Telepad = None
|
||||
zone.GUID(telepad)
|
||||
case _ =>
|
||||
None
|
||||
}) match {
|
||||
case Some(telepad : TelepadDeployable) =>
|
||||
log.info(s"BeforeUnload: deconstructing telepad $telepad that was linked to router $vehicle ...")
|
||||
telepad.Active = false
|
||||
zone.LocalEvents ! LocalServiceMessage.Deployables(RemoverActor.ClearSpecific(List(telepad), zone))
|
||||
zone.LocalEvents ! LocalServiceMessage.Deployables(RemoverActor.AddTask(telepad, zone, Some(0 seconds)))
|
||||
case _ => ;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
// Copyright (c) 2018 PSForever
|
||||
package net.psforever.objects
|
||||
|
||||
import akka.actor.{Actor, ActorContext, Props}
|
||||
|
|
@ -8,8 +8,11 @@ import net.psforever.objects.definition.{ComplexDeployableDefinition, SimpleDepl
|
|||
import net.psforever.objects.definition.converter.SmallDeployableConverter
|
||||
import net.psforever.objects.equipment.JammableUnit
|
||||
import net.psforever.objects.serverobject.PlanetSideServerObject
|
||||
import net.psforever.objects.serverobject.damage.Damageable
|
||||
import net.psforever.objects.vital.{StandardResolutions, Vitality}
|
||||
import net.psforever.objects.zones.Zone
|
||||
import net.psforever.types.{PlanetSideGUID, Vector3}
|
||||
import services.Service
|
||||
import services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||
import services.local.{LocalAction, LocalServiceMessage}
|
||||
|
||||
|
|
@ -17,14 +20,6 @@ import scala.concurrent.duration._
|
|||
|
||||
class ExplosiveDeployable(cdef : ExplosiveDeployableDefinition) extends ComplexDeployable(cdef)
|
||||
with JammableUnit {
|
||||
private var exploded : Boolean = false
|
||||
|
||||
def Exploded : Boolean = exploded
|
||||
|
||||
def Exploded_=(fuse : Boolean) : Boolean = {
|
||||
exploded = fuse
|
||||
Exploded
|
||||
}
|
||||
|
||||
override def Definition : ExplosiveDeployableDefinition = cdef
|
||||
}
|
||||
|
|
@ -59,38 +54,48 @@ object ExplosiveDeployableDefinition {
|
|||
}
|
||||
}
|
||||
|
||||
class ExplosiveDeployableControl(mine : ExplosiveDeployable) extends Actor {
|
||||
def receive : Receive = {
|
||||
case Vitality.Damage(damage_func) =>
|
||||
val originalHealth = mine.Health
|
||||
if(originalHealth > 0) {
|
||||
val cause = damage_func(mine)
|
||||
ExplosiveDeployableControl.HandleDamageResolution(mine, cause, originalHealth - mine.Health)
|
||||
}
|
||||
class ExplosiveDeployableControl(mine : ExplosiveDeployable) extends Actor
|
||||
with Damageable {
|
||||
def DamageableObject = mine
|
||||
|
||||
case _ => ;
|
||||
def receive : Receive = takesDamage
|
||||
.orElse {
|
||||
case _ => ;
|
||||
}
|
||||
|
||||
protected def TakesDamage : Receive = {
|
||||
case Vitality.Damage(applyDamageTo) =>
|
||||
if(mine.CanDamage) {
|
||||
val originalHealth = mine.Health
|
||||
val cause = applyDamageTo(mine)
|
||||
val damage = originalHealth - mine.Health
|
||||
if(Damageable.CanDamageOrJammer(mine, damage, cause)) {
|
||||
ExplosiveDeployableControl.DamageResolution(mine, cause, damage)
|
||||
}
|
||||
else {
|
||||
mine.Health = originalHealth
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object ExplosiveDeployableControl {
|
||||
def HandleDamageResolution(target : ExplosiveDeployable, cause : ResolvedProjectile, damage : Int) : Unit = {
|
||||
val zone = target.Zone
|
||||
val playerGUID = zone.LivePlayers.find { p => cause.projectile.owner.Name.equals(p.Name) } match {
|
||||
case Some(player) => player.GUID
|
||||
case _ => PlanetSideGUID(0)
|
||||
}
|
||||
def DamageResolution(target : ExplosiveDeployable, cause : ResolvedProjectile, damage : Int) : Unit = {
|
||||
target.History(cause)
|
||||
if(target.Health == 0) {
|
||||
HandleDestructionAwareness(target, playerGUID, cause)
|
||||
DestructionAwareness(target, cause)
|
||||
}
|
||||
else if(!target.Jammed && cause.projectile.profile.JammerProjectile) {
|
||||
else if(!target.Jammed && Damageable.CanJammer(target, cause)) {
|
||||
if(target.Jammed = {
|
||||
val radius = cause.projectile.profile.DamageRadius
|
||||
Vector3.DistanceSquared(cause.hit_pos, cause.target.Position) < radius * radius
|
||||
}) {
|
||||
if(target.Definition.DetonateOnJamming) {
|
||||
target.Zone.LocalEvents ! LocalServiceMessage(target.Zone.Id, LocalAction.Detonate(target.GUID, target))
|
||||
val zone = target.Zone
|
||||
zone.Activity ! Zone.HotSpot.Activity(cause.target, cause.projectile.owner, cause.hit_pos)
|
||||
zone.LocalEvents ! LocalServiceMessage(zone.Id, LocalAction.Detonate(target.GUID, target))
|
||||
}
|
||||
HandleDestructionAwareness(target, playerGUID, cause)
|
||||
DestructionAwareness(target, cause)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -98,13 +103,19 @@ object ExplosiveDeployableControl {
|
|||
/**
|
||||
* na
|
||||
* @param target na
|
||||
* @param attribution na
|
||||
* @param lastShot na
|
||||
* @param cause na
|
||||
*/
|
||||
def HandleDestructionAwareness(target : ExplosiveDeployable, attribution : PlanetSideGUID, lastShot : ResolvedProjectile) : Unit = {
|
||||
def DestructionAwareness(target : ExplosiveDeployable, cause : ResolvedProjectile) : Unit = {
|
||||
val zone = target.Zone
|
||||
val attribution = zone.LivePlayers.find { p => cause.projectile.owner.Name.equals(p.Name) } match {
|
||||
case Some(player) => player.GUID
|
||||
case _ => PlanetSideGUID(0)
|
||||
}
|
||||
target.Destroyed = true
|
||||
Deployables.AnnounceDestroyDeployable(target, Some(if(target.Jammed) 0 seconds else 500 milliseconds))
|
||||
zone.AvatarEvents ! AvatarServiceMessage(zone.Id, AvatarAction.Destroy(target.GUID, attribution, attribution, target.Position))
|
||||
zone.AvatarEvents ! AvatarServiceMessage(zone.Id, AvatarAction.Destroy(target.GUID, attribution, Service.defaultPlayerGUID, target.Position))
|
||||
if(target.Health == 0) {
|
||||
zone.LocalEvents ! LocalServiceMessage(zone.Id, LocalAction.TriggerEffect(Service.defaultPlayerGUID, "detonate_damaged_mine", target.GUID))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import net.psforever.objects.serverobject.tube.SpawnTubeDefinition
|
|||
import net.psforever.objects.serverobject.resourcesilo.ResourceSiloDefinition
|
||||
import net.psforever.objects.serverobject.structures.SphereOfInfluence
|
||||
import net.psforever.objects.serverobject.turret.{FacilityTurretDefinition, TurretUpgrade}
|
||||
import net.psforever.objects.vehicles.{DestroyedVehicle, SeatArmorRestriction, UtilityType}
|
||||
import net.psforever.objects.vehicles.{DestroyedVehicle, InternalTelepadDefinition, SeatArmorRestriction, UtilityType}
|
||||
import net.psforever.objects.vital.{DamageType, StandardMaxDamage, StandardResolutions}
|
||||
import net.psforever.types.{CertificationType, ExoSuitType, PlanetSideEmpire, Vector3}
|
||||
|
||||
|
|
@ -29,6 +29,8 @@ import scala.concurrent.duration._
|
|||
object GlobalDefinitions {
|
||||
// Characters
|
||||
val avatar = new AvatarDefinition(121)
|
||||
avatar.MaxHealth = 100
|
||||
avatar.Damageable = true
|
||||
/*
|
||||
exo-suits
|
||||
*/
|
||||
|
|
@ -889,7 +891,7 @@ object GlobalDefinitions {
|
|||
|
||||
val sensor_shield = SensorDeployableDefinition(DeployedItem.sensor_shield)
|
||||
|
||||
val tank_traps = SimpleDeployableDefinition(DeployedItem.tank_traps)
|
||||
val tank_traps = TrapDeployableDefinition(DeployedItem.tank_traps)
|
||||
|
||||
val portable_manned_turret = TurretDeployableDefinition(DeployedItem.portable_manned_turret)
|
||||
|
||||
|
|
@ -904,7 +906,7 @@ object GlobalDefinitions {
|
|||
val router_telepad_deployable = SimpleDeployableDefinition(DeployedItem.router_telepad_deployable)
|
||||
|
||||
//this is only treated like a deployable
|
||||
val internal_router_telepad_deployable = SimpleDeployableDefinition(DeployedItem.router_telepad_deployable)
|
||||
val internal_router_telepad_deployable = InternalTelepadDefinition() //objectId: 744
|
||||
init_deployables()
|
||||
|
||||
/*
|
||||
|
|
@ -1626,7 +1628,7 @@ object GlobalDefinitions {
|
|||
max.ResistanceDirectHit = 6
|
||||
max.ResistanceSplash = 35
|
||||
max.ResistanceAggravated = 10
|
||||
max.Damage = StandardMaxDamage
|
||||
max.DamageUsing = StandardMaxDamage
|
||||
max.Model = StandardResolutions.Max
|
||||
}
|
||||
|
||||
|
|
@ -4470,7 +4472,10 @@ object GlobalDefinitions {
|
|||
nano_dispenser.FireModes.head.AmmoSlotIndex = 0
|
||||
nano_dispenser.FireModes.head.Magazine = 100
|
||||
nano_dispenser.FireModes.head.CustomMagazine = Ammo.upgrade_canister -> 1
|
||||
nano_dispenser.FireModes.head.Modifiers.Damage0 = 0
|
||||
nano_dispenser.FireModes.head.Modifiers.Damage1 = 20
|
||||
nano_dispenser.FireModes.head.Modifiers.Damage2 = 0
|
||||
nano_dispenser.FireModes.head.Modifiers.Damage3 = 0
|
||||
nano_dispenser.FireModes.head.Modifiers.Damage4 = 20
|
||||
nano_dispenser.Tile = InventoryTile.Tile63
|
||||
|
||||
|
|
@ -5156,6 +5161,9 @@ object GlobalDefinitions {
|
|||
private def init_vehicles() : Unit = {
|
||||
fury.Name = "fury"
|
||||
fury.MaxHealth = 650
|
||||
fury.Damageable = true
|
||||
fury.Repairable = true
|
||||
fury.RepairIfDestroyed = false
|
||||
fury.MaxShields = 130 + 1
|
||||
fury.Seats += 0 -> new SeatDefinition()
|
||||
fury.Seats(0).Bailable = true
|
||||
|
|
@ -5171,6 +5179,9 @@ object GlobalDefinitions {
|
|||
|
||||
quadassault.Name = "quadassault" // Basilisk
|
||||
quadassault.MaxHealth = 650
|
||||
quadassault.Damageable = true
|
||||
quadassault.Repairable = true
|
||||
quadassault.RepairIfDestroyed = false
|
||||
quadassault.MaxShields = 130 + 1
|
||||
quadassault.Seats += 0 -> new SeatDefinition()
|
||||
quadassault.Seats(0).Bailable = true
|
||||
|
|
@ -5186,6 +5197,9 @@ object GlobalDefinitions {
|
|||
|
||||
quadstealth.Name = "quadstealth" // Wraith
|
||||
quadstealth.MaxHealth = 650
|
||||
quadstealth.Damageable = true
|
||||
quadstealth.Repairable = true
|
||||
quadstealth.RepairIfDestroyed = false
|
||||
quadstealth.MaxShields = 130 + 1
|
||||
quadstealth.CanCloak = true
|
||||
quadstealth.Seats += 0 -> new SeatDefinition()
|
||||
|
|
@ -5201,6 +5215,9 @@ object GlobalDefinitions {
|
|||
|
||||
two_man_assault_buggy.Name = "two_man_assault_buggy" // Harasser
|
||||
two_man_assault_buggy.MaxHealth = 1250
|
||||
two_man_assault_buggy.Damageable = true
|
||||
two_man_assault_buggy.Repairable = true
|
||||
two_man_assault_buggy.RepairIfDestroyed = false
|
||||
two_man_assault_buggy.MaxShields = 250 + 1
|
||||
two_man_assault_buggy.Seats += 0 -> new SeatDefinition()
|
||||
two_man_assault_buggy.Seats(0).Bailable = true
|
||||
|
|
@ -5218,6 +5235,9 @@ object GlobalDefinitions {
|
|||
|
||||
skyguard.Name = "skyguard"
|
||||
skyguard.MaxHealth = 1000
|
||||
skyguard.Damageable = true
|
||||
skyguard.Repairable = true
|
||||
skyguard.RepairIfDestroyed = false
|
||||
skyguard.MaxShields = 200 + 1
|
||||
skyguard.Seats += 0 -> new SeatDefinition()
|
||||
skyguard.Seats(0).Bailable = true
|
||||
|
|
@ -5236,6 +5256,9 @@ object GlobalDefinitions {
|
|||
|
||||
threemanheavybuggy.Name = "threemanheavybuggy" // Marauder
|
||||
threemanheavybuggy.MaxHealth = 1700
|
||||
threemanheavybuggy.Damageable = true
|
||||
threemanheavybuggy.Repairable = true
|
||||
threemanheavybuggy.RepairIfDestroyed = false
|
||||
threemanheavybuggy.MaxShields = 340 + 1
|
||||
threemanheavybuggy.Seats += 0 -> new SeatDefinition()
|
||||
threemanheavybuggy.Seats(0).Bailable = true
|
||||
|
|
@ -5259,6 +5282,9 @@ object GlobalDefinitions {
|
|||
|
||||
twomanheavybuggy.Name = "twomanheavybuggy" // Enforcer
|
||||
twomanheavybuggy.MaxHealth = 1800
|
||||
twomanheavybuggy.Damageable = true
|
||||
twomanheavybuggy.Repairable = true
|
||||
twomanheavybuggy.RepairIfDestroyed = false
|
||||
twomanheavybuggy.MaxShields = 360 + 1
|
||||
twomanheavybuggy.Seats += 0 -> new SeatDefinition()
|
||||
twomanheavybuggy.Seats(0).Bailable = true
|
||||
|
|
@ -5277,6 +5303,9 @@ object GlobalDefinitions {
|
|||
|
||||
twomanhoverbuggy.Name = "twomanhoverbuggy" // Thresher
|
||||
twomanhoverbuggy.MaxHealth = 1600
|
||||
twomanhoverbuggy.Damageable = true
|
||||
twomanhoverbuggy.Repairable = true
|
||||
twomanhoverbuggy.RepairIfDestroyed = false
|
||||
twomanhoverbuggy.MaxShields = 320 + 1
|
||||
twomanhoverbuggy.Seats += 0 -> new SeatDefinition()
|
||||
twomanhoverbuggy.Seats(0).Bailable = true
|
||||
|
|
@ -5295,6 +5324,9 @@ object GlobalDefinitions {
|
|||
|
||||
mediumtransport.Name = "mediumtransport" // Deliverer
|
||||
mediumtransport.MaxHealth = 2500
|
||||
mediumtransport.Damageable = true
|
||||
mediumtransport.Repairable = true
|
||||
mediumtransport.RepairIfDestroyed = false
|
||||
mediumtransport.MaxShields = 500 + 1
|
||||
mediumtransport.Seats += 0 -> new SeatDefinition()
|
||||
mediumtransport.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax
|
||||
|
|
@ -5320,6 +5352,9 @@ object GlobalDefinitions {
|
|||
|
||||
battlewagon.Name = "battlewagon" // Raider
|
||||
battlewagon.MaxHealth = 2500
|
||||
battlewagon.Damageable = true
|
||||
battlewagon.Repairable = true
|
||||
battlewagon.RepairIfDestroyed = false
|
||||
battlewagon.MaxShields = 500 + 1
|
||||
battlewagon.Seats += 0 -> new SeatDefinition()
|
||||
battlewagon.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax
|
||||
|
|
@ -5348,6 +5383,9 @@ object GlobalDefinitions {
|
|||
|
||||
thunderer.Name = "thunderer"
|
||||
thunderer.MaxHealth = 2500
|
||||
thunderer.Damageable = true
|
||||
thunderer.Repairable = true
|
||||
thunderer.RepairIfDestroyed = false
|
||||
thunderer.MaxShields = 500 + 1
|
||||
thunderer.Seats += 0 -> new SeatDefinition()
|
||||
thunderer.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax
|
||||
|
|
@ -5373,6 +5411,9 @@ object GlobalDefinitions {
|
|||
|
||||
aurora.Name = "aurora"
|
||||
aurora.MaxHealth = 2500
|
||||
aurora.Damageable = true
|
||||
aurora.Repairable = true
|
||||
aurora.RepairIfDestroyed = false
|
||||
aurora.MaxShields = 500 + 1
|
||||
aurora.Seats += 0 -> new SeatDefinition()
|
||||
aurora.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax
|
||||
|
|
@ -5398,6 +5439,9 @@ object GlobalDefinitions {
|
|||
|
||||
apc_tr.Name = "apc_tr" // Juggernaut
|
||||
apc_tr.MaxHealth = 6000
|
||||
apc_tr.Damageable = true
|
||||
apc_tr.Repairable = true
|
||||
apc_tr.RepairIfDestroyed = false
|
||||
apc_tr.MaxShields = 1200 + 1
|
||||
apc_tr.Seats += 0 -> new SeatDefinition()
|
||||
apc_tr.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax
|
||||
|
|
@ -5445,6 +5489,9 @@ object GlobalDefinitions {
|
|||
|
||||
apc_nc.Name = "apc_nc" // Vindicator
|
||||
apc_nc.MaxHealth = 6000
|
||||
apc_nc.Damageable = true
|
||||
apc_nc.Repairable = true
|
||||
apc_nc.RepairIfDestroyed = false
|
||||
apc_nc.MaxShields = 1200 + 1
|
||||
apc_nc.Seats += 0 -> new SeatDefinition()
|
||||
apc_nc.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax
|
||||
|
|
@ -5492,6 +5539,9 @@ object GlobalDefinitions {
|
|||
|
||||
apc_vs.Name = "apc_vs" // Leviathan
|
||||
apc_vs.MaxHealth = 6000
|
||||
apc_vs.Damageable = true
|
||||
apc_vs.Repairable = true
|
||||
apc_vs.RepairIfDestroyed = false
|
||||
apc_vs.MaxShields = 1200 + 1
|
||||
apc_vs.Seats += 0 -> new SeatDefinition()
|
||||
apc_vs.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax
|
||||
|
|
@ -5539,6 +5589,9 @@ object GlobalDefinitions {
|
|||
|
||||
lightning.Name = "lightning"
|
||||
lightning.MaxHealth = 2000
|
||||
lightning.Damageable = true
|
||||
lightning.Repairable = true
|
||||
lightning.RepairIfDestroyed = false
|
||||
lightning.MaxShields = 400 + 1
|
||||
lightning.Seats += 0 -> new SeatDefinition()
|
||||
lightning.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax
|
||||
|
|
@ -5555,6 +5608,9 @@ object GlobalDefinitions {
|
|||
|
||||
prowler.Name = "prowler"
|
||||
prowler.MaxHealth = 4800
|
||||
prowler.Damageable = true
|
||||
prowler.Repairable = true
|
||||
prowler.RepairIfDestroyed = false
|
||||
prowler.MaxShields = 960 + 1
|
||||
prowler.Seats += 0 -> new SeatDefinition()
|
||||
prowler.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax
|
||||
|
|
@ -5576,6 +5632,9 @@ object GlobalDefinitions {
|
|||
|
||||
vanguard.Name = "vanguard"
|
||||
vanguard.MaxHealth = 5400
|
||||
vanguard.Damageable = true
|
||||
vanguard.Repairable = true
|
||||
vanguard.RepairIfDestroyed = false
|
||||
vanguard.MaxShields = 1080 + 1
|
||||
vanguard.Seats += 0 -> new SeatDefinition()
|
||||
vanguard.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax
|
||||
|
|
@ -5593,6 +5652,9 @@ object GlobalDefinitions {
|
|||
|
||||
magrider.Name = "magrider"
|
||||
magrider.MaxHealth = 4200
|
||||
magrider.Damageable = true
|
||||
magrider.Repairable = true
|
||||
magrider.RepairIfDestroyed = false
|
||||
magrider.MaxShields = 840 + 1
|
||||
magrider.Seats += 0 -> new SeatDefinition()
|
||||
magrider.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax
|
||||
|
|
@ -5613,6 +5675,9 @@ object GlobalDefinitions {
|
|||
val utilityConverter = new UtilityVehicleConverter
|
||||
ant.Name = "ant"
|
||||
ant.MaxHealth = 2000
|
||||
ant.Damageable = true
|
||||
ant.Repairable = true
|
||||
ant.RepairIfDestroyed = false
|
||||
ant.MaxShields = 400 + 1
|
||||
ant.Seats += 0 -> new SeatDefinition()
|
||||
ant.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax
|
||||
|
|
@ -5630,6 +5695,9 @@ object GlobalDefinitions {
|
|||
|
||||
ams.Name = "ams"
|
||||
ams.MaxHealth = 3000
|
||||
ams.Damageable = true
|
||||
ams.Repairable = true
|
||||
ams.RepairIfDestroyed = false
|
||||
ams.MaxShields = 600 + 1
|
||||
ams.Seats += 0 -> new SeatDefinition()
|
||||
ams.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax
|
||||
|
|
@ -5652,6 +5720,9 @@ object GlobalDefinitions {
|
|||
val variantConverter = new VariantVehicleConverter
|
||||
router.Name = "router"
|
||||
router.MaxHealth = 4000
|
||||
router.Damageable = true
|
||||
router.Repairable = true
|
||||
router.RepairIfDestroyed = false
|
||||
router.MaxShields = 800 + 1
|
||||
router.Seats += 0 -> new SeatDefinition()
|
||||
router.MountPoints += 1 -> 0
|
||||
|
|
@ -5671,6 +5742,9 @@ object GlobalDefinitions {
|
|||
|
||||
switchblade.Name = "switchblade"
|
||||
switchblade.MaxHealth = 1750
|
||||
switchblade.Damageable = true
|
||||
switchblade.Repairable = true
|
||||
switchblade.RepairIfDestroyed = false
|
||||
switchblade.MaxShields = 350 + 1
|
||||
switchblade.Seats += 0 -> new SeatDefinition()
|
||||
switchblade.Seats(0).ControlledWeapon = 1
|
||||
|
|
@ -5691,6 +5765,9 @@ object GlobalDefinitions {
|
|||
|
||||
flail.Name = "flail"
|
||||
flail.MaxHealth = 2400
|
||||
flail.Damageable = true
|
||||
flail.Repairable = true
|
||||
flail.RepairIfDestroyed = false
|
||||
flail.MaxShields = 480 + 1
|
||||
flail.Seats += 0 -> new SeatDefinition()
|
||||
flail.Seats(0).ControlledWeapon = 1
|
||||
|
|
@ -5709,6 +5786,9 @@ object GlobalDefinitions {
|
|||
|
||||
mosquito.Name = "mosquito"
|
||||
mosquito.MaxHealth = 665
|
||||
mosquito.Damageable = true
|
||||
mosquito.Repairable = true
|
||||
mosquito.RepairIfDestroyed = false
|
||||
mosquito.MaxShields = 133 + 1
|
||||
mosquito.CanFly = true
|
||||
mosquito.Seats += 0 -> new SeatDefinition()
|
||||
|
|
@ -5726,6 +5806,9 @@ object GlobalDefinitions {
|
|||
|
||||
lightgunship.Name = "lightgunship" // Reaver
|
||||
lightgunship.MaxHealth = 1000
|
||||
lightgunship.Damageable = true
|
||||
lightgunship.Repairable = true
|
||||
lightgunship.RepairIfDestroyed = false
|
||||
lightgunship.MaxShields = 200 + 1
|
||||
lightgunship.CanFly = true
|
||||
lightgunship.Seats += 0 -> new SeatDefinition()
|
||||
|
|
@ -5744,6 +5827,9 @@ object GlobalDefinitions {
|
|||
|
||||
wasp.Name = "wasp"
|
||||
wasp.MaxHealth = 515
|
||||
wasp.Damageable = true
|
||||
wasp.Repairable = true
|
||||
wasp.RepairIfDestroyed = false
|
||||
wasp.MaxShields = 103 + 1
|
||||
wasp.CanFly = true
|
||||
wasp.Seats += 0 -> new SeatDefinition()
|
||||
|
|
@ -5761,6 +5847,9 @@ object GlobalDefinitions {
|
|||
|
||||
liberator.Name = "liberator"
|
||||
liberator.MaxHealth = 2500
|
||||
liberator.Damageable = true
|
||||
liberator.Repairable = true
|
||||
liberator.RepairIfDestroyed = false
|
||||
liberator.MaxShields = 500 + 1
|
||||
liberator.CanFly = true
|
||||
liberator.Seats += 0 -> new SeatDefinition()
|
||||
|
|
@ -5786,6 +5875,9 @@ object GlobalDefinitions {
|
|||
|
||||
vulture.Name = "vulture"
|
||||
vulture.MaxHealth = 2500
|
||||
vulture.Damageable = true
|
||||
vulture.Repairable = true
|
||||
vulture.RepairIfDestroyed = false
|
||||
vulture.MaxShields = 500 + 1
|
||||
vulture.CanFly = true
|
||||
vulture.Seats += 0 -> new SeatDefinition()
|
||||
|
|
@ -5811,6 +5903,10 @@ object GlobalDefinitions {
|
|||
|
||||
dropship.Name = "dropship" // Galaxy
|
||||
dropship.MaxHealth = 5000
|
||||
dropship.Damageable = true
|
||||
dropship.Repairable = true
|
||||
dropship.RepairDistance = 20
|
||||
dropship.RepairIfDestroyed = false
|
||||
dropship.MaxShields = 1000 + 1
|
||||
dropship.CanFly = true
|
||||
dropship.Seats += 0 -> new SeatDefinition()
|
||||
|
|
@ -5868,6 +5964,10 @@ object GlobalDefinitions {
|
|||
|
||||
galaxy_gunship.Name = "galaxy_gunship"
|
||||
galaxy_gunship.MaxHealth = 6000
|
||||
galaxy_gunship.Damageable = true
|
||||
galaxy_gunship.Repairable = true
|
||||
galaxy_gunship.RepairDistance = 20
|
||||
galaxy_gunship.RepairIfDestroyed = false
|
||||
galaxy_gunship.MaxShields = 1200 + 1
|
||||
galaxy_gunship.CanFly = true
|
||||
galaxy_gunship.Seats += 0 -> new SeatDefinition()
|
||||
|
|
@ -5902,6 +6002,10 @@ object GlobalDefinitions {
|
|||
|
||||
lodestar.Name = "lodestar"
|
||||
lodestar.MaxHealth = 5000
|
||||
lodestar.Damageable = true
|
||||
lodestar.Repairable = true
|
||||
lodestar.RepairDistance = 20
|
||||
lodestar.RepairIfDestroyed = false
|
||||
lodestar.MaxShields = 1000 + 1
|
||||
lodestar.CanFly = true
|
||||
lodestar.Seats += 0 -> new SeatDefinition()
|
||||
|
|
@ -5926,6 +6030,9 @@ object GlobalDefinitions {
|
|||
|
||||
phantasm.Name = "phantasm"
|
||||
phantasm.MaxHealth = 2500
|
||||
phantasm.Damageable = true
|
||||
phantasm.Repairable = true
|
||||
phantasm.RepairIfDestroyed = false
|
||||
phantasm.MaxShields = 500 + 1
|
||||
phantasm.CanCloak = true
|
||||
phantasm.CanFly = true
|
||||
|
|
@ -5958,23 +6065,35 @@ object GlobalDefinitions {
|
|||
boomer.Name = "boomer"
|
||||
boomer.Descriptor = "Boomers"
|
||||
boomer.MaxHealth = 100
|
||||
boomer.Damageable = true
|
||||
boomer.DamageableByFriendlyFire = false
|
||||
boomer.Repairable = false
|
||||
boomer.DeployCategory = DeployableCategory.Boomers
|
||||
boomer.DeployTime = Duration.create(1000, "ms")
|
||||
|
||||
he_mine.Name = "he_mine"
|
||||
he_mine.Descriptor = "Mines"
|
||||
he_mine.MaxHealth = 100
|
||||
he_mine.Damageable = true
|
||||
he_mine.DamageableByFriendlyFire = false
|
||||
he_mine.Repairable = false
|
||||
he_mine.DeployTime = Duration.create(1000, "ms")
|
||||
|
||||
jammer_mine.Name = "jammer_mine"
|
||||
jammer_mine.Descriptor = "JammerMines"
|
||||
jammer_mine.MaxHealth = 100
|
||||
jammer_mine.Damageable = true
|
||||
jammer_mine.DamageableByFriendlyFire = false
|
||||
jammer_mine.Repairable = false
|
||||
jammer_mine.DeployTime = Duration.create(1000, "ms")
|
||||
jammer_mine.DetonateOnJamming = false
|
||||
|
||||
spitfire_turret.Name = "spitfire_turret"
|
||||
spitfire_turret.Descriptor = "Spitfires"
|
||||
spitfire_turret.MaxHealth = 100
|
||||
spitfire_turret.Damageable = true
|
||||
spitfire_turret.Repairable = true
|
||||
spitfire_turret.RepairIfDestroyed = false
|
||||
spitfire_turret.Weapons += 1 -> new mutable.HashMap()
|
||||
spitfire_turret.Weapons(1) += TurretUpgrade.None -> spitfire_weapon
|
||||
spitfire_turret.ReserveAmmunition = false
|
||||
|
|
@ -5985,6 +6104,9 @@ object GlobalDefinitions {
|
|||
spitfire_cloaked.Name = "spitfire_cloaked"
|
||||
spitfire_cloaked.Descriptor = "CloakingSpitfires"
|
||||
spitfire_cloaked.MaxHealth = 100
|
||||
spitfire_cloaked.Damageable = true
|
||||
spitfire_cloaked.Repairable = true
|
||||
spitfire_cloaked.RepairIfDestroyed = false
|
||||
spitfire_cloaked.Weapons += 1 -> new mutable.HashMap()
|
||||
spitfire_cloaked.Weapons(1) += TurretUpgrade.None -> spitfire_weapon
|
||||
spitfire_cloaked.ReserveAmmunition = false
|
||||
|
|
@ -5995,6 +6117,9 @@ object GlobalDefinitions {
|
|||
spitfire_aa.Name = "spitfire_aa"
|
||||
spitfire_aa.Descriptor = "FlakSpitfires"
|
||||
spitfire_aa.MaxHealth = 100
|
||||
spitfire_aa.Damageable = true
|
||||
spitfire_aa.Repairable = true
|
||||
spitfire_aa.RepairIfDestroyed = false
|
||||
spitfire_aa.Weapons += 1 -> new mutable.HashMap()
|
||||
spitfire_aa.Weapons(1) += TurretUpgrade.None -> spitfire_aa_weapon
|
||||
spitfire_aa.ReserveAmmunition = false
|
||||
|
|
@ -6005,17 +6130,25 @@ object GlobalDefinitions {
|
|||
motionalarmsensor.Name = "motionalarmsensor"
|
||||
motionalarmsensor.Descriptor = "MotionSensors"
|
||||
motionalarmsensor.MaxHealth = 100
|
||||
motionalarmsensor.Damageable = true
|
||||
motionalarmsensor.Repairable = true
|
||||
motionalarmsensor.RepairIfDestroyed = false
|
||||
motionalarmsensor.DeployTime = Duration.create(1000, "ms")
|
||||
|
||||
sensor_shield.Name = "sensor_shield"
|
||||
sensor_shield.Descriptor = "SensorShields"
|
||||
sensor_shield.MaxHealth = 100
|
||||
sensor_shield.Damageable = true
|
||||
sensor_shield.Repairable = true
|
||||
sensor_shield.RepairIfDestroyed = false
|
||||
sensor_shield.DeployTime = Duration.create(5000, "ms")
|
||||
|
||||
tank_traps.Name = "tank_traps"
|
||||
tank_traps.Descriptor = "TankTraps"
|
||||
tank_traps.MaxHealth = 5000
|
||||
tank_traps.Packet = new TRAPConverter
|
||||
tank_traps.Damageable = true
|
||||
tank_traps.Repairable = true
|
||||
tank_traps.RepairIfDestroyed = false
|
||||
tank_traps.DeployCategory = DeployableCategory.TankTraps
|
||||
tank_traps.DeployTime = Duration.create(6000, "ms")
|
||||
tank_traps.Model = StandardResolutions.SimpleDeployables
|
||||
|
|
@ -6024,6 +6157,9 @@ object GlobalDefinitions {
|
|||
portable_manned_turret.Name = "portable_manned_turret"
|
||||
portable_manned_turret.Descriptor = "FieldTurrets"
|
||||
portable_manned_turret.MaxHealth = 1000
|
||||
portable_manned_turret.Damageable = true
|
||||
portable_manned_turret.Repairable = true
|
||||
portable_manned_turret.RepairIfDestroyed = false
|
||||
portable_manned_turret.MountPoints += 1 -> 0
|
||||
portable_manned_turret.MountPoints += 2 -> 0
|
||||
portable_manned_turret.Weapons += 1 -> new mutable.HashMap()
|
||||
|
|
@ -6038,6 +6174,9 @@ object GlobalDefinitions {
|
|||
portable_manned_turret_nc.Name = "portable_manned_turret_nc"
|
||||
portable_manned_turret_nc.Descriptor = "FieldTurrets"
|
||||
portable_manned_turret_nc.MaxHealth = 1000
|
||||
portable_manned_turret_nc.Damageable = true
|
||||
portable_manned_turret_nc.Repairable = true
|
||||
portable_manned_turret_nc.RepairIfDestroyed = false
|
||||
portable_manned_turret_nc.MountPoints += 1 -> 0
|
||||
portable_manned_turret_nc.MountPoints += 2 -> 0
|
||||
portable_manned_turret_nc.Weapons += 1 -> new mutable.HashMap()
|
||||
|
|
@ -6052,6 +6191,9 @@ object GlobalDefinitions {
|
|||
portable_manned_turret_tr.Name = "portable_manned_turret_tr"
|
||||
portable_manned_turret_tr.Descriptor = "FieldTurrets"
|
||||
portable_manned_turret_tr.MaxHealth = 1000
|
||||
portable_manned_turret_tr.Damageable = true
|
||||
portable_manned_turret_tr.Repairable = true
|
||||
portable_manned_turret_tr.RepairIfDestroyed = false
|
||||
portable_manned_turret_tr.MountPoints += 1 -> 0
|
||||
portable_manned_turret_tr.MountPoints += 2 -> 0
|
||||
portable_manned_turret_tr.Weapons += 1 -> new mutable.HashMap()
|
||||
|
|
@ -6066,6 +6208,9 @@ object GlobalDefinitions {
|
|||
portable_manned_turret_vs.Name = "portable_manned_turret_vs"
|
||||
portable_manned_turret_vs.Descriptor = "FieldTurrets"
|
||||
portable_manned_turret_vs.MaxHealth = 1000
|
||||
portable_manned_turret_vs.Damageable = true
|
||||
portable_manned_turret_vs.Repairable = true
|
||||
portable_manned_turret_vs.RepairIfDestroyed = false
|
||||
portable_manned_turret_vs.MountPoints += 1 -> 0
|
||||
portable_manned_turret_vs.MountPoints += 2 -> 0
|
||||
portable_manned_turret_vs.Weapons += 1 -> new mutable.HashMap()
|
||||
|
|
@ -6080,18 +6225,27 @@ object GlobalDefinitions {
|
|||
deployable_shield_generator.Name = "deployable_shield_generator"
|
||||
deployable_shield_generator.Descriptor = "ShieldGenerators"
|
||||
deployable_shield_generator.MaxHealth = 1700
|
||||
deployable_shield_generator.Damageable = true
|
||||
deployable_shield_generator.Repairable = true
|
||||
deployable_shield_generator.RepairIfDestroyed = false
|
||||
deployable_shield_generator.DeployTime = Duration.create(6000, "ms")
|
||||
deployable_shield_generator.Model = StandardResolutions.ComplexDeployables
|
||||
|
||||
router_telepad_deployable.Name = "router_telepad_deployable"
|
||||
router_telepad_deployable.MaxHealth = 100
|
||||
router_telepad_deployable.Damageable = true
|
||||
router_telepad_deployable.Repairable = false
|
||||
router_telepad_deployable.DeployTime = Duration.create(1, "ms")
|
||||
router_telepad_deployable.DeployCategory = DeployableCategory.Telepads
|
||||
router_telepad_deployable.Packet = new TelepadDeployableConverter
|
||||
router_telepad_deployable.Model = StandardResolutions.SimpleDeployables
|
||||
|
||||
internal_router_telepad_deployable.Name = "router_telepad_deployable"
|
||||
internal_router_telepad_deployable.MaxHealth = 1
|
||||
internal_router_telepad_deployable.Damageable = false
|
||||
internal_router_telepad_deployable.Repairable = false
|
||||
internal_router_telepad_deployable.DeployTime = Duration.create(1, "ms")
|
||||
internal_router_telepad_deployable.DeployCategory = DeployableCategory.Telepads
|
||||
internal_router_telepad_deployable.Packet = new InternalTelepadDeployableConverter
|
||||
}
|
||||
|
||||
|
|
@ -6102,14 +6256,24 @@ object GlobalDefinitions {
|
|||
ams_respawn_tube.Name = "ams_respawn_tube"
|
||||
ams_respawn_tube.Delay = 5
|
||||
ams_respawn_tube.SpecificPointFunc = SpawnPoint.AMS
|
||||
ams_respawn_tube.Damageable = false
|
||||
ams_respawn_tube.Repairable = false
|
||||
|
||||
matrix_terminala.Name = "matrix_terminala"
|
||||
matrix_terminala.Damageable = false
|
||||
matrix_terminala.Repairable = false
|
||||
|
||||
matrix_terminalb.Name = "matrix_terminalb"
|
||||
matrix_terminalb.Damageable = false
|
||||
matrix_terminalb.Repairable = false
|
||||
|
||||
matrix_terminalc.Name = "matrix_terminalc"
|
||||
matrix_terminalc.Damageable = false
|
||||
matrix_terminalc.Repairable = false
|
||||
|
||||
spawn_terminal.Name = "spawn_terminal"
|
||||
spawn_terminal.Damageable = false
|
||||
spawn_terminal.Repairable = false
|
||||
|
||||
order_terminal.Name = "order_terminal"
|
||||
order_terminal.Tab += 0 -> OrderTerminalDefinition.EquipmentPage(EquipmentTerminalDefinition.infantryAmmunition ++ EquipmentTerminalDefinition.infantryWeapons)
|
||||
|
|
@ -6118,6 +6282,11 @@ object GlobalDefinitions {
|
|||
order_terminal.Tab += 3 -> OrderTerminalDefinition.EquipmentPage(EquipmentTerminalDefinition.vehicleAmmunition)
|
||||
order_terminal.Tab += 4 -> OrderTerminalDefinition.InfantryLoadoutPage()
|
||||
order_terminal.SellEquipmentByDefault = true
|
||||
order_terminal.MaxHealth = 500
|
||||
order_terminal.Damageable = true
|
||||
order_terminal.Repairable = true
|
||||
order_terminal.RepairIfDestroyed = true
|
||||
order_terminal.Subtract.Damage1 = 8
|
||||
|
||||
order_terminala.Name = "order_terminala"
|
||||
order_terminala.Tab += 0 -> OrderTerminalDefinition.EquipmentPage(EquipmentTerminalDefinition.infantryAmmunition ++ EquipmentTerminalDefinition.infantryWeapons)
|
||||
|
|
@ -6127,6 +6296,8 @@ object GlobalDefinitions {
|
|||
order_terminala.Tab += 4 -> OrderTerminalDefinition.InfantryLoadoutPage()
|
||||
order_terminala.Tab(4).asInstanceOf[OrderTerminalDefinition.InfantryLoadoutPage].Exclude = ExoSuitType.MAX
|
||||
order_terminala.SellEquipmentByDefault = true
|
||||
order_terminala.Damageable = false
|
||||
order_terminala.Repairable = false
|
||||
|
||||
order_terminalb.Name = "order_terminalb"
|
||||
order_terminalb.Tab += 0 -> OrderTerminalDefinition.EquipmentPage(EquipmentTerminalDefinition.infantryAmmunition ++ EquipmentTerminalDefinition.infantryWeapons)
|
||||
|
|
@ -6136,6 +6307,8 @@ object GlobalDefinitions {
|
|||
order_terminalb.Tab += 4 -> OrderTerminalDefinition.InfantryLoadoutPage()
|
||||
order_terminalb.Tab(4).asInstanceOf[OrderTerminalDefinition.InfantryLoadoutPage].Exclude = ExoSuitType.MAX
|
||||
order_terminalb.SellEquipmentByDefault = true
|
||||
order_terminalb.Damageable = false
|
||||
order_terminalb.Repairable = false
|
||||
|
||||
vanu_equipment_term.Name = "vanu_equipment_term"
|
||||
vanu_equipment_term.Tab += 0 -> OrderTerminalDefinition.EquipmentPage(EquipmentTerminalDefinition.infantryAmmunition ++ EquipmentTerminalDefinition.infantryWeapons)
|
||||
|
|
@ -6144,55 +6317,124 @@ object GlobalDefinitions {
|
|||
vanu_equipment_term.Tab += 3 -> OrderTerminalDefinition.EquipmentPage(EquipmentTerminalDefinition.vehicleAmmunition)
|
||||
vanu_equipment_term.Tab += 4 -> OrderTerminalDefinition.InfantryLoadoutPage()
|
||||
vanu_equipment_term.SellEquipmentByDefault = true
|
||||
vanu_equipment_term.Damageable = false
|
||||
vanu_equipment_term.Repairable = false
|
||||
|
||||
cert_terminal.Name = "cert_terminal"
|
||||
cert_terminal.Tab += 0 -> OrderTerminalDefinition.CertificationPage(CertTerminalDefinition.certs)
|
||||
cert_terminal.MaxHealth = 500
|
||||
cert_terminal.Damageable = true
|
||||
cert_terminal.Repairable = true
|
||||
cert_terminal.RepairIfDestroyed = true
|
||||
cert_terminal.Subtract.Damage1 = 8
|
||||
|
||||
implant_terminal_mech.Name = "implant_terminal_mech"
|
||||
implant_terminal_mech.MaxHealth = 1500 //TODO 1000; right now, 1000 (mech) + 500 (interface)
|
||||
implant_terminal_mech.Damageable = true
|
||||
implant_terminal_mech.Repairable = true
|
||||
implant_terminal_mech.RepairIfDestroyed = true
|
||||
|
||||
implant_terminal_interface.Name = "implant_terminal_interface"
|
||||
implant_terminal_interface.Tab += 0 -> OrderTerminalDefinition.ImplantPage(ImplantTerminalDefinition.implants)
|
||||
implant_terminal_interface.MaxHealth = 500
|
||||
implant_terminal_interface.Damageable = false //TODO true
|
||||
implant_terminal_interface.Repairable = true
|
||||
implant_terminal_interface.RepairIfDestroyed = true
|
||||
|
||||
ground_vehicle_terminal.Name = "ground_vehicle_terminal"
|
||||
ground_vehicle_terminal.Tab += 46769 -> OrderTerminalDefinition.VehiclePage(VehicleTerminalDefinition.groundVehicles, VehicleTerminalDefinition.trunk)
|
||||
ground_vehicle_terminal.Tab += 4 -> OrderTerminalDefinition.VehicleLoadoutPage()
|
||||
ground_vehicle_terminal.MaxHealth = 500
|
||||
ground_vehicle_terminal.Damageable = true
|
||||
ground_vehicle_terminal.Repairable = true
|
||||
ground_vehicle_terminal.RepairIfDestroyed = true
|
||||
ground_vehicle_terminal.Subtract.Damage1 = 8
|
||||
|
||||
air_vehicle_terminal.Name = "air_vehicle_terminal"
|
||||
air_vehicle_terminal.Tab += 46769 -> OrderTerminalDefinition.VehiclePage(VehicleTerminalDefinition.flight1Vehicles, VehicleTerminalDefinition.trunk)
|
||||
air_vehicle_terminal.Tab += 4 -> OrderTerminalDefinition.VehicleLoadoutPage()
|
||||
air_vehicle_terminal.MaxHealth = 500
|
||||
air_vehicle_terminal.Damageable = true
|
||||
air_vehicle_terminal.Repairable = true
|
||||
air_vehicle_terminal.RepairIfDestroyed = true
|
||||
air_vehicle_terminal.Subtract.Damage1 = 8
|
||||
|
||||
dropship_vehicle_terminal.Name = "dropship_vehicle_terminal"
|
||||
dropship_vehicle_terminal.Tab += 46769 -> OrderTerminalDefinition.VehiclePage(VehicleTerminalDefinition.flight1Vehicles ++ VehicleTerminalDefinition.flight2Vehicles, VehicleTerminalDefinition.trunk)
|
||||
dropship_vehicle_terminal.Tab += 4 -> OrderTerminalDefinition.VehicleLoadoutPage()
|
||||
dropship_vehicle_terminal.MaxHealth = 500
|
||||
dropship_vehicle_terminal.Damageable = true
|
||||
dropship_vehicle_terminal.Repairable = true
|
||||
dropship_vehicle_terminal.RepairIfDestroyed = true
|
||||
dropship_vehicle_terminal.Subtract.Damage1 = 8
|
||||
|
||||
vehicle_terminal_combined.Name = "vehicle_terminal_combined"
|
||||
vehicle_terminal_combined.Tab += 46769 -> OrderTerminalDefinition.VehiclePage(VehicleTerminalDefinition.flight1Vehicles ++ VehicleTerminalDefinition.groundVehicles, VehicleTerminalDefinition.trunk)
|
||||
vehicle_terminal_combined.Tab += 4 -> OrderTerminalDefinition.VehicleLoadoutPage()
|
||||
vehicle_terminal_combined.MaxHealth = 500
|
||||
vehicle_terminal_combined.Damageable = true
|
||||
vehicle_terminal_combined.Repairable = true
|
||||
vehicle_terminal_combined.RepairIfDestroyed = true
|
||||
vehicle_terminal_combined.Subtract.Damage1 = 8
|
||||
|
||||
vanu_air_vehicle_term.Name = "vanu_air_vehicle_term"
|
||||
vanu_air_vehicle_term.Tab += 46769 -> OrderTerminalDefinition.VehiclePage(VehicleTerminalDefinition.flight1Vehicles, VehicleTerminalDefinition.trunk)
|
||||
vanu_air_vehicle_term.Tab += 4 -> OrderTerminalDefinition.VehicleLoadoutPage()
|
||||
vanu_air_vehicle_term.MaxHealth = 500
|
||||
vanu_air_vehicle_term.Damageable = true
|
||||
vanu_air_vehicle_term.Repairable = true
|
||||
vanu_air_vehicle_term.RepairIfDestroyed = true
|
||||
vanu_air_vehicle_term.Subtract.Damage1 = 8
|
||||
|
||||
vanu_vehicle_term.Name = "vanu_vehicle_term"
|
||||
vanu_vehicle_term.Tab += 46769 -> OrderTerminalDefinition.VehiclePage(VehicleTerminalDefinition.groundVehicles, VehicleTerminalDefinition.trunk)
|
||||
vanu_vehicle_term.Tab += 4 -> OrderTerminalDefinition.VehicleLoadoutPage()
|
||||
vanu_vehicle_term.MaxHealth = 500
|
||||
vanu_vehicle_term.Damageable = true
|
||||
vanu_vehicle_term.Repairable = true
|
||||
vanu_vehicle_term.RepairIfDestroyed = true
|
||||
vanu_vehicle_term.Subtract.Damage1 = 8
|
||||
|
||||
bfr_terminal.Name = "bfr_terminal"
|
||||
bfr_terminal.Tab += 46769 -> OrderTerminalDefinition.VehiclePage(VehicleTerminalDefinition.bfrVehicles, VehicleTerminalDefinition.trunk)
|
||||
bfr_terminal.Tab += 4 -> OrderTerminalDefinition.VehicleLoadoutPage()
|
||||
bfr_terminal.MaxHealth = 500
|
||||
bfr_terminal.Damageable = true
|
||||
bfr_terminal.Repairable = true
|
||||
bfr_terminal.RepairIfDestroyed = true
|
||||
bfr_terminal.Subtract.Damage1 = 8
|
||||
|
||||
respawn_tube.Name = "respawn_tube"
|
||||
respawn_tube.Delay = 10
|
||||
respawn_tube.SpecificPointFunc = SpawnPoint.Tube
|
||||
respawn_tube.MaxHealth = 1000
|
||||
respawn_tube.Damageable = true
|
||||
respawn_tube.DamageableByFriendlyFire = false
|
||||
respawn_tube.Repairable = true
|
||||
respawn_tube.RepairIfDestroyed = true
|
||||
respawn_tube.Subtract.Damage1 = 8
|
||||
|
||||
respawn_tube_sanctuary.Name = "respawn_tube"
|
||||
respawn_tube_sanctuary.Delay = 10
|
||||
respawn_tube_sanctuary.SpecificPointFunc = SpawnPoint.Default
|
||||
respawn_tube_sanctuary.Damageable = false //true?
|
||||
respawn_tube_sanctuary.DamageableByFriendlyFire = false
|
||||
respawn_tube_sanctuary.Repairable = true
|
||||
|
||||
respawn_tube_tower.Name = "respawn_tube_tower"
|
||||
respawn_tube_tower.Delay = 10
|
||||
respawn_tube_tower.SpecificPointFunc = SpawnPoint.Tube
|
||||
respawn_tube_tower.MaxHealth = 1000
|
||||
respawn_tube_tower.Damageable = true
|
||||
respawn_tube_tower.DamageableByFriendlyFire = false
|
||||
respawn_tube_tower.Repairable = true
|
||||
respawn_tube_tower.RepairIfDestroyed = true
|
||||
respawn_tube_tower.Subtract.Damage1 = 8
|
||||
|
||||
teleportpad_terminal.Name = "teleportpad_terminal"
|
||||
teleportpad_terminal.Tab += 0 -> OrderTerminalDefinition.EquipmentPage(EquipmentTerminalDefinition.routerTerminal)
|
||||
teleportpad_terminal.Damageable = false
|
||||
teleportpad_terminal.Repairable = false
|
||||
|
||||
medical_terminal.Name = "medical_terminal"
|
||||
medical_terminal.Interval = 500
|
||||
|
|
@ -6200,6 +6442,10 @@ object GlobalDefinitions {
|
|||
medical_terminal.ArmorAmount = 10
|
||||
medical_terminal.UseRadius = 0.75f
|
||||
medical_terminal.TargetValidation += EffectTarget.Category.Player -> EffectTarget.Validation.Medical
|
||||
medical_terminal.MaxHealth = 500
|
||||
medical_terminal.Damageable = true
|
||||
medical_terminal.Repairable = true
|
||||
medical_terminal.RepairIfDestroyed = true
|
||||
|
||||
adv_med_terminal.Name = "adv_med_terminal"
|
||||
adv_med_terminal.Interval = 500
|
||||
|
|
@ -6207,18 +6453,26 @@ object GlobalDefinitions {
|
|||
adv_med_terminal.ArmorAmount = 15
|
||||
adv_med_terminal.UseRadius = 0.75f
|
||||
adv_med_terminal.TargetValidation += EffectTarget.Category.Player -> EffectTarget.Validation.Medical
|
||||
adv_med_terminal.MaxHealth = 750
|
||||
adv_med_terminal.Damageable = true
|
||||
adv_med_terminal.Repairable = true
|
||||
adv_med_terminal.RepairIfDestroyed = true
|
||||
|
||||
crystals_health_a.Name = "crystals_health_a"
|
||||
crystals_health_a.Interval = 500
|
||||
crystals_health_a.HealAmount = 4
|
||||
crystals_health_a.UseRadius = 5
|
||||
crystals_health_a.TargetValidation += EffectTarget.Category.Player -> EffectTarget.Validation.HealthCrystal
|
||||
crystals_health_a.Damageable = false
|
||||
crystals_health_a.Repairable = false
|
||||
|
||||
crystals_health_b.Name = "crystals_health_b"
|
||||
crystals_health_b.Interval = 500
|
||||
crystals_health_b.HealAmount = 4
|
||||
crystals_health_b.UseRadius = 5
|
||||
crystals_health_b.TargetValidation += EffectTarget.Category.Player -> EffectTarget.Validation.HealthCrystal
|
||||
crystals_health_b.Damageable = false
|
||||
crystals_health_b.Repairable = false
|
||||
|
||||
portable_med_terminal.Name = "portable_med_terminal"
|
||||
portable_med_terminal.Interval = 500
|
||||
|
|
@ -6226,53 +6480,116 @@ object GlobalDefinitions {
|
|||
portable_med_terminal.ArmorAmount = 10
|
||||
portable_med_terminal.UseRadius = 3
|
||||
portable_med_terminal.TargetValidation += EffectTarget.Category.Player -> EffectTarget.Validation.Medical
|
||||
portable_med_terminal.MaxHealth = 500
|
||||
portable_med_terminal.Damageable = false //TODO actually true
|
||||
portable_med_terminal.Repairable = false
|
||||
|
||||
pad_landing_frame.Name = "pad_landing_frame"
|
||||
pad_landing_frame.Interval = 1000
|
||||
pad_landing_frame.HealAmount = 60
|
||||
pad_landing_frame.UseRadius = 20
|
||||
pad_landing_frame.TargetValidation += EffectTarget.Category.Aircraft -> EffectTarget.Validation.PadLanding
|
||||
pad_landing_frame.Damageable = false
|
||||
pad_landing_frame.Repairable = false
|
||||
|
||||
pad_landing_tower_frame.Name = "pad_landing_tower_frame"
|
||||
pad_landing_tower_frame.Interval = 1000
|
||||
pad_landing_tower_frame.HealAmount = 60
|
||||
pad_landing_tower_frame.UseRadius = 20
|
||||
pad_landing_tower_frame.TargetValidation += EffectTarget.Category.Aircraft -> EffectTarget.Validation.PadLanding
|
||||
pad_landing_tower_frame.Damageable = false
|
||||
pad_landing_tower_frame.Repairable = false
|
||||
|
||||
repair_silo.Name = "repair_silo"
|
||||
repair_silo.Interval = 1000
|
||||
repair_silo.HealAmount = 60
|
||||
repair_silo.UseRadius = 20
|
||||
repair_silo.TargetValidation += EffectTarget.Category.Vehicle -> EffectTarget.Validation.RepairSilo
|
||||
repair_silo.Damageable = false
|
||||
repair_silo.Repairable = false
|
||||
|
||||
mb_pad_creation.Name = "mb_pad_creation"
|
||||
mb_pad_creation.Damageable = false
|
||||
mb_pad_creation.Repairable = false
|
||||
|
||||
dropship_pad_doors.Name = "dropship_pad_doors"
|
||||
dropship_pad_doors.Damageable = false
|
||||
dropship_pad_doors.Repairable = false
|
||||
|
||||
vanu_vehicle_creation_pad.Name = "vanu_vehicle_creation_pad"
|
||||
vanu_vehicle_creation_pad.Damageable = false
|
||||
vanu_vehicle_creation_pad.Repairable = false
|
||||
|
||||
mb_locker.Name = "mb_locker"
|
||||
mb_locker.Damageable = false
|
||||
mb_locker.Repairable = false
|
||||
|
||||
lock_external.Name = "lock_external"
|
||||
lock_external.Damageable = false
|
||||
lock_external.Repairable = false
|
||||
|
||||
door.Name = "door"
|
||||
door.Damageable = false
|
||||
door.Repairable = false
|
||||
|
||||
resource_silo.Name = "resource_silo"
|
||||
resource_silo.Damageable = false
|
||||
resource_silo.Repairable = false
|
||||
|
||||
capture_terminal.Name = "capture_terminal"
|
||||
capture_terminal.Damageable = false
|
||||
capture_terminal.Repairable = false
|
||||
|
||||
secondary_capture.Name = "secondary_capture"
|
||||
secondary_capture.Damageable = false
|
||||
secondary_capture.Repairable = false
|
||||
|
||||
vanu_control_console.Name = "vanu_control_console"
|
||||
vanu_control_console.Damageable = false
|
||||
vanu_control_console.Repairable = false
|
||||
|
||||
lodestar_repair_terminal.Name = "lodestar_repair_terminal"
|
||||
lodestar_repair_terminal.Interval = 1000
|
||||
lodestar_repair_terminal.HealAmount = 60
|
||||
lodestar_repair_terminal.UseRadius = 20
|
||||
lodestar_repair_terminal.TargetValidation += EffectTarget.Category.Vehicle -> EffectTarget.Validation.RepairSilo
|
||||
lodestar_repair_terminal.Damageable = false
|
||||
lodestar_repair_terminal.Repairable = false
|
||||
|
||||
multivehicle_rearm_terminal.Name = "multivehicle_rearm_terminal"
|
||||
multivehicle_rearm_terminal.Tab += 3 -> OrderTerminalDefinition.EquipmentPage(EquipmentTerminalDefinition.vehicleAmmunition)
|
||||
multivehicle_rearm_terminal.Tab += 4 -> OrderTerminalDefinition.VehicleLoadoutPage()
|
||||
multivehicle_rearm_terminal.SellEquipmentByDefault = true //TODO ?
|
||||
multivehicle_rearm_terminal.Damageable = false
|
||||
multivehicle_rearm_terminal.Repairable = false
|
||||
|
||||
bfr_rearm_terminal.Name = "bfr_rearm_terminal"
|
||||
bfr_rearm_terminal.Tab += 3 -> OrderTerminalDefinition.EquipmentPage(Map.empty[String, ()=>Equipment]) //TODO add stock to page
|
||||
bfr_rearm_terminal.Tab += 4 -> OrderTerminalDefinition.VehicleLoadoutPage()
|
||||
bfr_rearm_terminal.SellEquipmentByDefault = true //TODO ?
|
||||
bfr_rearm_terminal.Damageable = false
|
||||
bfr_rearm_terminal.Repairable = false
|
||||
|
||||
air_rearm_terminal.Name = "air_rearm_terminal"
|
||||
air_rearm_terminal.Tab += 3 -> OrderTerminalDefinition.EquipmentPage(EquipmentTerminalDefinition.vehicleAmmunition)
|
||||
air_rearm_terminal.Tab += 4 -> OrderTerminalDefinition.VehicleLoadoutPage()
|
||||
air_rearm_terminal.SellEquipmentByDefault = true //TODO ?
|
||||
air_rearm_terminal.Damageable = false
|
||||
air_rearm_terminal.Repairable = false
|
||||
|
||||
ground_rearm_terminal.Name = "ground_rearm_terminal"
|
||||
ground_rearm_terminal.Tab += 3 -> OrderTerminalDefinition.EquipmentPage(EquipmentTerminalDefinition.vehicleAmmunition)
|
||||
ground_rearm_terminal.Tab += 4 -> OrderTerminalDefinition.VehicleLoadoutPage()
|
||||
ground_rearm_terminal.SellEquipmentByDefault = true //TODO ?
|
||||
ground_rearm_terminal.Damageable = false
|
||||
ground_rearm_terminal.Repairable = false
|
||||
|
||||
manned_turret.Name = "manned_turret"
|
||||
manned_turret.MaxHealth = 3600
|
||||
manned_turret.Damageable = true
|
||||
manned_turret.DamageDisablesAt = 0
|
||||
manned_turret.Repairable = true
|
||||
manned_turret.RepairIfDestroyed = true
|
||||
manned_turret.Weapons += 1 -> new mutable.HashMap()
|
||||
manned_turret.Weapons(1) += TurretUpgrade.None -> phalanx_sgl_hevgatcan
|
||||
manned_turret.Weapons(1) += TurretUpgrade.AVCombo -> phalanx_avcombo
|
||||
|
|
@ -6283,6 +6600,10 @@ object GlobalDefinitions {
|
|||
|
||||
vanu_sentry_turret.Name = "vanu_sentry_turret"
|
||||
vanu_sentry_turret.MaxHealth = 1500
|
||||
vanu_sentry_turret.Damageable = true
|
||||
vanu_sentry_turret.DamageDisablesAt = 0
|
||||
vanu_sentry_turret.Repairable = true
|
||||
vanu_sentry_turret.RepairIfDestroyed = true
|
||||
vanu_sentry_turret.Weapons += 1 -> new mutable.HashMap()
|
||||
vanu_sentry_turret.Weapons(1) += TurretUpgrade.None -> vanu_sentry_turret_weapon
|
||||
vanu_sentry_turret.MountPoints += 1 -> 0
|
||||
|
|
@ -6290,8 +6611,41 @@ object GlobalDefinitions {
|
|||
vanu_sentry_turret.FactionLocked = false
|
||||
vanu_sentry_turret.ReserveAmmunition = false
|
||||
|
||||
painbox.Name = "painbox"
|
||||
painbox.Damageable = false
|
||||
painbox.Repairable = false
|
||||
|
||||
painbox_continuous.Name = "painbox_continuous"
|
||||
painbox_continuous.Damageable = false
|
||||
painbox_continuous.Repairable = false
|
||||
|
||||
painbox_door_radius.Name = "painbox_door_radius"
|
||||
painbox_door_radius.Damageable = false
|
||||
painbox_door_radius.Repairable = false
|
||||
|
||||
painbox_door_radius_continuous.Name = "painbox_door_radius_continuous"
|
||||
painbox_door_radius_continuous.Damageable = false
|
||||
painbox_door_radius_continuous.Repairable = false
|
||||
|
||||
painbox_radius.Name = "painbox_radius"
|
||||
painbox_radius.Damageable = false
|
||||
painbox_radius.Repairable = false
|
||||
|
||||
painbox_radius_continuous.Name = "painbox_radius_continuous"
|
||||
painbox_radius_continuous.Damageable = false
|
||||
painbox_radius_continuous.Repairable = false
|
||||
|
||||
gen_control.Name = "gen_control"
|
||||
gen_control.Damageable = false
|
||||
gen_control.Repairable = false
|
||||
|
||||
generator.Name = "generator"
|
||||
generator.MaxHealth = 4000
|
||||
generator.Damageable = true
|
||||
generator.DamageableByFriendlyFire = false
|
||||
generator.Repairable = true
|
||||
generator.RepairDistance = 13.5f
|
||||
generator.RepairIfDestroyed = true
|
||||
generator.Subtract.Damage1 = 9
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import net.psforever.types.Vector3
|
|||
*/
|
||||
abstract class PlanetSideGameObject extends IdentifiableEntity with WorldEntity {
|
||||
private var entity : WorldEntity = new SimpleWorldEntity()
|
||||
private var destroyed : Boolean = false
|
||||
|
||||
def Entity : WorldEntity = entity
|
||||
|
||||
|
|
@ -35,6 +36,13 @@ abstract class PlanetSideGameObject extends IdentifiableEntity with WorldEntity
|
|||
Entity.Velocity = vec
|
||||
}
|
||||
|
||||
def Destroyed : Boolean = destroyed
|
||||
|
||||
def Destroyed_=(state : Boolean) : Boolean = {
|
||||
destroyed = state
|
||||
Destroyed
|
||||
}
|
||||
|
||||
def Definition : ObjectDefinition
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,9 +23,9 @@ class Player(private val core : Avatar) extends PlanetSideServerObject
|
|||
with Container
|
||||
with JammableUnit
|
||||
with ZoneAware {
|
||||
private var alive : Boolean = false
|
||||
Health = 0 //player health is artificially managed as a part of their lifecycle; start entity as dead
|
||||
Destroyed = true //see isAlive
|
||||
private var backpack : Boolean = false
|
||||
private var health : Int = 0
|
||||
private var stamina : Int = 0
|
||||
private var armor : Int = 0
|
||||
|
||||
|
|
@ -34,7 +34,6 @@ class Player(private val core : Avatar) extends PlanetSideServerObject
|
|||
private var capacitorLastUsedMillis : Long = 0
|
||||
private var capacitorLastChargedMillis : Long = 0
|
||||
|
||||
private var maxHealth : Int = 100 //TODO affected by empire benefits, territory benefits, and bops
|
||||
private var maxStamina : Int = 100 //does anything affect this?
|
||||
|
||||
private var exosuit : ExoSuitDefinition = GlobalDefinitions.Standard
|
||||
|
|
@ -84,14 +83,14 @@ class Player(private val core : Avatar) extends PlanetSideServerObject
|
|||
|
||||
def LFS : Boolean = core.LFS
|
||||
|
||||
def isAlive : Boolean = alive
|
||||
def isAlive : Boolean = !Destroyed
|
||||
|
||||
def isBackpack : Boolean = backpack
|
||||
|
||||
def Spawn : Boolean = {
|
||||
if(!isAlive && !isBackpack) {
|
||||
alive = true
|
||||
Health = MaxHealth
|
||||
Destroyed = false
|
||||
Health = Definition.DefaultHealth
|
||||
Stamina = MaxStamina
|
||||
Armor = MaxArmor
|
||||
Capacitor = 0
|
||||
|
|
@ -101,14 +100,15 @@ class Player(private val core : Avatar) extends PlanetSideServerObject
|
|||
}
|
||||
|
||||
def Die : Boolean = {
|
||||
alive = false
|
||||
Destroyed = true
|
||||
Health = 0
|
||||
Stamina = 0
|
||||
false
|
||||
}
|
||||
|
||||
def Revive : Boolean = {
|
||||
alive = true
|
||||
Destroyed = false
|
||||
Health = Definition.DefaultHealth
|
||||
true
|
||||
}
|
||||
|
||||
|
|
@ -122,20 +122,6 @@ class Player(private val core : Avatar) extends PlanetSideServerObject
|
|||
}
|
||||
}
|
||||
|
||||
def Health : Int = health
|
||||
|
||||
def Health_=(assignHealth : Int) : Int = {
|
||||
health = math.min(math.max(0, assignHealth), MaxHealth)
|
||||
Health
|
||||
}
|
||||
|
||||
def MaxHealth : Int = maxHealth
|
||||
|
||||
def MaxHealth_=(max : Int) : Int = {
|
||||
maxHealth = math.min(math.max(0, max), 65535)
|
||||
MaxHealth
|
||||
}
|
||||
|
||||
def Stamina : Int = stamina
|
||||
|
||||
def Stamina_=(assignEnergy : Int) : Int = {
|
||||
|
|
@ -662,6 +648,21 @@ object Player {
|
|||
}
|
||||
}
|
||||
|
||||
def GetHackLevel(player : Player): Int = {
|
||||
if(player.Certifications.contains(CertificationType.ExpertHacking) || player.Certifications.contains(CertificationType.ElectronicsExpert)) {
|
||||
3
|
||||
}
|
||||
else if(player.Certifications.contains(CertificationType.AdvancedHacking)) {
|
||||
2
|
||||
}
|
||||
else if (player.Certifications.contains(CertificationType.Hacking)) {
|
||||
1
|
||||
}
|
||||
else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
def toString(obj : Player) : String = {
|
||||
val guid = if(obj.HasGUID) { s" ${obj.Continent}-${obj.GUID.guid}" } else { "" }
|
||||
s"${obj.core}$guid ${obj.Health}/${obj.MaxHealth} ${obj.Armor}/${obj.MaxArmor}"
|
||||
|
|
|
|||
49
common/src/main/scala/net/psforever/objects/Players.scala
Normal file
49
common/src/main/scala/net/psforever/objects/Players.scala
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
// Copyright (c) 2020 PSForever
|
||||
package net.psforever.objects
|
||||
|
||||
import net.psforever.packet.game.{InventoryStateMessage, RepairMessage}
|
||||
import services.Service
|
||||
import services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||
|
||||
object Players {
|
||||
private val log = org.log4s.getLogger("Players")
|
||||
|
||||
/**
|
||||
* Evaluate the progress of the user applying a tool to modify some other server object.
|
||||
* This action is using the medical applicator to revive a fallen (dead but not released) ally.
|
||||
* @param target the player being affected by the revive action
|
||||
* @param user the player performing the revive action
|
||||
* @param item the tool being used to revive the target player
|
||||
* @param progress the current progress value
|
||||
* @return `true`, if the next cycle of progress should occur;
|
||||
* `false`, otherwise
|
||||
*/
|
||||
def RevivingTickAction(target : Player, user : Player, item : Tool)(progress : Float) : Boolean = {
|
||||
if(!target.isAlive && !target.isBackpack &&
|
||||
user.isAlive && !user.isMoving &&
|
||||
user.Slot(user.DrawnSlot).Equipment.contains(item) && item.Magazine > 0) {
|
||||
val magazine = item.Discharge
|
||||
val events = target.Zone.AvatarEvents
|
||||
val uname = user.Name
|
||||
events ! AvatarServiceMessage(uname, AvatarAction.SendResponse(Service.defaultPlayerGUID, InventoryStateMessage(item.AmmoSlot.Box.GUID, item.GUID, magazine)))
|
||||
events ! AvatarServiceMessage(uname, AvatarAction.SendResponse(Service.defaultPlayerGUID, RepairMessage(target.GUID, progress.toInt)))
|
||||
true
|
||||
}
|
||||
else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* na
|
||||
* @see `AvatarAction.Revive`
|
||||
* @see `AvatarResponse.Revive`
|
||||
* @param target the player being revived
|
||||
* @param medic the name of the player doing the reviving
|
||||
*/
|
||||
def FinishRevivingPlayer(target : Player, medic : String)() : Unit = {
|
||||
val name = target.Name
|
||||
log.info(s"$medic had revived $name")
|
||||
target.Zone.AvatarEvents ! AvatarServiceMessage(name, AvatarAction.Revive(target.GUID))
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
// Copyright (c) 2019 PSForever
|
||||
package net.psforever.objects
|
||||
|
||||
import akka.actor.{Actor, ActorContext, Props}
|
||||
|
|
@ -8,12 +8,12 @@ import net.psforever.objects.definition.converter.SmallDeployableConverter
|
|||
import net.psforever.objects.definition.{ComplexDeployableDefinition, SimpleDeployableDefinition}
|
||||
import net.psforever.objects.equipment.{JammableBehavior, JammableUnit}
|
||||
import net.psforever.objects.serverobject.PlanetSideServerObject
|
||||
import net.psforever.objects.serverobject.damage.{Damageable, DamageableEntity}
|
||||
import net.psforever.objects.serverobject.hackable.Hackable
|
||||
import net.psforever.objects.vital.{StandardResolutions, Vitality}
|
||||
import net.psforever.objects.zones.Zone
|
||||
import net.psforever.types.PlanetSideGUID
|
||||
import net.psforever.objects.serverobject.repair.RepairableEntity
|
||||
import net.psforever.objects.vital.StandardResolutions
|
||||
import net.psforever.types.{PlanetSideGUID, Vector3}
|
||||
import services.Service
|
||||
import services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||
import services.local.{LocalAction, LocalServiceMessage}
|
||||
import services.vehicle.{VehicleAction, VehicleServiceMessage}
|
||||
|
||||
|
|
@ -45,19 +45,25 @@ object SensorDeployableDefinition {
|
|||
}
|
||||
|
||||
class SensorDeployableControl(sensor : SensorDeployable) extends Actor
|
||||
with JammableBehavior {
|
||||
|
||||
with JammableBehavior
|
||||
with DamageableEntity
|
||||
with RepairableEntity {
|
||||
def JammableObject = sensor
|
||||
def DamageableObject = sensor
|
||||
def RepairableObject = sensor
|
||||
|
||||
def receive : Receive = jammableBehavior.orElse {
|
||||
case Vitality.Damage(damage_func) =>
|
||||
val originalHealth = sensor.Health
|
||||
if(originalHealth > 0) {
|
||||
val cause = damage_func(sensor)
|
||||
SensorDeployableControl.HandleDamageResolution(sensor, cause, originalHealth - sensor.Health)
|
||||
}
|
||||
def receive : Receive = jammableBehavior
|
||||
.orElse(takesDamage)
|
||||
.orElse(canBeRepairedByNanoDispenser)
|
||||
.orElse {
|
||||
case _ => ;
|
||||
}
|
||||
|
||||
case _ => ;
|
||||
override protected def DamageLog(msg : String) : Unit = { }
|
||||
|
||||
override protected def DestructionAwareness(target : Damageable.Target, cause : ResolvedProjectile) : Unit = {
|
||||
super.DestructionAwareness(target, cause)
|
||||
SensorDeployableControl.DestructionAwareness(sensor, PlanetSideGUID(0))
|
||||
}
|
||||
|
||||
override def StartJammeredSound(target : Any, dur : Int) : Unit = target match {
|
||||
|
|
@ -69,7 +75,8 @@ class SensorDeployableControl(sensor : SensorDeployable) extends Actor
|
|||
|
||||
override def StartJammeredStatus(target : Any, dur : Int) : Unit = target match {
|
||||
case obj : PlanetSideServerObject with JammableUnit if !obj.Jammed =>
|
||||
sensor.Zone.LocalEvents ! LocalServiceMessage(sensor.Zone.Id, LocalAction.TriggerEffectInfo(Service.defaultPlayerGUID, "on", obj.GUID, false, 1000))
|
||||
val zone = obj.Zone
|
||||
zone.LocalEvents ! LocalServiceMessage(zone.Id, LocalAction.TriggerEffectInfo(Service.defaultPlayerGUID, "on", obj.GUID, false, 1000))
|
||||
super.StartJammeredStatus(obj, dur)
|
||||
case _ => ;
|
||||
}
|
||||
|
|
@ -77,7 +84,8 @@ class SensorDeployableControl(sensor : SensorDeployable) extends Actor
|
|||
override def CancelJammeredSound(target : Any) : Unit = {
|
||||
target match {
|
||||
case obj : PlanetSideServerObject if jammedSound =>
|
||||
obj.Zone.VehicleEvents ! VehicleServiceMessage(obj.Zone.Id, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, obj.GUID, 54, 0))
|
||||
val zone = obj.Zone
|
||||
zone.VehicleEvents ! VehicleServiceMessage(zone.Id, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, obj.GUID, 54, 0))
|
||||
case _ => ;
|
||||
}
|
||||
super.CancelJammeredSound(target)
|
||||
|
|
@ -94,40 +102,32 @@ class SensorDeployableControl(sensor : SensorDeployable) extends Actor
|
|||
}
|
||||
|
||||
object SensorDeployableControl {
|
||||
def HandleDamageResolution(target : SensorDeployable, cause : ResolvedProjectile, damage : Int) : Unit = {
|
||||
val zone = target.Zone
|
||||
val targetGUID = target.GUID
|
||||
val playerGUID = zone.LivePlayers.find { p => cause.projectile.owner.Name.equals(p.Name) } match {
|
||||
case Some(player) => player.GUID
|
||||
case _ => PlanetSideGUID(0)
|
||||
}
|
||||
if(target.Health > 0) {
|
||||
//activity on map
|
||||
if(damage > 0) {
|
||||
zone.Activity ! Zone.HotSpot.Activity(cause.target, cause.projectile.owner, cause.hit_pos)
|
||||
}
|
||||
if(cause.projectile.profile.JammerProjectile) {
|
||||
target.Actor ! JammableUnit.Jammered(cause)
|
||||
}
|
||||
}
|
||||
else {
|
||||
HandleDestructionAwareness(target, playerGUID, cause)
|
||||
}
|
||||
zone.AvatarEvents ! AvatarServiceMessage(zone.Id, AvatarAction.PlanetsideAttribute(targetGUID, 0, target.Health))
|
||||
}
|
||||
|
||||
/**
|
||||
* na
|
||||
* @param target na
|
||||
* @param attribution na
|
||||
* @param lastShot na
|
||||
*/
|
||||
def HandleDestructionAwareness(target : SensorDeployable, attribution : PlanetSideGUID, lastShot : ResolvedProjectile) : Unit = {
|
||||
target.Actor ! JammableUnit.ClearJammeredSound()
|
||||
target.Actor ! JammableUnit.ClearJammeredStatus()
|
||||
def DestructionAwareness(target : Damageable.Target with Deployable, attribution : PlanetSideGUID) : Unit = {
|
||||
Deployables.AnnounceDestroyDeployable(target, Some(1 seconds))
|
||||
val zone = target.Zone
|
||||
Deployables.AnnounceDestroyDeployable(target, None)
|
||||
zone.LocalEvents ! LocalServiceMessage(zone.Id, LocalAction.TriggerEffectInfo(Service.defaultPlayerGUID, "on", target.GUID, false, 1000))
|
||||
zone.AvatarEvents ! AvatarServiceMessage(zone.Id, AvatarAction.Destroy(target.GUID, attribution, attribution, target.Position))
|
||||
zone.LocalEvents ! LocalServiceMessage(zone.Id,
|
||||
LocalAction.TriggerEffectInfo(Service.defaultPlayerGUID, "on", target.GUID, false, 1000)
|
||||
)
|
||||
//position the explosion effect near the bulky area of the sensor stalk
|
||||
val ang = target.Orientation
|
||||
val explosionPos = {
|
||||
val pos = target.Position
|
||||
val yRadians = ang.y.toRadians
|
||||
val d = Vector3.Rz(Vector3(0, 0.875f, 0), ang.z) * math.sin(yRadians).toFloat
|
||||
Vector3(
|
||||
pos.x + d.x,
|
||||
pos.y + d.y,
|
||||
pos.z + math.cos(yRadians).toFloat * 0.875f
|
||||
)
|
||||
}
|
||||
zone.LocalEvents ! LocalServiceMessage(zone.Id,
|
||||
LocalAction.TriggerEffectLocation(Service.defaultPlayerGUID, "motion_sensor_destroyed", explosionPos, ang)
|
||||
)
|
||||
//TODO replaced by an alternate model (charred stub)?
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,12 +7,13 @@ import net.psforever.objects.ce.{ComplexDeployable, Deployable, DeployableCatego
|
|||
import net.psforever.objects.definition.{ComplexDeployableDefinition, SimpleDeployableDefinition}
|
||||
import net.psforever.objects.definition.converter.ShieldGeneratorConverter
|
||||
import net.psforever.objects.equipment.{JammableBehavior, JammableUnit}
|
||||
import net.psforever.objects.serverobject.damage.Damageable.Target
|
||||
import net.psforever.objects.serverobject.damage.{Damageable, DamageableEntity}
|
||||
import net.psforever.objects.serverobject.PlanetSideServerObject
|
||||
import net.psforever.objects.serverobject.hackable.Hackable
|
||||
import net.psforever.objects.vital.Vitality
|
||||
import net.psforever.objects.zones.Zone
|
||||
import net.psforever.objects.serverobject.repair.RepairableEntity
|
||||
import net.psforever.objects.vital.resolution.ResolutionCalculations
|
||||
import net.psforever.types.PlanetSideGUID
|
||||
import services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||
import services.Service
|
||||
import services.vehicle.{VehicleAction, VehicleServiceMessage}
|
||||
|
||||
|
|
@ -34,32 +35,75 @@ class ShieldGeneratorDefinition extends ComplexDeployableDefinition(240) {
|
|||
}
|
||||
|
||||
class ShieldGeneratorControl(gen : ShieldGeneratorDeployable) extends Actor
|
||||
with JammableBehavior {
|
||||
|
||||
with JammableBehavior
|
||||
with DamageableEntity
|
||||
with RepairableEntity {
|
||||
def JammableObject = gen
|
||||
def DamageableObject = gen
|
||||
def RepairableObject = gen
|
||||
private var handleDamageToShields : Boolean = false
|
||||
|
||||
def receive : Receive = jammableBehavior
|
||||
.orElse(takesDamage)
|
||||
.orElse(canBeRepairedByNanoDispenser)
|
||||
.orElse {
|
||||
case Vitality.Damage(damage_func) => //note: damage status is reported as vehicle events, not local events
|
||||
if(gen.Health > 0) {
|
||||
val originalHealth = gen.Health
|
||||
val cause = damage_func(gen)
|
||||
val health = gen.Health
|
||||
val damageToHealth = originalHealth - health
|
||||
ShieldGeneratorControl.HandleDamageResolution(gen, cause, damageToHealth)
|
||||
if(damageToHealth > 0) {
|
||||
val name = gen.Actor.toString
|
||||
val slashPoint = name.lastIndexOf("/")
|
||||
org.log4s.getLogger("DamageResolution").info(s"${name.substring(slashPoint + 1, name.length - 1)}: BEFORE=$originalHealth, AFTER=$health, CHANGE=$damageToHealth")
|
||||
}
|
||||
}
|
||||
|
||||
case _ => ;
|
||||
}
|
||||
|
||||
/**
|
||||
* The shield generator has two upgrade paths - blocking projectiles, and providing ammunition like a terminal.
|
||||
* Both upgrade paths are possible using the nano dispenser with an armor canister,
|
||||
* and can only be started when the generator is undamaged.
|
||||
* @see `PlanetSideGameObject.CanRepair`
|
||||
* @see `RepairableEntity.CanPerformRepairs`
|
||||
* @param player the user of the nano dispenser tool
|
||||
* @param item the nano dispenser tool
|
||||
*/
|
||||
override def CanBeRepairedByNanoDispenser(player : Player, item : Tool) : Unit = {
|
||||
if(gen.CanRepair) {
|
||||
super.CanBeRepairedByNanoDispenser(player, item)
|
||||
}
|
||||
else if(!gen.Destroyed) {
|
||||
//TODO reinforced shield upgrade not implemented yet
|
||||
//TODO ammunition supply upgrade not implemented yet
|
||||
}
|
||||
}
|
||||
|
||||
override protected def PerformDamage(target : Damageable.Target, applyDamageTo : ResolutionCalculations.Output) : Unit = {
|
||||
val originalHealth = gen.Health
|
||||
val originalShields = gen.Shields
|
||||
val cause = applyDamageTo(target)
|
||||
val health = gen.Health
|
||||
val shields = gen.Shields
|
||||
val damageToHealth = originalHealth - health
|
||||
val damageToShields = originalShields - shields
|
||||
val damage = damageToHealth + damageToShields
|
||||
if(WillAffectTarget(target, damage, cause)) {
|
||||
target.History(cause)
|
||||
DamageLog(target,s"BEFORE=$originalHealth/$originalShields, AFTER=$health/$shields, CHANGE=$damageToHealth/$damageToShields")
|
||||
handleDamageToShields = damageToShields > 0
|
||||
HandleDamage(target, cause, damageToHealth)
|
||||
}
|
||||
else {
|
||||
gen.Health = originalHealth
|
||||
gen.Shields = originalShields
|
||||
}
|
||||
}
|
||||
|
||||
override protected def DamageAwareness(target : Damageable.Target, cause : ResolvedProjectile, amount : Int) : Unit = {
|
||||
super.DamageAwareness(target, cause, amount)
|
||||
ShieldGeneratorControl.DamageAwareness(gen, cause, handleDamageToShields)
|
||||
handleDamageToShields = false
|
||||
}
|
||||
|
||||
override protected def DestructionAwareness(target : Target, cause : ResolvedProjectile) : Unit = {
|
||||
super.DestructionAwareness(target, cause)
|
||||
ShieldGeneratorControl.DestructionAwareness(gen, PlanetSideGUID(0))
|
||||
}
|
||||
|
||||
/*
|
||||
while the shield generator is technically a supported jammable target, how that works is currently unknown
|
||||
electing to use a "status only, no sound" approach by overriding one with an empty function is not entirely arbitrary
|
||||
the superclass of "status" calls also sets the jammed object property
|
||||
check the object definition for proper feature activation
|
||||
*/
|
||||
override def StartJammeredSound(target : Any, dur : Int) : Unit = { }
|
||||
|
||||
|
|
@ -86,40 +130,23 @@ object ShieldGeneratorControl {
|
|||
/**
|
||||
* na
|
||||
* @param target na
|
||||
* @param cause na
|
||||
* @param damageToShields na
|
||||
*/
|
||||
def HandleDamageResolution(target : ShieldGeneratorDeployable, cause : ResolvedProjectile, damage : Int) : Unit = {
|
||||
val zone = target.Zone
|
||||
val targetGUID = target.GUID
|
||||
val playerGUID = zone.LivePlayers.find { p => cause.projectile.owner.Name.equals(p.Name) } match {
|
||||
case Some(player) => player.GUID
|
||||
case _ => PlanetSideGUID(0)
|
||||
def DamageAwareness(target : ShieldGeneratorDeployable, cause : ResolvedProjectile, damageToShields : Boolean) : Unit = {
|
||||
//shields
|
||||
if(damageToShields) {
|
||||
val zone = target.Zone
|
||||
zone.VehicleEvents ! VehicleServiceMessage(zone.Id, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, target.GUID, 68, target.Shields))
|
||||
}
|
||||
if(target.Health > 0) {
|
||||
//activity on map
|
||||
if(damage > 0) {
|
||||
zone.Activity ! Zone.HotSpot.Activity(cause.target, cause.projectile.owner, cause.hit_pos)
|
||||
}
|
||||
if(cause.projectile.profile.JammerProjectile) {
|
||||
target.Actor ! JammableUnit.Jammered(cause)
|
||||
}
|
||||
}
|
||||
else {
|
||||
HandleDestructionAwareness(target, playerGUID, cause)
|
||||
}
|
||||
zone.AvatarEvents ! AvatarServiceMessage(zone.Id, AvatarAction.PlanetsideAttribute(targetGUID, 0, target.Health))
|
||||
}
|
||||
|
||||
/**
|
||||
* na
|
||||
* @param target na
|
||||
* @param attribution na
|
||||
* @param lastShot na
|
||||
*/
|
||||
def HandleDestructionAwareness(target : ShieldGeneratorDeployable, attribution : PlanetSideGUID, lastShot : ResolvedProjectile) : Unit = {
|
||||
target.Actor ! JammableUnit.ClearJammeredSound()
|
||||
target.Actor ! JammableUnit.ClearJammeredStatus()
|
||||
val zone = target.Zone
|
||||
def DestructionAwareness(target : Damageable.Target with Deployable, attribution : PlanetSideGUID) : Unit = {
|
||||
Deployables.AnnounceDestroyDeployable(target, None)
|
||||
zone.AvatarEvents ! AvatarServiceMessage(zone.Id, AvatarAction.Destroy(target.GUID, attribution, attribution, target.Position))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,6 +45,8 @@ trait SpawnPoint {
|
|||
*/
|
||||
def Definition : ObjectDefinition with SpawnPointDefinition
|
||||
|
||||
def Offline : Boolean = psso.Destroyed
|
||||
|
||||
/**
|
||||
* Determine a specific position and orientation in which to spawn the target.
|
||||
* @return a `Tuple` of `Vector3` objects;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,51 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
// Copyright (c) 2020 PSForever
|
||||
package net.psforever.objects
|
||||
|
||||
import net.psforever.objects.ce.SimpleDeployable
|
||||
import net.psforever.objects.definition.SimpleDeployableDefinition
|
||||
import akka.actor.{Actor, ActorContext, Props}
|
||||
import net.psforever.objects.ballistics.ResolvedProjectile
|
||||
import net.psforever.objects.ce.{ComplexDeployable, Deployable, DeployedItem}
|
||||
import net.psforever.objects.definition.converter.TRAPConverter
|
||||
import net.psforever.objects.definition.{ComplexDeployableDefinition, SimpleDeployableDefinition}
|
||||
import net.psforever.objects.serverobject.PlanetSideServerObject
|
||||
import net.psforever.objects.serverobject.damage.{Damageable, DamageableEntity}
|
||||
import net.psforever.objects.serverobject.repair.RepairableEntity
|
||||
import net.psforever.objects.vital.StandardResolutions
|
||||
|
||||
class TrapDeployable(cdef : SimpleDeployableDefinition) extends SimpleDeployable(cdef)
|
||||
class TrapDeployable(cdef : TrapDeployableDefinition) extends ComplexDeployable(cdef)
|
||||
|
||||
class TrapDeployableDefinition(objectId : Int) extends ComplexDeployableDefinition(objectId) {
|
||||
Model = StandardResolutions.SimpleDeployables
|
||||
Packet = new TRAPConverter
|
||||
|
||||
override def Initialize(obj : PlanetSideServerObject with Deployable, context : ActorContext) = {
|
||||
obj.Actor = context.actorOf(Props(classOf[TrapDeployableControl], obj), PlanetSideServerObject.UniqueActorName(obj))
|
||||
}
|
||||
|
||||
override def Uninitialize(obj : PlanetSideServerObject with Deployable, context : ActorContext) = {
|
||||
SimpleDeployableDefinition.SimpleUninitialize(obj, context)
|
||||
}
|
||||
}
|
||||
|
||||
object TrapDeployableDefinition {
|
||||
def apply(dtype : DeployedItem.Value) : TrapDeployableDefinition = {
|
||||
new TrapDeployableDefinition(dtype.id)
|
||||
}
|
||||
}
|
||||
|
||||
class TrapDeployableControl(trap : TrapDeployable) extends Actor
|
||||
with DamageableEntity
|
||||
with RepairableEntity {
|
||||
def DamageableObject = trap
|
||||
def RepairableObject = trap
|
||||
|
||||
def receive : Receive = takesDamage
|
||||
.orElse(canBeRepairedByNanoDispenser)
|
||||
.orElse {
|
||||
case _ =>
|
||||
}
|
||||
|
||||
override protected def DestructionAwareness(target : Damageable.Target, cause : ResolvedProjectile) : Unit = {
|
||||
super.DestructionAwareness(target, cause)
|
||||
Deployables.AnnounceDestroyDeployable(trap, None)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,30 +8,23 @@ import net.psforever.objects.definition.{ComplexDeployableDefinition, SimpleDepl
|
|||
import net.psforever.objects.definition.converter.SmallTurretConverter
|
||||
import net.psforever.objects.equipment.{JammableMountedWeapons, JammableUnit}
|
||||
import net.psforever.objects.serverobject.PlanetSideServerObject
|
||||
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
|
||||
import net.psforever.objects.serverobject.affinity.FactionAffinityBehavior
|
||||
import net.psforever.objects.serverobject.damage.Damageable.Target
|
||||
import net.psforever.objects.serverobject.damage.DamageableWeaponTurret
|
||||
import net.psforever.objects.serverobject.hackable.Hackable
|
||||
import net.psforever.objects.serverobject.mount.MountableBehavior
|
||||
import net.psforever.objects.serverobject.repair.RepairableWeaponTurret
|
||||
import net.psforever.objects.serverobject.turret.{TurretDefinition, WeaponTurret}
|
||||
import net.psforever.objects.vital.{StandardResolutions, StandardVehicleDamage, StandardVehicleResistance, Vitality}
|
||||
import net.psforever.objects.zones.Zone
|
||||
import net.psforever.types.PlanetSideGUID
|
||||
import services.Service
|
||||
import services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||
import services.vehicle.{VehicleAction, VehicleServiceMessage}
|
||||
import net.psforever.objects.vital.{StandardResolutions, StandardVehicleDamage, StandardVehicleResistance}
|
||||
|
||||
class TurretDeployable(tdef : TurretDeployableDefinition) extends ComplexDeployable(tdef)
|
||||
with WeaponTurret
|
||||
with JammableUnit
|
||||
with Hackable {
|
||||
WeaponTurret.LoadDefinition(this) //calls the equivalent of Health = Definition.MaxHealth
|
||||
WeaponTurret.LoadDefinition(this)
|
||||
|
||||
def MountPoints : Map[Int, Int] = Definition.MountPoints.toMap
|
||||
|
||||
//override to clarify inheritance conflict
|
||||
override def Health : Int = super[ComplexDeployable].Health
|
||||
//override to clarify inheritance conflict
|
||||
override def Health_=(toHealth : Int) : Int = super[ComplexDeployable].Health_=(toHealth)
|
||||
|
||||
override def Definition = tdef
|
||||
}
|
||||
|
||||
|
|
@ -39,8 +32,8 @@ class TurretDeployableDefinition(private val objectId : Int) extends ComplexDepl
|
|||
with TurretDefinition {
|
||||
Name = "turret_deployable"
|
||||
Packet = new SmallTurretConverter
|
||||
Damage = StandardVehicleDamage
|
||||
Resistance = StandardVehicleResistance
|
||||
DamageUsing = StandardVehicleDamage
|
||||
ResistUsing = StandardVehicleResistance
|
||||
Model = StandardResolutions.FacilityTurrets
|
||||
|
||||
//override to clarify inheritance conflict
|
||||
|
|
@ -68,113 +61,28 @@ object TurretDeployableDefinition {
|
|||
class TurretControl(turret : TurretDeployable) extends Actor
|
||||
with FactionAffinityBehavior.Check
|
||||
with JammableMountedWeapons //note: jammable status is reported as vehicle events, not local events
|
||||
with MountableBehavior.Mount
|
||||
with MountableBehavior.Dismount {
|
||||
def MountableObject = turret //do not add type!
|
||||
|
||||
with MountableBehavior.TurretMount
|
||||
with MountableBehavior.Dismount
|
||||
with DamageableWeaponTurret
|
||||
with RepairableWeaponTurret {
|
||||
def MountableObject = turret
|
||||
def JammableObject = turret
|
||||
|
||||
def FactionObject : FactionAffinity = turret
|
||||
def FactionObject = turret
|
||||
def DamageableObject = turret
|
||||
def RepairableObject = turret
|
||||
|
||||
def receive : Receive = checkBehavior
|
||||
.orElse(jammableBehavior)
|
||||
.orElse(mountBehavior)
|
||||
.orElse(dismountBehavior)
|
||||
.orElse(turretMountBehavior)
|
||||
.orElse(takesDamage)
|
||||
.orElse(canBeRepairedByNanoDispenser)
|
||||
.orElse {
|
||||
case Vitality.Damage(damage_func) => //note: damage status is reported as vehicle events, not local events
|
||||
if(turret.Health > 0) {
|
||||
val originalHealth = turret.Health
|
||||
val cause = damage_func(turret)
|
||||
val health = turret.Health
|
||||
val damageToHealth = originalHealth - health
|
||||
TurretControl.HandleDamageResolution(turret, cause, damageToHealth)
|
||||
if(damageToHealth > 0) {
|
||||
val name = turret.Actor.toString
|
||||
val slashPoint = name.lastIndexOf("/")
|
||||
org.log4s.getLogger("DamageResolution").info(s"${name.substring(slashPoint + 1, name.length - 1)}: BEFORE=$originalHealth, AFTER=$health, CHANGE=$damageToHealth")
|
||||
}
|
||||
}
|
||||
|
||||
case _ => ;
|
||||
}
|
||||
}
|
||||
|
||||
object TurretControl {
|
||||
/**
|
||||
* na
|
||||
* @param target na
|
||||
*/
|
||||
def HandleDamageResolution(target : TurretDeployable, cause : ResolvedProjectile, damage : Int) : Unit = {
|
||||
val zone = target.Zone
|
||||
val targetGUID = target.GUID
|
||||
val playerGUID = zone.LivePlayers.find { p => cause.projectile.owner.Name.equals(p.Name) } match {
|
||||
case Some(player) => player.GUID
|
||||
case _ => PlanetSideGUID(0)
|
||||
}
|
||||
if(target.Health > 0) {
|
||||
//activity on map
|
||||
if(damage > 0) {
|
||||
zone.Activity ! Zone.HotSpot.Activity(cause.target, cause.projectile.owner, cause.hit_pos)
|
||||
//alert occupants to damage source
|
||||
HandleDamageAwareness(target, playerGUID, cause)
|
||||
}
|
||||
if(cause.projectile.profile.JammerProjectile) {
|
||||
target.Actor ! JammableUnit.Jammered(cause)
|
||||
}
|
||||
}
|
||||
else {
|
||||
//alert to turret death (hence, occupants' deaths)
|
||||
HandleDestructionAwareness(target, playerGUID, cause)
|
||||
}
|
||||
zone.VehicleEvents ! VehicleServiceMessage(zone.Id, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, targetGUID, 0, target.Health))
|
||||
}
|
||||
|
||||
/**
|
||||
* na
|
||||
* @param target na
|
||||
* @param attribution na
|
||||
* @param lastShot na
|
||||
*/
|
||||
def HandleDamageAwareness(target : TurretDeployable, attribution : PlanetSideGUID, lastShot : ResolvedProjectile) : Unit = {
|
||||
val zone = target.Zone
|
||||
//alert occupants to damage source
|
||||
target.Seats.values.filter(seat => {
|
||||
seat.isOccupied && seat.Occupant.get.isAlive
|
||||
}).foreach(seat => {
|
||||
val tplayer = seat.Occupant.get
|
||||
zone.AvatarEvents ! AvatarServiceMessage(tplayer.Name, AvatarAction.HitHint(attribution, tplayer.GUID))
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* na
|
||||
* @param target na
|
||||
* @param attribution na
|
||||
* @param lastShot na
|
||||
*/
|
||||
def HandleDestructionAwareness(target : TurretDeployable, attribution : PlanetSideGUID, lastShot : ResolvedProjectile) : Unit = {
|
||||
target.Actor ! JammableUnit.ClearJammeredSound()
|
||||
target.Actor ! JammableUnit.ClearJammeredStatus()
|
||||
val zone = target.Zone
|
||||
val continentId = zone.Id
|
||||
//alert to vehicle death (hence, occupants' deaths)
|
||||
target.Seats.values.filter(seat => {
|
||||
seat.isOccupied && seat.Occupant.get.isAlive
|
||||
}).foreach(seat => {
|
||||
val tplayer = seat.Occupant.get
|
||||
tplayer.History(lastShot)
|
||||
tplayer.Actor ! Player.Die()
|
||||
})
|
||||
//vehicle wreckage has no weapons
|
||||
target.Weapons.values
|
||||
.filter {
|
||||
_.Equipment.nonEmpty
|
||||
}
|
||||
.foreach(slot => {
|
||||
val wep = slot.Equipment.get
|
||||
zone.AvatarEvents ! AvatarServiceMessage(continentId, AvatarAction.ObjectDelete(Service.defaultPlayerGUID, wep.GUID))
|
||||
})
|
||||
Deployables.AnnounceDestroyDeployable(target, None)
|
||||
zone.AvatarEvents ! AvatarServiceMessage(continentId, AvatarAction.Destroy(target.GUID, attribution, attribution, target.Position))
|
||||
override protected def DestructionAwareness(target : Target, cause : ResolvedProjectile) : Unit = {
|
||||
super.DestructionAwareness(target, cause)
|
||||
Deployables.AnnounceDestroyDeployable(turret, None)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import net.psforever.objects.serverobject.mount.Mountable
|
|||
import net.psforever.objects.serverobject.PlanetSideServerObject
|
||||
import net.psforever.objects.serverobject.affinity.FactionAffinity
|
||||
import net.psforever.objects.serverobject.deploy.Deployment
|
||||
import net.psforever.objects.serverobject.hackable.Hackable
|
||||
import net.psforever.objects.serverobject.structures.AmenityOwner
|
||||
import net.psforever.objects.vehicles._
|
||||
import net.psforever.objects.vital.{DamageResistanceModel, StandardResistanceProfile, Vitality}
|
||||
|
|
@ -65,6 +66,7 @@ import scala.annotation.tailrec
|
|||
* used in the initialization process (`loadVehicleDefinition`)
|
||||
*/
|
||||
class Vehicle(private val vehicleDef : VehicleDefinition) extends AmenityOwner
|
||||
with Hackable
|
||||
with FactionAffinity
|
||||
with Mountable
|
||||
with MountedWeapons
|
||||
|
|
@ -74,10 +76,8 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends AmenityOwner
|
|||
with StandardResistanceProfile
|
||||
with JammableUnit
|
||||
with Container {
|
||||
private var faction : PlanetSideEmpire.Value = PlanetSideEmpire.TR
|
||||
private var health : Int = 1
|
||||
private var faction : PlanetSideEmpire.Value = PlanetSideEmpire.NEUTRAL
|
||||
private var shields : Int = 0
|
||||
private var isDead : Boolean = false
|
||||
private var decal : Int = 0
|
||||
private var trunkAccess : Option[PlanetSideGUID] = None
|
||||
private var jammered : Boolean = false
|
||||
|
|
@ -144,28 +144,12 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends AmenityOwner
|
|||
MountedIn
|
||||
}
|
||||
|
||||
def IsDead : Boolean = {
|
||||
isDead
|
||||
}
|
||||
|
||||
def Health : Int = {
|
||||
health
|
||||
}
|
||||
|
||||
def Health_=(assignHealth : Int) : Int = {
|
||||
if(!isDead) {
|
||||
health = math.min(math.max(0, assignHealth), MaxHealth)
|
||||
override def Health_=(assignHealth : Int) : Int = {
|
||||
//TODO should vehicle class enforce this?
|
||||
if(!Destroyed) {
|
||||
super.Health_=(assignHealth)
|
||||
}
|
||||
|
||||
if(health == 0) {
|
||||
isDead = true
|
||||
}
|
||||
|
||||
health
|
||||
}
|
||||
|
||||
def MaxHealth : Int = {
|
||||
Definition.MaxHealth
|
||||
Health
|
||||
}
|
||||
|
||||
def Shields : Int = {
|
||||
|
|
@ -644,7 +628,7 @@ object Vehicle {
|
|||
def LoadDefinition(vehicle : Vehicle) : Vehicle = {
|
||||
val vdef : VehicleDefinition = vehicle.Definition
|
||||
//general stuff
|
||||
vehicle.Health = vdef.MaxHealth
|
||||
vehicle.Health = vdef.DefaultHealth
|
||||
//create weapons
|
||||
vehicle.weapons = vdef.Weapons.map({case (num, definition) =>
|
||||
val slot = EquipmentSlot(EquipmentSize.VehicleWeapon)
|
||||
|
|
|
|||
|
|
@ -1,20 +1,26 @@
|
|||
// Copyright (c) 2020 PSForever
|
||||
package net.psforever.objects
|
||||
|
||||
import net.psforever.objects.vehicles.VehicleLockState
|
||||
import net.psforever.objects.serverobject.CommonMessages
|
||||
import net.psforever.objects.vehicles.{CargoBehavior, VehicleLockState}
|
||||
import net.psforever.objects.zones.Zone
|
||||
import net.psforever.types.PlanetSideGUID
|
||||
import net.psforever.packet.game.TriggeredSound
|
||||
import net.psforever.types.{DriveState, PlanetSideGUID}
|
||||
import services.RemoverActor
|
||||
import services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||
import services.local.{LocalAction, LocalServiceMessage}
|
||||
import services.vehicle.{VehicleAction, VehicleServiceMessage}
|
||||
|
||||
object Vehicles {
|
||||
private val log = org.log4s.getLogger("Vehicles")
|
||||
|
||||
/**
|
||||
* na
|
||||
* @param vehicle na
|
||||
* @param tplayer na
|
||||
* @param player na
|
||||
* @return na
|
||||
*/
|
||||
def Own(vehicle : Vehicle, tplayer : Player) : Option[Vehicle] = Own(vehicle, Some(tplayer))
|
||||
def Own(vehicle : Vehicle, player : Player) : Option[Vehicle] = Own(vehicle, Some(player))
|
||||
|
||||
/**
|
||||
* na
|
||||
|
|
@ -151,4 +157,106 @@ object Vehicles {
|
|||
false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The orientation of a cargo vehicle as it is being loaded into and contained by a carrier vehicle.
|
||||
* The type of carrier is not an important consideration in determining the orientation, oddly enough.
|
||||
* @param vehicle the cargo vehicle
|
||||
* @return the orientation as an `Integer` value;
|
||||
* `0` for almost all cases
|
||||
*/
|
||||
def CargoOrientation(vehicle : Vehicle) : Int = {
|
||||
if(vehicle.Definition == GlobalDefinitions.router) {
|
||||
1
|
||||
}
|
||||
else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The process of hacking/jacking a vehicle is complete.
|
||||
* Change the faction of the vehicle to the hacker's faction and remove all occupants.
|
||||
* @param target The `Vehicle` object that has been hacked/jacked
|
||||
* @param hacker the one whoi performed the hack and will inherit ownership of the target vehicle
|
||||
* @param unk na; used by `HackMessage` as `unk5`
|
||||
*/
|
||||
def FinishHackingVehicle(target : Vehicle, hacker : Player, unk : Long)(): Unit = {
|
||||
log.info(s"Vehicle guid: ${target.GUID} has been jacked")
|
||||
import scala.concurrent.duration._
|
||||
val zone = target.Zone
|
||||
// Forcefully dismount any cargo
|
||||
target.CargoHolds.values.foreach(cargoHold => {
|
||||
cargoHold.Occupant match {
|
||||
case Some(cargo : Vehicle) => {
|
||||
cargo.Seats(0).Occupant match {
|
||||
case Some(cargoDriver: Player) =>
|
||||
CargoBehavior.HandleVehicleCargoDismount(target.Zone, cargoDriver.GUID, cargo.GUID, bailed = target.Flying, requestedByPassenger = false, kicked = true )
|
||||
case None =>
|
||||
log.error("FinishHackingVehicle: vehicle in cargo hold missing driver")
|
||||
CargoBehavior.HandleVehicleCargoDismount(hacker.GUID, cargo.GUID, cargo, target.GUID, target, false, false, true)
|
||||
}
|
||||
}
|
||||
case None => ;
|
||||
}
|
||||
})
|
||||
// Forcefully dismount all seated occupants from the vehicle
|
||||
target.Seats.values.foreach(seat => {
|
||||
seat.Occupant match {
|
||||
case Some(tplayer) =>
|
||||
seat.Occupant = None
|
||||
tplayer.VehicleSeated = None
|
||||
if(tplayer.HasGUID) {
|
||||
zone.VehicleEvents ! VehicleServiceMessage(zone.Id, VehicleAction.KickPassenger(tplayer.GUID, 4, unk2 = false, target.GUID))
|
||||
}
|
||||
case None => ;
|
||||
}
|
||||
})
|
||||
// If the vehicle can fly and is flying deconstruct it, and well played to whomever managed to hack a plane in mid air. I'm impressed.
|
||||
if(target.Definition.CanFly && target.Flying) {
|
||||
// todo: Should this force the vehicle to land in the same way as when a pilot bails with passengers on board?
|
||||
zone.VehicleEvents ! VehicleServiceMessage.Decon(RemoverActor.ClearSpecific(List(target), zone))
|
||||
zone.VehicleEvents ! VehicleServiceMessage.Decon(RemoverActor.AddTask(target, zone, Some(0 seconds)))
|
||||
} else { // Otherwise handle ownership transfer as normal
|
||||
// Remove ownership of our current vehicle, if we have one
|
||||
hacker.VehicleOwned match {
|
||||
case Some(guid : PlanetSideGUID) =>
|
||||
zone.GUID(guid) match {
|
||||
case Some(vehicle: Vehicle) =>
|
||||
Vehicles.Disown(hacker, vehicle)
|
||||
case _ => ;
|
||||
}
|
||||
case _ => ;
|
||||
}
|
||||
target.Owner match {
|
||||
case Some(previousOwnerGuid: PlanetSideGUID) =>
|
||||
// Remove ownership of the vehicle from the previous player
|
||||
zone.GUID(previousOwnerGuid) match {
|
||||
case Some(tplayer: Player) =>
|
||||
Vehicles.Disown(tplayer, target)
|
||||
case _ => ; // Vehicle already has no owner
|
||||
}
|
||||
case _ => ;
|
||||
}
|
||||
// Now take ownership of the jacked vehicle
|
||||
target.Actor ! CommonMessages.Hack(hacker, target)
|
||||
target.Faction = hacker.Faction
|
||||
Vehicles.Own(target, hacker)
|
||||
//todo: Send HackMessage -> HackCleared to vehicle? can be found in packet captures. Not sure if necessary.
|
||||
// And broadcast the faction change to other clients
|
||||
zone.AvatarEvents ! AvatarServiceMessage(hacker.Name, AvatarAction.SetEmpire(hacker.GUID, target.GUID, hacker.Faction))
|
||||
zone.AvatarEvents ! AvatarServiceMessage(zone.Id, AvatarAction.SetEmpire(hacker.GUID, target.GUID, hacker.Faction))
|
||||
}
|
||||
zone.LocalEvents ! LocalServiceMessage(zone.Id, LocalAction.TriggerSound(hacker.GUID, TriggeredSound.HackVehicle, target.Position, 30, 0.49803925f))
|
||||
// Clean up after specific vehicles, e.g. remove router telepads
|
||||
// If AMS is deployed, swap it to the new faction
|
||||
target.Definition match {
|
||||
case GlobalDefinitions.router =>
|
||||
log.info("FinishHackingVehicle: cleaning up after a router ...")
|
||||
Deployables.RemoveTelepad(target)
|
||||
case GlobalDefinitions.ams if target.DeploymentState == DriveState.Deployed =>
|
||||
zone.VehicleEvents ! VehicleServiceMessage.AMSDeploymentChange(zone)
|
||||
case _ => ;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,13 +2,17 @@
|
|||
package net.psforever.objects.avatar
|
||||
|
||||
import akka.actor.Actor
|
||||
import net.psforever.objects.Player
|
||||
import net.psforever.objects.ballistics.{PlayerSource, ResolvedProjectile, SourceEntry}
|
||||
import net.psforever.objects.equipment.{JammableBehavior, JammableUnit}
|
||||
import net.psforever.objects.vital.{PlayerSuicide, Vitality}
|
||||
import net.psforever.objects.{GlobalDefinitions, Player, Tool}
|
||||
import net.psforever.objects.ballistics.{PlayerSource, ResolvedProjectile}
|
||||
import net.psforever.objects.equipment.{Ammo, JammableBehavior, JammableUnit}
|
||||
import net.psforever.objects.serverobject.CommonMessages
|
||||
import net.psforever.objects.serverobject.damage.Damageable
|
||||
import net.psforever.objects.serverobject.mount.Mountable
|
||||
import net.psforever.objects.serverobject.repair.Repairable
|
||||
import net.psforever.objects.vital._
|
||||
import net.psforever.objects.zones.Zone
|
||||
import net.psforever.packet.game._
|
||||
import net.psforever.types.{ExoSuitType, PlanetSideGUID}
|
||||
import net.psforever.types.{ExoSuitType, Vector3}
|
||||
import services.Service
|
||||
import services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||
|
||||
|
|
@ -19,34 +23,108 @@ import scala.concurrent.duration._
|
|||
* stub for future development
|
||||
*/
|
||||
class PlayerControl(player : Player) extends Actor
|
||||
with JammableBehavior {
|
||||
with JammableBehavior
|
||||
with Damageable {
|
||||
def JammableObject = player
|
||||
def DamageableObject = player
|
||||
|
||||
private [this] val log = org.log4s.getLogger(player.Name)
|
||||
private [this] val damageLog = org.log4s.getLogger("DamageResolution")
|
||||
private [this] val damageLog = org.log4s.getLogger(Damageable.LogChannel)
|
||||
|
||||
def receive : Receive = jammableBehavior.orElse {
|
||||
case Player.Die() =>
|
||||
PlayerControl.HandleDestructionAwareness(player, player.GUID, None)
|
||||
def receive : Receive = jammableBehavior
|
||||
.orElse(takesDamage)
|
||||
.orElse {
|
||||
case Player.Die() =>
|
||||
if(player.isAlive) {
|
||||
PlayerControl.DestructionAwareness(player, None)
|
||||
}
|
||||
|
||||
case Vitality.Damage(resolution_function) =>
|
||||
case CommonMessages.Use(user, Some(item : Tool)) if item.Definition == GlobalDefinitions.medicalapplicator && player.isAlive =>
|
||||
//heal
|
||||
val originalHealth = player.Health
|
||||
val definition = player.Definition
|
||||
if(player.MaxHealth > 0 && originalHealth < player.MaxHealth &&
|
||||
user.Faction == player.Faction &&
|
||||
item.Magazine > 0 &&
|
||||
Vector3.Distance(user.Position, player.Position) < definition.RepairDistance) {
|
||||
val zone = player.Zone
|
||||
val events = zone.AvatarEvents
|
||||
val uname = user.Name
|
||||
val guid = player.GUID
|
||||
if(!(player.isMoving || user.isMoving)) { //only allow stationary heals
|
||||
val newHealth = player.Health = originalHealth + 10
|
||||
val magazine = item.Discharge
|
||||
events ! AvatarServiceMessage(uname, AvatarAction.SendResponse(Service.defaultPlayerGUID, InventoryStateMessage(item.AmmoSlot.Box.GUID, item.GUID, magazine.toLong)))
|
||||
events ! AvatarServiceMessage(zone.Id, AvatarAction.PlanetsideAttributeToAll(guid, 0, newHealth))
|
||||
player.History(HealFromEquipment(PlayerSource(player), PlayerSource(user), newHealth - originalHealth, GlobalDefinitions.medicalapplicator))
|
||||
}
|
||||
if(player != user) {
|
||||
//"Someone is trying to heal you"
|
||||
events ! AvatarServiceMessage(player.Name, AvatarAction.PlanetsideAttributeToAll(guid, 55, 1))
|
||||
//progress bar remains visible for all heal attempts
|
||||
events ! AvatarServiceMessage(uname, AvatarAction.SendResponse(Service.defaultPlayerGUID, RepairMessage(guid, player.Health * 100 / definition.MaxHealth)))
|
||||
}
|
||||
}
|
||||
|
||||
case CommonMessages.Use(user, Some(item : Tool)) if item.Definition == GlobalDefinitions.medicalapplicator && !player.isAlive =>
|
||||
//revive
|
||||
if(user != player && user.isAlive && !user.isMoving &&
|
||||
!player.isBackpack &&
|
||||
item.Magazine >= 25) {
|
||||
sender ! CommonMessages.Use(player, Some((item, user)))
|
||||
}
|
||||
|
||||
case CommonMessages.Use(user, Some(item : Tool)) if item.Definition == GlobalDefinitions.bank =>
|
||||
val originalArmor = player.Armor
|
||||
val definition = player.Definition
|
||||
if(player.MaxArmor > 0 && originalArmor < player.MaxArmor &&
|
||||
user.Faction == player.Faction &&
|
||||
item.AmmoType == Ammo.armor_canister && item.Magazine > 0 &&
|
||||
Vector3.Distance(user.Position, player.Position) < definition.RepairDistance) {
|
||||
val zone = player.Zone
|
||||
val events = zone.AvatarEvents
|
||||
val uname = user.Name
|
||||
val guid = player.GUID
|
||||
if(!(player.isMoving || user.isMoving)) { //only allow stationary repairs
|
||||
val newArmor = player.Armor = originalArmor + Repairable.Quality + RepairValue(item) + definition.RepairMod
|
||||
val magazine = item.Discharge
|
||||
events ! AvatarServiceMessage(uname, AvatarAction.SendResponse(Service.defaultPlayerGUID, InventoryStateMessage(item.AmmoSlot.Box.GUID, item.GUID, magazine.toLong)))
|
||||
events ! AvatarServiceMessage(zone.Id, AvatarAction.PlanetsideAttributeToAll(guid, 4, player.Armor))
|
||||
player.History(RepairFromEquipment(PlayerSource(player), PlayerSource(user), newArmor - originalArmor, GlobalDefinitions.bank))
|
||||
}
|
||||
if(player != user) {
|
||||
if(player.isAlive) {
|
||||
//"Someone is trying to repair you" gets strobed twice for visibility
|
||||
val msg = AvatarServiceMessage(player.Name, AvatarAction.PlanetsideAttributeToAll(guid, 56, 1))
|
||||
events ! msg
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
context.system.scheduler.scheduleOnce(250 milliseconds, events, msg)
|
||||
}
|
||||
//progress bar remains visible for all repair attempts
|
||||
events ! AvatarServiceMessage(uname, AvatarAction.SendResponse(Service.defaultPlayerGUID, RepairMessage(guid, player.Armor * 100 / player.MaxArmor)))
|
||||
}
|
||||
}
|
||||
|
||||
case _ => ;
|
||||
}
|
||||
|
||||
protected def TakesDamage : Receive = {
|
||||
case Vitality.Damage(applyDamageTo) =>
|
||||
if(player.isAlive) {
|
||||
val originalHealth = player.Health
|
||||
val originalArmor = player.Armor
|
||||
val originalCapacitor = player.Capacitor.toInt
|
||||
val cause = resolution_function(player)
|
||||
val cause = applyDamageTo(player)
|
||||
val health = player.Health
|
||||
val armor = player.Armor
|
||||
val capacitor = player.Capacitor.toInt
|
||||
val damageToHealth = originalHealth - health
|
||||
val damageToArmor = originalArmor - armor
|
||||
val damageToCapacitor = originalCapacitor - capacitor
|
||||
PlayerControl.HandleDamageResolution(player, cause, damageToHealth, damageToArmor, damageToCapacitor)
|
||||
if(damageToHealth != 0 || damageToArmor != 0 || damageToCapacitor != 0) {
|
||||
PlayerControl.HandleDamage(player, cause, damageToHealth, damageToArmor, damageToCapacitor)
|
||||
if(damageToHealth > 0 || damageToArmor > 0 || damageToCapacitor > 0) {
|
||||
damageLog.info(s"${player.Name}-infantry: BEFORE=$originalHealth/$originalArmor/$originalCapacitor, AFTER=$health/$armor/$capacitor, CHANGE=$damageToHealth/$damageToArmor/$damageToCapacitor")
|
||||
}
|
||||
}
|
||||
case _ => ;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -77,8 +155,12 @@ class PlayerControl(player : Player) extends Actor
|
|||
override def StartJammeredStatus(target : Any, dur : Int) : Unit = target match {
|
||||
case obj : Player =>
|
||||
//TODO these features
|
||||
obj.Zone.AvatarEvents ! AvatarServiceMessage(obj.Zone.Id, AvatarAction.DeactivateImplantSlot(obj.GUID, 1))
|
||||
obj.Zone.AvatarEvents ! AvatarServiceMessage(obj.Zone.Id, AvatarAction.DeactivateImplantSlot(obj.GUID, 2))
|
||||
val guid = obj.GUID
|
||||
val zone = obj.Zone
|
||||
val zoneId = zone.Id
|
||||
val events = zone.AvatarEvents
|
||||
events ! AvatarServiceMessage(zoneId, AvatarAction.DeactivateImplantSlot(guid, 1))
|
||||
events ! AvatarServiceMessage(zoneId, AvatarAction.DeactivateImplantSlot(guid, 2))
|
||||
obj.skipStaminaRegenForTurns = math.max(obj.skipStaminaRegenForTurns, 10)
|
||||
super.StartJammeredStatus(target, dur)
|
||||
case _ => ;
|
||||
|
|
@ -95,6 +177,13 @@ class PlayerControl(player : Player) extends Actor
|
|||
super.CancelJammeredSound(obj)
|
||||
case _ => ;
|
||||
}
|
||||
|
||||
def RepairValue(item : Tool) : Int = if(player.ExoSuit != ExoSuitType.MAX) {
|
||||
item.FireMode.Modifiers.Damage0
|
||||
}
|
||||
else {
|
||||
item.FireMode.Modifiers.Damage3
|
||||
}
|
||||
}
|
||||
|
||||
object PlayerControl {
|
||||
|
|
@ -102,62 +191,60 @@ object PlayerControl {
|
|||
* na
|
||||
* @param target na
|
||||
*/
|
||||
def HandleDamageResolution(target : Player, cause : ResolvedProjectile, damageToHealth : Int, damageToArmor : Int, damageToCapacitor : Int) : Unit = {
|
||||
def HandleDamage(target : Player, cause : ResolvedProjectile, damageToHealth : Int, damageToArmor : Int, damageToCapacitor : Int) : Unit = {
|
||||
val targetGUID = target.GUID
|
||||
val playerGUID = target.Zone.LivePlayers.find { p => cause.projectile.owner.Name.equals(p.Name) } match {
|
||||
case Some(player) => player.GUID
|
||||
case _ => PlanetSideGUID(0)
|
||||
}
|
||||
if(target.Health > 0) {
|
||||
//activity on map
|
||||
if(damageToHealth + damageToArmor > 0) {
|
||||
target.Zone.Activity ! Zone.HotSpot.Activity(cause.target, cause.projectile.owner, cause.hit_pos)
|
||||
//alert damage source
|
||||
HandleDamageAwareness(target, playerGUID, cause)
|
||||
val zone = target.Zone
|
||||
val zoneId = zone.Id
|
||||
val events = zone.AvatarEvents
|
||||
val health = target.Health
|
||||
if(health > 0) {
|
||||
if(damageToCapacitor > 0) {
|
||||
events ! AvatarServiceMessage(target.Name, AvatarAction.PlanetsideAttributeSelf(targetGUID, 7, target.Capacitor.toLong))
|
||||
}
|
||||
if(cause.projectile.profile.JammerProjectile) {
|
||||
if(damageToHealth > 0 || damageToArmor > 0) {
|
||||
target.History(cause)
|
||||
if(damageToHealth > 0) {
|
||||
events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(targetGUID, 0, health))
|
||||
}
|
||||
if(damageToArmor > 0) {
|
||||
events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(targetGUID, 4, target.Armor))
|
||||
}
|
||||
//activity on map
|
||||
zone.Activity ! Zone.HotSpot.Activity(cause.target, cause.projectile.owner, cause.hit_pos)
|
||||
//alert damage source
|
||||
DamageAwareness(target, cause)
|
||||
}
|
||||
if(Damageable.CanJammer(target, cause)) {
|
||||
target.Actor ! JammableUnit.Jammered(cause)
|
||||
}
|
||||
}
|
||||
else {
|
||||
HandleDestructionAwareness(target, playerGUID, Some(cause))
|
||||
}
|
||||
if(damageToHealth > 0) {
|
||||
target.Zone.AvatarEvents ! AvatarServiceMessage(target.Zone.Id, AvatarAction.PlanetsideAttributeToAll(targetGUID, 0, target.Health))
|
||||
}
|
||||
if(damageToArmor > 0) {
|
||||
target.Zone.AvatarEvents ! AvatarServiceMessage(target.Zone.Id, AvatarAction.PlanetsideAttributeToAll(targetGUID, 4, target.Armor))
|
||||
}
|
||||
if(damageToCapacitor > 0) {
|
||||
target.Zone.AvatarEvents ! AvatarServiceMessage(target.Name, AvatarAction.PlanetsideAttributeSelf(targetGUID, 7, target.Capacitor.toLong))
|
||||
if(damageToArmor > 0) {
|
||||
events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(targetGUID, 4, target.Armor))
|
||||
}
|
||||
DestructionAwareness(target, Some(cause))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* na
|
||||
* @param target na
|
||||
* @param attribution na
|
||||
* @param lastShot na
|
||||
* @param cause na
|
||||
*/
|
||||
def HandleDamageAwareness(target : Player, attribution : PlanetSideGUID, lastShot : ResolvedProjectile) : Unit = {
|
||||
val owner = lastShot.projectile.owner
|
||||
owner match {
|
||||
case pSource : PlayerSource =>
|
||||
target.Zone.LivePlayers.find(_.Name == pSource.Name) match {
|
||||
case Some(tplayer) =>
|
||||
target.Zone.AvatarEvents ! AvatarServiceMessage(
|
||||
target.Name,
|
||||
AvatarAction.HitHint(tplayer.GUID, target.GUID)
|
||||
)
|
||||
case None => ;
|
||||
}
|
||||
case vSource : SourceEntry =>
|
||||
target.Zone.AvatarEvents ! AvatarServiceMessage(
|
||||
target.Name,
|
||||
AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(10, vSource.Position))
|
||||
)
|
||||
case _ => ;
|
||||
}
|
||||
def DamageAwareness(target : Player, cause : ResolvedProjectile) : Unit = {
|
||||
val zone = target.Zone
|
||||
zone.AvatarEvents ! AvatarServiceMessage(
|
||||
target.Name,
|
||||
cause.projectile.owner match {
|
||||
case pSource : PlayerSource => //player damage
|
||||
val name = pSource.Name
|
||||
zone.LivePlayers.find(_.Name == name).orElse(zone.Corpses.find(_.Name == name)) match {
|
||||
case Some(player) => AvatarAction.HitHint(player.GUID, target.GUID)
|
||||
case None => AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(10, pSource.Position))
|
||||
}
|
||||
case source => AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(10, source.Position))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -173,10 +260,9 @@ object PlayerControl {
|
|||
* A maximum revive waiting timer is started.
|
||||
* When this timer reaches zero, the avatar will attempt to spawn back on its faction-specific sanctuary continent.
|
||||
* @param target na
|
||||
* @param attribution na
|
||||
* @param lastShot na
|
||||
* @param cause na
|
||||
*/
|
||||
def HandleDestructionAwareness(target : Player, attribution : PlanetSideGUID, lastShot : Option[ResolvedProjectile]) : Unit = {
|
||||
def DestructionAwareness(target : Player, cause : Option[ResolvedProjectile]) : Unit = {
|
||||
val player_guid = target.GUID
|
||||
val pos = target.Position
|
||||
val respawnTimer = 300000 //milliseconds
|
||||
|
|
@ -185,24 +271,49 @@ object PlayerControl {
|
|||
val nameChannel = target.Name
|
||||
val zoneChannel = zone.Id
|
||||
target.Die
|
||||
//unjam
|
||||
target.Actor ! JammableUnit.ClearJammeredSound()
|
||||
target.Actor ! JammableUnit.ClearJammeredStatus()
|
||||
events ! AvatarServiceMessage(nameChannel, AvatarAction.Killed(player_guid)) //align client interface fields with state
|
||||
if(target.VehicleSeated.nonEmpty) {
|
||||
//make player invisible (if not, the cadaver sticks out the side in a seated position)
|
||||
events ! AvatarServiceMessage(nameChannel, AvatarAction.PlanetsideAttributeToAll(player_guid, 29, 1))
|
||||
//only the dead player should "see" their own body, so that the death camera has something to focus on
|
||||
events ! AvatarServiceMessage(zoneChannel, AvatarAction.ObjectDelete(player_guid, player_guid))
|
||||
zone.GUID(target.VehicleSeated) match {
|
||||
case Some(obj : Mountable) =>
|
||||
//boot cadaver from seat
|
||||
events ! AvatarServiceMessage(nameChannel, AvatarAction.SendResponse(Service.defaultPlayerGUID,
|
||||
ObjectDetachMessage(obj.GUID, player_guid, target.Position, Vector3.Zero))
|
||||
)
|
||||
obj.PassengerInSeat(target) match {
|
||||
case Some(index) =>
|
||||
obj.Seats(index).Occupant = None
|
||||
case _ => ;
|
||||
}
|
||||
//make player invisible
|
||||
events ! AvatarServiceMessage(nameChannel, AvatarAction.PlanetsideAttributeToAll(player_guid, 29, 1))
|
||||
//only the dead player should "see" their own body, so that the death camera has something to focus on
|
||||
events ! AvatarServiceMessage(zoneChannel, AvatarAction.ObjectDelete(player_guid, player_guid))
|
||||
case _ => ;
|
||||
}
|
||||
events ! AvatarServiceMessage(zoneChannel, AvatarAction.PlanetsideAttributeToAll(player_guid, 0, 0)) //health
|
||||
events ! AvatarServiceMessage(nameChannel, AvatarAction.PlanetsideAttributeToAll(player_guid, 2, 0)) //stamina
|
||||
events ! AvatarServiceMessage(zoneChannel, AvatarAction.PlanetsideAttributeToAll(player_guid, 4, target.Armor)) //armor
|
||||
if(target.ExoSuit == ExoSuitType.MAX) {
|
||||
if(target.Capacitor > 0) {
|
||||
target.Capacitor = 0
|
||||
events ! AvatarServiceMessage(nameChannel, AvatarAction.PlanetsideAttributeToAll(player_guid, 7, 0)) // capacitor
|
||||
}
|
||||
val attribute = cause match {
|
||||
case Some(resolved) =>
|
||||
resolved.projectile.owner match {
|
||||
case pSource : PlayerSource =>
|
||||
val name = pSource.Name
|
||||
zone.LivePlayers.find(_.Name == name).orElse(zone.Corpses.find(_.Name == name)) match {
|
||||
case Some(player) => player.GUID
|
||||
case None => player_guid
|
||||
}
|
||||
case _ => player_guid
|
||||
}
|
||||
case _ => player_guid
|
||||
}
|
||||
events ! AvatarServiceMessage(
|
||||
nameChannel,
|
||||
AvatarAction.SendResponse(Service.defaultPlayerGUID, DestroyMessage(player_guid, player_guid, Service.defaultPlayerGUID, pos)) //how many players get this message?
|
||||
AvatarAction.SendResponse(Service.defaultPlayerGUID, DestroyMessage(player_guid, attribute, Service.defaultPlayerGUID, pos)) //how many players get this message?
|
||||
)
|
||||
events ! AvatarServiceMessage(
|
||||
nameChannel,
|
||||
|
|
@ -214,7 +325,7 @@ object PlayerControl {
|
|||
case Some(PlayerSuicide(_)) =>
|
||||
None
|
||||
case _ =>
|
||||
lastShot.orElse { target.LastShot } match {
|
||||
cause.orElse { target.LastShot } match {
|
||||
case out @ Some(shot) =>
|
||||
if(System.nanoTime - shot.hit_time < (10 seconds).toNanos) {
|
||||
out
|
||||
|
|
@ -227,7 +338,6 @@ object PlayerControl {
|
|||
}
|
||||
}) match {
|
||||
case Some(shot) =>
|
||||
zone.Activity ! Zone.HotSpot.Activity(pentry, shot.projectile.owner, shot.hit_pos)
|
||||
events ! AvatarServiceMessage(zoneChannel, AvatarAction.DestroyDisplay(shot.projectile.owner, pentry, shot.projectile.attribute_to))
|
||||
case None =>
|
||||
events ! AvatarServiceMessage(zoneChannel, AvatarAction.DestroyDisplay(pentry, pentry, 0))
|
||||
|
|
|
|||
|
|
@ -14,11 +14,11 @@ import net.psforever.types.Vector3
|
|||
* @param target what the projectile hit
|
||||
* @param damage_model the kind of damage model to which the `target` is/was subject
|
||||
* @param hit_pos where the projectile hit
|
||||
* @param hit_time the sequence timing when the projectile hit the target
|
||||
*/
|
||||
final case class ResolvedProjectile(resolution : ProjectileResolution.Value,
|
||||
projectile : Projectile,
|
||||
target : SourceEntry,
|
||||
damage_model : DamageResistanceModel,
|
||||
hit_pos : Vector3,
|
||||
hit_time : Long = System.nanoTime)
|
||||
hit_pos : Vector3) {
|
||||
val hit_time : Long = System.nanoTime
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,10 +6,6 @@ import net.psforever.objects.serverobject.PlanetSideServerObject
|
|||
|
||||
abstract class ComplexDeployable(cdef : ComplexDeployableDefinition) extends PlanetSideServerObject
|
||||
with Deployable {
|
||||
Health = Definition.MaxHealth
|
||||
|
||||
def MaxHealth : Int = Definition.MaxHealth
|
||||
|
||||
private var shields : Int = 0
|
||||
|
||||
def Shields : Int = shields
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
package net.psforever.objects.ce
|
||||
|
||||
import net.psforever.objects._
|
||||
import net.psforever.objects.definition.{BaseDeployableDefinition, ObjectDefinition}
|
||||
import net.psforever.objects.definition.DeployableDefinition
|
||||
import net.psforever.objects.serverobject.affinity.FactionAffinity
|
||||
import net.psforever.objects.vital.{DamageResistanceModel, Vitality}
|
||||
import net.psforever.objects.zones.ZoneAware
|
||||
|
|
@ -14,16 +14,8 @@ trait Deployable extends FactionAffinity
|
|||
with OwnableByPlayer
|
||||
with ZoneAware {
|
||||
this : PlanetSideGameObject =>
|
||||
private var health : Int = 1
|
||||
private var faction : PlanetSideEmpire.Value = PlanetSideEmpire.NEUTRAL
|
||||
|
||||
def Health : Int = health
|
||||
|
||||
def Health_=(toHealth : Int) : Int = {
|
||||
health = math.min(math.max(0, toHealth), MaxHealth)
|
||||
Health
|
||||
}
|
||||
|
||||
def MaxHealth : Int
|
||||
|
||||
def Faction : PlanetSideEmpire.Value = faction
|
||||
|
|
@ -35,7 +27,7 @@ trait Deployable extends FactionAffinity
|
|||
|
||||
def DamageModel : DamageResistanceModel = Definition.asInstanceOf[DamageResistanceModel]
|
||||
|
||||
def Definition : ObjectDefinition with BaseDeployableDefinition
|
||||
def Definition : DeployableDefinition
|
||||
}
|
||||
|
||||
object Deployable {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,5 @@ abstract class SimpleDeployable(cdef : SimpleDeployableDefinition) extends Plane
|
|||
with Deployable {
|
||||
Health = Definition.MaxHealth
|
||||
|
||||
def MaxHealth : Int = Definition.MaxHealth
|
||||
|
||||
def Definition = cdef
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,12 +3,14 @@ package net.psforever.objects.definition
|
|||
|
||||
import net.psforever.objects.avatar.Avatars
|
||||
import net.psforever.objects.definition.converter.AvatarConverter
|
||||
import net.psforever.objects.vital.VitalityDefinition
|
||||
|
||||
/**
|
||||
* The definition for game objects that look like other people, and also for players.
|
||||
* @param objectId the object's identifier number
|
||||
*/
|
||||
class AvatarDefinition(objectId : Int) extends ObjectDefinition(objectId) {
|
||||
class AvatarDefinition(objectId : Int) extends ObjectDefinition(objectId)
|
||||
with VitalityDefinition {
|
||||
Avatars(objectId) //let throw NoSuchElementException
|
||||
Packet = AvatarDefinition.converter
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,8 +26,8 @@ class ExoSuitDefinition(private val suitType : ExoSuitType.Value) extends BasicD
|
|||
protected var capacitorRechargePerSecond : Int = 0
|
||||
protected var capacitorDrainPerSecond : Int = 0
|
||||
Name = "exo-suit"
|
||||
Damage = StandardInfantryDamage
|
||||
Resistance = StandardInfantryResistance
|
||||
DamageUsing = StandardInfantryDamage
|
||||
ResistUsing = StandardInfantryResistance
|
||||
Model = StandardResolutions.Infantry
|
||||
|
||||
def SuitType : ExoSuitType.Value = suitType
|
||||
|
|
@ -142,8 +142,8 @@ class SpecialExoSuitDefinition(private val suitType : ExoSuitType.Value) extends
|
|||
obj.ResistanceDirectHit = ResistanceDirectHit
|
||||
obj.ResistanceSplash = ResistanceSplash
|
||||
obj.ResistanceAggravated = ResistanceAggravated
|
||||
obj.Damage = Damage
|
||||
obj.Resistance = Resistance
|
||||
obj.DamageUsing = DamageUsing
|
||||
obj.ResistUsing = ResistUsing
|
||||
obj.Model = Model
|
||||
(0 until 5).foreach(index => { obj.Holster(index, Holster(index)) })
|
||||
obj
|
||||
|
|
|
|||
|
|
@ -7,27 +7,16 @@ import net.psforever.objects.ce.{Deployable, DeployableCategory, DeployedItem}
|
|||
import net.psforever.objects.definition.converter.SmallDeployableConverter
|
||||
import net.psforever.objects.serverobject.PlanetSideServerObject
|
||||
import net.psforever.objects.vital.resistance.ResistanceProfileMutators
|
||||
import net.psforever.objects.vital.{DamageResistanceModel, NoResistanceSelection, StandardDeployableDamage}
|
||||
import net.psforever.objects.vital.{DamageResistanceModel, NoResistanceSelection, StandardDeployableDamage, VitalityDefinition}
|
||||
|
||||
import scala.concurrent.duration._
|
||||
|
||||
trait BaseDeployableDefinition extends DamageResistanceModel
|
||||
with ResistanceProfileMutators {
|
||||
trait BaseDeployableDefinition {
|
||||
private var category : DeployableCategory.Value = DeployableCategory.Boomers
|
||||
private var deployTime : Long = (1 second).toMillis //ms
|
||||
private var maxHealth : Int = 1
|
||||
Damage = StandardDeployableDamage
|
||||
Resistance = NoResistanceSelection
|
||||
|
||||
def Item : DeployedItem.Value
|
||||
|
||||
def MaxHealth : Int = maxHealth
|
||||
|
||||
def MaxHealth_=(toHealth : Int) : Int = {
|
||||
maxHealth = toHealth
|
||||
MaxHealth
|
||||
}
|
||||
|
||||
def DeployCategory : DeployableCategory.Value = category
|
||||
|
||||
def DeployCategory_=(cat : DeployableCategory.Value) : DeployableCategory.Value = {
|
||||
|
|
@ -53,20 +42,23 @@ trait BaseDeployableDefinition extends DamageResistanceModel
|
|||
def Uninitialize(obj : PlanetSideServerObject with Deployable, context : ActorContext) : Unit = { }
|
||||
}
|
||||
|
||||
class SimpleDeployableDefinition(private val objectId : Int) extends ObjectDefinition(objectId)
|
||||
abstract class DeployableDefinition(objectId : Int) extends ObjectDefinition(objectId)
|
||||
with DamageResistanceModel
|
||||
with ResistanceProfileMutators
|
||||
with VitalityDefinition
|
||||
with BaseDeployableDefinition {
|
||||
private val item = DeployedItem(objectId) //let throw NoSuchElementException
|
||||
DamageUsing = StandardDeployableDamage
|
||||
ResistUsing = NoResistanceSelection
|
||||
|
||||
def Item : DeployedItem.Value = item
|
||||
}
|
||||
|
||||
class SimpleDeployableDefinition(objectId : Int) extends DeployableDefinition(objectId) {
|
||||
Packet = new SmallDeployableConverter
|
||||
|
||||
def Item : DeployedItem.Value = item
|
||||
}
|
||||
|
||||
abstract class ComplexDeployableDefinition(private val objectId : Int) extends ObjectDefinition(objectId)
|
||||
with BaseDeployableDefinition {
|
||||
private val item = DeployedItem(objectId) //let throw NoSuchElementException
|
||||
|
||||
def Item : DeployedItem.Value = item
|
||||
}
|
||||
abstract class ComplexDeployableDefinition(objectId : Int) extends DeployableDefinition(objectId)
|
||||
|
||||
object SimpleDeployableDefinition {
|
||||
def apply(item : DeployedItem.Value) : SimpleDeployableDefinition =
|
||||
|
|
@ -75,7 +67,7 @@ object SimpleDeployableDefinition {
|
|||
def SimpleUninitialize(obj : PlanetSideGameObject, context : ActorContext) : Unit = { }
|
||||
|
||||
def SimpleUninitialize(obj : PlanetSideServerObject, context : ActorContext) : Unit = {
|
||||
obj.Actor ! akka.actor.PoisonPill
|
||||
context.stop(obj.Actor)
|
||||
obj.Actor = ActorRef.noSender
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,9 +16,9 @@ import scala.concurrent.duration._
|
|||
* @param objectId the object id that is associated with this sort of `Vehicle`
|
||||
*/
|
||||
class VehicleDefinition(objectId : Int) extends ObjectDefinition(objectId)
|
||||
with VitalityDefinition
|
||||
with ResistanceProfileMutators
|
||||
with DamageResistanceModel {
|
||||
private var maxHealth : Int = 100
|
||||
/** vehicle shields offered through amp station facility benefits (generally: 20% of health + 1) */
|
||||
private var maxShields : Int = 0
|
||||
/* key - seat index, value - seat object */
|
||||
|
|
@ -45,16 +45,11 @@ class VehicleDefinition(objectId : Int) extends ObjectDefinition(objectId)
|
|||
private var destroyedModel : Option[DestroyedVehicle.Value] = None
|
||||
Name = "vehicle"
|
||||
Packet = VehicleDefinition.converter
|
||||
Damage = StandardVehicleDamage
|
||||
Resistance = StandardVehicleResistance
|
||||
DamageUsing = StandardVehicleDamage
|
||||
ResistUsing = StandardVehicleResistance
|
||||
Model = StandardResolutions.Vehicle
|
||||
|
||||
def MaxHealth : Int = maxHealth
|
||||
|
||||
def MaxHealth_=(health : Int) : Int = {
|
||||
maxHealth = health
|
||||
MaxHealth
|
||||
}
|
||||
RepairDistance = 10
|
||||
RepairRestoresAt = 1
|
||||
|
||||
def MaxShields : Int = maxShields
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ class SmallDeployableConverter extends ObjectCreateConverter[PlanetSideGameObjec
|
|||
CommonFieldData(
|
||||
obj.Faction,
|
||||
bops = false,
|
||||
alternate = false,
|
||||
alternate = obj.Destroyed,
|
||||
false,
|
||||
None,
|
||||
jammered = obj match {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package net.psforever.objects.serverobject
|
||||
|
||||
import net.psforever.objects.{PlanetSideGameObject, Player}
|
||||
import net.psforever.objects.Player
|
||||
import net.psforever.objects.serverobject.hackable.Hackable
|
||||
|
||||
//temporary location for these messages
|
||||
object CommonMessages {
|
||||
final case class Use(player : Player, data : Option[Any] = None)
|
||||
final case class Unuse(player : Player, data : Option[Any] = None)
|
||||
final case class Hack(player : Player)
|
||||
final case class Hack(player : Player, obj : PlanetSideServerObject with Hackable, data : Option[Any] = None)
|
||||
final case class ClearHack()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,114 @@
|
|||
//Copyright (c) 2020 PSForever
|
||||
package net.psforever.objects.serverobject.damage
|
||||
|
||||
import akka.actor.Actor.Receive
|
||||
import net.psforever.objects.ballistics.ResolvedProjectile
|
||||
import net.psforever.objects.equipment.JammableUnit
|
||||
import net.psforever.objects.serverobject.PlanetSideServerObject
|
||||
import net.psforever.objects.serverobject.affinity.FactionAffinity
|
||||
import net.psforever.objects.serverobject.hackable.Hackable
|
||||
import net.psforever.objects.vital.Vitality
|
||||
|
||||
/**
|
||||
* The base "control" `Actor` mixin for damage-handling code.
|
||||
* A valid entity requires health points and
|
||||
* may have additional obstructions to adjusting those health points such as armor and shields.
|
||||
* All of these should be affected by the damage where applicable.
|
||||
*/
|
||||
trait Damageable {
|
||||
/**
|
||||
* Contextual access to the object being the target of this damage.
|
||||
* Needs declaration in lowest implementing code.
|
||||
* @return the entity controlled by this actor
|
||||
*/
|
||||
def DamageableObject : Damageable.Target
|
||||
|
||||
/** the official mixin hook; `orElse` onto the "control" `Actor` `receive` */
|
||||
final val takesDamage : Receive = TakesDamage
|
||||
|
||||
/**
|
||||
* Implementation of the mixin hook will be provided by a child class.
|
||||
* Override this method only when directly implementing.
|
||||
* @see `takesDamage`
|
||||
* @see `DamageableAmenity.PerformDamage`
|
||||
*/
|
||||
protected def TakesDamage : Receive
|
||||
}
|
||||
|
||||
object Damageable {
|
||||
/* the type of all entities governed by this mixin; see Repairable.Target */
|
||||
final type Target = PlanetSideServerObject with Vitality
|
||||
/* the master channel for logging damage resolution information
|
||||
* the format of the channel is expected to follow:
|
||||
* "[identifier]: BEFORE=[before1/before2/etc.] AFTER=[after1/after2/etc.] CHANGE=[change1/change2/etc.]"
|
||||
* ... where before1 - change1 = after1, and so forth, for each field that matters
|
||||
* the fields do not have to be labeled but the first (if not only) should always be Health
|
||||
*/
|
||||
final val LogChannel : String = "DamageResolution"
|
||||
|
||||
/**
|
||||
* Does the possibility exist that the designated target can be affected by this projectile's damage?
|
||||
* @see `Hackable`
|
||||
* @see `ObjectDefinition.DamageableByFriendlyFire`
|
||||
* @param obj the entity being damaged
|
||||
* @param damage the amount of damage
|
||||
* @param data historical information about the damage
|
||||
* @return `true`, if the target can be affected;
|
||||
* `false`, otherwise
|
||||
*/
|
||||
def CanDamage(obj : Vitality with FactionAffinity, damage : Int, data : ResolvedProjectile) : Boolean = {
|
||||
val definition = obj.Definition
|
||||
damage > 0 &&
|
||||
definition.Damageable &&
|
||||
(definition.DamageableByFriendlyFire ||
|
||||
(data.projectile.owner.Faction != obj.Faction ||
|
||||
(obj match {
|
||||
case hobj : Hackable => hobj.HackedBy.nonEmpty
|
||||
case _ => false
|
||||
})
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the possibility exist that the designated target can be affected by this projectile's jammer effect?
|
||||
* @see `Hackable`
|
||||
* @see `ProjectileDefinition..JammerProjectile`
|
||||
* @param obj the entity being damaged
|
||||
* @param data historical information about the damage
|
||||
* @return `true`, if the target can be affected;
|
||||
* `false`, otherwise
|
||||
*/
|
||||
def CanJammer(obj : Vitality with FactionAffinity, data : ResolvedProjectile) : Boolean = {
|
||||
val projectile = data.projectile
|
||||
projectile.profile.JammerProjectile &&
|
||||
obj.isInstanceOf[JammableUnit] &&
|
||||
(projectile.owner.Faction != obj.Faction ||
|
||||
(obj match {
|
||||
case hobj : Hackable => hobj.HackedBy.nonEmpty
|
||||
case _ => false
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the possibility exist that the designated target can be affected by this projectile?
|
||||
* @param obj the entity being damaged
|
||||
* @param damage the amount of damage
|
||||
* @param data historical information about the damage
|
||||
* @return `true`, if the target can be affected;
|
||||
* `false`, otherwise
|
||||
*/
|
||||
def CanDamageOrJammer(obj : Vitality with FactionAffinity, damage : Int, data : ResolvedProjectile) : Boolean = {
|
||||
CanDamage(obj, damage, data) || CanJammer(obj, data)
|
||||
}
|
||||
|
||||
/**
|
||||
* The entity has ben destroyed.
|
||||
* @param target the entity being damaged
|
||||
* @param cause historical information about the damage
|
||||
*/
|
||||
def DestructionAwareness(target : Damageable.Target, cause : ResolvedProjectile) : Unit = {
|
||||
target.Destroyed = true
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
//Copyright (c) 2020 PSForever
|
||||
package net.psforever.objects.serverobject.damage
|
||||
|
||||
import net.psforever.objects.ballistics.ResolvedProjectile
|
||||
import net.psforever.objects.serverobject.structures.Amenity
|
||||
import services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||
|
||||
/**
|
||||
* The "control" `Actor` mixin for damage-handling code
|
||||
* for the majority of `Damageable` `Amenity` objects installed in a facility or a field tower,
|
||||
* with specific exceptions for the `ImplantTerminalMech` and the `Generator`.
|
||||
*/
|
||||
trait DamageableAmenity extends DamageableEntity {
|
||||
def DamageableObject : Amenity
|
||||
|
||||
override protected def DestructionAwareness(target : Damageable.Target, cause : ResolvedProjectile) : Unit = {
|
||||
super.DestructionAwareness(target, cause)
|
||||
DamageableAmenity.DestructionAwareness(target, cause)
|
||||
target.ClearHistory()
|
||||
}
|
||||
}
|
||||
|
||||
object DamageableAmenity {
|
||||
/**
|
||||
* A destroyed `Amenity` target dispatches two messages to chance its model and operational states.
|
||||
* The common manifestation is a sparking entity that will no longer report being accessible.
|
||||
* These `PlanetSideAttributeMessage` attributes are the same as reported during zone load client configuration.
|
||||
* @see `AvatarAction.PlanetsideAttributeToAll`
|
||||
* @see `AvatarServiceMessage`
|
||||
* @see `Zone.AvatarEvents`
|
||||
* @param target the entity being destroyed
|
||||
* @param cause historical information about the damage
|
||||
*/
|
||||
def DestructionAwareness(target : Damageable.Target, cause : ResolvedProjectile) : Unit = {
|
||||
val zone = target.Zone
|
||||
val zoneId = zone.Id
|
||||
val events = zone.AvatarEvents
|
||||
val targetGUID = target.GUID
|
||||
events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(targetGUID, 50, 1))
|
||||
events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(targetGUID, 51, 1))
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,203 @@
|
|||
//Copyright (c) 2020 PSForever
|
||||
package net.psforever.objects.serverobject.damage
|
||||
|
||||
import akka.actor.Actor.Receive
|
||||
import net.psforever.objects.ballistics.ResolvedProjectile
|
||||
import net.psforever.objects.equipment.JammableUnit
|
||||
import net.psforever.objects.vital.Vitality
|
||||
import net.psforever.objects.vital.resolution.ResolutionCalculations
|
||||
import net.psforever.objects.zones.Zone
|
||||
import net.psforever.types.PlanetSideGUID
|
||||
import services.Service
|
||||
import services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||
|
||||
/**
|
||||
* The "control" `Actor` mixin for damage-handling code,
|
||||
* for both expansion into other mixins and specific application on its own.
|
||||
*/
|
||||
trait DamageableEntity extends Damageable {
|
||||
/** log specifically for damage events */
|
||||
private[this] val damageLog = org.log4s.getLogger(Damageable.LogChannel)
|
||||
|
||||
/**
|
||||
* Log a damage message.
|
||||
* @param msg the message for the damage log
|
||||
*/
|
||||
protected def DamageLog(msg : String) : Unit = {
|
||||
damageLog.info(msg)
|
||||
}
|
||||
/**
|
||||
* Log a damage message with a decorator for this target.
|
||||
* The decorator is constructed by the `Actor` name of the entity, sliced after the last forward/slash.
|
||||
* For example, for "foo/bar/name", the decorator is just "name".
|
||||
* @see `PlanetSideServerObject`
|
||||
* @param target the entity to be used for the decorator
|
||||
* @param msg the message for the damage log
|
||||
*/
|
||||
protected def DamageLog(target : Damageable.Target, msg : String) : Unit = {
|
||||
val name = target.Actor.toString
|
||||
val slashPoint = name.lastIndexOf("/")
|
||||
DamageLog(s"${name.substring(slashPoint + 1, name.length - 1)}: $msg")
|
||||
}
|
||||
|
||||
/**
|
||||
* Catch the expected damage message and apply checks to the target.
|
||||
* If adding custom message handling in an future child implementation,
|
||||
* override this method and call `super.TakesDamage.orElse { ... }`.
|
||||
* @see `Damageable.TakesDamage`
|
||||
* @see `ResolutionCalcultions.Output`
|
||||
* @see `Vitality.CanDamage`
|
||||
* @see `Vitality.Damage`
|
||||
*/
|
||||
protected def TakesDamage : Receive = {
|
||||
case Vitality.Damage(damage_func) =>
|
||||
val obj = DamageableObject
|
||||
if(obj.CanDamage) {
|
||||
PerformDamage(obj, damage_func)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assess the vital statistics of the target, apply the damage, and determine if any of those statistics changed.
|
||||
* By default, only take an interest in the change of "health".
|
||||
* If implementing custom `DamageableAmenity` with no new message handling, choose to override this method.
|
||||
* @see `DamageableAmenity.TakesDamage`
|
||||
* @see `ResolutionCalculations.Output`
|
||||
* @see `Vitality.Health`
|
||||
* @param target the entity to be damaged
|
||||
* @param applyDamageTo the function that applies the damage to the target in a target-tailored fashion
|
||||
*/
|
||||
protected def PerformDamage(target : Damageable.Target, applyDamageTo : ResolutionCalculations.Output) : Unit = {
|
||||
val originalHealth = target.Health
|
||||
val cause = applyDamageTo(target)
|
||||
val health = target.Health
|
||||
val damage = originalHealth - health
|
||||
if(WillAffectTarget(target, damage, cause)) {
|
||||
target.History(cause)
|
||||
DamageLog(target, s"BEFORE=$originalHealth, AFTER=$health, CHANGE=$damage")
|
||||
HandleDamage(target, cause, damage)
|
||||
}
|
||||
else {
|
||||
target.Health = originalHealth
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the damage or the projectile that caused the damage offer any reason
|
||||
* to execute the reminder of damage resolution considerations?
|
||||
* The projectile causing additional affects, e.g., jamming, should be tested here, when applicable.
|
||||
* Contrast with `Vitality.CanDamage`.
|
||||
* The damage value tested against should be the total value of all meaningful vital statistics affected.
|
||||
* @see `Damageable.CanDamageOrJammer`
|
||||
* @see `PerformDamage`
|
||||
* @param target the entity to be damaged
|
||||
* @param damage the amount of damage
|
||||
* @param cause historical information about the damage
|
||||
* @return `true`, if damage resolution is to be evaluated;
|
||||
* `false`, otherwise
|
||||
*/
|
||||
protected def WillAffectTarget(target : Damageable.Target, damage : Int, cause : ResolvedProjectile) : Boolean = {
|
||||
Damageable.CanDamageOrJammer(target, damage, cause)
|
||||
}
|
||||
|
||||
/**
|
||||
* Select between mere damage reception or target destruction.
|
||||
* @see `VitalDefinition.DamageDestroysAt`
|
||||
* @param target the entity being damaged
|
||||
* @param cause historical information about the damage
|
||||
* @param damage the amount of damage
|
||||
*/
|
||||
protected def HandleDamage(target : Damageable.Target, cause : ResolvedProjectile, damage : Int) : Unit = {
|
||||
if(!target.Destroyed && target.Health <= target.Definition.DamageDestroysAt) {
|
||||
DestructionAwareness(target, cause)
|
||||
}
|
||||
else {
|
||||
DamageAwareness(target, cause, damage)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* What happens when damage is sustained but the target does not get destroyed.
|
||||
* @param target the entity being damaged
|
||||
* @param cause historical information about the damage
|
||||
* @param amount the amount of damage
|
||||
*/
|
||||
protected def DamageAwareness(target : Damageable.Target, cause : ResolvedProjectile, amount : Int) : Unit = {
|
||||
DamageableEntity.DamageAwareness(target, cause, amount)
|
||||
}
|
||||
|
||||
/**
|
||||
* What happens when the target sustains too much damage and is destroyed.
|
||||
* @see `Damageable.DestructionAwareness`
|
||||
* @param target the entity being destroyed
|
||||
* @param cause historical information about the damage
|
||||
*/
|
||||
protected def DestructionAwareness(target : Damageable.Target, cause : ResolvedProjectile) : Unit = {
|
||||
Damageable.DestructionAwareness(target, cause)
|
||||
DamageableEntity.DestructionAwareness(target, cause)
|
||||
}
|
||||
}
|
||||
|
||||
object DamageableEntity {
|
||||
/**
|
||||
* A damaged target dispatches messages to:
|
||||
* - reports its adjusted its health;
|
||||
* - alert the activity monitor for that `Zone` about the damage; and,
|
||||
* - provide a feedback message regarding the damage.
|
||||
* @see `AvatarAction.PlanetsideAttributeToAll`
|
||||
* @see `AvatarAction.SendResponse`
|
||||
* @see `AvatarServiceMessage`
|
||||
* @see `DamageFeedbackMessage`
|
||||
* @see `JammableUnit.Jammered`
|
||||
* @see `Service.defaultPlayerGUID`
|
||||
* @see `Zone.Activity`
|
||||
* @see `Zone.AvatarEvents`
|
||||
* @see `Zone.HotSpot.Activity`
|
||||
* @see `Zone.LivePlayers`
|
||||
* @param target the entity being damaged
|
||||
* @param cause historical information about the damage
|
||||
*/
|
||||
def DamageAwareness(target : Damageable.Target, cause : ResolvedProjectile, amount : Int) : Unit = {
|
||||
if(Damageable.CanJammer(target, cause)) {
|
||||
target.Actor ! JammableUnit.Jammered(cause)
|
||||
}
|
||||
if(amount > 0) {
|
||||
val zone = target.Zone
|
||||
if(!target.Destroyed) {
|
||||
val tguid = target.GUID
|
||||
zone.AvatarEvents ! AvatarServiceMessage(zone.Id, AvatarAction.PlanetsideAttributeToAll(tguid, 0, target.Health))
|
||||
}
|
||||
zone.Activity ! Zone.HotSpot.Activity(cause.target, cause.projectile.owner, cause.hit_pos)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A destroyed target dispatches messages to:
|
||||
* - reports its adjusted its health; and,
|
||||
* - report about its destruction.
|
||||
* @see `AvatarAction.Destroy`
|
||||
* @see `AvatarAction.PlanetsideAttribute`
|
||||
* @see `AvatarServiceMessage`
|
||||
* @see `DamageFeedbackMessage`
|
||||
* @see `JammableUnit.ClearJammeredSound`
|
||||
* @see `JammableUnit.ClearJammeredStatus`
|
||||
* @see `Zone.AvatarEvents`
|
||||
* @param target the entity being destroyed
|
||||
* @param cause historical information about the damage
|
||||
*/
|
||||
def DestructionAwareness(target : Damageable.Target, cause : ResolvedProjectile) : Unit = {
|
||||
//un-jam
|
||||
target.Actor ! JammableUnit.ClearJammeredSound()
|
||||
target.Actor ! JammableUnit.ClearJammeredStatus()
|
||||
//
|
||||
val zone = target.Zone
|
||||
val zoneId = zone.Id
|
||||
val tguid = target.GUID
|
||||
val attribution = target.Zone.LivePlayers.find { p => cause.projectile.owner.Name.equals(p.Name) } match {
|
||||
case Some(player) => player.GUID
|
||||
case _ => PlanetSideGUID(0)
|
||||
}
|
||||
zone.AvatarEvents ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(tguid, 0, target.Health))
|
||||
zone.AvatarEvents ! AvatarServiceMessage(zoneId, AvatarAction.Destroy(tguid, attribution, Service.defaultPlayerGUID, target.Position))
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
//Copyright (c) 2020 PSForever
|
||||
package net.psforever.objects.serverobject.damage
|
||||
|
||||
import net.psforever.objects.Player
|
||||
import net.psforever.objects.ballistics.{PlayerSource, ResolvedProjectile}
|
||||
import net.psforever.objects.serverobject.mount.Mountable
|
||||
import net.psforever.packet.game.DamageWithPositionMessage
|
||||
import services.Service
|
||||
import services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||
|
||||
/**
|
||||
* Functions to assist other damage-dealing code for objects that contain users.
|
||||
*/
|
||||
object DamageableMountable {
|
||||
/**
|
||||
* A damaged target alerts its occupants (as it is a `Mountable` object) of the source of the damage.
|
||||
* @see `AvatarAction.HitHint`
|
||||
* @see `AvatarAction.SendResponse`
|
||||
* @see `AvatarServiceMessage`
|
||||
* @see `DamageWithPositionMessage`
|
||||
* @see `Mountable.Seats`
|
||||
* @see `Service.defaultPlayerGUID`
|
||||
* @see `Zone.AvatarEvents`
|
||||
* @see `Zone.LivePlayers`
|
||||
* @param target the entity being damaged
|
||||
* @param cause historical information about the damage
|
||||
*/
|
||||
def DamageAwareness(target : Damageable.Target with Mountable, cause : ResolvedProjectile) : Unit = {
|
||||
val zone = target.Zone
|
||||
val events = zone.AvatarEvents
|
||||
val occupants = target.Seats.values.collect {
|
||||
case seat if seat.isOccupied && seat.Occupant.get.isAlive =>
|
||||
seat.Occupant.get
|
||||
}
|
||||
(cause.projectile.owner match {
|
||||
case pSource : PlayerSource => //player damage
|
||||
val name = pSource.Name
|
||||
(zone.LivePlayers.find(_.Name == name).orElse(zone.Corpses.find(_.Name == name)) match {
|
||||
case Some(player) => AvatarAction.HitHint(player.GUID, player.GUID)
|
||||
case None => AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(10, pSource.Position))
|
||||
}) match {
|
||||
case AvatarAction.HitHint(_, guid) =>
|
||||
occupants.map { tplayer => (tplayer.Name, AvatarAction.HitHint(guid, tplayer.GUID)) }
|
||||
case msg =>
|
||||
occupants.map { tplayer => (tplayer.Name, msg) }
|
||||
}
|
||||
case source => //object damage
|
||||
val msg = AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(10, source.Position))
|
||||
occupants.map { tplayer => (tplayer.Name, msg) }
|
||||
}).foreach { case (channel, msg) =>
|
||||
events ! AvatarServiceMessage(channel, msg)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When the target dies, so do all of its occupants.
|
||||
* @see `Mountable.Seats`
|
||||
* @see `Player.Die`
|
||||
* @see `VitalsHistory.History`
|
||||
* @param target the entity being destroyed
|
||||
* @param cause historical information about the damage
|
||||
*/
|
||||
def DestructionAwareness(target : Damageable.Target with Mountable, cause : ResolvedProjectile) : Unit = {
|
||||
target.Seats.values.filter(seat => {
|
||||
seat.isOccupied && seat.Occupant.get.isAlive
|
||||
}).foreach(seat => {
|
||||
val tplayer = seat.Occupant.get
|
||||
tplayer.History(cause)
|
||||
tplayer.Actor ! Player.Die()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,187 @@
|
|||
//Copyright (c) 2020 PSForever
|
||||
package net.psforever.objects.serverobject.damage
|
||||
|
||||
import akka.actor.Actor.Receive
|
||||
import net.psforever.objects.{GlobalDefinitions, Vehicle}
|
||||
import net.psforever.objects.ballistics.ResolvedProjectile
|
||||
import net.psforever.objects.serverobject.damage.Damageable.Target
|
||||
import net.psforever.objects.serverobject.deploy.Deployment
|
||||
import net.psforever.objects.vital.resolution.ResolutionCalculations
|
||||
import net.psforever.types.{DriveState, PlanetSideGUID}
|
||||
import services.{RemoverActor, Service}
|
||||
import services.local.{LocalAction, LocalServiceMessage}
|
||||
import services.vehicle.{VehicleAction, VehicleService, VehicleServiceMessage}
|
||||
|
||||
import scala.concurrent.duration._
|
||||
|
||||
/**
|
||||
* The "control" `Actor` mixin for damage-handling code for `Vehicle` objects.
|
||||
*/
|
||||
trait DamageableVehicle extends DamageableEntity {
|
||||
/** vehicles (may) have shields; they need to be handled */
|
||||
private var handleDamageToShields : Boolean = false
|
||||
/** whether or not the vehicle has been damaged directly, report that damage has occurred */
|
||||
private var reportDamageToVehicle : Boolean = false
|
||||
|
||||
def DamageableObject : Vehicle
|
||||
|
||||
override protected def TakesDamage : Receive =
|
||||
super.TakesDamage.orElse {
|
||||
case DamageableVehicle.Damage(cause, damage) =>
|
||||
//cargo vehicles inherit feedback from carrier
|
||||
reportDamageToVehicle = damage > 0
|
||||
DamageAwareness(DamageableObject, cause, amount = 0)
|
||||
|
||||
case DamageableVehicle.Destruction(cause) =>
|
||||
//cargo vehicles are destroyed when carrier is destroyed
|
||||
val obj = DamageableObject
|
||||
obj.Health = 0
|
||||
obj.History(cause)
|
||||
DestructionAwareness(obj, cause)
|
||||
}
|
||||
|
||||
/**
|
||||
* Vehicles may have charged shields that absorb damage before the vehicle's own health is affected.
|
||||
* @param target the entity to be damaged
|
||||
* @param applyDamageTo the function that applies the damage to the target in a target-tailored fashion
|
||||
*/
|
||||
override protected def PerformDamage(target : Damageable.Target, applyDamageTo : ResolutionCalculations.Output) : Unit = {
|
||||
val obj = DamageableObject
|
||||
val originalHealth = obj.Health
|
||||
val originalShields = obj.Shields
|
||||
val cause = applyDamageTo(obj)
|
||||
val health = obj.Health
|
||||
val shields = obj.Shields
|
||||
val damageToHealth = originalHealth - health
|
||||
val damageToShields = originalShields - shields
|
||||
if(WillAffectTarget(target, damageToHealth + damageToShields, cause)) {
|
||||
target.History(cause)
|
||||
DamageLog(target, s"BEFORE=$originalHealth/$originalShields, AFTER=$health/$shields, CHANGE=$damageToHealth/$damageToShields")
|
||||
handleDamageToShields = damageToShields > 0
|
||||
HandleDamage(target, cause, damageToHealth + damageToShields)
|
||||
}
|
||||
else {
|
||||
obj.Health = originalHealth
|
||||
obj.Shields = originalShields
|
||||
}
|
||||
}
|
||||
|
||||
override protected def DamageAwareness(target : Target, cause : ResolvedProjectile, amount : Int) : Unit = {
|
||||
val obj = DamageableObject
|
||||
val handleShields = handleDamageToShields
|
||||
handleDamageToShields = false
|
||||
val handleReport = reportDamageToVehicle || amount > 0
|
||||
reportDamageToVehicle = false
|
||||
if(Damageable.CanDamageOrJammer(target, amount, cause)) {
|
||||
super.DamageAwareness(target, cause, amount)
|
||||
}
|
||||
if(handleReport) {
|
||||
DamageableMountable.DamageAwareness(obj, cause)
|
||||
}
|
||||
DamageableVehicle.DamageAwareness(obj, cause, amount, handleShields)
|
||||
}
|
||||
|
||||
override protected def DestructionAwareness(target : Target, cause : ResolvedProjectile) : Unit = {
|
||||
super.DestructionAwareness(target, cause)
|
||||
val obj = DamageableObject
|
||||
DamageableMountable.DestructionAwareness(obj, cause)
|
||||
DamageableVehicle.DestructionAwareness(obj, cause)
|
||||
DamageableWeaponTurret.DestructionAwareness(obj, cause)
|
||||
}
|
||||
}
|
||||
|
||||
object DamageableVehicle {
|
||||
/**
|
||||
* Message for instructing the target's cargo vehicles about a damage source affecting their carrier.
|
||||
* @param cause historical information about damage
|
||||
*/
|
||||
private case class Damage(cause : ResolvedProjectile, amount : Int)
|
||||
/**
|
||||
* Message for instructing the target's cargo vehicles that their carrier is destroyed,
|
||||
* and they should be destroyed too.
|
||||
* @param cause historical information about damage
|
||||
*/
|
||||
private case class Destruction(cause : ResolvedProjectile)
|
||||
|
||||
/**
|
||||
* Most all vehicles and the weapons mounted to them can jam
|
||||
* if the projectile that strikes (near) them has jammering properties.
|
||||
* A damaged carrier alerts its cargo vehicles of the source of the damage,
|
||||
* but it will not be affected by the same jammering effect.
|
||||
* If this vehicle has shields that were affected by previous damage, that is also reported to the clients.
|
||||
* @see `Service.defaultPlayerGUID`
|
||||
* @see `Vehicle.CargoHolds`
|
||||
* @see `VehicleAction.PlanetsideAttribute`
|
||||
* @see `VehicleServiceMessage`
|
||||
* @param target the entity being destroyed
|
||||
* @param cause historical information about the damage
|
||||
* @param damage how much damage was performed
|
||||
* @param damageToShields dispatch a shield strength update
|
||||
*/
|
||||
def DamageAwareness(target : Vehicle, cause : ResolvedProjectile, damage : Int, damageToShields : Boolean) : Unit = {
|
||||
//alert cargo occupants to damage source
|
||||
target.CargoHolds.values.foreach(hold => {
|
||||
hold.Occupant match {
|
||||
case Some(cargo) =>
|
||||
cargo.Actor ! DamageableVehicle.Damage(cause, damage + (if(damageToShields) 1 else 0))
|
||||
case None => ;
|
||||
}
|
||||
})
|
||||
//shields
|
||||
if(damageToShields) {
|
||||
val zone = target.Zone
|
||||
zone.VehicleEvents ! VehicleServiceMessage(s"${target.Actor}", VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, target.GUID, 68, target.Shields))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A destroyed carrier informs its cargo vehicles that they should also be destroyed
|
||||
* for reasons of the same cause being inherited as the source of damage.
|
||||
* Regardless of the amount of damage they carrier takes or some other target would take,
|
||||
* its cargo vehicles die immediately.
|
||||
* The vehicle's shields are zero'd out if they were previously energized
|
||||
* so that the vehicle's corpse does not act like it is still protected by vehicle shields.
|
||||
* Finally, the vehicle is tasked for deconstruction.
|
||||
* @see `Deployment.TryDeploymentChange`
|
||||
* @see `DriveState.Undeploying`
|
||||
* @see `Service.defaultPlayerGUID`
|
||||
* @see `Vehicle.CargoHolds`
|
||||
* @see `VehicleAction.PlanetsideAttribute`
|
||||
* @see `RemoverActor.AddTask`
|
||||
* @see `RemoverActor.ClearSpecific`
|
||||
* @see `VehicleServiceMessage`
|
||||
* @see `VehicleServiceMessage.Decon`
|
||||
* @see `Zone.VehicleEvents`
|
||||
* @param target the entity being destroyed
|
||||
* @param cause historical information about the damage
|
||||
*/
|
||||
def DestructionAwareness(target : Vehicle, cause : ResolvedProjectile) : Unit = {
|
||||
val zone = target.Zone
|
||||
//cargo vehicles die with us
|
||||
target.CargoHolds.values.foreach(hold => {
|
||||
hold.Occupant match {
|
||||
case Some(cargo) =>
|
||||
cargo.Actor ! DamageableVehicle.Destruction(cause)
|
||||
case None => ;
|
||||
}
|
||||
})
|
||||
//special considerations for certain vehicles
|
||||
target.Definition match {
|
||||
case GlobalDefinitions.ams =>
|
||||
target.Actor ! Deployment.TryDeploymentChange(DriveState.Undeploying)
|
||||
case GlobalDefinitions.router =>
|
||||
target.Actor ! Deployment.TryDeploymentChange(DriveState.Undeploying)
|
||||
VehicleService.BeforeUnloadVehicle(target, zone)
|
||||
zone.LocalEvents ! LocalServiceMessage(zone.Id, LocalAction.ToggleTeleportSystem(PlanetSideGUID(0), target, None))
|
||||
case _ => ;
|
||||
}
|
||||
//shields
|
||||
if(target.Shields > 0) {
|
||||
target.Shields = 0
|
||||
zone.VehicleEvents ! VehicleServiceMessage(zone.Id, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, target.GUID, 68, 0))
|
||||
}
|
||||
zone.VehicleEvents ! VehicleServiceMessage.Decon(RemoverActor.ClearSpecific(List(target), zone))
|
||||
zone.VehicleEvents ! VehicleServiceMessage.Decon(RemoverActor.AddTask(target, zone, Some(1 minute)))
|
||||
target.ClearHistory()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
//Copyright (c) 2020 PSForever
|
||||
package net.psforever.objects.serverobject.damage
|
||||
|
||||
import net.psforever.objects.ballistics.ResolvedProjectile
|
||||
import net.psforever.objects.serverobject.turret.{TurretUpgrade, WeaponTurret}
|
||||
import net.psforever.objects.vehicles.MountedWeapons
|
||||
import services.Service
|
||||
import services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||
import services.vehicle.support.TurretUpgrader
|
||||
import services.vehicle.VehicleServiceMessage
|
||||
|
||||
/**
|
||||
* The "control" `Actor` mixin for damage-handling code for `WeaponTurret` objects.
|
||||
*/
|
||||
trait DamageableWeaponTurret extends DamageableEntity {
|
||||
def DamageableObject : Damageable.Target with WeaponTurret
|
||||
|
||||
override protected def DamageAwareness(target : Damageable.Target, cause : ResolvedProjectile, amount : Int) : Unit = {
|
||||
super.DamageAwareness(target, cause, amount)
|
||||
if(amount > 0) {
|
||||
DamageableMountable.DamageAwareness(DamageableObject, cause)
|
||||
}
|
||||
}
|
||||
|
||||
override protected def DestructionAwareness(target : Damageable.Target, cause : ResolvedProjectile) : Unit = {
|
||||
super.DestructionAwareness(target, cause)
|
||||
val obj = DamageableObject
|
||||
DamageableWeaponTurret.DestructionAwareness(obj, cause)
|
||||
DamageableMountable.DestructionAwareness(obj, cause)
|
||||
}
|
||||
}
|
||||
|
||||
object DamageableWeaponTurret {
|
||||
/**
|
||||
* A destroyed target dispatches a message to conceal (delete) its weapons from users.
|
||||
* If affected by a jammer property, the jammer propoerty will be removed.
|
||||
* If the type of entity is a `WeaponTurret`, the weapons are converted to their "normal" upgrade state.
|
||||
* @see `AvatarAction.DeleteObject`
|
||||
* @see `AvatarServiceMessage`
|
||||
* @see `MountedWeapons`
|
||||
* @see `MountedWeapons.Weapons`
|
||||
* @see `Service.defaultPlayerGUID`
|
||||
* @see `TurretUpgrade.None`
|
||||
* @see `TurretUpgrader.AddTask`
|
||||
* @see `TurretUpgrader.ClearSpecific`
|
||||
* @see `WeaponTurret`
|
||||
* @see `VehicleServiceMessage.TurretUpgrade`
|
||||
* @see `Zone.AvatarEvents`
|
||||
* @see `Zone.VehicleEvents`
|
||||
* @param target the entity being destroyed;
|
||||
* note: `MountedWeapons` is a parent of `WeaponTurret`
|
||||
* but the handling code closely associates with the former
|
||||
* @param cause historical information about the damage
|
||||
*/
|
||||
def DestructionAwareness(target : Damageable.Target with MountedWeapons, cause : ResolvedProjectile) : Unit = {
|
||||
//wreckage has no (visible) mounted weapons
|
||||
val zone = target.Zone
|
||||
val zoneId = zone.Id
|
||||
val avatarEvents = zone.AvatarEvents
|
||||
target.Weapons.values
|
||||
.filter {
|
||||
_.Equipment.nonEmpty
|
||||
}
|
||||
.foreach(slot => {
|
||||
val wep = slot.Equipment.get
|
||||
avatarEvents ! AvatarServiceMessage(zoneId, AvatarAction.ObjectDelete(Service.defaultPlayerGUID, wep.GUID))
|
||||
})
|
||||
target match {
|
||||
case turret : WeaponTurret =>
|
||||
if(turret.Upgrade != TurretUpgrade.None) {
|
||||
val vehicleEvents = zone.VehicleEvents
|
||||
vehicleEvents ! VehicleServiceMessage.TurretUpgrade(TurretUpgrader.ClearSpecific(List(turret), zone))
|
||||
vehicleEvents ! VehicleServiceMessage.TurretUpgrade(TurretUpgrader.AddTask(turret, zone, TurretUpgrade.None))
|
||||
}
|
||||
case _ =>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,7 +4,6 @@ package net.psforever.objects.serverobject.doors
|
|||
import net.psforever.objects.Player
|
||||
import net.psforever.objects.serverobject.structures.Amenity
|
||||
import net.psforever.packet.game.UseItemMessage
|
||||
import net.psforever.types.Vector3
|
||||
|
||||
/**
|
||||
* A structure-owned server object that is a "door" that can open and can close.
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package net.psforever.objects.serverobject.doors
|
||||
|
||||
import net.psforever.objects.definition.ObjectDefinition
|
||||
import net.psforever.objects.serverobject.structures.AmenityDefinition
|
||||
|
||||
/**
|
||||
* The definition for any `Door`.
|
||||
* Object Id 242 is a generic door.
|
||||
*/
|
||||
class DoorDefinition extends ObjectDefinition(242) {
|
||||
class DoorDefinition extends AmenityDefinition(242) {
|
||||
Name = "door"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
package net.psforever.objects.serverobject.generator
|
||||
|
||||
import net.psforever.objects.serverobject.structures.Amenity
|
||||
import net.psforever.types.PlanetSideGeneratorState
|
||||
|
||||
/**
|
||||
* The generator is a big feature of all major facilities.
|
||||
|
|
@ -14,7 +15,15 @@ import net.psforever.objects.serverobject.structures.Amenity
|
|||
* @param gdef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields
|
||||
*/
|
||||
class Generator(private val gdef : GeneratorDefinition) extends Amenity {
|
||||
//TODO should have Vitality, to indicate damaged/destroyed property
|
||||
private var condition : PlanetSideGeneratorState.Value = PlanetSideGeneratorState.Normal
|
||||
|
||||
def Condition : PlanetSideGeneratorState.Value = condition
|
||||
|
||||
def Condition_=(state : PlanetSideGeneratorState.Value) : PlanetSideGeneratorState.Value = {
|
||||
condition = state
|
||||
Condition
|
||||
}
|
||||
|
||||
def Definition : GeneratorDefinition = gdef
|
||||
}
|
||||
|
||||
|
|
@ -24,15 +33,6 @@ object Generator {
|
|||
}
|
||||
|
||||
import akka.actor.ActorContext
|
||||
def Constructor(id : Int, context : ActorContext) : Generator = {
|
||||
import akka.actor.Props
|
||||
import net.psforever.objects.GlobalDefinitions
|
||||
|
||||
val obj = Generator(GlobalDefinitions.generator)
|
||||
obj.Actor = context.actorOf(Props(classOf[GeneratorControl], obj), s"${obj.Definition.Name}_$id")
|
||||
obj
|
||||
}
|
||||
|
||||
import net.psforever.types.Vector3
|
||||
def Constructor(pos : Vector3)(id : Int, context : ActorContext) : Generator = {
|
||||
import akka.actor.Props
|
||||
|
|
|
|||
|
|
@ -2,17 +2,147 @@
|
|||
package net.psforever.objects.serverobject.generator
|
||||
|
||||
import akka.actor.Actor
|
||||
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
|
||||
import net.psforever.objects.{Player, Tool}
|
||||
import net.psforever.objects.ballistics._
|
||||
import net.psforever.objects.serverobject.affinity.FactionAffinityBehavior
|
||||
import net.psforever.objects.serverobject.damage.Damageable.Target
|
||||
import net.psforever.objects.serverobject.damage.DamageableEntity
|
||||
import net.psforever.objects.serverobject.repair.{Repairable, RepairableEntity}
|
||||
import net.psforever.objects.serverobject.structures.Building
|
||||
import net.psforever.objects.vital.DamageFromExplosion
|
||||
import net.psforever.packet.game.TriggerEffectMessage
|
||||
import net.psforever.types.{PlanetSideGeneratorState, Vector3}
|
||||
import services.Service
|
||||
import services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||
|
||||
/**
|
||||
* An `Actor` that handles messages being dispatched to a specific `Generator`.
|
||||
* @param gen the `Generator` object being governed
|
||||
*/
|
||||
class GeneratorControl(gen : Generator) extends Actor
|
||||
with FactionAffinityBehavior.Check {
|
||||
def FactionObject : FactionAffinity = gen
|
||||
with FactionAffinityBehavior.Check
|
||||
with DamageableEntity
|
||||
with RepairableEntity {
|
||||
def FactionObject = gen
|
||||
def DamageableObject = gen
|
||||
def RepairableObject = gen
|
||||
var imminentExplosion : Boolean = false
|
||||
|
||||
def receive : Receive = checkBehavior.orElse {
|
||||
case _ => ;
|
||||
def receive : Receive = checkBehavior
|
||||
.orElse(takesDamage)
|
||||
.orElse(canBeRepairedByNanoDispenser)
|
||||
.orElse {
|
||||
case GeneratorControl.GeneratorExplodes() => //TODO this only works with projectiles right now!
|
||||
val zone = gen.Zone
|
||||
gen.Health = 0
|
||||
super.DestructionAwareness(gen, gen.LastShot.get)
|
||||
gen.Condition = PlanetSideGeneratorState.Destroyed
|
||||
GeneratorControl.UpdateOwner(gen)
|
||||
//kaboom
|
||||
zone.AvatarEvents ! AvatarServiceMessage(
|
||||
zone.Id, AvatarAction.SendResponse(
|
||||
Service.defaultPlayerGUID, TriggerEffectMessage(gen.GUID, "explosion_generator", None, None)
|
||||
)
|
||||
)
|
||||
imminentExplosion = false
|
||||
//kill everyone within 14m
|
||||
gen.Owner match {
|
||||
case b : Building =>
|
||||
val genDef = gen.Definition
|
||||
b.PlayersInSOI.collect {
|
||||
case player if player.isAlive && Vector3.DistanceSquared(player.Position, gen.Position) < 196 =>
|
||||
player.History(DamageFromExplosion(PlayerSource(player), genDef))
|
||||
player.Actor ! Player.Die()
|
||||
}
|
||||
case _ => ;
|
||||
}
|
||||
gen.ClearHistory()
|
||||
|
||||
case _ => ;
|
||||
}
|
||||
|
||||
override protected def CanPerformRepairs(obj : Target, player : Player, item : Tool) : Boolean = {
|
||||
!imminentExplosion && super.CanPerformRepairs(obj, player, item)
|
||||
}
|
||||
|
||||
override protected def WillAffectTarget(target : Target, damage : Int, cause : ResolvedProjectile) : Boolean = {
|
||||
!imminentExplosion && super.WillAffectTarget(target, damage, cause)
|
||||
}
|
||||
|
||||
override protected def DamageAwareness(target : Target, cause : ResolvedProjectile, amount : Int) : Unit = {
|
||||
super.DamageAwareness(target, cause, amount)
|
||||
GeneratorControl.DamageAwareness(gen, cause, amount)
|
||||
}
|
||||
|
||||
override protected def DestructionAwareness(target : Target, cause : ResolvedProjectile) : Unit = {
|
||||
if(!target.Destroyed) {
|
||||
target.Health = 1 //temporary
|
||||
imminentExplosion = true
|
||||
import scala.concurrent.duration._
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
context.system.scheduler.scheduleOnce(10 seconds, self, GeneratorControl.GeneratorExplodes())
|
||||
GeneratorControl.BroadcastGeneratorEvent(gen, 16)
|
||||
}
|
||||
}
|
||||
|
||||
override def Restoration(obj : Repairable.Target) : Unit = {
|
||||
super.Restoration(obj)
|
||||
gen.Condition = PlanetSideGeneratorState.Normal
|
||||
GeneratorControl.UpdateOwner(gen)
|
||||
GeneratorControl.BroadcastGeneratorEvent(gen, 17)
|
||||
}
|
||||
}
|
||||
|
||||
object GeneratorControl {
|
||||
/**
|
||||
* na
|
||||
*/
|
||||
private case class GeneratorExplodes()
|
||||
|
||||
/**
|
||||
* na
|
||||
* @param obj na
|
||||
*/
|
||||
private def UpdateOwner(obj : Generator) : Unit = {
|
||||
obj.Owner match {
|
||||
case b : Building => b.Actor ! Building.AmenityStateChange(obj)
|
||||
case _ => ;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* na
|
||||
* @param target the generator
|
||||
* @param event the action code for the event
|
||||
*/
|
||||
private def BroadcastGeneratorEvent(target : Generator, event : Int) : Unit = {
|
||||
target.Owner match {
|
||||
case b : Building =>
|
||||
val events = target.Zone.AvatarEvents
|
||||
val msg = AvatarAction.GenericObjectAction(Service.defaultPlayerGUID, target.Owner.GUID, event)
|
||||
b.PlayersInSOI.foreach { player =>
|
||||
events ! AvatarServiceMessage(player.Name, msg)
|
||||
}
|
||||
case _ => ;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If not destroyed, it will complain about being damaged.
|
||||
* @param target the entity being damaged
|
||||
* @param cause historical information about the damage
|
||||
* @param amount the amount of damage
|
||||
*/
|
||||
def DamageAwareness(target : Generator, cause : ResolvedProjectile, amount : Int) : Unit = {
|
||||
if(!target.Destroyed) {
|
||||
val health : Float = target.Health
|
||||
val max : Float = target.MaxHealth
|
||||
if(target.Condition != PlanetSideGeneratorState.Critical && health / max < 0.51f) { //becoming critical
|
||||
target.Condition = PlanetSideGeneratorState.Critical
|
||||
GeneratorControl.UpdateOwner(target)
|
||||
}
|
||||
//the generator is under attack
|
||||
GeneratorControl.BroadcastGeneratorEvent(target, 15)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
// Copyright (c) 2020 PSForever
|
||||
package net.psforever.objects.serverobject.generator
|
||||
|
||||
import net.psforever.objects.definition.ObjectDefinition
|
||||
import net.psforever.objects.serverobject.structures.AmenityDefinition
|
||||
|
||||
/**
|
||||
* The definition for a `Generator` object.
|
||||
*/
|
||||
class GeneratorDefinition(objectId : Int) extends ObjectDefinition(objectId) {
|
||||
class GeneratorDefinition(objectId : Int) extends AmenityDefinition(objectId) {
|
||||
Name = "generator"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,120 @@
|
|||
// Copyright (c) 2020 PSForever
|
||||
package net.psforever.objects.serverobject.hackable
|
||||
|
||||
import net.psforever.objects.{Player, Vehicle}
|
||||
import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject}
|
||||
import net.psforever.packet.game.{HackMessage, HackState}
|
||||
import net.psforever.types.PlanetSideGUID
|
||||
import services.Service
|
||||
import services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||
import services.local.{LocalAction, LocalServiceMessage}
|
||||
|
||||
import scala.util.{Failure, Success}
|
||||
|
||||
object GenericHackables {
|
||||
private val log = org.log4s.getLogger("HackableBehavior")
|
||||
|
||||
/**
|
||||
* na
|
||||
* @param player the player doing the hacking
|
||||
* @param obj the object being hacked
|
||||
* @return the percentage amount of progress per tick
|
||||
*/
|
||||
def GetHackSpeed(player : Player, obj: PlanetSideServerObject): Float = {
|
||||
val playerHackLevel = Player.GetHackLevel(player)
|
||||
val timeToHack = obj match {
|
||||
case vehicle : Vehicle => vehicle.JackingDuration(playerHackLevel)
|
||||
case hackable : Hackable => hackable.HackDuration(playerHackLevel)
|
||||
case _ =>
|
||||
log.warn(s"${player.Name} tried to hack an object that has no hack time defined - ${obj.Definition.Name}#${obj.GUID} on ${obj.Zone.Id}")
|
||||
0
|
||||
}
|
||||
if(timeToHack == 0) {
|
||||
log.warn(s"${player.Name} tried to hack an object that they don't have the correct hacking level for - ${obj.Definition.Name}#${obj.GUID} on ${obj.Zone.Id}")
|
||||
0f
|
||||
}
|
||||
else {
|
||||
//timeToHack is in seconds; progress is measured in quarters of a second (250ms)
|
||||
(100 / timeToHack) / 4
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate the progress of the user applying a tool to modify some server object.
|
||||
* This action is using the remote electronics kit to convert an enemy unit into an allied unit, primarily.
|
||||
* The act of transforming allied units of one kind into allied units of another kind (facility turret upgrades)
|
||||
* is also governed by this action per tick of progress.
|
||||
* @see `HackMessage`
|
||||
* @see `HackState`
|
||||
* @param progressType 1 - remote electronics kit hack (various ...);
|
||||
* 2 - nano dispenser (upgrade canister) turret upgrade
|
||||
* @param tplayer the player performing the action
|
||||
* @param target the object being affected
|
||||
* @param tool_guid the tool being used to affest the object
|
||||
* @param progress the current progress value
|
||||
* @return `true`, if the next cycle of progress should occur;
|
||||
* `false`, otherwise
|
||||
*/
|
||||
def HackingTickAction(progressType : Int, tplayer : Player, target : PlanetSideServerObject, tool_guid : PlanetSideGUID)(progress : Float) : Boolean = {
|
||||
//hack state for progress bar visibility
|
||||
val vis = if(progress <= 0L) {
|
||||
HackState.Start
|
||||
}
|
||||
else if(progress >= 100L) {
|
||||
HackState.Finished
|
||||
}
|
||||
else if(target.isMoving(1f)) {
|
||||
// If the object is moving (more than slightly to account for things like magriders rotating, or the last velocity reported being the magrider dipping down on dismount) then cancel the hack
|
||||
HackState.Cancelled
|
||||
}
|
||||
else {
|
||||
HackState.Ongoing
|
||||
}
|
||||
target.Zone.AvatarEvents ! AvatarServiceMessage(
|
||||
tplayer.Name,
|
||||
AvatarAction.SendResponse(Service.defaultPlayerGUID,
|
||||
if(!target.HasGUID) {
|
||||
//cancel the hack (target is gone)
|
||||
HackMessage(progressType, target.GUID, tplayer.GUID, 0, 0L, HackState.Cancelled, 8L)
|
||||
}
|
||||
else if(vis == HackState.Cancelled) {
|
||||
//cancel the hack (e.g. vehicle drove away)
|
||||
HackMessage(progressType, target.GUID, tplayer.GUID, 0, 0L, vis, 8L)
|
||||
}
|
||||
else {
|
||||
HackMessage(progressType, target.GUID, tplayer.GUID, progress.toInt, 0L, vis, 8L)
|
||||
}
|
||||
)
|
||||
)
|
||||
vis != HackState.Cancelled
|
||||
}
|
||||
|
||||
/**
|
||||
* The process of hacking an object is completed.
|
||||
* Pass the message onto the hackable object and onto the local events system.
|
||||
* @param target the `Hackable` object that has been hacked
|
||||
* @param user the player that is performing this hacking task
|
||||
* @param unk na;
|
||||
* used by `HackMessage` as `unk5`
|
||||
* @see `HackMessage`
|
||||
*/
|
||||
//TODO add params here depending on which params in HackMessage are important
|
||||
def FinishHacking(target : PlanetSideServerObject with Hackable, user : Player, unk : Long)() : Unit = {
|
||||
import akka.pattern.ask
|
||||
import scala.concurrent.duration._
|
||||
log.info(s"Hacked a $target")
|
||||
// Wait for the target actor to set the HackedBy property, otherwise LocalAction.HackTemporarily will not complete properly
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
val tplayer = user
|
||||
ask(target.Actor, CommonMessages.Hack(tplayer, target))(1 second).mapTo[Boolean].onComplete {
|
||||
case Success(_) =>
|
||||
val zone = target.Zone
|
||||
val zoneId = zone.Id
|
||||
val pguid = tplayer.GUID
|
||||
zone.LocalEvents ! LocalServiceMessage(zoneId, LocalAction.TriggerSound(pguid, target.HackSound, tplayer.Position, 30, 0.49803925f))
|
||||
zone.LocalEvents ! LocalServiceMessage(zoneId, LocalAction.HackTemporarily(pguid, zone, target, unk, target.HackEffectDuration(Player.GetHackLevel(user))))
|
||||
case Failure(_) => log.warn(s"Hack message failed on target guid: ${target.GUID}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2,11 +2,12 @@ package net.psforever.objects.serverobject.hackable
|
|||
|
||||
import net.psforever.objects.Player
|
||||
import net.psforever.objects.serverobject.affinity.FactionAffinity
|
||||
import net.psforever.objects.serverobject.hackable.Hackable.HackInfo
|
||||
import net.psforever.packet.game.TriggeredSound
|
||||
import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID, Vector3}
|
||||
|
||||
trait Hackable extends FactionAffinity {
|
||||
import Hackable._
|
||||
trait Hackable {
|
||||
_ : FactionAffinity =>
|
||||
/** inportant information regarding the hack and how it was started */
|
||||
private var hackedBy : Option[HackInfo] = None
|
||||
def HackedBy : Option[HackInfo] = hackedBy
|
||||
|
|
@ -66,6 +67,18 @@ trait Hackable extends FactionAffinity {
|
|||
hackDuration = arr
|
||||
arr
|
||||
}
|
||||
|
||||
// private var hackable : Option[Boolean] = None
|
||||
// def Hackable : Boolean = hackable.getOrElse(Definition.Hackable)
|
||||
//
|
||||
// def Hackable_=(state : Boolean) : Boolean = Hackable_=(Some(state))
|
||||
//
|
||||
// def Hackable_=(state : Option[Boolean]) : Boolean = {
|
||||
// hackable = state
|
||||
// Hackable
|
||||
// }
|
||||
//
|
||||
// def Definition : HackableDefinition
|
||||
}
|
||||
|
||||
object Hackable {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package net.psforever.objects.serverobject.hackable
|
||||
|
||||
import akka.actor.Actor
|
||||
|
|
@ -6,19 +7,19 @@ import net.psforever.objects.serverobject.CommonMessages
|
|||
object HackableBehavior {
|
||||
/**
|
||||
* The logic governing generic `Hackable` objects that use the `Hack` and `ClearHack` message.
|
||||
* This is a mix-in trait for combining with existing Receive` logic.
|
||||
* This is a mix-in trait for combining with existing `Receive` logic.
|
||||
* @see `Hackable`
|
||||
*/
|
||||
trait GenericHackable {
|
||||
this : Actor =>
|
||||
|
||||
def HackableObject : Hackable
|
||||
|
||||
val hackableBehavior : Receive = {
|
||||
case CommonMessages.Hack(player) =>
|
||||
case CommonMessages.Hack(player, _, _) =>
|
||||
val obj = HackableObject
|
||||
obj.HackedBy = player
|
||||
sender ! true
|
||||
|
||||
case CommonMessages.ClearHack() =>
|
||||
val obj = HackableObject
|
||||
obj.HackedBy = None
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
// Copyright (c) 2020 PSForever
|
||||
package net.psforever.objects.serverobject.hackable
|
||||
|
||||
class HackableDefinition {
|
||||
private var hackable : Boolean = false
|
||||
private var magicNumber : Long = 0
|
||||
|
||||
def Hackable : Boolean = hackable
|
||||
|
||||
def Hackable_=(state : Boolean) : Boolean = {
|
||||
hackable = state
|
||||
Hackable
|
||||
}
|
||||
|
||||
def MagicNumber : Long = magicNumber
|
||||
|
||||
def MagicNumber_=(magic : Long) : Long = {
|
||||
magicNumber = magic
|
||||
MagicNumber
|
||||
}
|
||||
}
|
||||
|
|
@ -2,12 +2,12 @@
|
|||
package net.psforever.objects.serverobject.implantmech
|
||||
|
||||
import net.psforever.objects.Player
|
||||
import net.psforever.objects.definition.ObjectDefinition
|
||||
import net.psforever.objects.serverobject.hackable.Hackable
|
||||
import net.psforever.objects.serverobject.mount.Mountable
|
||||
import net.psforever.objects.serverobject.structures.Amenity
|
||||
import net.psforever.objects.vehicles.Seat
|
||||
import net.psforever.packet.game.TriggeredSound
|
||||
import net.psforever.types.Vector3
|
||||
|
||||
/**
|
||||
* A structure-owned server object that is the visible and `Mountable` component of an implant terminal.
|
||||
|
|
@ -38,7 +38,7 @@ class ImplantTerminalMech(private val idef : ImplantTerminalMechDefinition) exte
|
|||
}
|
||||
}
|
||||
|
||||
def Definition : ObjectDefinition = idef
|
||||
def Definition : ImplantTerminalMechDefinition = idef
|
||||
}
|
||||
|
||||
object ImplantTerminalMech {
|
||||
|
|
@ -53,10 +53,21 @@ object ImplantTerminalMech {
|
|||
import akka.actor.ActorContext
|
||||
/**
|
||||
* Instantiate an configure a `ImplantTerminalMech` object
|
||||
* @param pos the position of the entity
|
||||
* @param id the unique id that will be assigned to this entity
|
||||
* @param context a context to allow the object to properly set up `ActorSystem` functionality
|
||||
* @return the `ImplantTerminalMech` object
|
||||
*/
|
||||
def Constructor(pos : Vector3)(id : Int, context : ActorContext) : ImplantTerminalMech = {
|
||||
import akka.actor.Props
|
||||
import net.psforever.objects.GlobalDefinitions
|
||||
|
||||
val obj = ImplantTerminalMech(GlobalDefinitions.implant_terminal_mech)
|
||||
obj.Position = pos
|
||||
obj.Actor = context.actorOf(Props(classOf[ImplantTerminalMechControl], obj), s"${GlobalDefinitions.implant_terminal_mech.Name}_$id")
|
||||
obj
|
||||
}
|
||||
@deprecated("use implant terminal mechs that have position","destroyAndRepair")
|
||||
def Constructor(id : Int, context : ActorContext) : ImplantTerminalMech = {
|
||||
import akka.actor.Props
|
||||
import net.psforever.objects.GlobalDefinitions
|
||||
|
|
|
|||
|
|
@ -2,26 +2,73 @@
|
|||
package net.psforever.objects.serverobject.implantmech
|
||||
|
||||
import akka.actor.Actor
|
||||
import net.psforever.objects.serverobject.mount.MountableBehavior
|
||||
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
|
||||
import net.psforever.objects.ballistics.ResolvedProjectile
|
||||
import net.psforever.objects.{GlobalDefinitions, Player, SimpleItem}
|
||||
import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject}
|
||||
import net.psforever.objects.serverobject.mount.{Mountable, MountableBehavior}
|
||||
import net.psforever.objects.serverobject.affinity.FactionAffinityBehavior
|
||||
import net.psforever.objects.serverobject.damage.Damageable.Target
|
||||
import net.psforever.objects.serverobject.damage.{Damageable, DamageableEntity, DamageableMountable}
|
||||
import net.psforever.objects.serverobject.hackable.HackableBehavior
|
||||
import net.psforever.objects.serverobject.repair.RepairableEntity
|
||||
import net.psforever.objects.serverobject.structures.Building
|
||||
|
||||
/**
|
||||
* An `Actor` that handles messages being dispatched to a specific `ImplantTerminalMech`.
|
||||
* @param mech the "mech" object being governed
|
||||
*/
|
||||
class ImplantTerminalMechControl(mech : ImplantTerminalMech) extends Actor with FactionAffinityBehavior.Check
|
||||
with MountableBehavior.Mount with MountableBehavior.Dismount with HackableBehavior.GenericHackable {
|
||||
def MountableObject = mech //do not add type!
|
||||
class ImplantTerminalMechControl(mech : ImplantTerminalMech) extends Actor
|
||||
with FactionAffinityBehavior.Check
|
||||
with MountableBehavior.Mount
|
||||
with MountableBehavior.Dismount
|
||||
with HackableBehavior.GenericHackable
|
||||
with DamageableEntity
|
||||
with RepairableEntity {
|
||||
def MountableObject = mech
|
||||
def HackableObject = mech
|
||||
|
||||
def FactionObject : FactionAffinity = mech
|
||||
def FactionObject = mech
|
||||
def DamageableObject = mech
|
||||
def RepairableObject = mech
|
||||
|
||||
def receive : Receive = checkBehavior
|
||||
.orElse(mountBehavior)
|
||||
.orElse(dismountBehavior)
|
||||
.orElse(hackableBehavior)
|
||||
.orElse(takesDamage)
|
||||
.orElse(canBeRepairedByNanoDispenser)
|
||||
.orElse {
|
||||
case CommonMessages.Use(player, Some(item : SimpleItem)) if item.Definition == GlobalDefinitions.remote_electronics_kit =>
|
||||
//TODO setup certifications check
|
||||
mech.Owner match {
|
||||
case b : Building if (b.Faction != player.Faction || b.CaptureConsoleIsHacked) && mech.HackedBy.isEmpty =>
|
||||
sender ! CommonMessages.Hack(player, mech, Some(item))
|
||||
case _ => ;
|
||||
}
|
||||
case _ => ;
|
||||
}
|
||||
|
||||
override protected def MountTest(obj : PlanetSideServerObject with Mountable, seatNumber : Int, player : Player) : Boolean = {
|
||||
val zone = obj.Zone
|
||||
zone.Map.TerminalToInterface.get(obj.GUID.guid) match {
|
||||
case Some(interface_guid) =>
|
||||
(zone.GUID(interface_guid) match {
|
||||
case Some(interface) => !interface.Destroyed
|
||||
case None => false
|
||||
}) &&
|
||||
super.MountTest(obj, seatNumber, player)
|
||||
case None =>
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
override protected def DamageAwareness(target : Target, cause : ResolvedProjectile, amount : Int) : Unit = {
|
||||
super.DamageAwareness(target, cause, amount)
|
||||
DamageableMountable.DamageAwareness(DamageableObject, cause)
|
||||
}
|
||||
|
||||
override protected def DestructionAwareness(target : Damageable.Target, cause : ResolvedProjectile) : Unit = {
|
||||
super.DestructionAwareness(target, cause)
|
||||
DamageableMountable.DestructionAwareness(DamageableObject, cause)
|
||||
target.ClearHistory()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package net.psforever.objects.serverobject.implantmech
|
||||
|
||||
import net.psforever.objects.definition.{ObjectDefinition, SeatDefinition}
|
||||
import net.psforever.objects.vehicles.SeatArmorRestriction
|
||||
import net.psforever.objects.definition.SeatDefinition
|
||||
import net.psforever.objects.serverobject.structures.AmenityDefinition
|
||||
|
||||
/**
|
||||
* The `Definition` for any `Terminal` that is of a type "implant_terminal_interface."
|
||||
* Implant terminals are composed of two components.
|
||||
* This `Definition` constructs the visible mechanical tube component that can be mounted.
|
||||
*/
|
||||
class ImplantTerminalMechDefinition extends ObjectDefinition(410) {
|
||||
class ImplantTerminalMechDefinition extends AmenityDefinition(410) {
|
||||
/* key - seat index, value - seat object */
|
||||
private val seats : Map[Int, SeatDefinition] = Map(0 -> new SeatDefinition)
|
||||
/* key - entry point index, value - seat index */
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
package net.psforever.objects.serverobject.locks
|
||||
|
||||
import akka.actor.Actor
|
||||
import net.psforever.objects.{GlobalDefinitions, SimpleItem}
|
||||
import net.psforever.objects.serverobject.CommonMessages
|
||||
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
|
||||
import net.psforever.objects.serverobject.hackable.HackableBehavior
|
||||
|
|
@ -18,6 +19,17 @@ class IFFLockControl(lock : IFFLock) extends Actor with FactionAffinityBehavior.
|
|||
def receive : Receive = checkBehavior
|
||||
.orElse(hackableBehavior)
|
||||
.orElse {
|
||||
case CommonMessages.Use(player, Some(item : SimpleItem)) if item.Definition == GlobalDefinitions.remote_electronics_kit =>
|
||||
if((lock.Faction != player.Faction && lock.HackedBy.isEmpty) || (lock.Faction == player.Faction && lock.HackedBy.nonEmpty)) {
|
||||
sender ! CommonMessages.Hack(player, lock, Some(item))
|
||||
}
|
||||
else {
|
||||
val log = org.log4s.getLogger
|
||||
log.warn("IFF lock is being hacked, but don't know how to handle this state:")
|
||||
log.warn(s"Lock - Faction=${lock.Faction}, HackedBy=${lock.HackedBy}")
|
||||
log.warn(s"Player - Faction=${player.Faction}")
|
||||
}
|
||||
|
||||
case _ => ; //no default message
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package net.psforever.objects.serverobject.locks
|
||||
|
||||
import net.psforever.objects.definition.ObjectDefinition
|
||||
import net.psforever.objects.serverobject.structures.AmenityDefinition
|
||||
|
||||
/**
|
||||
* The definition for any `IFFLock`.
|
||||
* Object Id 451 is a generic external lock.
|
||||
*/
|
||||
class IFFLockDefinition extends ObjectDefinition(451) {
|
||||
class IFFLockDefinition extends AmenityDefinition(451) {
|
||||
Name = "iff_lock"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
// Copyright (c) 2020 PSForever
|
||||
package net.psforever.objects.serverobject.locks
|
||||
|
||||
import services.Service
|
||||
import services.local.{LocalAction, LocalServiceMessage}
|
||||
|
||||
object IFFLocks {
|
||||
/**
|
||||
* The process of resecuring an IFF lock is finished
|
||||
* Clear the hack state and send to clients
|
||||
* @param lock the `IFFLock` object that has been resecured
|
||||
*/
|
||||
def FinishResecuringIFFLock(lock: IFFLock)() : Unit = {
|
||||
val zone = lock.Zone
|
||||
lock.Zone.LocalEvents ! LocalServiceMessage(zone.Id, LocalAction.ClearTemporaryHack(Service.defaultPlayerGUID, lock))
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,8 @@
|
|||
package net.psforever.objects.serverobject.mblocker
|
||||
|
||||
import akka.actor.Actor
|
||||
import net.psforever.objects.{GlobalDefinitions, SimpleItem}
|
||||
import net.psforever.objects.serverobject.CommonMessages
|
||||
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
|
||||
import net.psforever.objects.serverobject.hackable.HackableBehavior
|
||||
|
||||
|
|
@ -16,6 +18,11 @@ class LockerControl(locker : Locker) extends Actor with FactionAffinityBehavior.
|
|||
def receive : Receive = checkBehavior
|
||||
.orElse(hackableBehavior)
|
||||
.orElse {
|
||||
case CommonMessages.Use(player, Some(item : SimpleItem)) if item.Definition == GlobalDefinitions.remote_electronics_kit =>
|
||||
//TODO setup certifications check
|
||||
if(locker.Faction != player.Faction && locker.HackedBy.isEmpty) {
|
||||
sender ! CommonMessages.Hack(player, locker, Some(item))
|
||||
}
|
||||
case _ => ;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package net.psforever.objects.serverobject.mblocker
|
||||
|
||||
import net.psforever.objects.definition.ObjectDefinition
|
||||
import net.psforever.objects.serverobject.structures.AmenityDefinition
|
||||
|
||||
/**
|
||||
* The definition for any `Locker`.
|
||||
* Object Id 524.
|
||||
*/
|
||||
class LockerDefinition extends ObjectDefinition(524) {
|
||||
class LockerDefinition extends AmenityDefinition(524) {
|
||||
Name = "mb_locker"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,11 +2,12 @@
|
|||
package net.psforever.objects.serverobject.mount
|
||||
|
||||
import akka.actor.Actor
|
||||
import net.psforever.objects.{PlanetSideGameObject, Vehicle}
|
||||
import net.psforever.objects.{Player, Vehicle}
|
||||
import net.psforever.objects.entity.{Identifiable, WorldEntity}
|
||||
import net.psforever.objects.serverobject.PlanetSideServerObject
|
||||
import net.psforever.objects.serverobject.affinity.FactionAffinity
|
||||
import net.psforever.objects.serverobject.hackable.Hackable
|
||||
import net.psforever.objects.serverobject.turret.TurretDefinition
|
||||
import net.psforever.objects.serverobject.turret.WeaponTurret
|
||||
import net.psforever.types.DriveState
|
||||
|
||||
object MountableBehavior {
|
||||
|
|
@ -17,51 +18,50 @@ object MountableBehavior {
|
|||
* @see `Mountable`
|
||||
*/
|
||||
trait Mount {
|
||||
this : Actor =>
|
||||
|
||||
def MountableObject : PlanetSideGameObject with Mountable with FactionAffinity
|
||||
_ : Actor =>
|
||||
def MountableObject : PlanetSideServerObject with Mountable with FactionAffinity
|
||||
|
||||
val mountBehavior : Receive = {
|
||||
case Mountable.TryMount(user, seat_num) =>
|
||||
val obj = MountableObject
|
||||
obj.Seat(seat_num) match {
|
||||
case Some(seat) =>
|
||||
|
||||
var isHacked = false
|
||||
if(MountableObject.isInstanceOf[Hackable]) {
|
||||
// This is a special case for implant terminals, since they're both mountable and hackable, but not jackable.
|
||||
isHacked = MountableObject.asInstanceOf[Hackable].HackedBy.isDefined
|
||||
}
|
||||
|
||||
if((user.Faction == obj.Faction || isHacked) && (seat.Occupant = user).contains(user)) {
|
||||
user.VehicleSeated = obj.GUID
|
||||
sender ! Mountable.MountMessages(user, Mountable.CanMount(obj, seat_num))
|
||||
}
|
||||
else {
|
||||
sender ! Mountable.MountMessages(user, Mountable.CanNotMount(obj, seat_num))
|
||||
}
|
||||
case None =>
|
||||
sender ! Mountable.MountMessages(user, Mountable.CanNotMount(obj, seat_num))
|
||||
if(MountTest(MountableObject, seat_num, user)) {
|
||||
user.VehicleSeated = obj.GUID
|
||||
sender ! Mountable.MountMessages(user, Mountable.CanMount(obj, seat_num))
|
||||
}
|
||||
else {
|
||||
sender ! Mountable.MountMessages(user, Mountable.CanNotMount(obj, seat_num))
|
||||
}
|
||||
}
|
||||
|
||||
val turretMountBehavior : Receive = {
|
||||
case Mountable.TryMount(user, seat_num) =>
|
||||
val obj = MountableObject
|
||||
val definition = obj.Definition.asInstanceOf[TurretDefinition]
|
||||
obj.Seat(seat_num) match {
|
||||
case Some(seat) =>
|
||||
if((!definition.FactionLocked || user.Faction == obj.Faction) &&
|
||||
(seat.Occupant = user).contains(user)) {
|
||||
user.VehicleSeated = obj.GUID
|
||||
sender ! Mountable.MountMessages(user, Mountable.CanMount(obj, seat_num))
|
||||
}
|
||||
else {
|
||||
sender ! Mountable.MountMessages(user, Mountable.CanNotMount(obj, seat_num))
|
||||
}
|
||||
case None =>
|
||||
sender ! Mountable.MountMessages(user, Mountable.CanNotMount(obj, seat_num))
|
||||
}
|
||||
protected def MountTest(obj : PlanetSideServerObject with Mountable, seatNumber : Int, player : Player) : Boolean = {
|
||||
(player.Faction == obj.Faction ||
|
||||
(obj match {
|
||||
case o : Hackable => o.HackedBy.isDefined
|
||||
case _ => false
|
||||
})) &&
|
||||
!obj.Destroyed &&
|
||||
(obj.Seats.get(seatNumber) match {
|
||||
case Some(seat) => (seat.Occupant = player).contains(player)
|
||||
case _ => false
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
trait TurretMount extends Mount {
|
||||
_ : Actor =>
|
||||
|
||||
override protected def MountTest(obj : PlanetSideServerObject with Mountable, seatNumber : Int, player : Player) : Boolean = {
|
||||
obj match {
|
||||
case wep : WeaponTurret =>
|
||||
(!wep.Definition.FactionLocked || player.Faction == obj.Faction) &&
|
||||
!obj.Destroyed &&
|
||||
(obj.Seats.get(seatNumber) match {
|
||||
case Some(seat) => (seat.Occupant = player).contains(player)
|
||||
case _ => false
|
||||
})
|
||||
case _ =>
|
||||
super.MountTest(obj, seatNumber, player)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package net.psforever.objects.serverobject.pad
|
||||
|
||||
import net.psforever.objects.definition.ObjectDefinition
|
||||
import net.psforever.objects.serverobject.structures.AmenityDefinition
|
||||
|
||||
/**
|
||||
* The definition for any `VehicleSpawnPad`.
|
||||
*/
|
||||
class VehicleSpawnPadDefinition(objectId : Int) extends ObjectDefinition(objectId) {
|
||||
class VehicleSpawnPadDefinition(objectId : Int) extends AmenityDefinition(objectId) {
|
||||
|
||||
// Different pads require a Z offset to stop vehicles falling through the world after the pad rises from the floor, these values are found in game_objects.adb.lst
|
||||
private var vehicle_creation_z_offset = 0f
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
package net.psforever.objects.serverobject.painbox
|
||||
|
||||
import net.psforever.objects.definition.ObjectDefinition
|
||||
import net.psforever.objects.serverobject.structures.AmenityDefinition
|
||||
import net.psforever.types.Vector3
|
||||
|
||||
class PainboxDefinition(objectId : Int) extends ObjectDefinition(objectId) {
|
||||
class PainboxDefinition(objectId : Int) extends AmenityDefinition(objectId) {
|
||||
private var alwaysOn : Boolean = true
|
||||
private var radius : Float = 0f
|
||||
private var damage : Int = 10
|
||||
|
|
@ -39,7 +39,7 @@ class PainboxDefinition(objectId : Int) extends ObjectDefinition(objectId) {
|
|||
radius = 8.55f
|
||||
sphereOffset = Vector3.Zero
|
||||
case _ =>
|
||||
throw new IllegalArgumentException(s"${objectId} is not a valid painbox object id")
|
||||
throw new IllegalArgumentException(s"$objectId is not a valid painbox object id")
|
||||
}
|
||||
|
||||
def Radius : Float = radius
|
||||
|
|
|
|||
|
|
@ -0,0 +1,77 @@
|
|||
//Copyright (c) 2020 PSForever
|
||||
package net.psforever.objects.serverobject.repair
|
||||
|
||||
import akka.actor.Actor.Receive
|
||||
import net.psforever.objects.equipment.Ammo
|
||||
import net.psforever.objects.{GlobalDefinitions, Player, Tool}
|
||||
import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject}
|
||||
import net.psforever.objects.vital.Vitality
|
||||
|
||||
/**
|
||||
* The base "control" `Actor` mixin for repair-handling code
|
||||
* related to the nano dispenser tool loaded with an armor repair canister.
|
||||
* Unlike the `Damageable` mixin,
|
||||
* which should be extended to interact with all aspects of a target that impede access to its health points,
|
||||
* shield, armor, etc., `Repairable` only affects `Vitality.Health`.
|
||||
*/
|
||||
trait Repairable {
|
||||
/**
|
||||
* Contextual access to the object being the target of this damage.
|
||||
* Needs declaration in lowest implementing code.
|
||||
* @return the entity controlled by this actor
|
||||
*/
|
||||
def RepairableObject : Repairable.Target
|
||||
|
||||
/**
|
||||
* The official mixin hook; `orElse` onto the "control" `Actor` `receive`;
|
||||
* catch the expected repair message and apply initial checks to the item
|
||||
* @see `Ammo`
|
||||
* @see `CanBeRepairedByNanoDispenser`
|
||||
* @see `CommonMessages.Use`
|
||||
* @see `GlobalDefinitions`
|
||||
* @see `Tool.AmmoType`
|
||||
*/
|
||||
final val canBeRepairedByNanoDispenser : Receive = {
|
||||
case CommonMessages.Use(player, Some(item : Tool))
|
||||
if item.Definition == GlobalDefinitions.nano_dispenser && item.AmmoType == Ammo.armor_canister =>
|
||||
CanBeRepairedByNanoDispenser(player, item)
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of the mixin hook will be provided by a child class.
|
||||
* Override this method only when directly implementing.
|
||||
* @see `canBeRepairedByNanoDispenser`
|
||||
*/
|
||||
def CanBeRepairedByNanoDispenser(player : Player, item : Tool) : Unit
|
||||
|
||||
/**
|
||||
* The amount of repair that any specific tool provides.
|
||||
* @see `Repairable.Quality`
|
||||
* @param item the tool in question
|
||||
* @return an amount to add to the repair attempt progress
|
||||
*/
|
||||
def RepairValue(item : Tool) : Int = 0
|
||||
|
||||
/**
|
||||
* The entity is no longer destroyed.
|
||||
* @param obj the entity
|
||||
*/
|
||||
def Restoration(obj : Repairable.Target) : Unit = {
|
||||
Repairable.Restoration(obj)
|
||||
}
|
||||
}
|
||||
|
||||
object Repairable {
|
||||
/* the type of all entities governed by this mixin; see Damageable.Target */
|
||||
final type Target = PlanetSideServerObject with Vitality
|
||||
/* the basic repair value; originally found on the `armor_canister` object definition */
|
||||
final val Quality : Int = 12
|
||||
|
||||
/**
|
||||
* The entity is no longer destroyed.
|
||||
* @param target the entity
|
||||
*/
|
||||
def Restoration(target : Repairable.Target) : Unit = {
|
||||
target.Destroyed = false
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
//Copyright (c) 2020 PSForever
|
||||
package net.psforever.objects.serverobject.repair
|
||||
|
||||
import net.psforever.objects.serverobject.structures.Amenity
|
||||
import services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||
|
||||
/**
|
||||
* The "control" `Actor` mixin for repair-handling code
|
||||
* for the majority of `Repairable` `Amenity` objects installed in a facility or a field tower.
|
||||
*/
|
||||
trait RepairableAmenity extends RepairableEntity {
|
||||
def RepairableObject : Amenity
|
||||
|
||||
override def Restoration(obj : Repairable.Target) : Unit = {
|
||||
super.Restoration(obj)
|
||||
RepairableAmenity.Restoration(obj)
|
||||
}
|
||||
}
|
||||
|
||||
object RepairableAmenity {
|
||||
/**
|
||||
* A resotred `Amenity` target dispatches two messages to chance its model and operational states.
|
||||
* These `PlanetSideAttributeMessage` attributes are the same as reported during zone load client configuration.
|
||||
* @see `AvatarAction.PlanetsideAttributeToAll`
|
||||
* @see `AvatarServiceMessage`
|
||||
* @see `Zone.AvatarEvents`
|
||||
* @param target the entity being destroyed
|
||||
*/
|
||||
def Restoration(target : Repairable.Target) : Unit = {
|
||||
val zone = target.Zone
|
||||
val zoneId = zone.Id
|
||||
val events = zone.AvatarEvents
|
||||
val targetGUID = target.GUID
|
||||
events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(targetGUID, 50, 0))
|
||||
events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(targetGUID, 51, 0))
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,108 @@
|
|||
//Copyright (c) 2020 PSForever
|
||||
package net.psforever.objects.serverobject.repair
|
||||
|
||||
import net.psforever.objects.{Player, Tool}
|
||||
import net.psforever.packet.game.{InventoryStateMessage, RepairMessage}
|
||||
import net.psforever.types.{PlanetSideEmpire, Vector3}
|
||||
import services.Service
|
||||
import services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||
|
||||
/**
|
||||
* The "control" `Actor` mixin for repair-handling code,
|
||||
* for both expansion into other mixins and specific application on its own.
|
||||
* @see `Player`
|
||||
* @see `Tool`
|
||||
*/
|
||||
trait RepairableEntity extends Repairable {
|
||||
/**
|
||||
* Catch the expected repair message and
|
||||
* apply further checks to the combination of the target, the equipment, and tis user.
|
||||
* If the checks pass, perform the repair.
|
||||
* @param player the user of the nano dispenser tool
|
||||
* @param item the nano dispenser tool
|
||||
*/
|
||||
def CanBeRepairedByNanoDispenser(player : Player, item : Tool) : Unit = {
|
||||
val obj = RepairableObject
|
||||
if(CanPerformRepairs(obj, player, item)) {
|
||||
PerformRepairs(obj, player, item)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the combination of target entity, equipment user, and the equipment
|
||||
* to determine if the repair process attempt would be permitted.
|
||||
* It is not necessary to check that the tool and its ammunition are correct types;
|
||||
* that test was already performed.<br>
|
||||
* <br>
|
||||
* The target entity must be repairable and have less than full health
|
||||
* and, if it is destroyed, must have an object attribute that permits it to be repaired after being destroyed.<br>
|
||||
* The user must have the same faction affinity as the target entity or be neutral.<br>
|
||||
* The equipment must have some ammunition.<br>
|
||||
* The user must be alive and be within a certain distance of the target entity.
|
||||
* @see `org.log4s.getLogger`
|
||||
* @see `PlanetSideEmpire`
|
||||
* @see `Vector3.Distance`
|
||||
* @see `VitalityDefinition`
|
||||
* @param target the entity being repaired
|
||||
* @param player the user of the nano dispenser tool
|
||||
* @param item the nano dispenser tool
|
||||
* @return `true`, if the target entity can be repaired;
|
||||
* `false`, otherwise
|
||||
*/
|
||||
protected def CanPerformRepairs(target : Repairable.Target, player : Player, item : Tool) : Boolean = {
|
||||
val definition = target.Definition
|
||||
definition.Repairable && target.Health < definition.MaxHealth && (definition.RepairIfDestroyed || !target.Destroyed) &&
|
||||
(target.Faction == player.Faction || target.Faction == PlanetSideEmpire.NEUTRAL) && item.Magazine > 0 &&
|
||||
player.isAlive && Vector3.Distance(target.Position, player.Position) < definition.RepairDistance
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the health points change and enact that repair action if the targets are stationary.
|
||||
* Restore the target entity to a not destroyed state if applicable.
|
||||
* Always show the repair progress bar window by using the appropriate packet.
|
||||
* @see `AvatarAction.PlanetsideAttributeToAll`
|
||||
* @see `AvatarAction.SendResponse`
|
||||
* @see `AvatarService`
|
||||
* @see `InventoryStateMessage`
|
||||
* @see `PlanetSideGameObject.isMoving`
|
||||
* @see `RepairMessage`
|
||||
* @see `Service.defaultPlayerGUID`
|
||||
* @see `Tool.Discharge`
|
||||
* @see `Zone.AvatarEvents`
|
||||
* @param target the entity being repaired
|
||||
* @param player the user of the nano dispenser tool
|
||||
* @param item the nano dispenser tool
|
||||
*/
|
||||
protected def PerformRepairs(target : Repairable.Target, player : Player, item : Tool) : Unit = {
|
||||
val definition = target.Definition
|
||||
val zone = target.Zone
|
||||
val events = zone.AvatarEvents
|
||||
val name = player.Name
|
||||
val tguid = target.GUID
|
||||
val originalHealth = target.Health
|
||||
val updatedHealth = if(!(player.isMoving || target.isMoving)) { //only allow stationary repairs
|
||||
val newHealth = target.Health = originalHealth + Repairable.Quality + RepairValue(item) + definition.RepairMod
|
||||
val zoneId = zone.Id
|
||||
val magazine = item.Discharge
|
||||
events ! AvatarServiceMessage(name, AvatarAction.SendResponse(Service.defaultPlayerGUID, InventoryStateMessage(item.AmmoSlot.Box.GUID, item.GUID, magazine.toLong)))
|
||||
if(target.Destroyed) {
|
||||
if(newHealth >= definition.RepairRestoresAt) {
|
||||
events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(tguid, 0, newHealth))
|
||||
Restoration(target)
|
||||
}
|
||||
}
|
||||
else {
|
||||
events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(tguid, 0, newHealth))
|
||||
}
|
||||
newHealth
|
||||
}
|
||||
else {
|
||||
originalHealth
|
||||
}
|
||||
//progress bar remains visible
|
||||
events ! AvatarServiceMessage(name, AvatarAction.SendResponse(Service.defaultPlayerGUID, RepairMessage(tguid, updatedHealth * 100 / definition.MaxHealth)))
|
||||
}
|
||||
|
||||
/* random object repair modifier */
|
||||
override def RepairValue(item : Tool) : Int = item.FireMode.Modifiers.Damage1
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
//Copyright (c) 2020 PSForever
|
||||
package net.psforever.objects.serverobject.repair
|
||||
|
||||
import net.psforever.objects.Tool
|
||||
|
||||
/**
|
||||
* The "control" `Actor` mixin for repair-handling code for `Vehicle` objects.
|
||||
*/
|
||||
trait RepairableVehicle extends RepairableEntity {
|
||||
override def Restoration(obj : Repairable.Target) : Unit = {
|
||||
obj.Health = 0
|
||||
obj.Destroyed = true
|
||||
/* no vanilla vehicles are capable of being restored from destruction */
|
||||
/* if you wanted to properly restore a destroyed vehicle, the quickest way is an ObjectCreateMessage packet */
|
||||
/* additionally, the vehicle deconstruction task must be cancelled */
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
//Copyright (c) 2020 PSForever
|
||||
package net.psforever.objects.serverobject.repair
|
||||
|
||||
import net.psforever.objects.Tool
|
||||
import net.psforever.objects.serverobject.turret.WeaponTurret
|
||||
import net.psforever.objects.vehicles.MountedWeapons
|
||||
import services.Service
|
||||
import services.vehicle.{VehicleAction, VehicleServiceMessage}
|
||||
|
||||
/**
|
||||
* The "control" `Actor` mixin for repair-handling code for `WeaponTurret` objects.
|
||||
*/
|
||||
trait RepairableWeaponTurret extends RepairableEntity {
|
||||
def RepairableObject : Repairable.Target with WeaponTurret
|
||||
|
||||
override def Restoration(target : Repairable.Target) : Unit = {
|
||||
super.Restoration(target)
|
||||
RepairableWeaponTurret.Restoration(RepairableObject)
|
||||
}
|
||||
}
|
||||
|
||||
object RepairableWeaponTurret {
|
||||
/**
|
||||
* A restored target dispatches messages to reconstruct the weapons that were previously mounted to the turret
|
||||
* and may have been concealed/deleted when the target was destroyed.
|
||||
* @see `MountedWeapons`
|
||||
* @see `MountedWeapons.Weapons`
|
||||
* @see `Service.defaultPlayerGUID`
|
||||
* @see `WeaponTurret`
|
||||
* @see `VehicleAction.EquipmentInSlot`
|
||||
* @see `VehicleServiceMessage`
|
||||
* @see `Zone.VehicleEvents`
|
||||
* @param target the entity being destroyed;
|
||||
* note: `MountedWeapons` is a parent of `WeaponTurret`
|
||||
* but the handling code closely associates with the former
|
||||
*/
|
||||
def Restoration(target : Repairable.Target with MountedWeapons) : Unit = {
|
||||
val zone = target.Zone
|
||||
val zoneId = zone.Id
|
||||
val tguid = target.GUID
|
||||
val events = zone.VehicleEvents
|
||||
target.Weapons
|
||||
.map({ case (index, slot) => (index, slot.Equipment) })
|
||||
.collect { case (index, Some(tool : Tool)) =>
|
||||
events ! VehicleServiceMessage(
|
||||
zoneId,
|
||||
VehicleAction.EquipmentInSlot(Service.defaultPlayerGUID, tguid, index, tool)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
package net.psforever.objects.serverobject.resourcesilo
|
||||
|
||||
import akka.actor.{ActorContext, Props}
|
||||
import net.psforever.objects.{GlobalDefinitions, Player, Vehicle}
|
||||
import net.psforever.objects.{GlobalDefinitions, Player}
|
||||
import net.psforever.objects.serverobject.structures.Amenity
|
||||
import net.psforever.packet.game.UseItemMessage
|
||||
|
||||
|
|
|
|||
|
|
@ -30,7 +30,14 @@ class ResourceSiloControl(resourceSilo : ResourceSilo) extends Actor with Factio
|
|||
|
||||
def Processing : Receive = checkBehavior.orElse {
|
||||
case ResourceSilo.Use(player, msg) =>
|
||||
sender ! ResourceSilo.ResourceSiloMessage(player, msg, resourceSilo.Use(player, msg))
|
||||
if(resourceSilo.Faction == PlanetSideEmpire.NEUTRAL || player.Faction == resourceSilo.Faction) {
|
||||
resourceSilo.Zone.Vehicles.find(v => v.PassengerInSeat(player).contains(0)) match {
|
||||
case Some(vehicle) =>
|
||||
sender ! ResourceSilo.ResourceSiloMessage(player, msg, resourceSilo.Use(player, msg))
|
||||
case _ =>
|
||||
}
|
||||
}
|
||||
|
||||
case ResourceSilo.LowNtuWarning(enabled: Boolean) =>
|
||||
resourceSilo.LowNtuWarningOn = enabled
|
||||
log.trace(s"LowNtuWarning: Silo ${resourceSilo.GUID} low ntu warning set to $enabled")
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package net.psforever.objects.serverobject.resourcesilo
|
||||
|
||||
import net.psforever.objects.definition.ObjectDefinition
|
||||
import net.psforever.objects.serverobject.structures.AmenityDefinition
|
||||
|
||||
/**
|
||||
* The definition for any `Resource Silo`.
|
||||
* Object Id 731.
|
||||
*/
|
||||
class ResourceSiloDefinition extends ObjectDefinition(731) {
|
||||
class ResourceSiloDefinition extends AmenityDefinition(731) {
|
||||
Name = "resource_silo"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package net.psforever.objects.serverobject.structures
|
||||
|
||||
import net.psforever.objects.ballistics.ResolvedProjectile
|
||||
import net.psforever.objects.serverobject.PlanetSideServerObject
|
||||
import net.psforever.objects.vital.{DamageResistanceModel, StandardResistanceProfile, Vitality, VitalsActivity}
|
||||
import net.psforever.objects.zones.{Zone, ZoneAware}
|
||||
import net.psforever.types.{PlanetSideEmpire, Vector3}
|
||||
import net.psforever.objects.zones.{ Zone => World }
|
||||
import net.psforever.objects.zones.{Zone => World}
|
||||
|
||||
/**
|
||||
* Amenities are elements of the game that belong to other elements of the game.<br>
|
||||
|
|
@ -14,9 +16,13 @@ import net.psforever.objects.zones.{ Zone => World }
|
|||
* This association strips away at the internalization and redirects a reference to some properties somewhere else.
|
||||
* An `Amenity` object belongs to its `Owner` object;
|
||||
* the `Amenity` objects look to its `Owner` object for some of its properties.
|
||||
* @see `AmenityOwner`
|
||||
* @see `FactionAffinity`
|
||||
*/
|
||||
abstract class Amenity extends PlanetSideServerObject with ZoneAware {
|
||||
abstract class Amenity extends PlanetSideServerObject
|
||||
with Vitality
|
||||
with ZoneAware
|
||||
with StandardResistanceProfile {
|
||||
private[this] val log = org.log4s.getLogger("Amenity")
|
||||
/** what other entity has authority over this amenity; usually either a building or a vehicle */
|
||||
private var owner : AmenityOwner = Building.NoBuilding
|
||||
|
|
@ -71,4 +77,8 @@ abstract class Amenity extends PlanetSideServerObject with ZoneAware {
|
|||
}
|
||||
LocationOffset
|
||||
}
|
||||
|
||||
def DamageModel = Definition.asInstanceOf[DamageResistanceModel]
|
||||
|
||||
def Definition : AmenityDefinition
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
// Copyright (c) 2020 PSForever
|
||||
package net.psforever.objects.serverobject.structures
|
||||
|
||||
import net.psforever.objects.definition.ObjectDefinition
|
||||
import net.psforever.objects.vital.{DamageResistanceModel, StandardAmenityDamage, StandardAmenityResistance, StandardResolutions, VitalityDefinition}
|
||||
import net.psforever.objects.vital.resistance.ResistanceProfileMutators
|
||||
|
||||
abstract class AmenityDefinition(objectId : Int) extends ObjectDefinition(objectId)
|
||||
with ResistanceProfileMutators
|
||||
with DamageResistanceModel
|
||||
with VitalityDefinition {
|
||||
Name = "amenity"
|
||||
DamageUsing = StandardAmenityDamage
|
||||
ResistUsing = StandardAmenityResistance
|
||||
Model = StandardResolutions.Amenities
|
||||
}
|
||||
|
|
@ -6,14 +6,15 @@ import java.util.concurrent.TimeUnit
|
|||
import akka.actor.{ActorContext, ActorRef}
|
||||
import net.psforever.objects.{GlobalDefinitions, Player}
|
||||
import net.psforever.objects.definition.ObjectDefinition
|
||||
import net.psforever.objects.serverobject.generator.Generator
|
||||
import net.psforever.objects.serverobject.hackable.Hackable
|
||||
import net.psforever.objects.serverobject.painbox.Painbox
|
||||
import net.psforever.objects.serverobject.resourcesilo.ResourceSilo
|
||||
import net.psforever.objects.serverobject.terminals.CaptureTerminal
|
||||
import net.psforever.objects.serverobject.tube.SpawnTube
|
||||
import net.psforever.objects.zones.Zone
|
||||
import net.psforever.packet.game._
|
||||
import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID, Vector3}
|
||||
import net.psforever.packet.game.{Additional1, Additional2, Additional3}
|
||||
import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID, PlanetSideGeneratorState, Vector3}
|
||||
import scalax.collection.{Graph, GraphEdge}
|
||||
import services.Service
|
||||
import services.local.{LocalAction, LocalServiceMessage}
|
||||
|
|
@ -182,17 +183,17 @@ class Building(private val name: String,
|
|||
case _ =>
|
||||
(false, PlanetSideEmpire.NEUTRAL, 0L)
|
||||
}
|
||||
//TODO if we have a generator, get the current repair state
|
||||
val (generatorState, boostGeneratorPain) = (PlanetSideGeneratorState.Normal, false) // todo: poll pain field strength
|
||||
//if we have no generator, assume the state is "Normal"
|
||||
val (generatorState, boostGeneratorPain) = Amenities.find(x => x.isInstanceOf[Generator]) match {
|
||||
case Some(obj : Generator) =>
|
||||
(obj.Condition, false) // todo: poll pain field strength
|
||||
case _ =>
|
||||
(PlanetSideGeneratorState.Normal, false)
|
||||
}
|
||||
//if we have spawn tubes, determine if any of them are active
|
||||
val (spawnTubesNormal, boostSpawnPain) : (Boolean, Boolean) = {
|
||||
val o = Amenities.collect({ case _ : SpawnTube => true }) ///TODO obj.Health > 0
|
||||
if(o.nonEmpty) {
|
||||
(o.foldLeft(false)(_ || _), false) //TODO poll pain field strength
|
||||
}
|
||||
else {
|
||||
(true, false)
|
||||
}
|
||||
val o = Amenities.collect({ case tube : SpawnTube if !tube.Destroyed => tube })
|
||||
(o.nonEmpty, false) //TODO poll pain field strength
|
||||
}
|
||||
|
||||
val latticeBenefit : Int = {
|
||||
|
|
@ -312,6 +313,7 @@ object Building {
|
|||
obj
|
||||
}
|
||||
|
||||
final case class AmenityStateChange(obj : Amenity)
|
||||
final case class SendMapUpdate(all_clients: Boolean)
|
||||
final case class TriggerZoneMapUpdate(zone_num: Int)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ package net.psforever.objects.serverobject.structures
|
|||
|
||||
import akka.actor.{Actor, ActorRef}
|
||||
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
|
||||
import net.psforever.objects.serverobject.generator.Generator
|
||||
import net.psforever.objects.serverobject.tube.SpawnTube
|
||||
import net.psforever.objects.zones.InterstellarCluster
|
||||
import net.psforever.packet.game.BuildingInfoUpdateMessage
|
||||
import services.ServiceManager
|
||||
|
|
@ -35,43 +37,62 @@ class BuildingControl(building : Building) extends Actor with FactionAffinityBeh
|
|||
building.Amenities.foreach(_.Actor forward FactionAffinity.ConfirmFactionAffinity())
|
||||
}
|
||||
sender ! FactionAffinity.AssertFactionAffinity(building, faction)
|
||||
case Building.TriggerZoneMapUpdate(zone_num: Int) =>
|
||||
if(interstellarCluster != ActorRef.noSender) interstellarCluster ! InterstellarCluster.ZoneMapUpdate(zone_num)
|
||||
case Building.SendMapUpdate(all_clients: Boolean) =>
|
||||
val zoneNumber = building.Zone.Number
|
||||
val buildingNumber = building.MapId
|
||||
log.trace(s"sending BuildingInfoUpdateMessage update - zone=$zoneNumber, building=$buildingNumber")
|
||||
val (
|
||||
ntuLevel,
|
||||
isHacked, empireHack, hackTimeRemaining, controllingEmpire,
|
||||
unk1, unk1x,
|
||||
generatorState, spawnTubesNormal, forceDomeActive,
|
||||
latticeBenefit, cavernBenefit,
|
||||
unk4, unk5, unk6,
|
||||
unk7, unk7x,
|
||||
boostSpawnPain, boostGeneratorPain
|
||||
) = building.Info
|
||||
val msg = BuildingInfoUpdateMessage(
|
||||
zoneNumber,
|
||||
buildingNumber,
|
||||
ntuLevel,
|
||||
isHacked, empireHack, hackTimeRemaining, controllingEmpire,
|
||||
unk1, unk1x,
|
||||
generatorState, spawnTubesNormal, forceDomeActive,
|
||||
latticeBenefit, cavernBenefit,
|
||||
unk4, unk5, unk6,
|
||||
unk7, unk7x,
|
||||
boostSpawnPain, boostGeneratorPain
|
||||
)
|
||||
|
||||
if(all_clients) {
|
||||
if(galaxyService != ActorRef.noSender) galaxyService ! GalaxyServiceMessage(GalaxyAction.MapUpdate(msg))
|
||||
} else {
|
||||
// Fake a GalaxyServiceResponse response back to just the sender
|
||||
sender ! GalaxyServiceResponse("", GalaxyResponse.MapUpdate(msg))
|
||||
case Building.AmenityStateChange(obj : SpawnTube) =>
|
||||
if(building.Amenities.contains(obj)) {
|
||||
SendMapUpdate(allClients = true)
|
||||
}
|
||||
|
||||
case default =>
|
||||
log.warn(s"BuildingControl: Unknown message $default received from ${sender().path}")
|
||||
case Building.AmenityStateChange(obj : Generator) =>
|
||||
if(building.Amenities.contains(obj)) {
|
||||
SendMapUpdate(allClients = true)
|
||||
}
|
||||
|
||||
case Building.TriggerZoneMapUpdate(zone_num: Int) =>
|
||||
if(interstellarCluster != ActorRef.noSender) interstellarCluster ! InterstellarCluster.ZoneMapUpdate(zone_num)
|
||||
|
||||
case Building.SendMapUpdate(all_clients: Boolean) =>
|
||||
SendMapUpdate(all_clients)
|
||||
|
||||
case _ =>
|
||||
}
|
||||
|
||||
/**
|
||||
* na
|
||||
* @param allClients na
|
||||
*/
|
||||
def SendMapUpdate(allClients : Boolean) : Unit = {
|
||||
val zoneNumber = building.Zone.Number
|
||||
val buildingNumber = building.MapId
|
||||
log.trace(s"sending BuildingInfoUpdateMessage update - zone=$zoneNumber, building=$buildingNumber")
|
||||
val (
|
||||
ntuLevel,
|
||||
isHacked, empireHack, hackTimeRemaining, controllingEmpire,
|
||||
unk1, unk1x,
|
||||
generatorState, spawnTubesNormal, forceDomeActive,
|
||||
latticeBenefit, cavernBenefit,
|
||||
unk4, unk5, unk6,
|
||||
unk7, unk7x,
|
||||
boostSpawnPain, boostGeneratorPain
|
||||
) = building.Info
|
||||
val msg = BuildingInfoUpdateMessage(
|
||||
zoneNumber,
|
||||
buildingNumber,
|
||||
ntuLevel,
|
||||
isHacked, empireHack, hackTimeRemaining, controllingEmpire,
|
||||
unk1, unk1x,
|
||||
generatorState, spawnTubesNormal, forceDomeActive,
|
||||
latticeBenefit, cavernBenefit,
|
||||
unk4, unk5, unk6,
|
||||
unk7, unk7x,
|
||||
boostSpawnPain, boostGeneratorPain
|
||||
)
|
||||
|
||||
if(allClients) {
|
||||
if(galaxyService != ActorRef.noSender) galaxyService ! GalaxyServiceMessage(GalaxyAction.MapUpdate(msg))
|
||||
} else {
|
||||
// Fake a GalaxyServiceResponse response back to just the sender
|
||||
sender ! GalaxyServiceResponse("", GalaxyResponse.MapUpdate(msg))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ import net.psforever.objects.definition.ObjectDefinition
|
|||
import net.psforever.objects.serverobject.PlanetSideServerObject
|
||||
import net.psforever.objects.{GlobalDefinitions, SpawnPoint, SpawnPointDefinition}
|
||||
import net.psforever.objects.zones.Zone
|
||||
import net.psforever.packet.game.{Additional1, Additional2, Additional3, PlanetSideGeneratorState}
|
||||
import net.psforever.types.{PlanetSideEmpire, Vector3}
|
||||
import net.psforever.packet.game.{Additional1, Additional2, Additional3}
|
||||
import net.psforever.types.{PlanetSideEmpire, PlanetSideGeneratorState, Vector3}
|
||||
|
||||
import scala.collection.mutable
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
package net.psforever.objects.serverobject.terminals
|
||||
|
||||
import akka.actor.Actor
|
||||
import net.psforever.objects.{GlobalDefinitions, SimpleItem}
|
||||
import net.psforever.objects.serverobject.CommonMessages
|
||||
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
|
||||
import net.psforever.objects.serverobject.hackable.HackableBehavior
|
||||
|
|
@ -14,6 +15,15 @@ class CaptureTerminalControl(terminal : CaptureTerminal) extends Actor with Fact
|
|||
def receive : Receive = checkBehavior
|
||||
.orElse(hackableBehavior)
|
||||
.orElse {
|
||||
case _ => ; //no default message
|
||||
case CommonMessages.Use(player, Some(item : SimpleItem)) if item.Definition == GlobalDefinitions.remote_electronics_kit =>
|
||||
val canHack = terminal.HackedBy match {
|
||||
case Some(info) => info.hackerFaction != player.Faction
|
||||
case _ => terminal.Faction != player.Faction
|
||||
}
|
||||
if(canHack) {
|
||||
sender ! CommonMessages.Hack(player, terminal, Some(item))
|
||||
}
|
||||
|
||||
case _ => ; //no default message
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
package net.psforever.objects.serverobject.terminals
|
||||
|
||||
import net.psforever.objects.definition.ObjectDefinition
|
||||
import net.psforever.objects.serverobject.structures.AmenityDefinition
|
||||
|
||||
class CaptureTerminalDefinition(objectId : Int) extends ObjectDefinition(objectId) {
|
||||
class CaptureTerminalDefinition(objectId : Int) extends AmenityDefinition(objectId) {
|
||||
Name = objectId match {
|
||||
case 158 => "capture_terminal"
|
||||
case 751 => "secondary_capture"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
// Copyright (c) 2020 PSForever
|
||||
package net.psforever.objects.serverobject.terminals
|
||||
|
||||
import net.psforever.objects.Player
|
||||
import net.psforever.objects.serverobject.CommonMessages
|
||||
import services.local.{LocalAction, LocalServiceMessage}
|
||||
|
||||
import scala.util.{Failure, Success}
|
||||
|
||||
object CaptureTerminals {
|
||||
private val log = org.log4s.getLogger("CaptureTerminals")
|
||||
|
||||
/**
|
||||
* The process of hacking an object is completed.
|
||||
* Pass the message onto the hackable object and onto the local events system.
|
||||
* @param target the `Hackable` object that has been hacked
|
||||
* @param unk na;
|
||||
* used by `HackMessage` as `unk5`
|
||||
* @see `HackMessage`
|
||||
*/
|
||||
//TODO add params here depending on which params in HackMessage are important
|
||||
def FinishHackingCaptureConsole(target : CaptureTerminal, user : Player, unk : Long)() : Unit = {
|
||||
import akka.pattern.ask
|
||||
import scala.concurrent.duration._
|
||||
log.info(s"Hacked a $target")
|
||||
// Wait for the target actor to set the HackedBy property, otherwise LocalAction.HackTemporarily will not complete properly
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
val tplayer = user
|
||||
ask(target.Actor, CommonMessages.Hack(tplayer, target))(1 second).mapTo[Boolean].onComplete {
|
||||
case Success(_) =>
|
||||
val zone = target.Zone
|
||||
val zoneId = zone.Id
|
||||
val pguid = tplayer.GUID
|
||||
zone.LocalEvents ! LocalServiceMessage(zoneId, LocalAction.TriggerSound(pguid, target.HackSound, tplayer.Position, 30, 0.49803925f))
|
||||
zone.LocalEvents ! LocalServiceMessage(zoneId, LocalAction.HackCaptureTerminal(pguid, zone, target, unk, 8L, tplayer.Faction == target.Faction))
|
||||
case Failure(_) => log.warn(s"Hack message failed on target guid: ${target.GUID}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -4,8 +4,11 @@ package net.psforever.objects.serverobject.terminals
|
|||
import akka.actor.{Actor, ActorRef, Cancellable}
|
||||
import net.psforever.objects._
|
||||
import net.psforever.objects.serverobject.CommonMessages
|
||||
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
|
||||
import net.psforever.objects.serverobject.affinity.FactionAffinityBehavior
|
||||
import net.psforever.objects.serverobject.damage.DamageableAmenity
|
||||
import net.psforever.objects.serverobject.hackable.HackableBehavior
|
||||
import net.psforever.objects.serverobject.repair.RepairableAmenity
|
||||
import net.psforever.objects.serverobject.structures.Building
|
||||
|
||||
import scala.collection.mutable
|
||||
import scala.concurrent.duration._
|
||||
|
|
@ -16,27 +19,42 @@ import scala.concurrent.duration._
|
|||
* it returns the same type of messages - wrapped in a `TerminalMessage` - to the `sender`.
|
||||
* @param term the proximity unit (terminal)
|
||||
*/
|
||||
class ProximityTerminalControl(term : Terminal with ProximityUnit) extends Actor with FactionAffinityBehavior.Check with HackableBehavior.GenericHackable {
|
||||
class ProximityTerminalControl(term : Terminal with ProximityUnit) extends Actor
|
||||
with FactionAffinityBehavior.Check
|
||||
with HackableBehavior.GenericHackable
|
||||
with DamageableAmenity
|
||||
with RepairableAmenity {
|
||||
def FactionObject = term
|
||||
def HackableObject = term
|
||||
def TerminalObject = term
|
||||
def DamageableObject = term
|
||||
def RepairableObject = term
|
||||
|
||||
var terminalAction : Cancellable = DefaultCancellable.obj
|
||||
val callbacks : mutable.ListBuffer[ActorRef] = new mutable.ListBuffer[ActorRef]()
|
||||
val log = org.log4s.getLogger
|
||||
|
||||
def FactionObject : FactionAffinity = term
|
||||
def HackableObject = term
|
||||
|
||||
def TerminalObject : Terminal with ProximityUnit = term
|
||||
|
||||
def receive : Receive = checkBehavior
|
||||
.orElse(hackableBehavior)
|
||||
.orElse {
|
||||
case CommonMessages.Use(_, Some(target : PlanetSideGameObject)) =>
|
||||
if(term.Definition.asInstanceOf[ProximityDefinition].Validations.exists(p => p(target))) {
|
||||
Use(target, term.Continent, sender)
|
||||
}
|
||||
case CommonMessages.Use(_, Some((target : PlanetSideGameObject, callback : ActorRef))) =>
|
||||
if(term.Definition.asInstanceOf[ProximityDefinition].Validations.exists(p => p(target))) {
|
||||
Use(target, term.Continent, callback)
|
||||
}
|
||||
.orElse(hackableBehavior)
|
||||
.orElse(takesDamage)
|
||||
.orElse(canBeRepairedByNanoDispenser)
|
||||
.orElse {
|
||||
case CommonMessages.Use(player, Some(item : SimpleItem)) if item.Definition == GlobalDefinitions.remote_electronics_kit =>
|
||||
//TODO setup certifications check
|
||||
term.Owner match {
|
||||
case b : Building if (b.Faction != player.Faction || b.CaptureConsoleIsHacked) && term.HackedBy.isEmpty =>
|
||||
sender ! CommonMessages.Hack(player, term, Some(item))
|
||||
case _ => ;
|
||||
}
|
||||
|
||||
case CommonMessages.Use(_, Some(target : PlanetSideGameObject)) =>
|
||||
if(!term.Destroyed && term.Definition.asInstanceOf[ProximityDefinition].Validations.exists(p => p(target))) {
|
||||
Use(target, term.Continent, sender)
|
||||
}
|
||||
case CommonMessages.Use(_, Some((target : PlanetSideGameObject, callback : ActorRef))) =>
|
||||
if(!term.Destroyed && term.Definition.asInstanceOf[ProximityDefinition].Validations.exists(p => p(target))) {
|
||||
Use(target, term.Continent, callback)
|
||||
}
|
||||
|
||||
case CommonMessages.Use(_, _) =>
|
||||
log.warn(s"unexpected format for CommonMessages.Use in this context")
|
||||
|
|
@ -52,7 +70,7 @@ class ProximityTerminalControl(term : Terminal with ProximityUnit) extends Actor
|
|||
val validateFunc : PlanetSideGameObject=>Boolean = term.Validate(proxDef.UseRadius * proxDef.UseRadius, proxDef.Validations)
|
||||
val callbackList = callbacks.toList
|
||||
term.Targets.zipWithIndex.foreach({ case(target, index) =>
|
||||
if(validateFunc(target)) {
|
||||
if(!term.Destroyed && validateFunc(target)) {
|
||||
callbackList.lift(index) match {
|
||||
case Some(cback) =>
|
||||
cback ! ProximityUnit.Action(term, target)
|
||||
|
|
|
|||
|
|
@ -17,22 +17,20 @@ import net.psforever.types.{PlanetSideGUID, Vector3}
|
|||
* while `Vehicle`-owned terminals may not.
|
||||
* @param tdef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields
|
||||
*/
|
||||
class Terminal(tdef : TerminalDefinition) extends Amenity with Hackable {
|
||||
class Terminal(tdef : TerminalDefinition) extends Amenity
|
||||
with Hackable {
|
||||
HackSound = TriggeredSound.HackTerminal
|
||||
HackEffectDuration = Array(0, 30, 60, 90)
|
||||
HackDuration = Array(0, 10, 5, 3)
|
||||
|
||||
//the following fields and related methods are neither finalized nor integrated; GOTO Request
|
||||
private var health : Int = 100 //TODO not real health value
|
||||
|
||||
def Health : Int = health
|
||||
|
||||
def Damaged(dam : Int) : Unit = {
|
||||
health = Math.max(0, Health - dam)
|
||||
Health = Math.max(0, Health - dam)
|
||||
}
|
||||
|
||||
def Repair(rep : Int) : Unit = {
|
||||
health = Math.min(Health + rep, 100)
|
||||
Health = Math.min(Health + rep, 100)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -2,24 +2,43 @@
|
|||
package net.psforever.objects.serverobject.terminals
|
||||
|
||||
import akka.actor.Actor
|
||||
import net.psforever.objects.{GlobalDefinitions, SimpleItem}
|
||||
import net.psforever.objects.serverobject.CommonMessages
|
||||
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
|
||||
import net.psforever.objects.serverobject.affinity.FactionAffinityBehavior
|
||||
import net.psforever.objects.serverobject.damage.DamageableAmenity
|
||||
import net.psforever.objects.serverobject.hackable.HackableBehavior
|
||||
import net.psforever.objects.serverobject.repair.RepairableAmenity
|
||||
import net.psforever.objects.serverobject.structures.Building
|
||||
|
||||
/**
|
||||
* An `Actor` that handles messages being dispatched to a specific `Terminal`.
|
||||
* @param term the `Terminal` object being governed
|
||||
*/
|
||||
class TerminalControl(term : Terminal) extends Actor with FactionAffinityBehavior.Check with HackableBehavior.GenericHackable {
|
||||
def FactionObject : FactionAffinity = term
|
||||
class TerminalControl(term : Terminal) extends Actor
|
||||
with FactionAffinityBehavior.Check
|
||||
with HackableBehavior.GenericHackable
|
||||
with DamageableAmenity
|
||||
with RepairableAmenity {
|
||||
def FactionObject = term
|
||||
def HackableObject = term
|
||||
def DamageableObject = term
|
||||
def RepairableObject = term
|
||||
|
||||
def receive : Receive = checkBehavior
|
||||
.orElse(hackableBehavior)
|
||||
.orElse {
|
||||
.orElse(hackableBehavior)
|
||||
.orElse(takesDamage)
|
||||
.orElse(canBeRepairedByNanoDispenser)
|
||||
.orElse {
|
||||
case Terminal.Request(player, msg) =>
|
||||
sender ! Terminal.TerminalMessage(player, msg, term.Request(player, msg))
|
||||
|
||||
case CommonMessages.Use(player, Some(item : SimpleItem)) if item.Definition == GlobalDefinitions.remote_electronics_kit =>
|
||||
//TODO setup certifications check
|
||||
term.Owner match {
|
||||
case b : Building if (b.Faction != player.Faction || b.CaptureConsoleIsHacked) && term.HackedBy.isEmpty =>
|
||||
sender ! CommonMessages.Hack(player, term, Some(item))
|
||||
case _ => ;
|
||||
}
|
||||
case _ => ;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,12 +3,13 @@ package net.psforever.objects.serverobject.terminals
|
|||
|
||||
import net.psforever.objects.Player
|
||||
import net.psforever.objects.definition.converter.TerminalConverter
|
||||
import net.psforever.objects.serverobject.structures.AmenityDefinition
|
||||
|
||||
/**
|
||||
* The basic definition for any `Terminal` object.
|
||||
* @param objectId the object's identifier number
|
||||
*/
|
||||
abstract class TerminalDefinition(objectId : Int) extends net.psforever.objects.definition.ObjectDefinition(objectId) {
|
||||
abstract class TerminalDefinition(objectId : Int) extends AmenityDefinition(objectId) {
|
||||
Name = "terminal"
|
||||
Packet = new TerminalConverter
|
||||
|
||||
|
|
|
|||
|
|
@ -2,16 +2,47 @@
|
|||
package net.psforever.objects.serverobject.tube
|
||||
|
||||
import akka.actor.Actor
|
||||
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
|
||||
import net.psforever.objects.ballistics.ResolvedProjectile
|
||||
import net.psforever.objects.serverobject.affinity.FactionAffinityBehavior
|
||||
import net.psforever.objects.serverobject.damage.Damageable.Target
|
||||
import net.psforever.objects.serverobject.damage.DamageableAmenity
|
||||
import net.psforever.objects.serverobject.repair.{Repairable, RepairableAmenity}
|
||||
import net.psforever.objects.serverobject.structures.Building
|
||||
|
||||
/**
|
||||
* An `Actor` that handles messages being dispatched to a specific `SpawnTube`.
|
||||
* @param tube the `SpawnTube` object being governed
|
||||
*/
|
||||
class SpawnTubeControl(tube : SpawnTube) extends Actor with FactionAffinityBehavior.Check {
|
||||
def FactionObject : FactionAffinity = tube
|
||||
class SpawnTubeControl(tube : SpawnTube) extends Actor
|
||||
with FactionAffinityBehavior.Check
|
||||
with DamageableAmenity
|
||||
with RepairableAmenity {
|
||||
def FactionObject = tube
|
||||
def DamageableObject = tube
|
||||
def RepairableObject = tube
|
||||
|
||||
def receive : Receive = checkBehavior.orElse { case _ =>; }
|
||||
def receive : Receive = checkBehavior
|
||||
.orElse(takesDamage)
|
||||
.orElse(canBeRepairedByNanoDispenser)
|
||||
.orElse {
|
||||
case _ => ;
|
||||
}
|
||||
|
||||
override protected def DestructionAwareness(target : Target, cause : ResolvedProjectile) : Unit = {
|
||||
super.DestructionAwareness(target, cause)
|
||||
tube.Owner match {
|
||||
case b : Building => b.Actor ! Building.AmenityStateChange(tube)
|
||||
case _ => ;
|
||||
}
|
||||
}
|
||||
|
||||
override def Restoration(obj : Repairable.Target) : Unit = {
|
||||
super.Restoration(obj)
|
||||
tube.Owner match {
|
||||
case b : Building => b.Actor ! Building.AmenityStateChange(tube)
|
||||
case _ => ;
|
||||
}
|
||||
}
|
||||
|
||||
override def toString : String = tube.Definition.Name
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,15 +3,14 @@ package net.psforever.objects.serverobject.tube
|
|||
|
||||
import akka.actor.ActorContext
|
||||
import net.psforever.objects.SpawnPointDefinition
|
||||
import net.psforever.objects.definition.ObjectDefinition
|
||||
import net.psforever.objects.definition.converter.SpawnTubeConverter
|
||||
import net.psforever.objects.serverobject.PlanetSideServerObject
|
||||
import net.psforever.objects.serverobject.structures.Amenity
|
||||
import net.psforever.objects.serverobject.structures.{Amenity, AmenityDefinition}
|
||||
|
||||
/**
|
||||
* The definition for any spawn point in the game world.
|
||||
*/
|
||||
class SpawnTubeDefinition(object_id : Int) extends ObjectDefinition(object_id)
|
||||
class SpawnTubeDefinition(object_id : Int) extends AmenityDefinition(object_id)
|
||||
with SpawnPointDefinition {
|
||||
Packet = new SpawnTubeConverter
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,57 +4,14 @@ package net.psforever.objects.serverobject.turret
|
|||
import net.psforever.objects.equipment.JammableUnit
|
||||
import net.psforever.objects.serverobject.structures.Amenity
|
||||
import net.psforever.types.Vector3
|
||||
import net.psforever.objects.vital.{DamageResistanceModel, StandardResistanceProfile, Vitality}
|
||||
|
||||
class FacilityTurret(tDef : FacilityTurretDefinition) extends Amenity
|
||||
with WeaponTurret
|
||||
with JammableUnit
|
||||
with Vitality
|
||||
with StandardResistanceProfile {
|
||||
/** some turrets can be updated; they all start without updates */
|
||||
private var upgradePath : TurretUpgrade.Value = TurretUpgrade.None
|
||||
private var middleOfUpgrade : Boolean = false
|
||||
|
||||
with JammableUnit {
|
||||
WeaponTurret.LoadDefinition(this)
|
||||
|
||||
override def Health_=(toHealth : Int) = super.Health_=(math.max(1, toHealth)) //TODO properly handle destroyed facility turrets
|
||||
|
||||
def MaxHealth : Int = Definition.MaxHealth
|
||||
|
||||
def MountPoints : Map[Int, Int] = Definition.MountPoints.toMap
|
||||
|
||||
def Upgrade : TurretUpgrade.Value = upgradePath
|
||||
|
||||
def Upgrade_=(upgrade : TurretUpgrade.Value) : TurretUpgrade.Value = {
|
||||
middleOfUpgrade = true //blocking flag; block early
|
||||
var updated = false
|
||||
//upgrade each weapon as long as that weapon has a valid option for that upgrade
|
||||
Definition.Weapons.foreach({ case(index, upgradePaths) =>
|
||||
if(upgradePaths.contains(upgrade)) {
|
||||
updated = true
|
||||
weapons(index).Equipment.get.asInstanceOf[TurretWeapon].Upgrade = upgrade
|
||||
}
|
||||
})
|
||||
if(updated) {
|
||||
upgradePath = upgrade
|
||||
}
|
||||
else {
|
||||
middleOfUpgrade = false //reset
|
||||
}
|
||||
Upgrade
|
||||
}
|
||||
|
||||
def ConfirmUpgrade(upgrade : TurretUpgrade.Value) : TurretUpgrade.Value = {
|
||||
if(middleOfUpgrade && upgradePath == upgrade) {
|
||||
middleOfUpgrade = false
|
||||
}
|
||||
upgradePath
|
||||
}
|
||||
|
||||
def isUpgrading : Boolean = middleOfUpgrade
|
||||
|
||||
def DamageModel = Definition.asInstanceOf[DamageResistanceModel]
|
||||
|
||||
def Definition : FacilityTurretDefinition = tDef
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,20 +1,17 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package net.psforever.objects.serverobject.turret
|
||||
|
||||
import akka.actor.{Actor, Cancellable}
|
||||
import net.psforever.objects.{DefaultCancellable, GlobalDefinitions, Player}
|
||||
import akka.actor.Actor
|
||||
import net.psforever.objects.ballistics.ResolvedProjectile
|
||||
import net.psforever.objects.equipment.{JammableMountedWeapons, JammableUnit}
|
||||
import net.psforever.objects.serverobject.mount.{Mountable, MountableBehavior}
|
||||
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
|
||||
import net.psforever.objects.vital.Vitality
|
||||
import net.psforever.objects.zones.Zone
|
||||
import net.psforever.types.PlanetSideGUID
|
||||
import services.Service
|
||||
import net.psforever.objects.{GlobalDefinitions, Player}
|
||||
import net.psforever.objects.equipment.JammableMountedWeapons
|
||||
import net.psforever.objects.serverobject.mount.MountableBehavior
|
||||
import net.psforever.objects.serverobject.affinity.FactionAffinityBehavior
|
||||
import net.psforever.objects.serverobject.damage.DamageableWeaponTurret
|
||||
import net.psforever.objects.serverobject.repair.Repairable.Target
|
||||
import net.psforever.objects.serverobject.repair.RepairableWeaponTurret
|
||||
import services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||
import services.local.{LocalAction, LocalServiceMessage}
|
||||
import services.vehicle.{VehicleAction, VehicleServiceMessage}
|
||||
import services.vehicle.support.TurretUpgrader
|
||||
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
import scala.concurrent.duration._
|
||||
|
|
@ -30,27 +27,30 @@ import scala.concurrent.duration._
|
|||
*/
|
||||
class FacilityTurretControl(turret : FacilityTurret) extends Actor
|
||||
with FactionAffinityBehavior.Check
|
||||
with MountableBehavior.TurretMount
|
||||
with MountableBehavior.Dismount
|
||||
with DamageableWeaponTurret
|
||||
with RepairableWeaponTurret
|
||||
with JammableMountedWeapons {
|
||||
|
||||
def FactionObject = turret
|
||||
def MountableObject = turret
|
||||
def JammableObject = turret
|
||||
def DamageableObject = turret
|
||||
def RepairableObject = turret
|
||||
if(turret.Definition == GlobalDefinitions.vanu_sentry_turret) {
|
||||
// todo: Schedule this to start when weapon is discharged, not all the time
|
||||
context.system.scheduler.schedule(3 seconds, 200 milliseconds, self, FacilityTurret.RechargeAmmo())
|
||||
}
|
||||
|
||||
def MountableObject = turret
|
||||
|
||||
def JammableObject = turret
|
||||
|
||||
def FactionObject : FactionAffinity = turret
|
||||
|
||||
def receive : Receive = checkBehavior
|
||||
.orElse(jammableBehavior)
|
||||
.orElse(mountBehavior)
|
||||
.orElse(dismountBehavior)
|
||||
.orElse(takesDamage)
|
||||
.orElse(canBeRepairedByNanoDispenser)
|
||||
.orElse {
|
||||
case FacilityTurret.RechargeAmmo() =>
|
||||
val weapon = turret.ControlledWeapon(1).get.asInstanceOf[net.psforever.objects.Tool]
|
||||
|
||||
// recharge when last shot fired 3s delay, +1, 200ms interval
|
||||
if(weapon.Magazine < weapon.MaxMagazine && System.nanoTime() - weapon.LastDischarge > 3000000000L) {
|
||||
weapon.Magazine += 1
|
||||
|
|
@ -60,117 +60,27 @@ class FacilityTurretControl(turret : FacilityTurret) extends Actor
|
|||
case _ => ;
|
||||
}
|
||||
}
|
||||
case Mountable.TryMount(user, seat_num) =>
|
||||
turret.Seat(seat_num) match {
|
||||
case Some(seat) =>
|
||||
if((!turret.Definition.FactionLocked || user.Faction == turret.Faction) &&
|
||||
(seat.Occupant = user).contains(user)) {
|
||||
user.VehicleSeated = turret.GUID
|
||||
sender ! Mountable.MountMessages(user, Mountable.CanMount(turret, seat_num))
|
||||
}
|
||||
else {
|
||||
sender ! Mountable.MountMessages(user, Mountable.CanNotMount(turret, seat_num))
|
||||
}
|
||||
case None =>
|
||||
sender ! Mountable.MountMessages(user, Mountable.CanNotMount(turret, seat_num))
|
||||
}
|
||||
|
||||
case Vitality.Damage(damage_func) =>
|
||||
if(turret.Health > 0) {
|
||||
val originalHealth = turret.Health
|
||||
val cause = damage_func(turret)
|
||||
val health = turret.Health
|
||||
val damageToHealth = originalHealth - health
|
||||
FacilityTurretControl.HandleDamageResolution(turret, cause, damageToHealth)
|
||||
if(damageToHealth > 0) {
|
||||
val name = turret.Actor.toString
|
||||
val slashPoint = name.lastIndexOf("/")
|
||||
org.log4s.getLogger("DamageResolution").info(s"${name.substring(slashPoint + 1, name.length - 1)}: BEFORE=$originalHealth, AFTER=$health, CHANGE=$damageToHealth")
|
||||
}
|
||||
}
|
||||
|
||||
case _ => ;
|
||||
}
|
||||
}
|
||||
|
||||
object FacilityTurretControl {
|
||||
def HandleDamageResolution(target : FacilityTurret, cause : ResolvedProjectile, damage : Int) : Unit = {
|
||||
val zone = target.Zone
|
||||
val targetGUID = target.GUID
|
||||
val playerGUID = target.Zone.LivePlayers.find { p => cause.projectile.owner.Name.equals(p.Name) } match {
|
||||
case Some(player) => player.GUID
|
||||
case _ => targetGUID
|
||||
}
|
||||
val continentId = zone.Id
|
||||
if(target.Health > 1) {
|
||||
//alert occupants to damage source
|
||||
if(damage > 0) {
|
||||
zone.Activity ! Zone.HotSpot.Activity(cause.target, cause.projectile.owner, cause.hit_pos)
|
||||
//alert occupants to damage source
|
||||
HandleDamageAwareness(target, playerGUID, cause)
|
||||
}
|
||||
if(cause.projectile.profile.JammerProjectile) {
|
||||
target.Actor ! JammableUnit.Jammered(cause)
|
||||
}
|
||||
}
|
||||
else {
|
||||
//alert to vehicle death (hence, occupants' deaths)
|
||||
HandleDestructionAwareness(target, playerGUID, cause)
|
||||
}
|
||||
zone.VehicleEvents ! VehicleServiceMessage(continentId, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, targetGUID, 0, target.Health))
|
||||
}
|
||||
|
||||
/**
|
||||
* na
|
||||
* @param target na
|
||||
* @param attribution na
|
||||
* @param lastShot na
|
||||
*/
|
||||
def HandleDamageAwareness(target : FacilityTurret, attribution : PlanetSideGUID, lastShot : ResolvedProjectile) : Unit = {
|
||||
override protected def DestructionAwareness(target : Target, cause : ResolvedProjectile) : Unit = {
|
||||
super.DestructionAwareness(target, cause)
|
||||
val zone = target.Zone
|
||||
val zoneId = zone.Id
|
||||
//alert occupants to damage source
|
||||
target.Seats.values.filter(seat => {
|
||||
seat.isOccupied && seat.Occupant.get.isAlive
|
||||
}).foreach(seat => {
|
||||
val tplayer = seat.Occupant.get
|
||||
zone.AvatarEvents ! AvatarServiceMessage(zoneId, AvatarAction.HitHint(attribution, tplayer.GUID))
|
||||
})
|
||||
val events = zone.AvatarEvents
|
||||
val tguid = target.GUID
|
||||
events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(tguid, 50, 1))
|
||||
events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(tguid, 51, 1))
|
||||
}
|
||||
|
||||
/**
|
||||
* na
|
||||
* @param target na
|
||||
* @param attribution na
|
||||
* @param lastShot na
|
||||
*/
|
||||
def HandleDestructionAwareness(target : FacilityTurret, attribution : PlanetSideGUID, lastShot : ResolvedProjectile) : Unit = {
|
||||
target.Actor ! JammableUnit.ClearJammeredSound()
|
||||
target.Actor ! JammableUnit.ClearJammeredStatus()
|
||||
val zone = target.Zone
|
||||
override def Restoration(obj : Target) : Unit = {
|
||||
super.Restoration(obj)
|
||||
val zone = turret.Zone
|
||||
val zoneId = zone.Id
|
||||
target.Seats.values.filter(seat => {
|
||||
seat.isOccupied && seat.Occupant.get.isAlive
|
||||
}).foreach(seat => {
|
||||
val tplayer = seat.Occupant.get
|
||||
tplayer.History(lastShot)
|
||||
tplayer.Actor ! Player.Die()
|
||||
})
|
||||
//turret wreckage has no weapons
|
||||
// target.Weapons.values
|
||||
// .filter {
|
||||
// _.Equipment.nonEmpty
|
||||
// }
|
||||
// .foreach(slot => {
|
||||
// val wep = slot.Equipment.get
|
||||
// zone.AvatarEvents ! AvatarServiceMessage(continentId, AvatarAction.ObjectDelete(Service.defaultPlayerGUID, wep.GUID))
|
||||
// })
|
||||
// zone.AvatarEvents ! AvatarServiceMessage(continentId, AvatarAction.Destroy(targetGUID, playerGUID, playerGUID, player.Position))
|
||||
target.Health = 1 //TODO turret "death" at 0, as is proper
|
||||
zone.VehicleEvents ! VehicleServiceMessage(zoneId, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, target.GUID, 0, target.Health)) //TODO not necessary
|
||||
if(target.Upgrade != TurretUpgrade.None) {
|
||||
zone.VehicleEvents ! VehicleServiceMessage.TurretUpgrade(TurretUpgrader.ClearSpecific(List(target), zone))
|
||||
zone.VehicleEvents ! VehicleServiceMessage.TurretUpgrade(TurretUpgrader.AddTask(target, zone, TurretUpgrade.None))
|
||||
}
|
||||
val events = zone.AvatarEvents
|
||||
val tguid = turret.GUID
|
||||
events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(tguid, 50, 0))
|
||||
events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(tguid, 51, 0))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
// Copyright (c) 2019 PSForever
|
||||
package net.psforever.objects.serverobject.turret
|
||||
|
||||
import net.psforever.objects.definition.ObjectDefinition
|
||||
import net.psforever.objects.serverobject.structures.AmenityDefinition
|
||||
import net.psforever.objects.vital.{StandardResolutions, StandardVehicleDamage, StandardVehicleResistance}
|
||||
|
||||
/**
|
||||
* The definition for any `FacilityTurret`.
|
||||
* @param objectId the object's identifier number
|
||||
*/
|
||||
class FacilityTurretDefinition(private val objectId : Int) extends ObjectDefinition(objectId)
|
||||
class FacilityTurretDefinition(private val objectId : Int) extends AmenityDefinition(objectId)
|
||||
with TurretDefinition {
|
||||
Damage = StandardVehicleDamage
|
||||
Resistance = StandardVehicleResistance
|
||||
DamageUsing = StandardVehicleDamage
|
||||
ResistUsing = StandardVehicleResistance
|
||||
Model = StandardResolutions.FacilityTurrets
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,8 +15,6 @@ trait TurretDefinition extends ResistanceProfileMutators
|
|||
with DamageResistanceModel {
|
||||
odef : ObjectDefinition =>
|
||||
Turrets(odef.ObjectId) //let throw NoSuchElementException
|
||||
|
||||
private var maxHealth : Int = 100
|
||||
/* key - entry point index, value - seat index */
|
||||
private val mountPoints : mutable.HashMap[Int, Int] = mutable.HashMap()
|
||||
/* key - seat number, value - hash map (below) */
|
||||
|
|
@ -29,13 +27,6 @@ trait TurretDefinition extends ResistanceProfileMutators
|
|||
* see `MannedTurret.TurretAmmoBox` for details */
|
||||
private var hasReserveAmmunition : Boolean = false
|
||||
|
||||
def MaxHealth : Int = maxHealth
|
||||
|
||||
def MaxHealth_=(health : Int) : Int = {
|
||||
maxHealth = health
|
||||
MaxHealth
|
||||
}
|
||||
|
||||
def MountPoints : mutable.HashMap[Int, Int] = mountPoints
|
||||
|
||||
def Weapons : mutable.HashMap[Int, mutable.HashMap[TurretUpgrade.Value, ToolDefinition]] = weapons
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package net.psforever.objects.serverobject.turret
|
||||
|
||||
import net.psforever.objects.{AmmoBox, PlanetSideGameObject, Player, Tool}
|
||||
import net.psforever.objects.definition.{AmmoBoxDefinition, SeatDefinition, ToolDefinition}
|
||||
import net.psforever.objects._
|
||||
import net.psforever.objects.equipment.{Equipment, EquipmentSlot}
|
||||
import net.psforever.objects.inventory.{Container, GridInventory}
|
||||
import net.psforever.objects.serverobject.affinity.FactionAffinity
|
||||
|
|
@ -13,10 +13,8 @@ trait WeaponTurret extends FactionAffinity
|
|||
with Mountable
|
||||
with MountedWeapons
|
||||
with Container {
|
||||
this : PlanetSideGameObject =>
|
||||
_ : PlanetSideGameObject =>
|
||||
|
||||
private var health : Int = 1
|
||||
private var jammered : Boolean = false
|
||||
/** manned turrets have just one seat; this is just standard interface */
|
||||
protected val seats : Map[Int, Chair] = Map(0 -> Chair(new SeatDefinition() { ControlledWeapon = Some(1) }))
|
||||
/** turrets have just one weapon; this is just standard interface */
|
||||
|
|
@ -24,21 +22,21 @@ trait WeaponTurret extends FactionAffinity
|
|||
/** may or may not have inaccessible inventory space
|
||||
* see `ReserveAmmunition` in the definition */
|
||||
protected val inventory : GridInventory = new GridInventory() {
|
||||
|
||||
import net.psforever.types.PlanetSideGUID
|
||||
|
||||
override def Remove(index : Int) : Boolean = false
|
||||
override def Remove(guid : PlanetSideGUID) : Boolean = false
|
||||
}
|
||||
/** some turrets can be updated; they all start without updates */
|
||||
private var upgradePath : TurretUpgrade.Value = TurretUpgrade.None
|
||||
private var middleOfUpgrade : Boolean = false
|
||||
|
||||
def Health : Int = {
|
||||
health
|
||||
}
|
||||
|
||||
def Health_=(toHealth : Int) : Int = {
|
||||
health = toHealth
|
||||
health
|
||||
}
|
||||
/*
|
||||
do not mind what the IDE probably comments about these method prototypes for Health and MaxHealth
|
||||
they do not override methods in Vitality, unless overrode in any class that implements this one
|
||||
due to the inheritance requirement above, these statements are not required to be implemented or overrode ever
|
||||
they are purely for class visibility
|
||||
*/
|
||||
def Health : Int
|
||||
|
||||
def MaxHealth : Int
|
||||
|
||||
|
|
@ -81,13 +79,40 @@ trait WeaponTurret extends FactionAffinity
|
|||
}
|
||||
}
|
||||
|
||||
def Jammered : Boolean = jammered
|
||||
def Upgrade : TurretUpgrade.Value = upgradePath
|
||||
|
||||
def Jammered_=(jamState : Boolean) : Boolean = {
|
||||
jammered = jamState
|
||||
Jammered
|
||||
def Upgrade_=(upgrade : TurretUpgrade.Value) : TurretUpgrade.Value = {
|
||||
middleOfUpgrade = true //blocking flag; block early
|
||||
var updated = false
|
||||
//upgrade each weapon as long as that weapon has a valid option for that upgrade
|
||||
Definition match {
|
||||
case definition : TurretDefinition =>
|
||||
definition.Weapons.foreach({ case(index, upgradePaths) =>
|
||||
if(upgradePaths.contains(upgrade)) {
|
||||
updated = true
|
||||
weapons(index).Equipment.get.asInstanceOf[TurretWeapon].Upgrade = upgrade
|
||||
}
|
||||
})
|
||||
case _ => ;
|
||||
}
|
||||
if(updated) {
|
||||
upgradePath = upgrade
|
||||
}
|
||||
else {
|
||||
middleOfUpgrade = false //reset
|
||||
}
|
||||
Upgrade
|
||||
}
|
||||
|
||||
def ConfirmUpgrade(upgrade : TurretUpgrade.Value) : TurretUpgrade.Value = {
|
||||
if(middleOfUpgrade && upgradePath == upgrade) {
|
||||
middleOfUpgrade = false
|
||||
}
|
||||
upgradePath
|
||||
}
|
||||
|
||||
def isUpgrading : Boolean = middleOfUpgrade
|
||||
|
||||
def Definition : TurretDefinition
|
||||
}
|
||||
|
||||
|
|
@ -110,8 +135,6 @@ object WeaponTurret {
|
|||
*/
|
||||
def LoadDefinition(turret : WeaponTurret, tdef : TurretDefinition) : WeaponTurret = {
|
||||
import net.psforever.objects.equipment.EquipmentSize.BaseTurretWeapon
|
||||
//general stuff
|
||||
turret.Health = tdef.MaxHealth
|
||||
//create weapons; note the class
|
||||
turret.weapons = tdef.Weapons.map({case (num, upgradePaths) =>
|
||||
val slot = EquipmentSlot(BaseTurretWeapon)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,50 @@
|
|||
// Copyright (c) 2020 PSForever
|
||||
package net.psforever.objects.serverobject.turret
|
||||
|
||||
import net.psforever.objects.{Player, Tool}
|
||||
import net.psforever.packet.game.InventoryStateMessage
|
||||
import services.Service
|
||||
import services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||
import services.vehicle.VehicleServiceMessage
|
||||
import services.vehicle.support.TurretUpgrader
|
||||
|
||||
object WeaponTurrets {
|
||||
private val log = org.log4s.getLogger("WeaponTurrets")
|
||||
|
||||
/**
|
||||
* The process of upgrading a turret's weapon(s) is completed.
|
||||
* Pass the message onto the turret and onto the vehicle events system.
|
||||
* Additionally, force-deplete the ammunition count of the nano-dispenser used to perform the upgrade.
|
||||
* @param target the turret
|
||||
* @param tool the nano-dispenser that was used to perform this upgrade
|
||||
* @param upgrade the new upgrade state
|
||||
*/
|
||||
def FinishUpgradingMannedTurret(target : FacilityTurret, user : Player, tool : Tool, upgrade : TurretUpgrade.Value)() : Unit = {
|
||||
tool.Magazine = 0
|
||||
target.Zone.AvatarEvents ! AvatarServiceMessage(
|
||||
user.Name,
|
||||
AvatarAction.SendResponse(Service.defaultPlayerGUID, InventoryStateMessage(tool.AmmoSlot.Box.GUID, tool.GUID, 0))
|
||||
)
|
||||
FinishUpgradingMannedTurret(target, upgrade)
|
||||
}
|
||||
|
||||
/**
|
||||
* The process of upgrading a turret's weapon(s) is completed.
|
||||
* * Pass the message onto the turret and onto the vehicle events system.
|
||||
* @see `FacilityTurret`
|
||||
* @see `TurretUpgrade`
|
||||
* @see `TurretUpgrader.AddTask`
|
||||
* @see `TurretUpgrader.ClearSpecific`
|
||||
* @see `VehicleServiceMessage.TurretUpgrade`
|
||||
* @param target the facility turret being upgraded
|
||||
* @param upgrade the upgrade being applied to the turret (usually, it's weapon system)
|
||||
*/
|
||||
def FinishUpgradingMannedTurret(target : FacilityTurret, upgrade : TurretUpgrade.Value) : Unit = {
|
||||
log.info(s"Converting manned wall turret weapon to $upgrade")
|
||||
val zone = target.Zone
|
||||
val events = zone.VehicleEvents
|
||||
events ! VehicleServiceMessage.TurretUpgrade(TurretUpgrader.ClearSpecific(List(target), zone))
|
||||
events ! VehicleServiceMessage.TurretUpgrade(TurretUpgrader.AddTask(target, zone, upgrade))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,423 @@
|
|||
// Copyright (c) 2020 PSForever
|
||||
package net.psforever.objects.vehicles
|
||||
|
||||
import akka.actor.{Actor, Cancellable}
|
||||
import net.psforever.objects.zones.Zone
|
||||
import net.psforever.objects._
|
||||
import net.psforever.objects.vehicles.CargoBehavior.{CheckCargoDismount, CheckCargoMounting}
|
||||
import net.psforever.packet.game.{CargoMountPointStatusMessage, ObjectAttachMessage, ObjectDetachMessage, PlanetsideAttributeMessage}
|
||||
import net.psforever.types.{CargoStatus, PlanetSideGUID, Vector3}
|
||||
import services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||
import services.{RemoverActor, Service}
|
||||
import services.vehicle.{VehicleAction, VehicleServiceMessage}
|
||||
|
||||
import scala.concurrent.duration._
|
||||
|
||||
trait CargoBehavior {
|
||||
_ : Actor =>
|
||||
private var cargoMountTimer : Cancellable = DefaultCancellable.obj
|
||||
private var cargoDismountTimer : Cancellable = DefaultCancellable.obj
|
||||
|
||||
/* gate-keep mounting behavior so that unit does not try to dismount as cargo, or mount different vehicle */
|
||||
private var isMounting : Option[PlanetSideGUID] = None
|
||||
/* gate-keep dismounting behavior so that unit does not try to mount as cargo, or dismount from different vehicle */
|
||||
private var isDismounting : Option[PlanetSideGUID] = None
|
||||
|
||||
def CargoObject : Vehicle
|
||||
|
||||
val cargoBehavior : Receive = {
|
||||
case CheckCargoMounting(carrier_guid, mountPoint, iteration) =>
|
||||
val obj = CargoObject
|
||||
if((isMounting.isEmpty || isMounting.contains(carrier_guid)) && isDismounting.isEmpty &&
|
||||
CargoBehavior.HandleCheckCargoMounting(obj.Zone, carrier_guid, obj.GUID, obj, mountPoint, iteration)) {
|
||||
if(iteration == 0) {
|
||||
//open the cargo bay door
|
||||
obj.Zone.AvatarEvents ! AvatarServiceMessage(
|
||||
obj.Zone.Id,
|
||||
AvatarAction.SendResponse(
|
||||
Service.defaultPlayerGUID,
|
||||
CargoMountPointStatusMessage(carrier_guid, PlanetSideGUID(0), obj.GUID, PlanetSideGUID(0), mountPoint, CargoStatus.InProgress, 0)
|
||||
)
|
||||
)
|
||||
}
|
||||
isMounting = Some(carrier_guid)
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
cargoMountTimer.cancel
|
||||
cargoMountTimer = context.system.scheduler.scheduleOnce(250 milliseconds, self, CheckCargoMounting(carrier_guid, mountPoint, iteration + 1))
|
||||
}
|
||||
else {
|
||||
isMounting = None
|
||||
}
|
||||
|
||||
case CheckCargoDismount(carrier_guid, mountPoint, iteration) =>
|
||||
val obj = CargoObject
|
||||
if((isDismounting.isEmpty || isDismounting.contains(carrier_guid)) && isMounting.isEmpty &&
|
||||
CargoBehavior.HandleCheckCargoDismounting(obj.Zone, carrier_guid, obj.GUID, obj, mountPoint, iteration)) {
|
||||
isDismounting = Some(carrier_guid)
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
cargoDismountTimer.cancel
|
||||
cargoDismountTimer = context.system.scheduler.scheduleOnce(250 milliseconds, self, CheckCargoDismount(carrier_guid, mountPoint, iteration + 1))
|
||||
}
|
||||
else {
|
||||
isDismounting = None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object CargoBehavior {
|
||||
private val log = org.log4s.getLogger("CargoBehavior")
|
||||
|
||||
final case class CheckCargoMounting(carrier_guid: PlanetSideGUID, cargo_mountpoint: Int, iteration: Int)
|
||||
final case class CheckCargoDismount(carrier_guid: PlanetSideGUID, cargo_mountpoint: Int, iteration: Int)
|
||||
|
||||
/**
|
||||
* na
|
||||
* @param carrierGUID the ferrying carrier vehicle
|
||||
* @param cargoGUID the vehicle being ferried as cargo
|
||||
* @param cargo the vehicle being ferried as cargo
|
||||
* @param mountPoint the cargo hold to which the cargo vehicle is stowed
|
||||
* @param iteration number of times a proper mounting for this combination has been queried
|
||||
*/
|
||||
def HandleCheckCargoMounting(zone : Zone, carrierGUID : PlanetSideGUID, cargoGUID : PlanetSideGUID, cargo : Vehicle, mountPoint : Int, iteration : Int) : Boolean = {
|
||||
zone.GUID(carrierGUID) match {
|
||||
case Some(carrier : Vehicle) =>
|
||||
HandleCheckCargoMounting(cargoGUID, cargo, carrierGUID, carrier, mountPoint, iteration)
|
||||
case carrier if iteration > 0 =>
|
||||
log.error(s"HandleCheckCargoMounting: participant vehicles changed in the middle of a mounting event")
|
||||
LogCargoEventMissingVehicleError("HandleCheckCargoMounting: carrier", carrier, carrierGUID)
|
||||
false
|
||||
case _ =>
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* na
|
||||
* @param cargoGUID the vehicle being ferried as cargo
|
||||
* @param cargo the vehicle being ferried as cargo
|
||||
* @param carrierGUID the ferrying carrier vehicle
|
||||
* @param carrier the ferrying carrier vehicle
|
||||
* @param mountPoint the cargo hold to which the cargo vehicle is stowed
|
||||
* @param iteration number of times a proper mounting for this combination has been queried
|
||||
*/
|
||||
private def HandleCheckCargoMounting(cargoGUID : PlanetSideGUID, cargo : Vehicle, carrierGUID : PlanetSideGUID, carrier : Vehicle, mountPoint : Int, iteration : Int) : Boolean = {
|
||||
val zone = carrier.Zone
|
||||
val distance = Vector3.DistanceSquared(cargo.Position, carrier.Position)
|
||||
carrier.CargoHold(mountPoint) match {
|
||||
case Some(hold) if !hold.isOccupied =>
|
||||
log.debug(s"HandleCheckCargoMounting: mount distance between $cargoGUID and $carrierGUID - actual=$distance, target=64")
|
||||
if(distance <= 64) {
|
||||
//cargo vehicle is close enough to assume to be physically within the carrier's hold; mount it
|
||||
log.info(s"HandleCheckCargoMounting: mounting cargo vehicle in carrier at distance of $distance")
|
||||
cargo.MountedIn = carrierGUID
|
||||
hold.Occupant = cargo
|
||||
cargo.Velocity = None
|
||||
zone.VehicleEvents ! VehicleServiceMessage(s"${cargo.Actor}", VehicleAction.SendResponse(PlanetSideGUID(0), PlanetsideAttributeMessage(cargoGUID, 0, cargo.Health)))
|
||||
zone.VehicleEvents ! VehicleServiceMessage(s"${cargo.Actor}", VehicleAction.SendResponse(PlanetSideGUID(0), PlanetsideAttributeMessage(cargoGUID, 68, cargo.Shields)))
|
||||
val (attachMsg, mountPointMsg) = CargoMountBehaviorForAll(carrier, cargo, mountPoint)
|
||||
log.info(s"HandleCheckCargoMounting: $attachMsg")
|
||||
log.info(s"HandleCheckCargoMounting: $mountPointMsg")
|
||||
false
|
||||
}
|
||||
else if(distance > 625 || iteration >= 40) {
|
||||
//vehicles moved too far away or took too long to get into proper position; abort mounting
|
||||
log.info("HandleCheckCargoMounting: cargo vehicle is too far away or didn't mount within allocated time - aborting")
|
||||
val cargoDriverGUID = cargo.Seats(0).Occupant.get.GUID
|
||||
zone.VehicleEvents ! VehicleServiceMessage(
|
||||
zone.Id,
|
||||
VehicleAction.SendResponse(
|
||||
cargoDriverGUID,
|
||||
CargoMountPointStatusMessage(carrierGUID, PlanetSideGUID(0), PlanetSideGUID(0), cargoGUID, mountPoint, CargoStatus.Empty, 0)
|
||||
)
|
||||
)
|
||||
false
|
||||
//sending packet to the cargo vehicle's client results in player locking himself in his vehicle
|
||||
//player gets stuck as "always trying to remount the cargo hold"
|
||||
//obviously, don't do this
|
||||
}
|
||||
else {
|
||||
//cargo vehicle still not in position but there is more time to wait; reschedule check
|
||||
true
|
||||
}
|
||||
case None => ;
|
||||
log.warn(s"HandleCheckCargoMounting: carrier vehicle $carrier does not have a cargo hold #$mountPoint")
|
||||
false
|
||||
case _ =>
|
||||
if(iteration == 0) {
|
||||
log.warn(s"HandleCheckCargoMounting: carrier vehicle $carrier already possesses cargo in hold #$mountPoint; this operation was initiated incorrectly")
|
||||
}
|
||||
else {
|
||||
log.error(s"HandleCheckCargoMounting: something has attached to the carrier vehicle $carrier cargo of hold #$mountPoint while a cargo dismount event was ongoing; stopped at iteration $iteration / 40")
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* na
|
||||
* @param cargoGUID na
|
||||
* @param carrierGUID na
|
||||
* @param mountPoint na
|
||||
* @param iteration na
|
||||
*/
|
||||
def HandleCheckCargoDismounting(zone : Zone, carrierGUID : PlanetSideGUID, cargoGUID : PlanetSideGUID, cargo : Vehicle, mountPoint : Int, iteration : Int) : Boolean = {
|
||||
zone.GUID(carrierGUID) match {
|
||||
case Some(carrier : Vehicle) =>
|
||||
HandleCheckCargoDismounting(cargoGUID, cargo, carrierGUID, carrier, mountPoint, iteration)
|
||||
case carrier if iteration > 0 =>
|
||||
log.error(s"HandleCheckCargoDismounting: participant vehicles changed in the middle of a mounting event")
|
||||
LogCargoEventMissingVehicleError("HandleCheckCargoDismounting: carrier", carrier, carrierGUID)
|
||||
false
|
||||
case _ =>
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* na
|
||||
* @param cargoGUID na
|
||||
* @param cargo na
|
||||
* @param carrierGUID na
|
||||
* @param carrier na
|
||||
* @param mountPoint na
|
||||
* @param iteration na
|
||||
*/
|
||||
private def HandleCheckCargoDismounting(cargoGUID : PlanetSideGUID, cargo : Vehicle, carrierGUID : PlanetSideGUID, carrier : Vehicle, mountPoint : Int, iteration : Int) : Boolean = {
|
||||
val zone = carrier.Zone
|
||||
carrier.CargoHold(mountPoint) match {
|
||||
case Some(hold) if !hold.isOccupied =>
|
||||
val distance = Vector3.DistanceSquared(cargo.Position, carrier.Position)
|
||||
log.debug(s"HandleCheckCargoDismounting: mount distance between $cargoGUID and $carrierGUID - actual=$distance, target=225")
|
||||
if(distance > 225) {
|
||||
//cargo vehicle has moved far enough away; close the carrier's hold door
|
||||
log.info(s"HandleCheckCargoDismounting: dismount of cargo vehicle from carrier complete at distance of $distance")
|
||||
val cargoDriverGUID = cargo.Seats(0).Occupant.get.GUID
|
||||
zone.VehicleEvents ! VehicleServiceMessage(
|
||||
zone.Id,
|
||||
VehicleAction.SendResponse(
|
||||
cargoDriverGUID,
|
||||
CargoMountPointStatusMessage(carrierGUID, PlanetSideGUID(0), PlanetSideGUID(0), cargoGUID, mountPoint, CargoStatus.Empty, 0)
|
||||
)
|
||||
)
|
||||
false
|
||||
//sending packet to the cargo vehicle's client results in player locking himself in his vehicle
|
||||
//player gets stuck as "always trying to remount the cargo hold"
|
||||
//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
|
||||
cargo.MountedIn = carrierGUID
|
||||
hold.Occupant = cargo
|
||||
CargoMountBehaviorForAll(carrier, cargo, mountPoint)
|
||||
false
|
||||
}
|
||||
else {
|
||||
//cargo vehicle did not move far away enough yet and there is more time to wait; reschedule check
|
||||
true
|
||||
}
|
||||
case None =>
|
||||
log.warn(s"HandleCheckCargoDismounting: carrier vehicle $carrier does not have a cargo hold #$mountPoint")
|
||||
false
|
||||
case _ =>
|
||||
if(iteration == 0) {
|
||||
log.warn(s"HandleCheckCargoDismounting: carrier vehicle $carrier will not discharge the cargo of hold #$mountPoint; this operation was initiated incorrectly")
|
||||
}
|
||||
else {
|
||||
log.error(s"HandleCheckCargoDismounting: something has attached to the carrier vehicle $carrier cargo of hold #$mountPoint while a cargo dismount event was ongoing; stopped at iteration $iteration / 40")
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* na
|
||||
* @param player_guid na
|
||||
* @param cargo_guid na
|
||||
* @param bailed na
|
||||
* @param requestedByPassenger na
|
||||
* @param kicked na
|
||||
*/
|
||||
def HandleVehicleCargoDismount(zone : Zone, player_guid : PlanetSideGUID, cargo_guid : PlanetSideGUID, bailed : Boolean, requestedByPassenger : Boolean, kicked : Boolean) : Unit = {
|
||||
zone.GUID(cargo_guid) match {
|
||||
case Some(cargo : Vehicle) =>
|
||||
zone.GUID(cargo.MountedIn) match {
|
||||
case Some(ferry : Vehicle) =>
|
||||
HandleVehicleCargoDismount(player_guid, cargo_guid, cargo, ferry.GUID, ferry, bailed, requestedByPassenger, kicked)
|
||||
case _ =>
|
||||
log.warn(s"DismountVehicleCargo: target ${cargo.Definition.Name}@$cargo_guid does not know what treats it as cargo")
|
||||
}
|
||||
case _ =>
|
||||
log.warn(s"DismountVehicleCargo: target $cargo_guid either is not a vehicle in ${zone.Id} or does not exist")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* na
|
||||
* @param player_guid the target player
|
||||
* @param cargoGUID the globally unique number for the vehicle being ferried
|
||||
* @param cargo the vehicle being ferried
|
||||
* @param carrierGUID the globally unique number for the vehicle doing the ferrying
|
||||
* @param carrier the vehicle doing the ferrying
|
||||
* @param bailed the ferried vehicle is bailing from the cargo hold
|
||||
* @param requestedByPassenger the ferried vehicle is being politely disembarked from the cargo hold
|
||||
* @param kicked the ferried vehicle is being kicked out of the cargo hold
|
||||
*/
|
||||
def HandleVehicleCargoDismount(player_guid : PlanetSideGUID, cargoGUID : PlanetSideGUID, cargo : Vehicle, carrierGUID : PlanetSideGUID, carrier : Vehicle, bailed : Boolean, requestedByPassenger : Boolean, kicked : Boolean) : Unit = {
|
||||
val zone = carrier.Zone
|
||||
carrier.CargoHolds.find({case(_, hold) => hold.Occupant.contains(cargo)}) match {
|
||||
case Some((mountPoint, hold)) =>
|
||||
cargo.MountedIn = None
|
||||
hold.Occupant = None
|
||||
val driverOpt = cargo.Seats(0).Occupant
|
||||
val rotation : Vector3 = if(Vehicles.CargoOrientation(cargo) == 1) { //TODO: BFRs will likely also need this set
|
||||
//dismount router "sideways" in a lodestar
|
||||
carrier.Orientation.xy + Vector3.z((carrier.Orientation.z - 90) % 360)
|
||||
}
|
||||
else {
|
||||
carrier.Orientation
|
||||
}
|
||||
val cargoHoldPosition : Vector3 = if(carrier.Definition == GlobalDefinitions.dropship) {
|
||||
//the galaxy cargo bay is offset backwards from the center of the vehicle
|
||||
carrier.Position + Vector3.Rz(Vector3(0, 7, 0), math.toRadians(carrier.Orientation.z))
|
||||
}
|
||||
else {
|
||||
//the lodestar's cargo hold is almost the center of the vehicle
|
||||
carrier.Position
|
||||
}
|
||||
zone.VehicleEvents ! VehicleServiceMessage(s"${cargo.Actor}", VehicleAction.SendResponse(PlanetSideGUID(0), PlanetsideAttributeMessage(cargoGUID, 0, cargo.Health)))
|
||||
zone.VehicleEvents ! VehicleServiceMessage(s"${cargo.Actor}", VehicleAction.SendResponse(PlanetSideGUID(0), PlanetsideAttributeMessage(cargoGUID, 68, cargo.Shields)))
|
||||
if(carrier.Flying) {
|
||||
//the carrier vehicle is flying; eject the cargo vehicle
|
||||
val ejectCargoMsg = CargoMountPointStatusMessage(carrierGUID, PlanetSideGUID(0), PlanetSideGUID(0), cargoGUID, mountPoint, CargoStatus.InProgress, 0)
|
||||
val detachCargoMsg = ObjectDetachMessage(carrierGUID, cargoGUID, cargoHoldPosition - Vector3.z(1), rotation)
|
||||
val resetCargoMsg = CargoMountPointStatusMessage(carrierGUID, PlanetSideGUID(0), PlanetSideGUID(0), cargoGUID, mountPoint, CargoStatus.Empty, 0)
|
||||
zone.VehicleEvents ! VehicleServiceMessage(zone.Id, VehicleAction.SendResponse(Service.defaultPlayerGUID, ejectCargoMsg))
|
||||
zone.VehicleEvents ! VehicleServiceMessage(zone.Id, VehicleAction.SendResponse(Service.defaultPlayerGUID, detachCargoMsg))
|
||||
zone.VehicleEvents ! VehicleServiceMessage(zone.Id, VehicleAction.SendResponse(Service.defaultPlayerGUID, resetCargoMsg))
|
||||
log.debug(ejectCargoMsg.toString)
|
||||
log.debug(detachCargoMsg.toString)
|
||||
if(driverOpt.isEmpty) {
|
||||
//TODO cargo should drop like a rock like normal; until then, deconstruct it
|
||||
zone.VehicleEvents ! VehicleServiceMessage.Decon(RemoverActor.ClearSpecific(List(cargo), zone))
|
||||
zone.VehicleEvents ! VehicleServiceMessage.Decon(RemoverActor.AddTask(cargo, zone, Some(0 seconds)))
|
||||
}
|
||||
}
|
||||
else {
|
||||
//the carrier vehicle is not flying; just open the door and let the cargo vehicle back out; force it out if necessary
|
||||
val cargoStatusMessage = CargoMountPointStatusMessage(carrierGUID, PlanetSideGUID(0), cargoGUID, PlanetSideGUID(0), mountPoint, CargoStatus.InProgress, 0)
|
||||
val cargoDetachMessage = ObjectDetachMessage(carrierGUID, cargoGUID, cargoHoldPosition + Vector3.z(1f), rotation)
|
||||
zone.VehicleEvents ! VehicleServiceMessage(zone.Id, VehicleAction.SendResponse(Service.defaultPlayerGUID, cargoStatusMessage))
|
||||
zone.VehicleEvents ! VehicleServiceMessage(zone.Id, VehicleAction.SendResponse(Service.defaultPlayerGUID, cargoDetachMessage))
|
||||
driverOpt match {
|
||||
case Some(driver) =>
|
||||
zone.VehicleEvents ! VehicleServiceMessage(s"${driver.Name}", VehicleAction.KickCargo(player_guid, cargo, cargo.Definition.AutoPilotSpeed2, 2500))
|
||||
//check every quarter second if the vehicle has moved far enough away to be considered dismounted
|
||||
cargo.Actor ! CheckCargoDismount(carrierGUID, mountPoint, 0)
|
||||
case None =>
|
||||
val resetCargoMsg = CargoMountPointStatusMessage(carrierGUID, PlanetSideGUID(0), PlanetSideGUID(0), cargoGUID, mountPoint, CargoStatus.Empty, 0)
|
||||
zone.VehicleEvents ! VehicleServiceMessage(zone.Id, VehicleAction.SendResponse(PlanetSideGUID(0), resetCargoMsg)) //lazy
|
||||
//TODO cargo should back out like normal; until then, deconstruct it
|
||||
zone.VehicleEvents ! VehicleServiceMessage.Decon(RemoverActor.ClearSpecific(List(cargo), zone))
|
||||
zone.VehicleEvents ! VehicleServiceMessage.Decon(RemoverActor.AddTask(cargo, zone, Some(0 seconds)))
|
||||
}
|
||||
}
|
||||
|
||||
case None =>
|
||||
log.warn(s"HandleDismountVehicleCargo: can not locate cargo $cargo in any hold of the carrier vehicle $carrier")
|
||||
}
|
||||
}
|
||||
|
||||
//logging and messaging support functions
|
||||
/**
|
||||
* na
|
||||
* @param decorator custom text for these messages in the log
|
||||
* @param target an optional the target object
|
||||
* @param targetGUID the expected globally unique identifier of the target object
|
||||
*/
|
||||
def LogCargoEventMissingVehicleError(decorator : String, target : Option[PlanetSideGameObject], targetGUID : PlanetSideGUID) : Unit = {
|
||||
target match {
|
||||
case Some(_ : Vehicle) => ;
|
||||
case Some(_) => log.error(s"$decorator target $targetGUID no longer identifies as a vehicle")
|
||||
case None => log.error(s"$decorator target $targetGUID has gone missing")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Produce an `ObjectAttachMessage` packet and a `CargoMountPointStatusMessage` packet
|
||||
* that will set up a realized parent-child association between a ferrying vehicle and a ferried vehicle.
|
||||
* @see `CargoMountPointStatusMessage`
|
||||
* @see `ObjectAttachMessage`
|
||||
* @see `Vehicles.CargoOrientation`
|
||||
* @param carrier the ferrying vehicle
|
||||
* @param cargo the ferried vehicle
|
||||
* @param mountPoint the point on the ferryoing vehicle where the ferried vehicle is attached;
|
||||
* also known as a "cargo hold"
|
||||
* @return a tuple composed of an `ObjectAttachMessage` packet and a `CargoMountPointStatusMessage` packet
|
||||
*/
|
||||
def CargoMountMessages(carrier : Vehicle, cargo : Vehicle, mountPoint : Int) : (ObjectAttachMessage, CargoMountPointStatusMessage) = {
|
||||
CargoMountMessages(carrier.GUID, cargo.GUID, mountPoint, Vehicles.CargoOrientation(cargo))
|
||||
}
|
||||
|
||||
/**
|
||||
* Produce an `ObjectAttachMessage` packet and a `CargoMountPointStatusMessage` packet
|
||||
* that will set up a realized parent-child association between a ferrying vehicle and a ferried vehicle.
|
||||
* @see `CargoMountPointStatusMessage`
|
||||
* @see `ObjectAttachMessage`
|
||||
* @param carrierGUID the ferrying vehicle
|
||||
* @param cargoGUID the ferried vehicle
|
||||
* @param mountPoint the point on the ferryoing vehicle where the ferried vehicle is attached
|
||||
* @param orientation the positioning of the cargo vehicle in the carrier cargo bay
|
||||
* @return a tuple composed of an `ObjectAttachMessage` packet and a `CargoMountPointStatusMessage` packet
|
||||
*/
|
||||
def CargoMountMessages(carrierGUID : PlanetSideGUID, cargoGUID : PlanetSideGUID, mountPoint : Int, orientation : Int) : (ObjectAttachMessage, CargoMountPointStatusMessage) = {
|
||||
(
|
||||
ObjectAttachMessage(carrierGUID, cargoGUID, mountPoint),
|
||||
CargoMountPointStatusMessage(carrierGUID, cargoGUID, cargoGUID, PlanetSideGUID(0), mountPoint, CargoStatus.Occupied, orientation)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch an `ObjectAttachMessage` packet and a `CargoMountPointStatusMessage` packet to all other clients, not this one.
|
||||
* @see `CargoMountPointStatusMessage`
|
||||
* @see `ObjectAttachMessage`
|
||||
* @param carrier the ferrying vehicle
|
||||
* @param cargo the ferried vehicle
|
||||
* @param mountPoint the point on the ferryoing vehicle where the ferried vehicle is attached
|
||||
* @return a tuple composed of an `ObjectAttachMessage` packet and a `CargoMountPointStatusMessage` packet
|
||||
*/
|
||||
def CargoMountBehaviorForOthers(carrier : Vehicle, cargo : Vehicle, mountPoint : Int, exclude : PlanetSideGUID) : (ObjectAttachMessage, CargoMountPointStatusMessage) = {
|
||||
val msgs @ (attachMessage, mountPointStatusMessage) = CargoMountMessages(carrier, cargo, mountPoint)
|
||||
CargoMountMessagesForOthers(carrier.Zone, exclude, attachMessage, mountPointStatusMessage)
|
||||
msgs
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch an `ObjectAttachMessage` packet and a `CargoMountPointStatusMessage` packet to all other clients, not this one.
|
||||
* @see `CargoMountPointStatusMessage`
|
||||
* @see `ObjectAttachMessage`
|
||||
* @param attachMessage an `ObjectAttachMessage` packet suitable for initializing cargo operations
|
||||
* @param mountPointStatusMessage a `CargoMountPointStatusMessage` packet suitable for initializing cargo operations
|
||||
*/
|
||||
def CargoMountMessagesForOthers(zone : Zone, exclude : PlanetSideGUID, attachMessage : ObjectAttachMessage, mountPointStatusMessage : CargoMountPointStatusMessage) : Unit = {
|
||||
zone.VehicleEvents ! VehicleServiceMessage(zone.Id, VehicleAction.SendResponse(exclude, attachMessage))
|
||||
zone.VehicleEvents ! VehicleServiceMessage(zone.Id, VehicleAction.SendResponse(exclude, mountPointStatusMessage))
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch an `ObjectAttachMessage` packet and a `CargoMountPointStatusMessage` packet to everyone.
|
||||
* @see `CargoMountPointStatusMessage`
|
||||
* @see `ObjectAttachMessage`
|
||||
* @param carrier the ferrying vehicle
|
||||
* @param cargo the ferried vehicle
|
||||
* @param mountPoint the point on the ferryoing vehicle where the ferried vehicle is attached
|
||||
* @return a tuple composed of an `ObjectAttachMessage` packet and a `CargoMountPointStatusMessage` packet
|
||||
*/
|
||||
def CargoMountBehaviorForAll(carrier : Vehicle, cargo : Vehicle, mountPoint : Int) : (ObjectAttachMessage, CargoMountPointStatusMessage) = {
|
||||
val zone = carrier.Zone
|
||||
val zoneId = zone.Id
|
||||
val msgs @ (attachMessage, mountPointStatusMessage) = CargoMountMessages(carrier, cargo, mountPoint)
|
||||
zone.VehicleEvents ! VehicleServiceMessage(zoneId, VehicleAction.SendResponse(Service.defaultPlayerGUID, attachMessage))
|
||||
zone.VehicleEvents ! VehicleServiceMessage(zoneId, VehicleAction.SendResponse(Service.defaultPlayerGUID, mountPointStatusMessage))
|
||||
msgs
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +1,16 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package net.psforever.objects.vehicles
|
||||
|
||||
import akka.actor.ActorContext
|
||||
import net.psforever.objects.definition.SimpleDeployableDefinition
|
||||
import akka.actor.{ActorContext, ActorRef}
|
||||
import net.psforever.objects.definition.BaseDeployableDefinition
|
||||
import net.psforever.objects._
|
||||
import net.psforever.objects.ce.TelepadLike
|
||||
import net.psforever.objects.serverobject.structures.Amenity
|
||||
import net.psforever.objects.ce.{DeployedItem, TelepadLike}
|
||||
import net.psforever.objects.definition.converter.SmallDeployableConverter
|
||||
import net.psforever.objects.serverobject.PlanetSideServerObject
|
||||
import net.psforever.objects.serverobject.structures.{Amenity, AmenityDefinition}
|
||||
import net.psforever.objects.serverobject.terminals._
|
||||
import net.psforever.objects.serverobject.tube.{SpawnTube, SpawnTubeDefinition}
|
||||
import net.psforever.objects.vehicles.Utility.InternalTelepadDefinition
|
||||
import net.psforever.packet.game.ItemTransactionMessage
|
||||
import net.psforever.types.{PlanetSideGUID, Vector3}
|
||||
|
||||
|
|
@ -193,7 +196,8 @@ object Utility {
|
|||
* and allows it to serve as one of the terminal points of a Router-telepad teleportation system.
|
||||
* @param ddef na
|
||||
*/
|
||||
class InternalTelepad(ddef : SimpleDeployableDefinition) extends Amenity
|
||||
class InternalTelepad(ddef : InternalTelepadDefinition) extends Amenity
|
||||
with UtilityWorldEntity
|
||||
with TelepadLike {
|
||||
/** a link to the telepad that serves as the other endpoint of this teleportation system */
|
||||
private var activeTelepad : Option[PlanetSideGUID] = None
|
||||
|
|
@ -215,6 +219,21 @@ object Utility {
|
|||
def Definition = ddef
|
||||
}
|
||||
|
||||
/**
|
||||
* As the `InternalTelepad` object is a unique intersection of `Amenity` and `TelepadLike`
|
||||
* that is treated like a `Deployable`,
|
||||
* its definition must be a unique intersection of `AmenityDefinition` and `BaseDeployableDefinition`.
|
||||
* @see `AmenityDefinition`
|
||||
* @see `BaseDeployableDefinition`
|
||||
* @see `DeployableDefinition`
|
||||
*/
|
||||
class InternalTelepadDefinition extends AmenityDefinition(DeployedItem.router_telepad_deployable.id)
|
||||
with BaseDeployableDefinition {
|
||||
Packet = new SmallDeployableConverter
|
||||
|
||||
def Item : DeployedItem.Value = DeployedItem.router_telepad_deployable
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide the called-out object's logic.
|
||||
* @param util the type of the `Amenity` object
|
||||
|
|
@ -241,3 +260,15 @@ object Utility {
|
|||
TelepadLike.Setup
|
||||
}
|
||||
}
|
||||
|
||||
object InternalTelepadDefinition {
|
||||
def apply() : InternalTelepadDefinition =
|
||||
new InternalTelepadDefinition()
|
||||
|
||||
def SimpleUninitialize(obj : PlanetSideGameObject, context : ActorContext) : Unit = { }
|
||||
|
||||
def SimpleUninitialize(obj : PlanetSideServerObject, context : ActorContext) : Unit = {
|
||||
context.stop(obj.Actor)
|
||||
obj.Actor = ActorRef.noSender
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,19 +2,18 @@
|
|||
package net.psforever.objects.vehicles
|
||||
|
||||
import akka.actor.{Actor, ActorRef}
|
||||
import net.psforever.objects.{GlobalDefinitions, Player, Vehicle}
|
||||
import net.psforever.objects.{GlobalDefinitions, SimpleItem, Vehicle}
|
||||
import net.psforever.objects.ballistics.{ResolvedProjectile, VehicleSource}
|
||||
import net.psforever.objects.equipment.{JammableMountedWeapons, JammableUnit}
|
||||
import net.psforever.objects.equipment.JammableMountedWeapons
|
||||
import net.psforever.objects.serverobject.CommonMessages
|
||||
import net.psforever.objects.serverobject.mount.{Mountable, MountableBehavior}
|
||||
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
|
||||
import net.psforever.objects.serverobject.deploy.{Deployment, DeploymentBehavior}
|
||||
import net.psforever.objects.vital.{VehicleShieldCharge, Vitality}
|
||||
import net.psforever.objects.zones.Zone
|
||||
import net.psforever.types.{DriveState, ExoSuitType, PlanetSideGUID, Vector3}
|
||||
import services.{RemoverActor, Service}
|
||||
import services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||
import services.local.{LocalAction, LocalServiceMessage}
|
||||
import services.vehicle.{VehicleAction, VehicleService, VehicleServiceMessage}
|
||||
import net.psforever.objects.serverobject.damage.DamageableVehicle
|
||||
import net.psforever.objects.serverobject.deploy.DeploymentBehavior
|
||||
import net.psforever.objects.serverobject.repair.RepairableVehicle
|
||||
import net.psforever.objects.vital.VehicleShieldCharge
|
||||
import net.psforever.types.{ExoSuitType, PlanetSideGUID}
|
||||
import services.vehicle.{VehicleAction, VehicleServiceMessage}
|
||||
|
||||
/**
|
||||
* An `Actor` that handles messages being dispatched to a specific `Vehicle`.<br>
|
||||
|
|
@ -28,32 +27,38 @@ class VehicleControl(vehicle : Vehicle) extends Actor
|
|||
with DeploymentBehavior
|
||||
with MountableBehavior.Mount
|
||||
with MountableBehavior.Dismount
|
||||
with CargoBehavior
|
||||
with DamageableVehicle
|
||||
with RepairableVehicle
|
||||
with JammableMountedWeapons {
|
||||
|
||||
//make control actors belonging to utilities when making control actor belonging to vehicle
|
||||
vehicle.Utilities.foreach({case (_, util) => util.Setup })
|
||||
|
||||
def MountableObject = vehicle
|
||||
|
||||
def CargoObject = vehicle
|
||||
def JammableObject = vehicle
|
||||
|
||||
def FactionObject = vehicle
|
||||
|
||||
def DeploymentObject = vehicle
|
||||
def DamageableObject = vehicle
|
||||
def RepairableObject = vehicle
|
||||
|
||||
def receive : Receive = Enabled
|
||||
|
||||
override def postStop() : Unit = {
|
||||
super.postStop()
|
||||
vehicle.Utilities.values.foreach { util =>
|
||||
util().Actor ! akka.actor.PoisonPill
|
||||
context.stop(util().Actor)
|
||||
util().Actor = ActorRef.noSender
|
||||
}
|
||||
}
|
||||
|
||||
def Enabled : Receive = checkBehavior
|
||||
.orElse(deployBehavior)
|
||||
.orElse(cargoBehavior)
|
||||
.orElse(jammableBehavior)
|
||||
.orElse(takesDamage)
|
||||
.orElse(canBeRepairedByNanoDispenser)
|
||||
.orElse {
|
||||
case msg : Mountable.TryMount =>
|
||||
tryMountBehavior.apply(msg)
|
||||
|
|
@ -61,23 +66,6 @@ class VehicleControl(vehicle : Vehicle) extends Actor
|
|||
case msg : Mountable.TryDismount =>
|
||||
dismountBehavior.apply(msg)
|
||||
|
||||
case Vitality.Damage(damage_func) =>
|
||||
if(vehicle.Health > 0) {
|
||||
val originalHealth = vehicle.Health
|
||||
val originalShields = vehicle.Shields
|
||||
val cause = damage_func(vehicle)
|
||||
val health = vehicle.Health
|
||||
val shields = vehicle.Shields
|
||||
val damageToHealth = originalHealth - health
|
||||
val damageToShields = originalShields - shields
|
||||
VehicleControl.HandleDamageResolution(vehicle, cause, damageToHealth + damageToShields)
|
||||
if(damageToHealth > 0 || damageToShields > 0) {
|
||||
val name = vehicle.Actor.toString
|
||||
val slashPoint = name.lastIndexOf("/")
|
||||
org.log4s.getLogger("DamageResolution").info(s"${name.substring(slashPoint + 1, name.length - 1)}: BEFORE=$originalHealth/$originalShields, AFTER=$health/$shields, CHANGE=$damageToHealth/$damageToShields")
|
||||
}
|
||||
}
|
||||
|
||||
case Vehicle.ChargeShields(amount) =>
|
||||
val now : Long = System.nanoTime
|
||||
//make certain vehicle doesn't charge shields too quickly
|
||||
|
|
@ -95,6 +83,12 @@ class VehicleControl(vehicle : Vehicle) extends Actor
|
|||
}
|
||||
sender ! FactionAffinity.AssertFactionAffinity(vehicle, faction)
|
||||
|
||||
case CommonMessages.Use(player, Some(item : SimpleItem)) if item.Definition == GlobalDefinitions.remote_electronics_kit =>
|
||||
//TODO setup certifications check
|
||||
if(vehicle.Faction != player.Faction) {
|
||||
sender ! CommonMessages.Hack(player, vehicle, Some(item))
|
||||
}
|
||||
|
||||
case Vehicle.PrepareForDeletion() =>
|
||||
CancelJammeredSound(vehicle)
|
||||
CancelJammeredStatus(vehicle)
|
||||
|
|
@ -139,6 +133,12 @@ class VehicleControl(vehicle : Vehicle) extends Actor
|
|||
|
||||
case _ =>
|
||||
}
|
||||
|
||||
override def TryJammerEffectActivate(target : Any, cause : ResolvedProjectile) : Unit = {
|
||||
if(vehicle.MountedIn.isEmpty) {
|
||||
super.TryJammerEffectActivate(target, cause)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object VehicleControl {
|
||||
|
|
@ -160,115 +160,4 @@ object VehicleControl {
|
|||
case _ => false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* na
|
||||
* @param target na
|
||||
*/
|
||||
def HandleDamageResolution(target : Vehicle, cause : ResolvedProjectile, damage : Int) : Unit = {
|
||||
val zone = target.Zone
|
||||
val targetGUID = target.GUID
|
||||
val playerGUID = zone.LivePlayers.find { p => cause.projectile.owner.Name.equals(p.Name) } match {
|
||||
case Some(player) => player.GUID
|
||||
case _ => PlanetSideGUID(0)
|
||||
}
|
||||
if(target.Health > 0) {
|
||||
//activity on map
|
||||
if(damage > 0) {
|
||||
zone.Activity ! Zone.HotSpot.Activity(cause.target, cause.projectile.owner, cause.hit_pos)
|
||||
//alert occupants to damage source
|
||||
HandleDamageAwareness(target, playerGUID, cause)
|
||||
}
|
||||
if(cause.projectile.profile.JammerProjectile) {
|
||||
target.Actor ! JammableUnit.Jammered(cause)
|
||||
}
|
||||
}
|
||||
else {
|
||||
//alert to vehicle death (hence, occupants' deaths)
|
||||
HandleDestructionAwareness(target, playerGUID, cause)
|
||||
}
|
||||
zone.VehicleEvents ! VehicleServiceMessage(zone.Id, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, targetGUID, 0, target.Health))
|
||||
zone.VehicleEvents ! VehicleServiceMessage(s"${target.Actor}", VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, targetGUID, 68, target.Shields))
|
||||
}
|
||||
|
||||
/**
|
||||
* na
|
||||
* @param target na
|
||||
* @param attribution na
|
||||
* @param lastShot na
|
||||
*/
|
||||
def HandleDamageAwareness(target : Vehicle, attribution : PlanetSideGUID, lastShot : ResolvedProjectile) : Unit = {
|
||||
val zone = target.Zone
|
||||
//alert occupants to damage source
|
||||
target.Seats.values.filter(seat => {
|
||||
seat.isOccupied && seat.Occupant.get.isAlive
|
||||
}).foreach(seat => {
|
||||
val tplayer = seat.Occupant.get
|
||||
zone.AvatarEvents ! AvatarServiceMessage(tplayer.Name, AvatarAction.HitHint(attribution, tplayer.GUID))
|
||||
})
|
||||
//alert cargo occupants to damage source
|
||||
target.CargoHolds.values.foreach(hold => {
|
||||
hold.Occupant match {
|
||||
case Some(cargo) =>
|
||||
cargo.Health = 0
|
||||
cargo.Shields = 0
|
||||
cargo.History(lastShot)
|
||||
HandleDamageAwareness(cargo, attribution, lastShot)
|
||||
case None => ;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* na
|
||||
* @param target na
|
||||
* @param attribution na
|
||||
* @param lastShot na
|
||||
*/
|
||||
def HandleDestructionAwareness(target : Vehicle, attribution : PlanetSideGUID, lastShot : ResolvedProjectile) : Unit = {
|
||||
target.Actor ! JammableUnit.ClearJammeredSound()
|
||||
target.Actor ! JammableUnit.ClearJammeredStatus()
|
||||
val zone = target.Zone
|
||||
val continentId = zone.Id
|
||||
//alert to vehicle death (hence, occupants' deaths)
|
||||
target.Seats.values.filter(seat => {
|
||||
seat.isOccupied && seat.Occupant.get.isAlive
|
||||
}).foreach(seat => {
|
||||
val tplayer = seat.Occupant.get
|
||||
tplayer.History(lastShot)
|
||||
tplayer.Actor ! Player.Die()
|
||||
})
|
||||
//vehicle wreckage has no weapons
|
||||
target.Weapons.values
|
||||
.filter {
|
||||
_.Equipment.nonEmpty
|
||||
}
|
||||
.foreach(slot => {
|
||||
val wep = slot.Equipment.get
|
||||
zone.AvatarEvents ! AvatarServiceMessage(continentId, AvatarAction.ObjectDelete(Service.defaultPlayerGUID, wep.GUID))
|
||||
})
|
||||
target.CargoHolds.values.foreach(hold => {
|
||||
hold.Occupant match {
|
||||
case Some(cargo) =>
|
||||
cargo.Health = 0
|
||||
cargo.Shields = 0
|
||||
cargo.Position += Vector3.z(1)
|
||||
cargo.History(lastShot) //necessary to kill cargo vehicle occupants //TODO: collision damage
|
||||
HandleDestructionAwareness(cargo, attribution, lastShot) //might cause redundant packets
|
||||
case None => ;
|
||||
}
|
||||
})
|
||||
target.Definition match {
|
||||
case GlobalDefinitions.ams =>
|
||||
target.Actor ! Deployment.TryDeploymentChange(DriveState.Undeploying)
|
||||
case GlobalDefinitions.router =>
|
||||
target.Actor ! Deployment.TryDeploymentChange(DriveState.Undeploying)
|
||||
VehicleService.BeforeUnloadVehicle(target, zone)
|
||||
zone.LocalEvents ! LocalServiceMessage(zone.Id, LocalAction.ToggleTeleportSystem(PlanetSideGUID(0), target, None))
|
||||
case _ => ;
|
||||
}
|
||||
zone.AvatarEvents ! AvatarServiceMessage(continentId, AvatarAction.Destroy(target.GUID, attribution, attribution, target.Position))
|
||||
zone.VehicleEvents ! VehicleServiceMessage.Decon(RemoverActor.ClearSpecific(List(target), zone))
|
||||
zone.VehicleEvents ! VehicleServiceMessage.Decon(RemoverActor.AddTask(target, zone, Some(1 minute)))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,19 +11,11 @@ import net.psforever.objects.zones.Zone
|
|||
* after it has been loaded to a new location or to a new zone;
|
||||
* this channel name should be unique to the vehicle for at least the duration of the transition;
|
||||
* the vehicle-specific channel with which all passengers are coordinated back to the original vehicle
|
||||
* @param vehicle na
|
||||
* @param origin na
|
||||
* @param driverName na
|
||||
* @param passengers na
|
||||
* @param cargo na
|
||||
*/
|
||||
/**
|
||||
* The channel name for summoning passengers to the vehicle
|
||||
* after it has been loaded to a new location or to a new zone.
|
||||
* This channel name should be unique to the vehicle for at least the duration of the transition.
|
||||
* The vehicle-specific channel with which all passengers are coordinated back to the original vehicle.
|
||||
* @param vehicle the vehicle being moved (or having been moved)
|
||||
* @return the channel name
|
||||
* @param vehicle the vehicle in transport
|
||||
* @param origin where the vehicle originally was
|
||||
* @param driverName the name of the driver when the transport process started
|
||||
* @param passengers the paired names and seat indices of all passengers when the transport process started
|
||||
* @param cargo the paired driver names and cargo hold indices of all cargo vehicles when the transport process started
|
||||
*/
|
||||
final case class VehicleManifest(file : String,
|
||||
vehicle : Vehicle,
|
||||
|
|
@ -55,4 +47,4 @@ object VehicleManifest {
|
|||
def ManifestChannelName(vehicle : Vehicle) : String = {
|
||||
s"transport-vehicle-channel-${vehicle.GUID.guid}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,26 +26,26 @@ import net.psforever.objects.vital.resolution.ResolutionCalculations
|
|||
*/
|
||||
trait DamageResistanceModel {
|
||||
/** the functionality that processes damage; required */
|
||||
private var damage : DamageSelection = NoDamageSelection
|
||||
private var damageUsing : DamageSelection = NoDamageSelection
|
||||
|
||||
/** the functionality that processes resistance; optional */
|
||||
private var resistance : ResistanceSelection = NoResistanceSelection
|
||||
private var resistUsing : ResistanceSelection = NoResistanceSelection
|
||||
|
||||
/** the functionality that prepares for damage application actions; required */
|
||||
private var model : ResolutionCalculations.Form = NoResolutions.Calculate
|
||||
|
||||
def Damage : DamageSelection = damage
|
||||
def DamageUsing : DamageSelection = damageUsing
|
||||
|
||||
def Damage_=(selector : DamageSelection) : DamageSelection = {
|
||||
damage = selector
|
||||
Damage
|
||||
def DamageUsing_=(selector : DamageSelection) : DamageSelection = {
|
||||
damageUsing = selector
|
||||
DamageUsing
|
||||
}
|
||||
|
||||
def Resistance : ResistanceSelection = resistance
|
||||
def ResistUsing : ResistanceSelection = resistUsing
|
||||
|
||||
def Resistance_=(selector : ResistanceSelection) : ResistanceSelection = {
|
||||
resistance = selector
|
||||
Resistance
|
||||
def ResistUsing_=(selector : ResistanceSelection) : ResistanceSelection = {
|
||||
resistUsing = selector
|
||||
ResistUsing
|
||||
}
|
||||
|
||||
def Model : ResolutionCalculations.Form = model
|
||||
|
|
@ -61,8 +61,8 @@ trait DamageResistanceModel {
|
|||
* @return a function literal that encapsulates delayed modification instructions for certain objects
|
||||
*/
|
||||
def Calculate(data : ResolvedProjectile) : ResolutionCalculations.Output = {
|
||||
val dam : ProjectileCalculations.Form = Damage(data)
|
||||
val res : ProjectileCalculations.Form = Resistance(data)
|
||||
val dam : ProjectileCalculations.Form = DamageUsing(data)
|
||||
val res : ProjectileCalculations.Form = ResistUsing(data)
|
||||
Model(dam, res, data)
|
||||
}
|
||||
|
||||
|
|
@ -73,8 +73,8 @@ trait DamageResistanceModel {
|
|||
* @return a function literal that encapsulates delayed modification instructions for certain objects
|
||||
*/
|
||||
def Calculate(data : ResolvedProjectile, resolution : ProjectileResolution.Value) : ResolutionCalculations.Output = {
|
||||
val dam : ProjectileCalculations.Form = Damage(resolution)
|
||||
val res : ProjectileCalculations.Form = Resistance(resolution)
|
||||
val dam : ProjectileCalculations.Form = DamageUsing(resolution)
|
||||
val res : ProjectileCalculations.Form = ResistUsing(resolution)
|
||||
Model(dam, res, data)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -95,6 +95,18 @@ object AircraftLashDamage extends DamageCalculations(
|
|||
DistanceBetweenTargetandSource
|
||||
)
|
||||
|
||||
object AmenityHitDamage extends DamageCalculations(
|
||||
DirectHitDamageWithDegrade,
|
||||
DamageWithModifiers(DamageAgainstVehicle),
|
||||
DistanceBetweenTargetandSource
|
||||
)
|
||||
|
||||
object AmenitySplashDamage extends DamageCalculations(
|
||||
SplashDamageWithRadialDegrade,
|
||||
DamageWithModifiers(DamageAgainstVehicle),
|
||||
DistanceFromExplosionToTarget
|
||||
)
|
||||
|
||||
object NoDamageSelection extends DamageSelection {
|
||||
def Direct = None
|
||||
def Splash = None
|
||||
|
|
@ -130,3 +142,9 @@ object StandardDeployableDamage extends DamageSelection {
|
|||
def Splash = VehicleSplashDamage.Calculate
|
||||
def Lash = NoDamage.Calculate
|
||||
}
|
||||
|
||||
object StandardAmenityDamage extends DamageSelection {
|
||||
def Direct = AmenityHitDamage.Calculate
|
||||
def Splash = AmenitySplashDamage.Calculate
|
||||
def Lash = NoDamage.Calculate
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package net.psforever.objects.vital
|
||||
|
||||
import net.psforever.objects.ballistics.{PlayerSource, SourceEntry, VehicleSource}
|
||||
import net.psforever.objects.ballistics.{ObjectSource, PlayerSource, SourceEntry, VehicleSource}
|
||||
import net.psforever.objects.vital.projectile.ProjectileCalculations
|
||||
import net.psforever.objects.vital.resistance.{ResistanceCalculations, ResistanceSelection}
|
||||
|
||||
|
|
@ -50,6 +50,16 @@ object VehicleAggravatedResistance extends ResistanceCalculations[VehicleSource]
|
|||
ResistanceCalculations.VehicleAggravatedExtractor
|
||||
)
|
||||
|
||||
object AmenityHitResistance extends ResistanceCalculations[ObjectSource](
|
||||
ResistanceCalculations.ValidAmenityTarget,
|
||||
ResistanceCalculations.OtherDirectExtractor
|
||||
)
|
||||
|
||||
object AMenitySplashResistance extends ResistanceCalculations[ObjectSource](
|
||||
ResistanceCalculations.ValidAmenityTarget,
|
||||
ResistanceCalculations.OtherSplashExtractor
|
||||
)
|
||||
|
||||
object NoResistanceSelection extends ResistanceSelection {
|
||||
def Direct : ProjectileCalculations.Form = None
|
||||
def Splash : ProjectileCalculations.Form = None
|
||||
|
|
@ -70,3 +80,10 @@ object StandardVehicleResistance extends ResistanceSelection {
|
|||
def Lash : ProjectileCalculations.Form = VehicleLashResistance.Calculate
|
||||
def Aggravated : ProjectileCalculations.Form = VehicleAggravatedResistance.Calculate
|
||||
}
|
||||
|
||||
object StandardAmenityResistance extends ResistanceSelection {
|
||||
def Direct : ProjectileCalculations.Form = AmenityHitResistance.Calculate
|
||||
def Splash : ProjectileCalculations.Form = AmenityHitResistance.Calculate
|
||||
def Lash : ProjectileCalculations.Form = None
|
||||
def Aggravated : ProjectileCalculations.Form = None
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,4 +41,5 @@ object StandardResolutions extends ResolutionSelection {
|
|||
def SimpleDeployables : ResolutionCalculations.Form = SimpleResolutions.Calculate
|
||||
def ComplexDeployables : ResolutionCalculations.Form = ComplexDeployableResolutions.Calculate
|
||||
def FacilityTurrets : ResolutionCalculations.Form = SimpleResolutions.Calculate
|
||||
def Amenities : ResolutionCalculations.Form = SimpleResolutions.Calculate
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,101 +1,48 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package net.psforever.objects.vital
|
||||
|
||||
import net.psforever.objects.PlanetSideGameObject
|
||||
import net.psforever.objects.ballistics.{PlayerSource, ResolvedProjectile, SourceEntry, VehicleSource}
|
||||
import net.psforever.objects.definition.KitDefinition
|
||||
import net.psforever.objects.serverobject.painbox.Painbox
|
||||
import net.psforever.objects.serverobject.terminals.TerminalDefinition
|
||||
import net.psforever.objects.ballistics.ResolvedProjectile
|
||||
import net.psforever.objects.vital.resolution.ResolutionCalculations
|
||||
import net.psforever.types.{ExoSuitType, ImplantType}
|
||||
|
||||
abstract class VitalsActivity(target : SourceEntry) {
|
||||
def Target : SourceEntry = target
|
||||
val t : Long = System.nanoTime //???
|
||||
|
||||
def time : Long = t
|
||||
}
|
||||
|
||||
abstract class HealingActivity(target : SourceEntry) extends VitalsActivity(target)
|
||||
|
||||
abstract class DamagingActivity(target : SourceEntry) extends VitalsActivity(target)
|
||||
|
||||
final case class HealFromKit(target : PlayerSource, amount : Int, kit_def : KitDefinition) extends HealingActivity(target)
|
||||
|
||||
final case class HealFromTerm(target : PlayerSource, health : Int, armor : Int, term_def : TerminalDefinition) extends HealingActivity(target)
|
||||
|
||||
final case class HealFromImplant(target : PlayerSource, amount : Int, implant : ImplantType.Value) extends HealingActivity(target)
|
||||
|
||||
final case class HealFromExoSuitChange(target : PlayerSource, exosuit : ExoSuitType.Value) extends HealingActivity(target)
|
||||
|
||||
final case class RepairFromKit(target : PlayerSource, amount : Int, kit_def : KitDefinition) extends HealingActivity(target)
|
||||
|
||||
final case class RepairFromTerm(target : VehicleSource, amount : Int, term_def : TerminalDefinition) extends HealingActivity(target)
|
||||
|
||||
final case class VehicleShieldCharge(target : VehicleSource, amount : Int) extends HealingActivity(target) //TODO facility
|
||||
|
||||
final case class DamageFromProjectile(data : ResolvedProjectile) extends DamagingActivity(data.target)
|
||||
|
||||
final case class DamageFromPainbox(target : PlayerSource, painbox : Painbox, damage : Int) extends DamagingActivity(target)
|
||||
|
||||
final case class PlayerSuicide(target : PlayerSource) extends DamagingActivity(target)
|
||||
|
||||
/**
|
||||
* A vital object can be hurt or damaged or healed or repaired (HDHR).
|
||||
* The amount of HDHR is controlled by the damage model of this vital object reacting to stimulus.
|
||||
* A history of the previous changes in vital statistics of the underlying object is recorded
|
||||
* in reverse chronological order.
|
||||
* The damage model is also provided.
|
||||
* The damage model is provided.
|
||||
*/
|
||||
trait Vitality {
|
||||
this : PlanetSideGameObject =>
|
||||
trait Vitality extends VitalsHistory {
|
||||
private var health : Int = Definition.DefaultHealth
|
||||
private var defaultHealth : Option[Int] = None
|
||||
private var maxHealth : Option[Int] = None
|
||||
|
||||
/** a reverse-order list of chronological events that have occurred to these vital statistics */
|
||||
private var vitalHistory : List[VitalsActivity] = List.empty[VitalsActivity]
|
||||
def Health : Int = health
|
||||
|
||||
def History : List[VitalsActivity] = vitalHistory
|
||||
|
||||
/**
|
||||
* A `VitalsActivity` event must be recorded.
|
||||
* Add new entry to the front of the list (for recent activity).
|
||||
* @param action the fully-informed entry
|
||||
* @return the list of previous changes to this object's vital statistics
|
||||
*/
|
||||
def History(action : VitalsActivity) : List[VitalsActivity] = {
|
||||
vitalHistory = action +: vitalHistory
|
||||
vitalHistory
|
||||
def Health_=(assignHealth : Int) : Int = {
|
||||
health = math.min(math.max(0, assignHealth), MaxHealth)
|
||||
Health
|
||||
}
|
||||
|
||||
/**
|
||||
* Very common example of a `VitalsActivity` event involving weapon discharge.
|
||||
* @param projectile the fully-informed entry of discharge of a weapon
|
||||
* @return the list of previous changes to this object's vital statistics
|
||||
*/
|
||||
def History(projectile : ResolvedProjectile) : List[VitalsActivity] = {
|
||||
vitalHistory = DamageFromProjectile(projectile) +: vitalHistory
|
||||
vitalHistory
|
||||
def DefaultHealth : Int = defaultHealth.getOrElse(Definition.DefaultHealth)
|
||||
|
||||
def MaxHealth : Int = maxHealth.getOrElse(Definition.MaxHealth)
|
||||
|
||||
def MaxHealth_=(default : Int) : Int = MaxHealth_=(Some(default))
|
||||
|
||||
def MaxHealth_=(default : Option[Int]) : Int = {
|
||||
maxHealth = default
|
||||
MaxHealth
|
||||
}
|
||||
|
||||
/**
|
||||
* Find, specifically, the last instance of a weapon discharge vital statistics change.
|
||||
* @return information about the discharge
|
||||
*/
|
||||
def LastShot : Option[ResolvedProjectile] = {
|
||||
vitalHistory.find({p => p.isInstanceOf[DamageFromProjectile]}) match {
|
||||
case Some(entry : DamageFromProjectile) =>
|
||||
Some(entry.data)
|
||||
case _ =>
|
||||
None
|
||||
}
|
||||
def CanDamage : Boolean = {
|
||||
Definition.Damageable && Health > 0
|
||||
}
|
||||
|
||||
def ClearHistory() : List[VitalsActivity] = {
|
||||
val out = vitalHistory
|
||||
vitalHistory = List.empty[VitalsActivity]
|
||||
out
|
||||
def CanRepair : Boolean = {
|
||||
Definition.Repairable && Health < MaxHealth && (Health > 0 || Definition.RepairIfDestroyed)
|
||||
}
|
||||
|
||||
def DamageModel : DamageResistanceModel
|
||||
|
||||
def Definition : VitalityDefinition
|
||||
}
|
||||
|
||||
object Vitality {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,131 @@
|
|||
//Copyright (c) 2020 PSForever
|
||||
package net.psforever.objects.vital
|
||||
|
||||
/**
|
||||
* na<br>
|
||||
* <br>
|
||||
* The expected (but not enforced) relationship between values follows:
|
||||
* `0 <= DamageDestroysAt <= DamageDisablesAt < RepairRestoresAt <= MaxHealth`.
|
||||
*/
|
||||
trait VitalityDefinition {
|
||||
/** the maximum amount of health that any of the objects can be allocated;
|
||||
* corresponds to ADB property "maxhealth" */
|
||||
private var maxHealth : Int = 0
|
||||
/** the amount of health that all of the objects are spawned with;
|
||||
* defaults to `MaxHealth` if unset */
|
||||
private var defaultHealth : Option[Int] = None
|
||||
|
||||
def MaxHealth : Int = maxHealth
|
||||
|
||||
def MaxHealth_=(max : Int) : Int = {
|
||||
maxHealth = math.min(math.max(0, max), 65535)
|
||||
MaxHealth
|
||||
}
|
||||
|
||||
def DefaultHealth : Int = defaultHealth.getOrElse(MaxHealth)
|
||||
|
||||
def DefaultHealth_=(default : Int) : Int = DefaultHealth_=(Some(default))
|
||||
|
||||
def DefaultHealth_=(default : Option[Int]) : Int = {
|
||||
defaultHealth = default
|
||||
DefaultHealth
|
||||
}
|
||||
|
||||
/* damageable */
|
||||
/** whether the object type accepts damage;
|
||||
* corresponds to ABD property "damageable" */
|
||||
private var damageable : Boolean = false
|
||||
/** whether the object type accepts damage from allied sources;
|
||||
* corresponds to the opposite of ABD property "damage_immune_to_friendly_fire" */
|
||||
private var damageableByFriendlyFire : Boolean = true
|
||||
/** at what `Health` value the object type is considered "destroyed" */
|
||||
private var damageDestroysAt : Int = 0
|
||||
/** at what `Health` value the object type is considered "disabled";
|
||||
* some object types do not have anything to disable and just transition between "not destroyed" and "destroyed" */
|
||||
private var damageDisablesAt : Option[Int] = None
|
||||
|
||||
def Damageable : Boolean = damageable
|
||||
|
||||
def Damageable_=(state : Boolean) : Boolean = {
|
||||
damageable = state
|
||||
Damageable
|
||||
}
|
||||
|
||||
def DamageableByFriendlyFire : Boolean = damageableByFriendlyFire
|
||||
|
||||
def DamageableByFriendlyFire_=(state : Boolean) : Boolean = {
|
||||
damageableByFriendlyFire = state
|
||||
DamageableByFriendlyFire
|
||||
}
|
||||
|
||||
def DamageDisablesAt : Int = damageDisablesAt.getOrElse(MaxHealth/2)
|
||||
|
||||
def DamageDisablesAt_=(value : Int) : Int = DamageDisablesAt_=(Some(value))
|
||||
|
||||
def DamageDisablesAt_=(value : Option[Int]) : Int = {
|
||||
damageDisablesAt = value
|
||||
DamageDisablesAt
|
||||
}
|
||||
|
||||
def DamageDestroysAt : Int = damageDestroysAt
|
||||
|
||||
def DamageDestroysAt_=(value : Int) : Int = {
|
||||
damageDestroysAt = value
|
||||
DamageDestroysAt
|
||||
}
|
||||
|
||||
/* repairable */
|
||||
/** whether the object type accepts attempts to repair it with a nano dispenser tool;
|
||||
* corresponds to ABD property "canberepairedbynanodispenser" */
|
||||
private var repairable : Boolean = false
|
||||
/** how far away a target can get before repairing is no longer possible;
|
||||
* not exact in the least */
|
||||
private var repairDistance : Float = 5
|
||||
/** if the object type is damaged to the condition of "destroyed",
|
||||
* is it possible to repair it back to the condition of "not destroyed" */
|
||||
private var repairIfDestroyed : Boolean = false
|
||||
/** at what `Health` value the object type is considered "not destroyed";
|
||||
* this state is synonymous with "normal" or "functional";
|
||||
* as thus, it is opposite of both `damageDestroysAt` and `damageDisablesAt` */
|
||||
private var repairRestoresAt : Option[Int] = None
|
||||
/** object type specific modification value that chnages the base repair quality;
|
||||
* treat as additive */
|
||||
private var repairMod : Int = 0
|
||||
|
||||
def Repairable : Boolean = repairable
|
||||
|
||||
def Repairable_=(repair : Boolean) : Boolean = {
|
||||
repairable = repair
|
||||
Repairable
|
||||
}
|
||||
|
||||
def RepairDistance : Float = repairDistance
|
||||
|
||||
def RepairDistance_=(distance : Float) : Float = {
|
||||
repairDistance = math.max(0, distance)
|
||||
RepairDistance
|
||||
}
|
||||
|
||||
def RepairIfDestroyed : Boolean = repairIfDestroyed
|
||||
|
||||
def RepairIfDestroyed_=(repair : Boolean) : Boolean = {
|
||||
repairIfDestroyed = repair
|
||||
RepairIfDestroyed
|
||||
}
|
||||
|
||||
def RepairRestoresAt : Int = repairRestoresAt.getOrElse(MaxHealth/2)
|
||||
|
||||
def RepairRestoresAt_=(restore : Int) : Int = RepairRestoresAt_=(Some(restore))
|
||||
|
||||
def RepairRestoresAt_=(restore : Option[Int]) : Int = {
|
||||
repairRestoresAt = restore
|
||||
RepairRestoresAt
|
||||
}
|
||||
|
||||
def RepairMod : Int = repairMod
|
||||
|
||||
def RepairMod_=(mod : Int) : Int = {
|
||||
repairMod = mod
|
||||
RepairMod
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
// Copyright (c) 2020 PSForever
|
||||
package net.psforever.objects.vital
|
||||
|
||||
import net.psforever.objects.ballistics.{PlayerSource, ResolvedProjectile, SourceEntry, VehicleSource}
|
||||
import net.psforever.objects.definition.{EquipmentDefinition, KitDefinition, ObjectDefinition}
|
||||
import net.psforever.objects.serverobject.painbox.Painbox
|
||||
import net.psforever.objects.serverobject.terminals.TerminalDefinition
|
||||
import net.psforever.types.{ExoSuitType, ImplantType}
|
||||
|
||||
abstract class VitalsActivity(target : SourceEntry) {
|
||||
def Target : SourceEntry = target
|
||||
val t : Long = System.nanoTime //???
|
||||
|
||||
def time : Long = t
|
||||
}
|
||||
|
||||
abstract class HealingActivity(target : SourceEntry) extends VitalsActivity(target)
|
||||
|
||||
abstract class DamagingActivity(target : SourceEntry) extends VitalsActivity(target)
|
||||
|
||||
final case class HealFromKit(target : PlayerSource, amount : Int, kit_def : KitDefinition) extends HealingActivity(target)
|
||||
|
||||
final case class HealFromEquipment(target : PlayerSource, user : PlayerSource, amount : Int, equipment_def : EquipmentDefinition) extends HealingActivity(target)
|
||||
|
||||
final case class HealFromTerm(target : PlayerSource, health : Int, armor : Int, term_def : TerminalDefinition) extends HealingActivity(target)
|
||||
|
||||
final case class HealFromImplant(target : PlayerSource, amount : Int, implant : ImplantType.Value) extends HealingActivity(target)
|
||||
|
||||
final case class HealFromExoSuitChange(target : PlayerSource, exosuit : ExoSuitType.Value) extends HealingActivity(target)
|
||||
|
||||
final case class RepairFromKit(target : PlayerSource, amount : Int, kit_def : KitDefinition) extends HealingActivity(target)
|
||||
|
||||
final case class RepairFromEquipment(target : PlayerSource, user : PlayerSource, amount : Int, equipment_def : EquipmentDefinition) extends HealingActivity(target)
|
||||
|
||||
final case class RepairFromTerm(target : VehicleSource, amount : Int, term_def : TerminalDefinition) extends HealingActivity(target)
|
||||
|
||||
final case class VehicleShieldCharge(target : VehicleSource, amount : Int) extends HealingActivity(target) //TODO facility
|
||||
|
||||
final case class DamageFromProjectile(data : ResolvedProjectile) extends DamagingActivity(data.target)
|
||||
|
||||
final case class DamageFromPainbox(target : PlayerSource, painbox : Painbox, damage : Int) extends DamagingActivity(target)
|
||||
|
||||
final case class PlayerSuicide(target : PlayerSource) extends DamagingActivity(target)
|
||||
|
||||
final case class DamageFromExplosion(target : PlayerSource, cause : ObjectDefinition) extends DamagingActivity(target)
|
||||
|
||||
/**
|
||||
* A vital object can be hurt or damaged or healed or repaired (HDHR).
|
||||
* A history of the previous changes in vital statistics of the underlying object is recorded
|
||||
* in reverse chronological order.
|
||||
*/
|
||||
trait VitalsHistory {
|
||||
/** a reverse-order list of chronological events that have occurred to these vital statistics */
|
||||
private var vitalsHistory : List[VitalsActivity] = List.empty[VitalsActivity]
|
||||
|
||||
def History : List[VitalsActivity] = vitalsHistory
|
||||
|
||||
/**
|
||||
* A `VitalsActivity` event must be recorded.
|
||||
* Add new entry to the front of the list (for recent activity).
|
||||
* @param action the fully-informed entry
|
||||
* @return the list of previous changes to this object's vital statistics
|
||||
*/
|
||||
def History(action : VitalsActivity) : List[VitalsActivity] = History(Some(action))
|
||||
|
||||
/**
|
||||
* A `VitalsActivity` event must be recorded.
|
||||
* Add new entry to the front of the list (for recent activity).
|
||||
* @param action the fully-informed entry
|
||||
* @return the list of previous changes to this object's vital statistics
|
||||
*/
|
||||
def History(action : Option[VitalsActivity]) : List[VitalsActivity] = {
|
||||
action match {
|
||||
case Some(act) =>
|
||||
vitalsHistory = act +: vitalsHistory
|
||||
case None => ;
|
||||
}
|
||||
vitalsHistory
|
||||
}
|
||||
|
||||
/**
|
||||
* Very common example of a `VitalsActivity` event involving weapon discharge.
|
||||
* @param projectile the fully-informed entry of discharge of a weapon
|
||||
* @return the list of previous changes to this object's vital statistics
|
||||
*/
|
||||
def History(projectile : ResolvedProjectile) : List[VitalsActivity] = {
|
||||
vitalsHistory = DamageFromProjectile(projectile) +: vitalsHistory
|
||||
vitalsHistory
|
||||
}
|
||||
|
||||
/**
|
||||
* Find, specifically, the last instance of a weapon discharge vital statistics change.
|
||||
* @return information about the discharge
|
||||
*/
|
||||
def LastShot : Option[ResolvedProjectile] = {
|
||||
vitalsHistory.find({p => p.isInstanceOf[DamageFromProjectile]}) match {
|
||||
case Some(entry : DamageFromProjectile) =>
|
||||
Some(entry.data)
|
||||
case _ =>
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
def ClearHistory() : List[VitalsActivity] = {
|
||||
val out = vitalsHistory
|
||||
vitalsHistory = List.empty[VitalsActivity]
|
||||
out
|
||||
}
|
||||
}
|
||||
|
|
@ -59,7 +59,7 @@ object DamageCalculations {
|
|||
|
||||
def DamageAgainstMaxSuit(profile : DamageProfile) : Int = profile.Damage3
|
||||
|
||||
def DamageAgainstUnknown(profile : DamageProfile) : Int = profile.Damage4
|
||||
def DamageAgainstBFR(profile : DamageProfile) : Int = profile.Damage4
|
||||
|
||||
//raw damage selection functions
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -6,23 +6,28 @@ package net.psforever.objects.vital.damage
|
|||
* In the same way, the five damage modifiers that are applied to the same kind of damage.
|
||||
*/
|
||||
trait DamageProfile {
|
||||
/** `damage0` is for basic infantry */
|
||||
def Damage0 : Int
|
||||
|
||||
/** `damage0` is for basic infantry */
|
||||
def Damage0_=(damage : Int) : Int
|
||||
|
||||
/** `damage1` is for armor, amenities, deployables, etc. */
|
||||
def Damage1 : Int
|
||||
|
||||
/** `damage1` is for armor, amenities, deployables, etc. */
|
||||
def Damage1_=(damage : Int) : Int
|
||||
|
||||
/** `damage2` if for aircraft */
|
||||
def Damage2 : Int
|
||||
|
||||
/** `damage2` if for aircraft */
|
||||
def Damage2_=(damage : Int) : Int
|
||||
|
||||
/** `damage3` is for mechanized infantry */
|
||||
def Damage3 : Int
|
||||
|
||||
/** `damage3` is for mechanized infantry */
|
||||
def Damage3_=(damage : Int) : Int
|
||||
|
||||
/** `damage4` is for battleframe robotics */
|
||||
def Damage4 : Int
|
||||
|
||||
/** `damage4` is for battleframe robotics */
|
||||
def Damage4_=(damage : Int) : Int
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ package net.psforever.objects.vital.resistance
|
|||
import net.psforever.objects.GlobalDefinitions
|
||||
import net.psforever.objects.ballistics._
|
||||
import net.psforever.objects.definition.ExoSuitDefinition
|
||||
import net.psforever.objects.serverobject.structures.Amenity
|
||||
import net.psforever.objects.vital.projectile.ProjectileCalculations
|
||||
import net.psforever.types.ExoSuitType
|
||||
|
||||
|
|
@ -103,6 +104,20 @@ object ResistanceCalculations {
|
|||
}
|
||||
}
|
||||
|
||||
def ValidAmenityTarget(data : ResolvedProjectile) : Try[ObjectSource] = {
|
||||
data.target match {
|
||||
case target : ObjectSource =>
|
||||
if(target.obj.isInstanceOf[Amenity]) {
|
||||
Success(target)
|
||||
}
|
||||
else {
|
||||
failure("something else")
|
||||
}
|
||||
case _ =>
|
||||
failure("something else")
|
||||
}
|
||||
}
|
||||
|
||||
//extractors
|
||||
def NoResistExtractor(target : SourceEntry) : Int = 0
|
||||
|
||||
|
|
@ -122,5 +137,9 @@ object ResistanceCalculations {
|
|||
|
||||
def VehicleRadiationExtractor(target : VehicleSource) : Float = target.Definition.RadiationShielding
|
||||
|
||||
def OtherDirectExtractor(target : ObjectSource) : Int = target.Definition.asInstanceOf[ResistanceProfile].ResistanceDirectHit
|
||||
|
||||
def OtherSplashExtractor(target : ObjectSource) : Int = target.Definition.asInstanceOf[ResistanceProfile].ResistanceSplash
|
||||
|
||||
def MaximumResistance(target : SourceEntry) : Int = Integer.MAX_VALUE
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,11 @@ package net.psforever.objects.vital.resolution
|
|||
import net.psforever.objects.{Player, TurretDeployable, Vehicle}
|
||||
import net.psforever.objects.ballistics.{PlayerSource, ResolvedProjectile}
|
||||
import net.psforever.objects.ce.{ComplexDeployable, Deployable}
|
||||
import net.psforever.objects.serverobject.affinity.FactionAffinity
|
||||
import net.psforever.objects.serverobject.damage.Damageable
|
||||
import net.psforever.objects.serverobject.structures.Amenity
|
||||
import net.psforever.objects.serverobject.turret.FacilityTurret
|
||||
import net.psforever.objects.vital.Vitality
|
||||
import net.psforever.objects.vital.projectile.ProjectileCalculations
|
||||
|
||||
/**
|
||||
|
|
@ -112,10 +116,13 @@ object ResolutionCalculations {
|
|||
def SubtractWithRemainder(current : Int, damage : Int) : (Int, Int) = {
|
||||
val a = Math.max(0, current - damage)
|
||||
val remainingDamage = Math.abs(current - damage - a)
|
||||
|
||||
(a, remainingDamage)
|
||||
}
|
||||
|
||||
private def CanDamage(obj : Vitality with FactionAffinity, damage : Int, data : ResolvedProjectile) : Boolean = {
|
||||
obj.Health > 0 && Damageable.CanDamage(obj, damage, data)
|
||||
}
|
||||
|
||||
/**
|
||||
* The expanded `(Any)=>Unit` function for infantry.
|
||||
* Apply the damage values to the capacitor (if shielded NC max), health field and personal armor field for an infantry target.
|
||||
|
|
@ -130,7 +137,6 @@ object ResolutionCalculations {
|
|||
var result = (0, 0)
|
||||
//TODO Personal Shield implant test should go here and modify the values a and b
|
||||
if(player.isAlive && !(a == 0 && b == 0)) {
|
||||
player.History(data)
|
||||
if(player.Capacitor.toInt > 0 && player.isShielded) {
|
||||
// Subtract armour damage from capacitor
|
||||
result = SubtractWithRemainder(player.Capacitor.toInt, b)
|
||||
|
|
@ -172,8 +178,7 @@ object ResolutionCalculations {
|
|||
*/
|
||||
def VehicleApplication(damage : Int, data : ResolvedProjectile)(target : Any) : ResolvedProjectile = {
|
||||
target match {
|
||||
case vehicle : Vehicle if vehicle.Health > 0 && damage > 0 =>
|
||||
vehicle.History(data)
|
||||
case vehicle : Vehicle if CanDamage(vehicle, damage, data) =>
|
||||
val shields = vehicle.Shields
|
||||
if(shields > damage) {
|
||||
vehicle.Shields = shields - damage
|
||||
|
|
@ -192,12 +197,12 @@ object ResolutionCalculations {
|
|||
|
||||
def SimpleApplication(damage : Int, data : ResolvedProjectile)(target : Any) : ResolvedProjectile = {
|
||||
target match {
|
||||
case obj : Deployable if obj.Health > 0 =>
|
||||
case obj : Deployable if CanDamage(obj, damage, data) =>
|
||||
obj.Health -= damage
|
||||
obj.History(data)
|
||||
case turret : FacilityTurret if turret.Health > 0 =>
|
||||
case turret : FacilityTurret if CanDamage(turret, damage, data) =>
|
||||
turret.Health -= damage
|
||||
turret.History(data)
|
||||
case amenity : Amenity if CanDamage(amenity, damage, data) =>
|
||||
amenity.Health -= damage
|
||||
case _ => ;
|
||||
}
|
||||
data
|
||||
|
|
@ -205,8 +210,7 @@ object ResolutionCalculations {
|
|||
|
||||
def ComplexDeployableApplication(damage : Int, data : ResolvedProjectile)(target : Any) : ResolvedProjectile = {
|
||||
target match {
|
||||
case ce : ComplexDeployable if ce.Health > 0 && damage > 0 =>
|
||||
ce.History(data)
|
||||
case ce : ComplexDeployable if CanDamage(ce, damage, data) =>
|
||||
if(ce.Shields > 0) {
|
||||
if(damage > ce.Shields) {
|
||||
ce.Health -= (damage - ce.Shields)
|
||||
|
|
@ -220,8 +224,7 @@ object ResolutionCalculations {
|
|||
ce.Health -= damage
|
||||
}
|
||||
|
||||
case ce : TurretDeployable if ce.Health > 0 && damage > 0 =>
|
||||
ce.History(data)
|
||||
case ce : TurretDeployable if CanDamage(ce, damage, data) =>
|
||||
if(ce.Shields > 0) {
|
||||
if(damage > ce.Shields) {
|
||||
ce.Health -= (damage - ce.Shields)
|
||||
|
|
|
|||
|
|
@ -402,6 +402,16 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) {
|
|||
})
|
||||
//after all fixed GUID's are defined ...
|
||||
other.foreach(obj => guid.register(obj, "dynamic"))
|
||||
//TODO temporary; convert all old-style ImplantTerminalMech.Constructor with the kind that provides position data
|
||||
import net.psforever.objects.serverobject.implantmech.ImplantTerminalMech
|
||||
import net.psforever.objects.serverobject.terminals.Terminal
|
||||
zoneMap.TerminalToInterface.foreach { case (mech_guid, interface_guid) =>
|
||||
(GUID(mech_guid), GUID(interface_guid)) match {
|
||||
case (Some(mech : ImplantTerminalMech), Some(interface : Terminal)) =>
|
||||
mech.Position = interface.Position
|
||||
case _ => ;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def MakeBuildings(implicit context : ActorContext) : PairMap[Int, Building] = {
|
||||
|
|
@ -561,20 +571,26 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) {
|
|||
|
||||
def AvatarEvents : ActorRef = avatarEvents
|
||||
|
||||
def LocalEvents : ActorRef = localEvents
|
||||
|
||||
def VehicleEvents : ActorRef = vehicleEvents
|
||||
|
||||
//mainly for testing
|
||||
def Activity_=(bus : ActorRef) : ActorRef = {
|
||||
projector = bus
|
||||
Activity
|
||||
}
|
||||
|
||||
def AvatarEvents_=(bus : ActorRef) : ActorRef = {
|
||||
avatarEvents = bus
|
||||
AvatarEvents
|
||||
}
|
||||
|
||||
def LocalEvents : ActorRef = localEvents
|
||||
|
||||
def LocalEvents_=(bus : ActorRef) : ActorRef = {
|
||||
localEvents = bus
|
||||
LocalEvents
|
||||
}
|
||||
|
||||
def VehicleEvents : ActorRef = vehicleEvents
|
||||
|
||||
def VehicleEvents_=(bus : ActorRef) : ActorRef = {
|
||||
vehicleEvents = bus
|
||||
VehicleEvents
|
||||
|
|
|
|||
|
|
@ -89,7 +89,8 @@ class ZoneActor(zone : Zone) extends Actor {
|
|||
//ams
|
||||
zone.Vehicles
|
||||
.filter(veh =>
|
||||
veh.Definition == GlobalDefinitions.ams &&
|
||||
veh.Definition == GlobalDefinitions.ams &&
|
||||
!veh.Destroyed &&
|
||||
veh.DeploymentState == DriveState.Deployed &&
|
||||
veh.Faction == player.Faction
|
||||
)
|
||||
|
|
@ -120,12 +121,12 @@ class ZoneActor(zone : Zone) extends Actor {
|
|||
Set.empty[StructureType.Value]
|
||||
}
|
||||
zone.SpawnGroups()
|
||||
.filter({ case (building, _) =>
|
||||
.filter({ case (building, tubes) =>
|
||||
buildingTypeSet.contains(building.BuildingType) && (building match {
|
||||
case wg : WarpGate =>
|
||||
building.Faction == player.Faction || building.Faction == PlanetSideEmpire.NEUTRAL || wg.Broadcast
|
||||
case _ =>
|
||||
building.Faction == player.Faction
|
||||
building.Faction == player.Faction && !tubes.forall(sp => sp.Offline)
|
||||
})
|
||||
})
|
||||
.toSeq
|
||||
|
|
@ -144,7 +145,10 @@ class ZoneActor(zone : Zone) extends Actor {
|
|||
sender ! Zone.Lattice.SpawnPoint(zone.Id, tube)
|
||||
|
||||
case Some(tubes) =>
|
||||
sender ! Zone.Lattice.SpawnPoint(zone.Id, scala.util.Random.shuffle(tubes).head)
|
||||
val tube = scala.util.Random.shuffle(
|
||||
tubes.filter(sp => !sp.Offline)
|
||||
).head
|
||||
sender ! Zone.Lattice.SpawnPoint(zone.Id, tube)
|
||||
|
||||
case None =>
|
||||
sender ! Zone.Lattice.NoValidSpawnPoint(zone_number, Some(spawn_group))
|
||||
|
|
|
|||
|
|
@ -126,8 +126,8 @@ class ZoneMap(private val name : String) {
|
|||
|
||||
def TerminalToInterface : Map[Int, Int] = linkTerminalInterface
|
||||
|
||||
def TerminalToInterface(interface_guid : Int, terminal_guid : Int) : Unit = {
|
||||
linkTerminalInterface = linkTerminalInterface ++ Map(interface_guid -> terminal_guid)
|
||||
def TerminalToInterface(terminal_guid : Int, interface_guid : Int) : Unit = {
|
||||
linkTerminalInterface = linkTerminalInterface ++ Map(terminal_guid -> interface_guid)
|
||||
}
|
||||
|
||||
def TurretToWeapon : Map[Int, Int] = linkTurretWeapon
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ class ZoneVehicleActor(zone : Zone, vehicleList : ListBuffer[Vehicle]) extends A
|
|||
ZoneVehicleActor.recursiveFindVehicle(vehicleList.iterator, vehicle) match {
|
||||
case Some(index) =>
|
||||
vehicleList.remove(index)
|
||||
vehicle.Actor ! akka.actor.PoisonPill
|
||||
context.stop(vehicle.Actor)
|
||||
vehicle.Actor = ActorRef.noSender
|
||||
case None => ;
|
||||
sender ! Zone.Vehicle.CanNotDespawn(zone, vehicle, "can not find")
|
||||
|
|
|
|||
|
|
@ -2,25 +2,11 @@
|
|||
package net.psforever.packet.game
|
||||
|
||||
import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
|
||||
import net.psforever.types.PlanetSideEmpire
|
||||
import net.psforever.types.{PlanetSideEmpire, PlanetSideGeneratorState}
|
||||
import scodec.{Attempt, Codec, Err}
|
||||
import scodec.codecs._
|
||||
import shapeless.{::, HNil}
|
||||
|
||||
/**
|
||||
* An `Enumeration` `Codec` that represents that various states of a major facility's Generator.
|
||||
*/
|
||||
object PlanetSideGeneratorState extends Enumeration {
|
||||
type Type = Value
|
||||
val Normal,
|
||||
Critical,
|
||||
Destroyed,
|
||||
Unk3
|
||||
= Value
|
||||
|
||||
implicit val codec = PacketHelpers.createEnumerationCodec(this, uintL(2))
|
||||
}
|
||||
|
||||
/**
|
||||
* na
|
||||
* @param unk1 na
|
||||
|
|
|
|||
|
|
@ -7,6 +7,22 @@ import scodec.Codec
|
|||
import scodec.codecs._
|
||||
import shapeless.{::, HNil}
|
||||
|
||||
/**
|
||||
* na
|
||||
* @param unk1 na
|
||||
* @param unk2 if no global unique identifier (below), the alternate identification for the entity
|
||||
* @param unk2a the global unique identifier of the entity inflicting the damage
|
||||
* @param unk2b if no global unique identifier (above), the name of the entity inflicting the damage
|
||||
* @param unk2c if no global unique identifier (above), the object type of the entity inflicting the damage
|
||||
* @param unk3 if no global unique identifier (below), the alternate identification for the entity
|
||||
* @param unk3a the global unique identifier of the entity absorbing the damage
|
||||
* @param unk3b if no global unique identifier (above), the name of the entity absorbing the damage
|
||||
* @param unk3c if no global unique identifier (above), the object type of the entity absorbing the damage
|
||||
* @param unk3d na
|
||||
* @param unk4 na
|
||||
* @param unk5 the amount of damage
|
||||
* @param unk6 na
|
||||
*/
|
||||
final case class DamageFeedbackMessage(unk1 : Int,
|
||||
unk2 : Boolean,
|
||||
unk2a : Option[PlanetSideGUID],
|
||||
|
|
@ -55,12 +71,12 @@ object DamageFeedbackMessage extends Marshallable[DamageFeedbackMessage] {
|
|||
bool >>:~ { u3 =>
|
||||
("unk2a" | conditional(u2, PlanetSideGUID.codec)) ::
|
||||
(("unk2b" | conditional(!u2 && u3, PacketHelpers.encodedWideStringAligned(6))) >>:~ { u2b =>
|
||||
("unk2c" | conditional(!(u2 && u3), uintL(11))) ::
|
||||
("unk2c" | conditional(!u2 && !u3, uintL(11))) ::
|
||||
(bool >>:~ { u5 =>
|
||||
bool >>:~ { u6 =>
|
||||
("unk3a" | conditional(u5, PlanetSideGUID.codec)) ::
|
||||
("unk3b" | conditional(!u5 && u6, PacketHelpers.encodedWideStringAligned( if(u2b.nonEmpty) 3 else 1 ))) ::
|
||||
("unk3c" | conditional(!(u5 && u6), uintL(11))) ::
|
||||
("unk3c" | conditional(!u5 && !u6, uintL(11))) ::
|
||||
("unk3d" | conditional(!u5, uint2)) ::
|
||||
("unk4" | uint(3)) ::
|
||||
("unk5" | uint32L) ::
|
||||
|
|
|
|||
|
|
@ -9,8 +9,17 @@ import scodec.codecs._
|
|||
/**
|
||||
* na<br>
|
||||
* Global:<br>
|
||||
* `50 - Common Initialization?`<br>
|
||||
* `51 - Common Initialization?`<br>
|
||||
* `50 - State initialization for amenities`<br>
|
||||
* <ul>
|
||||
* <li>0 - Normal, accessible ("Press 'e' to ...")</li>
|
||||
* <li>1 - Fully destroyed model, inaccessible ("foo is destroyed and can not be accessed")</li>
|
||||
* </ul>
|
||||
* `51 - Common initialization for amenities, complementary to attribute 50`<br>
|
||||
* <ul>
|
||||
* <li>0 - Normal, accessible</li>
|
||||
* <li>1 - Partially destroyed model, still accessible</li>
|
||||
* <li>2 - Explicitly set in transition from state 1; same as state 1?</li>
|
||||
* </ul>
|
||||
* `67 - ???`<br>
|
||||
* <br>
|
||||
* Global (GUID=0)<br>
|
||||
|
|
@ -50,15 +59,16 @@ import scodec.codecs._
|
|||
* `5 - armorMax`<br>
|
||||
* `6 - PA_RELEASED - transform the (other) avatar in backpack on ground`<br>
|
||||
* `7 - Sets charge level for MAX capacitor`<br>
|
||||
* `8 - Enables empire specific max capacitor function - NC Shield, TR Overdrive, VS Jumpjets`
|
||||
* `9 - Possibly unused now - PA_SHIELDSTRENGTH in beta client`
|
||||
* `8 - Enables empire specific max capacitor function - NC Shield, TR Overdrive, VS Jumpjets`<br>
|
||||
* `9 - Possibly unused now - PA_SHIELDSTRENGTH in beta client`<br>
|
||||
* `14 - Something with grief`<br>
|
||||
* `15 - Weapon Lock. Value exemple : 600 to have 1 min lock. Max possible is 30min lock`<br>
|
||||
* `16 - PA_DECONSTRUCTING in beta client`<br>
|
||||
* `17 - BEP. Value seems to be the same as BattleExperienceMessage`<br>
|
||||
* `18 - CEP.`<br>
|
||||
* `19 - Anchors. Value is 0 to disengage, 1 to engage.`<br>
|
||||
* `20 - Control console hacking. "The FactionName has hacked into BaseName` - also sets timer on CC and yellow base warning lights on<br>
|
||||
* `20 - Control console hacking, affects CC timer, yellow base warning lights and message "The FactionName has hacked into BaseName".
|
||||
* Format is: Time left - 2 bytes, faction - 1 byte (1-4), isResecured - 1 byte (0-1)`<br>
|
||||
* <ul>
|
||||
* <li>65535 segments per faction in deciseconds (seconds * 10)</li>
|
||||
* <li>0-65535 = Neutral 0 seconds to 1h 49m 14s - 0x 00 00 00 00 to 0x FF FF 00 00</li>
|
||||
|
|
@ -67,66 +77,68 @@ import scodec.codecs._
|
|||
* <li>196608 - 262143 - VS - 0x 00 00 03 00</li>
|
||||
* <li>17039360 - CC Resecured - 0x 00 00 04 01</li>
|
||||
* </ul>
|
||||
* <br>These values seem to correspond to the following data structure: Time left - 2 bytes, faction - 1 byte (1-4), isResecured - 1 byte (0-1)<br>
|
||||
* `24 - Learn certifications with value :`<br>
|
||||
* 01 : Medium Assault<br>
|
||||
* 02 : Heavy Assault<br>
|
||||
* 03 : Special Assault<br>
|
||||
* 04 : Anti-Vehicular<br>
|
||||
* 05 : Sniping<br>
|
||||
* 06 : Elite Assault<br>
|
||||
* 07 : Air Cavalry, Scout<br>
|
||||
* 08 : Air Cavalry, Interceptor<br>
|
||||
* 09 : Air Cavalry, Assault<br>
|
||||
* 10 : Air Support<br>
|
||||
* 11 : ATV<br>
|
||||
* 12 : Light Scout<br>
|
||||
* 13 : Assault Buggy<br>
|
||||
* 14 : Armored Assault 1<br>
|
||||
* 15 : Armored Assault 2<br>
|
||||
* 16 : Ground Transport<br>
|
||||
* 17 : Ground Support<br>
|
||||
* 18 : BattleFrame Robotics<br>
|
||||
* 19 : Flail<br>
|
||||
* 20 : Switchblade<br>
|
||||
* 21 : Harasser<br>
|
||||
* 22 : Phantasm<br>
|
||||
* 23 : Galaxy Gunship<br>
|
||||
* 24 : BFR Anti Aircraft<br>
|
||||
* 25 : BFR Anti Infantry<br>
|
||||
* 26 : ?! Removed Cert ?<br>
|
||||
* 27 : ?! Removed Cert ?<br>
|
||||
* 28 : Reinforced ExoSuitDefinition<br>
|
||||
* 29 : Infiltration Suit<br>
|
||||
* 30 : MAX (Burster)<br>
|
||||
* 31 : MAX (Dual-Cycler)<br>
|
||||
* 32 : MAX (Pounder)<br>
|
||||
* 33 : Uni-MAX<br>
|
||||
* 34 : Medical<br>
|
||||
* 35 : Advanced Medical<br>
|
||||
* 36 : Hacking<br>
|
||||
* 37 : Advanced Hacking<br>
|
||||
* 38 : Expert Hacking<br>
|
||||
* 39 : Data Corruption<br>
|
||||
* 40 : Electronics Expert (= Expert Hacking + Data Corruption) Must have Advanced Hacking<br>
|
||||
* 41 : Engineering<br>
|
||||
* 42 : Combat Engineering<br>
|
||||
* 43 : Fortification Engineering<br>
|
||||
* 44 : Assault Engineering<br>
|
||||
* 45 : Advanced Engineering (= Fortification Engineering + Assault Engineering) Must have Combat Engineering<br>
|
||||
* `25 - Forget certifications (same order as 24)`<br>
|
||||
* `24 - Learn certification:`<br>
|
||||
* <ul>
|
||||
* <li>01 - Medium Assault</li>
|
||||
* <li>02 - Heavy Assault</li>
|
||||
* <li>03 - Special Assault</li>
|
||||
* <li>04 - Anti-Vehicular</li>
|
||||
* <li>05 - Sniping</li>
|
||||
* <li>06 - Elite Assault</li>
|
||||
* <li>07 - Air Cavalry, Scout</li>
|
||||
* <li>08 - Air Cavalry, Interceptor</li>
|
||||
* <li>09 - Air Cavalry, Assault</li>
|
||||
* <li>10 - Air Support</li>
|
||||
* <li>11 - ATV</li>
|
||||
* <li>12 - Light Scout</li>
|
||||
* <li>13 - Assault Buggy</li>
|
||||
* <li>14 - Armored Assault 1</li>
|
||||
* <li>15 - Armored Assault 2</li>
|
||||
* <li>16 - Ground Transport</li>
|
||||
* <li>17 - Ground Support</li>
|
||||
* <li>18 - BattleFrame Robotics</li>
|
||||
* <li>19 - Flail</li>
|
||||
* <li>20 - Switchblade</li>
|
||||
* <li>21 - Harasser</li>
|
||||
* <li>22 - Phantasm</li>
|
||||
* <li>23 - Galaxy Gunship</li>
|
||||
* <li>24 - BFR Anti Aircraft</li>
|
||||
* <li>25 - BFR Anti Infantry</li>
|
||||
* <li>26 - ?! Removed Cert ?</li>
|
||||
* <li>27 - ?! Removed Cert ?</li>
|
||||
* <li>28 - Reinforced ExoSuitDefinition</li>
|
||||
* <li>29 - Infiltration Suit</li>
|
||||
* <li>30 - AA MAX</li>
|
||||
* <li>31 - AI MAX</li>
|
||||
* <li>32 - AV MAX</li>
|
||||
* <li>33 - Uni-MAX</li>
|
||||
* <li>34 - Medical</li>
|
||||
* <li>35 - Advanced Medical</li>
|
||||
* <li>36 - Hacking</li>
|
||||
* <li>37 - Advanced Hacking</li>
|
||||
* <li>38 - Expert Hacking</li>
|
||||
* <li>39 - Data Corruption</li>
|
||||
* <li>40 - Electronics Expert (= Expert Hacking + Data Corruption) Must have Advanced Hacking</li>
|
||||
* <li>41 - Engineering</li>
|
||||
* <li>42 - Combat Engineering</li>
|
||||
* <li>43 - Fortification Engineering</li>
|
||||
* <li>44 - Assault Engineering</li>
|
||||
* <li>45 - Advanced Engineering (= Fortification Engineering + Assault Engineering) Must have Combat Engineering</li>
|
||||
* </ul>
|
||||
* `25 - Forget certification: ... (see 24)`<br>
|
||||
* `26 - Certification reset timer (in seconds)`
|
||||
* `27 - PA_JAMMED - plays jammed buzzing sound in vicinity of target, jams weapon discharge`<br>
|
||||
* `28 - PA_IMPLANT_ACTIVE - Plays implant sounds. Valid values seem to be up to 20.`<br>
|
||||
* `29 - PA_VAPORIZED - Visible ?! That's not the cloaked effect, Maybe for spectator mode ?. Value is 0 to visible, 1 to invisible.`<br>
|
||||
* `31 - Looking for Squad info (marquee and ui):`<br>
|
||||
* ` - 0 is LFS`<br>
|
||||
* ` - 1 is LFSM (Looking for Squad Members)`<br>
|
||||
* ` - n is the supplemental squad identifier number; same as "LFS;" for the leader, sets "LFSM" after the first manual flagging`<br>
|
||||
* `32 - Maintain the squad role index, when a member of a squad;<br>
|
||||
* - OLD: "Info under avatar name : 0 = Looking For Squad Members, 1 = LFS`"<br>
|
||||
* `35 - BR. Value is the BR`<br>
|
||||
* `36 - CR. Value is the CR`<br>
|
||||
* <ul>
|
||||
* <li>0 - LFS</li>
|
||||
* <li>1 is LFSM (Looking for Squad Members)`</li>
|
||||
* <li>`n` is the supplemental squad identifier number; same as "LFS;" for the leader, sets "LFSM" after the first manual flagging`</li>
|
||||
* </ul>
|
||||
* `32 - Maintain the squad role index, when a member of a squad`<br>
|
||||
* `35 - Battle Rank`<br>
|
||||
* `36 - Command Rank`<br>
|
||||
* `38 - Spawn active or not. MUST use base MapId not base GUID`<br>
|
||||
* `43 - Info on avatar name : 0 = Nothing, 1 = "(LD)" message`<br>
|
||||
* `45 - NTU charge bar 0-10, 5 = 50% full. Seems to apply to both ANT and NTU Silo (possibly siphons?)`<br>
|
||||
|
|
@ -136,13 +148,15 @@ import scodec.codecs._
|
|||
* `49 - Vehicle texture effects state? (>0 turns on ANT panel glow or ntu silo panel glow + orbs) (bit?)`<br>
|
||||
* `52 - Vehicle particle effects? (>0 turns on orbs going towards ANT. Doesn't affect silo) (bit?)`<br>
|
||||
* `53 - LFS. Value is 1 to flag LFS`<br>
|
||||
* `54 - Player "Aura". Values can be expressed in the first byte's lower nibble:`<br>
|
||||
* - 0 is nothing<br>
|
||||
* - 1 is plasma<br>
|
||||
* - 2 is ancient<br>
|
||||
* - 4 is LLU (?)<br>
|
||||
* - 8 is fire<br>
|
||||
* -- e.g., 13 = 8 + 4 + 1 = fire and LLU and plasma<br>
|
||||
* `54 - Player "Aura". Values can be expressed in the first byte's lower nibble:`
|
||||
* <ul>
|
||||
* <li>0 - nothing</li>
|
||||
* <li>1 - plasma</li>
|
||||
* <li>2 - ancient</li>
|
||||
* <li>4 - LLU (?)</li>
|
||||
* <li>8 - fire</li>
|
||||
* <li>e.g., 13 = 8 + 4 + 1 = fire and LLU and plasma</li>
|
||||
* </ul>
|
||||
* `55 - "Someone is attempting to Heal you". Value is 1`<br>
|
||||
* `56 - "Someone is attempting to Repair you". Value is 1`<br>
|
||||
* `64 - ????? related to using router telepads`
|
||||
|
|
@ -151,14 +165,25 @@ import scodec.codecs._
|
|||
* `77 - Cavern Facility Captures. Value is the number of captures`<br>
|
||||
* `78 - Cavern Kills. Value is the number of kills`<br>
|
||||
* `106 - Custom Head`<br>
|
||||
* `116 - Apply colour to REK beam and REK icon above players (0 = yellow, 1 = red, 2 = purple, 3 = blue)`<br>
|
||||
* `116 - Apply colour to REK beam and REK icon above players`
|
||||
* <ul>
|
||||
* <li>0 = yellow</li>
|
||||
* <li>1 = red</li>
|
||||
* <li>2 = purple</li>
|
||||
* <li>3 = blue</li>
|
||||
* </ul>
|
||||
* Client to Server : <br>
|
||||
* `106 - Custom Head`<br>
|
||||
* `224 - Player/vehicle joins black ops`<br>
|
||||
* `228 - Player/vehicle leaves black ops`<br>
|
||||
* <br>
|
||||
* `Vehicles:`<br>
|
||||
* `10 - Driver seat permissions (0 = Locked, 1 = Group, 3 = Empire)`<br>
|
||||
* `10 - Driver seat permissions`
|
||||
* <ul>
|
||||
* <li>0 - Locked</li>
|
||||
* <li>1 - Group</li>
|
||||
* <li>3 - Empire</li>
|
||||
* </ul>
|
||||
* `11 - Gunner seat(s) permissions (same)`<br>
|
||||
* `12 - Passenger seat(s) permissions (same)`<br>
|
||||
* `13 - Trunk permissions (same)`<br>
|
||||
|
|
@ -170,7 +195,6 @@ import scodec.codecs._
|
|||
* `80 - Damage vehicle (unknown value)`<br>
|
||||
* `81 - ???`<br>
|
||||
* `113 - Vehicle capacitor - e.g. Leviathan EMP charge`
|
||||
*
|
||||
* @param guid the object
|
||||
* @param attribute_type the field
|
||||
* @param attribute_value the value
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue