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