diff --git a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala
index a1246bbf..cd70fa26 100644
--- a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala
+++ b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala
@@ -13,6 +13,7 @@ import net.psforever.objects.serverobject.mblocker.LockerDefinition
import net.psforever.objects.serverobject.pad.VehicleSpawnPadDefinition
import net.psforever.objects.serverobject.terminals._
import net.psforever.objects.serverobject.tube.SpawnTubeDefinition
+import net.psforever.objects.serverobject.resourcesilo.ResourceSiloDefinition
import net.psforever.objects.vehicles.{SeatArmorRestriction, UtilityType}
import net.psforever.types.PlanetSideEmpire
@@ -565,6 +566,8 @@ object GlobalDefinitions {
val door = new DoorDefinition
+ val resource_silo = new ResourceSiloDefinition
+
/**
* Given a faction, provide the standard assault melee weapon.
* @param faction the faction
@@ -2703,7 +2706,11 @@ object GlobalDefinitions {
ant.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax
ant.MountPoints += 1 -> 0
ant.MountPoints += 2 -> 0
+ ant.Deployment = true
+ ant.DeployTime = 1500
+ ant.UndeployTime = 1500
ant.AutoPilotSpeeds = (18, 6)
+ ant.MaximumCapacitor = 1500
ant.Packet = utilityConverter
ams.Name = "ams"
diff --git a/common/src/main/scala/net/psforever/objects/Vehicle.scala b/common/src/main/scala/net/psforever/objects/Vehicle.scala
index 229560e7..2d8c0949 100644
--- a/common/src/main/scala/net/psforever/objects/Vehicle.scala
+++ b/common/src/main/scala/net/psforever/objects/Vehicle.scala
@@ -75,6 +75,7 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideServ
private var trunkAccess : Option[PlanetSideGUID] = None
private var jammered : Boolean = false
private var cloaked : Boolean = false
+ private var capacitor : Int = 0
/**
* Permissions control who gets to access different parts of the vehicle;
@@ -175,6 +176,19 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideServ
Cloaked
}
+ def Capacitor : Int = capacitor
+
+ def Capacitor_=(value: Int) : Int = {
+ if(value > Definition.MaximumCapacitor) {
+ capacitor = Definition.MaximumCapacitor
+ } else if (value < 0) {
+ capacitor = 0
+ } else {
+ capacitor = value
+ }
+ Capacitor
+ }
+
/**
* Given the index of an entry mounting point, return the infantry-accessible `Seat` associated with it.
* @param mountPoint an index representing the seat position / mounting point
diff --git a/common/src/main/scala/net/psforever/objects/definition/VehicleDefinition.scala b/common/src/main/scala/net/psforever/objects/definition/VehicleDefinition.scala
index 68c7e14e..3628af39 100644
--- a/common/src/main/scala/net/psforever/objects/definition/VehicleDefinition.scala
+++ b/common/src/main/scala/net/psforever/objects/definition/VehicleDefinition.scala
@@ -31,6 +31,7 @@ class VehicleDefinition(objectId : Int) extends ObjectDefinition(objectId) {
private var canBeOwned : Boolean = true
private var serverVehicleOverrideSpeeds : (Int, Int) = (0, 0)
private var deconTime : Option[FiniteDuration] = None
+ private var maximumCapacitor : Int = 0
Name = "vehicle"
Packet = VehicleDefinition.converter
@@ -128,6 +129,13 @@ class VehicleDefinition(objectId : Int) extends ObjectDefinition(objectId) {
def AutoPilotSpeed1 : Int = serverVehicleOverrideSpeeds._1
def AutoPilotSpeed2 : Int = serverVehicleOverrideSpeeds._2
+
+ def MaximumCapacitor : Int = maximumCapacitor
+
+ def MaximumCapacitor_=(maxCapacitor: Int) : Int = {
+ maximumCapacitor = maxCapacitor
+ MaximumCapacitor
+ }
}
object VehicleDefinition {
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/resourcesilo/ResourceSilo.scala b/common/src/main/scala/net/psforever/objects/serverobject/resourcesilo/ResourceSilo.scala
new file mode 100644
index 00000000..d62dccb0
--- /dev/null
+++ b/common/src/main/scala/net/psforever/objects/serverobject/resourcesilo/ResourceSilo.scala
@@ -0,0 +1,85 @@
+// Copyright (c) 2017 PSForever
+package net.psforever.objects.serverobject.resourcesilo
+
+import akka.actor.{ActorContext, Props}
+import net.psforever.objects.{GlobalDefinitions, Player, Vehicle}
+import net.psforever.objects.serverobject.structures.Amenity
+import net.psforever.packet.game.UseItemMessage
+
+class ResourceSilo extends Amenity {
+ private var chargeLevel : Int = 0
+ private val maximumCharge : Int = 1000
+ // For the flashing red light on top of the NTU silo on
+ private var lowNtuWarningOn : Int = 0
+
+ // For the NTU display bar
+ private var capacitorDisplay : Long = 0
+
+ def ChargeLevel : Int = chargeLevel
+
+ // Do not call directly. Use ResourceSilo.UpdateChargeLevel message to handle logic such as low ntu warnings
+ def ChargeLevel_=(charge: Int) : Int = {
+ if(charge < 0 ) {
+ chargeLevel = 0
+ } else if (charge > maximumCharge) {
+ chargeLevel = maximumCharge
+ } else {
+ chargeLevel = charge
+ }
+ ChargeLevel
+ }
+
+ def MaximumCharge : Int = maximumCharge
+
+ def LowNtuWarningOn : Int = lowNtuWarningOn
+ def LowNtuWarningOn_=(enabled: Int) : Int = {
+ lowNtuWarningOn = enabled
+ LowNtuWarningOn
+ }
+
+ def CapacitorDisplay : Long = capacitorDisplay
+ def CapacitorDisplay_=(value: Long) : Long = {
+ capacitorDisplay = value
+ CapacitorDisplay
+ }
+
+ def Definition : ResourceSiloDefinition = GlobalDefinitions.resource_silo
+
+ def Use(player: Player, msg : UseItemMessage) : ResourceSilo.Exchange = {
+ ResourceSilo.ChargeEvent()
+ }
+}
+
+
+object ResourceSilo {
+
+ final case class Use(player: Player, msg : UseItemMessage)
+ final case class UpdateChargeLevel(amount: Int)
+ final case class LowNtuWarning(enabled: Int)
+ sealed trait Exchange
+ final case class ChargeEvent() extends Exchange
+ final case class ResourceSiloMessage(player: Player, msg : UseItemMessage, response : Exchange)
+
+
+ /**
+ * Overloaded constructor.
+ * @return the `Resource Silo` object
+ */
+ def apply() : ResourceSilo = {
+ new ResourceSilo()
+ }
+
+ /**
+ * Instantiate an configure a `Resource Silo` object
+ * @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;
+ * not necessary for this object, but required by signature
+ * @return the `Locker` object
+ */
+ def Constructor(id : Int, context : ActorContext) : ResourceSilo = {
+ val obj = ResourceSilo()
+ obj.Actor = context.actorOf(Props(classOf[ResourceSiloControl], obj), s"${obj.Definition.Name}_$id")
+ obj.Actor ! "startup"
+ obj
+ }
+}
\ No newline at end of file
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/resourcesilo/ResourceSiloControl.scala b/common/src/main/scala/net/psforever/objects/serverobject/resourcesilo/ResourceSiloControl.scala
new file mode 100644
index 00000000..ae1a5db9
--- /dev/null
+++ b/common/src/main/scala/net/psforever/objects/serverobject/resourcesilo/ResourceSiloControl.scala
@@ -0,0 +1,90 @@
+// Copyright (c) 2017 PSForever
+package net.psforever.objects.serverobject.resourcesilo
+
+import akka.actor.{Actor, ActorRef}
+import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
+import net.psforever.objects.serverobject.structures.Building
+import net.psforever.packet.game.PlanetSideGUID
+import services.ServiceManager.Lookup
+import services._
+import services.avatar.{AvatarAction, AvatarServiceMessage}
+
+import scala.concurrent.ExecutionContext.Implicits.global
+import scala.concurrent.duration._
+
+
+/**
+ * An `Actor` that handles messages being dispatched to a specific `Resource Silo`.
+ * @param resourceSilo the `Resource Silo` object being governed
+ */
+class ResourceSiloControl(resourceSilo : ResourceSilo) extends Actor with FactionAffinityBehavior.Check {
+ def FactionObject : FactionAffinity = resourceSilo
+ var avatarService : ActorRef = Actor.noSender
+ private[this] val log = org.log4s.getLogger
+
+ def receive : Receive = {
+ case "startup" =>
+ ServiceManager.serviceManager ! Lookup("avatar")
+
+ case ServiceManager.LookupResult("avatar", endpoint) =>
+ avatarService = endpoint
+ log.info("ResourceSiloControl: Silo " + resourceSilo.GUID + " Got avatar service " + endpoint)
+
+ // todo: This is just a temporary solution to drain NTU over time. When base object destruction is properly implemented NTU should be deducted when base objects repair themselves
+ context.system.scheduler.schedule(5 second, 5 second, self, ResourceSilo.UpdateChargeLevel(-1))
+ context.become(Processing)
+
+ case _ => ;
+ }
+
+ def Processing : Receive = checkBehavior.orElse {
+ case ResourceSilo.Use(player, msg) =>
+ sender ! ResourceSilo.ResourceSiloMessage(player, msg, resourceSilo.Use(player, msg))
+ case ResourceSilo.LowNtuWarning(enabled: Int) =>
+ resourceSilo.LowNtuWarningOn = enabled
+ log.trace(s"LowNtuWarning: Silo ${resourceSilo.GUID} low ntu warning set to ${enabled}")
+ avatarService ! AvatarServiceMessage(resourceSilo.Owner.asInstanceOf[Building].Zone.Id, AvatarAction.PlanetsideAttribute(PlanetSideGUID(resourceSilo.Owner.asInstanceOf[Building].ModelId), 47, resourceSilo.LowNtuWarningOn))
+
+ case ResourceSilo.UpdateChargeLevel(amount: Int) =>
+ val siloChargeBeforeChange = resourceSilo.ChargeLevel
+
+ // Increase if positive passed in or decrease charge level if negative number is passed in
+ resourceSilo.ChargeLevel += amount
+ if(resourceSilo.ChargeLevel > 0) {
+ log.trace(s"UpdateChargeLevel: Silo ${resourceSilo.GUID} set to ${resourceSilo.ChargeLevel}")
+ }
+
+
+ val ntuBarLevel = scala.math.round((resourceSilo.ChargeLevel.toFloat / resourceSilo.MaximumCharge.toFloat) * 10).toInt
+ // Only send updated capacitor display value to all clients if it has actually changed
+ if(resourceSilo.CapacitorDisplay != ntuBarLevel) {
+ log.trace(s"Silo ${resourceSilo.GUID} NTU bar level has changed from ${resourceSilo.CapacitorDisplay} to ${ntuBarLevel}")
+ resourceSilo.CapacitorDisplay = ntuBarLevel
+ resourceSilo.Owner.Actor ! Building.SendMapUpdateToAllClients()
+ avatarService ! AvatarServiceMessage(resourceSilo.Owner.asInstanceOf[Building].Zone.Id, AvatarAction.PlanetsideAttribute(resourceSilo.GUID, 45, resourceSilo.CapacitorDisplay))
+ }
+
+ val ntuIsLow = resourceSilo.ChargeLevel.toFloat / resourceSilo.MaximumCharge.toFloat < 0.2f
+ if(resourceSilo.LowNtuWarningOn == 1 && !ntuIsLow){
+ self ! ResourceSilo.LowNtuWarning(0)
+ } else if (resourceSilo.LowNtuWarningOn == 0 && ntuIsLow) {
+ self ! ResourceSilo.LowNtuWarning(1)
+ }
+
+
+ if(resourceSilo.ChargeLevel == 0 && siloChargeBeforeChange > 0) {
+ // Oops, someone let the base run out of power. Shut it all down.
+ //todo: Make base neutral if silo hits zero NTU
+
+ // temporarily disabled until warpgates can bring ANTs from sanctuary, otherwise we'd be stuck in a situation with an unpowered base and no way to get an ANT to refill it.
+// avatarService ! AvatarServiceMessage(resourceSilo.Owner.asInstanceOf[Building].Zone.Id, AvatarAction.PlanetsideAttribute(PlanetSideGUID(resourceSilo.Owner.asInstanceOf[Building].ModelId), 48, 1))
+ } else if (siloChargeBeforeChange == 0 && resourceSilo.ChargeLevel > 0) {
+ // Power restored. Reactor Online. Sensors Online. Weapons Online. All systems nominal.
+ //todo: Check generator is online before starting up
+ avatarService ! AvatarServiceMessage(resourceSilo.Owner.asInstanceOf[Building].Zone.Id, AvatarAction.PlanetsideAttribute(PlanetSideGUID(resourceSilo.Owner.asInstanceOf[Building].ModelId), 48, 0))
+ }
+ case _ => ;
+ }
+
+
+}
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/resourcesilo/ResourceSiloDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/resourcesilo/ResourceSiloDefinition.scala
new file mode 100644
index 00000000..1f557062
--- /dev/null
+++ b/common/src/main/scala/net/psforever/objects/serverobject/resourcesilo/ResourceSiloDefinition.scala
@@ -0,0 +1,12 @@
+// Copyright (c) 2017 PSForever
+package net.psforever.objects.serverobject.resourcesilo
+
+import net.psforever.objects.definition.ObjectDefinition
+
+/**
+ * The definition for any `Resource Silo`.
+ * Object Id 731.
+ */
+class ResourceSiloDefinition extends ObjectDefinition(731) {
+ Name = "resource_silo"
+}
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/structures/Building.scala b/common/src/main/scala/net/psforever/objects/serverobject/structures/Building.scala
index 844da483..416b8662 100644
--- a/common/src/main/scala/net/psforever/objects/serverobject/structures/Building.scala
+++ b/common/src/main/scala/net/psforever/objects/serverobject/structures/Building.scala
@@ -11,8 +11,8 @@ import net.psforever.types.{PlanetSideEmpire, Vector3}
class Building(private val mapId : Int, private val zone : Zone, private val buildingType : StructureType.Value) extends PlanetSideServerObject {
/**
* The mapId is the identifier number used in BuildingInfoUpdateMessage.
- * The modelId is the identifier number used in SetEmpireMessage.
- */
+ * The modelId is the identifier number used in SetEmpireMessage / Facility hacking / PlanetSideAttributeMessage.
+ */
private var modelId : Option[Int] = None
private var faction : PlanetSideEmpire.Value = PlanetSideEmpire.NEUTRAL
private var amenities : List[Amenity] = List.empty
@@ -68,6 +68,7 @@ object Building {
val obj = new Building(id, zone, buildingType)
obj.Position = location
obj.Actor = context.actorOf(Props(classOf[BuildingControl], obj), s"$id-$buildingType-building")
+ obj.Actor ! "startup"
obj
}
@@ -75,6 +76,9 @@ object Building {
import akka.actor.Props
val obj = new Building(id, zone, buildingType)
obj.Actor = context.actorOf(Props(classOf[BuildingControl], obj), s"$id-$buildingType-building")
+ obj.Actor ! "startup"
obj
}
+
+ final case class SendMapUpdateToAllClients()
}
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/structures/BuildingControl.scala b/common/src/main/scala/net/psforever/objects/serverobject/structures/BuildingControl.scala
index 2f7b7b7c..f2daedba 100644
--- a/common/src/main/scala/net/psforever/objects/serverobject/structures/BuildingControl.scala
+++ b/common/src/main/scala/net/psforever/objects/serverobject/structures/BuildingControl.scala
@@ -1,20 +1,75 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.structures
-import akka.actor.Actor
+import akka.actor.{Actor, ActorRef}
+import net.psforever.objects.GlobalDefinitions
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
+import net.psforever.objects.serverobject.resourcesilo.ResourceSilo
+import net.psforever.packet.game.{BuildingInfoUpdateMessage, PlanetSideGeneratorState}
+import net.psforever.types.PlanetSideEmpire
+import services.ServiceManager
+import services.ServiceManager.Lookup
+import services.galaxy.{GalaxyAction, GalaxyServiceMessage}
class BuildingControl(building : Building) extends Actor with FactionAffinityBehavior.Check {
def FactionObject : FactionAffinity = building
+ var galaxyService : ActorRef = Actor.noSender
+ private[this] val log = org.log4s.getLogger
- def receive : Receive = checkBehavior.orElse {
+ def receive : Receive = {
+ case "startup" =>
+ ServiceManager.serviceManager ! Lookup("galaxy") //ask for a resolver to deal with the GUID system
+
+ case ServiceManager.LookupResult("galaxy", endpoint) =>
+ galaxyService = endpoint
+ log.trace("BuildingControl: Building " + building.ModelId + " Got galaxy service " + endpoint)
+
+ // todo: This is just a temporary solution to drain NTU over time. When base object destruction is properly implemented NTU should be deducted when base objects repair themselves
+ context.become(Processing)
+
+ case _ => log.warn("Message received before startup called");
+ }
+
+ def Processing : Receive = checkBehavior.orElse {
case FactionAffinity.ConvertFactionAffinity(faction) =>
val originalAffinity = building.Faction
if(originalAffinity != (building.Faction = faction)) {
building.Amenities.foreach(_.Actor forward FactionAffinity.ConfirmFactionAffinity())
}
sender ! FactionAffinity.AssertFactionAffinity(building, faction)
-
- case _ => ;
+ case Building.SendMapUpdateToAllClients() =>
+ log.info(s"Sending BuildingInfoUpdateMessage update to all clients. Zone: ${building.Zone.Number} - Building: ${building.ModelId}")
+ var ntuLevel = 0
+ building.Amenities.filter(x => (x.Definition == GlobalDefinitions.resource_silo)).headOption.asInstanceOf[Option[ResourceSilo]] match {
+ case Some(obj: ResourceSilo) =>
+ ntuLevel = obj.CapacitorDisplay.toInt
+ case _ => ;
+ }
+ galaxyService ! GalaxyServiceMessage(GalaxyAction.MapUpdate(
+ BuildingInfoUpdateMessage(
+ continent_id = building.Zone.Number, //Zone
+ building_id = building.Id, //Facility
+ ntu_level = ntuLevel,
+ is_hacked = false, //Hacked
+ PlanetSideEmpire.NEUTRAL, //Base hacked by
+ hack_time_remaining = 0, //Time remaining for hack (ms)
+ empire_own = building.Faction, //Base owned by
+ unk1 = 0, //!! Field != 0 will cause malformed packet. See class def.
+ unk1x = None,
+ generator_state = PlanetSideGeneratorState.Normal, //Generator state
+ spawn_tubes_normal = true, //Respawn tubes operating state
+ force_dome_active = false, //Force dome state
+ lattice_benefit = 0, //Lattice benefits
+ cavern_benefit = 0, //!! Field > 0 will cause malformed packet. See class def.
+ unk4 = Nil,
+ unk5 = 0,
+ unk6 = false,
+ unk7 = 8, //!! Field != 8 will cause malformed packet. See class def.
+ unk7x = None,
+ boost_spawn_pain = false, //Boosted spawn room pain field
+ boost_generator_pain = false //Boosted generator room pain field
+ )))
+ case default =>
+ log.warn(s"BuildingControl: Unknown message ${default} received from ${sender().path}")
}
}
diff --git a/common/src/main/scala/net/psforever/objects/vehicles/Seat.scala b/common/src/main/scala/net/psforever/objects/vehicles/Seat.scala
index f69f927d..bf907740 100644
--- a/common/src/main/scala/net/psforever/objects/vehicles/Seat.scala
+++ b/common/src/main/scala/net/psforever/objects/vehicles/Seat.scala
@@ -15,7 +15,7 @@ class Seat(private val seatDef : SeatDefinition) {
/**
* Is this seat occupied?
- * @return the GUID of the player sitting in this seat, or `None` if it is left vacant
+ * @return the Player object of the player sitting in this seat, or `None` if it is left vacant
*/
def Occupant : Option[Player] = {
this.occupant
@@ -25,7 +25,7 @@ class Seat(private val seatDef : SeatDefinition) {
* The player is trying to sit down.
* Seats are exclusive positions that can only hold one occupant at a time.
* @param player the player who wants to sit, or `None` if the occupant is getting up
- * @return the GUID of the player sitting in this seat, or `None` if it is left vacant
+ * @return the Player object of the player sitting in this seat, or `None` if it is left vacant
*/
def Occupant_=(player : Player) : Option[Player] = Occupant_=(Some(player))
diff --git a/common/src/main/scala/net/psforever/packet/game/PlanetsideAttributeMessage.scala b/common/src/main/scala/net/psforever/packet/game/PlanetsideAttributeMessage.scala
index 3e78aba3..96a5320e 100644
--- a/common/src/main/scala/net/psforever/packet/game/PlanetsideAttributeMessage.scala
+++ b/common/src/main/scala/net/psforever/packet/game/PlanetsideAttributeMessage.scala
@@ -39,7 +39,7 @@ import scodec.codecs._
*
* Players/General:
* Server to client :
- * `0 - health`
+ * `0 - health (setting to zero on vehicles/terminals will destroy them)`
* `1 - healthMax`
* `2 - stamina`
* `3 - staminaMax`
@@ -104,6 +104,11 @@ import scodec.codecs._
* `35 - BR. Value is the BR`
* `36 - CR. Value is the CR`
* `43 - Info on avatar name : 0 = Nothing, 1 = "(LD)" message`
+ * `45 - NTU charge bar 0-10, 5 = 50% full. Seems to apply to both ANT and NTU Silo (possibly siphons?)`
+ * 47 - Sets base NTU level to CRITICAL. MUST use base modelId not base GUID
+ * 48 - Set to 1 to send base power loss message & turns on red warning lights throughout base. MUST use base modelId not base GUID
+ * 49 - Vehicle texture effects state? (>0 turns on ANT panel glow or ntu silo panel glow + orbs) (bit?)
+ * `52 - Vehicle particle effects? (>0 turns on orbs going towards ANT. Doesn't affect silo) (bit?)
* `53 - LFS. Value is 1 to flag LFS`
* `54 - Player "Aura". Values can be expressed in the first byte's lower nibble:`
* - 0 is nothing
@@ -114,6 +119,7 @@ import scodec.codecs._
* -- e.g., 13 = 8 + 4 + 1 = fire and LLU and plasma
* `55 - "Someone is attempting to Heal you". Value is 1`
* `56 - "Someone is attempting to Repair you". Value is 1`
+ * `67 - Enables base shields (from cavern module/lock). MUST use base modelId not GUID`
* `73 - "You are locked into the Core Beam. Charging your Module now.". Value is 1 to active`
* `77 - Cavern Facility Captures. Value is the number of captures`
* `78 - Cavern Kills. Value is the number of kills`
@@ -129,10 +135,12 @@ import scodec.codecs._
* `13 - Trunk permissions (same)`
* `21 - Declare a player the vehicle's owner, by globally unique identifier`
* `22 - Toggles gunner and passenger mount points (1 = hides, 0 = reveals; this also locks their permissions)`
- * `68 - ???`
+ * `54 - Vehicle EMP? Plays sound as if vehicle had been hit by EMP`
+ * `68 - Vehicle shield health`
* `80 - Damage vehicle (unknown value)`
* `81 - ???`
- * `113 - ???`
+ * `113 - `Vehicle capacitor - e.g. Leviathan EMP charge`
+ *
* @param player_guid the player
* @param attribute_type na
* @param attribute_value na
diff --git a/common/src/main/scala/net/psforever/packet/game/WeaponFireMessage.scala b/common/src/main/scala/net/psforever/packet/game/WeaponFireMessage.scala
index d3a87bdf..eb3dc21f 100644
--- a/common/src/main/scala/net/psforever/packet/game/WeaponFireMessage.scala
+++ b/common/src/main/scala/net/psforever/packet/game/WeaponFireMessage.scala
@@ -6,10 +6,25 @@ import net.psforever.types.Vector3
import scodec.Codec
import scodec.codecs._
-/** WeaponFireMessage seems to be sent each time a weapon actually shoots.
+/**
+ * WeaponFireMessage seems to be sent each time a weapon actually shoots.
*
- * See [[PlayerStateMessageUpstream]] for explanation of seq_time.
- */
+ *
+ * @param seq_time See [[PlayerStateMessageUpstream]] for explanation of seq_time.
+ * @param weapon_guid
+ * @param projectile_guid
+ * @param shot_origin
+ * @param unk1 Always zero from testing so far
+ * @param unk2 Seems semi-random
+ * @param unk3 Seems semi-random
+ * @param unk4 Maximum travel distance in meters - seems to be zero for decimator rockets
+ * @param unk5 Possibly always 255 from testing
+ * @param unk6 0 for bullet
+ * 1 for possibly delayed explosion (thumper alt fire) or thresher/leviathan flux cannon
+ * 2 for vs starfire (lockon type?)
+ * 3 for thrown (e.g. grenades)
+ * @param unk7 Seems to be thrown weapon velocity/direction
+*/
final case class WeaponFireMessage(seq_time : Int,
weapon_guid : PlanetSideGUID,
projectile_guid : PlanetSideGUID,
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/VehicleData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/VehicleData.scala
index 9cfa09d8..773e9bb1 100644
--- a/common/src/main/scala/net/psforever/packet/game/objectcreate/VehicleData.scala
+++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/VehicleData.scala
@@ -57,7 +57,7 @@ final case class VariantVehicleData(unk : Int) extends SpecificVehicleData {
* activates the green camo and adjusts permissions
* @param destroyed this vehicle has ben destroyed;
* it's health should be less than 3/255, or 0%
- * @param unk1 na
+ * @param unk1 na. Valid values seem to be 0-3. Anything higher spawns a completely broken NC vehicle with no guns that can't move
* @param jammered this vehicle is under the influence of a jammer grenade
* @param unk2 na
* @param owner_guid the vehicle's (official) owner;
diff --git a/pslogin/src/main/scala/services/RemoverActor.scala b/common/src/main/scala/services/RemoverActor.scala
similarity index 100%
rename from pslogin/src/main/scala/services/RemoverActor.scala
rename to common/src/main/scala/services/RemoverActor.scala
diff --git a/pslogin/src/main/scala/services/Service.scala b/common/src/main/scala/services/Service.scala
similarity index 100%
rename from pslogin/src/main/scala/services/Service.scala
rename to common/src/main/scala/services/Service.scala
diff --git a/pslogin/src/main/scala/services/ServiceManager.scala b/common/src/main/scala/services/ServiceManager.scala
similarity index 100%
rename from pslogin/src/main/scala/services/ServiceManager.scala
rename to common/src/main/scala/services/ServiceManager.scala
diff --git a/pslogin/src/main/scala/services/avatar/AvatarAction.scala b/common/src/main/scala/services/avatar/AvatarAction.scala
similarity index 100%
rename from pslogin/src/main/scala/services/avatar/AvatarAction.scala
rename to common/src/main/scala/services/avatar/AvatarAction.scala
diff --git a/pslogin/src/main/scala/services/avatar/AvatarResponse.scala b/common/src/main/scala/services/avatar/AvatarResponse.scala
similarity index 100%
rename from pslogin/src/main/scala/services/avatar/AvatarResponse.scala
rename to common/src/main/scala/services/avatar/AvatarResponse.scala
diff --git a/pslogin/src/main/scala/services/avatar/AvatarService.scala b/common/src/main/scala/services/avatar/AvatarService.scala
similarity index 100%
rename from pslogin/src/main/scala/services/avatar/AvatarService.scala
rename to common/src/main/scala/services/avatar/AvatarService.scala
diff --git a/pslogin/src/main/scala/services/avatar/AvatarServiceMessage.scala b/common/src/main/scala/services/avatar/AvatarServiceMessage.scala
similarity index 100%
rename from pslogin/src/main/scala/services/avatar/AvatarServiceMessage.scala
rename to common/src/main/scala/services/avatar/AvatarServiceMessage.scala
diff --git a/pslogin/src/main/scala/services/avatar/AvatarServiceResponse.scala b/common/src/main/scala/services/avatar/AvatarServiceResponse.scala
similarity index 100%
rename from pslogin/src/main/scala/services/avatar/AvatarServiceResponse.scala
rename to common/src/main/scala/services/avatar/AvatarServiceResponse.scala
diff --git a/pslogin/src/main/scala/services/avatar/support/CorpseRemovalActor.scala b/common/src/main/scala/services/avatar/support/CorpseRemovalActor.scala
similarity index 100%
rename from pslogin/src/main/scala/services/avatar/support/CorpseRemovalActor.scala
rename to common/src/main/scala/services/avatar/support/CorpseRemovalActor.scala
diff --git a/pslogin/src/main/scala/services/avatar/support/DroppedItemRemover.scala b/common/src/main/scala/services/avatar/support/DroppedItemRemover.scala
similarity index 100%
rename from pslogin/src/main/scala/services/avatar/support/DroppedItemRemover.scala
rename to common/src/main/scala/services/avatar/support/DroppedItemRemover.scala
index 01bc9d47..ce91fdf8 100644
--- a/pslogin/src/main/scala/services/avatar/support/DroppedItemRemover.scala
+++ b/common/src/main/scala/services/avatar/support/DroppedItemRemover.scala
@@ -3,8 +3,8 @@ package services.avatar.support
import net.psforever.objects.equipment.Equipment
import net.psforever.objects.guid.{GUIDTask, TaskResolver}
-import services.avatar.{AvatarAction, AvatarServiceMessage}
import services.{RemoverActor, Service}
+import services.avatar.{AvatarAction, AvatarServiceMessage}
import scala.concurrent.duration._
diff --git a/common/src/main/scala/services/galaxy/GalaxyAction.scala b/common/src/main/scala/services/galaxy/GalaxyAction.scala
new file mode 100644
index 00000000..7bb9c794
--- /dev/null
+++ b/common/src/main/scala/services/galaxy/GalaxyAction.scala
@@ -0,0 +1,10 @@
+// Copyright (c) 2017 PSForever
+package services.galaxy
+
+import net.psforever.packet.game.{BuildingInfoUpdateMessage}
+
+object GalaxyAction {
+ trait Action
+
+ final case class MapUpdate(msg: BuildingInfoUpdateMessage) extends Action
+}
diff --git a/common/src/main/scala/services/galaxy/GalaxyResponse.scala b/common/src/main/scala/services/galaxy/GalaxyResponse.scala
new file mode 100644
index 00000000..8026085f
--- /dev/null
+++ b/common/src/main/scala/services/galaxy/GalaxyResponse.scala
@@ -0,0 +1,10 @@
+// Copyright (c) 2017 PSForever
+package services.galaxy
+
+import net.psforever.packet.game.{BuildingInfoUpdateMessage}
+
+object GalaxyResponse {
+ trait Response
+
+ final case class MapUpdate(msg: BuildingInfoUpdateMessage) extends Response
+}
diff --git a/common/src/main/scala/services/galaxy/GalaxyService.scala b/common/src/main/scala/services/galaxy/GalaxyService.scala
new file mode 100644
index 00000000..262d64b4
--- /dev/null
+++ b/common/src/main/scala/services/galaxy/GalaxyService.scala
@@ -0,0 +1,49 @@
+// Copyright (c) 2017 PSForever
+package services.galaxy
+
+import akka.actor.{Actor, Props}
+import net.psforever.packet.game.BuildingInfoUpdateMessage
+import services.local.support.{DoorCloseActor, HackClearActor}
+import services.{GenericEventBus, Service}
+
+class GalaxyService extends Actor {
+ private [this] val log = org.log4s.getLogger
+
+ override def preStart = {
+ log.info("Starting...")
+ }
+
+ val GalaxyEvents = new GenericEventBus[GalaxyServiceResponse]
+
+ def receive = {
+ // Service.Join requires a channel to be passed in normally but GalaxyService is an exception in that messages go to ALL connected players
+ case Service.Join(_) =>
+ val path = s"/Galaxy"
+ val who = sender()
+ log.info(s"$who has joined $path")
+ GalaxyEvents.subscribe(who, path)
+
+ case Service.Leave(None) =>
+ GalaxyEvents.unsubscribe(sender())
+
+ case Service.Leave(_) =>
+ val path = s"/Galaxy"
+ val who = sender()
+ log.info(s"$who has left $path")
+ GalaxyEvents.unsubscribe(who, path)
+
+ case Service.LeaveAll() =>
+ GalaxyEvents.unsubscribe(sender())
+
+ case GalaxyServiceMessage(action) =>
+ action match {
+ case GalaxyAction.MapUpdate(msg: BuildingInfoUpdateMessage) =>
+ GalaxyEvents.publish(
+ GalaxyServiceResponse(s"/Galaxy", GalaxyResponse.MapUpdate(msg))
+ )
+ case _ => ;
+ }
+ case msg =>
+ log.info(s"Unhandled message $msg from $sender")
+ }
+}
diff --git a/common/src/main/scala/services/galaxy/GalaxyServiceMessage.scala b/common/src/main/scala/services/galaxy/GalaxyServiceMessage.scala
new file mode 100644
index 00000000..a013af5e
--- /dev/null
+++ b/common/src/main/scala/services/galaxy/GalaxyServiceMessage.scala
@@ -0,0 +1,4 @@
+// Copyright (c) 2017 PSForever
+package services.galaxy
+
+final case class GalaxyServiceMessage(actionMessage : GalaxyAction.Action)
diff --git a/common/src/main/scala/services/galaxy/GalaxyServiceResponse.scala b/common/src/main/scala/services/galaxy/GalaxyServiceResponse.scala
new file mode 100644
index 00000000..28125a89
--- /dev/null
+++ b/common/src/main/scala/services/galaxy/GalaxyServiceResponse.scala
@@ -0,0 +1,9 @@
+// Copyright (c) 2017 PSForever
+package services.galaxy
+
+import net.psforever.packet.game.PlanetSideGUID
+import services.GenericEventBusMsg
+
+final case class GalaxyServiceResponse(toChannel : String,
+ replyMessage : GalaxyResponse.Response
+ ) extends GenericEventBusMsg
diff --git a/pslogin/src/main/scala/services/local/LocalAction.scala b/common/src/main/scala/services/local/LocalAction.scala
similarity index 100%
rename from pslogin/src/main/scala/services/local/LocalAction.scala
rename to common/src/main/scala/services/local/LocalAction.scala
diff --git a/pslogin/src/main/scala/services/local/LocalResponse.scala b/common/src/main/scala/services/local/LocalResponse.scala
similarity index 100%
rename from pslogin/src/main/scala/services/local/LocalResponse.scala
rename to common/src/main/scala/services/local/LocalResponse.scala
diff --git a/pslogin/src/main/scala/services/local/LocalService.scala b/common/src/main/scala/services/local/LocalService.scala
similarity index 100%
rename from pslogin/src/main/scala/services/local/LocalService.scala
rename to common/src/main/scala/services/local/LocalService.scala
index 5c01ee1a..2d2570ce 100644
--- a/pslogin/src/main/scala/services/local/LocalService.scala
+++ b/common/src/main/scala/services/local/LocalService.scala
@@ -2,8 +2,8 @@
package services.local
import akka.actor.{Actor, Props}
-import services.local.support.{DoorCloseActor, HackClearActor}
import services.{GenericEventBus, Service}
+import services.local.support.{DoorCloseActor, HackClearActor}
class LocalService extends Actor {
private val doorCloser = context.actorOf(Props[DoorCloseActor], "local-door-closer")
diff --git a/pslogin/src/main/scala/services/local/LocalServiceMessage.scala b/common/src/main/scala/services/local/LocalServiceMessage.scala
similarity index 100%
rename from pslogin/src/main/scala/services/local/LocalServiceMessage.scala
rename to common/src/main/scala/services/local/LocalServiceMessage.scala
diff --git a/pslogin/src/main/scala/services/local/LocalServiceResponse.scala b/common/src/main/scala/services/local/LocalServiceResponse.scala
similarity index 100%
rename from pslogin/src/main/scala/services/local/LocalServiceResponse.scala
rename to common/src/main/scala/services/local/LocalServiceResponse.scala
diff --git a/pslogin/src/main/scala/services/local/support/DoorCloseActor.scala b/common/src/main/scala/services/local/support/DoorCloseActor.scala
similarity index 100%
rename from pslogin/src/main/scala/services/local/support/DoorCloseActor.scala
rename to common/src/main/scala/services/local/support/DoorCloseActor.scala
diff --git a/pslogin/src/main/scala/services/local/support/HackClearActor.scala b/common/src/main/scala/services/local/support/HackClearActor.scala
similarity index 100%
rename from pslogin/src/main/scala/services/local/support/HackClearActor.scala
rename to common/src/main/scala/services/local/support/HackClearActor.scala
diff --git a/pslogin/src/main/scala/services/vehicle/VehicleAction.scala b/common/src/main/scala/services/vehicle/VehicleAction.scala
similarity index 100%
rename from pslogin/src/main/scala/services/vehicle/VehicleAction.scala
rename to common/src/main/scala/services/vehicle/VehicleAction.scala
diff --git a/pslogin/src/main/scala/services/vehicle/VehicleResponse.scala b/common/src/main/scala/services/vehicle/VehicleResponse.scala
similarity index 100%
rename from pslogin/src/main/scala/services/vehicle/VehicleResponse.scala
rename to common/src/main/scala/services/vehicle/VehicleResponse.scala
diff --git a/pslogin/src/main/scala/services/vehicle/VehicleService.scala b/common/src/main/scala/services/vehicle/VehicleService.scala
similarity index 100%
rename from pslogin/src/main/scala/services/vehicle/VehicleService.scala
rename to common/src/main/scala/services/vehicle/VehicleService.scala
diff --git a/pslogin/src/main/scala/services/vehicle/VehicleServiceMessage.scala b/common/src/main/scala/services/vehicle/VehicleServiceMessage.scala
similarity index 100%
rename from pslogin/src/main/scala/services/vehicle/VehicleServiceMessage.scala
rename to common/src/main/scala/services/vehicle/VehicleServiceMessage.scala
diff --git a/pslogin/src/main/scala/services/vehicle/VehicleServiceResponse.scala b/common/src/main/scala/services/vehicle/VehicleServiceResponse.scala
similarity index 100%
rename from pslogin/src/main/scala/services/vehicle/VehicleServiceResponse.scala
rename to common/src/main/scala/services/vehicle/VehicleServiceResponse.scala
diff --git a/pslogin/src/main/scala/services/vehicle/support/VehicleRemover.scala b/common/src/main/scala/services/vehicle/support/VehicleRemover.scala
similarity index 100%
rename from pslogin/src/main/scala/services/vehicle/support/VehicleRemover.scala
rename to common/src/main/scala/services/vehicle/support/VehicleRemover.scala
index cec2e65e..b60c9356 100644
--- a/pslogin/src/main/scala/services/vehicle/support/VehicleRemover.scala
+++ b/common/src/main/scala/services/vehicle/support/VehicleRemover.scala
@@ -4,8 +4,8 @@ package services.vehicle.support
import net.psforever.objects.Vehicle
import net.psforever.objects.guid.{GUIDTask, TaskResolver}
import net.psforever.objects.zones.Zone
-import services.vehicle.{VehicleAction, VehicleServiceMessage}
import services.{RemoverActor, Service}
+import services.vehicle.{VehicleAction, VehicleServiceMessage}
import scala.concurrent.duration._
diff --git a/pslogin/src/main/scala/Maps.scala b/pslogin/src/main/scala/Maps.scala
index 0a710e80..911979b8 100644
--- a/pslogin/src/main/scala/Maps.scala
+++ b/pslogin/src/main/scala/Maps.scala
@@ -9,6 +9,7 @@ import net.psforever.objects.serverobject.pad.VehicleSpawnPad
import net.psforever.objects.serverobject.structures.{Building, FoundationBuilder, StructureType, WarpGate}
import net.psforever.objects.serverobject.terminals.{ProximityTerminal, Terminal}
import net.psforever.objects.serverobject.tube.SpawnTube
+import net.psforever.objects.serverobject.resourcesilo.ResourceSilo
import net.psforever.types.Vector3
object Maps {
@@ -125,6 +126,7 @@ object Maps {
LocalObject(2050, Terminal.Constructor(repair_silo)) //rearm terminal A
LocalObject(2061, ProximityTerminal.Constructor(repair_silo)) //repair terminal B
LocalObject(2062, Terminal.Constructor(repair_silo)) //rearm terminal B
+ LocalObject(2094, ResourceSilo.Constructor) // NTU Silo
LocalObject(2239, Terminal.Constructor(spawn_terminal))
LocalObject(2244, Terminal.Constructor(spawn_terminal))
LocalObject(2245, Terminal.Constructor(spawn_terminal))
@@ -238,6 +240,7 @@ object Maps {
ObjectToBuilding(2050, 2)
ObjectToBuilding(2061, 2)
ObjectToBuilding(2062, 2)
+ ObjectToBuilding(2094, 2)
ObjectToBuilding(2145, 2)
ObjectToBuilding(2146, 2)
ObjectToBuilding(2147, 2)
diff --git a/pslogin/src/main/scala/PsLogin.scala b/pslogin/src/main/scala/PsLogin.scala
index ebbbfdfb..869ff6cb 100644
--- a/pslogin/src/main/scala/PsLogin.scala
+++ b/pslogin/src/main/scala/PsLogin.scala
@@ -19,6 +19,7 @@ import org.fusesource.jansi.Ansi._
import org.fusesource.jansi.Ansi.Color._
import services.ServiceManager
import services.avatar._
+import services.galaxy.GalaxyService
import services.local._
import services.vehicle.VehicleService
@@ -209,7 +210,8 @@ object PsLogin {
serviceManager ! ServiceManager.Register(Props[AvatarService], "avatar")
serviceManager ! ServiceManager.Register(Props[LocalService], "local")
serviceManager ! ServiceManager.Register(Props[VehicleService], "vehicle")
- serviceManager ! ServiceManager.Register(Props(classOf[InterstellarCluster], continentList), "galaxy")
+ serviceManager ! ServiceManager.Register(Props[GalaxyService], "galaxy")
+ serviceManager ! ServiceManager.Register(Props(classOf[InterstellarCluster], continentList), "cluster")
//attach event bus entry point to each zone
import akka.pattern.ask
diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala
index 714ae193..da0645bf 100644
--- a/pslogin/src/main/scala/WorldSessionActor.scala
+++ b/pslogin/src/main/scala/WorldSessionActor.scala
@@ -30,6 +30,7 @@ import net.psforever.objects.serverobject.locks.IFFLock
import net.psforever.objects.serverobject.mblocker.Locker
import net.psforever.objects.serverobject.pad.{VehicleSpawnControl, VehicleSpawnPad}
import net.psforever.objects.serverobject.pad.process.{AutoDriveControls, VehicleSpawnControlGuided}
+import net.psforever.objects.serverobject.resourcesilo.ResourceSilo
import net.psforever.objects.serverobject.structures.{Building, StructureType, WarpGate}
import net.psforever.objects.serverobject.terminals._
import net.psforever.objects.serverobject.terminals.Terminal.TerminalMessage
@@ -40,10 +41,13 @@ import net.psforever.packet.game.objectcreate._
import net.psforever.types._
import services.{RemoverActor, _}
import services.avatar.{AvatarAction, AvatarResponse, AvatarServiceMessage, AvatarServiceResponse}
+import services.galaxy.{GalaxyResponse, GalaxyServiceResponse}
import services.local.{LocalAction, LocalResponse, LocalServiceMessage, LocalServiceResponse}
import services.vehicle.VehicleAction.UnstowEquipment
import services.vehicle.{VehicleAction, VehicleResponse, VehicleServiceMessage, VehicleServiceResponse}
+import scala.concurrent.duration._
+import scala.concurrent.ExecutionContext.Implicits.global
import scala.annotation.tailrec
import scala.concurrent.Future
import scala.concurrent.duration._
@@ -60,8 +64,9 @@ class WorldSessionActor extends Actor with MDCContextAware {
var avatarService : ActorRef = ActorRef.noSender
var localService : ActorRef = ActorRef.noSender
var vehicleService : ActorRef = ActorRef.noSender
+ var galaxyService : ActorRef = ActorRef.noSender
var taskResolver : ActorRef = Actor.noSender
- var galaxy : ActorRef = Actor.noSender
+ var cluster : ActorRef = Actor.noSender
var continent : Zone = Zone.Nowhere
var player : Player = null
var avatar : Avatar = null
@@ -86,6 +91,8 @@ class WorldSessionActor extends Actor with MDCContextAware {
var progressBarUpdate : Cancellable = DefaultCancellable.obj
var reviveTimer : Cancellable = DefaultCancellable.obj
var respawnTimer : Cancellable = DefaultCancellable.obj
+ var antChargingTick : Cancellable = DefaultCancellable.obj
+ var antDischargingTick : Cancellable = DefaultCancellable.obj
/**
* Convert a boolean value into an integer value.
@@ -103,6 +110,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
localService ! Service.Leave()
vehicleService ! Service.Leave()
avatarService ! Service.Leave()
+ galaxyService ! Service.Leave()
LivePlayerList.Remove(sessionId)
if(player != null && player.HasGUID) {
@@ -205,6 +213,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
ServiceManager.serviceManager ! Lookup("local")
ServiceManager.serviceManager ! Lookup("vehicle")
ServiceManager.serviceManager ! Lookup("taskResolver")
+ ServiceManager.serviceManager ! Lookup("cluster")
ServiceManager.serviceManager ! Lookup("galaxy")
case _ =>
@@ -226,8 +235,11 @@ class WorldSessionActor extends Actor with MDCContextAware {
taskResolver = endpoint
log.info("ID: " + sessionId + " Got task resolver service " + endpoint)
case ServiceManager.LookupResult("galaxy", endpoint) =>
- galaxy = endpoint
+ galaxyService = endpoint
log.info("ID: " + sessionId + " Got galaxy service " + endpoint)
+ case ServiceManager.LookupResult("cluster", endpoint) =>
+ cluster = endpoint
+ log.info("ID: " + sessionId + " Got cluster service " + endpoint)
case ControlPacket(_, ctrl) =>
handleControlPkt(ctrl)
@@ -382,6 +394,12 @@ class WorldSessionActor extends Actor with MDCContextAware {
case _ => ;
}
+ case GalaxyServiceResponse(_, reply) =>
+ reply match {
+ case GalaxyResponse.MapUpdate(msg) =>
+ sendResponse(msg)
+ }
+
case LocalServiceResponse(_, guid, reply) =>
val tplayer_guid = if(player.HasGUID) { player.GUID} else { PlanetSideGUID(-1) }
reply match {
@@ -610,6 +628,17 @@ class WorldSessionActor extends Actor with MDCContextAware {
case Deployment.CanNotChangeDeployment(obj, state, reason) =>
CanNotChangeDeployment(obj, state, reason)
+ case ResourceSilo.ResourceSiloMessage(tplayer, msg, order) =>
+ val vehicle_guid = msg.avatar_guid
+ val silo_guid = msg.object_guid
+ order match {
+ case ResourceSilo.ChargeEvent() =>
+ antChargingTick.cancel() // If an ANT is refilling a NTU silo it isn't in a warpgate, so disable NTU regeneration
+ antDischargingTick.cancel()
+
+ antDischargingTick = context.system.scheduler.scheduleOnce(1000 milliseconds, self, NtuDischarging(player, continent.GUID(vehicle_guid).get.asInstanceOf[Vehicle], silo_guid))
+ }
+
case Door.DoorMessage(tplayer, msg, order) =>
val door_guid = msg.object_guid
order match {
@@ -1152,8 +1181,8 @@ class WorldSessionActor extends Actor with MDCContextAware {
val vehicle_guid = vehicle.GUID
sendResponse(PlanetsideAttributeMessage(vehicle_guid, 22, 0L)) //mount points on
//sendResponse(PlanetsideAttributeMessage(vehicle_guid, 0, 10))//vehicle.Definition.MaxHealth))
- sendResponse(PlanetsideAttributeMessage(vehicle_guid, 68, 0L)) //???
- sendResponse(PlanetsideAttributeMessage(vehicle_guid, 113, 0L)) //???
+ sendResponse(PlanetsideAttributeMessage(vehicle_guid, 68, 0L)) // Shield health
+ sendResponse(PlanetsideAttributeMessage(vehicle_guid, 113, 0L)) // Capacitor (EMP)
ReloadVehicleAccessPermissions(vehicle)
ServerVehicleLock(vehicle)
@@ -1353,7 +1382,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
reviveTimer.cancel
if(spawn_group == 2) {
sendResponse(ChatMsg(ChatMessageType.CMT_OPEN, false, "", "No friendly AMS is deployed in this region.", None))
- galaxy ! Zone.Lattice.RequestSpawnPoint(zone_number, player, 0)
+ cluster ! Zone.Lattice.RequestSpawnPoint(zone_number, player, 0)
}
else {
RequestSanctuaryZoneSpawn(player, zone_number)
@@ -1413,7 +1442,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
sendResponse(ReplicationStreamMessage(5, Some(6), Vector(SquadListing()))) //clear squad list
sendResponse(FriendsResponse(FriendAction.InitializeFriendList, 0, true, true, Nil))
sendResponse(FriendsResponse(FriendAction.InitializeIgnoreList, 0, true, true, Nil))
- galaxy ! InterstellarCluster.GetWorld("z6")
+ cluster ! InterstellarCluster.GetWorld("z6")
case InterstellarCluster.GiveWorld(zoneId, zone) =>
log.info(s"Zone $zoneId will now load")
@@ -1506,6 +1535,82 @@ class WorldSessionActor extends Actor with MDCContextAware {
//TacticsMessage?
StopBundlingPackets()
+ case NtuCharging(tplayer, vehicle) =>
+ log.trace(s"NtuCharging: Vehicle ${vehicle.GUID} is charging NTU capacitor.")
+
+ if(vehicle.Capacitor < vehicle.Definition.MaximumCapacitor) {
+ // Charging
+ vehicle.Capacitor += 100
+
+ sendResponse(PlanetsideAttributeMessage(vehicle.GUID, 45, scala.math.round((vehicle.Capacitor.toFloat / vehicle.Definition.MaximumCapacitor.toFloat) * 10) )) // set ntu on vehicle UI
+ avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(vehicle.GUID, 52, 1L)) // panel glow on
+ avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(vehicle.GUID, 49, 1L)) // orb particle effect on
+
+ antChargingTick = context.system.scheduler.scheduleOnce(1000 milliseconds, self, NtuCharging(player, vehicle)) // Repeat until fully charged
+ } else {
+ // Fully charged
+ sendResponse(PlanetsideAttributeMessage(vehicle.GUID, 45, scala.math.round((vehicle.Capacitor.toFloat / vehicle.Definition.MaximumCapacitor.toFloat) * 10).toInt)) // set ntu on vehicle UI
+
+ // Turning off glow/orb effects on ANT doesn't seem to work when deployed. Try to undeploy ANT from server side
+ context.system.scheduler.scheduleOnce(vehicle.UndeployTime milliseconds, vehicle.Actor, Deployment.TryUndeploy(DriveState.Undeploying))
+ }
+
+ case NtuDischarging(tplayer, vehicle, silo_guid) =>
+ log.trace(s"NtuDischarging: Vehicle ${vehicle.GUID} is discharging NTU into silo $silo_guid")
+ avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(vehicle.GUID, 49, 0L)) // orb particle effect off
+ avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(vehicle.GUID, 52, 1L)) // panel glow on
+
+ var silo = continent.GUID(silo_guid).get.asInstanceOf[ResourceSilo]
+
+ // Check vehicle is still deployed before continuing. User can undeploy manually or vehicle may not longer be present.
+ if(vehicle.DeploymentState == DriveState.Deployed) {
+ if(vehicle.Capacitor > 0 && silo.ChargeLevel < silo.MaximumCharge) {
+
+ // Make sure we don't exceed the silo maximum charge or remove much NTU from ANT if maximum is reached, or try to make ANT go below 0 NTU
+ var chargeToDeposit = Math.min(Math.min(vehicle.Capacitor, 100), (silo.MaximumCharge - silo.ChargeLevel))
+ vehicle.Capacitor -= chargeToDeposit
+ silo.Actor ! ResourceSilo.UpdateChargeLevel(chargeToDeposit)
+
+ avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(silo_guid, 49, 1L)) // panel glow on & orb particles on
+ sendResponse(PlanetsideAttributeMessage(vehicle.GUID, 45, scala.math.round((vehicle.Capacitor.toFloat / vehicle.Definition.MaximumCapacitor.toFloat) * 10))) // set ntu on vehicle UI
+
+ //todo: grant BEP to user
+ //todo: grant BEP to squad in range
+ //todo: notify map service to update ntu % on map for all users
+
+ //todo: handle silo orb / panel glow properly if more than one person is refilling silo and one player stops. effects should stay on until all players stop
+
+ if(vehicle.Capacitor > 0 && silo.ChargeLevel < silo.MaximumCharge) {
+ log.trace(s"NtuDischarging: ANT not empty and Silo not full. Scheduling another discharge")
+ // Silo still not full and ant still has charge left - keep rescheduling ticks
+ antDischargingTick = context.system.scheduler.scheduleOnce(1000 milliseconds, self, NtuDischarging(player, vehicle, silo_guid))
+ } else {
+ log.trace(s"NtuDischarging: ANT NTU empty or Silo NTU full.")
+
+ // Turning off glow/orb effects on ANT doesn't seem to work when deployed. Try to undeploy ANT from server side
+ context.system.scheduler.scheduleOnce(vehicle.UndeployTime milliseconds, vehicle.Actor, Deployment.TryUndeploy(DriveState.Undeploying))
+
+ avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(silo_guid, 49, 0L)) // panel glow off & orb particles off
+ antDischargingTick.cancel()
+ }
+ } else {
+ // This shouldn't normally be run, only if the client thinks the ANT has capacitor charge when it doesn't, or thinks the silo isn't full when it is.
+ log.warn(s"NtuDischarging: Invalid discharge state. ANT Capacitor: ${vehicle.Capacitor} Silo Capacitor: ${silo.ChargeLevel}")
+
+ // Turning off glow/orb effects on ANT doesn't seem to work when deployed. Try to undeploy ANT from server side
+ context.system.scheduler.scheduleOnce(vehicle.UndeployTime milliseconds, vehicle.Actor, Deployment.TryUndeploy(DriveState.Undeploying))
+
+ avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(silo_guid, 49, 0L)) // panel glow off & orb particles off
+ antDischargingTick.cancel()
+ }
+ } else {
+ log.trace(s"NtuDischarging: Vehicle is no longer deployed. Removing effects")
+ // Vehicle has changed from deployed and this should be the last timer tick sent
+ avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(vehicle.GUID, 52, 0L)) // panel glow off
+ avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(silo_guid, 49, 0L)) // panel glow off & orb particles off
+ antDischargingTick.cancel()
+ }
+
case ItemHacking(tplayer, target, tool_guid, delta, completeAction, tickAction) =>
progressBarUpdate.cancel
if(progressBarValue.isDefined) {
@@ -1634,7 +1739,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
//TODO check if can spawn on last continent/location from player?
//TODO if yes, get continent guid accessors
//TODO if no, get sanctuary guid accessors and reset the player's expectations
- galaxy ! InterstellarCluster.RequestClientInitialization()
+ cluster ! InterstellarCluster.RequestClientInitialization()
case default =>
log.error("Unsupported " + default + " in " + msg)
}
@@ -1649,6 +1754,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
avatarService ! Service.Join(continent.Id)
localService ! Service.Join(continent.Id)
vehicleService ! Service.Join(continent.Id)
+ galaxyService ! Service.Join("galaxy")
configZone(continent)
sendResponse(TimeOfDayMessage(1191182336))
//custom
@@ -1858,7 +1964,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
case msg @ SpawnRequestMessage(u1, u2, u3, u4, u5) =>
log.info(s"SpawnRequestMessage: $msg")
//TODO just focus on u5 and u2 for now
- galaxy ! Zone.Lattice.RequestSpawnPoint(u5.toInt, player, u2.toInt)
+ cluster ! Zone.Lattice.RequestSpawnPoint(u5.toInt, player, u2.toInt)
case msg @ SetChatFilterMessage(send_channel, origin, whitelist) =>
//log.info("SetChatFilters: " + msg)
@@ -1961,7 +2067,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
else if(trimContents.equals("!ams")) {
makeReply = false
if(player.isBackpack) { //player is on deployment screen (either dead or deconstructed)
- galaxy ! Zone.Lattice.RequestSpawnPoint(continent.Number, player, 2)
+ cluster ! Zone.Lattice.RequestSpawnPoint(continent.Number, player, 2)
}
}
// TODO: Depending on messagetype, may need to prepend sender's name to contents with proper spacing
@@ -2484,6 +2590,20 @@ class WorldSessionActor extends Actor with MDCContextAware {
sendResponse(GenericObjectStateMsg(object_guid, 16))
}
+ case Some(resourceSilo : ResourceSilo) =>
+ log.info(s"UseItem: Vehicle $avatar_guid is refilling resource silo $object_guid")
+ val vehicle = continent.GUID(avatar_guid).get.asInstanceOf[Vehicle]
+
+ if(resourceSilo.Faction == PlanetSideEmpire.NEUTRAL || player.Faction == resourceSilo.Faction) {
+ if(vehicle.Seat(0).get.Occupant.contains(player)) {
+ log.trace("UseItem: Player matches vehicle driver. Calling ResourceSilo.Use")
+ resourceSilo.Actor ! ResourceSilo.Use(player, msg)
+ }
+ } else {
+ log.warn(s"Player ${player.GUID} - ${player.Faction} tried to refill silo ${resourceSilo.GUID} - ${resourceSilo.Faction} belonging to another empire")
+ }
+
+
case Some(panel : IFFLock) =>
if(panel.Faction != player.Faction && panel.HackedBy.isEmpty) {
player.Slot(player.DrawnSlot).Equipment match {
@@ -3400,7 +3520,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
def TaskBeforeZoneChange(priorTask : TaskResolver.GiveTask, zoneId : String) : TaskResolver.GiveTask = {
TaskResolver.GiveTask(
new Task() {
- private val localService = galaxy
+ private val localService = cluster
private val localMsg = InterstellarCluster.GetWorld(zoneId)
override def isComplete : Task.Resolution.Value = priorTask.task.isComplete
@@ -3566,7 +3686,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
/**
* Gives a target player positive battle experience points only.
* If the player has access to more implant slots as a result of changing battle experience points, unlock those slots.
- * @param tplayer the player
+ * @param avatar the player
* @param bep the change in experience points, positive by assertion
* @return the player's current battle experience points
*/
@@ -4207,7 +4327,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
obj match {
case vehicle : Vehicle =>
ReloadVehicleAccessPermissions(vehicle) //TODO we should not have to do this imho
- //
+
if(obj.Definition == GlobalDefinitions.ams) {
obj.DeploymentState match {
case DriveState.Deployed =>
@@ -4220,6 +4340,27 @@ class WorldSessionActor extends Actor with MDCContextAware {
case _ => ;
}
}
+ if(obj.Definition == GlobalDefinitions.ant) {
+ obj.DeploymentState match {
+ case DriveState.Deployed =>
+ // We only want this WSA (not other player's WSA) to manage timers
+ if(vehicle.Seat(0).get.Occupant.contains(player)){
+ // Start ntu regeneration
+ // If vehicle sends UseItemMessage with silo as target NTU regeneration will be disabled and orb particles will be disabled
+ antChargingTick = context.system.scheduler.scheduleOnce(1000 milliseconds, self, NtuCharging(player, vehicle))
+ }
+ case DriveState.Undeploying =>
+ // We only want this WSA (not other player's WSA) to manage timers
+ if(vehicle.Seat(0).get.Occupant.contains(player)){
+ antChargingTick.cancel() // Stop charging NTU if charging
+ }
+
+ avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(obj.GUID, 52, 0L)) // panel glow off
+ avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(obj.GUID, 49, 0L)) // orb particles off
+ case DriveState.Mobile | DriveState.State7 | DriveState.Deploying =>
+ case _ => ;
+ }
+ }
case _ => ;
}
}
@@ -4279,18 +4420,25 @@ class WorldSessionActor extends Actor with MDCContextAware {
* @param building the building object
*/
def initFacility(continentNumber : Int, buildingNumber : Int, building : Building) : Unit = {
+ var ntuLevel = 0
+ building.Amenities.filter(x => (x.Definition == GlobalDefinitions.resource_silo)).headOption.asInstanceOf[Option[ResourceSilo]] match {
+ case Some(obj: ResourceSilo) =>
+ ntuLevel = obj.CapacitorDisplay.toInt
+ case _ => ;
+ }
+
sendResponse(
BuildingInfoUpdateMessage(
- continentNumber,
- buildingNumber,
- ntu_level = 8,
+ continent_id = continentNumber,
+ building_id = buildingNumber,
+ ntu_level = ntuLevel,
is_hacked = false,
empire_hack = PlanetSideEmpire.NEUTRAL,
- hack_time_remaining = 0,
- building.Faction,
+ hack_time_remaining = 0, // milliseconds
+ empire_own = building.Faction,
unk1 = 0, //!! Field != 0 will cause malformed packet. See class def.
unk1x = None,
- PlanetSideGeneratorState.Normal,
+ generator_state = PlanetSideGeneratorState.Normal,
spawn_tubes_normal = true,
force_dome_active = false,
lattice_benefit = 0,
@@ -4363,6 +4511,20 @@ class WorldSessionActor extends Actor with MDCContextAware {
val amenityId = amenity.GUID
sendResponse(PlanetsideAttributeMessage(amenityId, 50, 0))
sendResponse(PlanetsideAttributeMessage(amenityId, 51, 0))
+
+ amenity.Definition match {
+ case GlobalDefinitions.resource_silo =>
+ // Synchronise warning light & silo capacity
+ var silo = amenity.asInstanceOf[ResourceSilo]
+ sendResponse(PlanetsideAttributeMessage(amenityId, 45, silo.CapacitorDisplay))
+ sendResponse(PlanetsideAttributeMessage(amenityId, 47, silo.LowNtuWarningOn))
+
+ if(silo.ChargeLevel == 0) {
+ // temporarily disabled until warpgates can bring ANTs from sanctuary, otherwise we'd be stuck in a situation with an unpowered base and no way to get an ANT to refill it.
+ // sendResponse(PlanetsideAttributeMessage(PlanetSideGUID(silo.Owner.asInstanceOf[Building].ModelId), 48, 1))
+ }
+ case _ => ;
+ }
})
sendResponse(HackMessage(3, PlanetSideGUID(building.ModelId), PlanetSideGUID(0), 0, 3212836864L, HackState.HackCleared, 8))
})
@@ -4380,7 +4542,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
*
* 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.
- * @pararm tplayer the player to be killed
+ * @param tplayer the player to be killed
*/
def KillPlayer(tplayer : Player) : Unit = {
val player_guid = tplayer.GUID
@@ -4407,7 +4569,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
CancelAllProximityUnits()
import scala.concurrent.ExecutionContext.Implicits.global
- reviveTimer = context.system.scheduler.scheduleOnce(respawnTimer milliseconds, galaxy, Zone.Lattice.RequestSpawnPoint(Zones.SanctuaryZoneNumber(tplayer.Faction), tplayer, 7))
+ reviveTimer = context.system.scheduler.scheduleOnce(respawnTimer milliseconds, cluster, Zone.Lattice.RequestSpawnPoint(Zones.SanctuaryZoneNumber(tplayer.Faction), tplayer, 7))
}
/**
@@ -4573,7 +4735,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
sendResponse(DisconnectMessage("Player failed to load on faction's sanctuary continent. Please relog."))
}
else {
- galaxy ! Zone.Lattice.RequestSpawnPoint(sanctNumber, tplayer, 7)
+ cluster ! Zone.Lattice.RequestSpawnPoint(sanctNumber, tplayer, 7)
}
}
@@ -4633,7 +4795,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
/**
* For pure proximity-based units and services, disable any manual attempt at cutting off the functionality.
* If an existing timer can be found, cancel it.
- * @param terminal the proximity-based unit
+ * @param terminal_guid the proximity-based unit
*/
def ClearDelayedProximityUnitReset(terminal_guid : PlanetSideGUID) : Unit = {
delayedProximityTerminalResets.get(terminal_guid) match {
@@ -4730,7 +4892,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
* Restore, at most, a specific amount of health points on a player.
* Send messages to connected client and to events system.
* @param tplayer the player
- * @param repairValue the amount to heal;
+ * @param healValue the amount to heal;
* 10 by default
* @return whether the player can be repaired for any more health points
*/
@@ -4979,4 +5141,8 @@ object WorldSessionActor {
delta : Float,
completeAction : () => Unit,
tickAction : Option[() => Unit] = None)
+
+ private final case class NtuCharging(tplayer: Player,
+ vehicle: Vehicle)
+ private final case class NtuDischarging(tplayer: Player, vehicle: Vehicle, silo_guid: PlanetSideGUID)
}