From 29cf59775a27dd306eb8c47daaf47470de2981c1 Mon Sep 17 00:00:00 2001 From: FateJH Date: Wed, 18 Apr 2018 11:56:26 -0400 Subject: [PATCH] working medical terminal and stubs for other proximity-based terminal operations --- .../psforever/objects/GlobalDefinitions.scala | 8 ++ .../terminals/MedicalTerminalDefinition.scala | 28 ++++ .../terminals/ProximityTerminal.scala | 50 ++++++++ .../terminals/ProximityTerminalControl.scala | 29 +++++ .../serverobject/terminals/Terminal.scala | 4 + pslogin/src/main/scala/Maps.scala | 10 +- .../src/main/scala/WorldSessionActor.scala | 121 +++++++++++++++++- .../scala/services/local/LocalAction.scala | 1 + .../scala/services/local/LocalResponse.scala | 1 + .../scala/services/local/LocalService.scala | 4 + 10 files changed, 253 insertions(+), 3 deletions(-) create mode 100644 common/src/main/scala/net/psforever/objects/serverobject/terminals/MedicalTerminalDefinition.scala create mode 100644 common/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityTerminal.scala create mode 100644 common/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityTerminalControl.scala diff --git a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala index db4c14fe..0e542f53 100644 --- a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala +++ b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala @@ -515,6 +515,14 @@ object GlobalDefinitions { val respawn_tube_tower = new SpawnTubeDefinition(733) + val adv_med_terminal = new MedicalTerminalDefinition(38) + + val crystals_health_a = new MedicalTerminalDefinition(225) + + val crystals_health_b = new MedicalTerminalDefinition(226) + + val medical_terminal = new MedicalTerminalDefinition(529) + val spawn_pad = new VehicleSpawnPadDefinition val mb_locker = new LockerDefinition diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/MedicalTerminalDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/MedicalTerminalDefinition.scala new file mode 100644 index 00000000..a6abc94c --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/MedicalTerminalDefinition.scala @@ -0,0 +1,28 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.serverobject.terminals + +import net.psforever.objects.Player +import net.psforever.packet.game.ItemTransactionMessage + +class MedicalTerminalDefinition(objectId : Int) extends TerminalDefinition(objectId) { + Name = if(objectId == 38) { + "adv_med_terminal" + } + else if(objectId == 225) { + "crystals_health_a" + } + else if(objectId == 226) { + "crystals_health_b" + } + else if(objectId == 529) { + "medical_terminal" + } + else if(objectId == 689) { + "portable_med_terminal" + } + else { + throw new IllegalArgumentException("terminal must be either object id 38, object id 529, or object id 689") + } + + def Buy(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = Terminal.NoDeal() +} diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityTerminal.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityTerminal.scala new file mode 100644 index 00000000..b553a07d --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityTerminal.scala @@ -0,0 +1,50 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.serverobject.terminals + +import net.psforever.objects.Player +import net.psforever.packet.game.PlanetSideGUID + +class ProximityTerminal(tdef : TerminalDefinition) extends Terminal(tdef) { + private var users : Set[PlanetSideGUID] = Set.empty + + def NumberUsers : Int = users.size + + def AddUser(player_guid : PlanetSideGUID) : Int = { + users += player_guid + NumberUsers + } + + def RemoveUser(player_guid : PlanetSideGUID) : Int = { + users -= player_guid + NumberUsers + } +} + +object ProximityTerminal { + final case class Use(player : Player) + final case class Unuse(player : Player) + + /** + * Overloaded constructor. + * @param tdef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields + */ + def apply(tdef : TerminalDefinition) : ProximityTerminal = { + new ProximityTerminal(tdef) + } + + import akka.actor.ActorContext + + /** + * Instantiate an configure a `Terminal` object + * @param tdef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields + * @param id the unique id that will be assigned to this entity + * @param context a context to allow the object to properly set up `ActorSystem` functionality + * @return the `Terminal` object + */ + def Constructor(tdef : TerminalDefinition)(id : Int, context : ActorContext) : Terminal = { + import akka.actor.Props + val obj = ProximityTerminal(tdef) + obj.Actor = context.actorOf(Props(classOf[ProximityTerminalControl], obj), s"${tdef.Name}_$id") + obj + } +} diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityTerminalControl.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityTerminalControl.scala new file mode 100644 index 00000000..3964ff30 --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityTerminalControl.scala @@ -0,0 +1,29 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.serverobject.terminals + +import akka.actor.Actor +import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior} +import net.psforever.objects.serverobject.terminals.Terminal.TerminalMessage + +class ProximityTerminalControl(term : ProximityTerminal) extends Actor with FactionAffinityBehavior.Check { + def FactionObject : FactionAffinity = term + + def receive : Receive = checkBehavior.orElse { + case ProximityTerminal.Use(player) => + val hadNoUsers = term.NumberUsers == 0 + if(term.AddUser(player.GUID) == 1 && hadNoUsers) { + sender ! TerminalMessage(player, null, Terminal.StartProximityEffect(term)) + } + + case ProximityTerminal.Unuse(player) => + val hadUsers = term.NumberUsers > 0 + if(term.RemoveUser(player.GUID) == 0 && hadUsers) { + sender ! TerminalMessage(player, null, Terminal.StopProximityEffect(term)) + } + + case _ => + sender ! Terminal.NoDeal() + } + + override def toString : String = term.Definition.Name +} diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/Terminal.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/Terminal.scala index 3f6f6389..1bcf7455 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/Terminal.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/Terminal.scala @@ -190,6 +190,10 @@ object Terminal { */ final case class InfantryLoadout(exosuit : ExoSuitType.Value, subtype : Int = 0, holsters : List[InventoryItem], inventory : List[InventoryItem]) extends Exchange + final case class StartProximityEffect(terminal : ProximityTerminal) extends Exchange + + final case class StopProximityEffect(terminal : ProximityTerminal) extends Exchange + /** * Overloaded constructor. * @param tdef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields diff --git a/pslogin/src/main/scala/Maps.scala b/pslogin/src/main/scala/Maps.scala index a6f8cd0a..2bdf50e8 100644 --- a/pslogin/src/main/scala/Maps.scala +++ b/pslogin/src/main/scala/Maps.scala @@ -7,7 +7,7 @@ import net.psforever.objects.serverobject.locks.IFFLock import net.psforever.objects.serverobject.mblocker.Locker import net.psforever.objects.serverobject.pad.VehicleSpawnPad import net.psforever.objects.serverobject.structures.{Building, FoundationBuilder, StructureType, WarpGate} -import net.psforever.objects.serverobject.terminals.Terminal +import net.psforever.objects.serverobject.terminals.{ProximityTerminal, Terminal} import net.psforever.objects.serverobject.tube.SpawnTube import net.psforever.types.Vector3 @@ -452,6 +452,10 @@ object Maps { LocalObject(691, Locker.Constructor) LocalObject(692, Locker.Constructor) LocalObject(693, Locker.Constructor) + LocalObject(778, ProximityTerminal.Constructor(medical_terminal)) + LocalObject(779, ProximityTerminal.Constructor(medical_terminal)) + LocalObject(780, ProximityTerminal.Constructor(medical_terminal)) + LocalObject(781, ProximityTerminal.Constructor(medical_terminal)) LocalObject(842, Terminal.Constructor(order_terminal)) LocalObject(843, Terminal.Constructor(order_terminal)) LocalObject(844, Terminal.Constructor(order_terminal)) @@ -495,6 +499,10 @@ object Maps { ObjectToBuilding(691, 2) ObjectToBuilding(692, 2) ObjectToBuilding(693, 2) + ObjectToBuilding(778, 2) + ObjectToBuilding(779, 2) + ObjectToBuilding(780, 2) + ObjectToBuilding(781, 2) ObjectToBuilding(842, 2) ObjectToBuilding(843, 2) ObjectToBuilding(844, 2) diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index d6c597d9..19b0caa5 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -26,7 +26,7 @@ import net.psforever.objects.serverobject.implantmech.ImplantTerminalMech import net.psforever.objects.serverobject.locks.IFFLock import net.psforever.objects.serverobject.mblocker.Locker import net.psforever.objects.serverobject.pad.VehicleSpawnPad -import net.psforever.objects.serverobject.terminals.{MatrixTerminalDefinition, Terminal} +import net.psforever.objects.serverobject.terminals.{MatrixTerminalDefinition, ProximityTerminal, Terminal} import net.psforever.objects.serverobject.terminals.Terminal.TerminalMessage import net.psforever.objects.vehicles.{AccessPermissionGroup, Utility, VehicleLockState} import net.psforever.objects.serverobject.structures.{Building, StructureType, WarpGate} @@ -66,6 +66,8 @@ class WorldSessionActor extends Actor with MDCContextAware { var speed : Float = 1.0f var spectator : Boolean = false var admin : Boolean = false + var usingMedicalTerminal : Option[PlanetSideGUID] = None + var usingProximityTerminal : Set[PlanetSideGUID] = Set.empty var clientKeepAlive : Cancellable = DefaultCancellable.obj var progressBarUpdate : Cancellable = DefaultCancellable.obj @@ -371,6 +373,11 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(HackMessage(0, target_guid, guid, 100, unk1, HackState.Hacked, unk2)) } + case LocalResponse.ProximityTerminalEffect(object_guid, effectState) => + if(player.GUID != guid) { + sendResponse(ProximityTerminalUseMessage(PlanetSideGUID(0), object_guid, effectState)) + } + case LocalResponse.TriggerSound(sound, pos, unk, volume) => sendResponse(TriggerSoundMessage(sound, pos, unk, volume)) @@ -955,6 +962,20 @@ class WorldSessionActor extends Actor with MDCContextAware { log.error(s"$tplayer wanted to spawn a vehicle, but there was no spawn pad associated with terminal ${msg.terminal_guid} to accept it") } + case Terminal.StartProximityEffect(term) => + val player_guid = player.GUID + val term_guid = term.GUID + StartUsingProximityUnit(term) //redundant but cautious + sendResponse(ProximityTerminalUseMessage(player_guid, term_guid, true)) + localService ! LocalServiceMessage(continent.Id, LocalAction.ProximityTerminalEffect(player_guid, term_guid, true)) + + case Terminal.StopProximityEffect(term) => + val player_guid = player.GUID + val term_guid = term.GUID + StopUsingProximityUnit(term) //redundant but cautious + sendResponse(ProximityTerminalUseMessage(player_guid, term_guid, false)) + localService ! LocalServiceMessage(continent.Id, LocalAction.ProximityTerminalEffect(player_guid, term_guid, false)) + case Terminal.NoDeal() => log.warn(s"$tplayer made a request but the terminal rejected the order $msg") sendResponse(ItemTransactionResultMessage(msg.terminal_guid, msg.transaction_type, false)) @@ -2173,6 +2194,23 @@ class WorldSessionActor extends Actor with MDCContextAware { case None => ; } + case msg @ ProximityTerminalUseMessage(player_guid, object_guid, _) => + log.info(s"ProximityTerminal: $msg") + continent.GUID(object_guid) match { + case Some(obj : ProximityTerminal) => + if(usingProximityTerminal.contains(object_guid)) { + SelectProximityTerminal(obj) + } + else { + //obj.Actor ! ProximityTerminal.Use(player) + StartUsingProximityUnit(obj) + } + case Some(obj) => ; + log.warn(s"ProximityTerminal: object is not a terminal - $obj") + case None => + log.warn(s"ProximityTerminal: no object with guid $object_guid found") + } + case msg @ UnuseItemMessage(player_guid, object_guid) => log.info("UnuseItem: " + msg) continent.GUID(object_guid) match { @@ -3398,7 +3436,7 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(HackMessage(3, PlanetSideGUID(building.ModelId), PlanetSideGUID(0), 0, 3212836864L, HackState.HackCleared, 8)) }) } - + /** * The player has lost all his vitality and must be killed.
*
@@ -3599,6 +3637,85 @@ class WorldSessionActor extends Actor with MDCContextAware { } } + def StartUsingProximityUnit(terminal : ProximityTerminal) : Unit = { + val term_guid = terminal.GUID + if(!usingProximityTerminal.contains(term_guid)) { + terminal.Definition match { + case GlobalDefinitions.adv_med_terminal | GlobalDefinitions.medical_terminal => + usingMedicalTerminal = Some(term_guid) + case _ => ; + } + usingProximityTerminal += term_guid + terminal.Actor ! ProximityTerminal.Use(player) + } + } + + def StopUsingProximityUnit(terminal : ProximityTerminal) : Unit = { + val term_guid = terminal.GUID + if(usingProximityTerminal.contains(term_guid)) { + if(usingMedicalTerminal.contains(term_guid)) { + usingMedicalTerminal = None + } + usingProximityTerminal -= term_guid + terminal.Actor ! ProximityTerminal.Unuse(player) + } + } + + def SelectProximityTerminal(terminal : ProximityTerminal) : Unit = { + terminal.Definition match { + case GlobalDefinitions.adv_med_terminal | GlobalDefinitions.medical_terminal => + ProximityMedicalTerminal(terminal) + + case GlobalDefinitions.crystals_health_a | GlobalDefinitions.crystals_health_b => + ProximityHealCrystal(terminal) + + case _ => ; + } + } + + def ProximityMedicalTerminal(terminal : ProximityTerminal) : Unit = { + val object_guid : PlanetSideGUID = terminal.GUID + val healthUp : Boolean = player.Health < player.MaxHealth + val armorUp : Boolean = player.Armor < player.MaxArmor + if(healthUp || armorUp) { + val player_guid = player.GUID + val fullHealth = ProximityHeal(player_guid, object_guid) + val fullArmor = ProximityArmorRepair(player_guid, object_guid) + if(fullHealth && fullArmor) { + log.info(s"ProximityTerminal: ${player.Name} is all healed up") + StopUsingProximityUnit(terminal) + } + } + } + + def ProximityHealCrystal(terminal : ProximityTerminal) : Unit = { + val object_guid : PlanetSideGUID = terminal.GUID + val healthUp : Boolean = player.Health < player.MaxHealth + if(healthUp) { + val player_guid = player.GUID + if(ProximityHeal(object_guid, player.GUID)) { + log.info(s"ProximityTerminal: ${player.Name} is all healed up") + StopUsingProximityUnit(terminal) + } + } + } + + def ProximityHeal(player_guid : PlanetSideGUID, object_guid : PlanetSideGUID, healValue : Int = 10) : Boolean = { + log.info(s"ProximityTerminal: dispensing health to ${player.Name} - <3") + player.Health = player.Health + healValue + sendResponse(PlanetsideAttributeMessage(player_guid, 0, player.Health)) + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(player.GUID, 0, player.Health)) + player.Health == player.MaxHealth + } + + def ProximityArmorRepair(player_guid : PlanetSideGUID, object_guid : PlanetSideGUID, repairValue : Int = 10) : Boolean = { + log.info(s"ProximityTerminal: dispensing armor to ${player.Name} - c[=") + player.Armor = player.Armor + repairValue + sendResponse(PlanetsideAttributeMessage(player_guid, 4, player.Armor)) + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(player.GUID, 4, player.Armor)) + player.Armor == player.MaxArmor + } + def failWithError(error : String) = { log.error(error) sendResponse(ConnectionClose()) diff --git a/pslogin/src/main/scala/services/local/LocalAction.scala b/pslogin/src/main/scala/services/local/LocalAction.scala index 4003fd9b..1c04f2e7 100644 --- a/pslogin/src/main/scala/services/local/LocalAction.scala +++ b/pslogin/src/main/scala/services/local/LocalAction.scala @@ -14,5 +14,6 @@ object LocalAction { final case class DoorCloses(player_guid : PlanetSideGUID, door_guid : PlanetSideGUID) extends Action final case class HackClear(player_guid : PlanetSideGUID, target : PlanetSideServerObject, unk1 : Long, unk2 : Long = 8L) extends Action final case class HackTemporarily(player_guid : PlanetSideGUID, continent : Zone, target : PlanetSideServerObject, unk1 : Long, unk2 : Long = 8L) extends Action + final case class ProximityTerminalEffect(player_guid : PlanetSideGUID, object_guid : PlanetSideGUID, effectState : Boolean) extends Action final case class TriggerSound(player_guid : PlanetSideGUID, sound : TriggeredSound.Value, pos : Vector3, unk : Int, volume : Float) extends Action } diff --git a/pslogin/src/main/scala/services/local/LocalResponse.scala b/pslogin/src/main/scala/services/local/LocalResponse.scala index 72bd523f..fdc2aa37 100644 --- a/pslogin/src/main/scala/services/local/LocalResponse.scala +++ b/pslogin/src/main/scala/services/local/LocalResponse.scala @@ -11,5 +11,6 @@ object LocalResponse { final case class DoorCloses(door_guid : PlanetSideGUID) extends Response final case class HackClear(target_guid : PlanetSideGUID, unk1 : Long, unk2 : Long) extends Response final case class HackObject(target_guid : PlanetSideGUID, unk1 : Long, unk2 : Long) extends Response + final case class ProximityTerminalEffect(object_guid : PlanetSideGUID, effectState : Boolean) extends Response final case class TriggerSound(sound : TriggeredSound.Value, pos : Vector3, unk : Int, volume : Float) extends Response } diff --git a/pslogin/src/main/scala/services/local/LocalService.scala b/pslogin/src/main/scala/services/local/LocalService.scala index b11eef4e..5c01ee1a 100644 --- a/pslogin/src/main/scala/services/local/LocalService.scala +++ b/pslogin/src/main/scala/services/local/LocalService.scala @@ -55,6 +55,10 @@ class LocalService extends Actor { LocalEvents.publish( LocalServiceResponse(s"/$forChannel/Local", player_guid, LocalResponse.HackObject(target.GUID, unk1, unk2)) ) + case LocalAction.ProximityTerminalEffect(player_guid, object_guid, effectState) => + LocalEvents.publish( + LocalServiceResponse(s"/$forChannel/Local", player_guid, LocalResponse.ProximityTerminalEffect(object_guid, effectState)) + ) case LocalAction.TriggerSound(player_guid, sound, pos, unk, volume) => LocalEvents.publish( LocalServiceResponse(s"/$forChannel/Local", player_guid, LocalResponse.TriggerSound(sound, pos, unk, volume))