From 3e40a2f3199f53efbfadc5439cf0f43dc5f26512 Mon Sep 17 00:00:00 2001 From: Mazo Date: Tue, 22 Oct 2019 17:15:46 +0100 Subject: [PATCH] Initial merge of some PTS v3 changes (#281) --- .../psforever/objects/GlobalDefinitions.scala | 139 ++++- .../scala/net/psforever/objects/Player.scala | 11 +- .../objects/ballistics/PlayerSource.scala | 4 +- .../serverobject/structures/Building.scala | 1 + .../EquipmentTerminalDefinition.scala | 11 +- .../psforever/objects/vital/Vitality.scala | 2 + .../packet/game/LoginRespMessage.scala | 14 +- .../game/PlanetsideAttributeMessage.scala | 7 +- .../packet/game/VNLWorldStatusMessage.scala | 2 +- .../game/objectcreate/ObjectClass.scala | 2 + .../scala/net/psforever/types/BailType.scala | 6 + .../net/psforever/types/CargoStatus.scala | 1 + .../net/psforever/types/DriveState.scala | 3 + .../psforever/types/MeritCommendation.scala | 3 + .../scala/services/avatar/AvatarAction.scala | 1 + .../services/avatar/AvatarResponse.scala | 1 + .../scala/services/avatar/AvatarService.scala | 4 + .../main/scala/services/chat/ChatAction.scala | 18 + .../scala/services/chat/ChatResponse.scala | 19 + .../scala/services/chat/ChatService.scala | 113 ++++ .../services/chat/ChatServiceMessage.scala | 4 + .../services/chat/ChatServiceResponse.scala | 18 + .../src/main/scala/CryptoSessionActor.scala | 1 + .../src/main/scala/PacketCodingActor.scala | 18 +- pslogin/src/main/scala/PsLogin.scala | 2 + .../src/main/scala/WorldSessionActor.scala | 570 ++++++++++++++++-- pslogin/src/main/scala/csr/CSRZoneImpl.scala | 2 +- 27 files changed, 893 insertions(+), 84 deletions(-) create mode 100644 common/src/main/scala/services/chat/ChatAction.scala create mode 100644 common/src/main/scala/services/chat/ChatResponse.scala create mode 100644 common/src/main/scala/services/chat/ChatService.scala create mode 100644 common/src/main/scala/services/chat/ChatServiceMessage.scala create mode 100644 common/src/main/scala/services/chat/ChatServiceResponse.scala diff --git a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala index e5da7c05..f833b0fb 100644 --- a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala +++ b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala @@ -463,6 +463,12 @@ object GlobalDefinitions { val flamethrower_ammo = AmmoBoxDefinition(Ammo.flamethrower_ammo) + val winchester_ammo = AmmoBoxDefinition(Ammo.winchester_ammo) + + val pellet_gun_ammo = AmmoBoxDefinition(Ammo.pellet_gun_ammo) + + val six_shooter_ammo = AmmoBoxDefinition(Ammo.six_shooter_ammo) + val dualcycler_ammo = AmmoBoxDefinition(Ammo.dualcycler_ammo) val pounder_ammo = AmmoBoxDefinition(Ammo.pounder_ammo) @@ -652,6 +658,14 @@ object GlobalDefinitions { val flamethrower = ToolDefinition(ObjectClass.flamethrower) + val winchester = ToolDefinition(ObjectClass.winchester) + + val pellet_gun = ToolDefinition(ObjectClass.pellet_gun) + + val six_shooter = ToolDefinition(ObjectClass.six_shooter) + + val dynomite = ToolDefinition(ObjectClass.dynomite) + val trhev_dualcycler = new ToolDefinition(ObjectClass.trhev_dualcycler) { override def NextFireModeIndex(index : Int) : Int = index } @@ -1204,6 +1218,21 @@ object GlobalDefinitions { case PlanetSideEmpire.NEUTRAL => bullet_9mm } } + /** + * For a given faction, provide the AP ammunition for the medium assault rifle. + * The ammunition value here must work with the result of obtaining the rifle using the faction. + * @param faction the faction + * @return thr `AmmoBoxDefinition` for the rifle's ammo + * @see `GlobalDefinitions.MediumRifle` + */ + def MediumRifleAPAmmo(faction : PlanetSideEmpire.Value) : AmmoBoxDefinition = { + faction match { + case PlanetSideEmpire.TR => bullet_9mm_AP + case PlanetSideEmpire.NC => bullet_9mm_AP + case PlanetSideEmpire.VS => energy_cell + case PlanetSideEmpire.NEUTRAL => bullet_9mm_AP + } + } /** * For a given faction, provide the heavy assault rifle. @@ -1236,6 +1265,22 @@ object GlobalDefinitions { } } + /** + * For a given faction, provide the AP ammunition for the heavy assault rifle. + * The ammunition value here must work with the result of obtaining the rifle using the faction. + * @param faction the faction + * @return thr `AmmoBoxDefinition` for the rifle's ammo + * @see `GlobalDefinitions.HeavyRifle` + */ + def HeavyRifleAPAmmo(faction : PlanetSideEmpire.Value) : AmmoBoxDefinition = { + faction match { + case PlanetSideEmpire.TR => bullet_9mm_AP + case PlanetSideEmpire.NC => shotgun_shell_AP + case PlanetSideEmpire.VS => energy_cell + case PlanetSideEmpire.NEUTRAL => bullet_9mm_AP + } + } + /** * For a given faction, provide the anti-vehicular launcher. * @param faction the faction @@ -1268,13 +1313,13 @@ object GlobalDefinitions { def MAXArms(subtype : Int, faction : PlanetSideEmpire.Value) : ToolDefinition = { if(subtype == 1) { - AIMAX(faction) + AI_MAX(faction) } else if(subtype == 2) { - AVMAX(faction) + AV_MAX(faction) } else if(subtype == 3) { - AAMAX(faction) + AA_MAX(faction) } else { suppressor //there are no common pool MAX arms @@ -1292,7 +1337,7 @@ object GlobalDefinitions { } } - def AIMAX(faction : PlanetSideEmpire.Value) : ToolDefinition = { + def AI_MAX(faction : PlanetSideEmpire.Value) : ToolDefinition = { faction match { case PlanetSideEmpire.TR => trhev_dualcycler case PlanetSideEmpire.NC => nchev_scattercannon @@ -1301,7 +1346,16 @@ object GlobalDefinitions { } } - def AVMAX(faction : PlanetSideEmpire.Value) : ToolDefinition = { + def AI_MAXAmmo(faction : PlanetSideEmpire.Value) : AmmoBoxDefinition = { + faction match { + case PlanetSideEmpire.TR => dualcycler_ammo + case PlanetSideEmpire.NC => scattercannon_ammo + case PlanetSideEmpire.VS => quasar_ammo + case PlanetSideEmpire.NEUTRAL => bullet_9mm //there are no common pool MAX arms + } + } + + def AV_MAX(faction : PlanetSideEmpire.Value) : ToolDefinition = { faction match { case PlanetSideEmpire.TR => trhev_pounder case PlanetSideEmpire.NC => nchev_falcon @@ -1310,7 +1364,16 @@ object GlobalDefinitions { } } - def AAMAX(faction : PlanetSideEmpire.Value) : ToolDefinition = { + def AV_MAXAmmo(faction : PlanetSideEmpire.Value) : AmmoBoxDefinition = { + faction match { + case PlanetSideEmpire.TR => pounder_ammo + case PlanetSideEmpire.NC => falcon_ammo + case PlanetSideEmpire.VS => comet_ammo + case PlanetSideEmpire.NEUTRAL => bullet_9mm //there are no common pool MAX arms + } + } + + def AA_MAX(faction : PlanetSideEmpire.Value) : ToolDefinition = { faction match { case PlanetSideEmpire.TR => trhev_burster case PlanetSideEmpire.NC => nchev_sparrow @@ -1319,6 +1382,15 @@ object GlobalDefinitions { } } + def AA_MAXAmmo(faction : PlanetSideEmpire.Value) : AmmoBoxDefinition = { + faction match { + case PlanetSideEmpire.TR => burster_ammo + case PlanetSideEmpire.NC => sparrow_ammo + case PlanetSideEmpire.VS => starfire_ammo + case PlanetSideEmpire.NEUTRAL => bullet_9mm //there are no common pool MAX arms + } + } + def PortableMannedTurret(faction :PlanetSideEmpire.Value) : TurretDeployableDefinition = { faction match { case PlanetSideEmpire.TR => portable_manned_turret_tr @@ -1336,7 +1408,7 @@ object GlobalDefinitions { */ def isGrenade(edef : EquipmentDefinition) : Boolean = { edef match { - case `frag_grenade` | `jammer_grenade` | `plasma_grenade` => + case `frag_grenade` | `jammer_grenade` | `plasma_grenade` | `dynomite` => true case _ => false @@ -1651,6 +1723,18 @@ object GlobalDefinitions { flamethrower_ammo.Capacity = 100 flamethrower_ammo.Tile = InventoryTile.Tile44 + winchester_ammo.Name = "winchester_ammo" + winchester_ammo.Capacity = 10 + winchester_ammo.Tile = InventoryTile.Tile33 + + pellet_gun_ammo.Name = "pellet_gun_ammo" + pellet_gun_ammo.Capacity = 8 + pellet_gun_ammo.Tile = InventoryTile.Tile33 + + six_shooter_ammo.Name = "six_shooter_ammo" + six_shooter_ammo.Capacity = 12 + six_shooter_ammo.Tile = InventoryTile.Tile33 + dualcycler_ammo.Name = "dualcycler_ammo" dualcycler_ammo.Capacity = 100 dualcycler_ammo.Tile = InventoryTile.Tile44 @@ -4079,6 +4163,47 @@ object GlobalDefinitions { flamethrower.FireModes(1).Rounds = 50 flamethrower.Tile = InventoryTile.Tile63 + winchester.Name = "winchester" + winchester.Size = EquipmentSize.Rifle + winchester.AmmoTypes += winchester_ammo + winchester.ProjectileTypes += winchester_projectile + winchester.FireModes += new FireModeDefinition + winchester.FireModes.head.AmmoTypeIndices += 0 + winchester.FireModes.head.AmmoSlotIndex = 0 + winchester.FireModes.head.Magazine = 1 + winchester.Tile = InventoryTile.Tile93 + + pellet_gun.Name = "pellet_gun" + pellet_gun.Size = EquipmentSize.Rifle + pellet_gun.AmmoTypes += pellet_gun_ammo + pellet_gun.ProjectileTypes += pellet_gun_projectile + pellet_gun.FireModes += new PelletFireModeDefinition + pellet_gun.FireModes.head.AmmoTypeIndices += 0 + pellet_gun.FireModes.head.AmmoSlotIndex = 0 + pellet_gun.FireModes.head.Magazine = 1 + pellet_gun.FireModes.head.Chamber = 8 //1 shells * 8 pellets = 8 + pellet_gun.Tile = InventoryTile.Tile63 + + six_shooter.Name = "six_shooter" + six_shooter.Size = EquipmentSize.Pistol + six_shooter.AmmoTypes += six_shooter_ammo + six_shooter.ProjectileTypes += six_shooter_projectile + six_shooter.FireModes += new FireModeDefinition + six_shooter.FireModes.head.AmmoTypeIndices += 0 + six_shooter.FireModes.head.AmmoSlotIndex = 0 + six_shooter.FireModes.head.Magazine = 6 + six_shooter.Tile = InventoryTile.Tile33 + + dynomite.Name = "dynomite" + dynomite.Size = EquipmentSize.Pistol + dynomite.AmmoTypes += frag_grenade_ammo + dynomite.ProjectileTypes += dynomite_projectile + dynomite.FireModes += new FireModeDefinition + dynomite.FireModes.head.AmmoTypeIndices += 0 + dynomite.FireModes.head.AmmoSlotIndex = 0 + dynomite.FireModes.head.Magazine = 1 + dynomite.Tile = InventoryTile.Tile22 + trhev_dualcycler.Name = "trhev_dualcycler" trhev_dualcycler.Size = EquipmentSize.Max trhev_dualcycler.AmmoTypes += dualcycler_ammo diff --git a/common/src/main/scala/net/psforever/objects/Player.scala b/common/src/main/scala/net/psforever/objects/Player.scala index 250c81b2..09e261c2 100644 --- a/common/src/main/scala/net/psforever/objects/Player.scala +++ b/common/src/main/scala/net/psforever/objects/Player.scala @@ -48,9 +48,14 @@ class Player(private val core : Avatar) extends PlanetSideGameObject private var continent : String = "home2" //the zone id - //SouNourS things - /** Last medkituse. */ - var lastMedkit : Long = 0 + var silenced : Boolean = false + var firstLoad : Boolean = false + def FirstLoad : Boolean = firstLoad + def FirstLoad_=(status : Boolean) : Boolean = { + firstLoad = status + FirstLoad + } + var death_by : Int = 0 var lastSeenStreamMessage : Array[Long] = Array.fill[Long](65535)(0L) var lastShotSeq_time : Int = -1 /** From PlanetsideAttributeMessage */ diff --git a/common/src/main/scala/net/psforever/objects/ballistics/PlayerSource.scala b/common/src/main/scala/net/psforever/objects/ballistics/PlayerSource.scala index cb054d4e..e2a34fc2 100644 --- a/common/src/main/scala/net/psforever/objects/ballistics/PlayerSource.scala +++ b/common/src/main/scala/net/psforever/objects/ballistics/PlayerSource.scala @@ -7,6 +7,7 @@ import net.psforever.objects.vital.resistance.ResistanceProfile import net.psforever.types.{ExoSuitType, PlanetSideEmpire, Vector3} final case class PlayerSource(name : String, + char_id : Long, obj_def : ObjectDefinition, faction : PlanetSideEmpire.Value, exosuit : ExoSuitType.Value, @@ -19,6 +20,7 @@ final case class PlayerSource(name : String, modifiers : ResistanceProfile) extends SourceEntry { override def Name = name override def Faction = faction + override def CharId = char_id def Definition = obj_def def ExoSuit = exosuit def Seated = seated @@ -32,7 +34,7 @@ final case class PlayerSource(name : String, object PlayerSource { def apply(tplayer : Player) : PlayerSource = { - PlayerSource(tplayer.Name, tplayer.Definition, tplayer.Faction, tplayer.ExoSuit, tplayer.VehicleSeated.nonEmpty, + PlayerSource(tplayer.Name, tplayer.CharId, tplayer.Definition, tplayer.Faction, tplayer.ExoSuit, tplayer.VehicleSeated.nonEmpty, tplayer.Health, tplayer.Armor, tplayer.Position, tplayer.Orientation, tplayer.Velocity, tplayer.asInstanceOf[ResistanceProfile]) } } 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 c8593fc7..5b7bd202 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 @@ -155,6 +155,7 @@ object Building { def Structure(buildingType : StructureType.Value, location : Vector3)(guid : Int, map_id : Int, zone : Zone, context : ActorContext) : Building = { import akka.actor.Props + val obj = new Building(guid, map_id, zone, buildingType, GlobalDefinitions.building) obj.Position = location obj.Actor = context.actorOf(Props(classOf[BuildingControl], obj), s"$map_id-$buildingType-building") diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/EquipmentTerminalDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/EquipmentTerminalDefinition.scala index 2f1d56c7..b1b66624 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/EquipmentTerminalDefinition.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/EquipmentTerminalDefinition.scala @@ -66,7 +66,10 @@ object EquipmentTerminalDefinition { "lancer_cartridge" -> MakeAmmoBox(lancer_cartridge), "bolt" -> MakeAmmoBox(bolt), "oicw_ammo" -> MakeAmmoBox(oicw_ammo), //scorpion missile - "flamethrower_ammo" -> MakeAmmoBox(flamethrower_ammo) + "flamethrower_ammo" -> MakeAmmoBox(flamethrower_ammo), + "winchester_ammo" -> MakeAmmoBox(winchester_ammo), + "pellet_gun_ammo" -> MakeAmmoBox(pellet_gun_ammo), + "six_shooter_ammo" -> MakeAmmoBox(six_shooter_ammo) ) val maxAmmo : Map[String, () => Equipment] = Map( "dualcycler_ammo" -> MakeAmmoBox(dualcycler_ammo), @@ -172,7 +175,11 @@ object EquipmentTerminalDefinition { "heavy_sniper" -> MakeTool(heavy_sniper), //hsr "bolt_driver" -> MakeTool(bolt_driver), "oicw" -> MakeTool(oicw), //scorpion - "flamethrower" -> MakeTool(flamethrower) + "flamethrower" -> MakeTool(flamethrower), + "winchester" -> MakeTool(winchester), + "pellet_gun" -> MakeTool(pellet_gun), + "six_shooter" -> MakeTool(six_shooter), + "dynomite" -> MakeTool(dynomite) ) /** * A `Map` of operations for producing the `Tool` `Equipment` for utilities. diff --git a/common/src/main/scala/net/psforever/objects/vital/Vitality.scala b/common/src/main/scala/net/psforever/objects/vital/Vitality.scala index f5945790..e15c696f 100644 --- a/common/src/main/scala/net/psforever/objects/vital/Vitality.scala +++ b/common/src/main/scala/net/psforever/objects/vital/Vitality.scala @@ -26,6 +26,8 @@ final case class HealFromImplant(target : PlayerSource, amount : Int, implant : final case class HealFromExoSuitChange(target : PlayerSource, exosuit : ExoSuitType.Value) extends HealingActivity(target) +final case class RepairFromKit(target : PlayerSource, amount : Int, kit_def : KitDefinition) extends HealingActivity(target) + final case class RepairFromTerm(target : VehicleSource, amount : Int, term_def : TerminalDefinition) extends HealingActivity(target) final case class VehicleShieldCharge(target : VehicleSource, amount : Int) extends HealingActivity(target) //TODO facility diff --git a/common/src/main/scala/net/psforever/packet/game/LoginRespMessage.scala b/common/src/main/scala/net/psforever/packet/game/LoginRespMessage.scala index 0d17aefa..7936a407 100644 --- a/common/src/main/scala/net/psforever/packet/game/LoginRespMessage.scala +++ b/common/src/main/scala/net/psforever/packet/game/LoginRespMessage.scala @@ -44,6 +44,7 @@ object LoginRespMessage extends Marshallable[LoginRespMessage] { object LoginError extends Enumeration { type Type = Value val Success = Value(0) + val unk1 = Value(1) val BadUsernameOrPassword = Value(5) val BadVersion = Value(0xf) @@ -53,18 +54,19 @@ object LoginRespMessage extends Marshallable[LoginRespMessage] { object StationError extends Enumeration { type Type = Value val AccountActive = Value(1) - val AccountClosed = Value(2) + val AccountClosed = Value(2) // "Your Station account is currently closed" implicit val codec = PacketHelpers.createLongEnumerationCodec(this, uint32L) } object StationSubscriptionStatus extends Enumeration { type Type = Value - val None = Value(1) - val Active = Value(2) /// Not sure about this one (guessing) - val Closed = Value(4) - val Trial = Value(5) /// Not sure about this one either - val TrialExpired = Value(6) + val None = Value(1) // "You do not have a PlanetSide subscription" + val Active = Value(2) /// Not sure about this one (guessing) (no ingame error message) + val unk3 = Value(3) + val Closed = Value(4) // "Your PlanetSide subscription is currently closed" + val Trial = Value(5) /// Not sure about this one either (no ingame error message) + val TrialExpired = Value(6) // "Your trial PlanetSide subscription has expired" implicit val codec = PacketHelpers.createLongEnumerationCodec(this, uint32L) } 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 4c33eab1..e5aae35a 100644 --- a/common/src/main/scala/net/psforever/packet/game/PlanetsideAttributeMessage.scala +++ b/common/src/main/scala/net/psforever/packet/game/PlanetsideAttributeMessage.scala @@ -126,11 +126,12 @@ import scodec.codecs._ * - OLD: "Info under avatar name : 0 = Looking For Squad Members, 1 = LFS`"
* `35 - BR. Value is the BR`
* `36 - CR. Value is the CR`
+ * `38 - Spawn active or not. MUST use base MapId not base GUID`
* `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?)`
* `46 - Sends "Generator damage is at a critical level!" message` - * `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`
+ * `47 - Sets base NTU level to CRITICAL. MUST use base MapId not base GUID`
+ * `48 - Set to 1 to send base power loss message & turns on red warning lights throughout base. MUST use base MapId 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`
@@ -143,7 +144,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`
+ * `67 - Enables base shields (from cavern module/lock). MUST use base MapId 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`
diff --git a/common/src/main/scala/net/psforever/packet/game/VNLWorldStatusMessage.scala b/common/src/main/scala/net/psforever/packet/game/VNLWorldStatusMessage.scala index 6f34382c..75ac8289 100644 --- a/common/src/main/scala/net/psforever/packet/game/VNLWorldStatusMessage.scala +++ b/common/src/main/scala/net/psforever/packet/game/VNLWorldStatusMessage.scala @@ -18,7 +18,7 @@ object WorldStatus extends Enumeration { // this enumeration starts from one and is subtracted from before processing (0x005FF12A) object ServerType extends Enumeration(1) { type Type = Value - val Development, Beta, Released = Value + val Development, Beta, Released, Released_Gemini = Value implicit val codec = PacketHelpers.createEnumerationCodec(this, uint8L) } diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/ObjectClass.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/ObjectClass.scala index 776d367c..527fdc5d 100644 --- a/common/src/main/scala/net/psforever/packet/game/objectcreate/ObjectClass.scala +++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/ObjectClass.scala @@ -392,6 +392,8 @@ object ObjectClass { final val order_terminal = 612 final val order_terminala = 613 final val order_terminalb = 614 + final val portable_ammo_terminal = 684 + final val portable_order_terminal = 690 final val targeting_laser_dispenser = 851 final val teleportpad_terminal = 853 diff --git a/common/src/main/scala/net/psforever/types/BailType.scala b/common/src/main/scala/net/psforever/types/BailType.scala index a2bff67f..3290c5a4 100644 --- a/common/src/main/scala/net/psforever/types/BailType.scala +++ b/common/src/main/scala/net/psforever/types/BailType.scala @@ -8,7 +8,13 @@ object BailType extends Enumeration { type Type = Value val Normal = Value(0) + val Unk1 = Value(1) // to have Xtoolspar working + val Unk2 = Value(2) // to have Xtoolspar working + val Unk3 = Value(3) // to have Xtoolspar working val Kicked = Value(4) // User was kicked out by vehicle owner or locked from vehicle + val Unk5 = Value(5) // to have Xtoolspar working + val Unk6 = Value(6) // to have Xtoolspar working + val Unk7 = Value(7) // to have Xtoolspar working val Bailed = Value(8) // User bailed out implicit val codec = PacketHelpers.createEnumerationCodec(this, uint4L) diff --git a/common/src/main/scala/net/psforever/types/CargoStatus.scala b/common/src/main/scala/net/psforever/types/CargoStatus.scala index 9cee94a7..691e8793 100644 --- a/common/src/main/scala/net/psforever/types/CargoStatus.scala +++ b/common/src/main/scala/net/psforever/types/CargoStatus.scala @@ -9,6 +9,7 @@ object CargoStatus extends Enumeration { val Empty = Value(0) val InProgress = Value(1) + val UNK1 = Value(2) // to have Xtoolspar working val Occupied = Value(3) implicit val codec = PacketHelpers.createEnumerationCodec(this, uint4L) diff --git a/common/src/main/scala/net/psforever/types/DriveState.scala b/common/src/main/scala/net/psforever/types/DriveState.scala index 07109a68..dd0f8ccc 100644 --- a/common/src/main/scala/net/psforever/types/DriveState.scala +++ b/common/src/main/scala/net/psforever/types/DriveState.scala @@ -15,6 +15,9 @@ object DriveState extends Enumeration { val Undeploying = Value(1) val Deploying = Value(2) val Deployed = Value(3) + val UNK4 = Value(4) // to have Xtoolspar working + val UNK5 = Value(5) // to have Xtoolspar working + val UNK6 = Value(6) // to have Xtoolspar working val State7 = Value(7) //unknown; not encountered on a vehicle that can deploy; functions like Mobile val State127 = Value(127) //unknown } diff --git a/common/src/main/scala/net/psforever/types/MeritCommendation.scala b/common/src/main/scala/net/psforever/types/MeritCommendation.scala index 45f70efc..03a430e0 100644 --- a/common/src/main/scala/net/psforever/types/MeritCommendation.scala +++ b/common/src/main/scala/net/psforever/types/MeritCommendation.scala @@ -507,6 +507,9 @@ object MeritCommendation extends Enumeration { if(n > Int.MaxValue) { Attempt.failure(Err(s"value $n is too high, above maximum integer value ${Int.MaxValue}")) } + else if(n > 429) { // TODO remove that. It's for use Xtoolspar. + Attempt.failure(Err(s"value $n should not exist")) + } else { Attempt.successful(MeritCommendation(n.toInt)) } diff --git a/common/src/main/scala/services/avatar/AvatarAction.scala b/common/src/main/scala/services/avatar/AvatarAction.scala index 63135825..c9042cbe 100644 --- a/common/src/main/scala/services/avatar/AvatarAction.scala +++ b/common/src/main/scala/services/avatar/AvatarAction.scala @@ -36,6 +36,7 @@ object AvatarAction { final case class ObjectDelete(player_guid : PlanetSideGUID, item_guid : PlanetSideGUID, unk : Int = 0) extends Action final case class ObjectHeld(player_guid : PlanetSideGUID, slot : Int) extends Action final case class PlanetsideAttribute(player_guid : PlanetSideGUID, attribute_type : Int, attribute_value : Long) extends Action + final case class PlanetsideAttributeSelf(player_guid : PlanetSideGUID, attribute_type : Int, attribute_value : Long) extends Action final case class PlayerState(player_guid : PlanetSideGUID, msg : PlayerStateMessageUpstream, spectator : Boolean, weaponInHand : Boolean) extends Action final case class PickupItem(player_guid : PlanetSideGUID, zone : Zone, target : PlanetSideGameObject with Container, slot : Int, item : Equipment, unk : Int = 0) extends Action final case class PutDownFDU(player_guid : PlanetSideGUID) extends Action diff --git a/common/src/main/scala/services/avatar/AvatarResponse.scala b/common/src/main/scala/services/avatar/AvatarResponse.scala index 54df469e..f7f1a3b3 100644 --- a/common/src/main/scala/services/avatar/AvatarResponse.scala +++ b/common/src/main/scala/services/avatar/AvatarResponse.scala @@ -30,6 +30,7 @@ object AvatarResponse { final case class ObjectDelete(item_guid : PlanetSideGUID, unk : Int) extends Response final case class ObjectHeld(slot : Int) extends Response final case class PlanetsideAttribute(attribute_type : Int, attribute_value : Long) extends Response + final case class PlanetsideAttributeSelf(attribute_type : Int, attribute_value : Long) extends Response final case class PlayerState(msg : PlayerStateMessageUpstream, spectator : Boolean, weaponInHand : Boolean) extends Response final case class PutDownFDU(target_guid : PlanetSideGUID) extends Response final case class Release(player : Player) extends Response diff --git a/common/src/main/scala/services/avatar/AvatarService.scala b/common/src/main/scala/services/avatar/AvatarService.scala index 8f942218..18f5c7c0 100644 --- a/common/src/main/scala/services/avatar/AvatarService.scala +++ b/common/src/main/scala/services/avatar/AvatarService.scala @@ -139,6 +139,10 @@ class AvatarService extends Actor { AvatarEvents.publish( AvatarServiceResponse(s"/$forChannel/Avatar", guid, AvatarResponse.PlanetsideAttribute(attribute_type, attribute_value)) ) + case AvatarAction.PlanetsideAttributeSelf(guid, attribute_type, attribute_value) => + AvatarEvents.publish( + AvatarServiceResponse(s"/$forChannel/Avatar", guid, AvatarResponse.PlanetsideAttributeSelf(attribute_type, attribute_value)) + ) case AvatarAction.PlayerState(guid, msg, spectator, weapon) => AvatarEvents.publish( AvatarServiceResponse(s"/$forChannel/Avatar", guid, AvatarResponse.PlayerState(msg, spectator, weapon)) diff --git a/common/src/main/scala/services/chat/ChatAction.scala b/common/src/main/scala/services/chat/ChatAction.scala new file mode 100644 index 00000000..f1077381 --- /dev/null +++ b/common/src/main/scala/services/chat/ChatAction.scala @@ -0,0 +1,18 @@ +// Copyright (c) 2017 PSForever +package services.chat + +import net.psforever.objects.zones.Zone +import net.psforever.packet.game.{ChatMsg, PlanetSideGUID} +import net.psforever.types.{PlanetSideEmpire, Vector3} + +object ChatAction { + sealed trait Action + + final case class Local(player_guid : PlanetSideGUID, player_name : String, continent : Zone, player_pos : Vector3, player_faction : PlanetSideEmpire.Value, msg : ChatMsg) extends Action + final case class Tell(player_guid : PlanetSideGUID, player_name : String, msg : ChatMsg) extends Action + final case class Broadcast(player_guid : PlanetSideGUID, player_name : String, continent : Zone, player_pos : Vector3, player_faction : PlanetSideEmpire.Value, msg : ChatMsg) extends Action + final case class Voice(player_guid : PlanetSideGUID, player_name : String, continent : Zone, player_pos : Vector3, player_faction : PlanetSideEmpire.Value, msg : ChatMsg) extends Action + final case class Note(player_guid : PlanetSideGUID, player_name : String, msg : ChatMsg) extends Action + final case class Squad(player_guid : PlanetSideGUID, player_name : String, continent : Zone, player_pos : Vector3, player_faction : PlanetSideEmpire.Value, msg : ChatMsg) extends Action + final case class GM(player_guid : PlanetSideGUID, player_name : String, msg : ChatMsg) extends Action +} \ No newline at end of file diff --git a/common/src/main/scala/services/chat/ChatResponse.scala b/common/src/main/scala/services/chat/ChatResponse.scala new file mode 100644 index 00000000..1d7e7e52 --- /dev/null +++ b/common/src/main/scala/services/chat/ChatResponse.scala @@ -0,0 +1,19 @@ +// Copyright (c) 2017 PSForever +package services.chat + +import net.psforever.packet.game.PlanetSideGUID +import net.psforever.types.ChatMessageType + +object ChatResponse { + sealed trait Response + + final case class Local(sender : String, messageType : ChatMessageType.Value, wideContents : Boolean, recipient : String, contents : String, note : Option[String]) extends Response + final case class Tell(sender : String, messageType : ChatMessageType.Value, wideContents : Boolean, recipient : String, contents : String, note : Option[String]) extends Response + final case class UTell(sender : String, messageType : ChatMessageType.Value, wideContents : Boolean, recipient : String, contents : String, note : Option[String]) extends Response + final case class Broadcast(messageType : ChatMessageType.Value, wideContents : Boolean, recipient : String, contents : String, note : Option[String]) extends Response + final case class Voice(messageType : ChatMessageType.Value, wideContents : Boolean, recipient : String, contents : String, note : Option[String]) extends Response + final case class Unk45(sender : String, messageType : ChatMessageType.Value, wideContents : Boolean, recipient : String, contents : String, note : Option[String]) extends Response + final case class Squad(sender : String, messageType : ChatMessageType.Value, wideContents : Boolean, recipient : String, contents : String, note : Option[String]) extends Response + + final case class Text(toChannel : String, avatar_guid : PlanetSideGUID, personal : Int, messageType : ChatMessageType.Value, wideContents : Boolean, recipient : String, contents : String, note : Option[String]) +} \ No newline at end of file diff --git a/common/src/main/scala/services/chat/ChatService.scala b/common/src/main/scala/services/chat/ChatService.scala new file mode 100644 index 00000000..085792dc --- /dev/null +++ b/common/src/main/scala/services/chat/ChatService.scala @@ -0,0 +1,113 @@ +// Copyright (c) 2017 PSForever +package services.chat + +import akka.actor.Actor +import net.psforever.objects.LivePlayerList +import net.psforever.packet.game.{ChatMsg, PlanetSideGUID} +import net.psforever.types.ChatMessageType +import services.{GenericEventBus, Service} + +class ChatService extends Actor { + private [this] val log = org.log4s.getLogger + + override def preStart = { + log.info("Starting....") + } + + val ChatEvents = new GenericEventBus[ChatServiceResponse] + + def receive = { + case Service.Join(channel) => + val path = s"/Chat/$channel" + val who = sender() + log.info(s"$who has joined $path") + ChatEvents.subscribe(who, path) + case Service.Leave(None) => + ChatEvents.unsubscribe(sender()) + case Service.Leave(Some(channel)) => + val path = s"/Chat/$channel" + val who = sender() + log.info(s"$who has left $path") + ChatEvents.unsubscribe(who, path) + case Service.LeaveAll() => + ChatEvents.unsubscribe(sender()) + + case ChatServiceMessage(forChannel, action) => + action match { + case ChatAction.Local(player_guid, player_name, cont, player_pos, player_faction, msg) => // local + ChatEvents.publish( + ChatServiceResponse(s"/Chat/$forChannel", player_guid, player_name, cont, player_pos, player_faction, 2, ChatMsg(ChatMessageType.CMT_OPEN,msg.wideContents,player_name,msg.contents,None)) + ) + case ChatAction.Tell(player_guid, player_name, msg) => // tell + var good : Boolean = false + LivePlayerList.WorldPopulation(_ => true).foreach(char => { + if (char.name.equalsIgnoreCase(msg.recipient)) { + good = true + } + }) + if(good) { + ChatEvents.publish( + ChatServiceResponse(s"/Chat/$forChannel", player_guid, player_name, target = 0, replyMessage = ChatMsg(ChatMessageType.CMT_TELL,msg.wideContents,msg.recipient,msg.contents,None)) + ) + ChatEvents.publish( + ChatServiceResponse(s"/Chat/$forChannel", player_guid, player_name, target = 1, replyMessage = ChatMsg(ChatMessageType.U_CMT_TELLFROM,msg.wideContents,msg.recipient,msg.contents,None)) + ) + } else { + ChatEvents.publish( + ChatServiceResponse(s"/Chat/$forChannel", player_guid, player_name, target = 1, replyMessage = ChatMsg(ChatMessageType.U_CMT_TELLFROM,msg.wideContents,msg.recipient,msg.contents,None)) + ) + ChatEvents.publish( + ChatServiceResponse(s"/Chat/$forChannel", player_guid, player_name, target = 1, replyMessage = ChatMsg(ChatMessageType.UNK_45,msg.wideContents,"","@NoTell_Target",None)) + ) + } + case ChatAction.Broadcast(player_guid, player_name, cont, player_pos, player_faction, msg) => // broadcast + ChatEvents.publish( + ChatServiceResponse(s"/Chat/$forChannel", player_guid, player_name, cont, player_pos, player_faction, 2, ChatMsg(msg.messageType,msg.wideContents,player_name,msg.contents,None)) + ) + case ChatAction.Voice(player_guid, player_name, cont, player_pos, player_faction, msg) => // voice + ChatEvents.publish( + ChatServiceResponse(s"/Chat/$forChannel", player_guid, player_name, cont, player_pos, player_faction, 2, ChatMsg(ChatMessageType.CMT_VOICE,false,player_name,msg.contents,None)) + ) + + case ChatAction.Note(player_guid, player_name, msg) => // note + ChatEvents.publish( + ChatServiceResponse(s"/Chat/$forChannel", player_guid, player_name, target = 1, replyMessage = ChatMsg(ChatMessageType.U_CMT_GMTELLFROM,true, msg.recipient,msg.contents,None)) + ) + ChatEvents.publish( + ChatServiceResponse(s"/Chat/$forChannel", player_guid, player_name, target = 1, replyMessage = ChatMsg(ChatMessageType.CMT_GMTELL,true,"Server","Why do you try to /note ? That's a GM command ! ... Or not, nobody can /note",None)) + ) + case ChatAction.Squad(player_guid, player_name, cont, player_pos, player_faction, msg) => // squad + ChatEvents.publish( + ChatServiceResponse(s"/Chat/$forChannel", player_guid, player_name, cont, player_pos, player_faction, 2, ChatMsg(ChatMessageType.CMT_SQUAD,msg.wideContents,player_name,msg.contents,None)) + ) + case ChatAction.GM(player_guid, player_name, msg) => // GM + msg.messageType match { + case ChatMessageType.CMT_SILENCE => + ChatEvents.publish( + ChatServiceResponse(s"/Chat/$forChannel", player_guid, msg.contents, target = 0, replyMessage = ChatMsg(ChatMessageType.CMT_SILENCE, true, "", "", None)) + ) +// if(player_guid != PlanetSideGUID(0)) { +// +// val args = msg.contents.split(" ") +// var silence_name : String = "" +// var silence_time : Int = 5 +// if (args.length == 1) { +// silence_name = args(0) +// } +// else if (args.length == 2) { +// silence_name = args(0) +// silence_time = args(1).toInt +// } +// ChatEvents.publish( +// ChatServiceResponse(s"/Chat/$forChannel", player_guid, player_name, target = 1, replyMessage = ChatMsg(ChatMessageType.UNK_45, true, "", silence_name + " silenced for " + silence_time + " min(s)", None)) +// ) +// } + case _ => ; + } + case _ => ; + } + + case msg => + log.info(s"Unhandled message $msg from $sender") + } +} \ No newline at end of file diff --git a/common/src/main/scala/services/chat/ChatServiceMessage.scala b/common/src/main/scala/services/chat/ChatServiceMessage.scala new file mode 100644 index 00000000..7cb944a4 --- /dev/null +++ b/common/src/main/scala/services/chat/ChatServiceMessage.scala @@ -0,0 +1,4 @@ +// Copyright (c) 2017 PSForever +package services.chat + +final case class ChatServiceMessage(forChannel : String, actionMessage : ChatAction.Action) \ No newline at end of file diff --git a/common/src/main/scala/services/chat/ChatServiceResponse.scala b/common/src/main/scala/services/chat/ChatServiceResponse.scala new file mode 100644 index 00000000..bfdb0016 --- /dev/null +++ b/common/src/main/scala/services/chat/ChatServiceResponse.scala @@ -0,0 +1,18 @@ +// Copyright (c) 2017 PSForever +package services.chat + +import net.psforever.objects.zones.Zone +import net.psforever.packet.game.{ChatMsg, PlanetSideGUID} +import net.psforever.types.{PlanetSideEmpire, Vector3} +import services.GenericEventBusMsg + +final case class ChatServiceResponse(toChannel : String, + avatar_guid : PlanetSideGUID, + avatar_name : String, + cont : Zone = Zone.Nowhere, + avatar_pos : Vector3 = Vector3(0f,0f,0f), + avatar_faction : PlanetSideEmpire.Value = PlanetSideEmpire.NEUTRAL, + + target : Int, + replyMessage : ChatMsg + ) extends GenericEventBusMsg \ No newline at end of file diff --git a/pslogin/src/main/scala/CryptoSessionActor.scala b/pslogin/src/main/scala/CryptoSessionActor.scala index db3f9874..4aadc1ee 100644 --- a/pslogin/src/main/scala/CryptoSessionActor.scala +++ b/pslogin/src/main/scala/CryptoSessionActor.scala @@ -80,6 +80,7 @@ class CryptoSessionActor extends Actor with MDCContextAware { clientNonce = nonce serverNonce = Math.abs(random.nextInt()) sendResponse(PacketCoding.CreateControlPacket(ServerStart(nonce, serverNonce))) + log.trace(s"ClientStart($nonce), $serverNonce") context.become(CryptoExchange) case _ => diff --git a/pslogin/src/main/scala/PacketCodingActor.scala b/pslogin/src/main/scala/PacketCodingActor.scala index 96e91a9b..ad855a5d 100644 --- a/pslogin/src/main/scala/PacketCodingActor.scala +++ b/pslogin/src/main/scala/PacketCodingActor.scala @@ -55,7 +55,7 @@ class PacketCodingActor extends Actor with MDCContextAware { private var relatedABufferTimeout : Cancellable = DefaultCancellable.obj def AddSlottedPacketToLog(subslot: Int, packet : ByteVector): Unit = { - val log_limit = 100 // Number of SlottedMetaPackets to keep in history + val log_limit = 500 // Number of SlottedMetaPackets to keep in history if(slottedPacketLog.size > log_limit) { slottedPacketLog = slottedPacketLog.drop(slottedPacketLog.size - log_limit) } @@ -326,7 +326,7 @@ class PacketCodingActor extends Actor with MDCContextAware { case Successful(packet) => handlePacketContainer(packet) case Failure(ex) => - log.info(s"Failed to unmarshal $description: $ex") + log.info(s"Failed to unmarshal $description: $ex. Data : $data") } } @@ -372,7 +372,7 @@ class PacketCodingActor extends Actor with MDCContextAware { packets.foreach { UnmarshalInnerPacket(_, "the inner packet of a MultiPacketEx") } case RelatedA(slot, subslot) => - log.trace(s"Client indicated a packet is missing prior to slot: $slot subslot: $subslot") + log.trace(s"Client indicated a packet is missing prior to slot: $slot subslot: $subslot, session: ${sessionId}") relatedALog += subslot @@ -382,14 +382,16 @@ class PacketCodingActor extends Actor with MDCContextAware { relatedABufferTimeout = context.system.scheduler.scheduleOnce(100 milliseconds, self, PacketCodingActor.SubslotResend()) case RelatedB(slot, subslot) => - log.trace(s"result $slot: subslot $subslot accepted") + log.trace(s"result $slot: subslot $subslot accepted, session: ${sessionId}") // The client has indicated it's received up to a certain subslot, that means we can purge the log of any subslots prior to and including the confirmed subslot // Find where this subslot is stored in the packet log (if at all) and drop anything to the left of it, including itself - val pos = slottedPacketLog.keySet.toArray.indexOf(subslot) - if(pos != -1) { - slottedPacketLog = slottedPacketLog.drop(pos+1) - log.trace(s"Subslots left in log: ${slottedPacketLog.keySet.toString()}") + if(relatedABufferTimeout.isCancelled || relatedABufferTimeout == DefaultCancellable.obj) { + val pos = slottedPacketLog.keySet.toArray.indexOf(subslot) + if(pos != -1) { + slottedPacketLog = slottedPacketLog.drop(pos+1) + log.trace(s"Subslots left in log: ${slottedPacketLog.keySet.toString()}") + } } case _ => sendResponseRight(container) diff --git a/pslogin/src/main/scala/PsLogin.scala b/pslogin/src/main/scala/PsLogin.scala index cfc0c164..82585df9 100644 --- a/pslogin/src/main/scala/PsLogin.scala +++ b/pslogin/src/main/scala/PsLogin.scala @@ -20,6 +20,7 @@ import org.fusesource.jansi.Ansi._ import org.fusesource.jansi.Ansi.Color._ import services.ServiceManager import services.avatar._ +import services.chat.ChatService import services.galaxy.GalaxyService import services.local._ import services.teamwork.SquadService @@ -257,6 +258,7 @@ object PsLogin { serviceManager ! ServiceManager.Register(RandomPool(50).props(Props[TaskResolver]), "taskResolver") serviceManager ! ServiceManager.Register(Props[AvatarService], "avatar") serviceManager ! ServiceManager.Register(Props[LocalService], "local") + serviceManager ! ServiceManager.Register(Props[ChatService], "chat") serviceManager ! ServiceManager.Register(Props[VehicleService], "vehicle") serviceManager ! ServiceManager.Register(Props[GalaxyService], "galaxy") serviceManager ! ServiceManager.Register(Props[SquadService], "squad") diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index 82fc7ec8..b4237a44 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -51,6 +51,7 @@ import services.{RemoverActor, vehicle, _} import services.avatar.{AvatarAction, AvatarResponse, AvatarServiceMessage, AvatarServiceResponse} import services.galaxy.{GalaxyResponse, GalaxyServiceResponse} import services.local.{LocalAction, LocalResponse, LocalServiceMessage, LocalServiceResponse} +import services.chat._ import services.vehicle.support.TurretUpgrader import services.vehicle.{VehicleAction, VehicleResponse, VehicleServiceMessage, VehicleServiceResponse} import services.teamwork.{SquadAction => SquadServiceAction, SquadServiceMessage, SquadServiceResponse, SquadResponse, SquadService} @@ -77,6 +78,7 @@ class WorldSessionActor extends Actor with MDCContextAware { var rightRef : ActorRef = ActorRef.noSender var avatarService : ActorRef = ActorRef.noSender var localService : ActorRef = ActorRef.noSender + var chatService: ActorRef = ActorRef.noSender var vehicleService : ActorRef = ActorRef.noSender var galaxyService : ActorRef = ActorRef.noSender var squadService : ActorRef = ActorRef.noSender @@ -93,12 +95,23 @@ class WorldSessionActor extends Actor with MDCContextAware { var speed : Float = 1.0f var spectator : Boolean = false var admin : Boolean = false + var noSpawnPointHere : Boolean = false var usingMedicalTerminal : Option[PlanetSideGUID] = None var controlled : Option[Int] = None //keep track of avatar's ServerVehicleOverride state var traveler : Traveler = null var deadState : DeadState.Value = DeadState.Dead + var whenUsedLastAAMAX : Long = 0 + var whenUsedLastAIMAX : Long = 0 + var whenUsedLastAVMAX : Long = 0 + var whenUsedLastMAX : Array[Long] = Array.fill[Long](4)(0L) + var whenUsedLastMAXName : Array[String] = Array.fill[String](4)("") + var whenUsedLastItem : Array[Long] = Array.fill[Long](1020)(0L) + var whenUsedLastItemName : Array[String] = Array.fill[String](1020)("") var whenUsedLastKit : Long = 0 + var whenUsedLastSMKit : Long = 0 + var whenUsedLastSAKit : Long = 0 + var whenUsedLastSSKit : Long = 0 val projectiles : Array[Option[Projectile]] = Array.fill[Option[Projectile]](Projectile.RangeUID - Projectile.BaseUID)(None) var drawDeloyableIcon : PlanetSideGameObject with Deployable => Unit = RedrawDeployableIcons var updateSquad : () => Unit = NoSquadUpdates @@ -134,6 +147,9 @@ class WorldSessionActor extends Actor with MDCContextAware { var squadUpdateCounter : Int = 0 val queuedSquadActions : Seq[() => Unit] = Seq(SquadUpdates, NoSquadUpdates, NoSquadUpdates, NoSquadUpdates) + var timeDL : Long = 0 + var timeSurge : Long = 0 + var amsSpawnPoints : List[SpawnPoint] = Nil var clientKeepAlive : Cancellable = DefaultCancellable.obj var progressBarUpdate : Cancellable = DefaultCancellable.obj @@ -159,6 +175,7 @@ class WorldSessionActor extends Actor with MDCContextAware { respawnTimer.cancel PlayerActionsToCancel() localService ! Service.Leave() + chatService ! Service.Leave() vehicleService ! Service.Leave() avatarService ! Service.Leave() galaxyService ! Service.Leave() @@ -284,6 +301,7 @@ class WorldSessionActor extends Actor with MDCContextAware { context.become(Started) ServiceManager.serviceManager ! Lookup("avatar") ServiceManager.serviceManager ! Lookup("local") + ServiceManager.serviceManager ! Lookup("chat") ServiceManager.serviceManager ! Lookup("vehicle") ServiceManager.serviceManager ! Lookup("taskResolver") ServiceManager.serviceManager ! Lookup("cluster") @@ -302,6 +320,9 @@ class WorldSessionActor extends Actor with MDCContextAware { case ServiceManager.LookupResult("local", endpoint) => localService = endpoint log.info("ID: " + sessionId + " Got local service " + endpoint) + case ServiceManager.LookupResult("chat", endpoint) => + chatService = endpoint + log.info("ID: " + sessionId + " Got chat service " + endpoint) case ServiceManager.LookupResult("vehicle", endpoint) => vehicleService = endpoint log.info("ID: " + sessionId + " Got vehicle service " + endpoint) @@ -350,6 +371,9 @@ class WorldSessionActor extends Actor with MDCContextAware { case LocalServiceResponse(toChannel, guid, reply) => HandleLocalServiceResponse(toChannel, guid, reply) + case ChatServiceResponse(toChannel, guid, avatar_name, cont, avatar_pos, avatar_faction, target, reply) => + HandleChatServiceResponse(toChannel, guid, avatar_name, cont, avatar_pos, avatar_faction, target, reply) + case Mountable.MountMessages(tplayer, reply) => HandleMountMessages(tplayer, reply) @@ -751,11 +775,16 @@ class WorldSessionActor extends Actor with MDCContextAware { val popTR = poplist.count(_.faction == PlanetSideEmpire.TR) val popNC = poplist.count(_.faction == PlanetSideEmpire.NC) val popVS = poplist.count(_.faction == PlanetSideEmpire.VS) + // StopBundlingPackets() is called on ClientInitializationComplete StartBundlingPackets() + zone.Buildings.foreach({ case (id, building) => initBuilding(continentNumber, building.MapId, building) }) sendResponse(ZonePopulationUpdateMessage(continentNumber, 414, 138, popTR, 138, popNC, 138, popVS, 138, popBO)) - sendResponse(ContinentalLockUpdateMessage(continentNumber, PlanetSideEmpire.NEUTRAL)) + if (continentNumber == 11) sendResponse(ContinentalLockUpdateMessage(continentNumber, PlanetSideEmpire.NC)) // "The NC have captured the NC Sanctuary." + else if (continentNumber == 12) sendResponse(ContinentalLockUpdateMessage(continentNumber, PlanetSideEmpire.TR)) // "The TR have captured the TR Sanctuary." + else if (continentNumber == 13) sendResponse(ContinentalLockUpdateMessage(continentNumber, PlanetSideEmpire.VS)) // "The VS have captured the VS Sanctuary." + else sendResponse(ContinentalLockUpdateMessage(continentNumber, PlanetSideEmpire.NEUTRAL)) //CaptureFlagUpdateMessage() //VanuModuleUpdateMessage() //ModuleLimitsMessage() @@ -1372,6 +1401,11 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(PlanetsideAttributeMessage(guid, attribute_type, attribute_value)) } + case AvatarResponse.PlanetsideAttributeSelf(attribute_type, attribute_value) => + if (tplayer_guid == guid) { + sendResponse(PlanetsideAttributeMessage(guid, attribute_type, attribute_value)) + } + case AvatarResponse.PlayerState(msg, spectating, weaponInHand) => if(tplayer_guid != guid) { val now = System.currentTimeMillis() @@ -1571,6 +1605,7 @@ class WorldSessionActor extends Actor with MDCContextAware { log.trace(s"Clearing hack for ${target_guid}") // Reset hack state for all players sendResponse(HackMessage(0, target_guid, guid, 0, unk1, HackState.HackCleared, unk2)) + case LocalResponse.HackObject(target_guid, unk1, unk2) => sendResponse(HackMessage(0, target_guid, guid, 100, unk1, HackState.Hacked, unk2)) case LocalResponse.HackCaptureTerminal(target_guid, unk1, unk2, isResecured) => @@ -1638,6 +1673,78 @@ class WorldSessionActor extends Actor with MDCContextAware { } } + /** + * na + * @param toChannel na + * @param avatar_guid na + * @param target na + * @param reply na + */ + def HandleChatServiceResponse(toChannel : String, avatar_guid : PlanetSideGUID, avatar_name : String, cont : Zone, avatar_pos : Vector3, avatar_faction : PlanetSideEmpire.Value, target : Int, reply : ChatMsg) : Unit = { + val tplayer_guid = if(player.HasGUID) player.GUID + else PlanetSideGUID(0) + target match { + case 0 => // for other(s) user(s) + if (player.GUID != avatar_guid) { + reply.messageType match { + case ChatMessageType.CMT_TELL => + if (player.Name == reply.recipient) { + sendResponse(ChatMsg(reply.messageType, reply.wideContents, avatar_name, reply.contents, reply.note)) + } + case ChatMessageType.CMT_SILENCE => + val args = avatar_name.split(" ") + var silence_name : String = "" + var silence_time : Int = 5 + if (args.length == 1) { + silence_name = args(0) + } + else if (args.length == 2) { + silence_name = args(0) + silence_time = args(1).toInt + } + if (player.Name == args(0)) { + if(!player.silenced) { + sendResponse(ChatMsg(ChatMessageType.UNK_71, reply.wideContents, reply.recipient, "@silence_on", reply.note)) + player.silenced = true + context.system.scheduler.scheduleOnce(silence_time minutes, chatService, ChatServiceMessage("gm", ChatAction.GM(PlanetSideGUID(0), player.Name, ChatMsg(ChatMessageType.CMT_SILENCE, true, "", player.Name, None)))) + } + else { + sendResponse(ChatMsg(ChatMessageType.UNK_71, reply.wideContents, reply.recipient, "@silence_off", reply.note)) + player.silenced = false + } + } + case _ => + sendResponse(ChatMsg(reply.messageType, reply.wideContents, reply.recipient, reply.contents, reply.note)) + } + } + case 1 => // for player + if (player.Name == avatar_name) { + if ((reply.contents.length > 1 && (reply.contents.dropRight(reply.contents.length - 1) != "!" || reply.contents.drop(1).dropRight(reply.contents.length - 2) == "!")) || reply.contents.length == 1) { + sendResponse(ChatMsg(reply.messageType, reply.wideContents, reply.recipient, reply.contents, reply.note)) + } + } + case 2 => // both case + if ((reply.contents.length > 1 && (reply.contents.dropRight(reply.contents.length - 1) != "!" || reply.contents.drop(1).dropRight(reply.contents.length - 2) == "!")) || reply.contents.length == 1) { + reply.messageType match { + case ChatMessageType.CMT_OPEN => + if (Vector3.Distance(player.Position, avatar_pos) < 25 && player.Faction == avatar_faction && player.Continent == cont.Id) { + sendResponse(ChatMsg(reply.messageType, reply.wideContents, reply.recipient, reply.contents, reply.note)) + } + case ChatMessageType.CMT_SQUAD => + if (player.Faction == avatar_faction) { + sendResponse(ChatMsg(reply.messageType, reply.wideContents, reply.recipient, reply.contents, reply.note)) + } + case ChatMessageType.CMT_VOICE => + if (Vector3.Distance(player.Position, avatar_pos) < 25 && player.Continent == cont.Id) { + sendResponse(ChatMsg(reply.messageType, reply.wideContents, reply.recipient, reply.contents, reply.note)) + } + case _ => + sendResponse(ChatMsg(reply.messageType, reply.wideContents, reply.recipient, reply.contents, reply.note)) + } + } + } + } + /** * na * @param tplayer na @@ -1747,7 +1854,20 @@ class WorldSessionActor extends Actor with MDCContextAware { //TODO check exo-suit permissions val originalSuit = tplayer.ExoSuit val originalSubtype = Loadout.DetermineSubtype(tplayer) - if(originalSuit != exosuit || originalSubtype != subtype) { + + val lTime = System.currentTimeMillis + var changeArmor : Boolean = true + if (lTime - whenUsedLastMAX(subtype) < 300000) { + changeArmor = false + } + if (changeArmor && exosuit.id == 2) { + for (i <- 1 to 3) { + sendResponse(AvatarVehicleTimerMessage(tplayer.GUID, whenUsedLastMAXName(i), 300, true)) + whenUsedLastMAX(i) = lTime + } + } + + if(originalSuit != exosuit || originalSubtype != subtype && changeArmor) { sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, true)) //prepare lists of valid objects val beforeInventory = tplayer.Inventory.Clear() @@ -1919,15 +2039,15 @@ class WorldSessionActor extends Actor with MDCContextAware { case Terminal.InfantryLoadout(exosuit, subtype, holsters, inventory) => log.info(s"$tplayer wants to change equipment loadout to their option #${msg.unk1 + 1}") sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Loadout, true)) + //sanitize exo-suit for change + val originalSuit = player.ExoSuit + val originalSubtype = Loadout.DetermineSubtype(tplayer) //prepare lists of valid objects val beforeFreeHand = tplayer.FreeHand.Equipment val dropPred = DropPredicate(tplayer) val (dropHolsters, beforeHolsters) = clearHolsters(tplayer.Holsters().iterator).partition(dropPred) val (dropInventory, beforeInventory) = tplayer.Inventory.Clear().partition(dropPred) tplayer.FreeHand.Equipment = None //terminal and inventory will close, so prematurely dropping should be fine - //sanitize exo-suit for change - val originalSuit = player.ExoSuit - val originalSubtype = Loadout.DetermineSubtype(tplayer) val fallbackSuit = ExoSuitType.Standard val fallbackSubtype = 0 //a loadout with a prohibited exo-suit type will result in a fallback exo-suit type @@ -1942,7 +2062,18 @@ class WorldSessionActor extends Actor with MDCContextAware { case permissions => tplayer.Certifications.intersect(permissions.toSet).nonEmpty }) { - (exosuit, subtype) + val lTime = System.currentTimeMillis + if (lTime - whenUsedLastMAX(subtype) < 300000){ // PTS v3 hack + (originalSuit, subtype) + } else { + if (lTime - whenUsedLastMAX(subtype) > 300000 && subtype != 0) { + for (i <- 1 to 3) { + sendResponse(AvatarVehicleTimerMessage(tplayer.GUID, whenUsedLastMAXName(i), 300, true)) + whenUsedLastMAX(i) = lTime + } + } + (exosuit, subtype) + } } else { log.warn(s"$tplayer no longer has permission to wear the exo-suit type $exosuit; will wear $fallbackSuit instead") @@ -2221,34 +2352,43 @@ class WorldSessionActor extends Actor with MDCContextAware { case Terminal.BuyVehicle(vehicle, weapons, trunk) => continent.Map.TerminalToSpawnPad.get(msg.terminal_guid.guid) match { case Some(pad_guid) => - val toFaction = tplayer.Faction - val pad = continent.GUID(pad_guid).get.asInstanceOf[VehicleSpawnPad] - vehicle.Faction = toFaction - vehicle.Continent = continent.Id - vehicle.Position = pad.Position - vehicle.Orientation = pad.Orientation - //default loadout, weapons - val vWeapons = vehicle.Weapons - weapons.foreach(entry => { - val index = entry.start - vWeapons.get(index) match { - case Some(slot) => - entry.obj.Faction = toFaction - slot.Equipment = None - slot.Equipment = entry.obj - case None => - log.warn(s"applying default loadout to $vehicle on spawn, but can not find a mounted weapon @ $index") - } - }) - //default loadout, trunk - val vTrunk = vehicle.Trunk - vTrunk.Clear() - trunk.foreach(entry => { - entry.obj.Faction = toFaction - vTrunk += entry.start -> entry.obj - }) - taskResolver ! RegisterNewVehicle(vehicle, pad) - sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, true)) + val lTime = System.currentTimeMillis + if (lTime - whenUsedLastItem(vehicle.Definition.ObjectId) > 300000) { + whenUsedLastItem(vehicle.Definition.ObjectId) = lTime + whenUsedLastItemName(vehicle.Definition.ObjectId) = msg.item_name + sendResponse(AvatarVehicleTimerMessage(tplayer.GUID, msg.item_name, 300, true)) + val toFaction = tplayer.Faction + val pad = continent.GUID(pad_guid).get.asInstanceOf[VehicleSpawnPad] + vehicle.Faction = toFaction + vehicle.Continent = continent.Id + vehicle.Position = pad.Position + vehicle.Orientation = pad.Orientation + //default loadout, weapons + val vWeapons = vehicle.Weapons + weapons.foreach(entry => { + val index = entry.start + vWeapons.get(index) match { + case Some(slot) => + entry.obj.Faction = toFaction + slot.Equipment = None + slot.Equipment = entry.obj + case None => + log.warn(s"applying default loadout to $vehicle on spawn, but can not find a mounted weapon @ $index") + } + }) + //default loadout, trunk + val vTrunk = vehicle.Trunk + vTrunk.Clear() + trunk.foreach(entry => { + entry.obj.Faction = toFaction + vTrunk += entry.start -> entry.obj + }) + taskResolver ! RegisterNewVehicle(vehicle, pad) + sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, true)) + } + else { + sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, false)) + } case None => log.error(s"$tplayer wanted to spawn a vehicle, but there was no spawn pad associated with terminal ${msg.terminal_guid} to accept it") @@ -3090,7 +3230,7 @@ class WorldSessionActor extends Actor with MDCContextAware { case (index, loadout : VehicleLoadout) => sendResponse(FavoritesMessage(LoadoutType.Vehicle, guid, index - 10, loadout.label)) } - sendResponse(SetChatFilterMessage(ChatChannel.Local, false, ChatChannel.values.toList)) //TODO will not always be "on" like this + sendResponse(SetChatFilterMessage(ChatChannel.Broadcast, false, ChatChannel.values.toList)) //TODO will not always be "on" like this deadState = DeadState.Alive sendResponse(AvatarDeadStateMessage(DeadState.Alive, 0, 0, tplayer.Position, player.Faction, true)) //looking for squad (members) @@ -3125,6 +3265,7 @@ class WorldSessionActor extends Actor with MDCContextAware { }) StopBundlingPackets() drawDeloyableIcon = DontRedrawIcons + //assert or transfer vehicle ownership continent.GUID(player.VehicleOwned) match { case Some(vehicle : Vehicle) if vehicle.OwnerName.contains(tplayer.Name) => @@ -3133,6 +3274,7 @@ class WorldSessionActor extends Actor with MDCContextAware { case _ => player.VehicleOwned = None } + //if driver of a vehicle, summon any passengers and cargo vehicles left behind on previous continent GetVehicleAndSeat() match { case (Some(vehicle), Some(0)) => @@ -3146,6 +3288,9 @@ class WorldSessionActor extends Actor with MDCContextAware { interstellarFerryTopLevelGUID = None case _ => ; } + if (noSpawnPointHere) { + RequestSanctuaryZoneSpawn(player, continent.Number) + } } /** @@ -3649,10 +3794,62 @@ class WorldSessionActor extends Actor with MDCContextAware { } } vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.UpdateAmsSpawnPoint(continent)) + + chatService ! Service.Join("local") + chatService ! Service.Join("squad") + chatService ! Service.Join("voice") + chatService ! Service.Join("tell") + chatService ! Service.Join("broadcast") + chatService ! Service.Join("note") + chatService ! Service.Join("gm") + self ! SetCurrentAvatar(player) case msg @ PlayerStateMessageUpstream(avatar_guid, pos, vel, yaw, pitch, yaw_upper, seq_time, unk3, is_crouching, is_jumping, unk4, is_cloaking, unk5, unk6) => if(deadState == DeadState.Alive) { + if (timeDL != 0) { + if (System.currentTimeMillis() - timeDL > 500) { + player.Stamina = player.Stamina - 1 + avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.PlanetsideAttributeSelf(player.GUID, 2, player.Stamina)) + timeDL = System.currentTimeMillis() + } + } + if (timeSurge != 0) { + if (System.currentTimeMillis() - timeSurge > 500 && player.ExoSuit == ExoSuitType.Agile) { + player.Stamina = player.Stamina - 1 + avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.PlanetsideAttributeSelf(player.GUID, 2, player.Stamina)) + timeSurge = System.currentTimeMillis() + } + else if (System.currentTimeMillis() - timeSurge > 333 && player.ExoSuit == ExoSuitType.Reinforced) { + player.Stamina = player.Stamina - 1 + avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.PlanetsideAttributeSelf(player.GUID, 2, player.Stamina)) + timeSurge = System.currentTimeMillis() + } + else if (System.currentTimeMillis() - timeSurge > 1000 && ( player.ExoSuit == ExoSuitType.Infiltration || player.ExoSuit == ExoSuitType.Standard )) { + player.Stamina = player.Stamina - 1 + avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.PlanetsideAttributeSelf(player.GUID, 2, player.Stamina)) + timeSurge = System.currentTimeMillis() + } + } + if (player.Stamina == 0) { + if (avatar.Implants(0).Active) { + avatar.Implants(0).Active = false + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(player.GUID, 28, avatar.Implant(0).id * 2)) + sendResponse(AvatarImplantMessage(PlanetSideGUID(player.GUID.guid),ImplantAction.Activation,0,0)) + timeDL = 0 + } + if (avatar.Implants(1).Active) { + avatar.Implants(1).Active = false + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(player.GUID, 28, avatar.Implant(1).id * 2)) + sendResponse(AvatarImplantMessage(PlanetSideGUID(player.GUID.guid),ImplantAction.Activation,1,0)) + timeSurge = 0 + } + } + if (vel.isEmpty && player.Stamina != player.MaxStamina) { + player.Stamina = player.Stamina + 1 + avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.PlanetsideAttributeSelf(player.GUID, 2, player.Stamina)) + } + player.Position = pos player.Velocity = vel player.Orientation = Vector3(player.Orientation.x, pitch, yaw) @@ -3697,6 +3894,10 @@ class WorldSessionActor extends Actor with MDCContextAware { avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlayerState(avatar_guid, msg, spectator, wepInHand)) updateSquad() } + else { + timeDL = 0 + timeSurge = 0 + } case msg @ ChildObjectStateMessage(object_guid, pitch, yaw) => //the majority of the following check retrieves information to determine if we are in control of the child @@ -3885,6 +4086,7 @@ class WorldSessionActor extends Actor with MDCContextAware { log.info("Chat: " + msg) } else { + log.info("Chat: " + msg) makeReply = false } if(messagetype == ChatMessageType.CMT_SUICIDE) { @@ -3906,20 +4108,32 @@ class WorldSessionActor extends Actor with MDCContextAware { if(messagetype == ChatMessageType.CMT_VOICE) { sendResponse(ChatMsg(ChatMessageType.CMT_VOICE, false, player.Name, contents, None)) } - // TODO: handle this appropriately - if(messagetype == ChatMessageType.CMT_QUIT) { + + if(messagetype == ChatMessageType.CMT_QUIT) { // TODO: handle this appropriately sendResponse(DropCryptoSession()) sendResponse(DropSession(sessionId, "user quit")) } //dev hack; consider bang-commands to complement slash-commands in future if(trimContents.equals("!loc")) { + makeReply = true echoContents = s"zone=${continent.Id} pos=${player.Position.x},${player.Position.y},${player.Position.z}; ori=${player.Orientation.x},${player.Orientation.y},${player.Orientation.z}" log.info(echoContents) } + else if (trimContents.equals("!list") && admin) { + sendResponse(ChatMsg(ChatMessageType.CMT_TELL, has_wide_contents, "Server", + "\\#8ID / Name (faction) Cont-PosX/PosY/PosZ", note_contents)) + continent.LivePlayers.filterNot(_.GUID == player.GUID).sortBy(_.Name).foreach(char => { + sendResponse(ChatMsg(ChatMessageType.CMT_TELL, has_wide_contents, "Server", + "GUID / Name: " + char.GUID.guid + " / " + char.Name + " (" + char.Faction + ") " + + char.Continent + "-" + char.Position.x.toInt + "/" + char.Position.y.toInt + "/" + char.Position.z.toInt, note_contents)) + }) + } else if(trimContents.equals("!ams")) { makeReply = false - if(deadState == DeadState.Release) { //player is on deployment screen (either dead or deconstructed) - cluster ! Zone.Lattice.RequestSpawnPoint(continent.Number, player, 2) + if(player.isBackpack) { //player is on deployment screen (either dead or deconstructed) + if(deadState == DeadState.Release) { //player is on deployment screen (either dead or deconstructed) + cluster ! Zone.Lattice.RequestSpawnPoint(continent.Number, player, 2) + } } } // TODO: Depending on messagetype, may need to prepend sender's name to contents with proper spacing @@ -3928,6 +4142,43 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(ChatMsg(messagetype, has_wide_contents, recipient, echoContents, note_contents)) } + if (messagetype == ChatMessageType.CMT_OPEN && !player.silenced) { + chatService ! ChatServiceMessage("local", ChatAction.Local(player.GUID, player.Name, continent, player.Position, player.Faction, msg)) + } + else if (messagetype == ChatMessageType.CMT_VOICE) { + chatService ! ChatServiceMessage("voice", ChatAction.Voice(player.GUID, player.Name, continent, player.Position, player.Faction, msg)) + } + else if (messagetype == ChatMessageType.CMT_TELL && !player.silenced) { + chatService ! ChatServiceMessage("tell", ChatAction.Tell(player.GUID, player.Name, msg)) + } + else if (messagetype == ChatMessageType.CMT_BROADCAST && !player.silenced) { + chatService ! ChatServiceMessage("broadcast", ChatAction.Broadcast(player.GUID, player.Name, continent, player.Position, player.Faction, msg)) + } + else if (messagetype == ChatMessageType.CMT_NOTE) { + chatService ! ChatServiceMessage("note", ChatAction.Note(player.GUID, player.Name, msg)) + } + else if (messagetype == ChatMessageType.CMT_SILENCE && admin) { + chatService ! ChatServiceMessage("gm", ChatAction.GM(player.GUID, player.Name, msg)) + } + else if (messagetype == ChatMessageType.CMT_SQUAD && !player.silenced) { + chatService ! ChatServiceMessage("squad", ChatAction.Squad(player.GUID, player.Name, continent, player.Position, player.Faction, msg)) + } + else if (messagetype == ChatMessageType.CMT_WHO || messagetype == ChatMessageType.CMT_WHO_CSR || messagetype == ChatMessageType.CMT_WHO_CR || + messagetype == ChatMessageType.CMT_WHO_PLATOONLEADERS || messagetype == ChatMessageType.CMT_WHO_SQUADLEADERS || messagetype == ChatMessageType.CMT_WHO_TEAMS) { + val poplist = continent.Players + val popTR = poplist.count(_.faction == PlanetSideEmpire.TR) + val popNC = poplist.count(_.faction == PlanetSideEmpire.NC) + val popVS = poplist.count(_.faction == PlanetSideEmpire.VS) + val contName = continent.Map.Name + + StartBundlingPackets() + sendResponse(ChatMsg(ChatMessageType.CMT_WHO, true, "", "That command doesn't work for now, but : ", None)) + sendResponse(ChatMsg(ChatMessageType.CMT_WHO, true, "", "NC online : " + popNC + " on " + contName, None)) + sendResponse(ChatMsg(ChatMessageType.CMT_WHO, true, "", "TR online : " + popTR + " on " + contName, None)) + sendResponse(ChatMsg(ChatMessageType.CMT_WHO, true, "", "VS online : " + popVS + " on " + contName, None)) + StopBundlingPackets() + } + case msg @ VoiceHostRequest(unk, PlanetSideGUID(player_guid), data) => log.info("Player "+player_guid+" requested in-game voice chat.") sendResponse(VoiceHostKill()) @@ -4171,6 +4422,9 @@ class WorldSessionActor extends Actor with MDCContextAware { case msg @ AvatarJumpMessage(state) => //log.info("AvatarJump: " + msg) + player.Stamina = player.Stamina - 10 + if(player.Stamina < 0) player.Stamina = 0 + avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.PlanetsideAttributeSelf(player.GUID, 2, player.Stamina)) case msg @ ZipLineMessage(player_guid,origin_side,action,id,pos) => log.info("ZipLineMessage: " + msg) @@ -4369,8 +4623,26 @@ class WorldSessionActor extends Actor with MDCContextAware { log.warn(s"LootItem: can not find where to put $item_guid") } - case msg @ AvatarImplantMessage(_, _, _, _) => //(player_guid, unk1, unk2, implant) => + case msg @ AvatarImplantMessage(_, action, slot, status) => //(player_guid, unk1, unk2, implant) => log.info("AvatarImplantMessage: " + msg) + if (avatar.Implants(slot).Initialized) { + if(action == ImplantAction.Activation && status == 1) { // active + avatar.Implants(slot).Active = true + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(player.GUID, 28, avatar.Implant(slot).id * 2 + 1)) + if (avatar.Implant(slot).id == 3) { + timeDL = System.currentTimeMillis() + player.Stamina = player.Stamina - 3 + avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.PlanetsideAttributeSelf(player.GUID, 2, player.Stamina)) + } + if (avatar.Implant(slot).id == 9) timeSurge = System.currentTimeMillis() + } else if(action == ImplantAction.Activation && status == 0) { //desactive + avatar.Implants(slot).Active = false + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(player.GUID, 28, avatar.Implant(slot).id * 2)) + if (avatar.Implant(slot).id == 3) timeDL = 0 + if (avatar.Implant(slot).id == 9) timeSurge = 0 + } + sendResponse(AvatarImplantMessage(PlanetSideGUID(player.GUID.guid),action,slot,status)) + } case msg @ UseItemMessage(avatar_guid, item_used_guid, object_guid, unk2, unk3, unk4, unk5, unk6, unk7, unk8, itemType) => log.info("UseItem: " + msg) @@ -4389,6 +4661,7 @@ class WorldSessionActor extends Actor with MDCContextAware { // A base is hacked // The lock is hacked // The player is on the inside of the door, determined by the lock orientation + lock.HackedBy.isDefined || lock.Owner.asInstanceOf[Building].CaptureConsoleIsHacked || lock.Faction == PlanetSideEmpire.NEUTRAL || playerIsOnInside case None => !door.isOpen // If there's no linked IFF lock just open the door if it's closed. })) { @@ -4475,6 +4748,76 @@ class WorldSessionActor extends Actor with MDCContextAware { } } } + else if(kit.Definition == GlobalDefinitions.super_medkit) { + if(player.Health == player.MaxHealth) { + sendResponse(ChatMsg(ChatMessageType.UNK_225, false, "", "@HealComplete", None)) + } + else if(System.currentTimeMillis - whenUsedLastSMKit < 1200000) { + sendResponse(ChatMsg(ChatMessageType.UNK_225, false, "", s"@TimeUntilNextUse^${1200 - (System.currentTimeMillis - whenUsedLastSMKit) / 1000}~", None)) + } + else { + player.Find(kit) match { + case Some(index) => + whenUsedLastSMKit = System.currentTimeMillis + player.Slot(index).Equipment = None //remove from slot immediately; must exist on client for next packet + sendResponse(UseItemMessage(avatar_guid, item_used_guid, object_guid, 0, unk3, unk4, unk5, unk6, unk7, unk8, itemType)) + sendResponse(ObjectDeleteMessage(kit.GUID, 0)) + taskResolver ! GUIDTask.UnregisterEquipment(kit)(continent.GUID) + player.History(HealFromKit(PlayerSource(player), 100, kit.Definition)) + player.Health = player.Health + 100 + sendResponse(PlanetsideAttributeMessage(avatar_guid, 0, player.Health)) + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(avatar_guid, 0, player.Health)) + case None => + log.error(s"UseItem: anticipated a $kit, but can't find it") + } + } + } + else if(kit.Definition == GlobalDefinitions.super_armorkit) { + if(player.Armor == player.MaxArmor) { + sendResponse(ChatMsg(ChatMessageType.UNK_225, false, "", "Armor at maximum - No repairing required.", None)) + } + else if(System.currentTimeMillis - whenUsedLastSAKit < 1200000) { + sendResponse(ChatMsg(ChatMessageType.UNK_225, false, "", s"@TimeUntilNextUse^${1200 - (System.currentTimeMillis - whenUsedLastSAKit) / 1000}~", None)) + } + else { + player.Find(kit) match { + case Some(index) => + whenUsedLastSAKit = System.currentTimeMillis + player.Slot(index).Equipment = None //remove from slot immediately; must exist on client for next packet + sendResponse(UseItemMessage(avatar_guid, item_used_guid, object_guid, 0, unk3, unk4, unk5, unk6, unk7, unk8, itemType)) + sendResponse(ObjectDeleteMessage(kit.GUID, 0)) + taskResolver ! GUIDTask.UnregisterEquipment(kit)(continent.GUID) + player.History(RepairFromKit(PlayerSource(player), 200, kit.Definition)) + player.Armor = player.Armor + 200 + sendResponse(PlanetsideAttributeMessage(avatar_guid, 4, player.Armor)) + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(avatar_guid, 4, player.Armor)) + case None => + log.error(s"UseItem: anticipated a $kit, but can't find it") + } + } + } + else if(kit.Definition == GlobalDefinitions.super_staminakit) { + if(player.Stamina == player.MaxStamina) { + sendResponse(ChatMsg(ChatMessageType.UNK_225, false, "", "Stamina at maximum - No recharge required.", None)) + } + else if(System.currentTimeMillis - whenUsedLastSSKit < 1200000) { + sendResponse(ChatMsg(ChatMessageType.UNK_225, false, "", s"@TimeUntilNextUse^${300 - (System.currentTimeMillis - whenUsedLastSSKit) / 1200}~", None)) + } + else { + player.Find(kit) match { + case Some(index) => + whenUsedLastSSKit = System.currentTimeMillis + player.Slot(index).Equipment = None //remove from slot immediately; must exist on client for next packet + sendResponse(UseItemMessage(avatar_guid, item_used_guid, object_guid, 0, unk3, unk4, unk5, unk6, unk7, unk8, itemType)) + sendResponse(ObjectDeleteMessage(kit.GUID, 0)) + taskResolver ! GUIDTask.UnregisterEquipment(kit)(continent.GUID) + player.Stamina = player.Stamina + 100 + sendResponse(PlanetsideAttributeMessage(avatar_guid, 2, player.Stamina)) + case None => + log.error(s"UseItem: anticipated a $kit, but can't find it") + } + } + } else { log.warn(s"UseItem: $kit behavior not supported") } @@ -4488,6 +4831,93 @@ class WorldSessionActor extends Actor with MDCContextAware { log.warn(s"UseItem: anticipated a Kit $item_used_guid, but can't find it") } } + else if (itemType == 121 && unk3) { + FindWeapon match { + case Some(tool: Tool) => + if (tool.Definition.ObjectId == 132) { + // TODO : bank ? + continent.GUID(object_guid) match { + case Some(tplayer: Player) => + if (player.GUID != tplayer.GUID && Vector3.Distance(player.Position, tplayer.Position) < 5 && player.Faction == tplayer.Faction && player.Velocity.isEmpty) { + if (tplayer.MaxArmor - tplayer.Armor <= 15) { + tplayer.Armor = tplayer.MaxArmor + // sendResponse(QuantityUpdateMessage(PlanetSideGUID(8214),ammo_quantity_left)) + val RepairPercent: Int = tplayer.Armor * 100 / tplayer.MaxArmor + sendResponse(RepairMessage(object_guid, RepairPercent)) + avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.PlanetsideAttributeSelf(tplayer.GUID, 4, tplayer.Armor)) + avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.PlanetsideAttribute(tplayer.GUID, 4, tplayer.Armor)) + } + if (tplayer.MaxArmor - tplayer.Armor > 15) { + tplayer.Armor += 15 + // sendResponse(QuantityUpdateMessage(PlanetSideGUID(8214),ammo_quantity_left)) + val RepairPercent: Int = tplayer.Armor * 100 / tplayer.MaxArmor + sendResponse(RepairMessage(object_guid, RepairPercent)) + avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.PlanetsideAttributeSelf(tplayer.GUID, 4, tplayer.Armor)) + avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.PlanetsideAttribute(tplayer.GUID, 4, tplayer.Armor)) + } + } else if (player.GUID == object_guid && player.Velocity.isEmpty) { + if (player.MaxArmor - player.Armor <= 15) { + player.Armor = player.MaxArmor + // sendResponse(QuantityUpdateMessage(PlanetSideGUID(8214),ammo_quantity_left)) +// sendResponse(RepairMessage(object_guid, player.Armor)) // Todo is that needed ? + sendResponse(PlanetsideAttributeMessage(player.GUID, 4, player.Armor)) + avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.PlanetsideAttribute(player.GUID, 4, player.Armor)) + } + if (player.MaxArmor - player.Armor > 15) { + player.Armor += 15 + // sendResponse(QuantityUpdateMessage(PlanetSideGUID(8214),ammo_quantity_left)) +// sendResponse(RepairMessage(object_guid, player.Armor)) // Todo is that needed ? + sendResponse(PlanetsideAttributeMessage(player.GUID, 4, player.Armor)) + avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.PlanetsideAttribute(player.GUID, 4, player.Armor)) + } + } + case _ => ; + } + } else if (tool.Definition.ObjectId == 531) { + // TODO : med app ? + continent.GUID(object_guid) match { + case Some(tplayer: Player) => + if (player.GUID != tplayer.GUID && Vector3.Distance(player.Position, tplayer.Position) < 5 && player.Faction == tplayer.Faction && player.Velocity.isEmpty) { + if (tplayer.MaxHealth - tplayer.Health <= 10) { + tplayer.Health = tplayer.MaxHealth + // sendResponse(QuantityUpdateMessage(PlanetSideGUID(8214),ammo_quantity_left)) + val RepairPercent: Int = tplayer.Health * 100 / tplayer.MaxHealth + sendResponse(RepairMessage(object_guid, RepairPercent)) + avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.PlanetsideAttributeSelf(tplayer.GUID, 0, tplayer.Health)) + avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.PlanetsideAttribute(tplayer.GUID, 0, tplayer.Health)) + } + if (tplayer.MaxHealth - tplayer.Health > 10) { + tplayer.Health += 10 + // sendResponse(QuantityUpdateMessage(PlanetSideGUID(8214),ammo_quantity_left)) + val RepairPercent: Int = tplayer.Health * 100 / tplayer.MaxHealth + sendResponse(RepairMessage(object_guid, RepairPercent)) + avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.PlanetsideAttributeSelf(tplayer.GUID, 0, tplayer.Health)) + avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.PlanetsideAttribute(tplayer.GUID, 0, tplayer.Health)) + } + } + case _ => ; + } + if (player.GUID == object_guid && player.Velocity.isEmpty) { + if (player.MaxHealth - player.Health <= 10) { + player.Health = player.MaxHealth + // sendResponse(QuantityUpdateMessage(PlanetSideGUID(8214),ammo_quantity_left)) +// sendResponse(RepairMessage(object_guid, player.Health)) // Todo is that needed ? + sendResponse(PlanetsideAttributeMessage(player.GUID, 0, player.Health)) + avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.PlanetsideAttribute(player.GUID, 0, player.Health)) + } + if (player.MaxHealth - player.Health > 10) { + player.Health += 10 + // sendResponse(QuantityUpdateMessage(PlanetSideGUID(8214),ammo_quantity_left)) +// sendResponse(RepairMessage(object_guid, player.Health)) // Todo is that needed ? + sendResponse(PlanetsideAttributeMessage(player.GUID, 0, player.Health)) + avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.PlanetsideAttribute(player.GUID, 0, player.Health)) + } + } + + } + case None => ; + } + } case Some(locker : Locker) => if(locker.Faction != player.Faction && locker.HackedBy.isEmpty) { @@ -4568,6 +4998,12 @@ class WorldSessionActor extends Actor with MDCContextAware { } else if(ammo == Ammo.armor_canister && obj.Health < obj.MaxHealth) { //repair turret + obj.Health += 48 + if (obj.Health > obj.MaxHealth) obj.Health = obj.MaxHealth + // sendResponse(QuantityUpdateMessage(PlanetSideGUID(8214),ammo_quantity_left)) + val RepairPercent: Int = obj.Health * 100 / obj.MaxHealth + sendResponse(RepairMessage(object_guid, RepairPercent)) + avatarService ! AvatarServiceMessage(obj.Continent, AvatarAction.PlanetsideAttribute(obj.GUID, 0, obj.Health)) } } else if(tool.Definition == GlobalDefinitions.trek) { @@ -4603,6 +5039,15 @@ class WorldSessionActor extends Actor with MDCContextAware { equipment.get.Definition match { case GlobalDefinitions.nano_dispenser => //TODO repairing behavior + if (player.Velocity.isEmpty && Vector3.Distance(player.Position, obj.Position) < 5) { + if (obj.Health < obj.MaxHealth) { + obj.Health += 48 + // sendResponse(QuantityUpdateMessage(PlanetSideGUID(8214),ammo_quantity_left)) + val RepairPercent: Int = obj.Health * 100 / obj.MaxHealth + sendResponse(RepairMessage(object_guid, RepairPercent)) + avatarService ! AvatarServiceMessage(obj.Continent, AvatarAction.PlanetsideAttribute(obj.GUID, 0, obj.Health)) + } + } case _ => ; } @@ -4943,6 +5388,11 @@ class WorldSessionActor extends Actor with MDCContextAware { EmptyMagazine(weapon_guid, tool) } else { //shooting + if (tool.FireModeIndex == 1 && (tool.Definition.Name == "anniversary_guna" || tool.Definition.Name == "anniversary_gun" || tool.Definition.Name == "anniversary_gunb")) { + player.Stamina = 0 + avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.PlanetsideAttributeSelf(player.GUID, 2, player.Stamina)) + } + prefire = shooting.orElse(Some(weapon_guid)) tool.Discharge val projectileIndex = projectile_guid.guid - Projectile.BaseUID @@ -5051,8 +5501,11 @@ class WorldSessionActor extends Actor with MDCContextAware { })) => cluster ! Zone.Lattice.RequestSpecificSpawnPoint(dest_continent_guid.guid, player, dest_building_guid) - case _ => - RequestSanctuaryZoneSpawn(player, continent.Number) + case Some(wg : WarpGate) if(!wg.Active) => + log.info(s"WarpgateRequest: inactive WarpGate") + + case _ => + RequestSanctuaryZoneSpawn(player, continent.Number) } } else { @@ -5218,7 +5671,7 @@ class WorldSessionActor extends Actor with MDCContextAware { self ! DismountVehicleCargoMsg(player.GUID, vehicle.GUID, true, false, false) } case None => ; // No vehicle in cargo - } + } case None => ; // Not a cargo mounting point } @@ -6107,7 +6560,7 @@ class WorldSessionActor extends Actor with MDCContextAware { def UnAccessContents(vehicle : Vehicle) : Unit = { vehicleService ! Service.Leave(Some(s"${vehicle.Actor}")) vehicle.Trunk.Items.foreach(entry =>{ - sendResponse(ObjectDeleteMessage(entry.obj.GUID, 0)) + sendResponse(ObjectDeleteMessage(entry.obj.GUID, 0)) }) } @@ -6989,8 +7442,8 @@ class WorldSessionActor extends Actor with MDCContextAware { ) = building.Info sendResponse( BuildingInfoUpdateMessage( - continentNumber, - buildingNumber, + building.Zone.Number, + building.MapId, ntuLevel, isHacked, empireHack, hackTimeRemaining, controllingEmpire, unk1, unk1x, @@ -7020,8 +7473,8 @@ class WorldSessionActor extends Actor with MDCContextAware { case wg : WarpGate => sendResponse( BuildingInfoUpdateMessage( - continentNumber, - buildingNumber, + building.Zone.Number, + building.MapId, ntu_level = 0, is_hacked = false, empire_hack = PlanetSideEmpire.NEUTRAL, @@ -7320,6 +7773,19 @@ class WorldSessionActor extends Actor with MDCContextAware { continent.Population ! Zone.Population.Spawn(avatar, player) //cautious redundancy deadState = DeadState.Alive + + val lTime = System.currentTimeMillis + for (i <- 0 to whenUsedLastItem.length-1) { + if (lTime - whenUsedLastItem(i) < 300000) { + sendResponse(AvatarVehicleTimerMessage(player.GUID, whenUsedLastItemName(i), 300 - ((lTime - whenUsedLastItem(i)) / 1000 toInt), true)) + } + } + for (i <- 1 to 3) { + if (lTime - whenUsedLastMAX(i) < 300000) { + sendResponse(AvatarVehicleTimerMessage(player.GUID, whenUsedLastMAXName(i), 300 - ((lTime - whenUsedLastMAX(i)) / 1000 toInt), true)) + } + } + } /** @@ -7759,7 +8225,7 @@ class WorldSessionActor extends Actor with MDCContextAware { * @param flight whether the vehicle is ascending or not, if the vehicle is an applicable type */ def ServerVehicleOverride(vehicle : Vehicle, speed : Int = 0, flight : Int = 0) : Unit = { - controlled = Some(speed) + controlled = Some(speed) sendResponse(ServerVehicleOverrideMsg(true, true, false, false, flight, 0, speed, Some(0))) } @@ -8083,7 +8549,7 @@ class WorldSessionActor extends Actor with MDCContextAware { * Additional effort is exerted to ensure that the requirements for the given ammunition are satisfied. * If no satisfactory combination is achieved, the original state will be restored. * @param obj the `ConstructionItem` object - * @param originalModeIndex the starting point ammunition type mode index + * @param originalAmmoIndex the starting point ammunition type mode index */ def PerformConstructionItemAmmoChange(obj : ConstructionItem, originalAmmoIndex : Int) : Unit = { val certifications = player.Certifications @@ -9029,8 +9495,8 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(PlayerStateShiftMessage(ShiftState(0, dest.Position, player.Orientation.z))) UseRouterTelepadEffect(pguid, sguid, dguid) StopBundlingPackets() -// vehicleService ! VehicleServiceMessage.Decon(RemoverActor.ClearSpecific(List(router), continent)) -// vehicleService ! VehicleServiceMessage.Decon(RemoverActor.AddTask(router, continent, router.Definition.DeconstructionTime)) + // vehicleService ! VehicleServiceMessage.Decon(RemoverActor.ClearSpecific(List(router), continent)) + // vehicleService ! VehicleServiceMessage.Decon(RemoverActor.AddTask(router, continent, router.Definition.DeconstructionTime)) localService ! LocalServiceMessage(continent.Id, LocalAction.RouterTelepadTransport(pguid, pguid, sguid, dguid)) } else { diff --git a/pslogin/src/main/scala/csr/CSRZoneImpl.scala b/pslogin/src/main/scala/csr/CSRZoneImpl.scala index 9fc33c3e..a509aefd 100644 --- a/pslogin/src/main/scala/csr/CSRZoneImpl.scala +++ b/pslogin/src/main/scala/csr/CSRZoneImpl.scala @@ -337,7 +337,7 @@ object CSRZoneImpl { "anu" -> Vector3(3479, 2556, 56), "bel" -> Vector3(3665, 4626, 58), "caer" -> Vector3(4570, 2601, 56), - "dagd" -> Vector3(5825, 4449, 55), + "dagda" -> Vector3(5825, 4449, 55), "eadon" -> Vector3(2725, 2853, 53), "gwydion" -> Vector3(5566, 3739, 61), "lugh" -> Vector3(6083, 5069, 72),