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 package net.psforever.objects
import akka.actor.ActorRef import akka.actor.ActorRef
import scala.concurrent.duration._
import scala.concurrent.duration._
import net.psforever.objects.ce.{Deployable, DeployedItem} 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.objects.zones.Zone
import net.psforever.packet.game.{DeployableInfo, DeploymentAction} import net.psforever.packet.game.{DeployableInfo, DeploymentAction}
import net.psforever.types.PlanetSideGUID import net.psforever.types.PlanetSideGUID
@ -13,6 +13,8 @@ import services.RemoverActor
import services.local.{LocalAction, LocalServiceMessage} import services.local.{LocalAction, LocalServiceMessage}
object Deployables { object Deployables {
private val log = org.log4s.getLogger("Deployables")
object Make { object Make {
def apply(item : DeployedItem.Value) : ()=>PlanetSideGameObject with Deployable = cemap(item) 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; * @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) * `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 val zone = target.Zone
target.OwnerName match { target.OwnerName match {
case Some(owner) => case Some(owner) =>
@ -102,4 +104,23 @@ object Deployables {
}) })
boomers ++ 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 package net.psforever.objects
import akka.actor.{Actor, ActorContext, Props} 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.definition.converter.SmallDeployableConverter
import net.psforever.objects.equipment.JammableUnit import net.psforever.objects.equipment.JammableUnit
import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.damage.Damageable
import net.psforever.objects.vital.{StandardResolutions, Vitality} import net.psforever.objects.vital.{StandardResolutions, Vitality}
import net.psforever.objects.zones.Zone
import net.psforever.types.{PlanetSideGUID, Vector3} import net.psforever.types.{PlanetSideGUID, Vector3}
import services.Service
import services.avatar.{AvatarAction, AvatarServiceMessage} import services.avatar.{AvatarAction, AvatarServiceMessage}
import services.local.{LocalAction, LocalServiceMessage} import services.local.{LocalAction, LocalServiceMessage}
@ -17,14 +20,6 @@ import scala.concurrent.duration._
class ExplosiveDeployable(cdef : ExplosiveDeployableDefinition) extends ComplexDeployable(cdef) class ExplosiveDeployable(cdef : ExplosiveDeployableDefinition) extends ComplexDeployable(cdef)
with JammableUnit { with JammableUnit {
private var exploded : Boolean = false
def Exploded : Boolean = exploded
def Exploded_=(fuse : Boolean) : Boolean = {
exploded = fuse
Exploded
}
override def Definition : ExplosiveDeployableDefinition = cdef override def Definition : ExplosiveDeployableDefinition = cdef
} }
@ -59,38 +54,48 @@ object ExplosiveDeployableDefinition {
} }
} }
class ExplosiveDeployableControl(mine : ExplosiveDeployable) extends Actor { class ExplosiveDeployableControl(mine : ExplosiveDeployable) extends Actor
def receive : Receive = { with Damageable {
case Vitality.Damage(damage_func) => def DamageableObject = mine
val originalHealth = mine.Health
if(originalHealth > 0) {
val cause = damage_func(mine)
ExplosiveDeployableControl.HandleDamageResolution(mine, cause, originalHealth - mine.Health)
}
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 { object ExplosiveDeployableControl {
def HandleDamageResolution(target : ExplosiveDeployable, cause : ResolvedProjectile, damage : Int) : Unit = { def DamageResolution(target : ExplosiveDeployable, cause : ResolvedProjectile, damage : Int) : Unit = {
val zone = target.Zone target.History(cause)
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) { 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 = { if(target.Jammed = {
val radius = cause.projectile.profile.DamageRadius val radius = cause.projectile.profile.DamageRadius
Vector3.DistanceSquared(cause.hit_pos, cause.target.Position) < radius * radius Vector3.DistanceSquared(cause.hit_pos, cause.target.Position) < radius * radius
}) { }) {
if(target.Definition.DetonateOnJamming) { 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 * na
* @param target na * @param target na
* @param attribution na * @param cause na
* @param lastShot na
*/ */
def HandleDestructionAwareness(target : ExplosiveDeployable, attribution : PlanetSideGUID, lastShot : ResolvedProjectile) : Unit = { def DestructionAwareness(target : ExplosiveDeployable, cause : ResolvedProjectile) : Unit = {
val zone = target.Zone 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)) 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.resourcesilo.ResourceSiloDefinition
import net.psforever.objects.serverobject.structures.SphereOfInfluence import net.psforever.objects.serverobject.structures.SphereOfInfluence
import net.psforever.objects.serverobject.turret.{FacilityTurretDefinition, TurretUpgrade} 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.objects.vital.{DamageType, StandardMaxDamage, StandardResolutions}
import net.psforever.types.{CertificationType, ExoSuitType, PlanetSideEmpire, Vector3} import net.psforever.types.{CertificationType, ExoSuitType, PlanetSideEmpire, Vector3}
@ -29,6 +29,8 @@ import scala.concurrent.duration._
object GlobalDefinitions { object GlobalDefinitions {
// Characters // Characters
val avatar = new AvatarDefinition(121) val avatar = new AvatarDefinition(121)
avatar.MaxHealth = 100
avatar.Damageable = true
/* /*
exo-suits exo-suits
*/ */
@ -889,7 +891,7 @@ object GlobalDefinitions {
val sensor_shield = SensorDeployableDefinition(DeployedItem.sensor_shield) 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) val portable_manned_turret = TurretDeployableDefinition(DeployedItem.portable_manned_turret)
@ -904,7 +906,7 @@ object GlobalDefinitions {
val router_telepad_deployable = SimpleDeployableDefinition(DeployedItem.router_telepad_deployable) val router_telepad_deployable = SimpleDeployableDefinition(DeployedItem.router_telepad_deployable)
//this is only treated like a 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() init_deployables()
/* /*
@ -1626,7 +1628,7 @@ object GlobalDefinitions {
max.ResistanceDirectHit = 6 max.ResistanceDirectHit = 6
max.ResistanceSplash = 35 max.ResistanceSplash = 35
max.ResistanceAggravated = 10 max.ResistanceAggravated = 10
max.Damage = StandardMaxDamage max.DamageUsing = StandardMaxDamage
max.Model = StandardResolutions.Max max.Model = StandardResolutions.Max
} }
@ -4470,7 +4472,10 @@ object GlobalDefinitions {
nano_dispenser.FireModes.head.AmmoSlotIndex = 0 nano_dispenser.FireModes.head.AmmoSlotIndex = 0
nano_dispenser.FireModes.head.Magazine = 100 nano_dispenser.FireModes.head.Magazine = 100
nano_dispenser.FireModes.head.CustomMagazine = Ammo.upgrade_canister -> 1 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.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.FireModes.head.Modifiers.Damage4 = 20
nano_dispenser.Tile = InventoryTile.Tile63 nano_dispenser.Tile = InventoryTile.Tile63
@ -5156,6 +5161,9 @@ object GlobalDefinitions {
private def init_vehicles() : Unit = { private def init_vehicles() : Unit = {
fury.Name = "fury" fury.Name = "fury"
fury.MaxHealth = 650 fury.MaxHealth = 650
fury.Damageable = true
fury.Repairable = true
fury.RepairIfDestroyed = false
fury.MaxShields = 130 + 1 fury.MaxShields = 130 + 1
fury.Seats += 0 -> new SeatDefinition() fury.Seats += 0 -> new SeatDefinition()
fury.Seats(0).Bailable = true fury.Seats(0).Bailable = true
@ -5171,6 +5179,9 @@ object GlobalDefinitions {
quadassault.Name = "quadassault" // Basilisk quadassault.Name = "quadassault" // Basilisk
quadassault.MaxHealth = 650 quadassault.MaxHealth = 650
quadassault.Damageable = true
quadassault.Repairable = true
quadassault.RepairIfDestroyed = false
quadassault.MaxShields = 130 + 1 quadassault.MaxShields = 130 + 1
quadassault.Seats += 0 -> new SeatDefinition() quadassault.Seats += 0 -> new SeatDefinition()
quadassault.Seats(0).Bailable = true quadassault.Seats(0).Bailable = true
@ -5186,6 +5197,9 @@ object GlobalDefinitions {
quadstealth.Name = "quadstealth" // Wraith quadstealth.Name = "quadstealth" // Wraith
quadstealth.MaxHealth = 650 quadstealth.MaxHealth = 650
quadstealth.Damageable = true
quadstealth.Repairable = true
quadstealth.RepairIfDestroyed = false
quadstealth.MaxShields = 130 + 1 quadstealth.MaxShields = 130 + 1
quadstealth.CanCloak = true quadstealth.CanCloak = true
quadstealth.Seats += 0 -> new SeatDefinition() 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.Name = "two_man_assault_buggy" // Harasser
two_man_assault_buggy.MaxHealth = 1250 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.MaxShields = 250 + 1
two_man_assault_buggy.Seats += 0 -> new SeatDefinition() two_man_assault_buggy.Seats += 0 -> new SeatDefinition()
two_man_assault_buggy.Seats(0).Bailable = true two_man_assault_buggy.Seats(0).Bailable = true
@ -5218,6 +5235,9 @@ object GlobalDefinitions {
skyguard.Name = "skyguard" skyguard.Name = "skyguard"
skyguard.MaxHealth = 1000 skyguard.MaxHealth = 1000
skyguard.Damageable = true
skyguard.Repairable = true
skyguard.RepairIfDestroyed = false
skyguard.MaxShields = 200 + 1 skyguard.MaxShields = 200 + 1
skyguard.Seats += 0 -> new SeatDefinition() skyguard.Seats += 0 -> new SeatDefinition()
skyguard.Seats(0).Bailable = true skyguard.Seats(0).Bailable = true
@ -5236,6 +5256,9 @@ object GlobalDefinitions {
threemanheavybuggy.Name = "threemanheavybuggy" // Marauder threemanheavybuggy.Name = "threemanheavybuggy" // Marauder
threemanheavybuggy.MaxHealth = 1700 threemanheavybuggy.MaxHealth = 1700
threemanheavybuggy.Damageable = true
threemanheavybuggy.Repairable = true
threemanheavybuggy.RepairIfDestroyed = false
threemanheavybuggy.MaxShields = 340 + 1 threemanheavybuggy.MaxShields = 340 + 1
threemanheavybuggy.Seats += 0 -> new SeatDefinition() threemanheavybuggy.Seats += 0 -> new SeatDefinition()
threemanheavybuggy.Seats(0).Bailable = true threemanheavybuggy.Seats(0).Bailable = true
@ -5259,6 +5282,9 @@ object GlobalDefinitions {
twomanheavybuggy.Name = "twomanheavybuggy" // Enforcer twomanheavybuggy.Name = "twomanheavybuggy" // Enforcer
twomanheavybuggy.MaxHealth = 1800 twomanheavybuggy.MaxHealth = 1800
twomanheavybuggy.Damageable = true
twomanheavybuggy.Repairable = true
twomanheavybuggy.RepairIfDestroyed = false
twomanheavybuggy.MaxShields = 360 + 1 twomanheavybuggy.MaxShields = 360 + 1
twomanheavybuggy.Seats += 0 -> new SeatDefinition() twomanheavybuggy.Seats += 0 -> new SeatDefinition()
twomanheavybuggy.Seats(0).Bailable = true twomanheavybuggy.Seats(0).Bailable = true
@ -5277,6 +5303,9 @@ object GlobalDefinitions {
twomanhoverbuggy.Name = "twomanhoverbuggy" // Thresher twomanhoverbuggy.Name = "twomanhoverbuggy" // Thresher
twomanhoverbuggy.MaxHealth = 1600 twomanhoverbuggy.MaxHealth = 1600
twomanhoverbuggy.Damageable = true
twomanhoverbuggy.Repairable = true
twomanhoverbuggy.RepairIfDestroyed = false
twomanhoverbuggy.MaxShields = 320 + 1 twomanhoverbuggy.MaxShields = 320 + 1
twomanhoverbuggy.Seats += 0 -> new SeatDefinition() twomanhoverbuggy.Seats += 0 -> new SeatDefinition()
twomanhoverbuggy.Seats(0).Bailable = true twomanhoverbuggy.Seats(0).Bailable = true
@ -5295,6 +5324,9 @@ object GlobalDefinitions {
mediumtransport.Name = "mediumtransport" // Deliverer mediumtransport.Name = "mediumtransport" // Deliverer
mediumtransport.MaxHealth = 2500 mediumtransport.MaxHealth = 2500
mediumtransport.Damageable = true
mediumtransport.Repairable = true
mediumtransport.RepairIfDestroyed = false
mediumtransport.MaxShields = 500 + 1 mediumtransport.MaxShields = 500 + 1
mediumtransport.Seats += 0 -> new SeatDefinition() mediumtransport.Seats += 0 -> new SeatDefinition()
mediumtransport.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax mediumtransport.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax
@ -5320,6 +5352,9 @@ object GlobalDefinitions {
battlewagon.Name = "battlewagon" // Raider battlewagon.Name = "battlewagon" // Raider
battlewagon.MaxHealth = 2500 battlewagon.MaxHealth = 2500
battlewagon.Damageable = true
battlewagon.Repairable = true
battlewagon.RepairIfDestroyed = false
battlewagon.MaxShields = 500 + 1 battlewagon.MaxShields = 500 + 1
battlewagon.Seats += 0 -> new SeatDefinition() battlewagon.Seats += 0 -> new SeatDefinition()
battlewagon.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax battlewagon.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax
@ -5348,6 +5383,9 @@ object GlobalDefinitions {
thunderer.Name = "thunderer" thunderer.Name = "thunderer"
thunderer.MaxHealth = 2500 thunderer.MaxHealth = 2500
thunderer.Damageable = true
thunderer.Repairable = true
thunderer.RepairIfDestroyed = false
thunderer.MaxShields = 500 + 1 thunderer.MaxShields = 500 + 1
thunderer.Seats += 0 -> new SeatDefinition() thunderer.Seats += 0 -> new SeatDefinition()
thunderer.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax thunderer.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax
@ -5373,6 +5411,9 @@ object GlobalDefinitions {
aurora.Name = "aurora" aurora.Name = "aurora"
aurora.MaxHealth = 2500 aurora.MaxHealth = 2500
aurora.Damageable = true
aurora.Repairable = true
aurora.RepairIfDestroyed = false
aurora.MaxShields = 500 + 1 aurora.MaxShields = 500 + 1
aurora.Seats += 0 -> new SeatDefinition() aurora.Seats += 0 -> new SeatDefinition()
aurora.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax aurora.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax
@ -5398,6 +5439,9 @@ object GlobalDefinitions {
apc_tr.Name = "apc_tr" // Juggernaut apc_tr.Name = "apc_tr" // Juggernaut
apc_tr.MaxHealth = 6000 apc_tr.MaxHealth = 6000
apc_tr.Damageable = true
apc_tr.Repairable = true
apc_tr.RepairIfDestroyed = false
apc_tr.MaxShields = 1200 + 1 apc_tr.MaxShields = 1200 + 1
apc_tr.Seats += 0 -> new SeatDefinition() apc_tr.Seats += 0 -> new SeatDefinition()
apc_tr.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax apc_tr.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax
@ -5445,6 +5489,9 @@ object GlobalDefinitions {
apc_nc.Name = "apc_nc" // Vindicator apc_nc.Name = "apc_nc" // Vindicator
apc_nc.MaxHealth = 6000 apc_nc.MaxHealth = 6000
apc_nc.Damageable = true
apc_nc.Repairable = true
apc_nc.RepairIfDestroyed = false
apc_nc.MaxShields = 1200 + 1 apc_nc.MaxShields = 1200 + 1
apc_nc.Seats += 0 -> new SeatDefinition() apc_nc.Seats += 0 -> new SeatDefinition()
apc_nc.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax apc_nc.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax
@ -5492,6 +5539,9 @@ object GlobalDefinitions {
apc_vs.Name = "apc_vs" // Leviathan apc_vs.Name = "apc_vs" // Leviathan
apc_vs.MaxHealth = 6000 apc_vs.MaxHealth = 6000
apc_vs.Damageable = true
apc_vs.Repairable = true
apc_vs.RepairIfDestroyed = false
apc_vs.MaxShields = 1200 + 1 apc_vs.MaxShields = 1200 + 1
apc_vs.Seats += 0 -> new SeatDefinition() apc_vs.Seats += 0 -> new SeatDefinition()
apc_vs.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax apc_vs.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax
@ -5539,6 +5589,9 @@ object GlobalDefinitions {
lightning.Name = "lightning" lightning.Name = "lightning"
lightning.MaxHealth = 2000 lightning.MaxHealth = 2000
lightning.Damageable = true
lightning.Repairable = true
lightning.RepairIfDestroyed = false
lightning.MaxShields = 400 + 1 lightning.MaxShields = 400 + 1
lightning.Seats += 0 -> new SeatDefinition() lightning.Seats += 0 -> new SeatDefinition()
lightning.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax lightning.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax
@ -5555,6 +5608,9 @@ object GlobalDefinitions {
prowler.Name = "prowler" prowler.Name = "prowler"
prowler.MaxHealth = 4800 prowler.MaxHealth = 4800
prowler.Damageable = true
prowler.Repairable = true
prowler.RepairIfDestroyed = false
prowler.MaxShields = 960 + 1 prowler.MaxShields = 960 + 1
prowler.Seats += 0 -> new SeatDefinition() prowler.Seats += 0 -> new SeatDefinition()
prowler.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax prowler.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax
@ -5576,6 +5632,9 @@ object GlobalDefinitions {
vanguard.Name = "vanguard" vanguard.Name = "vanguard"
vanguard.MaxHealth = 5400 vanguard.MaxHealth = 5400
vanguard.Damageable = true
vanguard.Repairable = true
vanguard.RepairIfDestroyed = false
vanguard.MaxShields = 1080 + 1 vanguard.MaxShields = 1080 + 1
vanguard.Seats += 0 -> new SeatDefinition() vanguard.Seats += 0 -> new SeatDefinition()
vanguard.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax vanguard.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax
@ -5593,6 +5652,9 @@ object GlobalDefinitions {
magrider.Name = "magrider" magrider.Name = "magrider"
magrider.MaxHealth = 4200 magrider.MaxHealth = 4200
magrider.Damageable = true
magrider.Repairable = true
magrider.RepairIfDestroyed = false
magrider.MaxShields = 840 + 1 magrider.MaxShields = 840 + 1
magrider.Seats += 0 -> new SeatDefinition() magrider.Seats += 0 -> new SeatDefinition()
magrider.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax magrider.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax
@ -5613,6 +5675,9 @@ object GlobalDefinitions {
val utilityConverter = new UtilityVehicleConverter val utilityConverter = new UtilityVehicleConverter
ant.Name = "ant" ant.Name = "ant"
ant.MaxHealth = 2000 ant.MaxHealth = 2000
ant.Damageable = true
ant.Repairable = true
ant.RepairIfDestroyed = false
ant.MaxShields = 400 + 1 ant.MaxShields = 400 + 1
ant.Seats += 0 -> new SeatDefinition() ant.Seats += 0 -> new SeatDefinition()
ant.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax ant.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax
@ -5630,6 +5695,9 @@ object GlobalDefinitions {
ams.Name = "ams" ams.Name = "ams"
ams.MaxHealth = 3000 ams.MaxHealth = 3000
ams.Damageable = true
ams.Repairable = true
ams.RepairIfDestroyed = false
ams.MaxShields = 600 + 1 ams.MaxShields = 600 + 1
ams.Seats += 0 -> new SeatDefinition() ams.Seats += 0 -> new SeatDefinition()
ams.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax ams.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax
@ -5652,6 +5720,9 @@ object GlobalDefinitions {
val variantConverter = new VariantVehicleConverter val variantConverter = new VariantVehicleConverter
router.Name = "router" router.Name = "router"
router.MaxHealth = 4000 router.MaxHealth = 4000
router.Damageable = true
router.Repairable = true
router.RepairIfDestroyed = false
router.MaxShields = 800 + 1 router.MaxShields = 800 + 1
router.Seats += 0 -> new SeatDefinition() router.Seats += 0 -> new SeatDefinition()
router.MountPoints += 1 -> 0 router.MountPoints += 1 -> 0
@ -5671,6 +5742,9 @@ object GlobalDefinitions {
switchblade.Name = "switchblade" switchblade.Name = "switchblade"
switchblade.MaxHealth = 1750 switchblade.MaxHealth = 1750
switchblade.Damageable = true
switchblade.Repairable = true
switchblade.RepairIfDestroyed = false
switchblade.MaxShields = 350 + 1 switchblade.MaxShields = 350 + 1
switchblade.Seats += 0 -> new SeatDefinition() switchblade.Seats += 0 -> new SeatDefinition()
switchblade.Seats(0).ControlledWeapon = 1 switchblade.Seats(0).ControlledWeapon = 1
@ -5691,6 +5765,9 @@ object GlobalDefinitions {
flail.Name = "flail" flail.Name = "flail"
flail.MaxHealth = 2400 flail.MaxHealth = 2400
flail.Damageable = true
flail.Repairable = true
flail.RepairIfDestroyed = false
flail.MaxShields = 480 + 1 flail.MaxShields = 480 + 1
flail.Seats += 0 -> new SeatDefinition() flail.Seats += 0 -> new SeatDefinition()
flail.Seats(0).ControlledWeapon = 1 flail.Seats(0).ControlledWeapon = 1
@ -5709,6 +5786,9 @@ object GlobalDefinitions {
mosquito.Name = "mosquito" mosquito.Name = "mosquito"
mosquito.MaxHealth = 665 mosquito.MaxHealth = 665
mosquito.Damageable = true
mosquito.Repairable = true
mosquito.RepairIfDestroyed = false
mosquito.MaxShields = 133 + 1 mosquito.MaxShields = 133 + 1
mosquito.CanFly = true mosquito.CanFly = true
mosquito.Seats += 0 -> new SeatDefinition() mosquito.Seats += 0 -> new SeatDefinition()
@ -5726,6 +5806,9 @@ object GlobalDefinitions {
lightgunship.Name = "lightgunship" // Reaver lightgunship.Name = "lightgunship" // Reaver
lightgunship.MaxHealth = 1000 lightgunship.MaxHealth = 1000
lightgunship.Damageable = true
lightgunship.Repairable = true
lightgunship.RepairIfDestroyed = false
lightgunship.MaxShields = 200 + 1 lightgunship.MaxShields = 200 + 1
lightgunship.CanFly = true lightgunship.CanFly = true
lightgunship.Seats += 0 -> new SeatDefinition() lightgunship.Seats += 0 -> new SeatDefinition()
@ -5744,6 +5827,9 @@ object GlobalDefinitions {
wasp.Name = "wasp" wasp.Name = "wasp"
wasp.MaxHealth = 515 wasp.MaxHealth = 515
wasp.Damageable = true
wasp.Repairable = true
wasp.RepairIfDestroyed = false
wasp.MaxShields = 103 + 1 wasp.MaxShields = 103 + 1
wasp.CanFly = true wasp.CanFly = true
wasp.Seats += 0 -> new SeatDefinition() wasp.Seats += 0 -> new SeatDefinition()
@ -5761,6 +5847,9 @@ object GlobalDefinitions {
liberator.Name = "liberator" liberator.Name = "liberator"
liberator.MaxHealth = 2500 liberator.MaxHealth = 2500
liberator.Damageable = true
liberator.Repairable = true
liberator.RepairIfDestroyed = false
liberator.MaxShields = 500 + 1 liberator.MaxShields = 500 + 1
liberator.CanFly = true liberator.CanFly = true
liberator.Seats += 0 -> new SeatDefinition() liberator.Seats += 0 -> new SeatDefinition()
@ -5786,6 +5875,9 @@ object GlobalDefinitions {
vulture.Name = "vulture" vulture.Name = "vulture"
vulture.MaxHealth = 2500 vulture.MaxHealth = 2500
vulture.Damageable = true
vulture.Repairable = true
vulture.RepairIfDestroyed = false
vulture.MaxShields = 500 + 1 vulture.MaxShields = 500 + 1
vulture.CanFly = true vulture.CanFly = true
vulture.Seats += 0 -> new SeatDefinition() vulture.Seats += 0 -> new SeatDefinition()
@ -5811,6 +5903,10 @@ object GlobalDefinitions {
dropship.Name = "dropship" // Galaxy dropship.Name = "dropship" // Galaxy
dropship.MaxHealth = 5000 dropship.MaxHealth = 5000
dropship.Damageable = true
dropship.Repairable = true
dropship.RepairDistance = 20
dropship.RepairIfDestroyed = false
dropship.MaxShields = 1000 + 1 dropship.MaxShields = 1000 + 1
dropship.CanFly = true dropship.CanFly = true
dropship.Seats += 0 -> new SeatDefinition() dropship.Seats += 0 -> new SeatDefinition()
@ -5868,6 +5964,10 @@ object GlobalDefinitions {
galaxy_gunship.Name = "galaxy_gunship" galaxy_gunship.Name = "galaxy_gunship"
galaxy_gunship.MaxHealth = 6000 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.MaxShields = 1200 + 1
galaxy_gunship.CanFly = true galaxy_gunship.CanFly = true
galaxy_gunship.Seats += 0 -> new SeatDefinition() galaxy_gunship.Seats += 0 -> new SeatDefinition()
@ -5902,6 +6002,10 @@ object GlobalDefinitions {
lodestar.Name = "lodestar" lodestar.Name = "lodestar"
lodestar.MaxHealth = 5000 lodestar.MaxHealth = 5000
lodestar.Damageable = true
lodestar.Repairable = true
lodestar.RepairDistance = 20
lodestar.RepairIfDestroyed = false
lodestar.MaxShields = 1000 + 1 lodestar.MaxShields = 1000 + 1
lodestar.CanFly = true lodestar.CanFly = true
lodestar.Seats += 0 -> new SeatDefinition() lodestar.Seats += 0 -> new SeatDefinition()
@ -5926,6 +6030,9 @@ object GlobalDefinitions {
phantasm.Name = "phantasm" phantasm.Name = "phantasm"
phantasm.MaxHealth = 2500 phantasm.MaxHealth = 2500
phantasm.Damageable = true
phantasm.Repairable = true
phantasm.RepairIfDestroyed = false
phantasm.MaxShields = 500 + 1 phantasm.MaxShields = 500 + 1
phantasm.CanCloak = true phantasm.CanCloak = true
phantasm.CanFly = true phantasm.CanFly = true
@ -5958,23 +6065,35 @@ object GlobalDefinitions {
boomer.Name = "boomer" boomer.Name = "boomer"
boomer.Descriptor = "Boomers" boomer.Descriptor = "Boomers"
boomer.MaxHealth = 100 boomer.MaxHealth = 100
boomer.Damageable = true
boomer.DamageableByFriendlyFire = false
boomer.Repairable = false
boomer.DeployCategory = DeployableCategory.Boomers boomer.DeployCategory = DeployableCategory.Boomers
boomer.DeployTime = Duration.create(1000, "ms") boomer.DeployTime = Duration.create(1000, "ms")
he_mine.Name = "he_mine" he_mine.Name = "he_mine"
he_mine.Descriptor = "Mines" he_mine.Descriptor = "Mines"
he_mine.MaxHealth = 100 he_mine.MaxHealth = 100
he_mine.Damageable = true
he_mine.DamageableByFriendlyFire = false
he_mine.Repairable = false
he_mine.DeployTime = Duration.create(1000, "ms") he_mine.DeployTime = Duration.create(1000, "ms")
jammer_mine.Name = "jammer_mine" jammer_mine.Name = "jammer_mine"
jammer_mine.Descriptor = "JammerMines" jammer_mine.Descriptor = "JammerMines"
jammer_mine.MaxHealth = 100 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.DeployTime = Duration.create(1000, "ms")
jammer_mine.DetonateOnJamming = false jammer_mine.DetonateOnJamming = false
spitfire_turret.Name = "spitfire_turret" spitfire_turret.Name = "spitfire_turret"
spitfire_turret.Descriptor = "Spitfires" spitfire_turret.Descriptor = "Spitfires"
spitfire_turret.MaxHealth = 100 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 -> new mutable.HashMap()
spitfire_turret.Weapons(1) += TurretUpgrade.None -> spitfire_weapon spitfire_turret.Weapons(1) += TurretUpgrade.None -> spitfire_weapon
spitfire_turret.ReserveAmmunition = false spitfire_turret.ReserveAmmunition = false
@ -5985,6 +6104,9 @@ object GlobalDefinitions {
spitfire_cloaked.Name = "spitfire_cloaked" spitfire_cloaked.Name = "spitfire_cloaked"
spitfire_cloaked.Descriptor = "CloakingSpitfires" spitfire_cloaked.Descriptor = "CloakingSpitfires"
spitfire_cloaked.MaxHealth = 100 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 -> new mutable.HashMap()
spitfire_cloaked.Weapons(1) += TurretUpgrade.None -> spitfire_weapon spitfire_cloaked.Weapons(1) += TurretUpgrade.None -> spitfire_weapon
spitfire_cloaked.ReserveAmmunition = false spitfire_cloaked.ReserveAmmunition = false
@ -5995,6 +6117,9 @@ object GlobalDefinitions {
spitfire_aa.Name = "spitfire_aa" spitfire_aa.Name = "spitfire_aa"
spitfire_aa.Descriptor = "FlakSpitfires" spitfire_aa.Descriptor = "FlakSpitfires"
spitfire_aa.MaxHealth = 100 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 -> new mutable.HashMap()
spitfire_aa.Weapons(1) += TurretUpgrade.None -> spitfire_aa_weapon spitfire_aa.Weapons(1) += TurretUpgrade.None -> spitfire_aa_weapon
spitfire_aa.ReserveAmmunition = false spitfire_aa.ReserveAmmunition = false
@ -6005,17 +6130,25 @@ object GlobalDefinitions {
motionalarmsensor.Name = "motionalarmsensor" motionalarmsensor.Name = "motionalarmsensor"
motionalarmsensor.Descriptor = "MotionSensors" motionalarmsensor.Descriptor = "MotionSensors"
motionalarmsensor.MaxHealth = 100 motionalarmsensor.MaxHealth = 100
motionalarmsensor.Damageable = true
motionalarmsensor.Repairable = true
motionalarmsensor.RepairIfDestroyed = false
motionalarmsensor.DeployTime = Duration.create(1000, "ms") motionalarmsensor.DeployTime = Duration.create(1000, "ms")
sensor_shield.Name = "sensor_shield" sensor_shield.Name = "sensor_shield"
sensor_shield.Descriptor = "SensorShields" sensor_shield.Descriptor = "SensorShields"
sensor_shield.MaxHealth = 100 sensor_shield.MaxHealth = 100
sensor_shield.Damageable = true
sensor_shield.Repairable = true
sensor_shield.RepairIfDestroyed = false
sensor_shield.DeployTime = Duration.create(5000, "ms") sensor_shield.DeployTime = Duration.create(5000, "ms")
tank_traps.Name = "tank_traps" tank_traps.Name = "tank_traps"
tank_traps.Descriptor = "TankTraps" tank_traps.Descriptor = "TankTraps"
tank_traps.MaxHealth = 5000 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.DeployCategory = DeployableCategory.TankTraps
tank_traps.DeployTime = Duration.create(6000, "ms") tank_traps.DeployTime = Duration.create(6000, "ms")
tank_traps.Model = StandardResolutions.SimpleDeployables tank_traps.Model = StandardResolutions.SimpleDeployables
@ -6024,6 +6157,9 @@ object GlobalDefinitions {
portable_manned_turret.Name = "portable_manned_turret" portable_manned_turret.Name = "portable_manned_turret"
portable_manned_turret.Descriptor = "FieldTurrets" portable_manned_turret.Descriptor = "FieldTurrets"
portable_manned_turret.MaxHealth = 1000 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 += 1 -> 0
portable_manned_turret.MountPoints += 2 -> 0 portable_manned_turret.MountPoints += 2 -> 0
portable_manned_turret.Weapons += 1 -> new mutable.HashMap() 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.Name = "portable_manned_turret_nc"
portable_manned_turret_nc.Descriptor = "FieldTurrets" portable_manned_turret_nc.Descriptor = "FieldTurrets"
portable_manned_turret_nc.MaxHealth = 1000 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 += 1 -> 0
portable_manned_turret_nc.MountPoints += 2 -> 0 portable_manned_turret_nc.MountPoints += 2 -> 0
portable_manned_turret_nc.Weapons += 1 -> new mutable.HashMap() 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.Name = "portable_manned_turret_tr"
portable_manned_turret_tr.Descriptor = "FieldTurrets" portable_manned_turret_tr.Descriptor = "FieldTurrets"
portable_manned_turret_tr.MaxHealth = 1000 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 += 1 -> 0
portable_manned_turret_tr.MountPoints += 2 -> 0 portable_manned_turret_tr.MountPoints += 2 -> 0
portable_manned_turret_tr.Weapons += 1 -> new mutable.HashMap() 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.Name = "portable_manned_turret_vs"
portable_manned_turret_vs.Descriptor = "FieldTurrets" portable_manned_turret_vs.Descriptor = "FieldTurrets"
portable_manned_turret_vs.MaxHealth = 1000 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 += 1 -> 0
portable_manned_turret_vs.MountPoints += 2 -> 0 portable_manned_turret_vs.MountPoints += 2 -> 0
portable_manned_turret_vs.Weapons += 1 -> new mutable.HashMap() 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.Name = "deployable_shield_generator"
deployable_shield_generator.Descriptor = "ShieldGenerators" deployable_shield_generator.Descriptor = "ShieldGenerators"
deployable_shield_generator.MaxHealth = 1700 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.DeployTime = Duration.create(6000, "ms")
deployable_shield_generator.Model = StandardResolutions.ComplexDeployables deployable_shield_generator.Model = StandardResolutions.ComplexDeployables
router_telepad_deployable.Name = "router_telepad_deployable" router_telepad_deployable.Name = "router_telepad_deployable"
router_telepad_deployable.MaxHealth = 100 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.DeployTime = Duration.create(1, "ms")
router_telepad_deployable.DeployCategory = DeployableCategory.Telepads
router_telepad_deployable.Packet = new TelepadDeployableConverter router_telepad_deployable.Packet = new TelepadDeployableConverter
router_telepad_deployable.Model = StandardResolutions.SimpleDeployables router_telepad_deployable.Model = StandardResolutions.SimpleDeployables
internal_router_telepad_deployable.Name = "router_telepad_deployable" internal_router_telepad_deployable.Name = "router_telepad_deployable"
internal_router_telepad_deployable.MaxHealth = 1 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.DeployTime = Duration.create(1, "ms")
internal_router_telepad_deployable.DeployCategory = DeployableCategory.Telepads
internal_router_telepad_deployable.Packet = new InternalTelepadDeployableConverter internal_router_telepad_deployable.Packet = new InternalTelepadDeployableConverter
} }
@ -6102,14 +6256,24 @@ object GlobalDefinitions {
ams_respawn_tube.Name = "ams_respawn_tube" ams_respawn_tube.Name = "ams_respawn_tube"
ams_respawn_tube.Delay = 5 ams_respawn_tube.Delay = 5
ams_respawn_tube.SpecificPointFunc = SpawnPoint.AMS ams_respawn_tube.SpecificPointFunc = SpawnPoint.AMS
ams_respawn_tube.Damageable = false
ams_respawn_tube.Repairable = false
matrix_terminala.Name = "matrix_terminala" matrix_terminala.Name = "matrix_terminala"
matrix_terminala.Damageable = false
matrix_terminala.Repairable = false
matrix_terminalb.Name = "matrix_terminalb" matrix_terminalb.Name = "matrix_terminalb"
matrix_terminalb.Damageable = false
matrix_terminalb.Repairable = false
matrix_terminalc.Name = "matrix_terminalc" matrix_terminalc.Name = "matrix_terminalc"
matrix_terminalc.Damageable = false
matrix_terminalc.Repairable = false
spawn_terminal.Name = "spawn_terminal" spawn_terminal.Name = "spawn_terminal"
spawn_terminal.Damageable = false
spawn_terminal.Repairable = false
order_terminal.Name = "order_terminal" order_terminal.Name = "order_terminal"
order_terminal.Tab += 0 -> OrderTerminalDefinition.EquipmentPage(EquipmentTerminalDefinition.infantryAmmunition ++ EquipmentTerminalDefinition.infantryWeapons) 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 += 3 -> OrderTerminalDefinition.EquipmentPage(EquipmentTerminalDefinition.vehicleAmmunition)
order_terminal.Tab += 4 -> OrderTerminalDefinition.InfantryLoadoutPage() order_terminal.Tab += 4 -> OrderTerminalDefinition.InfantryLoadoutPage()
order_terminal.SellEquipmentByDefault = true 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.Name = "order_terminala"
order_terminala.Tab += 0 -> OrderTerminalDefinition.EquipmentPage(EquipmentTerminalDefinition.infantryAmmunition ++ EquipmentTerminalDefinition.infantryWeapons) 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 -> OrderTerminalDefinition.InfantryLoadoutPage()
order_terminala.Tab(4).asInstanceOf[OrderTerminalDefinition.InfantryLoadoutPage].Exclude = ExoSuitType.MAX order_terminala.Tab(4).asInstanceOf[OrderTerminalDefinition.InfantryLoadoutPage].Exclude = ExoSuitType.MAX
order_terminala.SellEquipmentByDefault = true order_terminala.SellEquipmentByDefault = true
order_terminala.Damageable = false
order_terminala.Repairable = false
order_terminalb.Name = "order_terminalb" order_terminalb.Name = "order_terminalb"
order_terminalb.Tab += 0 -> OrderTerminalDefinition.EquipmentPage(EquipmentTerminalDefinition.infantryAmmunition ++ EquipmentTerminalDefinition.infantryWeapons) 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 -> OrderTerminalDefinition.InfantryLoadoutPage()
order_terminalb.Tab(4).asInstanceOf[OrderTerminalDefinition.InfantryLoadoutPage].Exclude = ExoSuitType.MAX order_terminalb.Tab(4).asInstanceOf[OrderTerminalDefinition.InfantryLoadoutPage].Exclude = ExoSuitType.MAX
order_terminalb.SellEquipmentByDefault = true order_terminalb.SellEquipmentByDefault = true
order_terminalb.Damageable = false
order_terminalb.Repairable = false
vanu_equipment_term.Name = "vanu_equipment_term" vanu_equipment_term.Name = "vanu_equipment_term"
vanu_equipment_term.Tab += 0 -> OrderTerminalDefinition.EquipmentPage(EquipmentTerminalDefinition.infantryAmmunition ++ EquipmentTerminalDefinition.infantryWeapons) 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 += 3 -> OrderTerminalDefinition.EquipmentPage(EquipmentTerminalDefinition.vehicleAmmunition)
vanu_equipment_term.Tab += 4 -> OrderTerminalDefinition.InfantryLoadoutPage() vanu_equipment_term.Tab += 4 -> OrderTerminalDefinition.InfantryLoadoutPage()
vanu_equipment_term.SellEquipmentByDefault = true vanu_equipment_term.SellEquipmentByDefault = true
vanu_equipment_term.Damageable = false
vanu_equipment_term.Repairable = false
cert_terminal.Name = "cert_terminal" cert_terminal.Name = "cert_terminal"
cert_terminal.Tab += 0 -> OrderTerminalDefinition.CertificationPage(CertTerminalDefinition.certs) 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.Name = "implant_terminal_interface"
implant_terminal_interface.Tab += 0 -> OrderTerminalDefinition.ImplantPage(ImplantTerminalDefinition.implants) 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.Name = "ground_vehicle_terminal"
ground_vehicle_terminal.Tab += 46769 -> OrderTerminalDefinition.VehiclePage(VehicleTerminalDefinition.groundVehicles, VehicleTerminalDefinition.trunk) ground_vehicle_terminal.Tab += 46769 -> OrderTerminalDefinition.VehiclePage(VehicleTerminalDefinition.groundVehicles, VehicleTerminalDefinition.trunk)
ground_vehicle_terminal.Tab += 4 -> OrderTerminalDefinition.VehicleLoadoutPage() 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.Name = "air_vehicle_terminal"
air_vehicle_terminal.Tab += 46769 -> OrderTerminalDefinition.VehiclePage(VehicleTerminalDefinition.flight1Vehicles, VehicleTerminalDefinition.trunk) air_vehicle_terminal.Tab += 46769 -> OrderTerminalDefinition.VehiclePage(VehicleTerminalDefinition.flight1Vehicles, VehicleTerminalDefinition.trunk)
air_vehicle_terminal.Tab += 4 -> OrderTerminalDefinition.VehicleLoadoutPage() 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.Name = "dropship_vehicle_terminal"
dropship_vehicle_terminal.Tab += 46769 -> OrderTerminalDefinition.VehiclePage(VehicleTerminalDefinition.flight1Vehicles ++ VehicleTerminalDefinition.flight2Vehicles, VehicleTerminalDefinition.trunk) dropship_vehicle_terminal.Tab += 46769 -> OrderTerminalDefinition.VehiclePage(VehicleTerminalDefinition.flight1Vehicles ++ VehicleTerminalDefinition.flight2Vehicles, VehicleTerminalDefinition.trunk)
dropship_vehicle_terminal.Tab += 4 -> OrderTerminalDefinition.VehicleLoadoutPage() 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.Name = "vehicle_terminal_combined"
vehicle_terminal_combined.Tab += 46769 -> OrderTerminalDefinition.VehiclePage(VehicleTerminalDefinition.flight1Vehicles ++ VehicleTerminalDefinition.groundVehicles, VehicleTerminalDefinition.trunk) vehicle_terminal_combined.Tab += 46769 -> OrderTerminalDefinition.VehiclePage(VehicleTerminalDefinition.flight1Vehicles ++ VehicleTerminalDefinition.groundVehicles, VehicleTerminalDefinition.trunk)
vehicle_terminal_combined.Tab += 4 -> OrderTerminalDefinition.VehicleLoadoutPage() 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.Name = "vanu_air_vehicle_term"
vanu_air_vehicle_term.Tab += 46769 -> OrderTerminalDefinition.VehiclePage(VehicleTerminalDefinition.flight1Vehicles, VehicleTerminalDefinition.trunk) vanu_air_vehicle_term.Tab += 46769 -> OrderTerminalDefinition.VehiclePage(VehicleTerminalDefinition.flight1Vehicles, VehicleTerminalDefinition.trunk)
vanu_air_vehicle_term.Tab += 4 -> OrderTerminalDefinition.VehicleLoadoutPage() 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.Name = "vanu_vehicle_term"
vanu_vehicle_term.Tab += 46769 -> OrderTerminalDefinition.VehiclePage(VehicleTerminalDefinition.groundVehicles, VehicleTerminalDefinition.trunk) vanu_vehicle_term.Tab += 46769 -> OrderTerminalDefinition.VehiclePage(VehicleTerminalDefinition.groundVehicles, VehicleTerminalDefinition.trunk)
vanu_vehicle_term.Tab += 4 -> OrderTerminalDefinition.VehicleLoadoutPage() 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.Name = "bfr_terminal"
bfr_terminal.Tab += 46769 -> OrderTerminalDefinition.VehiclePage(VehicleTerminalDefinition.bfrVehicles, VehicleTerminalDefinition.trunk) bfr_terminal.Tab += 46769 -> OrderTerminalDefinition.VehiclePage(VehicleTerminalDefinition.bfrVehicles, VehicleTerminalDefinition.trunk)
bfr_terminal.Tab += 4 -> OrderTerminalDefinition.VehicleLoadoutPage() 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.Name = "respawn_tube"
respawn_tube.Delay = 10 respawn_tube.Delay = 10
respawn_tube.SpecificPointFunc = SpawnPoint.Tube 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.Name = "respawn_tube"
respawn_tube_sanctuary.Delay = 10 respawn_tube_sanctuary.Delay = 10
respawn_tube_sanctuary.SpecificPointFunc = SpawnPoint.Default 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.Name = "respawn_tube_tower"
respawn_tube_tower.Delay = 10 respawn_tube_tower.Delay = 10
respawn_tube_tower.SpecificPointFunc = SpawnPoint.Tube 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.Name = "teleportpad_terminal"
teleportpad_terminal.Tab += 0 -> OrderTerminalDefinition.EquipmentPage(EquipmentTerminalDefinition.routerTerminal) teleportpad_terminal.Tab += 0 -> OrderTerminalDefinition.EquipmentPage(EquipmentTerminalDefinition.routerTerminal)
teleportpad_terminal.Damageable = false
teleportpad_terminal.Repairable = false
medical_terminal.Name = "medical_terminal" medical_terminal.Name = "medical_terminal"
medical_terminal.Interval = 500 medical_terminal.Interval = 500
@ -6200,6 +6442,10 @@ object GlobalDefinitions {
medical_terminal.ArmorAmount = 10 medical_terminal.ArmorAmount = 10
medical_terminal.UseRadius = 0.75f medical_terminal.UseRadius = 0.75f
medical_terminal.TargetValidation += EffectTarget.Category.Player -> EffectTarget.Validation.Medical 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.Name = "adv_med_terminal"
adv_med_terminal.Interval = 500 adv_med_terminal.Interval = 500
@ -6207,18 +6453,26 @@ object GlobalDefinitions {
adv_med_terminal.ArmorAmount = 15 adv_med_terminal.ArmorAmount = 15
adv_med_terminal.UseRadius = 0.75f adv_med_terminal.UseRadius = 0.75f
adv_med_terminal.TargetValidation += EffectTarget.Category.Player -> EffectTarget.Validation.Medical 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.Name = "crystals_health_a"
crystals_health_a.Interval = 500 crystals_health_a.Interval = 500
crystals_health_a.HealAmount = 4 crystals_health_a.HealAmount = 4
crystals_health_a.UseRadius = 5 crystals_health_a.UseRadius = 5
crystals_health_a.TargetValidation += EffectTarget.Category.Player -> EffectTarget.Validation.HealthCrystal 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.Name = "crystals_health_b"
crystals_health_b.Interval = 500 crystals_health_b.Interval = 500
crystals_health_b.HealAmount = 4 crystals_health_b.HealAmount = 4
crystals_health_b.UseRadius = 5 crystals_health_b.UseRadius = 5
crystals_health_b.TargetValidation += EffectTarget.Category.Player -> EffectTarget.Validation.HealthCrystal 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.Name = "portable_med_terminal"
portable_med_terminal.Interval = 500 portable_med_terminal.Interval = 500
@ -6226,53 +6480,116 @@ object GlobalDefinitions {
portable_med_terminal.ArmorAmount = 10 portable_med_terminal.ArmorAmount = 10
portable_med_terminal.UseRadius = 3 portable_med_terminal.UseRadius = 3
portable_med_terminal.TargetValidation += EffectTarget.Category.Player -> EffectTarget.Validation.Medical 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.Name = "pad_landing_frame"
pad_landing_frame.Interval = 1000 pad_landing_frame.Interval = 1000
pad_landing_frame.HealAmount = 60 pad_landing_frame.HealAmount = 60
pad_landing_frame.UseRadius = 20 pad_landing_frame.UseRadius = 20
pad_landing_frame.TargetValidation += EffectTarget.Category.Aircraft -> EffectTarget.Validation.PadLanding 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.Name = "pad_landing_tower_frame"
pad_landing_tower_frame.Interval = 1000 pad_landing_tower_frame.Interval = 1000
pad_landing_tower_frame.HealAmount = 60 pad_landing_tower_frame.HealAmount = 60
pad_landing_tower_frame.UseRadius = 20 pad_landing_tower_frame.UseRadius = 20
pad_landing_tower_frame.TargetValidation += EffectTarget.Category.Aircraft -> EffectTarget.Validation.PadLanding 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.Name = "repair_silo"
repair_silo.Interval = 1000 repair_silo.Interval = 1000
repair_silo.HealAmount = 60 repair_silo.HealAmount = 60
repair_silo.UseRadius = 20 repair_silo.UseRadius = 20
repair_silo.TargetValidation += EffectTarget.Category.Vehicle -> EffectTarget.Validation.RepairSilo 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.Name = "lodestar_repair_terminal"
lodestar_repair_terminal.Interval = 1000 lodestar_repair_terminal.Interval = 1000
lodestar_repair_terminal.HealAmount = 60 lodestar_repair_terminal.HealAmount = 60
lodestar_repair_terminal.UseRadius = 20 lodestar_repair_terminal.UseRadius = 20
lodestar_repair_terminal.TargetValidation += EffectTarget.Category.Vehicle -> EffectTarget.Validation.RepairSilo 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.Name = "multivehicle_rearm_terminal"
multivehicle_rearm_terminal.Tab += 3 -> OrderTerminalDefinition.EquipmentPage(EquipmentTerminalDefinition.vehicleAmmunition) multivehicle_rearm_terminal.Tab += 3 -> OrderTerminalDefinition.EquipmentPage(EquipmentTerminalDefinition.vehicleAmmunition)
multivehicle_rearm_terminal.Tab += 4 -> OrderTerminalDefinition.VehicleLoadoutPage() multivehicle_rearm_terminal.Tab += 4 -> OrderTerminalDefinition.VehicleLoadoutPage()
multivehicle_rearm_terminal.SellEquipmentByDefault = true //TODO ? 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.Name = "bfr_rearm_terminal"
bfr_rearm_terminal.Tab += 3 -> OrderTerminalDefinition.EquipmentPage(Map.empty[String, ()=>Equipment]) //TODO add stock to page 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.Tab += 4 -> OrderTerminalDefinition.VehicleLoadoutPage()
bfr_rearm_terminal.SellEquipmentByDefault = true //TODO ? 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.Name = "air_rearm_terminal"
air_rearm_terminal.Tab += 3 -> OrderTerminalDefinition.EquipmentPage(EquipmentTerminalDefinition.vehicleAmmunition) air_rearm_terminal.Tab += 3 -> OrderTerminalDefinition.EquipmentPage(EquipmentTerminalDefinition.vehicleAmmunition)
air_rearm_terminal.Tab += 4 -> OrderTerminalDefinition.VehicleLoadoutPage() air_rearm_terminal.Tab += 4 -> OrderTerminalDefinition.VehicleLoadoutPage()
air_rearm_terminal.SellEquipmentByDefault = true //TODO ? 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.Name = "ground_rearm_terminal"
ground_rearm_terminal.Tab += 3 -> OrderTerminalDefinition.EquipmentPage(EquipmentTerminalDefinition.vehicleAmmunition) ground_rearm_terminal.Tab += 3 -> OrderTerminalDefinition.EquipmentPage(EquipmentTerminalDefinition.vehicleAmmunition)
ground_rearm_terminal.Tab += 4 -> OrderTerminalDefinition.VehicleLoadoutPage() ground_rearm_terminal.Tab += 4 -> OrderTerminalDefinition.VehicleLoadoutPage()
ground_rearm_terminal.SellEquipmentByDefault = true //TODO ? ground_rearm_terminal.SellEquipmentByDefault = true //TODO ?
ground_rearm_terminal.Damageable = false
ground_rearm_terminal.Repairable = false
manned_turret.Name = "manned_turret" manned_turret.Name = "manned_turret"
manned_turret.MaxHealth = 3600 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 -> new mutable.HashMap()
manned_turret.Weapons(1) += TurretUpgrade.None -> phalanx_sgl_hevgatcan manned_turret.Weapons(1) += TurretUpgrade.None -> phalanx_sgl_hevgatcan
manned_turret.Weapons(1) += TurretUpgrade.AVCombo -> phalanx_avcombo manned_turret.Weapons(1) += TurretUpgrade.AVCombo -> phalanx_avcombo
@ -6283,6 +6600,10 @@ object GlobalDefinitions {
vanu_sentry_turret.Name = "vanu_sentry_turret" vanu_sentry_turret.Name = "vanu_sentry_turret"
vanu_sentry_turret.MaxHealth = 1500 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 -> new mutable.HashMap()
vanu_sentry_turret.Weapons(1) += TurretUpgrade.None -> vanu_sentry_turret_weapon vanu_sentry_turret.Weapons(1) += TurretUpgrade.None -> vanu_sentry_turret_weapon
vanu_sentry_turret.MountPoints += 1 -> 0 vanu_sentry_turret.MountPoints += 1 -> 0
@ -6290,8 +6611,41 @@ object GlobalDefinitions {
vanu_sentry_turret.FactionLocked = false vanu_sentry_turret.FactionLocked = false
vanu_sentry_turret.ReserveAmmunition = 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.Name = "gen_control"
gen_control.Damageable = false
gen_control.Repairable = false
generator.Name = "generator" 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 { abstract class PlanetSideGameObject extends IdentifiableEntity with WorldEntity {
private var entity : WorldEntity = new SimpleWorldEntity() private var entity : WorldEntity = new SimpleWorldEntity()
private var destroyed : Boolean = false
def Entity : WorldEntity = entity def Entity : WorldEntity = entity
@ -35,6 +36,13 @@ abstract class PlanetSideGameObject extends IdentifiableEntity with WorldEntity
Entity.Velocity = vec Entity.Velocity = vec
} }
def Destroyed : Boolean = destroyed
def Destroyed_=(state : Boolean) : Boolean = {
destroyed = state
Destroyed
}
def Definition : ObjectDefinition def Definition : ObjectDefinition
} }

View file

@ -23,9 +23,9 @@ class Player(private val core : Avatar) extends PlanetSideServerObject
with Container with Container
with JammableUnit with JammableUnit
with ZoneAware { 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 backpack : Boolean = false
private var health : Int = 0
private var stamina : Int = 0 private var stamina : Int = 0
private var armor : 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 capacitorLastUsedMillis : Long = 0
private var capacitorLastChargedMillis : 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 maxStamina : Int = 100 //does anything affect this?
private var exosuit : ExoSuitDefinition = GlobalDefinitions.Standard private var exosuit : ExoSuitDefinition = GlobalDefinitions.Standard
@ -84,14 +83,14 @@ class Player(private val core : Avatar) extends PlanetSideServerObject
def LFS : Boolean = core.LFS def LFS : Boolean = core.LFS
def isAlive : Boolean = alive def isAlive : Boolean = !Destroyed
def isBackpack : Boolean = backpack def isBackpack : Boolean = backpack
def Spawn : Boolean = { def Spawn : Boolean = {
if(!isAlive && !isBackpack) { if(!isAlive && !isBackpack) {
alive = true Destroyed = false
Health = MaxHealth Health = Definition.DefaultHealth
Stamina = MaxStamina Stamina = MaxStamina
Armor = MaxArmor Armor = MaxArmor
Capacitor = 0 Capacitor = 0
@ -101,14 +100,15 @@ class Player(private val core : Avatar) extends PlanetSideServerObject
} }
def Die : Boolean = { def Die : Boolean = {
alive = false Destroyed = true
Health = 0 Health = 0
Stamina = 0 Stamina = 0
false false
} }
def Revive : Boolean = { def Revive : Boolean = {
alive = true Destroyed = false
Health = Definition.DefaultHealth
true 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 : Int = stamina
def Stamina_=(assignEnergy : Int) : Int = { 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 = { def toString(obj : Player) : String = {
val guid = if(obj.HasGUID) { s" ${obj.Continent}-${obj.GUID.guid}" } else { "" } val guid = if(obj.HasGUID) { s" ${obj.Continent}-${obj.GUID.guid}" } else { "" }
s"${obj.core}$guid ${obj.Health}/${obj.MaxHealth} ${obj.Armor}/${obj.MaxArmor}" 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 package net.psforever.objects
import akka.actor.{Actor, ActorContext, Props} 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.definition.{ComplexDeployableDefinition, SimpleDeployableDefinition}
import net.psforever.objects.equipment.{JammableBehavior, JammableUnit} import net.psforever.objects.equipment.{JammableBehavior, JammableUnit}
import net.psforever.objects.serverobject.PlanetSideServerObject 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.serverobject.hackable.Hackable
import net.psforever.objects.vital.{StandardResolutions, Vitality} import net.psforever.objects.serverobject.repair.RepairableEntity
import net.psforever.objects.zones.Zone import net.psforever.objects.vital.StandardResolutions
import net.psforever.types.PlanetSideGUID import net.psforever.types.{PlanetSideGUID, Vector3}
import services.Service import services.Service
import services.avatar.{AvatarAction, AvatarServiceMessage}
import services.local.{LocalAction, LocalServiceMessage} import services.local.{LocalAction, LocalServiceMessage}
import services.vehicle.{VehicleAction, VehicleServiceMessage} import services.vehicle.{VehicleAction, VehicleServiceMessage}
@ -45,19 +45,25 @@ object SensorDeployableDefinition {
} }
class SensorDeployableControl(sensor : SensorDeployable) extends Actor class SensorDeployableControl(sensor : SensorDeployable) extends Actor
with JammableBehavior { with JammableBehavior
with DamageableEntity
with RepairableEntity {
def JammableObject = sensor def JammableObject = sensor
def DamageableObject = sensor
def RepairableObject = sensor
def receive : Receive = jammableBehavior.orElse { def receive : Receive = jammableBehavior
case Vitality.Damage(damage_func) => .orElse(takesDamage)
val originalHealth = sensor.Health .orElse(canBeRepairedByNanoDispenser)
if(originalHealth > 0) { .orElse {
val cause = damage_func(sensor) case _ => ;
SensorDeployableControl.HandleDamageResolution(sensor, cause, originalHealth - sensor.Health) }
}
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 { 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 { override def StartJammeredStatus(target : Any, dur : Int) : Unit = target match {
case obj : PlanetSideServerObject with JammableUnit if !obj.Jammed => 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) super.StartJammeredStatus(obj, dur)
case _ => ; case _ => ;
} }
@ -77,7 +84,8 @@ class SensorDeployableControl(sensor : SensorDeployable) extends Actor
override def CancelJammeredSound(target : Any) : Unit = { override def CancelJammeredSound(target : Any) : Unit = {
target match { target match {
case obj : PlanetSideServerObject if jammedSound => 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 _ => ; case _ => ;
} }
super.CancelJammeredSound(target) super.CancelJammeredSound(target)
@ -94,40 +102,32 @@ class SensorDeployableControl(sensor : SensorDeployable) extends Actor
} }
object SensorDeployableControl { 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 * na
* @param target na * @param target na
* @param attribution na * @param attribution na
* @param lastShot na
*/ */
def HandleDestructionAwareness(target : SensorDeployable, attribution : PlanetSideGUID, lastShot : ResolvedProjectile) : Unit = { def DestructionAwareness(target : Damageable.Target with Deployable, attribution : PlanetSideGUID) : Unit = {
target.Actor ! JammableUnit.ClearJammeredSound() Deployables.AnnounceDestroyDeployable(target, Some(1 seconds))
target.Actor ! JammableUnit.ClearJammeredStatus()
val zone = target.Zone val zone = target.Zone
Deployables.AnnounceDestroyDeployable(target, None) zone.LocalEvents ! LocalServiceMessage(zone.Id,
zone.LocalEvents ! LocalServiceMessage(zone.Id, LocalAction.TriggerEffectInfo(Service.defaultPlayerGUID, "on", target.GUID, false, 1000)) LocalAction.TriggerEffectInfo(Service.defaultPlayerGUID, "on", target.GUID, false, 1000)
zone.AvatarEvents ! AvatarServiceMessage(zone.Id, AvatarAction.Destroy(target.GUID, attribution, attribution, target.Position)) )
//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.{ComplexDeployableDefinition, SimpleDeployableDefinition}
import net.psforever.objects.definition.converter.ShieldGeneratorConverter import net.psforever.objects.definition.converter.ShieldGeneratorConverter
import net.psforever.objects.equipment.{JammableBehavior, JammableUnit} 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.PlanetSideServerObject
import net.psforever.objects.serverobject.hackable.Hackable import net.psforever.objects.serverobject.hackable.Hackable
import net.psforever.objects.vital.Vitality import net.psforever.objects.serverobject.repair.RepairableEntity
import net.psforever.objects.zones.Zone import net.psforever.objects.vital.resolution.ResolutionCalculations
import net.psforever.types.PlanetSideGUID import net.psforever.types.PlanetSideGUID
import services.avatar.{AvatarAction, AvatarServiceMessage}
import services.Service import services.Service
import services.vehicle.{VehicleAction, VehicleServiceMessage} import services.vehicle.{VehicleAction, VehicleServiceMessage}
@ -34,32 +35,75 @@ class ShieldGeneratorDefinition extends ComplexDeployableDefinition(240) {
} }
class ShieldGeneratorControl(gen : ShieldGeneratorDeployable) extends Actor class ShieldGeneratorControl(gen : ShieldGeneratorDeployable) extends Actor
with JammableBehavior { with JammableBehavior
with DamageableEntity
with RepairableEntity {
def JammableObject = gen def JammableObject = gen
def DamageableObject = gen
def RepairableObject = gen
private var handleDamageToShields : Boolean = false
def receive : Receive = jammableBehavior def receive : Receive = jammableBehavior
.orElse(takesDamage)
.orElse(canBeRepairedByNanoDispenser)
.orElse { .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 _ => ; 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 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 check the object definition for proper feature activation
the superclass of "status" calls also sets the jammed object property
*/ */
override def StartJammeredSound(target : Any, dur : Int) : Unit = { } override def StartJammeredSound(target : Any, dur : Int) : Unit = { }
@ -86,40 +130,23 @@ object ShieldGeneratorControl {
/** /**
* na * na
* @param target na * @param target na
* @param cause na
* @param damageToShields na
*/ */
def HandleDamageResolution(target : ShieldGeneratorDeployable, cause : ResolvedProjectile, damage : Int) : Unit = { def DamageAwareness(target : ShieldGeneratorDeployable, cause : ResolvedProjectile, damageToShields : Boolean) : Unit = {
val zone = target.Zone //shields
val targetGUID = target.GUID if(damageToShields) {
val playerGUID = zone.LivePlayers.find { p => cause.projectile.owner.Name.equals(p.Name) } match { val zone = target.Zone
case Some(player) => player.GUID zone.VehicleEvents ! VehicleServiceMessage(zone.Id, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, target.GUID, 68, target.Shields))
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 * na
* @param target na * @param target na
* @param attribution na * @param attribution na
* @param lastShot na
*/ */
def HandleDestructionAwareness(target : ShieldGeneratorDeployable, attribution : PlanetSideGUID, lastShot : ResolvedProjectile) : Unit = { def DestructionAwareness(target : Damageable.Target with Deployable, attribution : PlanetSideGUID) : Unit = {
target.Actor ! JammableUnit.ClearJammeredSound()
target.Actor ! JammableUnit.ClearJammeredStatus()
val zone = target.Zone
Deployables.AnnounceDestroyDeployable(target, None) 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 Definition : ObjectDefinition with SpawnPointDefinition
def Offline : Boolean = psso.Destroyed
/** /**
* Determine a specific position and orientation in which to spawn the target. * Determine a specific position and orientation in which to spawn the target.
* @return a `Tuple` of `Vector3` objects; * @return a `Tuple` of `Vector3` objects;

View file

@ -1,7 +1,51 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2020 PSForever
package net.psforever.objects package net.psforever.objects
import net.psforever.objects.ce.SimpleDeployable import akka.actor.{Actor, ActorContext, Props}
import net.psforever.objects.definition.SimpleDeployableDefinition 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.definition.converter.SmallTurretConverter
import net.psforever.objects.equipment.{JammableMountedWeapons, JammableUnit} import net.psforever.objects.equipment.{JammableMountedWeapons, JammableUnit}
import net.psforever.objects.serverobject.PlanetSideServerObject 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.hackable.Hackable
import net.psforever.objects.serverobject.mount.MountableBehavior 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.serverobject.turret.{TurretDefinition, WeaponTurret}
import net.psforever.objects.vital.{StandardResolutions, StandardVehicleDamage, StandardVehicleResistance, Vitality} import net.psforever.objects.vital.{StandardResolutions, StandardVehicleDamage, StandardVehicleResistance}
import net.psforever.objects.zones.Zone
import net.psforever.types.PlanetSideGUID
import services.Service
import services.avatar.{AvatarAction, AvatarServiceMessage}
import services.vehicle.{VehicleAction, VehicleServiceMessage}
class TurretDeployable(tdef : TurretDeployableDefinition) extends ComplexDeployable(tdef) class TurretDeployable(tdef : TurretDeployableDefinition) extends ComplexDeployable(tdef)
with WeaponTurret with WeaponTurret
with JammableUnit with JammableUnit
with Hackable { with Hackable {
WeaponTurret.LoadDefinition(this) //calls the equivalent of Health = Definition.MaxHealth WeaponTurret.LoadDefinition(this)
def MountPoints : Map[Int, Int] = Definition.MountPoints.toMap 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 override def Definition = tdef
} }
@ -39,8 +32,8 @@ class TurretDeployableDefinition(private val objectId : Int) extends ComplexDepl
with TurretDefinition { with TurretDefinition {
Name = "turret_deployable" Name = "turret_deployable"
Packet = new SmallTurretConverter Packet = new SmallTurretConverter
Damage = StandardVehicleDamage DamageUsing = StandardVehicleDamage
Resistance = StandardVehicleResistance ResistUsing = StandardVehicleResistance
Model = StandardResolutions.FacilityTurrets Model = StandardResolutions.FacilityTurrets
//override to clarify inheritance conflict //override to clarify inheritance conflict
@ -68,113 +61,28 @@ object TurretDeployableDefinition {
class TurretControl(turret : TurretDeployable) extends Actor class TurretControl(turret : TurretDeployable) extends Actor
with FactionAffinityBehavior.Check with FactionAffinityBehavior.Check
with JammableMountedWeapons //note: jammable status is reported as vehicle events, not local events with JammableMountedWeapons //note: jammable status is reported as vehicle events, not local events
with MountableBehavior.Mount with MountableBehavior.TurretMount
with MountableBehavior.Dismount { with MountableBehavior.Dismount
def MountableObject = turret //do not add type! with DamageableWeaponTurret
with RepairableWeaponTurret {
def MountableObject = turret
def JammableObject = turret def JammableObject = turret
def FactionObject = turret
def FactionObject : FactionAffinity = turret def DamageableObject = turret
def RepairableObject = turret
def receive : Receive = checkBehavior def receive : Receive = checkBehavior
.orElse(jammableBehavior) .orElse(jammableBehavior)
.orElse(mountBehavior)
.orElse(dismountBehavior) .orElse(dismountBehavior)
.orElse(turretMountBehavior) .orElse(takesDamage)
.orElse(canBeRepairedByNanoDispenser)
.orElse { .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 _ => ; case _ => ;
} }
}
object TurretControl { override protected def DestructionAwareness(target : Target, cause : ResolvedProjectile) : Unit = {
/** super.DestructionAwareness(target, cause)
* na Deployables.AnnounceDestroyDeployable(turret, None)
* @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))
} }
} }

View file

@ -9,6 +9,7 @@ import net.psforever.objects.serverobject.mount.Mountable
import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.affinity.FactionAffinity import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.serverobject.deploy.Deployment 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.serverobject.structures.AmenityOwner
import net.psforever.objects.vehicles._ import net.psforever.objects.vehicles._
import net.psforever.objects.vital.{DamageResistanceModel, StandardResistanceProfile, Vitality} import net.psforever.objects.vital.{DamageResistanceModel, StandardResistanceProfile, Vitality}
@ -65,6 +66,7 @@ import scala.annotation.tailrec
* used in the initialization process (`loadVehicleDefinition`) * used in the initialization process (`loadVehicleDefinition`)
*/ */
class Vehicle(private val vehicleDef : VehicleDefinition) extends AmenityOwner class Vehicle(private val vehicleDef : VehicleDefinition) extends AmenityOwner
with Hackable
with FactionAffinity with FactionAffinity
with Mountable with Mountable
with MountedWeapons with MountedWeapons
@ -74,10 +76,8 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends AmenityOwner
with StandardResistanceProfile with StandardResistanceProfile
with JammableUnit with JammableUnit
with Container { with Container {
private var faction : PlanetSideEmpire.Value = PlanetSideEmpire.TR private var faction : PlanetSideEmpire.Value = PlanetSideEmpire.NEUTRAL
private var health : Int = 1
private var shields : Int = 0 private var shields : Int = 0
private var isDead : Boolean = false
private var decal : Int = 0 private var decal : Int = 0
private var trunkAccess : Option[PlanetSideGUID] = None private var trunkAccess : Option[PlanetSideGUID] = None
private var jammered : Boolean = false private var jammered : Boolean = false
@ -144,28 +144,12 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends AmenityOwner
MountedIn MountedIn
} }
def IsDead : Boolean = { override def Health_=(assignHealth : Int) : Int = {
isDead //TODO should vehicle class enforce this?
} if(!Destroyed) {
super.Health_=(assignHealth)
def Health : Int = {
health
}
def Health_=(assignHealth : Int) : Int = {
if(!isDead) {
health = math.min(math.max(0, assignHealth), MaxHealth)
} }
Health
if(health == 0) {
isDead = true
}
health
}
def MaxHealth : Int = {
Definition.MaxHealth
} }
def Shields : Int = { def Shields : Int = {
@ -644,7 +628,7 @@ object Vehicle {
def LoadDefinition(vehicle : Vehicle) : Vehicle = { def LoadDefinition(vehicle : Vehicle) : Vehicle = {
val vdef : VehicleDefinition = vehicle.Definition val vdef : VehicleDefinition = vehicle.Definition
//general stuff //general stuff
vehicle.Health = vdef.MaxHealth vehicle.Health = vdef.DefaultHealth
//create weapons //create weapons
vehicle.weapons = vdef.Weapons.map({case (num, definition) => vehicle.weapons = vdef.Weapons.map({case (num, definition) =>
val slot = EquipmentSlot(EquipmentSize.VehicleWeapon) val slot = EquipmentSlot(EquipmentSize.VehicleWeapon)

View file

@ -1,20 +1,26 @@
// Copyright (c) 2020 PSForever // Copyright (c) 2020 PSForever
package net.psforever.objects 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.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.avatar.{AvatarAction, AvatarServiceMessage}
import services.local.{LocalAction, LocalServiceMessage}
import services.vehicle.{VehicleAction, VehicleServiceMessage} import services.vehicle.{VehicleAction, VehicleServiceMessage}
object Vehicles { object Vehicles {
private val log = org.log4s.getLogger("Vehicles")
/** /**
* na * na
* @param vehicle na * @param vehicle na
* @param tplayer na * @param player na
* @return 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 * na
@ -151,4 +157,106 @@ object Vehicles {
false 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 package net.psforever.objects.avatar
import akka.actor.Actor import akka.actor.Actor
import net.psforever.objects.Player import net.psforever.objects.{GlobalDefinitions, Player, Tool}
import net.psforever.objects.ballistics.{PlayerSource, ResolvedProjectile, SourceEntry} import net.psforever.objects.ballistics.{PlayerSource, ResolvedProjectile}
import net.psforever.objects.equipment.{JammableBehavior, JammableUnit} import net.psforever.objects.equipment.{Ammo, JammableBehavior, JammableUnit}
import net.psforever.objects.vital.{PlayerSuicide, Vitality} 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.objects.zones.Zone
import net.psforever.packet.game._ import net.psforever.packet.game._
import net.psforever.types.{ExoSuitType, PlanetSideGUID} import net.psforever.types.{ExoSuitType, Vector3}
import services.Service import services.Service
import services.avatar.{AvatarAction, AvatarServiceMessage} import services.avatar.{AvatarAction, AvatarServiceMessage}
@ -19,34 +23,108 @@ import scala.concurrent.duration._
* stub for future development * stub for future development
*/ */
class PlayerControl(player : Player) extends Actor class PlayerControl(player : Player) extends Actor
with JammableBehavior { with JammableBehavior
with Damageable {
def JammableObject = player def JammableObject = player
def DamageableObject = player
private [this] val log = org.log4s.getLogger(player.Name) private [this] val damageLog = org.log4s.getLogger(Damageable.LogChannel)
private [this] val damageLog = org.log4s.getLogger("DamageResolution")
def receive : Receive = jammableBehavior.orElse { def receive : Receive = jammableBehavior
case Player.Die() => .orElse(takesDamage)
PlayerControl.HandleDestructionAwareness(player, player.GUID, None) .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) { if(player.isAlive) {
val originalHealth = player.Health val originalHealth = player.Health
val originalArmor = player.Armor val originalArmor = player.Armor
val originalCapacitor = player.Capacitor.toInt val originalCapacitor = player.Capacitor.toInt
val cause = resolution_function(player) val cause = applyDamageTo(player)
val health = player.Health val health = player.Health
val armor = player.Armor val armor = player.Armor
val capacitor = player.Capacitor.toInt val capacitor = player.Capacitor.toInt
val damageToHealth = originalHealth - health val damageToHealth = originalHealth - health
val damageToArmor = originalArmor - armor val damageToArmor = originalArmor - armor
val damageToCapacitor = originalCapacitor - capacitor val damageToCapacitor = originalCapacitor - capacitor
PlayerControl.HandleDamageResolution(player, cause, damageToHealth, damageToArmor, damageToCapacitor) PlayerControl.HandleDamage(player, cause, damageToHealth, damageToArmor, damageToCapacitor)
if(damageToHealth != 0 || damageToArmor != 0 || damageToCapacitor != 0) { 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") 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 { override def StartJammeredStatus(target : Any, dur : Int) : Unit = target match {
case obj : Player => case obj : Player =>
//TODO these features //TODO these features
obj.Zone.AvatarEvents ! AvatarServiceMessage(obj.Zone.Id, AvatarAction.DeactivateImplantSlot(obj.GUID, 1)) val guid = obj.GUID
obj.Zone.AvatarEvents ! AvatarServiceMessage(obj.Zone.Id, AvatarAction.DeactivateImplantSlot(obj.GUID, 2)) 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) obj.skipStaminaRegenForTurns = math.max(obj.skipStaminaRegenForTurns, 10)
super.StartJammeredStatus(target, dur) super.StartJammeredStatus(target, dur)
case _ => ; case _ => ;
@ -95,6 +177,13 @@ class PlayerControl(player : Player) extends Actor
super.CancelJammeredSound(obj) super.CancelJammeredSound(obj)
case _ => ; case _ => ;
} }
def RepairValue(item : Tool) : Int = if(player.ExoSuit != ExoSuitType.MAX) {
item.FireMode.Modifiers.Damage0
}
else {
item.FireMode.Modifiers.Damage3
}
} }
object PlayerControl { object PlayerControl {
@ -102,62 +191,60 @@ object PlayerControl {
* na * na
* @param target 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 targetGUID = target.GUID
val playerGUID = target.Zone.LivePlayers.find { p => cause.projectile.owner.Name.equals(p.Name) } match { val zone = target.Zone
case Some(player) => player.GUID val zoneId = zone.Id
case _ => PlanetSideGUID(0) val events = zone.AvatarEvents
} val health = target.Health
if(target.Health > 0) { if(health > 0) {
//activity on map if(damageToCapacitor > 0) {
if(damageToHealth + damageToArmor > 0) { events ! AvatarServiceMessage(target.Name, AvatarAction.PlanetsideAttributeSelf(targetGUID, 7, target.Capacitor.toLong))
target.Zone.Activity ! Zone.HotSpot.Activity(cause.target, cause.projectile.owner, cause.hit_pos)
//alert damage source
HandleDamageAwareness(target, playerGUID, cause)
} }
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) target.Actor ! JammableUnit.Jammered(cause)
} }
} }
else { else {
HandleDestructionAwareness(target, playerGUID, Some(cause)) if(damageToArmor > 0) {
} events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(targetGUID, 4, target.Armor))
if(damageToHealth > 0) { }
target.Zone.AvatarEvents ! AvatarServiceMessage(target.Zone.Id, AvatarAction.PlanetsideAttributeToAll(targetGUID, 0, target.Health)) DestructionAwareness(target, Some(cause))
}
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))
} }
} }
/** /**
* na * na
* @param target na * @param target na
* @param attribution na * @param cause na
* @param lastShot na
*/ */
def HandleDamageAwareness(target : Player, attribution : PlanetSideGUID, lastShot : ResolvedProjectile) : Unit = { def DamageAwareness(target : Player, cause : ResolvedProjectile) : Unit = {
val owner = lastShot.projectile.owner val zone = target.Zone
owner match { zone.AvatarEvents ! AvatarServiceMessage(
case pSource : PlayerSource => target.Name,
target.Zone.LivePlayers.find(_.Name == pSource.Name) match { cause.projectile.owner match {
case Some(tplayer) => case pSource : PlayerSource => //player damage
target.Zone.AvatarEvents ! AvatarServiceMessage( val name = pSource.Name
target.Name, zone.LivePlayers.find(_.Name == name).orElse(zone.Corpses.find(_.Name == name)) match {
AvatarAction.HitHint(tplayer.GUID, target.GUID) case Some(player) => AvatarAction.HitHint(player.GUID, target.GUID)
) case None => AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(10, pSource.Position))
case None => ; }
} case source => AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(10, source.Position))
case vSource : SourceEntry => }
target.Zone.AvatarEvents ! AvatarServiceMessage( )
target.Name,
AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(10, vSource.Position))
)
case _ => ;
}
} }
/** /**
@ -173,10 +260,9 @@ object PlayerControl {
* A maximum revive waiting timer is started. * 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. * When this timer reaches zero, the avatar will attempt to spawn back on its faction-specific sanctuary continent.
* @param target na * @param target na
* @param attribution na * @param cause na
* @param lastShot 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 player_guid = target.GUID
val pos = target.Position val pos = target.Position
val respawnTimer = 300000 //milliseconds val respawnTimer = 300000 //milliseconds
@ -185,24 +271,49 @@ object PlayerControl {
val nameChannel = target.Name val nameChannel = target.Name
val zoneChannel = zone.Id val zoneChannel = zone.Id
target.Die target.Die
//unjam
target.Actor ! JammableUnit.ClearJammeredSound() target.Actor ! JammableUnit.ClearJammeredSound()
target.Actor ! JammableUnit.ClearJammeredStatus() target.Actor ! JammableUnit.ClearJammeredStatus()
events ! AvatarServiceMessage(nameChannel, AvatarAction.Killed(player_guid)) //align client interface fields with state events ! AvatarServiceMessage(nameChannel, AvatarAction.Killed(player_guid)) //align client interface fields with state
if(target.VehicleSeated.nonEmpty) { zone.GUID(target.VehicleSeated) match {
//make player invisible (if not, the cadaver sticks out the side in a seated position) case Some(obj : Mountable) =>
events ! AvatarServiceMessage(nameChannel, AvatarAction.PlanetsideAttributeToAll(player_guid, 29, 1)) //boot cadaver from seat
//only the dead player should "see" their own body, so that the death camera has something to focus on events ! AvatarServiceMessage(nameChannel, AvatarAction.SendResponse(Service.defaultPlayerGUID,
events ! AvatarServiceMessage(zoneChannel, AvatarAction.ObjectDelete(player_guid, player_guid)) 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(zoneChannel, AvatarAction.PlanetsideAttributeToAll(player_guid, 0, 0)) //health
events ! AvatarServiceMessage(nameChannel, AvatarAction.PlanetsideAttributeToAll(player_guid, 2, 0)) //stamina events ! AvatarServiceMessage(nameChannel, AvatarAction.PlanetsideAttributeToAll(player_guid, 2, 0)) //stamina
events ! AvatarServiceMessage(zoneChannel, AvatarAction.PlanetsideAttributeToAll(player_guid, 4, target.Armor)) //armor if(target.Capacitor > 0) {
if(target.ExoSuit == ExoSuitType.MAX) { target.Capacitor = 0
events ! AvatarServiceMessage(nameChannel, AvatarAction.PlanetsideAttributeToAll(player_guid, 7, 0)) // capacitor 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( events ! AvatarServiceMessage(
nameChannel, 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( events ! AvatarServiceMessage(
nameChannel, nameChannel,
@ -214,7 +325,7 @@ object PlayerControl {
case Some(PlayerSuicide(_)) => case Some(PlayerSuicide(_)) =>
None None
case _ => case _ =>
lastShot.orElse { target.LastShot } match { cause.orElse { target.LastShot } match {
case out @ Some(shot) => case out @ Some(shot) =>
if(System.nanoTime - shot.hit_time < (10 seconds).toNanos) { if(System.nanoTime - shot.hit_time < (10 seconds).toNanos) {
out out
@ -227,7 +338,6 @@ object PlayerControl {
} }
}) match { }) match {
case Some(shot) => 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)) events ! AvatarServiceMessage(zoneChannel, AvatarAction.DestroyDisplay(shot.projectile.owner, pentry, shot.projectile.attribute_to))
case None => case None =>
events ! AvatarServiceMessage(zoneChannel, AvatarAction.DestroyDisplay(pentry, pentry, 0)) 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 target what the projectile hit
* @param damage_model the kind of damage model to which the `target` is/was subject * @param damage_model the kind of damage model to which the `target` is/was subject
* @param hit_pos where the projectile hit * @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, final case class ResolvedProjectile(resolution : ProjectileResolution.Value,
projectile : Projectile, projectile : Projectile,
target : SourceEntry, target : SourceEntry,
damage_model : DamageResistanceModel, damage_model : DamageResistanceModel,
hit_pos : Vector3, hit_pos : Vector3) {
hit_time : Long = System.nanoTime) 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 abstract class ComplexDeployable(cdef : ComplexDeployableDefinition) extends PlanetSideServerObject
with Deployable { with Deployable {
Health = Definition.MaxHealth
def MaxHealth : Int = Definition.MaxHealth
private var shields : Int = 0 private var shields : Int = 0
def Shields : Int = shields def Shields : Int = shields

View file

@ -2,7 +2,7 @@
package net.psforever.objects.ce package net.psforever.objects.ce
import net.psforever.objects._ 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.serverobject.affinity.FactionAffinity
import net.psforever.objects.vital.{DamageResistanceModel, Vitality} import net.psforever.objects.vital.{DamageResistanceModel, Vitality}
import net.psforever.objects.zones.ZoneAware import net.psforever.objects.zones.ZoneAware
@ -14,16 +14,8 @@ trait Deployable extends FactionAffinity
with OwnableByPlayer with OwnableByPlayer
with ZoneAware { with ZoneAware {
this : PlanetSideGameObject => this : PlanetSideGameObject =>
private var health : Int = 1
private var faction : PlanetSideEmpire.Value = PlanetSideEmpire.NEUTRAL 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 MaxHealth : Int
def Faction : PlanetSideEmpire.Value = faction def Faction : PlanetSideEmpire.Value = faction
@ -35,7 +27,7 @@ trait Deployable extends FactionAffinity
def DamageModel : DamageResistanceModel = Definition.asInstanceOf[DamageResistanceModel] def DamageModel : DamageResistanceModel = Definition.asInstanceOf[DamageResistanceModel]
def Definition : ObjectDefinition with BaseDeployableDefinition def Definition : DeployableDefinition
} }
object Deployable { object Deployable {

View file

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

View file

@ -3,12 +3,14 @@ package net.psforever.objects.definition
import net.psforever.objects.avatar.Avatars import net.psforever.objects.avatar.Avatars
import net.psforever.objects.definition.converter.AvatarConverter 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. * The definition for game objects that look like other people, and also for players.
* @param objectId the object's identifier number * @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 Avatars(objectId) //let throw NoSuchElementException
Packet = AvatarDefinition.converter 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 capacitorRechargePerSecond : Int = 0
protected var capacitorDrainPerSecond : Int = 0 protected var capacitorDrainPerSecond : Int = 0
Name = "exo-suit" Name = "exo-suit"
Damage = StandardInfantryDamage DamageUsing = StandardInfantryDamage
Resistance = StandardInfantryResistance ResistUsing = StandardInfantryResistance
Model = StandardResolutions.Infantry Model = StandardResolutions.Infantry
def SuitType : ExoSuitType.Value = suitType def SuitType : ExoSuitType.Value = suitType
@ -142,8 +142,8 @@ class SpecialExoSuitDefinition(private val suitType : ExoSuitType.Value) extends
obj.ResistanceDirectHit = ResistanceDirectHit obj.ResistanceDirectHit = ResistanceDirectHit
obj.ResistanceSplash = ResistanceSplash obj.ResistanceSplash = ResistanceSplash
obj.ResistanceAggravated = ResistanceAggravated obj.ResistanceAggravated = ResistanceAggravated
obj.Damage = Damage obj.DamageUsing = DamageUsing
obj.Resistance = Resistance obj.ResistUsing = ResistUsing
obj.Model = Model obj.Model = Model
(0 until 5).foreach(index => { obj.Holster(index, Holster(index)) }) (0 until 5).foreach(index => { obj.Holster(index, Holster(index)) })
obj 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.definition.converter.SmallDeployableConverter
import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.vital.resistance.ResistanceProfileMutators 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._ import scala.concurrent.duration._
trait BaseDeployableDefinition extends DamageResistanceModel trait BaseDeployableDefinition {
with ResistanceProfileMutators {
private var category : DeployableCategory.Value = DeployableCategory.Boomers private var category : DeployableCategory.Value = DeployableCategory.Boomers
private var deployTime : Long = (1 second).toMillis //ms private var deployTime : Long = (1 second).toMillis //ms
private var maxHealth : Int = 1
Damage = StandardDeployableDamage
Resistance = NoResistanceSelection
def Item : DeployedItem.Value def Item : DeployedItem.Value
def MaxHealth : Int = maxHealth
def MaxHealth_=(toHealth : Int) : Int = {
maxHealth = toHealth
MaxHealth
}
def DeployCategory : DeployableCategory.Value = category def DeployCategory : DeployableCategory.Value = category
def DeployCategory_=(cat : DeployableCategory.Value) : DeployableCategory.Value = { def DeployCategory_=(cat : DeployableCategory.Value) : DeployableCategory.Value = {
@ -53,20 +42,23 @@ trait BaseDeployableDefinition extends DamageResistanceModel
def Uninitialize(obj : PlanetSideServerObject with Deployable, context : ActorContext) : Unit = { } 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 { with BaseDeployableDefinition {
private val item = DeployedItem(objectId) //let throw NoSuchElementException 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 Packet = new SmallDeployableConverter
def Item : DeployedItem.Value = item
} }
abstract class ComplexDeployableDefinition(private val objectId : Int) extends ObjectDefinition(objectId) abstract class ComplexDeployableDefinition(objectId : Int) extends DeployableDefinition(objectId)
with BaseDeployableDefinition {
private val item = DeployedItem(objectId) //let throw NoSuchElementException
def Item : DeployedItem.Value = item
}
object SimpleDeployableDefinition { object SimpleDeployableDefinition {
def apply(item : DeployedItem.Value) : SimpleDeployableDefinition = def apply(item : DeployedItem.Value) : SimpleDeployableDefinition =
@ -75,7 +67,7 @@ object SimpleDeployableDefinition {
def SimpleUninitialize(obj : PlanetSideGameObject, context : ActorContext) : Unit = { } def SimpleUninitialize(obj : PlanetSideGameObject, context : ActorContext) : Unit = { }
def SimpleUninitialize(obj : PlanetSideServerObject, context : ActorContext) : Unit = { def SimpleUninitialize(obj : PlanetSideServerObject, context : ActorContext) : Unit = {
obj.Actor ! akka.actor.PoisonPill context.stop(obj.Actor)
obj.Actor = ActorRef.noSender 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` * @param objectId the object id that is associated with this sort of `Vehicle`
*/ */
class VehicleDefinition(objectId : Int) extends ObjectDefinition(objectId) class VehicleDefinition(objectId : Int) extends ObjectDefinition(objectId)
with VitalityDefinition
with ResistanceProfileMutators with ResistanceProfileMutators
with DamageResistanceModel { with DamageResistanceModel {
private var maxHealth : Int = 100
/** vehicle shields offered through amp station facility benefits (generally: 20% of health + 1) */ /** vehicle shields offered through amp station facility benefits (generally: 20% of health + 1) */
private var maxShields : Int = 0 private var maxShields : Int = 0
/* key - seat index, value - seat object */ /* key - seat index, value - seat object */
@ -45,16 +45,11 @@ class VehicleDefinition(objectId : Int) extends ObjectDefinition(objectId)
private var destroyedModel : Option[DestroyedVehicle.Value] = None private var destroyedModel : Option[DestroyedVehicle.Value] = None
Name = "vehicle" Name = "vehicle"
Packet = VehicleDefinition.converter Packet = VehicleDefinition.converter
Damage = StandardVehicleDamage DamageUsing = StandardVehicleDamage
Resistance = StandardVehicleResistance ResistUsing = StandardVehicleResistance
Model = StandardResolutions.Vehicle Model = StandardResolutions.Vehicle
RepairDistance = 10
def MaxHealth : Int = maxHealth RepairRestoresAt = 1
def MaxHealth_=(health : Int) : Int = {
maxHealth = health
MaxHealth
}
def MaxShields : Int = maxShields def MaxShields : Int = maxShields

View file

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

View file

@ -1,12 +1,13 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject 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 //temporary location for these messages
object CommonMessages { object CommonMessages {
final case class Use(player : Player, data : Option[Any] = None) final case class Use(player : Player, data : Option[Any] = None)
final case class Unuse(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() 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.Player
import net.psforever.objects.serverobject.structures.Amenity import net.psforever.objects.serverobject.structures.Amenity
import net.psforever.packet.game.UseItemMessage 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. * 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 // Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.doors package net.psforever.objects.serverobject.doors
import net.psforever.objects.definition.ObjectDefinition import net.psforever.objects.serverobject.structures.AmenityDefinition
/** /**
* The definition for any `Door`. * The definition for any `Door`.
* Object Id 242 is a generic door. * Object Id 242 is a generic door.
*/ */
class DoorDefinition extends ObjectDefinition(242) { class DoorDefinition extends AmenityDefinition(242) {
Name = "door" Name = "door"
} }

View file

@ -2,6 +2,7 @@
package net.psforever.objects.serverobject.generator package net.psforever.objects.serverobject.generator
import net.psforever.objects.serverobject.structures.Amenity import net.psforever.objects.serverobject.structures.Amenity
import net.psforever.types.PlanetSideGeneratorState
/** /**
* The generator is a big feature of all major facilities. * 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 * @param gdef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields
*/ */
class Generator(private val gdef : GeneratorDefinition) extends Amenity { 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 def Definition : GeneratorDefinition = gdef
} }
@ -24,15 +33,6 @@ object Generator {
} }
import akka.actor.ActorContext 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 import net.psforever.types.Vector3
def Constructor(pos : Vector3)(id : Int, context : ActorContext) : Generator = { def Constructor(pos : Vector3)(id : Int, context : ActorContext) : Generator = {
import akka.actor.Props import akka.actor.Props

View file

@ -2,17 +2,147 @@
package net.psforever.objects.serverobject.generator package net.psforever.objects.serverobject.generator
import akka.actor.Actor 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`. * An `Actor` that handles messages being dispatched to a specific `Generator`.
* @param gen the `Generator` object being governed * @param gen the `Generator` object being governed
*/ */
class GeneratorControl(gen : Generator) extends Actor class GeneratorControl(gen : Generator) extends Actor
with FactionAffinityBehavior.Check { with FactionAffinityBehavior.Check
def FactionObject : FactionAffinity = gen with DamageableEntity
with RepairableEntity {
def FactionObject = gen
def DamageableObject = gen
def RepairableObject = gen
var imminentExplosion : Boolean = false
def receive : Receive = checkBehavior.orElse { def receive : Receive = checkBehavior
case _ => ; .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 // Copyright (c) 2020 PSForever
package net.psforever.objects.serverobject.generator 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. * The definition for a `Generator` object.
*/ */
class GeneratorDefinition(objectId : Int) extends ObjectDefinition(objectId) { class GeneratorDefinition(objectId : Int) extends AmenityDefinition(objectId) {
Name = "generator" 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.Player
import net.psforever.objects.serverobject.affinity.FactionAffinity import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.serverobject.hackable.Hackable.HackInfo
import net.psforever.packet.game.TriggeredSound import net.psforever.packet.game.TriggeredSound
import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID, Vector3} import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID, Vector3}
trait Hackable extends FactionAffinity { trait Hackable {
import Hackable._ _ : FactionAffinity =>
/** inportant information regarding the hack and how it was started */ /** inportant information regarding the hack and how it was started */
private var hackedBy : Option[HackInfo] = None private var hackedBy : Option[HackInfo] = None
def HackedBy : Option[HackInfo] = hackedBy def HackedBy : Option[HackInfo] = hackedBy
@ -66,6 +67,18 @@ trait Hackable extends FactionAffinity {
hackDuration = arr hackDuration = arr
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 { object Hackable {

View file

@ -1,3 +1,4 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.hackable package net.psforever.objects.serverobject.hackable
import akka.actor.Actor import akka.actor.Actor
@ -6,19 +7,19 @@ import net.psforever.objects.serverobject.CommonMessages
object HackableBehavior { object HackableBehavior {
/** /**
* The logic governing generic `Hackable` objects that use the `Hack` and `ClearHack` message. * 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` * @see `Hackable`
*/ */
trait GenericHackable { trait GenericHackable {
this : Actor => this : Actor =>
def HackableObject : Hackable def HackableObject : Hackable
val hackableBehavior : Receive = { val hackableBehavior : Receive = {
case CommonMessages.Hack(player) => case CommonMessages.Hack(player, _, _) =>
val obj = HackableObject val obj = HackableObject
obj.HackedBy = player obj.HackedBy = player
sender ! true sender ! true
case CommonMessages.ClearHack() => case CommonMessages.ClearHack() =>
val obj = HackableObject val obj = HackableObject
obj.HackedBy = None 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 package net.psforever.objects.serverobject.implantmech
import net.psforever.objects.Player import net.psforever.objects.Player
import net.psforever.objects.definition.ObjectDefinition
import net.psforever.objects.serverobject.hackable.Hackable import net.psforever.objects.serverobject.hackable.Hackable
import net.psforever.objects.serverobject.mount.Mountable import net.psforever.objects.serverobject.mount.Mountable
import net.psforever.objects.serverobject.structures.Amenity import net.psforever.objects.serverobject.structures.Amenity
import net.psforever.objects.vehicles.Seat import net.psforever.objects.vehicles.Seat
import net.psforever.packet.game.TriggeredSound 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. * 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 { object ImplantTerminalMech {
@ -53,10 +53,21 @@ object ImplantTerminalMech {
import akka.actor.ActorContext import akka.actor.ActorContext
/** /**
* Instantiate an configure a `ImplantTerminalMech` object * 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 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 * @param context a context to allow the object to properly set up `ActorSystem` functionality
* @return the `ImplantTerminalMech` object * @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 = { def Constructor(id : Int, context : ActorContext) : ImplantTerminalMech = {
import akka.actor.Props import akka.actor.Props
import net.psforever.objects.GlobalDefinitions import net.psforever.objects.GlobalDefinitions

View file

@ -2,26 +2,73 @@
package net.psforever.objects.serverobject.implantmech package net.psforever.objects.serverobject.implantmech
import akka.actor.Actor import akka.actor.Actor
import net.psforever.objects.serverobject.mount.MountableBehavior import net.psforever.objects.ballistics.ResolvedProjectile
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior} 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.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`. * An `Actor` that handles messages being dispatched to a specific `ImplantTerminalMech`.
* @param mech the "mech" object being governed * @param mech the "mech" object being governed
*/ */
class ImplantTerminalMechControl(mech : ImplantTerminalMech) extends Actor with FactionAffinityBehavior.Check class ImplantTerminalMechControl(mech : ImplantTerminalMech) extends Actor
with MountableBehavior.Mount with MountableBehavior.Dismount with HackableBehavior.GenericHackable { with FactionAffinityBehavior.Check
def MountableObject = mech //do not add type! with MountableBehavior.Mount
with MountableBehavior.Dismount
with HackableBehavior.GenericHackable
with DamageableEntity
with RepairableEntity {
def MountableObject = mech
def HackableObject = mech def HackableObject = mech
def FactionObject = mech
def FactionObject : FactionAffinity = mech def DamageableObject = mech
def RepairableObject = mech
def receive : Receive = checkBehavior def receive : Receive = checkBehavior
.orElse(mountBehavior) .orElse(mountBehavior)
.orElse(dismountBehavior) .orElse(dismountBehavior)
.orElse(hackableBehavior) .orElse(hackableBehavior)
.orElse(takesDamage)
.orElse(canBeRepairedByNanoDispenser)
.orElse { .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 _ => ; 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 // Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.implantmech package net.psforever.objects.serverobject.implantmech
import net.psforever.objects.definition.{ObjectDefinition, SeatDefinition} import net.psforever.objects.definition.SeatDefinition
import net.psforever.objects.vehicles.SeatArmorRestriction import net.psforever.objects.serverobject.structures.AmenityDefinition
/** /**
* The `Definition` for any `Terminal` that is of a type "implant_terminal_interface." * The `Definition` for any `Terminal` that is of a type "implant_terminal_interface."
* Implant terminals are composed of two components. * Implant terminals are composed of two components.
* This `Definition` constructs the visible mechanical tube component that can be mounted. * 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 */ /* key - seat index, value - seat object */
private val seats : Map[Int, SeatDefinition] = Map(0 -> new SeatDefinition) private val seats : Map[Int, SeatDefinition] = Map(0 -> new SeatDefinition)
/* key - entry point index, value - seat index */ /* key - entry point index, value - seat index */

View file

@ -2,6 +2,7 @@
package net.psforever.objects.serverobject.locks package net.psforever.objects.serverobject.locks
import akka.actor.Actor import akka.actor.Actor
import net.psforever.objects.{GlobalDefinitions, SimpleItem}
import net.psforever.objects.serverobject.CommonMessages import net.psforever.objects.serverobject.CommonMessages
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior} import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
import net.psforever.objects.serverobject.hackable.HackableBehavior import net.psforever.objects.serverobject.hackable.HackableBehavior
@ -18,6 +19,17 @@ class IFFLockControl(lock : IFFLock) extends Actor with FactionAffinityBehavior.
def receive : Receive = checkBehavior def receive : Receive = checkBehavior
.orElse(hackableBehavior) .orElse(hackableBehavior)
.orElse { .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 case _ => ; //no default message
} }
} }

View file

@ -1,12 +1,12 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.locks package net.psforever.objects.serverobject.locks
import net.psforever.objects.definition.ObjectDefinition import net.psforever.objects.serverobject.structures.AmenityDefinition
/** /**
* The definition for any `IFFLock`. * The definition for any `IFFLock`.
* Object Id 451 is a generic external lock. * Object Id 451 is a generic external lock.
*/ */
class IFFLockDefinition extends ObjectDefinition(451) { class IFFLockDefinition extends AmenityDefinition(451) {
Name = "iff_lock" 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 package net.psforever.objects.serverobject.mblocker
import akka.actor.Actor 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.{FactionAffinity, FactionAffinityBehavior}
import net.psforever.objects.serverobject.hackable.HackableBehavior import net.psforever.objects.serverobject.hackable.HackableBehavior
@ -16,6 +18,11 @@ class LockerControl(locker : Locker) extends Actor with FactionAffinityBehavior.
def receive : Receive = checkBehavior def receive : Receive = checkBehavior
.orElse(hackableBehavior) .orElse(hackableBehavior)
.orElse { .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 _ => ; case _ => ;
} }
} }

View file

@ -1,12 +1,12 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.mblocker package net.psforever.objects.serverobject.mblocker
import net.psforever.objects.definition.ObjectDefinition import net.psforever.objects.serverobject.structures.AmenityDefinition
/** /**
* The definition for any `Locker`. * The definition for any `Locker`.
* Object Id 524. * Object Id 524.
*/ */
class LockerDefinition extends ObjectDefinition(524) { class LockerDefinition extends AmenityDefinition(524) {
Name = "mb_locker" Name = "mb_locker"
} }

View file

@ -2,11 +2,12 @@
package net.psforever.objects.serverobject.mount package net.psforever.objects.serverobject.mount
import akka.actor.Actor 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.entity.{Identifiable, WorldEntity}
import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.affinity.FactionAffinity import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.serverobject.hackable.Hackable 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 import net.psforever.types.DriveState
object MountableBehavior { object MountableBehavior {
@ -17,51 +18,50 @@ object MountableBehavior {
* @see `Mountable` * @see `Mountable`
*/ */
trait Mount { trait Mount {
this : Actor => _ : Actor =>
def MountableObject : PlanetSideServerObject with Mountable with FactionAffinity
def MountableObject : PlanetSideGameObject with Mountable with FactionAffinity
val mountBehavior : Receive = { val mountBehavior : Receive = {
case Mountable.TryMount(user, seat_num) => case Mountable.TryMount(user, seat_num) =>
val obj = MountableObject val obj = MountableObject
obj.Seat(seat_num) match { if(MountTest(MountableObject, seat_num, user)) {
case Some(seat) => user.VehicleSeated = obj.GUID
sender ! Mountable.MountMessages(user, Mountable.CanMount(obj, seat_num))
var isHacked = false }
if(MountableObject.isInstanceOf[Hackable]) { else {
// This is a special case for implant terminals, since they're both mountable and hackable, but not jackable. sender ! Mountable.MountMessages(user, Mountable.CanNotMount(obj, seat_num))
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))
} }
} }
val turretMountBehavior : Receive = { protected def MountTest(obj : PlanetSideServerObject with Mountable, seatNumber : Int, player : Player) : Boolean = {
case Mountable.TryMount(user, seat_num) => (player.Faction == obj.Faction ||
val obj = MountableObject (obj match {
val definition = obj.Definition.asInstanceOf[TurretDefinition] case o : Hackable => o.HackedBy.isDefined
obj.Seat(seat_num) match { case _ => false
case Some(seat) => })) &&
if((!definition.FactionLocked || user.Faction == obj.Faction) && !obj.Destroyed &&
(seat.Occupant = user).contains(user)) { (obj.Seats.get(seatNumber) match {
user.VehicleSeated = obj.GUID case Some(seat) => (seat.Occupant = player).contains(player)
sender ! Mountable.MountMessages(user, Mountable.CanMount(obj, seat_num)) case _ => false
} })
else { }
sender ! Mountable.MountMessages(user, Mountable.CanNotMount(obj, seat_num)) }
}
case None => trait TurretMount extends Mount {
sender ! Mountable.MountMessages(user, Mountable.CanNotMount(obj, seat_num)) _ : 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 // Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.pad package net.psforever.objects.serverobject.pad
import net.psforever.objects.definition.ObjectDefinition import net.psforever.objects.serverobject.structures.AmenityDefinition
/** /**
* The definition for any `VehicleSpawnPad`. * 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 // 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 private var vehicle_creation_z_offset = 0f

View file

@ -1,9 +1,9 @@
package net.psforever.objects.serverobject.painbox package net.psforever.objects.serverobject.painbox
import net.psforever.objects.definition.ObjectDefinition import net.psforever.objects.serverobject.structures.AmenityDefinition
import net.psforever.types.Vector3 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 alwaysOn : Boolean = true
private var radius : Float = 0f private var radius : Float = 0f
private var damage : Int = 10 private var damage : Int = 10
@ -39,7 +39,7 @@ class PainboxDefinition(objectId : Int) extends ObjectDefinition(objectId) {
radius = 8.55f radius = 8.55f
sphereOffset = Vector3.Zero sphereOffset = Vector3.Zero
case _ => 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 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 package net.psforever.objects.serverobject.resourcesilo
import akka.actor.{ActorContext, Props} 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.objects.serverobject.structures.Amenity
import net.psforever.packet.game.UseItemMessage 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 { def Processing : Receive = checkBehavior.orElse {
case ResourceSilo.Use(player, msg) => 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) => case ResourceSilo.LowNtuWarning(enabled: Boolean) =>
resourceSilo.LowNtuWarningOn = enabled resourceSilo.LowNtuWarningOn = enabled
log.trace(s"LowNtuWarning: Silo ${resourceSilo.GUID} low ntu warning set to $enabled") log.trace(s"LowNtuWarning: Silo ${resourceSilo.GUID} low ntu warning set to $enabled")

View file

@ -1,12 +1,12 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.resourcesilo 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`. * The definition for any `Resource Silo`.
* Object Id 731. * Object Id 731.
*/ */
class ResourceSiloDefinition extends ObjectDefinition(731) { class ResourceSiloDefinition extends AmenityDefinition(731) {
Name = "resource_silo" Name = "resource_silo"
} }

View file

@ -1,10 +1,12 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.structures package net.psforever.objects.serverobject.structures
import net.psforever.objects.ballistics.ResolvedProjectile
import net.psforever.objects.serverobject.PlanetSideServerObject 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.objects.zones.{Zone, ZoneAware}
import net.psforever.types.{PlanetSideEmpire, Vector3} 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> * 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. * This association strips away at the internalization and redirects a reference to some properties somewhere else.
* An `Amenity` object belongs to its `Owner` object; * An `Amenity` object belongs to its `Owner` object;
* the `Amenity` objects look to its `Owner` object for some of its properties. * the `Amenity` objects look to its `Owner` object for some of its properties.
* @see `AmenityOwner`
* @see `FactionAffinity` * @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") private[this] val log = org.log4s.getLogger("Amenity")
/** what other entity has authority over this amenity; usually either a building or a vehicle */ /** what other entity has authority over this amenity; usually either a building or a vehicle */
private var owner : AmenityOwner = Building.NoBuilding private var owner : AmenityOwner = Building.NoBuilding
@ -71,4 +77,8 @@ abstract class Amenity extends PlanetSideServerObject with ZoneAware {
} }
LocationOffset 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 akka.actor.{ActorContext, ActorRef}
import net.psforever.objects.{GlobalDefinitions, Player} import net.psforever.objects.{GlobalDefinitions, Player}
import net.psforever.objects.definition.ObjectDefinition 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.hackable.Hackable
import net.psforever.objects.serverobject.painbox.Painbox import net.psforever.objects.serverobject.painbox.Painbox
import net.psforever.objects.serverobject.resourcesilo.ResourceSilo import net.psforever.objects.serverobject.resourcesilo.ResourceSilo
import net.psforever.objects.serverobject.terminals.CaptureTerminal import net.psforever.objects.serverobject.terminals.CaptureTerminal
import net.psforever.objects.serverobject.tube.SpawnTube import net.psforever.objects.serverobject.tube.SpawnTube
import net.psforever.objects.zones.Zone import net.psforever.objects.zones.Zone
import net.psforever.packet.game._ import net.psforever.packet.game.{Additional1, Additional2, Additional3}
import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID, Vector3} import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID, PlanetSideGeneratorState, Vector3}
import scalax.collection.{Graph, GraphEdge} import scalax.collection.{Graph, GraphEdge}
import services.Service import services.Service
import services.local.{LocalAction, LocalServiceMessage} import services.local.{LocalAction, LocalServiceMessage}
@ -182,17 +183,17 @@ class Building(private val name: String,
case _ => case _ =>
(false, PlanetSideEmpire.NEUTRAL, 0L) (false, PlanetSideEmpire.NEUTRAL, 0L)
} }
//TODO if we have a generator, get the current repair state //if we have no generator, assume the state is "Normal"
val (generatorState, boostGeneratorPain) = (PlanetSideGeneratorState.Normal, false) // todo: poll pain field strength 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 //if we have spawn tubes, determine if any of them are active
val (spawnTubesNormal, boostSpawnPain) : (Boolean, Boolean) = { val (spawnTubesNormal, boostSpawnPain) : (Boolean, Boolean) = {
val o = Amenities.collect({ case _ : SpawnTube => true }) ///TODO obj.Health > 0 val o = Amenities.collect({ case tube : SpawnTube if !tube.Destroyed => tube })
if(o.nonEmpty) { (o.nonEmpty, false) //TODO poll pain field strength
(o.foldLeft(false)(_ || _), false) //TODO poll pain field strength
}
else {
(true, false)
}
} }
val latticeBenefit : Int = { val latticeBenefit : Int = {
@ -312,6 +313,7 @@ object Building {
obj obj
} }
final case class AmenityStateChange(obj : Amenity)
final case class SendMapUpdate(all_clients: Boolean) final case class SendMapUpdate(all_clients: Boolean)
final case class TriggerZoneMapUpdate(zone_num: Int) 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 akka.actor.{Actor, ActorRef}
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior} 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.objects.zones.InterstellarCluster
import net.psforever.packet.game.BuildingInfoUpdateMessage import net.psforever.packet.game.BuildingInfoUpdateMessage
import services.ServiceManager import services.ServiceManager
@ -35,43 +37,62 @@ class BuildingControl(building : Building) extends Actor with FactionAffinityBeh
building.Amenities.foreach(_.Actor forward FactionAffinity.ConfirmFactionAffinity()) building.Amenities.foreach(_.Actor forward FactionAffinity.ConfirmFactionAffinity())
} }
sender ! FactionAffinity.AssertFactionAffinity(building, faction) 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) { case Building.AmenityStateChange(obj : SpawnTube) =>
if(galaxyService != ActorRef.noSender) galaxyService ! GalaxyServiceMessage(GalaxyAction.MapUpdate(msg)) if(building.Amenities.contains(obj)) {
} else { SendMapUpdate(allClients = true)
// Fake a GalaxyServiceResponse response back to just the sender
sender ! GalaxyServiceResponse("", GalaxyResponse.MapUpdate(msg))
} }
case default => case Building.AmenityStateChange(obj : Generator) =>
log.warn(s"BuildingControl: Unknown message $default received from ${sender().path}") 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.serverobject.PlanetSideServerObject
import net.psforever.objects.{GlobalDefinitions, SpawnPoint, SpawnPointDefinition} import net.psforever.objects.{GlobalDefinitions, SpawnPoint, SpawnPointDefinition}
import net.psforever.objects.zones.Zone import net.psforever.objects.zones.Zone
import net.psforever.packet.game.{Additional1, Additional2, Additional3, PlanetSideGeneratorState} import net.psforever.packet.game.{Additional1, Additional2, Additional3}
import net.psforever.types.{PlanetSideEmpire, Vector3} import net.psforever.types.{PlanetSideEmpire, PlanetSideGeneratorState, Vector3}
import scala.collection.mutable import scala.collection.mutable

View file

@ -2,6 +2,7 @@
package net.psforever.objects.serverobject.terminals package net.psforever.objects.serverobject.terminals
import akka.actor.Actor import akka.actor.Actor
import net.psforever.objects.{GlobalDefinitions, SimpleItem}
import net.psforever.objects.serverobject.CommonMessages import net.psforever.objects.serverobject.CommonMessages
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior} import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
import net.psforever.objects.serverobject.hackable.HackableBehavior import net.psforever.objects.serverobject.hackable.HackableBehavior
@ -14,6 +15,15 @@ class CaptureTerminalControl(terminal : CaptureTerminal) extends Actor with Fact
def receive : Receive = checkBehavior def receive : Receive = checkBehavior
.orElse(hackableBehavior) .orElse(hackableBehavior)
.orElse { .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 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 { Name = objectId match {
case 158 => "capture_terminal" case 158 => "capture_terminal"
case 751 => "secondary_capture" 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 akka.actor.{Actor, ActorRef, Cancellable}
import net.psforever.objects._ import net.psforever.objects._
import net.psforever.objects.serverobject.CommonMessages 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.hackable.HackableBehavior
import net.psforever.objects.serverobject.repair.RepairableAmenity
import net.psforever.objects.serverobject.structures.Building
import scala.collection.mutable import scala.collection.mutable
import scala.concurrent.duration._ 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`. * it returns the same type of messages - wrapped in a `TerminalMessage` - to the `sender`.
* @param term the proximity unit (terminal) * @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 var terminalAction : Cancellable = DefaultCancellable.obj
val callbacks : mutable.ListBuffer[ActorRef] = new mutable.ListBuffer[ActorRef]() val callbacks : mutable.ListBuffer[ActorRef] = new mutable.ListBuffer[ActorRef]()
val log = org.log4s.getLogger val log = org.log4s.getLogger
def FactionObject : FactionAffinity = term
def HackableObject = term
def TerminalObject : Terminal with ProximityUnit = term
def receive : Receive = checkBehavior def receive : Receive = checkBehavior
.orElse(hackableBehavior) .orElse(hackableBehavior)
.orElse { .orElse(takesDamage)
case CommonMessages.Use(_, Some(target : PlanetSideGameObject)) => .orElse(canBeRepairedByNanoDispenser)
if(term.Definition.asInstanceOf[ProximityDefinition].Validations.exists(p => p(target))) { .orElse {
Use(target, term.Continent, sender) case CommonMessages.Use(player, Some(item : SimpleItem)) if item.Definition == GlobalDefinitions.remote_electronics_kit =>
} //TODO setup certifications check
case CommonMessages.Use(_, Some((target : PlanetSideGameObject, callback : ActorRef))) => term.Owner match {
if(term.Definition.asInstanceOf[ProximityDefinition].Validations.exists(p => p(target))) { case b : Building if (b.Faction != player.Faction || b.CaptureConsoleIsHacked) && term.HackedBy.isEmpty =>
Use(target, term.Continent, callback) 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(_, _) => case CommonMessages.Use(_, _) =>
log.warn(s"unexpected format for CommonMessages.Use in this context") 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 validateFunc : PlanetSideGameObject=>Boolean = term.Validate(proxDef.UseRadius * proxDef.UseRadius, proxDef.Validations)
val callbackList = callbacks.toList val callbackList = callbacks.toList
term.Targets.zipWithIndex.foreach({ case(target, index) => term.Targets.zipWithIndex.foreach({ case(target, index) =>
if(validateFunc(target)) { if(!term.Destroyed && validateFunc(target)) {
callbackList.lift(index) match { callbackList.lift(index) match {
case Some(cback) => case Some(cback) =>
cback ! ProximityUnit.Action(term, target) cback ! ProximityUnit.Action(term, target)

View file

@ -17,22 +17,20 @@ import net.psforever.types.{PlanetSideGUID, Vector3}
* while `Vehicle`-owned terminals may not. * while `Vehicle`-owned terminals may not.
* @param tdef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields * @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 HackSound = TriggeredSound.HackTerminal
HackEffectDuration = Array(0, 30, 60, 90) HackEffectDuration = Array(0, 30, 60, 90)
HackDuration = Array(0, 10, 5, 3) HackDuration = Array(0, 10, 5, 3)
//the following fields and related methods are neither finalized nor integrated; GOTO Request //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 = { def Damaged(dam : Int) : Unit = {
health = Math.max(0, Health - dam) Health = Math.max(0, Health - dam)
} }
def Repair(rep : Int) : Unit = { 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 package net.psforever.objects.serverobject.terminals
import akka.actor.Actor import akka.actor.Actor
import net.psforever.objects.{GlobalDefinitions, SimpleItem}
import net.psforever.objects.serverobject.CommonMessages 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.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`. * An `Actor` that handles messages being dispatched to a specific `Terminal`.
* @param term the `Terminal` object being governed * @param term the `Terminal` object being governed
*/ */
class TerminalControl(term : Terminal) extends Actor with FactionAffinityBehavior.Check with HackableBehavior.GenericHackable { class TerminalControl(term : Terminal) extends Actor
def FactionObject : FactionAffinity = term with FactionAffinityBehavior.Check
with HackableBehavior.GenericHackable
with DamageableAmenity
with RepairableAmenity {
def FactionObject = term
def HackableObject = term def HackableObject = term
def DamageableObject = term
def RepairableObject = term
def receive : Receive = checkBehavior def receive : Receive = checkBehavior
.orElse(hackableBehavior) .orElse(hackableBehavior)
.orElse { .orElse(takesDamage)
.orElse(canBeRepairedByNanoDispenser)
.orElse {
case Terminal.Request(player, msg) => case Terminal.Request(player, msg) =>
sender ! Terminal.TerminalMessage(player, msg, term.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 _ => ; case _ => ;
} }

View file

@ -3,12 +3,13 @@ package net.psforever.objects.serverobject.terminals
import net.psforever.objects.Player import net.psforever.objects.Player
import net.psforever.objects.definition.converter.TerminalConverter import net.psforever.objects.definition.converter.TerminalConverter
import net.psforever.objects.serverobject.structures.AmenityDefinition
/** /**
* The basic definition for any `Terminal` object. * The basic definition for any `Terminal` object.
* @param objectId the object's identifier number * @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" Name = "terminal"
Packet = new TerminalConverter Packet = new TerminalConverter

View file

@ -2,16 +2,47 @@
package net.psforever.objects.serverobject.tube package net.psforever.objects.serverobject.tube
import akka.actor.Actor 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`. * An `Actor` that handles messages being dispatched to a specific `SpawnTube`.
* @param tube the `SpawnTube` object being governed * @param tube the `SpawnTube` object being governed
*/ */
class SpawnTubeControl(tube : SpawnTube) extends Actor with FactionAffinityBehavior.Check { class SpawnTubeControl(tube : SpawnTube) extends Actor
def FactionObject : FactionAffinity = tube 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 override def toString : String = tube.Definition.Name
} }

View file

@ -3,15 +3,14 @@ package net.psforever.objects.serverobject.tube
import akka.actor.ActorContext import akka.actor.ActorContext
import net.psforever.objects.SpawnPointDefinition import net.psforever.objects.SpawnPointDefinition
import net.psforever.objects.definition.ObjectDefinition
import net.psforever.objects.definition.converter.SpawnTubeConverter import net.psforever.objects.definition.converter.SpawnTubeConverter
import net.psforever.objects.serverobject.PlanetSideServerObject 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. * 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 { with SpawnPointDefinition {
Packet = new SpawnTubeConverter 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.equipment.JammableUnit
import net.psforever.objects.serverobject.structures.Amenity import net.psforever.objects.serverobject.structures.Amenity
import net.psforever.types.Vector3 import net.psforever.types.Vector3
import net.psforever.objects.vital.{DamageResistanceModel, StandardResistanceProfile, Vitality}
class FacilityTurret(tDef : FacilityTurretDefinition) extends Amenity class FacilityTurret(tDef : FacilityTurretDefinition) extends Amenity
with WeaponTurret with WeaponTurret
with JammableUnit 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
WeaponTurret.LoadDefinition(this) 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 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 def Definition : FacilityTurretDefinition = tDef
} }

View file

@ -1,20 +1,17 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.turret package net.psforever.objects.serverobject.turret
import akka.actor.{Actor, Cancellable} import akka.actor.Actor
import net.psforever.objects.{DefaultCancellable, GlobalDefinitions, Player}
import net.psforever.objects.ballistics.ResolvedProjectile import net.psforever.objects.ballistics.ResolvedProjectile
import net.psforever.objects.equipment.{JammableMountedWeapons, JammableUnit} import net.psforever.objects.{GlobalDefinitions, Player}
import net.psforever.objects.serverobject.mount.{Mountable, MountableBehavior} import net.psforever.objects.equipment.JammableMountedWeapons
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior} import net.psforever.objects.serverobject.mount.MountableBehavior
import net.psforever.objects.vital.Vitality import net.psforever.objects.serverobject.affinity.FactionAffinityBehavior
import net.psforever.objects.zones.Zone import net.psforever.objects.serverobject.damage.DamageableWeaponTurret
import net.psforever.types.PlanetSideGUID import net.psforever.objects.serverobject.repair.Repairable.Target
import services.Service import net.psforever.objects.serverobject.repair.RepairableWeaponTurret
import services.avatar.{AvatarAction, AvatarServiceMessage} import services.avatar.{AvatarAction, AvatarServiceMessage}
import services.local.{LocalAction, LocalServiceMessage} import services.local.{LocalAction, LocalServiceMessage}
import services.vehicle.{VehicleAction, VehicleServiceMessage}
import services.vehicle.support.TurretUpgrader
import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._ import scala.concurrent.duration._
@ -30,27 +27,30 @@ import scala.concurrent.duration._
*/ */
class FacilityTurretControl(turret : FacilityTurret) extends Actor class FacilityTurretControl(turret : FacilityTurret) extends Actor
with FactionAffinityBehavior.Check with FactionAffinityBehavior.Check
with MountableBehavior.TurretMount
with MountableBehavior.Dismount with MountableBehavior.Dismount
with DamageableWeaponTurret
with RepairableWeaponTurret
with JammableMountedWeapons { with JammableMountedWeapons {
def FactionObject = turret
def MountableObject = turret
def JammableObject = turret
def DamageableObject = turret
def RepairableObject = turret
if(turret.Definition == GlobalDefinitions.vanu_sentry_turret) { if(turret.Definition == GlobalDefinitions.vanu_sentry_turret) {
// todo: Schedule this to start when weapon is discharged, not all the time // todo: Schedule this to start when weapon is discharged, not all the time
context.system.scheduler.schedule(3 seconds, 200 milliseconds, self, FacilityTurret.RechargeAmmo()) 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 def receive : Receive = checkBehavior
.orElse(jammableBehavior) .orElse(jammableBehavior)
.orElse(mountBehavior)
.orElse(dismountBehavior) .orElse(dismountBehavior)
.orElse(takesDamage)
.orElse(canBeRepairedByNanoDispenser)
.orElse { .orElse {
case FacilityTurret.RechargeAmmo() => case FacilityTurret.RechargeAmmo() =>
val weapon = turret.ControlledWeapon(1).get.asInstanceOf[net.psforever.objects.Tool] val weapon = turret.ControlledWeapon(1).get.asInstanceOf[net.psforever.objects.Tool]
// recharge when last shot fired 3s delay, +1, 200ms interval // recharge when last shot fired 3s delay, +1, 200ms interval
if(weapon.Magazine < weapon.MaxMagazine && System.nanoTime() - weapon.LastDischarge > 3000000000L) { if(weapon.Magazine < weapon.MaxMagazine && System.nanoTime() - weapon.LastDischarge > 3000000000L) {
weapon.Magazine += 1 weapon.Magazine += 1
@ -60,117 +60,27 @@ class FacilityTurretControl(turret : FacilityTurret) extends Actor
case _ => ; 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 _ => ; case _ => ;
} }
}
object FacilityTurretControl { override protected def DestructionAwareness(target : Target, cause : ResolvedProjectile) : Unit = {
def HandleDamageResolution(target : FacilityTurret, cause : ResolvedProjectile, damage : Int) : Unit = { super.DestructionAwareness(target, cause)
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 = {
val zone = target.Zone val zone = target.Zone
val zoneId = zone.Id val zoneId = zone.Id
//alert occupants to damage source val events = zone.AvatarEvents
target.Seats.values.filter(seat => { val tguid = target.GUID
seat.isOccupied && seat.Occupant.get.isAlive events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(tguid, 50, 1))
}).foreach(seat => { events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(tguid, 51, 1))
val tplayer = seat.Occupant.get
zone.AvatarEvents ! AvatarServiceMessage(zoneId, AvatarAction.HitHint(attribution, tplayer.GUID))
})
} }
/** override def Restoration(obj : Target) : Unit = {
* na super.Restoration(obj)
* @param target na val zone = turret.Zone
* @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
val zoneId = zone.Id val zoneId = zone.Id
target.Seats.values.filter(seat => { val events = zone.AvatarEvents
seat.isOccupied && seat.Occupant.get.isAlive val tguid = turret.GUID
}).foreach(seat => { events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(tguid, 50, 0))
val tplayer = seat.Occupant.get events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(tguid, 51, 0))
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))
}
} }
} }

View file

@ -1,17 +1,17 @@
// Copyright (c) 2019 PSForever // Copyright (c) 2019 PSForever
package net.psforever.objects.serverobject.turret 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} import net.psforever.objects.vital.{StandardResolutions, StandardVehicleDamage, StandardVehicleResistance}
/** /**
* The definition for any `FacilityTurret`. * The definition for any `FacilityTurret`.
* @param objectId the object's identifier number * @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 { with TurretDefinition {
Damage = StandardVehicleDamage DamageUsing = StandardVehicleDamage
Resistance = StandardVehicleResistance ResistUsing = StandardVehicleResistance
Model = StandardResolutions.FacilityTurrets Model = StandardResolutions.FacilityTurrets
} }

View file

@ -15,8 +15,6 @@ trait TurretDefinition extends ResistanceProfileMutators
with DamageResistanceModel { with DamageResistanceModel {
odef : ObjectDefinition => odef : ObjectDefinition =>
Turrets(odef.ObjectId) //let throw NoSuchElementException Turrets(odef.ObjectId) //let throw NoSuchElementException
private var maxHealth : Int = 100
/* key - entry point index, value - seat index */ /* key - entry point index, value - seat index */
private val mountPoints : mutable.HashMap[Int, Int] = mutable.HashMap() private val mountPoints : mutable.HashMap[Int, Int] = mutable.HashMap()
/* key - seat number, value - hash map (below) */ /* key - seat number, value - hash map (below) */
@ -29,13 +27,6 @@ trait TurretDefinition extends ResistanceProfileMutators
* see `MannedTurret.TurretAmmoBox` for details */ * see `MannedTurret.TurretAmmoBox` for details */
private var hasReserveAmmunition : Boolean = false 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 MountPoints : mutable.HashMap[Int, Int] = mountPoints
def Weapons : mutable.HashMap[Int, mutable.HashMap[TurretUpgrade.Value, ToolDefinition]] = weapons def Weapons : mutable.HashMap[Int, mutable.HashMap[TurretUpgrade.Value, ToolDefinition]] = weapons

View file

@ -1,8 +1,8 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.turret 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.definition.{AmmoBoxDefinition, SeatDefinition, ToolDefinition}
import net.psforever.objects._
import net.psforever.objects.equipment.{Equipment, EquipmentSlot} import net.psforever.objects.equipment.{Equipment, EquipmentSlot}
import net.psforever.objects.inventory.{Container, GridInventory} import net.psforever.objects.inventory.{Container, GridInventory}
import net.psforever.objects.serverobject.affinity.FactionAffinity import net.psforever.objects.serverobject.affinity.FactionAffinity
@ -13,10 +13,8 @@ trait WeaponTurret extends FactionAffinity
with Mountable with Mountable
with MountedWeapons with MountedWeapons
with Container { 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 */ /** 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) })) protected val seats : Map[Int, Chair] = Map(0 -> Chair(new SeatDefinition() { ControlledWeapon = Some(1) }))
/** turrets have just one weapon; this is just standard interface */ /** 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 /** may or may not have inaccessible inventory space
* see `ReserveAmmunition` in the definition */ * see `ReserveAmmunition` in the definition */
protected val inventory : GridInventory = new GridInventory() { protected val inventory : GridInventory = new GridInventory() {
import net.psforever.types.PlanetSideGUID import net.psforever.types.PlanetSideGUID
override def Remove(index : Int) : Boolean = false override def Remove(index : Int) : Boolean = false
override def Remove(guid : PlanetSideGUID) : 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 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
def Health_=(toHealth : Int) : Int = { they are purely for class visibility
health = toHealth */
health def Health : Int
}
def MaxHealth : 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 = { def Upgrade_=(upgrade : TurretUpgrade.Value) : TurretUpgrade.Value = {
jammered = jamState middleOfUpgrade = true //blocking flag; block early
Jammered 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 def Definition : TurretDefinition
} }
@ -110,8 +135,6 @@ object WeaponTurret {
*/ */
def LoadDefinition(turret : WeaponTurret, tdef : TurretDefinition) : WeaponTurret = { def LoadDefinition(turret : WeaponTurret, tdef : TurretDefinition) : WeaponTurret = {
import net.psforever.objects.equipment.EquipmentSize.BaseTurretWeapon import net.psforever.objects.equipment.EquipmentSize.BaseTurretWeapon
//general stuff
turret.Health = tdef.MaxHealth
//create weapons; note the class //create weapons; note the class
turret.weapons = tdef.Weapons.map({case (num, upgradePaths) => turret.weapons = tdef.Weapons.map({case (num, upgradePaths) =>
val slot = EquipmentSlot(BaseTurretWeapon) 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 // Copyright (c) 2017 PSForever
package net.psforever.objects.vehicles package net.psforever.objects.vehicles
import akka.actor.ActorContext import akka.actor.{ActorContext, ActorRef}
import net.psforever.objects.definition.SimpleDeployableDefinition import net.psforever.objects.definition.BaseDeployableDefinition
import net.psforever.objects._ import net.psforever.objects._
import net.psforever.objects.ce.TelepadLike import net.psforever.objects.ce.{DeployedItem, TelepadLike}
import net.psforever.objects.serverobject.structures.Amenity 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.terminals._
import net.psforever.objects.serverobject.tube.{SpawnTube, SpawnTubeDefinition} import net.psforever.objects.serverobject.tube.{SpawnTube, SpawnTubeDefinition}
import net.psforever.objects.vehicles.Utility.InternalTelepadDefinition
import net.psforever.packet.game.ItemTransactionMessage import net.psforever.packet.game.ItemTransactionMessage
import net.psforever.types.{PlanetSideGUID, Vector3} 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. * and allows it to serve as one of the terminal points of a Router-telepad teleportation system.
* @param ddef na * @param ddef na
*/ */
class InternalTelepad(ddef : SimpleDeployableDefinition) extends Amenity class InternalTelepad(ddef : InternalTelepadDefinition) extends Amenity
with UtilityWorldEntity
with TelepadLike { with TelepadLike {
/** a link to the telepad that serves as the other endpoint of this teleportation system */ /** a link to the telepad that serves as the other endpoint of this teleportation system */
private var activeTelepad : Option[PlanetSideGUID] = None private var activeTelepad : Option[PlanetSideGUID] = None
@ -215,6 +219,21 @@ object Utility {
def Definition = ddef 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. * Provide the called-out object's logic.
* @param util the type of the `Amenity` object * @param util the type of the `Amenity` object
@ -241,3 +260,15 @@ object Utility {
TelepadLike.Setup 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 package net.psforever.objects.vehicles
import akka.actor.{Actor, ActorRef} 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.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.mount.{Mountable, MountableBehavior}
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior} import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
import net.psforever.objects.serverobject.deploy.{Deployment, DeploymentBehavior} import net.psforever.objects.serverobject.damage.DamageableVehicle
import net.psforever.objects.vital.{VehicleShieldCharge, Vitality} import net.psforever.objects.serverobject.deploy.DeploymentBehavior
import net.psforever.objects.zones.Zone import net.psforever.objects.serverobject.repair.RepairableVehicle
import net.psforever.types.{DriveState, ExoSuitType, PlanetSideGUID, Vector3} import net.psforever.objects.vital.VehicleShieldCharge
import services.{RemoverActor, Service} import net.psforever.types.{ExoSuitType, PlanetSideGUID}
import services.avatar.{AvatarAction, AvatarServiceMessage} import services.vehicle.{VehicleAction, VehicleServiceMessage}
import services.local.{LocalAction, LocalServiceMessage}
import services.vehicle.{VehicleAction, VehicleService, VehicleServiceMessage}
/** /**
* An `Actor` that handles messages being dispatched to a specific `Vehicle`.<br> * 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 DeploymentBehavior
with MountableBehavior.Mount with MountableBehavior.Mount
with MountableBehavior.Dismount with MountableBehavior.Dismount
with CargoBehavior
with DamageableVehicle
with RepairableVehicle
with JammableMountedWeapons { with JammableMountedWeapons {
//make control actors belonging to utilities when making control actor belonging to vehicle //make control actors belonging to utilities when making control actor belonging to vehicle
vehicle.Utilities.foreach({case (_, util) => util.Setup }) vehicle.Utilities.foreach({case (_, util) => util.Setup })
def MountableObject = vehicle def MountableObject = vehicle
def CargoObject = vehicle
def JammableObject = vehicle def JammableObject = vehicle
def FactionObject = vehicle def FactionObject = vehicle
def DeploymentObject = vehicle def DeploymentObject = vehicle
def DamageableObject = vehicle
def RepairableObject = vehicle
def receive : Receive = Enabled def receive : Receive = Enabled
override def postStop() : Unit = { override def postStop() : Unit = {
super.postStop() super.postStop()
vehicle.Utilities.values.foreach { util => vehicle.Utilities.values.foreach { util =>
util().Actor ! akka.actor.PoisonPill context.stop(util().Actor)
util().Actor = ActorRef.noSender util().Actor = ActorRef.noSender
} }
} }
def Enabled : Receive = checkBehavior def Enabled : Receive = checkBehavior
.orElse(deployBehavior) .orElse(deployBehavior)
.orElse(cargoBehavior)
.orElse(jammableBehavior) .orElse(jammableBehavior)
.orElse(takesDamage)
.orElse(canBeRepairedByNanoDispenser)
.orElse { .orElse {
case msg : Mountable.TryMount => case msg : Mountable.TryMount =>
tryMountBehavior.apply(msg) tryMountBehavior.apply(msg)
@ -61,23 +66,6 @@ class VehicleControl(vehicle : Vehicle) extends Actor
case msg : Mountable.TryDismount => case msg : Mountable.TryDismount =>
dismountBehavior.apply(msg) 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) => case Vehicle.ChargeShields(amount) =>
val now : Long = System.nanoTime val now : Long = System.nanoTime
//make certain vehicle doesn't charge shields too quickly //make certain vehicle doesn't charge shields too quickly
@ -95,6 +83,12 @@ class VehicleControl(vehicle : Vehicle) extends Actor
} }
sender ! FactionAffinity.AssertFactionAffinity(vehicle, faction) 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() => case Vehicle.PrepareForDeletion() =>
CancelJammeredSound(vehicle) CancelJammeredSound(vehicle)
CancelJammeredStatus(vehicle) CancelJammeredStatus(vehicle)
@ -139,6 +133,12 @@ class VehicleControl(vehicle : Vehicle) extends Actor
case _ => case _ =>
} }
override def TryJammerEffectActivate(target : Any, cause : ResolvedProjectile) : Unit = {
if(vehicle.MountedIn.isEmpty) {
super.TryJammerEffectActivate(target, cause)
}
}
} }
object VehicleControl { object VehicleControl {
@ -160,115 +160,4 @@ object VehicleControl {
case _ => false 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; * 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; * 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 * the vehicle-specific channel with which all passengers are coordinated back to the original vehicle
* @param vehicle na * @param vehicle the vehicle in transport
* @param origin na * @param origin where the vehicle originally was
* @param driverName na * @param driverName the name of the driver when the transport process started
* @param passengers na * @param passengers the paired names and seat indices of all passengers when the transport process started
* @param cargo na * @param cargo the paired driver names and cargo hold indices of all cargo vehicles when the transport process started
*/
/**
* 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
*/ */
final case class VehicleManifest(file : String, final case class VehicleManifest(file : String,
vehicle : Vehicle, vehicle : Vehicle,
@ -55,4 +47,4 @@ object VehicleManifest {
def ManifestChannelName(vehicle : Vehicle) : String = { def ManifestChannelName(vehicle : Vehicle) : String = {
s"transport-vehicle-channel-${vehicle.GUID.guid}" s"transport-vehicle-channel-${vehicle.GUID.guid}"
} }
} }

View file

@ -26,26 +26,26 @@ import net.psforever.objects.vital.resolution.ResolutionCalculations
*/ */
trait DamageResistanceModel { trait DamageResistanceModel {
/** the functionality that processes damage; required */ /** the functionality that processes damage; required */
private var damage : DamageSelection = NoDamageSelection private var damageUsing : DamageSelection = NoDamageSelection
/** the functionality that processes resistance; optional */ /** 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 */ /** the functionality that prepares for damage application actions; required */
private var model : ResolutionCalculations.Form = NoResolutions.Calculate private var model : ResolutionCalculations.Form = NoResolutions.Calculate
def Damage : DamageSelection = damage def DamageUsing : DamageSelection = damageUsing
def Damage_=(selector : DamageSelection) : DamageSelection = { def DamageUsing_=(selector : DamageSelection) : DamageSelection = {
damage = selector damageUsing = selector
Damage DamageUsing
} }
def Resistance : ResistanceSelection = resistance def ResistUsing : ResistanceSelection = resistUsing
def Resistance_=(selector : ResistanceSelection) : ResistanceSelection = { def ResistUsing_=(selector : ResistanceSelection) : ResistanceSelection = {
resistance = selector resistUsing = selector
Resistance ResistUsing
} }
def Model : ResolutionCalculations.Form = model def Model : ResolutionCalculations.Form = model
@ -61,8 +61,8 @@ trait DamageResistanceModel {
* @return a function literal that encapsulates delayed modification instructions for certain objects * @return a function literal that encapsulates delayed modification instructions for certain objects
*/ */
def Calculate(data : ResolvedProjectile) : ResolutionCalculations.Output = { def Calculate(data : ResolvedProjectile) : ResolutionCalculations.Output = {
val dam : ProjectileCalculations.Form = Damage(data) val dam : ProjectileCalculations.Form = DamageUsing(data)
val res : ProjectileCalculations.Form = Resistance(data) val res : ProjectileCalculations.Form = ResistUsing(data)
Model(dam, res, data) Model(dam, res, data)
} }
@ -73,8 +73,8 @@ trait DamageResistanceModel {
* @return a function literal that encapsulates delayed modification instructions for certain objects * @return a function literal that encapsulates delayed modification instructions for certain objects
*/ */
def Calculate(data : ResolvedProjectile, resolution : ProjectileResolution.Value) : ResolutionCalculations.Output = { def Calculate(data : ResolvedProjectile, resolution : ProjectileResolution.Value) : ResolutionCalculations.Output = {
val dam : ProjectileCalculations.Form = Damage(resolution) val dam : ProjectileCalculations.Form = DamageUsing(resolution)
val res : ProjectileCalculations.Form = Resistance(resolution) val res : ProjectileCalculations.Form = ResistUsing(resolution)
Model(dam, res, data) Model(dam, res, data)
} }
} }

View file

@ -95,6 +95,18 @@ object AircraftLashDamage extends DamageCalculations(
DistanceBetweenTargetandSource DistanceBetweenTargetandSource
) )
object AmenityHitDamage extends DamageCalculations(
DirectHitDamageWithDegrade,
DamageWithModifiers(DamageAgainstVehicle),
DistanceBetweenTargetandSource
)
object AmenitySplashDamage extends DamageCalculations(
SplashDamageWithRadialDegrade,
DamageWithModifiers(DamageAgainstVehicle),
DistanceFromExplosionToTarget
)
object NoDamageSelection extends DamageSelection { object NoDamageSelection extends DamageSelection {
def Direct = None def Direct = None
def Splash = None def Splash = None
@ -130,3 +142,9 @@ object StandardDeployableDamage extends DamageSelection {
def Splash = VehicleSplashDamage.Calculate def Splash = VehicleSplashDamage.Calculate
def Lash = NoDamage.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 // Copyright (c) 2017 PSForever
package net.psforever.objects.vital 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.projectile.ProjectileCalculations
import net.psforever.objects.vital.resistance.{ResistanceCalculations, ResistanceSelection} import net.psforever.objects.vital.resistance.{ResistanceCalculations, ResistanceSelection}
@ -50,6 +50,16 @@ object VehicleAggravatedResistance extends ResistanceCalculations[VehicleSource]
ResistanceCalculations.VehicleAggravatedExtractor ResistanceCalculations.VehicleAggravatedExtractor
) )
object AmenityHitResistance extends ResistanceCalculations[ObjectSource](
ResistanceCalculations.ValidAmenityTarget,
ResistanceCalculations.OtherDirectExtractor
)
object AMenitySplashResistance extends ResistanceCalculations[ObjectSource](
ResistanceCalculations.ValidAmenityTarget,
ResistanceCalculations.OtherSplashExtractor
)
object NoResistanceSelection extends ResistanceSelection { object NoResistanceSelection extends ResistanceSelection {
def Direct : ProjectileCalculations.Form = None def Direct : ProjectileCalculations.Form = None
def Splash : ProjectileCalculations.Form = None def Splash : ProjectileCalculations.Form = None
@ -70,3 +80,10 @@ object StandardVehicleResistance extends ResistanceSelection {
def Lash : ProjectileCalculations.Form = VehicleLashResistance.Calculate def Lash : ProjectileCalculations.Form = VehicleLashResistance.Calculate
def Aggravated : ProjectileCalculations.Form = VehicleAggravatedResistance.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 SimpleDeployables : ResolutionCalculations.Form = SimpleResolutions.Calculate
def ComplexDeployables : ResolutionCalculations.Form = ComplexDeployableResolutions.Calculate def ComplexDeployables : ResolutionCalculations.Form = ComplexDeployableResolutions.Calculate
def FacilityTurrets : ResolutionCalculations.Form = SimpleResolutions.Calculate def FacilityTurrets : ResolutionCalculations.Form = SimpleResolutions.Calculate
def Amenities : ResolutionCalculations.Form = SimpleResolutions.Calculate
} }

View file

@ -1,101 +1,48 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
package net.psforever.objects.vital package net.psforever.objects.vital
import net.psforever.objects.PlanetSideGameObject import net.psforever.objects.ballistics.ResolvedProjectile
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.vital.resolution.ResolutionCalculations 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). * 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. * 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 * The damage model is provided.
* in reverse chronological order.
* The damage model is also provided.
*/ */
trait Vitality { trait Vitality extends VitalsHistory {
this : PlanetSideGameObject => 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 */ def Health : Int = health
private var vitalHistory : List[VitalsActivity] = List.empty[VitalsActivity]
def History : List[VitalsActivity] = vitalHistory def Health_=(assignHealth : Int) : Int = {
health = math.min(math.max(0, assignHealth), MaxHealth)
/** Health
* 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 DefaultHealth : Int = defaultHealth.getOrElse(Definition.DefaultHealth)
* Very common example of a `VitalsActivity` event involving weapon discharge.
* @param projectile the fully-informed entry of discharge of a weapon def MaxHealth : Int = maxHealth.getOrElse(Definition.MaxHealth)
* @return the list of previous changes to this object's vital statistics
*/ def MaxHealth_=(default : Int) : Int = MaxHealth_=(Some(default))
def History(projectile : ResolvedProjectile) : List[VitalsActivity] = {
vitalHistory = DamageFromProjectile(projectile) +: vitalHistory def MaxHealth_=(default : Option[Int]) : Int = {
vitalHistory maxHealth = default
MaxHealth
} }
/** def CanDamage : Boolean = {
* Find, specifically, the last instance of a weapon discharge vital statistics change. Definition.Damageable && Health > 0
* @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 ClearHistory() : List[VitalsActivity] = { def CanRepair : Boolean = {
val out = vitalHistory Definition.Repairable && Health < MaxHealth && (Health > 0 || Definition.RepairIfDestroyed)
vitalHistory = List.empty[VitalsActivity]
out
} }
def DamageModel : DamageResistanceModel def DamageModel : DamageResistanceModel
def Definition : VitalityDefinition
} }
object Vitality { 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 DamageAgainstMaxSuit(profile : DamageProfile) : Int = profile.Damage3
def DamageAgainstUnknown(profile : DamageProfile) : Int = profile.Damage4 def DamageAgainstBFR(profile : DamageProfile) : Int = profile.Damage4
//raw damage selection functions //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. * In the same way, the five damage modifiers that are applied to the same kind of damage.
*/ */
trait DamageProfile { trait DamageProfile {
/** `damage0` is for basic infantry */
def Damage0 : Int def Damage0 : Int
/** `damage0` is for basic infantry */
def Damage0_=(damage : Int) : Int def Damage0_=(damage : Int) : Int
/** `damage1` is for armor, amenities, deployables, etc. */
def Damage1 : Int def Damage1 : Int
/** `damage1` is for armor, amenities, deployables, etc. */
def Damage1_=(damage : Int) : Int def Damage1_=(damage : Int) : Int
/** `damage2` if for aircraft */
def Damage2 : Int def Damage2 : Int
/** `damage2` if for aircraft */
def Damage2_=(damage : Int) : Int def Damage2_=(damage : Int) : Int
/** `damage3` is for mechanized infantry */
def Damage3 : Int def Damage3 : Int
/** `damage3` is for mechanized infantry */
def Damage3_=(damage : Int) : Int def Damage3_=(damage : Int) : Int
/** `damage4` is for battleframe robotics */
def Damage4 : Int def Damage4 : Int
/** `damage4` is for battleframe robotics */
def Damage4_=(damage : Int) : Int 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.GlobalDefinitions
import net.psforever.objects.ballistics._ import net.psforever.objects.ballistics._
import net.psforever.objects.definition.ExoSuitDefinition import net.psforever.objects.definition.ExoSuitDefinition
import net.psforever.objects.serverobject.structures.Amenity
import net.psforever.objects.vital.projectile.ProjectileCalculations import net.psforever.objects.vital.projectile.ProjectileCalculations
import net.psforever.types.ExoSuitType 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 //extractors
def NoResistExtractor(target : SourceEntry) : Int = 0 def NoResistExtractor(target : SourceEntry) : Int = 0
@ -122,5 +137,9 @@ object ResistanceCalculations {
def VehicleRadiationExtractor(target : VehicleSource) : Float = target.Definition.RadiationShielding 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 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.{Player, TurretDeployable, Vehicle}
import net.psforever.objects.ballistics.{PlayerSource, ResolvedProjectile} import net.psforever.objects.ballistics.{PlayerSource, ResolvedProjectile}
import net.psforever.objects.ce.{ComplexDeployable, Deployable} 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.serverobject.turret.FacilityTurret
import net.psforever.objects.vital.Vitality
import net.psforever.objects.vital.projectile.ProjectileCalculations import net.psforever.objects.vital.projectile.ProjectileCalculations
/** /**
@ -112,10 +116,13 @@ object ResolutionCalculations {
def SubtractWithRemainder(current : Int, damage : Int) : (Int, Int) = { def SubtractWithRemainder(current : Int, damage : Int) : (Int, Int) = {
val a = Math.max(0, current - damage) val a = Math.max(0, current - damage)
val remainingDamage = Math.abs(current - damage - a) val remainingDamage = Math.abs(current - damage - a)
(a, remainingDamage) (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. * 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. * 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) var result = (0, 0)
//TODO Personal Shield implant test should go here and modify the values a and b //TODO Personal Shield implant test should go here and modify the values a and b
if(player.isAlive && !(a == 0 && b == 0)) { if(player.isAlive && !(a == 0 && b == 0)) {
player.History(data)
if(player.Capacitor.toInt > 0 && player.isShielded) { if(player.Capacitor.toInt > 0 && player.isShielded) {
// Subtract armour damage from capacitor // Subtract armour damage from capacitor
result = SubtractWithRemainder(player.Capacitor.toInt, b) result = SubtractWithRemainder(player.Capacitor.toInt, b)
@ -172,8 +178,7 @@ object ResolutionCalculations {
*/ */
def VehicleApplication(damage : Int, data : ResolvedProjectile)(target : Any) : ResolvedProjectile = { def VehicleApplication(damage : Int, data : ResolvedProjectile)(target : Any) : ResolvedProjectile = {
target match { target match {
case vehicle : Vehicle if vehicle.Health > 0 && damage > 0 => case vehicle : Vehicle if CanDamage(vehicle, damage, data) =>
vehicle.History(data)
val shields = vehicle.Shields val shields = vehicle.Shields
if(shields > damage) { if(shields > damage) {
vehicle.Shields = shields - damage vehicle.Shields = shields - damage
@ -192,12 +197,12 @@ object ResolutionCalculations {
def SimpleApplication(damage : Int, data : ResolvedProjectile)(target : Any) : ResolvedProjectile = { def SimpleApplication(damage : Int, data : ResolvedProjectile)(target : Any) : ResolvedProjectile = {
target match { target match {
case obj : Deployable if obj.Health > 0 => case obj : Deployable if CanDamage(obj, damage, data) =>
obj.Health -= damage obj.Health -= damage
obj.History(data) case turret : FacilityTurret if CanDamage(turret, damage, data) =>
case turret : FacilityTurret if turret.Health > 0 =>
turret.Health -= damage turret.Health -= damage
turret.History(data) case amenity : Amenity if CanDamage(amenity, damage, data) =>
amenity.Health -= damage
case _ => ; case _ => ;
} }
data data
@ -205,8 +210,7 @@ object ResolutionCalculations {
def ComplexDeployableApplication(damage : Int, data : ResolvedProjectile)(target : Any) : ResolvedProjectile = { def ComplexDeployableApplication(damage : Int, data : ResolvedProjectile)(target : Any) : ResolvedProjectile = {
target match { target match {
case ce : ComplexDeployable if ce.Health > 0 && damage > 0 => case ce : ComplexDeployable if CanDamage(ce, damage, data) =>
ce.History(data)
if(ce.Shields > 0) { if(ce.Shields > 0) {
if(damage > ce.Shields) { if(damage > ce.Shields) {
ce.Health -= (damage - ce.Shields) ce.Health -= (damage - ce.Shields)
@ -220,8 +224,7 @@ object ResolutionCalculations {
ce.Health -= damage ce.Health -= damage
} }
case ce : TurretDeployable if ce.Health > 0 && damage > 0 => case ce : TurretDeployable if CanDamage(ce, damage, data) =>
ce.History(data)
if(ce.Shields > 0) { if(ce.Shields > 0) {
if(damage > ce.Shields) { if(damage > ce.Shields) {
ce.Health -= (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 ... //after all fixed GUID's are defined ...
other.foreach(obj => guid.register(obj, "dynamic")) 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] = { 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 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 = { def AvatarEvents_=(bus : ActorRef) : ActorRef = {
avatarEvents = bus avatarEvents = bus
AvatarEvents AvatarEvents
} }
def LocalEvents : ActorRef = localEvents
def LocalEvents_=(bus : ActorRef) : ActorRef = { def LocalEvents_=(bus : ActorRef) : ActorRef = {
localEvents = bus localEvents = bus
LocalEvents LocalEvents
} }
def VehicleEvents : ActorRef = vehicleEvents
def VehicleEvents_=(bus : ActorRef) : ActorRef = { def VehicleEvents_=(bus : ActorRef) : ActorRef = {
vehicleEvents = bus vehicleEvents = bus
VehicleEvents VehicleEvents

View file

@ -89,7 +89,8 @@ class ZoneActor(zone : Zone) extends Actor {
//ams //ams
zone.Vehicles zone.Vehicles
.filter(veh => .filter(veh =>
veh.Definition == GlobalDefinitions.ams && veh.Definition == GlobalDefinitions.ams &&
!veh.Destroyed &&
veh.DeploymentState == DriveState.Deployed && veh.DeploymentState == DriveState.Deployed &&
veh.Faction == player.Faction veh.Faction == player.Faction
) )
@ -120,12 +121,12 @@ class ZoneActor(zone : Zone) extends Actor {
Set.empty[StructureType.Value] Set.empty[StructureType.Value]
} }
zone.SpawnGroups() zone.SpawnGroups()
.filter({ case (building, _) => .filter({ case (building, tubes) =>
buildingTypeSet.contains(building.BuildingType) && (building match { buildingTypeSet.contains(building.BuildingType) && (building match {
case wg : WarpGate => case wg : WarpGate =>
building.Faction == player.Faction || building.Faction == PlanetSideEmpire.NEUTRAL || wg.Broadcast building.Faction == player.Faction || building.Faction == PlanetSideEmpire.NEUTRAL || wg.Broadcast
case _ => case _ =>
building.Faction == player.Faction building.Faction == player.Faction && !tubes.forall(sp => sp.Offline)
}) })
}) })
.toSeq .toSeq
@ -144,7 +145,10 @@ class ZoneActor(zone : Zone) extends Actor {
sender ! Zone.Lattice.SpawnPoint(zone.Id, tube) sender ! Zone.Lattice.SpawnPoint(zone.Id, tube)
case Some(tubes) => 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 => case None =>
sender ! Zone.Lattice.NoValidSpawnPoint(zone_number, Some(spawn_group)) 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 : Map[Int, Int] = linkTerminalInterface
def TerminalToInterface(interface_guid : Int, terminal_guid : Int) : Unit = { def TerminalToInterface(terminal_guid : Int, interface_guid : Int) : Unit = {
linkTerminalInterface = linkTerminalInterface ++ Map(interface_guid -> terminal_guid) linkTerminalInterface = linkTerminalInterface ++ Map(terminal_guid -> interface_guid)
} }
def TurretToWeapon : Map[Int, Int] = linkTurretWeapon 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 { ZoneVehicleActor.recursiveFindVehicle(vehicleList.iterator, vehicle) match {
case Some(index) => case Some(index) =>
vehicleList.remove(index) vehicleList.remove(index)
vehicle.Actor ! akka.actor.PoisonPill context.stop(vehicle.Actor)
vehicle.Actor = ActorRef.noSender vehicle.Actor = ActorRef.noSender
case None => ; case None => ;
sender ! Zone.Vehicle.CanNotDespawn(zone, vehicle, "can not find") sender ! Zone.Vehicle.CanNotDespawn(zone, vehicle, "can not find")

View file

@ -2,25 +2,11 @@
package net.psforever.packet.game package net.psforever.packet.game
import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket} 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.{Attempt, Codec, Err}
import scodec.codecs._ import scodec.codecs._
import shapeless.{::, HNil} 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 * na
* @param unk1 na * @param unk1 na

View file

@ -7,6 +7,22 @@ import scodec.Codec
import scodec.codecs._ import scodec.codecs._
import shapeless.{::, HNil} 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, final case class DamageFeedbackMessage(unk1 : Int,
unk2 : Boolean, unk2 : Boolean,
unk2a : Option[PlanetSideGUID], unk2a : Option[PlanetSideGUID],
@ -55,12 +71,12 @@ object DamageFeedbackMessage extends Marshallable[DamageFeedbackMessage] {
bool >>:~ { u3 => bool >>:~ { u3 =>
("unk2a" | conditional(u2, PlanetSideGUID.codec)) :: ("unk2a" | conditional(u2, PlanetSideGUID.codec)) ::
(("unk2b" | conditional(!u2 && u3, PacketHelpers.encodedWideStringAligned(6))) >>:~ { u2b => (("unk2b" | conditional(!u2 && u3, PacketHelpers.encodedWideStringAligned(6))) >>:~ { u2b =>
("unk2c" | conditional(!(u2 && u3), uintL(11))) :: ("unk2c" | conditional(!u2 && !u3, uintL(11))) ::
(bool >>:~ { u5 => (bool >>:~ { u5 =>
bool >>:~ { u6 => bool >>:~ { u6 =>
("unk3a" | conditional(u5, PlanetSideGUID.codec)) :: ("unk3a" | conditional(u5, PlanetSideGUID.codec)) ::
("unk3b" | conditional(!u5 && u6, PacketHelpers.encodedWideStringAligned( if(u2b.nonEmpty) 3 else 1 ))) :: ("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)) :: ("unk3d" | conditional(!u5, uint2)) ::
("unk4" | uint(3)) :: ("unk4" | uint(3)) ::
("unk5" | uint32L) :: ("unk5" | uint32L) ::

View file

@ -9,8 +9,17 @@ import scodec.codecs._
/** /**
* na<br> * na<br>
* Global:<br> * Global:<br>
* `50 - Common Initialization?`<br> * `50 - State initialization for amenities`<br>
* `51 - Common Initialization?`<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> * `67 - ???`<br>
* <br> * <br>
* Global (GUID=0)<br> * Global (GUID=0)<br>
@ -50,15 +59,16 @@ import scodec.codecs._
* `5 - armorMax`<br> * `5 - armorMax`<br>
* `6 - PA_RELEASED - transform the (other) avatar in backpack on ground`<br> * `6 - PA_RELEASED - transform the (other) avatar in backpack on ground`<br>
* `7 - Sets charge level for MAX capacitor`<br> * `7 - Sets charge level for MAX capacitor`<br>
* `8 - Enables empire specific max capacitor function - NC Shield, TR Overdrive, VS Jumpjets` * `8 - Enables empire specific max capacitor function - NC Shield, TR Overdrive, VS Jumpjets`<br>
* `9 - Possibly unused now - PA_SHIELDSTRENGTH in beta client` * `9 - Possibly unused now - PA_SHIELDSTRENGTH in beta client`<br>
* `14 - Something with grief`<br> * `14 - Something with grief`<br>
* `15 - Weapon Lock. Value exemple : 600 to have 1 min lock. Max possible is 30min lock`<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> * `16 - PA_DECONSTRUCTING in beta client`<br>
* `17 - BEP. Value seems to be the same as BattleExperienceMessage`<br> * `17 - BEP. Value seems to be the same as BattleExperienceMessage`<br>
* `18 - CEP.`<br> * `18 - CEP.`<br>
* `19 - Anchors. Value is 0 to disengage, 1 to engage.`<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> * <ul>
* <li>65535 segments per faction in deciseconds (seconds * 10)</li> * <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> * <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>196608 - 262143 - VS - 0x 00 00 03 00</li>
* <li>17039360 - CC Resecured - 0x 00 00 04 01</li> * <li>17039360 - CC Resecured - 0x 00 00 04 01</li>
* </ul> * </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 certification:`<br>
* `24 - Learn certifications with value :`<br> * <ul>
* 01 : Medium Assault<br> * <li>01 - Medium Assault</li>
* 02 : Heavy Assault<br> * <li>02 - Heavy Assault</li>
* 03 : Special Assault<br> * <li>03 - Special Assault</li>
* 04 : Anti-Vehicular<br> * <li>04 - Anti-Vehicular</li>
* 05 : Sniping<br> * <li>05 - Sniping</li>
* 06 : Elite Assault<br> * <li>06 - Elite Assault</li>
* 07 : Air Cavalry, Scout<br> * <li>07 - Air Cavalry, Scout</li>
* 08 : Air Cavalry, Interceptor<br> * <li>08 - Air Cavalry, Interceptor</li>
* 09 : Air Cavalry, Assault<br> * <li>09 - Air Cavalry, Assault</li>
* 10 : Air Support<br> * <li>10 - Air Support</li>
* 11 : ATV<br> * <li>11 - ATV</li>
* 12 : Light Scout<br> * <li>12 - Light Scout</li>
* 13 : Assault Buggy<br> * <li>13 - Assault Buggy</li>
* 14 : Armored Assault 1<br> * <li>14 - Armored Assault 1</li>
* 15 : Armored Assault 2<br> * <li>15 - Armored Assault 2</li>
* 16 : Ground Transport<br> * <li>16 - Ground Transport</li>
* 17 : Ground Support<br> * <li>17 - Ground Support</li>
* 18 : BattleFrame Robotics<br> * <li>18 - BattleFrame Robotics</li>
* 19 : Flail<br> * <li>19 - Flail</li>
* 20 : Switchblade<br> * <li>20 - Switchblade</li>
* 21 : Harasser<br> * <li>21 - Harasser</li>
* 22 : Phantasm<br> * <li>22 - Phantasm</li>
* 23 : Galaxy Gunship<br> * <li>23 - Galaxy Gunship</li>
* 24 : BFR Anti Aircraft<br> * <li>24 - BFR Anti Aircraft</li>
* 25 : BFR Anti Infantry<br> * <li>25 - BFR Anti Infantry</li>
* 26 : ?! Removed Cert ?<br> * <li>26 - ?! Removed Cert ?</li>
* 27 : ?! Removed Cert ?<br> * <li>27 - ?! Removed Cert ?</li>
* 28 : Reinforced ExoSuitDefinition<br> * <li>28 - Reinforced ExoSuitDefinition</li>
* 29 : Infiltration Suit<br> * <li>29 - Infiltration Suit</li>
* 30 : MAX (Burster)<br> * <li>30 - AA MAX</li>
* 31 : MAX (Dual-Cycler)<br> * <li>31 - AI MAX</li>
* 32 : MAX (Pounder)<br> * <li>32 - AV MAX</li>
* 33 : Uni-MAX<br> * <li>33 - Uni-MAX</li>
* 34 : Medical<br> * <li>34 - Medical</li>
* 35 : Advanced Medical<br> * <li>35 - Advanced Medical</li>
* 36 : Hacking<br> * <li>36 - Hacking</li>
* 37 : Advanced Hacking<br> * <li>37 - Advanced Hacking</li>
* 38 : Expert Hacking<br> * <li>38 - Expert Hacking</li>
* 39 : Data Corruption<br> * <li>39 - Data Corruption</li>
* 40 : Electronics Expert (= Expert Hacking + Data Corruption) Must have Advanced Hacking<br> * <li>40 - Electronics Expert (= Expert Hacking + Data Corruption) Must have Advanced Hacking</li>
* 41 : Engineering<br> * <li>41 - Engineering</li>
* 42 : Combat Engineering<br> * <li>42 - Combat Engineering</li>
* 43 : Fortification Engineering<br> * <li>43 - Fortification Engineering</li>
* 44 : Assault Engineering<br> * <li>44 - Assault Engineering</li>
* 45 : Advanced Engineering (= Fortification Engineering + Assault Engineering) Must have Combat Engineering<br> * <li>45 - Advanced Engineering (= Fortification Engineering + Assault Engineering) Must have Combat Engineering</li>
* `25 - Forget certifications (same order as 24)`<br> * </ul>
* `25 - Forget certification: ... (see 24)`<br>
* `26 - Certification reset timer (in seconds)` * `26 - Certification reset timer (in seconds)`
* `27 - PA_JAMMED - plays jammed buzzing sound in vicinity of target, jams weapon discharge`<br> * `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> * `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> * `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> * `31 - Looking for Squad info (marquee and ui):`<br>
* ` - 0 is LFS`<br> * <ul>
* ` - 1 is LFSM (Looking for Squad Members)`<br> * <li>0 - LFS</li>
* ` - n is the supplemental squad identifier number; same as "LFS;" for the leader, sets "LFSM" after the first manual flagging`<br> * <li>1 is LFSM (Looking for Squad Members)`</li>
* `32 - Maintain the squad role index, when a member of a squad;<br> * <li>`n` is the supplemental squad identifier number; same as "LFS;" for the leader, sets "LFSM" after the first manual flagging`</li>
* - OLD: "Info under avatar name : 0 = Looking For Squad Members, 1 = LFS`"<br> * </ul>
* `35 - BR. Value is the BR`<br> * `32 - Maintain the squad role index, when a member of a squad`<br>
* `36 - CR. Value is the CR`<br> * `35 - Battle Rank`<br>
* `36 - Command Rank`<br>
* `38 - Spawn active or not. MUST use base MapId not base GUID`<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> * `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> * `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> * `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> * `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> * `53 - LFS. Value is 1 to flag LFS`<br>
* `54 - Player "Aura". Values can be expressed in the first byte's lower nibble:`<br> * `54 - Player "Aura". Values can be expressed in the first byte's lower nibble:`
* - 0 is nothing<br> * <ul>
* - 1 is plasma<br> * <li>0 - nothing</li>
* - 2 is ancient<br> * <li>1 - plasma</li>
* - 4 is LLU (?)<br> * <li>2 - ancient</li>
* - 8 is fire<br> * <li>4 - LLU (?)</li>
* -- e.g., 13 = 8 + 4 + 1 = fire and LLU and plasma<br> * <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> * `55 - "Someone is attempting to Heal you". Value is 1`<br>
* `56 - "Someone is attempting to Repair you". Value is 1`<br> * `56 - "Someone is attempting to Repair you". Value is 1`<br>
* `64 - ????? related to using router telepads` * `64 - ????? related to using router telepads`
@ -151,14 +165,25 @@ import scodec.codecs._
* `77 - Cavern Facility Captures. Value is the number of captures`<br> * `77 - Cavern Facility Captures. Value is the number of captures`<br>
* `78 - Cavern Kills. Value is the number of kills`<br> * `78 - Cavern Kills. Value is the number of kills`<br>
* `106 - Custom Head`<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> * Client to Server : <br>
* `106 - Custom Head`<br> * `106 - Custom Head`<br>
* `224 - Player/vehicle joins black ops`<br> * `224 - Player/vehicle joins black ops`<br>
* `228 - Player/vehicle leaves black ops`<br> * `228 - Player/vehicle leaves black ops`<br>
* <br> * <br>
* `Vehicles:`<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> * `11 - Gunner seat(s) permissions (same)`<br>
* `12 - Passenger seat(s) permissions (same)`<br> * `12 - Passenger seat(s) permissions (same)`<br>
* `13 - Trunk permissions (same)`<br> * `13 - Trunk permissions (same)`<br>
@ -170,7 +195,6 @@ import scodec.codecs._
* `80 - Damage vehicle (unknown value)`<br> * `80 - Damage vehicle (unknown value)`<br>
* `81 - ???`<br> * `81 - ???`<br>
* `113 - Vehicle capacitor - e.g. Leviathan EMP charge` * `113 - Vehicle capacitor - e.g. Leviathan EMP charge`
*
* @param guid the object * @param guid the object
* @param attribute_type the field * @param attribute_type the field
* @param attribute_value the value * @param attribute_value the value

Some files were not shown because too many files have changed in this diff Show more