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:
Fate-JH 2020-04-14 15:17:32 -04:00 committed by GitHub
parent 840006dca8
commit c2f6baf551
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
124 changed files with 8530 additions and 2503 deletions

View file

@ -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 _ => ;
}
}
}

View file

@ -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))
}
}
}

View file

@ -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
}
}

View file

@ -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
}

View file

@ -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}"

View 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))
}
}

View file

@ -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)?
}
}

View file

@ -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))
}
}

View file

@ -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;

View file

@ -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)
}
}

View file

@ -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)
}
}

View file

@ -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)

View file

@ -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 _ => ;
}
}
}

View file

@ -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))

View file

@ -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
}

View file

@ -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

View file

@ -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 {

View file

@ -8,7 +8,5 @@ abstract class SimpleDeployable(cdef : SimpleDeployableDefinition) extends Plane
with Deployable {
Health = Definition.MaxHealth
def MaxHealth : Int = Definition.MaxHealth
def Definition = cdef
}

View file

@ -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
}

View file

@ -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

View file

@ -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
}
}

View file

@ -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

View file

@ -17,7 +17,7 @@ class SmallDeployableConverter extends ObjectCreateConverter[PlanetSideGameObjec
CommonFieldData(
obj.Faction,
bops = false,
alternate = false,
alternate = obj.Destroyed,
false,
None,
jammered = obj match {

View file

@ -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()
}

View file

@ -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
}
}

View file

@ -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))
}
}

View file

@ -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))
}
}

View file

@ -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()
})
}
}

View file

@ -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()
}
}

View file

@ -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 _ =>
}
}
}

View file

@ -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.

View file

@ -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"
}

View file

@ -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

View file

@ -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)
}
}
}

View file

@ -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"
}

View file

@ -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}")
}
}
}

View file

@ -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 {

View file

@ -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

View file

@ -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
}
}

View file

@ -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

View file

@ -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()
}
}

View file

@ -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 */

View file

@ -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
}
}

View file

@ -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"
}

View file

@ -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))
}
}

View file

@ -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 _ => ;
}
}

View file

@ -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"
}

View file

@ -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)
}
}
}

View file

@ -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

View file

@ -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

View file

@ -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
}
}

View file

@ -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))
}
}

View file

@ -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
}

View file

@ -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 */
}
}

View file

@ -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)
)
}
}
}

View file

@ -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

View file

@ -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")

View file

@ -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"
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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)
}

View file

@ -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))
}
}
}

View file

@ -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

View file

@ -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
}
}

View file

@ -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"

View file

@ -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}")
}
}
}

View file

@ -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)

View file

@ -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)
}
/**

View file

@ -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 _ => ;
}

View file

@ -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

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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))
}
}

View file

@ -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
}

View file

@ -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

View file

@ -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)

View file

@ -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))
}
}

View file

@ -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
}
}

View file

@ -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
}
}

View file

@ -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)))
}
}

View file

@ -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}"
}
}
}

View file

@ -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)
}
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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 {

View file

@ -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
}
}

View file

@ -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
}
}

View file

@ -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
/**

View file

@ -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
}

View file

@ -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
}

View file

@ -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)

View file

@ -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

View file

@ -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))

View file

@ -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

View file

@ -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")

View file

@ -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

View file

@ -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) ::

View file

@ -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