From b292739b5468cb780020ce083f7ff0709b376f92 Mon Sep 17 00:00:00 2001 From: FateJH Date: Fri, 5 Jan 2018 21:04:55 -0500 Subject: [PATCH 1/7] synchronized weapon fire, weapon dry fire, reload cycle, and reload of ammunition from inventory contents --- .../src/main/scala/WorldSessionActor.scala | 226 ++++++++++++++++-- .../scala/services/avatar/AvatarAction.scala | 5 +- .../services/avatar/AvatarResponse.scala | 5 +- .../scala/services/avatar/AvatarService.scala | 16 +- .../services/vehicle/VehicleAction.scala | 3 +- .../services/vehicle/VehicleResponse.scala | 3 +- .../services/vehicle/VehicleService.scala | 6 + .../main/test/scala/AvatarServiceTest.scala | 4 +- 8 files changed, 237 insertions(+), 31 deletions(-) diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index 9beb218a..8d1566fc 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -23,11 +23,11 @@ import net.psforever.objects.serverobject.pad.VehicleSpawnPad import net.psforever.objects.serverobject.terminals.Terminal import net.psforever.objects.vehicles.{AccessPermissionGroup, VehicleLockState} import net.psforever.objects.zones.{InterstellarCluster, Zone} -import net.psforever.packet.game.objectcreate.{DetailedCharacterData, _} +import net.psforever.packet.game.objectcreate._ import net.psforever.types._ import services._ -import services.avatar._ -import services.local._ +import services.avatar.{AvatarAction, AvatarResponse, AvatarServiceMessage, AvatarServiceResponse} +import services.local.{LocalAction, LocalResponse, LocalServiceMessage, LocalServiceResponse} import services.vehicle.{VehicleAction, VehicleResponse, VehicleServiceMessage, VehicleServiceResponse} import scala.annotation.tailrec @@ -47,6 +47,7 @@ class WorldSessionActor extends Actor with MDCContextAware { var galaxy : ActorRef = Actor.noSender var continent : Zone = null var progressBarValue : Option[Float] = None + var shooting : Option[PlanetSideGUID] = None var clientKeepAlive : Cancellable = DefaultCancellable.obj var progressBarUpdate : Cancellable = DefaultCancellable.obj @@ -145,6 +146,16 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(PacketCoding.CreateGamePacket(0, ArmorChangedMessage(guid, suit, subtype))) } + case AvatarResponse.ChangeFireState_Start(weapon_guid) => + if(player.GUID != guid) { + sendResponse(PacketCoding.CreateGamePacket(0, ChangeFireStateMessage_Start(weapon_guid))) + } + + case AvatarResponse.ChangeFireState_Stop(weapon_guid) => + if(player.GUID != guid) { + sendResponse(PacketCoding.CreateGamePacket(0, ChangeFireStateMessage_Stop(weapon_guid))) + } + case AvatarResponse.ConcealPlayer() => if(player.GUID != guid) { sendResponse(PacketCoding.CreateGamePacket(0, GenericObjectActionMessage(guid, 36))) @@ -215,7 +226,7 @@ class WorldSessionActor extends Actor with MDCContextAware { ((distanceSq < 900 || weaponInHand) && time > 200) || (distanceSq < 10000 && time > 500) || (distanceSq < 160000 && (msg.is_jumping || time < 200)) || - (distanceSq < 160000 && msg.vel.isEmpty && time > 2000) || + (distanceSq < 160000 && (msg.vel.isEmpty || Vector3.MagnitudeSquared(msg.vel.get).toInt == 0) && time > 2000) || (distanceSq < 160000 && time > 1000) || (distanceSq > 160000 && time > 5000)) { @@ -240,9 +251,14 @@ class WorldSessionActor extends Actor with MDCContextAware { } } - case AvatarResponse.Reload(mag) => + case AvatarResponse.Reload(item_guid) => if(player.GUID != guid) { - sendResponse(PacketCoding.CreateGamePacket(0, ReloadMessage(guid, mag, 0))) + sendResponse(PacketCoding.CreateGamePacket(0, ReloadMessage(item_guid, 1, 0))) + } + + case AvatarResponse.WeaponDryFire(weapon_guid) => + if(player.GUID != guid) { + sendResponse(PacketCoding.CreateGamePacket(0, WeaponJammedMessage(weapon_guid))) } case _ => ; @@ -283,6 +299,20 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(PacketCoding.CreateGamePacket(0, ChildObjectStateMessage(object_guid, pitch, yaw))) } + case VehicleResponse.InventoryState(obj, parent_guid, start, con_data) => + if(player.GUID != guid) { + //TODO prefer ObjectDetachMessage, but how to force ammo pools to update properly? + val obj_guid = obj.GUID + sendResponse(PacketCoding.CreateGamePacket(0, ObjectDeleteMessage(obj_guid, 0))) + sendResponse(PacketCoding.CreateGamePacket(0, + ObjectCreateDetailedMessage( + obj.Definition.ObjectId, + obj_guid, + ObjectCreateMessageParent(parent_guid, start), + con_data) + )) + } + case VehicleResponse.DismountVehicle(unk1, unk2) => if(player.GUID != guid) { sendResponse(PacketCoding.CreateGamePacket(0, DismountVehicleMsg(guid, unk1, unk2))) @@ -1306,10 +1336,19 @@ class WorldSessionActor extends Actor with MDCContextAware { case msg @ ChangeFireStateMessage_Start(item_guid) => log.info("ChangeFireState_Start: " + msg) + if(shooting.isEmpty) { + //TODO check that player can shoot item_guid? + shooting = Some(item_guid) + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ChangeFireState_Start(player.GUID, item_guid)) + } case msg @ ChangeFireStateMessage_Stop(item_guid) => log.info("ChangeFireState_Stop: " + msg) - progressBarUpdate.cancel + if(shooting.contains(item_guid)) { + shooting = None + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ChangeFireState_Stop(player.GUID, item_guid)) + } + progressBarUpdate.cancel //TODO independent action? case msg @ EmoteMsg(avatar_guid, emote) => log.info("Emote: " + msg) @@ -1339,7 +1378,7 @@ class WorldSessionActor extends Actor with MDCContextAware { case msg @ ReloadMessage(item_guid, ammo_clip, unk1) => log.info("Reload: " + msg) - val reloadValue : Int = player.VehicleSeated match { + (player.VehicleSeated match { case Some(vehicle_guid) => //weapon is vehicle turret? continent.GUID(vehicle_guid) match { case Some(vehicle : Vehicle) => @@ -1347,29 +1386,68 @@ class WorldSessionActor extends Actor with MDCContextAware { case Some(seat_num) => vehicle.WeaponControlledFromSeat(seat_num) match { case Some(item : Tool) => - item.FireMode.Magazine + //TODO check that item controlled by seat is item_guid? + (Some(vehicle), Some(item)) case _ => ; - 0 + (None, None) } case None => ; - 0 + (None, None) } case _ => ; - 0 + (None, None) } case None => //not in vehicle; weapon in hand? - log.info(s"${player.DrawnSlot} -> ${player.Slot(player.DrawnSlot).Equipment}") player.Slot(player.DrawnSlot).Equipment match { //TODO check that item in hand is item_guid? case Some(item : Tool) => - item.FireMode.Magazine - case Some(_) | None => ; - 0 + (Some(player), Some(item)) + case _ => ; + (None, None) } - } - if(reloadValue > 0) { - //TODO hunt for ammunition in backpack/trunk - sendResponse(PacketCoding.CreateGamePacket(0, ReloadMessage(item_guid, reloadValue, unk1))) + }) match { + case (Some(obj), Some(tool : Tool)) => + val currentMagazine : Int = tool.Magazine + val magazineSize : Int = tool.MaxMagazine + val reloadValue : Int = magazineSize - currentMagazine + if(magazineSize > 0 && reloadValue > 0) { + FindReloadAmmunition(obj, tool.AmmoType, reloadValue).reverse match { + case Nil => + log.warn(s"ReloadMessage: no ammunition could be found for $item_guid") + case list @ x :: xs => + val (deleteFunc, modifyFunc) : ((Int, PlanetSideGUID)=>Unit, (AmmoBox, Int)=>Unit) = obj match { + case (veh : Vehicle) => + (DeleteAmmunitionInVehicle(veh), ModifyAmmunitionInVehicle(veh)) + case _ => + (DeleteAmmunition(obj), ModifyAmmunition(obj)) + } + xs.foreach(item => { + deleteFunc(item.start, item.obj.GUID) + }) + val box = x.obj.asInstanceOf[AmmoBox] + val tailReloadValue : Int = if(xs.isEmpty) { 0 } else { xs.map(_.obj.asInstanceOf[AmmoBox].Capacity).reduceLeft(_ + _) } + val sumReloadValue : Int = box.Capacity + tailReloadValue + val actualReloadValue = (if(sumReloadValue <= reloadValue) { + deleteFunc(x.start, box.GUID) + sumReloadValue + } + else { + modifyFunc(box, reloadValue - tailReloadValue) + reloadValue + }) + currentMagazine + log.info(s"ReloadMessage: success, $tool <- $actualReloadValue ${tool.AmmoType}") + tool.Magazine = actualReloadValue + sendResponse(PacketCoding.CreateGamePacket(0, ReloadMessage(item_guid, actualReloadValue, unk1))) + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.Reload(player.GUID, item_guid)) + } + } + else { + log.warn(s"ReloadMessage: item $item_guid can not reload (full=$magazineSize, want=$reloadValue)") + } + case (_, Some(_)) => + log.error(s"ReloadMessage: the object that was found for $item_guid was not a Tool") + case (_, None) => + log.error(s"ReloadMessage: can not find $item_guid") } case msg @ ObjectHeldMessage(avatar_guid, held_holsters, unk1) => @@ -1673,9 +1751,60 @@ class WorldSessionActor extends Actor with MDCContextAware { case msg @ WeaponDelayFireMessage(seq_time, weapon_guid) => log.info("WeaponDelayFire: " + msg) + (player.VehicleSeated match { + case Some(vehicle_guid) => + continent.GUID(vehicle_guid) match { + case Some(obj : Vehicle) => + obj.PassengerInSeat(player) match { + case Some(seat_num) => + obj.WeaponControlledFromSeat(seat_num) + case None => + None + } + case _ => + None + } + case None => + player.Slot(player.DrawnSlot).Equipment + }) match { + case Some(tool : Tool) => + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.WeaponDryFire(player.GUID, weapon_guid)) + case _ => ; + } case msg @ WeaponFireMessage(seq_time, weapon_guid, projectile_guid, shot_origin, unk1, unk2, unk3, unk4, unk5, unk6, unk7) => log.info("WeaponFire: " + msg) + (player.VehicleSeated match { + case Some(vehicle_guid) => + continent.GUID(vehicle_guid) match { + case Some(obj : Vehicle) => + obj.PassengerInSeat(player) match { + case Some(seat_num) => + obj.WeaponControlledFromSeat(seat_num) + case None => + None + } + case _ => + None + } + case None => + player.Slot(player.DrawnSlot).Equipment + }) match { + case Some(tool : Tool) => + if(tool.Magazine <= 0) { //safety: enforce ammunition depletion + tool.Magazine = 0 + sendResponse(PacketCoding.CreateGamePacket(0, InventoryStateMessage(tool.AmmoSlot.Box.GUID, weapon_guid, 0))) + sendResponse(PacketCoding.CreateGamePacket(0, ChangeFireStateMessage_Stop(weapon_guid))) + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ChangeFireState_Stop(player.GUID, weapon_guid)) + sendResponse(PacketCoding.CreateGamePacket(0, WeaponDryFireMessage(weapon_guid))) + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.WeaponDryFire(player.GUID, weapon_guid)) + } + else { //shooting + tool.Magazine = tool.Magazine - 1 + //TODO other stuff + } + case _ => ; + } case msg @ WeaponLazeTargetPositionMessage(weapon, pos1, pos2) => log.info("Lazing position: " + pos2.toString) @@ -1727,9 +1856,7 @@ class WorldSessionActor extends Actor with MDCContextAware { val seats = obj.Seats.values seats.find(seat => seat.Occupant.contains(player)) match { case Some(seat) => - val vel = obj.Velocity.getOrElse(Vector3(0f, 0f, 0f)) - val has_vel : Int = math.abs(vel.x * vel.y * vel.z).toInt - if(seat.Bailable || obj.Velocity.isEmpty || has_vel == 0) { //ugh, float comparison + if(seat.Bailable || obj.Velocity.isEmpty || Vector3.MagnitudeSquared(obj.Velocity.get).toInt == 0) { //ugh, float comparison seat.Occupant = None //special actions obj match { @@ -2355,6 +2482,59 @@ class WorldSessionActor extends Actor with MDCContextAware { }) } + /** + * Within a specified `Container`, find the smallest number of `AmmoBox` objects of a certain type of `Ammo` + * whose sum capacities is greater than, or equal to, a `desiredAmount`.
+ *
+ * In an occupied `List` of returned `Inventory` entries, all but the last entry is considered emptied. + * The last entry may require having its `Capacity` be set to a non-zero number. + * @param obj the `Container` to search + * @param ammoType the type of `Ammo` to search for + * @param desiredAmount how much ammunition is requested to be found + * @return a `List` of all discovered entries totaling approximately the amount of the requested `Ammo` + */ + def FindReloadAmmunition(obj : Container, ammoType : Ammo.Value, desiredAmount : Int) : List[InventoryItem] = { + var currentAmount : Int = 0 + obj.Inventory.Items + .map({ case ((_, item)) => item }) + .filter(obj => { + obj.obj match { + case (box : AmmoBox) => + box.AmmoType == ammoType + case _ => + false + } + }) + .toList + .sortBy(_.start) + .takeWhile(entry => { + val previousAmount = currentAmount + currentAmount += entry.obj.asInstanceOf[AmmoBox].Capacity + previousAmount < desiredAmount + }) + } + + private def DeleteAmmunition(obj : PlanetSideGameObject with Container)(start : Int, item_guid : PlanetSideGUID) : Unit = { + obj.Inventory -= start + sendResponse(PacketCoding.CreateGamePacket(0, ObjectDeleteMessage(item_guid, 0))) + } + + private def DeleteAmmunitionInVehicle(obj : Vehicle)(start : Int, item_guid : PlanetSideGUID) : Unit = { + DeleteAmmunition(obj)(start, item_guid) + vehicleService ! VehicleServiceMessage(s"${obj.Actor}", VehicleAction.UnstowEquipment(player.GUID, item_guid)) + } + + private def ModifyAmmunition(obj : PlanetSideGameObject with Container)(box : AmmoBox, reloadValue : Int) : Unit = { + val capacity = box.Capacity - reloadValue + box.Capacity = capacity + sendResponse(PacketCoding.CreateGamePacket(0, InventoryStateMessage(box.GUID, obj.GUID, capacity))) + } + + private def ModifyAmmunitionInVehicle(obj : Vehicle)(box : AmmoBox, reloadValue : Int) : Unit = { + val capacity = ModifyAmmunition(obj)(box, reloadValue) + vehicleService ! VehicleServiceMessage(s"${obj.Actor}", VehicleAction.InventoryState(player.GUID, box, obj.GUID, obj.Find(box).get, box.Definition.Packet.DetailedConstructorData(box).get)) + } + /** * A predicate used to determine if an `InventoryItem` object contains `Equipment` that should be dropped. * Used to filter through lists of object data before it is placed into a player's inventory. diff --git a/pslogin/src/main/scala/services/avatar/AvatarAction.scala b/pslogin/src/main/scala/services/avatar/AvatarAction.scala index 3b9f54e6..ef0d4926 100644 --- a/pslogin/src/main/scala/services/avatar/AvatarAction.scala +++ b/pslogin/src/main/scala/services/avatar/AvatarAction.scala @@ -10,6 +10,8 @@ object AvatarAction { trait Action final case class ArmorChanged(player_guid : PlanetSideGUID, suit : ExoSuitType.Value, subtype : Int) extends Action + final case class ChangeFireState_Start(player_guid : PlanetSideGUID, weapon_guid : PlanetSideGUID) extends Action + final case class ChangeFireState_Stop(player_guid : PlanetSideGUID, weapon_guid : PlanetSideGUID) extends Action final case class ConcealPlayer(player_guid : PlanetSideGUID) extends Action //final case class DropItem(pos : Vector3, orient : Vector3, item : PlanetSideGUID) extends Action final case class EquipmentInHand(player_guid : PlanetSideGUID, slot : Int, item : Equipment) extends Action @@ -21,7 +23,8 @@ object AvatarAction { 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 PlayerState(player_guid : PlanetSideGUID, msg : PlayerStateMessageUpstream, spectator : Boolean, weaponInHand : Boolean) extends Action - final case class Reload(player_guid : PlanetSideGUID, mag : Int) extends Action + final case class Reload(player_guid : PlanetSideGUID, weapon_guid : PlanetSideGUID) extends Action + final case class WeaponDryFire(player_guid : PlanetSideGUID, weapon_guid : PlanetSideGUID) extends Action // final case class PlayerStateShift(killer : PlanetSideGUID, victim : PlanetSideGUID) extends Action // final case class DestroyDisplay(killer : PlanetSideGUID, victim : PlanetSideGUID) extends Action // final case class HitHintReturn(killer : PlanetSideGUID, victim : PlanetSideGUID) extends Action diff --git a/pslogin/src/main/scala/services/avatar/AvatarResponse.scala b/pslogin/src/main/scala/services/avatar/AvatarResponse.scala index abcf6246..0a892825 100644 --- a/pslogin/src/main/scala/services/avatar/AvatarResponse.scala +++ b/pslogin/src/main/scala/services/avatar/AvatarResponse.scala @@ -10,6 +10,8 @@ object AvatarResponse { trait Response final case class ArmorChanged(suit : ExoSuitType.Value, subtype : Int) extends Response + final case class ChangeFireState_Start(weapon_guid : PlanetSideGUID) extends Response + final case class ChangeFireState_Stop(weapon_guid : PlanetSideGUID) extends Response final case class ConcealPlayer() extends Response //final case class DropItem(pos : Vector3, orient : Vector3, item : PlanetSideGUID) extends Response final case class EquipmentInHand(slot : Int, item : Equipment) extends Response @@ -21,7 +23,8 @@ object AvatarResponse { final case class ObjectHeld(slot : Int) extends Response final case class PlanetsideAttribute(attribute_type : Int, attribute_value : Long) extends Response final case class PlayerState(msg : PlayerStateMessageUpstream, spectator : Boolean, weaponInHand : Boolean) extends Response - final case class Reload(mag : Int) extends Response + final case class Reload(weapon_guid : PlanetSideGUID) extends Response + final case class WeaponDryFire(weapon_guid : PlanetSideGUID) extends Response // final case class PlayerStateShift(itemID : PlanetSideGUID) extends Response // final case class DestroyDisplay(itemID : PlanetSideGUID) extends Response // final case class HitHintReturn(itemID : PlanetSideGUID) extends Response diff --git a/pslogin/src/main/scala/services/avatar/AvatarService.scala b/pslogin/src/main/scala/services/avatar/AvatarService.scala index ac32c07e..09196cb1 100644 --- a/pslogin/src/main/scala/services/avatar/AvatarService.scala +++ b/pslogin/src/main/scala/services/avatar/AvatarService.scala @@ -39,6 +39,14 @@ class AvatarService extends Actor { AvatarEvents.publish( AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.ArmorChanged(suit, subtype)) ) + case AvatarAction.ChangeFireState_Start(player_guid, weapon_guid) => + AvatarEvents.publish( + AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.ChangeFireState_Start(weapon_guid)) + ) + case AvatarAction.ChangeFireState_Stop(player_guid, weapon_guid) => + AvatarEvents.publish( + AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.ChangeFireState_Stop(weapon_guid)) + ) case AvatarAction.ConcealPlayer(player_guid) => AvatarEvents.publish( AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.ConcealPlayer()) @@ -71,9 +79,13 @@ class AvatarService extends Actor { AvatarEvents.publish( AvatarServiceResponse(s"/$forChannel/Avatar", guid, AvatarResponse.PlayerState(msg, spectator, weapon)) ) - case AvatarAction.Reload(player_guid, mag) => + case AvatarAction.Reload(player_guid, weapon_guid) => AvatarEvents.publish( - AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.Reload(mag)) + AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.Reload(weapon_guid)) + ) + case AvatarAction.WeaponDryFire(player_guid, weapon_guid) => + AvatarEvents.publish( + AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.WeaponDryFire(weapon_guid)) ) case _ => ; } diff --git a/pslogin/src/main/scala/services/vehicle/VehicleAction.scala b/pslogin/src/main/scala/services/vehicle/VehicleAction.scala index 7887a023..5bf6a5d4 100644 --- a/pslogin/src/main/scala/services/vehicle/VehicleAction.scala +++ b/pslogin/src/main/scala/services/vehicle/VehicleAction.scala @@ -1,7 +1,7 @@ // Copyright (c) 2017 PSForever package services.vehicle -import net.psforever.objects.Vehicle +import net.psforever.objects.{PlanetSideGameObject, Vehicle} import net.psforever.objects.equipment.Equipment import net.psforever.objects.zones.Zone import net.psforever.packet.game.PlanetSideGUID @@ -14,6 +14,7 @@ object VehicleAction { final case class Awareness(player_guid : PlanetSideGUID, vehicle_guid : PlanetSideGUID) extends Action final case class ChildObjectState(player_guid : PlanetSideGUID, object_guid : PlanetSideGUID, pitch : Float, yaw : Float) extends Action final case class DismountVehicle(player_guid : PlanetSideGUID, unk1 : Int, unk2 : Boolean) extends Action + final case class InventoryState(player_guid : PlanetSideGUID, obj : PlanetSideGameObject, parent_guid : PlanetSideGUID, start : Int, con_data : ConstructorData) extends Action final case class KickPassenger(player_guid : PlanetSideGUID, unk1 : Int, unk2 : Boolean, vehicle_guid : PlanetSideGUID) extends Action final case class LoadVehicle(player_guid : PlanetSideGUID, vehicle : Vehicle, vtype : Int, vguid : PlanetSideGUID, vdata : ConstructorData) extends Action final case class MountVehicle(player_guid : PlanetSideGUID, object_guid : PlanetSideGUID, seat : Int) extends Action diff --git a/pslogin/src/main/scala/services/vehicle/VehicleResponse.scala b/pslogin/src/main/scala/services/vehicle/VehicleResponse.scala index 1b7de780..32c08412 100644 --- a/pslogin/src/main/scala/services/vehicle/VehicleResponse.scala +++ b/pslogin/src/main/scala/services/vehicle/VehicleResponse.scala @@ -1,7 +1,7 @@ // Copyright (c) 2017 PSForever package services.vehicle -import net.psforever.objects.Vehicle +import net.psforever.objects.{PlanetSideGameObject, Vehicle} import net.psforever.packet.game.PlanetSideGUID import net.psforever.packet.game.objectcreate.ConstructorData import net.psforever.types.Vector3 @@ -12,6 +12,7 @@ object VehicleResponse { final case class Awareness(vehicle_guid : PlanetSideGUID) extends Response final case class ChildObjectState(object_guid : PlanetSideGUID, pitch : Float, yaw : Float) extends Response final case class DismountVehicle(unk1 : Int, unk2 : Boolean) extends Response + final case class InventoryState(obj : PlanetSideGameObject, parent_guid : PlanetSideGUID, start : Int, con_data : ConstructorData) extends Response final case class KickPassenger(unk1 : Int, unk2 : Boolean, vehicle_guid : PlanetSideGUID) extends Response final case class LoadVehicle(vehicle : Vehicle, vtype : Int, vguid : PlanetSideGUID, vdata : ConstructorData) extends Response final case class MountVehicle(object_guid : PlanetSideGUID, seat : Int) extends Response diff --git a/pslogin/src/main/scala/services/vehicle/VehicleService.scala b/pslogin/src/main/scala/services/vehicle/VehicleService.scala index 003c8dc3..9fa88ee3 100644 --- a/pslogin/src/main/scala/services/vehicle/VehicleService.scala +++ b/pslogin/src/main/scala/services/vehicle/VehicleService.scala @@ -2,6 +2,8 @@ package services.vehicle import akka.actor.{Actor, ActorRef, Props} +import net.psforever.packet.game.PlanetSideGUID +import net.psforever.packet.game.objectcreate.ConstructorData import services.vehicle.support.{DeconstructionActor, DelayedDeconstructionActor, VehicleContextActor} import services.{GenericEventBus, Service} @@ -47,6 +49,10 @@ class VehicleService extends Actor { VehicleEvents.publish( VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.ChildObjectState(object_guid, pitch, yaw)) ) + case VehicleAction.InventoryState(player_guid, obj, parent_guid, start, con_data) => + VehicleEvents.publish( + VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.InventoryState(obj, parent_guid, start, con_data)) + ) case VehicleAction.DismountVehicle(player_guid, unk1, unk2) => VehicleEvents.publish( VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.DismountVehicle(unk1, unk2)) diff --git a/pslogin/src/main/test/scala/AvatarServiceTest.scala b/pslogin/src/main/test/scala/AvatarServiceTest.scala index a8974800..9c43ac25 100644 --- a/pslogin/src/main/test/scala/AvatarServiceTest.scala +++ b/pslogin/src/main/test/scala/AvatarServiceTest.scala @@ -176,8 +176,8 @@ class AvatarServiceCTest extends ActorTest { "pass Reload" in { val service = system.actorOf(Props[AvatarService], "service") service ! Service.Join("test") - service ! AvatarServiceMessage("test", AvatarAction.Reload(PlanetSideGUID(10), 35)) - expectMsg(AvatarServiceResponse("/test/Avatar", PlanetSideGUID(10), AvatarResponse.Reload(35))) + service ! AvatarServiceMessage("test", AvatarAction.Reload(PlanetSideGUID(10), PlanetSideGUID(40))) + expectMsg(AvatarServiceResponse("/test/Avatar", PlanetSideGUID(10), AvatarResponse.Reload(PlanetSideGUID(40)))) } } } From 9e51e33246cb5524f30257ee5446a9cc03dd8b15 Mon Sep 17 00:00:00 2001 From: FateJH Date: Tue, 9 Jan 2018 20:28:36 -0500 Subject: [PATCH 2/7] synchronizing change ammo --- .../scala/net/psforever/objects/AmmoBox.scala | 31 +- .../packet/game/ObjectDetachMessage.scala | 18 +- .../test/scala/objects/ConverterTest.scala | 3 +- .../test/scala/objects/EquipmentTest.scala | 35 ++ .../test/scala/objects/InventoryTest.scala | 8 +- .../src/main/scala/WorldSessionActor.scala | 359 ++++++++++++++++-- .../scala/services/avatar/AvatarAction.scala | 3 +- .../services/avatar/AvatarResponse.scala | 3 +- .../scala/services/avatar/AvatarService.scala | 8 +- .../main/test/scala/AvatarServiceTest.scala | 7 +- 10 files changed, 407 insertions(+), 68 deletions(-) diff --git a/common/src/main/scala/net/psforever/objects/AmmoBox.scala b/common/src/main/scala/net/psforever/objects/AmmoBox.scala index f95cebb7..835488ec 100644 --- a/common/src/main/scala/net/psforever/objects/AmmoBox.scala +++ b/common/src/main/scala/net/psforever/objects/AmmoBox.scala @@ -36,17 +36,26 @@ object AmmoBox { new AmmoBox(ammoDef, Some(capacity)) } - import net.psforever.packet.game.PlanetSideGUID - def apply(guid : PlanetSideGUID, ammoDef : AmmoBoxDefinition) : AmmoBox = { - val obj = new AmmoBox(ammoDef) - obj.GUID = guid - obj - } - - def apply(guid : PlanetSideGUID, ammoDef : AmmoBoxDefinition, capacity : Int) : AmmoBox = { - val obj = new AmmoBox(ammoDef, Some(capacity)) - obj.GUID = guid - obj + /** + * Accepting an `AmmoBox` object that has an uncertain amount of ammunition in it, + * create multiple `AmmoBox` objects where none contain more than the maximum capacity for that ammunition type, + * and the sum of all objects' capacities is the original object's capacity. + * @param box an `AmmoBox` object of unspecified capacity + * @return a `List` of `AmmoBox` objects with correct capacities + */ + def Split(box : AmmoBox) : List[AmmoBox] = { + val ammoDef = box.Definition + var boxCap : Int = box.Capacity + val maxCap : Int = ammoDef.Capacity + val splitCap : Int = boxCap / maxCap + val list : List[AmmoBox] = List.fill(splitCap)(new AmmoBox(ammoDef)) + val leftover = boxCap - maxCap * splitCap + if(leftover > 0) { + list :+ AmmoBox(ammoDef, leftover) + } + else { + list + } } def limitCapacity(count : Int, min : Int = 0) : Int = math.min(math.max(min, count), 65535) diff --git a/common/src/main/scala/net/psforever/packet/game/ObjectDetachMessage.scala b/common/src/main/scala/net/psforever/packet/game/ObjectDetachMessage.scala index 4d6abda7..a68fa1fb 100644 --- a/common/src/main/scala/net/psforever/packet/game/ObjectDetachMessage.scala +++ b/common/src/main/scala/net/psforever/packet/game/ObjectDetachMessage.scala @@ -9,17 +9,17 @@ import scodec.codecs._ /** * Dispatched by the server to cause two associated objects to disentangle from one another.
*
- * `ObjectDetachMessage` is the opposite to `ObjectAttachMessage`. - * When detached, the resulting freed object will be placed at the given coordinates. - * For some container objects, most often static ones, a default placement point does exist. + * `ObjectDetachMessage` is the opposite of `ObjectAttachMessage`. + * When detached, the resulting freed object will be placed at the given coordinates in the game world. + * For detachment from some container objects, a default placement point may exist. * This usually matches the position where the original mounting occurred, or is relative to the current position of the container. - * Using a position that is not the mounting one, in this case, counts as a temporary teleport of the character. - * As soon as available, e.g., the end of an animation, the character will rw-appear at the mounting point. - * The object may also have its orientation aspect changed.
+ * This mounting position overrides the input one, but other temporary side-effects may occur. + * For example, if a player detaches from a vehicle with coordinates for "somewhere else," + * the camera will temporarily be moved to that location "somewhere else" for the duration of the animation + * but it will soon regain the player who appeared where expected.
*
- * This packet is considered proper response to:
- * - `DismountVehicleMsg`
- * - `DropItemMessage` + * An object that is already dropped is a special case where the parent (container) does not technically exist. + * The parent also does not need to exist as the object will still be transported to the specified coordinates. * @param parent_guid the container/connector object * @param child_guid the contained/connected object * @param pos where the contained/connected object will be placed after it has detached diff --git a/common/src/test/scala/objects/ConverterTest.scala b/common/src/test/scala/objects/ConverterTest.scala index fe4f8d58..f9a22fde 100644 --- a/common/src/test/scala/objects/ConverterTest.scala +++ b/common/src/test/scala/objects/ConverterTest.scala @@ -277,7 +277,8 @@ class ConverterTest extends Specification { fury_def.TrunkSize = InventoryTile(11, 11) fury_def.TrunkOffset = 30 - val hellfire_ammo_box = AmmoBox(PlanetSideGUID(432), hellfire_ammo) + val hellfire_ammo_box = AmmoBox(hellfire_ammo) + hellfire_ammo_box.GUID = PlanetSideGUID(432) val fury = Vehicle(fury_def) fury.GUID = PlanetSideGUID(413) diff --git a/common/src/test/scala/objects/EquipmentTest.scala b/common/src/test/scala/objects/EquipmentTest.scala index db2de872..3fbe4501 100644 --- a/common/src/test/scala/objects/EquipmentTest.scala +++ b/common/src/test/scala/objects/EquipmentTest.scala @@ -65,6 +65,41 @@ class EquipmentTest extends Specification { obj.Capacity = 65536 obj.Capacity mustEqual 65535 } + + "split (0)" in { + val obj = AmmoBox(bullet_9mm) + obj.Capacity = 0 + val list = AmmoBox.Split(obj) + list.size mustEqual 0 + } + + "split (1)" in { + val obj = AmmoBox(bullet_9mm) + obj.Capacity = 50 + val list = AmmoBox.Split(obj) + list.size mustEqual 1 + list.head.Capacity mustEqual 50 + } + + "split (2)" in { + val obj = AmmoBox(bullet_9mm) + obj.Capacity = 75 + val list = AmmoBox.Split(obj) + list.size mustEqual 2 + list.head.Capacity mustEqual 50 + list(1).Capacity mustEqual 25 + } + + "split (4)" in { + val obj = AmmoBox(bullet_9mm) + obj.Capacity = 165 + val list = AmmoBox.Split(obj) + list.size mustEqual 4 + list.head.Capacity mustEqual 50 + list(1).Capacity mustEqual 50 + list(2).Capacity mustEqual 50 + list(3).Capacity mustEqual 15 + } } "Tool" should { diff --git a/common/src/test/scala/objects/InventoryTest.scala b/common/src/test/scala/objects/InventoryTest.scala index 34ec7491..3eea16e6 100644 --- a/common/src/test/scala/objects/InventoryTest.scala +++ b/common/src/test/scala/objects/InventoryTest.scala @@ -12,8 +12,12 @@ import scala.collection.mutable.ListBuffer import scala.util.Success class InventoryTest extends Specification { - val bullet9mmBox1 = AmmoBox(PlanetSideGUID(1), bullet_9mm) - val bullet9mmBox2 = AmmoBox(PlanetSideGUID(2), bullet_9mm) + val + bullet9mmBox1 = AmmoBox(bullet_9mm) + bullet9mmBox1.GUID = PlanetSideGUID(1) + val + bullet9mmBox2 = AmmoBox(bullet_9mm) + bullet9mmBox2.GUID = PlanetSideGUID(2) "GridInventory" should { "construct" in { diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index 8d1566fc..e1127d25 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -146,6 +146,19 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(PacketCoding.CreateGamePacket(0, ArmorChangedMessage(guid, suit, subtype))) } + case AvatarResponse.ChangeAmmo(weapon_guid, weapon_slot, ammo_id, ammo_guid, ammo_data) => + if(player.GUID != guid) { + sendResponse(PacketCoding.CreateGamePacket(0, + ObjectCreateMessage( + ammo_id, + ammo_guid, + ObjectCreateMessageParent(weapon_guid, weapon_slot), + ammo_data + )) + ) + sendResponse(PacketCoding.CreateGamePacket(0, ChangeAmmoMessage(weapon_guid, 1))) + } + case AvatarResponse.ChangeFireState_Start(weapon_guid) => if(player.GUID != guid) { sendResponse(PacketCoding.CreateGamePacket(0, ChangeFireStateMessage_Start(weapon_guid))) @@ -176,15 +189,14 @@ class WorldSessionActor extends Actor with MDCContextAware { ) } - case AvatarResponse.EquipmentOnGround(pos, orient, item) => + case AvatarResponse.EquipmentOnGround(pos, orient, item_id, item_guid, item_data) => if(player.GUID != guid) { - val definition = item.Definition sendResponse( PacketCoding.CreateGamePacket(0, ObjectCreateMessage( - definition.ObjectId, - item.GUID, - DroppedItemData(PlacementData(pos, Vector3(0f, 0f, orient.z)), definition.Packet.ConstructorData(item).get) + item_id, + item_guid, + DroppedItemData(PlacementData(pos, Vector3(0f, 0f, orient.z)), item_data) ) ) ) @@ -299,6 +311,11 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(PacketCoding.CreateGamePacket(0, ChildObjectStateMessage(object_guid, pitch, yaw))) } + case VehicleResponse.DismountVehicle(unk1, unk2) => + if(player.GUID != guid) { + sendResponse(PacketCoding.CreateGamePacket(0, DismountVehicleMsg(guid, unk1, unk2))) + } + case VehicleResponse.InventoryState(obj, parent_guid, start, con_data) => if(player.GUID != guid) { //TODO prefer ObjectDetachMessage, but how to force ammo pools to update properly? @@ -313,11 +330,6 @@ class WorldSessionActor extends Actor with MDCContextAware { )) } - case VehicleResponse.DismountVehicle(unk1, unk2) => - if(player.GUID != guid) { - sendResponse(PacketCoding.CreateGamePacket(0, DismountVehicleMsg(guid, unk1, unk2))) - } - case VehicleResponse.KickPassenger(unk1, unk2, vehicle_guid) => sendResponse(PacketCoding.CreateGamePacket(0, DismountVehicleMsg(guid, unk1, unk2))) if(guid == player.GUID) { @@ -571,7 +583,8 @@ class WorldSessionActor extends Actor with MDCContextAware { // ) ) ) - avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.EquipmentOnGround(tplayer.GUID, pos, orient, obj)) + val objDef = obj.Definition + avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.EquipmentOnGround(tplayer.GUID, pos, orient, objDef.ObjectId, obj.GUID, objDef.Packet.ConstructorData(obj).get)) }) sendResponse(PacketCoding.CreateGamePacket(0, ItemTransactionResultMessage (msg.terminal_guid, TransactionType.Buy, true))) } @@ -665,7 +678,8 @@ class WorldSessionActor extends Actor with MDCContextAware { ObjectDetachMessage(tplayer.GUID, obj.GUID, pos, 0f, 0f, orient.z) ) ) - avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.EquipmentOnGround(tplayer.GUID, pos, orient, obj)) + val objDef = obj.Definition + avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.EquipmentOnGround(tplayer.GUID, pos, orient, objDef.ObjectId, obj.GUID, objDef.Packet.ConstructorData(obj).get)) }) sendResponse(PacketCoding.CreateGamePacket(0, ItemTransactionResultMessage (msg.terminal_guid, TransactionType.InfantryLoadout, true))) @@ -1207,9 +1221,9 @@ class WorldSessionActor extends Actor with MDCContextAware { val parent_guid = PlanetSideGUID(terminal_guid) continent.GUID(interface_guid) match { case Some(obj : Terminal) => - val obj_def = obj.Definition - val obj_uid = obj_def.ObjectId - val obj_data = obj_def.Packet.ConstructorData(obj).get + val objDef = obj.Definition + val obj_uid = objDef.ObjectId + val obj_data = objDef.Packet.ConstructorData(obj).get sendResponse(PacketCoding.CreateGamePacket(0, ObjectCreateMessage( obj_uid, @@ -1331,6 +1345,152 @@ class WorldSessionActor extends Actor with MDCContextAware { case msg @ VoiceHostInfo(player_guid, data) => sendResponse(PacketCoding.CreateGamePacket(0, VoiceHostKill())) + case msg @ ChangeAmmoMessage(item_guid, unk1) => + log.info("ChangeAmmo: " + msg) + (player.VehicleSeated match { + case Some(vehicle_guid) => //weapon is vehicle turret? + continent.GUID(vehicle_guid) match { + case Some(vehicle : Vehicle) => + vehicle.PassengerInSeat(player) match { + case Some(seat_num) => + vehicle.WeaponControlledFromSeat(seat_num) match { + case Some(item : Tool) => + //TODO check that item controlled by seat is item_guid? + (Some(vehicle), Some(item)) + case _ => ; + (None, None) + } + case None => ; + (None, None) + } + case _ => ; + (None, None) + } + case None => //not in vehicle; weapon in hand? + player.Slot(player.DrawnSlot).Equipment match { + //TODO check that item in hand is item_guid? + case Some(item : Tool) => + (Some(player), Some(item)) + case _ => ; + (None, None) + } + }) match { + case (Some(obj), Some(tool : Tool)) => + val originalAmmoType = tool.AmmoType + val fullMagazine = tool.MaxMagazine + do { + val requestedAmmoType = tool.NextAmmoType + if(requestedAmmoType != tool.AmmoSlot.Box.AmmoType) { + FindReloadAmmunition(obj, requestedAmmoType, fullMagazine).reverse match { + case Nil => ; + case x :: xs => + val (deleteFunc, modifyFunc) : ((Int, AmmoBox)=>Unit, (AmmoBox, Int)=>Unit) = obj match { + case (veh : Vehicle) => + (DeleteAmmunitionInVehicle(veh), ModifyAmmunitionInVehicle(veh)) + case _ => + (DeleteAmmunition(obj), ModifyAmmunition(obj)) + } + val (stowFuncTask, stowFunc) : ((Int, AmmoBox)=>TaskResolver.GiveTask, (Int, AmmoBox)=>Unit) = obj match { + case (veh : Vehicle) => + (StowNewAmmunitionInVehicles(veh), StowAmmunitionInVehicles(veh)) + case _ => + (StowNewAmmunition(obj), StowAmmunition(obj)) + } + xs.foreach(item => { + obj.Inventory -= x.start + deleteFunc(item.start, item.obj.asInstanceOf[AmmoBox]) + }) + + //box will be the replacement ammo; give it the discovered magazine and load it into the weapon @ 0 + val box = x.obj.asInstanceOf[AmmoBox] + val originalBoxCapacity = box.Capacity + val tailReloadValue : Int = if(xs.isEmpty) { 0 } else { xs.map(_.obj.asInstanceOf[AmmoBox].Capacity).reduceLeft(_ + _) } + val sumReloadValue : Int = originalBoxCapacity + tailReloadValue + val previousBox = tool.AmmoSlot.Box //current magazine in tool + sendResponse(PacketCoding.CreateGamePacket(0, ObjectDetachMessage(tool.GUID, previousBox.GUID, Vector3(0f, 0f, 0f), 0f, 0f, 0f))) + sendResponse(PacketCoding.CreateGamePacket(0, ObjectDetachMessage(player.GUID, box.GUID, Vector3(0f, 0f, 0f), 0f, 0f, 0f))) + obj.Inventory -= x.start //remove replacement ammo from inventory + val ammoSlotIndex = tool.FireMode.AmmoSlotIndex + tool.AmmoSlots(ammoSlotIndex).Box = box //put replacement ammo in tool + sendResponse(PacketCoding.CreateGamePacket(0, ObjectAttachMessage(tool.GUID, box.GUID, ammoSlotIndex))) + + //handle inventory contents + box.Capacity = (if(sumReloadValue <= fullMagazine) { + sumReloadValue + } + else { + val splitReloadAmmo : Int = sumReloadValue - fullMagazine + log.info(s"ChangeAmmo: taking ${originalBoxCapacity - splitReloadAmmo} from a box of ${originalBoxCapacity} $requestedAmmoType") + val boxForInventory = AmmoBox(box.Definition, splitReloadAmmo) + obj.Inventory += x.start -> boxForInventory //block early; assumption warning: swappable ammo types have the same icon size + taskResolver ! stowFuncTask(x.start, boxForInventory) + fullMagazine + }) + sendResponse(PacketCoding.CreateGamePacket(0, InventoryStateMessage(box.GUID, tool.GUID, box.Capacity))) //should work for both players and vehicles + log.info(s"ChangeAmmo: loading ${box.Capacity} $requestedAmmoType into ${tool.GUID} @ $ammoSlotIndex") + if(previousBox.Capacity > 0) { + //divide capacity across other existing and not full boxes of that ammo type + var capacity = previousBox.Capacity + val iter = obj.Inventory.Items + .map({case(_, entry) => entry }) + .filter(entry => { + entry.obj match { + case (item : AmmoBox) => + item.AmmoType == originalAmmoType && item.FullCapacity != item.Capacity + case _ => + false + } + }) + .toList + .sortBy(_.start) + .iterator + while(capacity > 0 && iter.hasNext) { + val entry = iter.next + val item : AmmoBox = entry.obj.asInstanceOf[AmmoBox] + val ammoAllocated = math.min(item.FullCapacity - item.Capacity, capacity) + log.info(s"ChangeAmmo: putting $ammoAllocated back into a box of ${item.Capacity} $originalAmmoType") + capacity -= ammoAllocated + modifyFunc(item, -ammoAllocated) + } + previousBox.Capacity = capacity + } + if(previousBox.Capacity > 0) { + //TODO split previousBox into AmmoBox objects of appropriate max capacity, e.g., 100 9mm -> 2 x 50 9mm + obj.Inventory.Fit(previousBox.Definition.Tile) match { + case Some(index) => //put retained magazine in inventory + stowFunc(index, previousBox) + case None => //drop + log.info(s"ChangeAmmo: dropping ammo box $previousBox") + val pos = player.Position + val orient = player.Orientation + sendResponse(PacketCoding.CreateGamePacket(0, ObjectDetachMessage(Service.defaultPlayerGUID, previousBox.GUID, pos, 0f, 0f, orient.z))) + val orient2 = Vector3(0f, 0f, orient.z) + continent.Ground ! Zone.DropItemOnGround(previousBox, pos, orient2) + val objDef = previousBox.Definition + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.EquipmentOnGround(player.GUID, pos, orient2, objDef.ObjectId, previousBox.GUID, objDef.Packet.ConstructorData(previousBox).get)) + } + } + else { + taskResolver ! GUIDTask.UnregisterObjectTask(previousBox)(continent.GUID) + } + + //announce swapped ammunition box in weapon + val boxDef = box.Definition + val box_guid = box.GUID + val tool_guid = tool.GUID + sendResponse(PacketCoding.CreateGamePacket(0, ChangeAmmoMessage(tool_guid, box.Capacity))) + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ChangeAmmo(player.GUID, tool_guid, ammoSlotIndex, boxDef.ObjectId, box.GUID, boxDef.Packet.ConstructorData(box).get)) + } + } + } + while(tool.AmmoType != originalAmmoType && tool.AmmoType != tool.AmmoSlot.Box.AmmoType) + + case (_, Some(_)) => + log.error(s"ChangeAmmo: the object that was found for $item_guid was not a Tool") + case (_, None) => + log.error(s"ChangeAmmo: can not find $item_guid") + } + case msg @ ChangeFireModeMessage(item_guid, fire_mode) => log.info("ChangeFireMode: " + msg) @@ -1363,7 +1523,8 @@ class WorldSessionActor extends Actor with MDCContextAware { player.FreeHand.Equipment = None continent.Ground ! Zone.DropItemOnGround(item, player.Position, orient) sendResponse(PacketCoding.CreateGamePacket(0, ObjectDetachMessage(player.GUID, item.GUID, player.Position, 0f, 0f, player.Orientation.z))) - avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.EquipmentOnGround(player.GUID, player.Position, orient, item)) + val objDef = item.Definition + avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.EquipmentOnGround(player.GUID, player.Position, orient, objDef.ObjectId, item.GUID, objDef.Packet.ConstructorData(item).get)) } else { log.warn(s"item in hand was ${item.GUID} but trying to drop $item_guid; nothing will be dropped") @@ -1415,20 +1576,20 @@ class WorldSessionActor extends Actor with MDCContextAware { case Nil => log.warn(s"ReloadMessage: no ammunition could be found for $item_guid") case list @ x :: xs => - val (deleteFunc, modifyFunc) : ((Int, PlanetSideGUID)=>Unit, (AmmoBox, Int)=>Unit) = obj match { + val (deleteFunc, modifyFunc) : ((Int, AmmoBox)=>Unit, (AmmoBox, Int)=>Unit) = obj match { case (veh : Vehicle) => (DeleteAmmunitionInVehicle(veh), ModifyAmmunitionInVehicle(veh)) case _ => (DeleteAmmunition(obj), ModifyAmmunition(obj)) } xs.foreach(item => { - deleteFunc(item.start, item.obj.GUID) + deleteFunc(item.start, item.obj.asInstanceOf[AmmoBox]) }) val box = x.obj.asInstanceOf[AmmoBox] val tailReloadValue : Int = if(xs.isEmpty) { 0 } else { xs.map(_.obj.asInstanceOf[AmmoBox].Capacity).reduceLeft(_ + _) } val sumReloadValue : Int = box.Capacity + tailReloadValue val actualReloadValue = (if(sumReloadValue <= reloadValue) { - deleteFunc(x.start, box.GUID) + deleteFunc(x.start, box) sumReloadValue } else { @@ -1564,7 +1725,8 @@ class WorldSessionActor extends Actor with MDCContextAware { val orient : Vector3 = Vector3(0f, 0f, sourceOrientZ) continent.Actor ! Zone.DropItemOnGround(item2, pos, orient) sendResponse(PacketCoding.CreateGamePacket(0, ObjectDetachMessage(source_guid, item2.GUID, pos, 0f, 0f, sourceOrientZ))) //ground - avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.EquipmentOnGround(player.GUID, pos, orient, item2)) + val objDef = item2.Definition + avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.EquipmentOnGround(player.GUID, pos, orient, objDef.ObjectId, item2.GUID, objDef.Packet.ConstructorData(item2).get)) } case None => //just move item over @@ -1614,9 +1776,6 @@ class WorldSessionActor extends Actor with MDCContextAware { case msg @ LootItemMessage(item_guid, target_guid) => log.info("LootItem: " + msg) - case msg @ ChangeAmmoMessage(item_guid, unk1) => - log.info("ChangeAmmo: " + msg) - case msg @ AvatarImplantMessage(_, _, _, _) => //(player_guid, unk1, unk2, implant) => log.info("AvatarImplantMessage: " + msg) @@ -1751,6 +1910,9 @@ class WorldSessionActor extends Actor with MDCContextAware { case msg @ WeaponDelayFireMessage(seq_time, weapon_guid) => log.info("WeaponDelayFire: " + msg) + + case msg @ WeaponDryFireMessage(weapon_guid) => + log.info("WeaponDryFireMessage: "+msg) (player.VehicleSeated match { case Some(vehicle_guid) => continent.GUID(vehicle_guid) match { @@ -1986,9 +2148,6 @@ class WorldSessionActor extends Actor with MDCContextAware { case msg @ HitHint(source, player_guid) => log.info("HitHint: "+msg) - case msg @ WeaponDryFireMessage(weapon) => - log.info("WeaponDryFireMessage: "+msg) - case msg @ TargetingImplantRequest(list) => log.info("TargetingImplantRequest: "+msg) @@ -2063,7 +2222,7 @@ class WorldSessionActor extends Actor with MDCContextAware { * @see `PutInSlot` * @return a `TaskResolver.GiveTask` message */ - private def PutEquipmentInSlot(target : Player, obj : Equipment, index : Int) : TaskResolver.GiveTask = { + private def PutEquipmentInSlot(target : PlanetSideGameObject with Container, obj : Equipment, index : Int) : TaskResolver.GiveTask = { val regTask = GUIDTask.RegisterEquipment(obj)(continent.GUID) obj match { case tool : Tool => @@ -2085,7 +2244,7 @@ class WorldSessionActor extends Actor with MDCContextAware { * @see `RemoveFromSlot` * @return a `TaskResolver.GiveTask` message */ - private def RemoveEquipmentFromSlot(target : Player, obj : Equipment, index : Int) : TaskResolver.GiveTask = { + private def RemoveEquipmentFromSlot(target : PlanetSideGameObject with Container, obj : Equipment, index : Int) : TaskResolver.GiveTask = { val regTask = GUIDTask.UnregisterEquipment(obj)(continent.GUID) //to avoid an error from a GUID-less object from being searchable, it is removed from the inventory first obj match { @@ -2103,7 +2262,7 @@ class WorldSessionActor extends Actor with MDCContextAware { * @param index the slot where the new `Equipment` will be placed * @return a `TaskResolver.GiveTask` message */ - private def PutInSlot(target : Player, obj : Equipment, index : Int) : TaskResolver.GiveTask = { + private def PutInSlot(target : PlanetSideGameObject with Container, obj : Equipment, index : Int) : TaskResolver.GiveTask = { TaskResolver.GiveTask( new Task() { private val localTarget = target @@ -2139,7 +2298,7 @@ class WorldSessionActor extends Actor with MDCContextAware { ) ) if(0 <= localIndex && localIndex < 5) { - localService ! AvatarServiceMessage(localTarget.Continent, AvatarAction.EquipmentInHand(localTarget.GUID, localIndex, localObject)) + localService ! AvatarServiceMessage(continent.Id, AvatarAction.EquipmentInHand(localTarget.GUID, localIndex, localObject)) } } }) @@ -2256,7 +2415,7 @@ class WorldSessionActor extends Actor with MDCContextAware { * @param index the slot where the `Equipment` is stored * @return a `TaskResolver.GiveTask` message */ - private def RemoveFromSlot(target : Player, obj : Equipment, index : Int) : TaskResolver.GiveTask = { + private def RemoveFromSlot(target : PlanetSideGameObject with Container, obj : Equipment, index : Int) : TaskResolver.GiveTask = { TaskResolver.GiveTask( new Task() { private val localTarget = target @@ -2265,6 +2424,7 @@ class WorldSessionActor extends Actor with MDCContextAware { private val localObjectGUID = obj.GUID private val localAnnounce = self //self may not be the same when it executes private val localService = avatarService + private val localContinent = continent.Id override def isComplete : Task.Resolution.Value = { if(localTarget.Slot(localIndex).Equipment.contains(localObject)) { @@ -2283,7 +2443,7 @@ class WorldSessionActor extends Actor with MDCContextAware { override def onSuccess() : Unit = { localAnnounce ! ResponseToSelf(PacketCoding.CreateGamePacket(0, ObjectDeleteMessage(localObjectGUID, 0))) if(0 <= localIndex && localIndex < 5) { - localService ! AvatarServiceMessage(localTarget.Continent, AvatarAction.ObjectDelete(localTarget.GUID, localObjectGUID)) + localService ! AvatarServiceMessage(localContinent, AvatarAction.ObjectDelete(localTarget.GUID, localObjectGUID)) } } } @@ -2456,24 +2616,36 @@ class WorldSessionActor extends Actor with MDCContextAware { } } + /** + * Common preparation for interfacing with a vehicle. + * Join a vehicle-specific group for shared updates. + * Construct every object in the vehicle's inventory fpr shared manipulation updates. + * @param vehicle the vehicle + */ def AccessContents(vehicle : Vehicle) : Unit = { vehicleService ! Service.Join(s"${vehicle.Actor}") val parent_guid = vehicle.GUID vehicle.Trunk.Items.foreach({ case ((_, entry)) => val obj = entry.obj - val obj_def = obj.Definition + val objDef = obj.Definition sendResponse(PacketCoding.CreateGamePacket(0, ObjectCreateDetailedMessage( - obj_def.ObjectId, + objDef.ObjectId, obj.GUID, ObjectCreateMessageParent(parent_guid, entry.start), - obj_def.Packet.DetailedConstructorData(obj).get + objDef.Packet.DetailedConstructorData(obj).get ) )) }) } + /** + * Common preparation for disengaging from a vehicle. + * Leave the vehicle-specific group that was used for shared updates. + * Deconstruct every object in the vehicle's inventory. + * @param vehicle the vehicle + */ def UnAccessContents(vehicle : Vehicle) : Unit = { vehicleService ! Service.Leave(Some(s"${vehicle.Actor}")) vehicle.Trunk.Items.foreach({ @@ -2514,27 +2686,138 @@ class WorldSessionActor extends Actor with MDCContextAware { }) } - private def DeleteAmmunition(obj : PlanetSideGameObject with Container)(start : Int, item_guid : PlanetSideGUID) : Unit = { + /** + * Given an object that contains a box of amunition in its `Inventory` at a certain location, + * remove it permanently. + * @param obj the `Container` + * @param start where the ammunition can be found + * @param item an object to unregister (should have been the ammunition that was removed); + * not explicitly checked + */ + private def DeleteAmmunition(obj : PlanetSideGameObject with Container)(start : Int, item : AmmoBox) : Unit = { + val item_guid = item.GUID obj.Inventory -= start + taskResolver ! GUIDTask.UnregisterEquipment(item)(continent.GUID) sendResponse(PacketCoding.CreateGamePacket(0, ObjectDeleteMessage(item_guid, 0))) } - private def DeleteAmmunitionInVehicle(obj : Vehicle)(start : Int, item_guid : PlanetSideGUID) : Unit = { - DeleteAmmunition(obj)(start, item_guid) + /** + * Given a vehicle that contains a box of amunition in its `Trunk` at a certain location, + * remove it permanently. + * @see `DeleteAmmunition` + * @param obj the `Vehicle` + * @param start where the ammunition can be found + * @param item an object to unregister (should have been the ammunition that was removed); + * not explicitly checked + */ + private def DeleteAmmunitionInVehicle(obj : Vehicle)(start : Int, item : AmmoBox) : Unit = { + val item_guid = item.GUID + DeleteAmmunition(obj)(start, item) vehicleService ! VehicleServiceMessage(s"${obj.Actor}", VehicleAction.UnstowEquipment(player.GUID, item_guid)) } + /** + * Given an object that contains a box of amunition in its `Inventry` at a certain location, + * change the amount of ammunition within that box. + * @param obj the `Container` + * @param box an `AmmoBox` to modify + * @param reloadValue the value to modify the `AmmoBox`; + * subtracted from the current `Capacity` of `Box` + */ private def ModifyAmmunition(obj : PlanetSideGameObject with Container)(box : AmmoBox, reloadValue : Int) : Unit = { val capacity = box.Capacity - reloadValue box.Capacity = capacity sendResponse(PacketCoding.CreateGamePacket(0, InventoryStateMessage(box.GUID, obj.GUID, capacity))) } + /** + * Given a vehicle that contains a box of amunition in its `Trunk` at a certain location, + * change the amount of ammunition within that box. + * @param obj the `Container` + * @param box an `AmmoBox` to modify + * @param reloadValue the value to modify the `AmmoBox`; + * subtracted from the current `Capacity` of `Box` + */ private def ModifyAmmunitionInVehicle(obj : Vehicle)(box : AmmoBox, reloadValue : Int) : Unit = { val capacity = ModifyAmmunition(obj)(box, reloadValue) vehicleService ! VehicleServiceMessage(s"${obj.Actor}", VehicleAction.InventoryState(player.GUID, box, obj.GUID, obj.Find(box).get, box.Definition.Packet.DetailedConstructorData(box).get)) } + /** + * Announce that an already-registered `AmmoBox` object exists in a given position in some `Container` object's inventory. + * @see `StowAmmunitionInVehicles` + * @see `ChangeAmmoMessage` + * @param obj the `Container` object + * @param index an index in `obj`'s inventory + * @param item an `AmmoBox` + */ + def StowAmmunition(obj : PlanetSideGameObject with Container)(index : Int, item : AmmoBox) : Unit = { + obj.Inventory += index -> item + sendResponse(PacketCoding.CreateGamePacket(0, ObjectAttachMessage(obj.GUID, item.GUID, index))) + } + + /** + * Announce that an already-registered `AmmoBox` object exists in a given position in some vehicle's inventory. + * @see `StowAmmunition` + * @see `ChangeAmmoMessage` + * @param obj the `Vehicle` object + * @param index an index in `obj`'s inventory + * @param item an `AmmoBox` + */ + def StowAmmunitionInVehicles(obj : Vehicle)(index : Int, item : AmmoBox) : Unit = { + StowAmmunition(obj)(index, item) + vehicleService ! VehicleServiceMessage(s"${obj.Actor}", VehicleAction.StowEquipment(player.GUID, obj.GUID, index, item)) + } + + /** + * Prepare tasking that registers an `AmmoBox` object + * and announces that it exists in a given position in some `Container` object's inventory. + * `PutEquipmentInSlot` is the fastest way to achieve these goals. + * @see `StowNewAmmunitionInVehicles` + * @see `ChangeAmmoMessage` + * @param obj the `Container` object + * @param index an index in `obj`'s inventory + * @param item an `AmmoBox` + * @return a `TaskResolver.GiveTask` chain that executes the action + */ + def StowNewAmmunition(obj : PlanetSideGameObject with Container)(index : Int, item : AmmoBox) : TaskResolver.GiveTask = { + PutEquipmentInSlot(obj, item, index) + } + + /** + * Prepare tasking that registers an `AmmoBox` object + * and announces that it exists in a given position in some vehicle's inventory. + * `PutEquipmentInSlot` is the fastest way to achieve these goals. + * @see `StowNewAmmunition` + * @see `ChangeAmmoMessage` + * @param obj the `Container` object + * @param index an index in `obj`'s inventory + * @param item an `AmmoBox` + * @return a `TaskResolver.GiveTask` chain that executes the action + */ + def StowNewAmmunitionInVehicles(obj : Vehicle)(index : Int, item : AmmoBox) : TaskResolver.GiveTask = { + TaskResolver.GiveTask( + new Task() { + private val localService = vehicleService + private val localPlayer = player + private val localVehicle = obj + private val localIndex = index + private val localItem = item + + override def isComplete : Task.Resolution.Value = Task.Resolution.Success + + def Execute(resolver : ActorRef) : Unit = { + localService ! VehicleServiceMessage( + s"${localVehicle.Actor}", + VehicleAction.StowEquipment(localPlayer.GUID, localVehicle.GUID, localIndex, localItem) + ) + resolver ! scala.util.Success(this) + } + }, + List(StowNewAmmunition(obj)(index, item)) + ) + } + /** * A predicate used to determine if an `InventoryItem` object contains `Equipment` that should be dropped. * Used to filter through lists of object data before it is placed into a player's inventory. diff --git a/pslogin/src/main/scala/services/avatar/AvatarAction.scala b/pslogin/src/main/scala/services/avatar/AvatarAction.scala index ef0d4926..9a09742a 100644 --- a/pslogin/src/main/scala/services/avatar/AvatarAction.scala +++ b/pslogin/src/main/scala/services/avatar/AvatarAction.scala @@ -10,12 +10,13 @@ object AvatarAction { trait Action final case class ArmorChanged(player_guid : PlanetSideGUID, suit : ExoSuitType.Value, subtype : Int) extends Action + final case class ChangeAmmo(player_guid : PlanetSideGUID, weapon_guid : PlanetSideGUID, weapon_slot : Int, ammo_id : Int, ammo_guid : PlanetSideGUID, ammo_data : ConstructorData) extends Action final case class ChangeFireState_Start(player_guid : PlanetSideGUID, weapon_guid : PlanetSideGUID) extends Action final case class ChangeFireState_Stop(player_guid : PlanetSideGUID, weapon_guid : PlanetSideGUID) extends Action final case class ConcealPlayer(player_guid : PlanetSideGUID) extends Action //final case class DropItem(pos : Vector3, orient : Vector3, item : PlanetSideGUID) extends Action final case class EquipmentInHand(player_guid : PlanetSideGUID, slot : Int, item : Equipment) extends Action - final case class EquipmentOnGround(player_guid : PlanetSideGUID, pos : Vector3, orient : Vector3, item : Equipment) extends Action + final case class EquipmentOnGround(player_guid : PlanetSideGUID, pos : Vector3, orient : Vector3, item_id : Int, item_guid : PlanetSideGUID, item_data : ConstructorData) extends Action final case class LoadPlayer(player_guid : PlanetSideGUID, pdata : ConstructorData) extends Action // final case class LoadMap(msg : PlanetSideGUID) extends Action // final case class unLoadMap(msg : PlanetSideGUID) extends Action diff --git a/pslogin/src/main/scala/services/avatar/AvatarResponse.scala b/pslogin/src/main/scala/services/avatar/AvatarResponse.scala index 0a892825..1da7fcba 100644 --- a/pslogin/src/main/scala/services/avatar/AvatarResponse.scala +++ b/pslogin/src/main/scala/services/avatar/AvatarResponse.scala @@ -10,12 +10,13 @@ object AvatarResponse { trait Response final case class ArmorChanged(suit : ExoSuitType.Value, subtype : Int) extends Response + final case class ChangeAmmo(weapon_guid : PlanetSideGUID, weapon_slot : Int, ammo_id : Int, ammo_guid : PlanetSideGUID, ammo_data : ConstructorData) extends Response final case class ChangeFireState_Start(weapon_guid : PlanetSideGUID) extends Response final case class ChangeFireState_Stop(weapon_guid : PlanetSideGUID) extends Response final case class ConcealPlayer() extends Response //final case class DropItem(pos : Vector3, orient : Vector3, item : PlanetSideGUID) extends Response final case class EquipmentInHand(slot : Int, item : Equipment) extends Response - final case class EquipmentOnGround(pos : Vector3, orient : Vector3, item : Equipment) extends Response + final case class EquipmentOnGround(pos : Vector3, orient : Vector3, item_id : Int, item_guid : PlanetSideGUID, item_data : ConstructorData) extends Response final case class LoadPlayer(pdata : ConstructorData) extends Response // final case class unLoadMap() extends Response // final case class LoadMap() extends Response diff --git a/pslogin/src/main/scala/services/avatar/AvatarService.scala b/pslogin/src/main/scala/services/avatar/AvatarService.scala index 09196cb1..5e47c3e8 100644 --- a/pslogin/src/main/scala/services/avatar/AvatarService.scala +++ b/pslogin/src/main/scala/services/avatar/AvatarService.scala @@ -39,6 +39,10 @@ class AvatarService extends Actor { AvatarEvents.publish( AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.ArmorChanged(suit, subtype)) ) + case AvatarAction.ChangeAmmo(player_guid, weapon_guid, weapon_slot, ammo_id, ammo_guid, ammo_data) => + AvatarEvents.publish( + AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.ChangeAmmo(weapon_guid, weapon_slot, ammo_id, ammo_guid, ammo_data)) + ) case AvatarAction.ChangeFireState_Start(player_guid, weapon_guid) => AvatarEvents.publish( AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.ChangeFireState_Start(weapon_guid)) @@ -55,9 +59,9 @@ class AvatarService extends Actor { AvatarEvents.publish( AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.EquipmentInHand(slot, obj)) ) - case AvatarAction.EquipmentOnGround(player_guid, pos, orient, obj) => + case AvatarAction.EquipmentOnGround(player_guid, pos, orient, item_id, item_guid, item_data) => AvatarEvents.publish( - AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.EquipmentOnGround(pos, orient, obj)) + AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.EquipmentOnGround(pos, orient, item_id, item_guid, item_data)) ) case AvatarAction.LoadPlayer(player_guid, pdata) => AvatarEvents.publish( diff --git a/pslogin/src/main/test/scala/AvatarServiceTest.scala b/pslogin/src/main/test/scala/AvatarServiceTest.scala index 9c43ac25..6bbdb9bf 100644 --- a/pslogin/src/main/test/scala/AvatarServiceTest.scala +++ b/pslogin/src/main/test/scala/AvatarServiceTest.scala @@ -94,14 +94,15 @@ class AvatarService5Test extends ActorTest { } class AvatarService6Test extends ActorTest { - val tool = Tool(GlobalDefinitions.beamer) + val toolDef = GlobalDefinitions.beamer + val tool = Tool(toolDef) "AvatarService" should { "pass EquipmentOnGround" in { val service = system.actorOf(Props[AvatarService], "service") service ! Service.Join("test") - service ! AvatarServiceMessage("test", AvatarAction.EquipmentOnGround(PlanetSideGUID(10), Vector3(300f, 200f, 100f), Vector3(450f, 300f, 150f), tool)) - expectMsg(AvatarServiceResponse("/test/Avatar", PlanetSideGUID(10), AvatarResponse.EquipmentOnGround(Vector3(300f, 200f, 100f), Vector3(450f, 300f, 150f), tool))) + service ! AvatarServiceMessage("test", AvatarAction.EquipmentOnGround(PlanetSideGUID(10), Vector3(300f, 200f, 100f), Vector3(450f, 300f, 150f), toolDef.ObjectId, PlanetSideGUID(11), toolDef.Packet.ConstructorData(tool).get)) + expectMsg(AvatarServiceResponse("/test/Avatar", PlanetSideGUID(10), AvatarResponse.EquipmentOnGround(Vector3(300f, 200f, 100f), Vector3(450f, 300f, 150f), toolDef.ObjectId, PlanetSideGUID(11), toolDef.Packet.ConstructorData(tool).get))) } } } From 1f04c840d2534fa2c66e56359e9bf5a9258ffe1b Mon Sep 17 00:00:00 2001 From: FateJH Date: Wed, 10 Jan 2018 14:19:45 -0500 Subject: [PATCH 3/7] synchronized change fire mode; addsupport for fire mode in DetailedWeaponData; corrected bug with LastDrawnSlot/ObjectHeldMessage --- .../psforever/objects/GlobalDefinitions.scala | 7 +- .../scala/net/psforever/objects/Player.scala | 9 +- .../scala/net/psforever/objects/Tool.scala | 10 +- .../definition/AmmoBoxDefinition.scala | 4 +- .../objects/definition/AvatarDefinition.scala | 4 +- .../objects/definition/KitDefinition.scala | 4 +- .../objects/definition/ToolDefinition.scala | 4 +- .../definition/VehicleDefinition.scala | 4 +- .../definition/converter/ToolConverter.scala | 2 +- .../equipment/FireModeDefinition.scala | 8 - .../objectcreate/DetailedWeaponData.scala | 36 ++- .../packet/game/objectcreate/WeaponData.scala | 2 +- .../DetailedCharacterDataTest.scala | 104 ++++----- .../DetailedWeaponDataTest.scala | 2 +- .../test/scala/objects/ConverterTest.scala | 44 +++- .../test/scala/objects/EquipmentTest.scala | 3 - .../src/test/scala/objects/PlayerTest.scala | 12 +- .../src/main/scala/WorldSessionActor.scala | 215 +++++++++--------- .../scala/services/avatar/AvatarAction.scala | 4 +- .../services/avatar/AvatarResponse.scala | 4 +- .../scala/services/avatar/AvatarService.scala | 8 +- .../main/test/scala/AvatarServiceTest.scala | 67 +++++- 22 files changed, 333 insertions(+), 224 deletions(-) diff --git a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala index 30395f27..03f64231 100644 --- a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala +++ b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala @@ -1032,10 +1032,12 @@ object GlobalDefinitions { chainblade.FireModes.head.AmmoTypeIndices += 0 chainblade.FireModes.head.AmmoSlotIndex = 0 chainblade.FireModes.head.Magazine = 1 + chainblade.FireModes.head.Chamber = 0 chainblade.FireModes += new FireModeDefinition chainblade.FireModes(1).AmmoTypeIndices += 0 chainblade.FireModes(1).AmmoSlotIndex = 0 chainblade.FireModes(1).Magazine = 1 + chainblade.FireModes.head.Chamber = 0 magcutter.Size = EquipmentSize.Melee magcutter.AmmoTypes += melee_ammo @@ -1043,10 +1045,12 @@ object GlobalDefinitions { magcutter.FireModes.head.AmmoTypeIndices += 0 magcutter.FireModes.head.AmmoSlotIndex = 0 magcutter.FireModes.head.Magazine = 1 + magcutter.FireModes.head.Chamber = 0 magcutter.FireModes += new FireModeDefinition magcutter.FireModes(1).AmmoTypeIndices += 0 magcutter.FireModes(1).AmmoSlotIndex = 0 magcutter.FireModes(1).Magazine = 1 + magcutter.FireModes.head.Chamber = 0 forceblade.Size = EquipmentSize.Melee forceblade.AmmoTypes += melee_ammo @@ -1583,7 +1587,8 @@ object GlobalDefinitions { trek.FireModes += new FireModeDefinition trek.FireModes(1).AmmoTypeIndices += 0 trek.FireModes(1).AmmoSlotIndex = 0 - trek.FireModes(1).Magazine = 0 + trek.FireModes(1).Magazine = 1 + trek.FireModes(1).Chamber = 0 trek.Tile = InventoryTile.Tile33 flail_targeting_laser.Packet = new CommandDetonaterConverter diff --git a/common/src/main/scala/net/psforever/objects/Player.scala b/common/src/main/scala/net/psforever/objects/Player.scala index 90efc893..52425265 100644 --- a/common/src/main/scala/net/psforever/objects/Player.scala +++ b/common/src/main/scala/net/psforever/objects/Player.scala @@ -31,7 +31,7 @@ class Player(private val name : String, private val fifthSlot : EquipmentSlot = new OffhandEquipmentSlot(EquipmentSize.Inventory) private val inventory : GridInventory = GridInventory() private var drawnSlot : Int = Player.HandsDownSlot - private var lastDrawnSlot : Int = 0 + private var lastDrawnSlot : Int = Player.HandsDownSlot private val loadouts : Array[Option[Loadout]] = Array.fill[Option[Loadout]](10)(None) @@ -325,16 +325,15 @@ class Player(private val name : String, def DrawnSlot : Int = drawnSlot - def DrawnSlot_=(slot : Int = Player.HandsDownSlot) : Int = { + def DrawnSlot_=(slot : Int) : Int = { if(slot != drawnSlot) { - val origDrawnSlot : Int = drawnSlot if(slot == Player.HandsDownSlot) { drawnSlot = slot } - else if(-1 < slot && slot < 5 && holsters(slot).Equipment.isDefined) { + else if(VisibleSlots.contains(slot) && holsters(slot).Equipment.isDefined) { drawnSlot = slot + lastDrawnSlot = slot } - lastDrawnSlot = if(-1 < origDrawnSlot && origDrawnSlot < 5) { origDrawnSlot } else { lastDrawnSlot } } DrawnSlot } diff --git a/common/src/main/scala/net/psforever/objects/Tool.scala b/common/src/main/scala/net/psforever/objects/Tool.scala index 068f13a0..df6c3df1 100644 --- a/common/src/main/scala/net/psforever/objects/Tool.scala +++ b/common/src/main/scala/net/psforever/objects/Tool.scala @@ -1,7 +1,7 @@ // Copyright (c) 2017 PSForever package net.psforever.objects -import net.psforever.objects.definition.ToolDefinition +import net.psforever.objects.definition.{AmmoBoxDefinition, ToolDefinition} import net.psforever.objects.equipment.{Ammo, Equipment, FireModeDefinition, FireModeSwitch} import scala.annotation.tailrec @@ -126,7 +126,7 @@ object Tool { */ private var ammoTypeIndex : Int = 0 /** a reference to the actual `AmmoBox` of this slot */ - private var box : AmmoBox = AmmoBox(tdef.AmmoTypes(ammoTypeIndex), fdef.Magazine) + private var box : AmmoBox = AmmoBox(AmmoDefinition, fdef.Magazine) def AmmoTypeIndex : Int = ammoTypeIndex @@ -135,13 +135,17 @@ object Tool { AmmoTypeIndex } + private def AmmoDefinition : AmmoBoxDefinition = { + tdef.AmmoTypes(fdef.AmmoTypeIndices(ammoTypeIndex)) + } + /** * This is a reference to the `Ammo.Value` whose `AmmoBoxDefinition` should be loaded into `box`. * It may not be the correct `Ammo.Value` whose `AmmoBoxDefinition` is loaded into `box` such as is the case during ammunition swaps. * Generally, convert from this index, to the index in the fire mode's ammunition list, to the index in the `ToolDefinition`'s ammunition list. * @return the `Ammo` type that should be loaded into the magazine right now */ - def AmmoType : Ammo.Value = tdef.AmmoTypes(fdef.AmmoTypeIndices(ammoTypeIndex)).AmmoType + def AmmoType : Ammo.Value = AmmoDefinition.AmmoType def AllAmmoTypes : List[Ammo.Value] = { fdef.AmmoTypeIndices.map(index => tdef.AmmoTypes(fdef.AmmoTypeIndices(index)).AmmoType).toList diff --git a/common/src/main/scala/net/psforever/objects/definition/AmmoBoxDefinition.scala b/common/src/main/scala/net/psforever/objects/definition/AmmoBoxDefinition.scala index 9598245f..4a7f4b96 100644 --- a/common/src/main/scala/net/psforever/objects/definition/AmmoBoxDefinition.scala +++ b/common/src/main/scala/net/psforever/objects/definition/AmmoBoxDefinition.scala @@ -10,7 +10,7 @@ class AmmoBoxDefinition(objectId : Int) extends EquipmentDefinition(objectId) { private var capacity : Int = 1 Name = "ammo box" Size = EquipmentSize.Inventory - Packet = new AmmoBoxConverter() + Packet = AmmoBoxDefinition.converter def AmmoType : Ammo.Value = ammoType @@ -23,6 +23,8 @@ class AmmoBoxDefinition(objectId : Int) extends EquipmentDefinition(objectId) { } object AmmoBoxDefinition { + private val converter = new AmmoBoxConverter() + def apply(objectId: Int) : AmmoBoxDefinition = { new AmmoBoxDefinition(objectId) } diff --git a/common/src/main/scala/net/psforever/objects/definition/AvatarDefinition.scala b/common/src/main/scala/net/psforever/objects/definition/AvatarDefinition.scala index 4f265ccf..3d26b714 100644 --- a/common/src/main/scala/net/psforever/objects/definition/AvatarDefinition.scala +++ b/common/src/main/scala/net/psforever/objects/definition/AvatarDefinition.scala @@ -10,10 +10,12 @@ import net.psforever.objects.definition.converter.AvatarConverter */ class AvatarDefinition(objectId : Int) extends ObjectDefinition(objectId) { Avatars(objectId) //let throw NoSuchElementException - Packet = new AvatarConverter() + Packet = AvatarDefinition.converter } object AvatarDefinition { + private val converter = new AvatarConverter() + def apply(objectId: Int) : AvatarDefinition = { new AvatarDefinition(objectId) } diff --git a/common/src/main/scala/net/psforever/objects/definition/KitDefinition.scala b/common/src/main/scala/net/psforever/objects/definition/KitDefinition.scala index f9d7ceae..ca318554 100644 --- a/common/src/main/scala/net/psforever/objects/definition/KitDefinition.scala +++ b/common/src/main/scala/net/psforever/objects/definition/KitDefinition.scala @@ -15,10 +15,12 @@ class KitDefinition(objectId : Int) extends EquipmentDefinition(objectId) { Size = EquipmentSize.Inventory Tile = InventoryTile.Tile42 Name = "kit" - Packet = new KitConverter() + Packet = KitDefinition.converter } object KitDefinition { + private val converter = new KitConverter() + def apply(objectId: Int) : KitDefinition = { new KitDefinition(objectId) } diff --git a/common/src/main/scala/net/psforever/objects/definition/ToolDefinition.scala b/common/src/main/scala/net/psforever/objects/definition/ToolDefinition.scala index f40e3946..54cda716 100644 --- a/common/src/main/scala/net/psforever/objects/definition/ToolDefinition.scala +++ b/common/src/main/scala/net/psforever/objects/definition/ToolDefinition.scala @@ -10,7 +10,7 @@ class ToolDefinition(objectId : Int) extends EquipmentDefinition(objectId) { private val ammoTypes : mutable.ListBuffer[AmmoBoxDefinition] = new mutable.ListBuffer[AmmoBoxDefinition] private val fireModes : mutable.ListBuffer[FireModeDefinition] = new mutable.ListBuffer[FireModeDefinition] Name = "tool" - Packet = new ToolConverter() + Packet = ToolDefinition.converter def AmmoTypes : mutable.ListBuffer[AmmoBoxDefinition] = ammoTypes @@ -18,6 +18,8 @@ class ToolDefinition(objectId : Int) extends EquipmentDefinition(objectId) { } object ToolDefinition { + private val converter = new ToolConverter() + def apply(objectId : Int) : ToolDefinition = { new ToolDefinition(objectId) } diff --git a/common/src/main/scala/net/psforever/objects/definition/VehicleDefinition.scala b/common/src/main/scala/net/psforever/objects/definition/VehicleDefinition.scala index 708741ef..a84e97c6 100644 --- a/common/src/main/scala/net/psforever/objects/definition/VehicleDefinition.scala +++ b/common/src/main/scala/net/psforever/objects/definition/VehicleDefinition.scala @@ -26,7 +26,7 @@ class VehicleDefinition(objectId : Int) extends ObjectDefinition(objectId) { private var canCloak : Boolean = false private var canBeOwned : Boolean = true Name = "vehicle" - Packet = new VehicleConverter + Packet = VehicleDefinition.converter def MaxHealth : Int = maxHealth @@ -87,6 +87,8 @@ class VehicleDefinition(objectId : Int) extends ObjectDefinition(objectId) { } object VehicleDefinition { + private val converter = new VehicleConverter + def apply(objectId: Int) : VehicleDefinition = { new VehicleDefinition(objectId) } diff --git a/common/src/main/scala/net/psforever/objects/definition/converter/ToolConverter.scala b/common/src/main/scala/net/psforever/objects/definition/converter/ToolConverter.scala index eb386d6a..5718ff16 100644 --- a/common/src/main/scala/net/psforever/objects/definition/converter/ToolConverter.scala +++ b/common/src/main/scala/net/psforever/objects/definition/converter/ToolConverter.scala @@ -25,6 +25,6 @@ class ToolConverter extends ObjectCreateConverter[Tool]() { val box = obj.AmmoSlots(index).Box slots += InternalSlot(box.Definition.ObjectId, box.GUID, index, box.Definition.Packet.DetailedConstructorData(box).get) }) - Success(DetailedWeaponData(4,8, slots.toList)(maxSlot)) + Success(DetailedWeaponData(4,8, obj.FireModeIndex, slots.toList)(maxSlot)) } } diff --git a/common/src/main/scala/net/psforever/objects/equipment/FireModeDefinition.scala b/common/src/main/scala/net/psforever/objects/equipment/FireModeDefinition.scala index e1f7862b..423154bc 100644 --- a/common/src/main/scala/net/psforever/objects/equipment/FireModeDefinition.scala +++ b/common/src/main/scala/net/psforever/objects/equipment/FireModeDefinition.scala @@ -9,7 +9,6 @@ class FireModeDefinition { private var chamber : Int = 1 //how many rounds are queued to be fired at once, e.g., 3 for the Jackhammer's triple burst private var magazine : Int = 1 //how many rounds are queued for each reload cycle // private var target : Any = _ //target designation (self? other?) - private var resetAmmoIndexOnSwap : Boolean = false //when changing fire modes, do not attempt to match previous mode's ammo type //damage modifiers will follow here ... @@ -46,13 +45,6 @@ class FireModeDefinition { // target = setAsTarget // Target // } - - def ResetAmmoIndexOnSwap : Boolean = resetAmmoIndexOnSwap - - def ResetAmmoIndexOnSwap_=(reset : Boolean) : Boolean = { - resetAmmoIndexOnSwap = reset - ResetAmmoIndexOnSwap - } } object FireModeDefinition { diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/DetailedWeaponData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/DetailedWeaponData.scala index 9e4ecaf8..ba55ad81 100644 --- a/common/src/main/scala/net/psforever/packet/game/objectcreate/DetailedWeaponData.scala +++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/DetailedWeaponData.scala @@ -28,6 +28,7 @@ import shapeless.{::, HNil} */ final case class DetailedWeaponData(unk1 : Int, unk2 : Int, + fire_mode : Int, ammo : List[InternalSlot] )(implicit val mag_capacity : Int = 1) extends ConstructorData { override def bitsize : Long = { @@ -35,7 +36,7 @@ final case class DetailedWeaponData(unk1 : Int, for(o <- ammo) { bitsize += o.bitsize } - 61L + bitsize + 61L + bitsize //51 + 10 (from InventoryData) + ammo } } @@ -51,7 +52,21 @@ object DetailedWeaponData extends Marshallable[DetailedWeaponData] { * @return a `DetailedWeaponData` object */ def apply(unk1 : Int, unk2 : Int, cls : Int, guid : PlanetSideGUID, parentSlot : Int, ammo : DetailedAmmoBoxData) : DetailedWeaponData = - new DetailedWeaponData(unk1, unk2, InternalSlot(cls, guid, parentSlot, ammo) :: Nil) + new DetailedWeaponData(unk1, unk2, 0, InternalSlot(cls, guid, parentSlot, ammo) :: Nil) + + + /** + * Overloaded constructor for creating `DetailedWeaponData` while masking use of `InternalSlot` for its `DetailedAmmoBoxData`. + * @param unk1 na + * @param unk2 na + * @param cls the code for the type of object (ammunition) being constructed + * @param guid the globally unique id assigned to the ammunition + * @param parentSlot the slot where the ammunition is to be installed in the weapon + * @param ammo the constructor data for the ammunition + * @return a `DetailedWeaponData` object + */ + def apply(unk1 : Int, unk2 : Int, fire_mode : Int, cls : Int, guid : PlanetSideGUID, parentSlot : Int, ammo : DetailedAmmoBoxData) : DetailedWeaponData = + new DetailedWeaponData(unk1, unk2, fire_mode, InternalSlot(cls, guid, parentSlot, ammo) :: Nil) /** * A `Codec` for `DetailedWeaponData` @@ -60,17 +75,18 @@ object DetailedWeaponData extends Marshallable[DetailedWeaponData] { * @return a `WeaponData` object or a `BitVector` */ def codec(mag_capacity : Int = 1) : Codec[DetailedWeaponData] = ( - ("unk1" | uintL(3)) :: + ("unk1" | uint(3)) :: bool :: //weapon refuses to shoot if set (not weapons lock?) ("unk2" | uint4L) :: //8 - common; 4 - jammers weapons; 2 - weapon breaks; 1, 0 - safe uint24 :: - uint16 :: - uint2L :: + uint(12) :: + ("fire_mode" | uint(3)) :: //TODO size? + uint(3) :: ("ammo" | InventoryData.codec_detailed) :: bool ).exmap[DetailedWeaponData] ( { - case unk1 :: false :: unk2 :: 2 :: 0 :: 3 :: InventoryData(ammo) :: false :: HNil => + case unk1 :: false :: unk2 :: 2 :: 0 :: fmode :: 3 :: InventoryData(ammo) :: false :: HNil => val magSize = ammo.size if(mag_capacity == 0 || magSize == 0) { Attempt.failure(Err("weapon must decode some ammunition")) @@ -79,21 +95,21 @@ object DetailedWeaponData extends Marshallable[DetailedWeaponData] { Attempt.failure(Err(s"weapon decodes too much or too little ammunition - actual $magSize, expected $mag_capacity")) } else { - Attempt.successful(DetailedWeaponData(unk1, unk2, ammo)(magSize)) + Attempt.successful(DetailedWeaponData(unk1, unk2, fmode, ammo)(magSize)) } case _ => Attempt.failure(Err("invalid weapon data format")) }, { - case obj @ DetailedWeaponData(unk1, unk2, ammo) => + case obj @ DetailedWeaponData(unk1, unk2, fmode, ammo) => val magSize = ammo.size val magCapacity = obj.mag_capacity if(mag_capacity == 0 || magCapacity == 0 || magSize == 0) { Attempt.failure(Err("weapon must encode some ammunition")) } else if(magCapacity < 0 || mag_capacity < 0) { - Attempt.successful(unk1 :: false :: unk2 :: 2 :: 0 :: 3 :: InventoryData(ammo) :: false :: HNil) + Attempt.successful(unk1 :: false :: unk2 :: 2 :: 0 :: fmode :: 3 :: InventoryData(ammo) :: false :: HNil) } else { if(magCapacity != mag_capacity) { @@ -106,7 +122,7 @@ object DetailedWeaponData extends Marshallable[DetailedWeaponData] { Attempt.failure(Err("weapon encodes too much ammunition (255+ types!)")) } else { - Attempt.successful(unk1 :: false :: unk2 :: 2 :: 0 :: 3 :: InventoryData(ammo) :: false :: HNil) + Attempt.successful(unk1 :: false :: unk2 :: 2 :: 0 :: fmode :: 3 :: InventoryData(ammo) :: false :: HNil) } } diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/WeaponData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/WeaponData.scala index 1bd19eff..b27e53d8 100644 --- a/common/src/main/scala/net/psforever/packet/game/objectcreate/WeaponData.scala +++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/WeaponData.scala @@ -102,7 +102,7 @@ object WeaponData extends Marshallable[WeaponData] { bool :: //weapon refuses to shoot if set (not weapons lock?) ("unk2" | uint4L) :: //8 - common; 4 - jammers weapons; 2 - weapon breaks; 1, 0 - safe uint(20) :: - ("fire_mode" | int(3)) :: + ("fire_mode" | int(3)) :: //TODO size? bool :: bool :: ("ammo" | InventoryData.codec) :: diff --git a/common/src/test/scala/game/objectcreatedetailed/DetailedCharacterDataTest.scala b/common/src/test/scala/game/objectcreatedetailed/DetailedCharacterDataTest.scala index 89f69019..38a31b52 100644 --- a/common/src/test/scala/game/objectcreatedetailed/DetailedCharacterDataTest.scala +++ b/common/src/test/scala/game/objectcreatedetailed/DetailedCharacterDataTest.scala @@ -652,59 +652,59 @@ class DetailedCharacterDataTest extends Specification { InventoryData( List( InternalSlot(531, PlanetSideGUID(4202), 0, - DetailedWeaponData(2, 8, List(InternalSlot(389, PlanetSideGUID(3942), 0,DetailedAmmoBoxData(8, 100)))) + DetailedWeaponData(2, 8, 0, List(InternalSlot(389, PlanetSideGUID(3942), 0,DetailedAmmoBoxData(8, 100)))) ), InternalSlot(132, PlanetSideGUID(6924), 1, - DetailedWeaponData(2, 8, List(InternalSlot(111, PlanetSideGUID(9157), 0, DetailedAmmoBoxData(8, 100)))) + DetailedWeaponData(2, 8, 0, List(InternalSlot(111, PlanetSideGUID(9157), 0, DetailedAmmoBoxData(8, 100)))) ), InternalSlot(714, PlanetSideGUID(8498), 2, - DetailedWeaponData(2, 8, List(InternalSlot(755, PlanetSideGUID(5356), 0, DetailedAmmoBoxData(8, 16)))) + DetailedWeaponData(2, 8, 0, List(InternalSlot(755, PlanetSideGUID(5356), 0, DetailedAmmoBoxData(8, 16)))) ), InternalSlot(468, PlanetSideGUID(7198), 4, - DetailedWeaponData(2, 8, List(InternalSlot(540, PlanetSideGUID(5009), 0, DetailedAmmoBoxData(8, 1)))) + DetailedWeaponData(2, 8, 0, List(InternalSlot(540, PlanetSideGUID(5009), 0, DetailedAmmoBoxData(8, 1)))) ), InternalSlot(456, PlanetSideGUID(5374), 5, DetailedLockerContainerData(8, Some(InventoryData(List( InternalSlot(429, PlanetSideGUID(3021), 0, - DetailedWeaponData(6, 8, List(InternalSlot(272, PlanetSideGUID(8729), 0, DetailedAmmoBoxData(8, 0)))) + DetailedWeaponData(6, 8, 0, List(InternalSlot(272, PlanetSideGUID(8729), 0, DetailedAmmoBoxData(8, 0)))) ), InternalSlot(838, PlanetSideGUID(8467), 9, - DetailedWeaponData(6, 8, List(InternalSlot(839, PlanetSideGUID(8603), 0, DetailedAmmoBoxData(8, 5)))) + DetailedWeaponData(6, 8, 0, List(InternalSlot(839, PlanetSideGUID(8603), 0, DetailedAmmoBoxData(8, 5)))) ), InternalSlot(272, PlanetSideGUID(3266), 18, DetailedAmmoBoxData(8, 27)), InternalSlot(577, PlanetSideGUID(2934), 22, - DetailedWeaponData(6, 8, List(InternalSlot(111, PlanetSideGUID(4682), 0, DetailedAmmoBoxData(8, 100)))) + DetailedWeaponData(6, 8, 0, List(InternalSlot(111, PlanetSideGUID(4682), 0, DetailedAmmoBoxData(8, 100)))) ), InternalSlot(839, PlanetSideGUID(3271), 90, DetailedAmmoBoxData(8, 15)), InternalSlot(839, PlanetSideGUID(7174), 94, DetailedAmmoBoxData(8, 6)), InternalSlot(429, PlanetSideGUID(6084), 98, - DetailedWeaponData(6, 8, List(InternalSlot(272, PlanetSideGUID(5928), 0, DetailedAmmoBoxData(8, 35)))) + DetailedWeaponData(6, 8, 0, List(InternalSlot(272, PlanetSideGUID(5928), 0, DetailedAmmoBoxData(8, 35)))) ), InternalSlot(462, PlanetSideGUID(5000), 108, - DetailedWeaponData(6, 8, List(InternalSlot(463, PlanetSideGUID(6277), 0, DetailedAmmoBoxData(8, 150)))) + DetailedWeaponData(6, 8, 0, List(InternalSlot(463, PlanetSideGUID(6277), 0, DetailedAmmoBoxData(8, 150)))) ), InternalSlot(429, PlanetSideGUID(4341), 189, - DetailedWeaponData(6, 8, List(InternalSlot(272, PlanetSideGUID(7043), 0, DetailedAmmoBoxData(8, 35)))) + DetailedWeaponData(6, 8, 0, List(InternalSlot(272, PlanetSideGUID(7043), 0, DetailedAmmoBoxData(8, 35)))) ), InternalSlot(556, PlanetSideGUID(4168), 198, - DetailedWeaponData(6, 8, List(InternalSlot(28, PlanetSideGUID(8937), 0, DetailedAmmoBoxData(8, 100)))) + DetailedWeaponData(6, 8, 0, List(InternalSlot(28, PlanetSideGUID(8937), 0, DetailedAmmoBoxData(8, 100)))) ), InternalSlot(272, PlanetSideGUID(3173), 207, DetailedAmmoBoxData(8, 50)), InternalSlot(462, PlanetSideGUID(3221), 210, - DetailedWeaponData(6, 8, List(InternalSlot(463, PlanetSideGUID(4031), 0, DetailedAmmoBoxData(8, 150)))) + DetailedWeaponData(6, 8, 0, List(InternalSlot(463, PlanetSideGUID(4031), 0, DetailedAmmoBoxData(8, 150)))) ), InternalSlot(556, PlanetSideGUID(6853), 280, - DetailedWeaponData(6, 8, List(InternalSlot(29, PlanetSideGUID(8524), 0, DetailedAmmoBoxData(8, 67)))) + DetailedWeaponData(6, 8, 0, List(InternalSlot(29, PlanetSideGUID(8524), 0, DetailedAmmoBoxData(8, 67)))) ), InternalSlot(556, PlanetSideGUID(4569), 290, - DetailedWeaponData(6, 8, List(InternalSlot(28, PlanetSideGUID(5584), 0, DetailedAmmoBoxData(8, 100)))) + DetailedWeaponData(6, 8, 0, List(InternalSlot(28, PlanetSideGUID(5584), 0, DetailedAmmoBoxData(8, 100)))) ), InternalSlot(462, PlanetSideGUID(9294), 300, - DetailedWeaponData(6, 8, List(InternalSlot(463, PlanetSideGUID(3118), 0, DetailedAmmoBoxData(8, 150)))) + DetailedWeaponData(6, 8, 0, List(InternalSlot(463, PlanetSideGUID(3118), 0, DetailedAmmoBoxData(8, 150)))) ), InternalSlot(272, PlanetSideGUID(4759), 387, DetailedAmmoBoxData(8, 50)), InternalSlot(462, PlanetSideGUID(7377), 390, - DetailedWeaponData(6, 8, List(InternalSlot(463, PlanetSideGUID(8155), 0, DetailedAmmoBoxData(8, 150)))) + DetailedWeaponData(6, 8, 0, List(InternalSlot(463, PlanetSideGUID(8155), 0, DetailedAmmoBoxData(8, 150)))) ), InternalSlot(843, PlanetSideGUID(6709), 480, DetailedAmmoBoxData(8, 1)), InternalSlot(843, PlanetSideGUID(5276), 484, DetailedAmmoBoxData(8, 1)), @@ -714,109 +714,109 @@ class DetailedCharacterDataTest extends Specification { InternalSlot(842, PlanetSideGUID(7279), 500, DetailedAmmoBoxData(8, 1)), InternalSlot(842, PlanetSideGUID(5415), 504, DetailedAmmoBoxData(8, 1)), InternalSlot(175, PlanetSideGUID(5741), 540, - DetailedWeaponData(6, 8, List(InternalSlot(540, PlanetSideGUID(5183), 0, DetailedAmmoBoxData(8, 1)))) + DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(5183), 0, DetailedAmmoBoxData(8, 1)))) ), InternalSlot(324, PlanetSideGUID(6208), 541, - DetailedWeaponData(6, 8, List(InternalSlot(540, PlanetSideGUID(5029), 0, DetailedAmmoBoxData(8, 1)))) + DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(5029), 0, DetailedAmmoBoxData(8, 1)))) ), InternalSlot(324, PlanetSideGUID(8589), 542, - DetailedWeaponData(6, 8, List(InternalSlot(540, PlanetSideGUID(9217), 0, DetailedAmmoBoxData(8, 1)))) + DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(9217), 0, DetailedAmmoBoxData(8, 1)))) ), InternalSlot(175, PlanetSideGUID(8901), 543, - DetailedWeaponData(6, 8, List(InternalSlot(540, PlanetSideGUID(7633), 0, DetailedAmmoBoxData(8, 1)))) + DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(7633), 0, DetailedAmmoBoxData(8, 1)))) ), InternalSlot(175, PlanetSideGUID(8419), 544, - DetailedWeaponData(6, 8, List(InternalSlot(540, PlanetSideGUID(6546), 0, DetailedAmmoBoxData(8, 1)))) + DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(6546), 0, DetailedAmmoBoxData(8, 1)))) ), InternalSlot(175, PlanetSideGUID(4715), 545, - DetailedWeaponData(6, 8, List(InternalSlot(540, PlanetSideGUID(8453), 0, DetailedAmmoBoxData(8, 1)))) + DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(8453), 0, DetailedAmmoBoxData(8, 1)))) ), InternalSlot(324, PlanetSideGUID(3577), 546, - DetailedWeaponData(6, 8, List(InternalSlot(540, PlanetSideGUID(9202), 0, DetailedAmmoBoxData(8, 1)))) + DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(9202), 0, DetailedAmmoBoxData(8, 1)))) ), InternalSlot(324, PlanetSideGUID(6003), 547, - DetailedWeaponData(6, 8, List(InternalSlot(540, PlanetSideGUID(3260), 0, DetailedAmmoBoxData(8, 1)))) + DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(3260), 0, DetailedAmmoBoxData(8, 1)))) ), InternalSlot(324, PlanetSideGUID(9140), 548, - DetailedWeaponData(6, 8, List(InternalSlot(540,PlanetSideGUID(3815),0,DetailedAmmoBoxData(8, 1)))) + DetailedWeaponData(6, 8, 0, List(InternalSlot(540,PlanetSideGUID(3815),0,DetailedAmmoBoxData(8, 1)))) ), InternalSlot(324, PlanetSideGUID(4913), 549, - DetailedWeaponData(6, 8, List(InternalSlot(540,PlanetSideGUID(7222),0,DetailedAmmoBoxData(8, 1)))) + DetailedWeaponData(6, 8, 0, List(InternalSlot(540,PlanetSideGUID(7222),0,DetailedAmmoBoxData(8, 1)))) ), InternalSlot(324, PlanetSideGUID(6954), 550, - DetailedWeaponData(6, 8, List(InternalSlot(540,PlanetSideGUID(2953),0,DetailedAmmoBoxData(8, 1)))) + DetailedWeaponData(6, 8, 0, List(InternalSlot(540,PlanetSideGUID(2953),0,DetailedAmmoBoxData(8, 1)))) ), InternalSlot(324, PlanetSideGUID(6405), 551, - DetailedWeaponData(6, 8, List(InternalSlot(540,PlanetSideGUID(4676),0,DetailedAmmoBoxData(8, 1)))) + DetailedWeaponData(6, 8, 0, List(InternalSlot(540,PlanetSideGUID(4676),0,DetailedAmmoBoxData(8, 1)))) ), InternalSlot(324, PlanetSideGUID(8915), 552, - DetailedWeaponData(6, 8, List(InternalSlot(540,PlanetSideGUID(4018),0,DetailedAmmoBoxData(8, 1)))) + DetailedWeaponData(6, 8, 0, List(InternalSlot(540,PlanetSideGUID(4018),0,DetailedAmmoBoxData(8, 1)))) ), InternalSlot(324, PlanetSideGUID(4993), 553, - DetailedWeaponData(6, 8, List(InternalSlot(540,PlanetSideGUID(6775),0,DetailedAmmoBoxData(8, 1)))) + DetailedWeaponData(6, 8, 0, List(InternalSlot(540,PlanetSideGUID(6775),0,DetailedAmmoBoxData(8, 1)))) ), InternalSlot(175, PlanetSideGUID(5053), 554, - DetailedWeaponData(6, 8, List(InternalSlot(540,PlanetSideGUID(6418),0,DetailedAmmoBoxData(8, 1)))) + DetailedWeaponData(6, 8, 0, List(InternalSlot(540,PlanetSideGUID(6418),0,DetailedAmmoBoxData(8, 1)))) ), InternalSlot(324, PlanetSideGUID(9244), 555, - DetailedWeaponData(6, 8, List(InternalSlot(540,PlanetSideGUID(3327),0,DetailedAmmoBoxData(8, 1)))) + DetailedWeaponData(6, 8, 0, List(InternalSlot(540,PlanetSideGUID(3327),0,DetailedAmmoBoxData(8, 1)))) ), InternalSlot(468, PlanetSideGUID(6292), 556, - DetailedWeaponData(6, 8, List(InternalSlot(540,PlanetSideGUID(6918),0,DetailedAmmoBoxData(8, 1)))) + DetailedWeaponData(6, 8, 0, List(InternalSlot(540,PlanetSideGUID(6918),0,DetailedAmmoBoxData(8, 1)))) ), InternalSlot(842, PlanetSideGUID(5357), 558, DetailedAmmoBoxData(8, 1)), InternalSlot(844, PlanetSideGUID(4435), 562, DetailedAmmoBoxData(8, 1)), InternalSlot(843, PlanetSideGUID(7242), 566, DetailedAmmoBoxData(8, 1)), InternalSlot(175, PlanetSideGUID(7330), 570, - DetailedWeaponData(6, 8, List(InternalSlot(540, PlanetSideGUID(4786), 0, DetailedAmmoBoxData(8, 1)))) + DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(4786), 0, DetailedAmmoBoxData(8, 1)))) ), InternalSlot(468, PlanetSideGUID(7415), 571, - DetailedWeaponData(6, 8, List(InternalSlot(540, PlanetSideGUID(6536), 0, DetailedAmmoBoxData(8, 1)))) + DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(6536), 0, DetailedAmmoBoxData(8, 1)))) ), InternalSlot(175, PlanetSideGUID(3949), 572, - DetailedWeaponData(6, 8, List(InternalSlot(540, PlanetSideGUID(7526), 0, DetailedAmmoBoxData(8, 1)))) + DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(7526), 0, DetailedAmmoBoxData(8, 1)))) ), InternalSlot(175, PlanetSideGUID(3805), 573, - DetailedWeaponData(6, 8, List(InternalSlot(540, PlanetSideGUID(7358), 0, DetailedAmmoBoxData(8, 1)))) + DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(7358), 0, DetailedAmmoBoxData(8, 1)))) ), InternalSlot(324, PlanetSideGUID(4493), 574, - DetailedWeaponData(6, 8, List(InternalSlot(540, PlanetSideGUID(6852), 0, DetailedAmmoBoxData(8, 1)))) + DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(6852), 0, DetailedAmmoBoxData(8, 1)))) ), InternalSlot(324, PlanetSideGUID(5762), 575, - DetailedWeaponData(6, 8, List(InternalSlot(540, PlanetSideGUID(3463), 0, DetailedAmmoBoxData(8, 1)))) + DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(3463), 0, DetailedAmmoBoxData(8, 1)))) ), InternalSlot(175, PlanetSideGUID(3315), 576, - DetailedWeaponData(6, 8, List(InternalSlot(540, PlanetSideGUID(7619), 0, DetailedAmmoBoxData(8, 1)))) + DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(7619), 0, DetailedAmmoBoxData(8, 1)))) ), InternalSlot(324, PlanetSideGUID(6263), 577, - DetailedWeaponData(6, 8, List(InternalSlot(540, PlanetSideGUID(5912), 0, DetailedAmmoBoxData(8, 1)))) + DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(5912), 0, DetailedAmmoBoxData(8, 1)))) ), InternalSlot(468, PlanetSideGUID(4028), 578, - DetailedWeaponData(6, 8, List(InternalSlot(540, PlanetSideGUID(8021), 0, DetailedAmmoBoxData(8, 1)))) + DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(8021), 0, DetailedAmmoBoxData(8, 1)))) ), InternalSlot(175, PlanetSideGUID(2843), 579, - DetailedWeaponData(6, 8, List(InternalSlot(540, PlanetSideGUID(7250), 0, DetailedAmmoBoxData(8, 1)))) + DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(7250), 0, DetailedAmmoBoxData(8, 1)))) ), InternalSlot(175, PlanetSideGUID(9143), 580, - DetailedWeaponData(6, 8, List(InternalSlot(540, PlanetSideGUID(5195), 0, DetailedAmmoBoxData(8, 1)))) + DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(5195), 0, DetailedAmmoBoxData(8, 1)))) ), InternalSlot(468, PlanetSideGUID(5024), 581, - DetailedWeaponData(6, 8, List(InternalSlot(540, PlanetSideGUID(4287), 0, DetailedAmmoBoxData(8, 1)))) + DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(4287), 0, DetailedAmmoBoxData(8, 1)))) ), InternalSlot(468, PlanetSideGUID(6582), 582, - DetailedWeaponData(6, 8, List(InternalSlot(540, PlanetSideGUID(4915), 0, DetailedAmmoBoxData(8, 1)))) + DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(4915), 0, DetailedAmmoBoxData(8, 1)))) ), InternalSlot(468, PlanetSideGUID(6425), 583, - DetailedWeaponData(6, 8, List(InternalSlot(540, PlanetSideGUID(8872), 0, DetailedAmmoBoxData(8, 1)))) + DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(8872), 0, DetailedAmmoBoxData(8, 1)))) ), InternalSlot(468, PlanetSideGUID(4431), 584, - DetailedWeaponData(6, 8, List(InternalSlot(540, PlanetSideGUID(4191), 0, DetailedAmmoBoxData(8, 1)))) + DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(4191), 0, DetailedAmmoBoxData(8, 1)))) ), InternalSlot(175, PlanetSideGUID(8339), 585, - DetailedWeaponData(6, 8, List(InternalSlot(540, PlanetSideGUID(7317), 0, DetailedAmmoBoxData(8, 1)))) + DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(7317), 0, DetailedAmmoBoxData(8, 1)))) ), InternalSlot(175, PlanetSideGUID(3277), 586, - DetailedWeaponData(6, 8, List(InternalSlot(540, PlanetSideGUID(6469), 0, DetailedAmmoBoxData(8, 1)))) + DetailedWeaponData(6, 8, 0, List(InternalSlot(540, PlanetSideGUID(6469), 0, DetailedAmmoBoxData(8, 1)))) ) )))) ), @@ -825,11 +825,11 @@ class DetailedCharacterDataTest extends Specification { InternalSlot(728, PlanetSideGUID(7181), 12, DetailedREKData(4, 16)), InternalSlot(536, PlanetSideGUID(4077), 33, DetailedAmmoBoxData(8, 1)), InternalSlot(680, PlanetSideGUID(4377), 37, - DetailedWeaponData(2, 8, List(InternalSlot(681, PlanetSideGUID(8905), 0, DetailedAmmoBoxData(8, 3)))) + DetailedWeaponData(2, 8, 0, List(InternalSlot(681, PlanetSideGUID(8905), 0, DetailedAmmoBoxData(8, 3)))) ), InternalSlot(32, PlanetSideGUID(5523), 39, DetailedACEData(4)), InternalSlot(673, PlanetSideGUID(3661), 60, - DetailedWeaponData(2, 8, List(InternalSlot(674, PlanetSideGUID(8542), 0, DetailedAmmoBoxData(8, 3)))) + DetailedWeaponData(2, 8, 0, List(InternalSlot(674, PlanetSideGUID(8542), 0, DetailedAmmoBoxData(8, 3)))) ) ) ) diff --git a/common/src/test/scala/game/objectcreatedetailed/DetailedWeaponDataTest.scala b/common/src/test/scala/game/objectcreatedetailed/DetailedWeaponDataTest.scala index 90c92a2a..05cb8fd5 100644 --- a/common/src/test/scala/game/objectcreatedetailed/DetailedWeaponDataTest.scala +++ b/common/src/test/scala/game/objectcreatedetailed/DetailedWeaponDataTest.scala @@ -72,7 +72,7 @@ class DetailedWeaponDataTest extends Specification { } "encode (punisher)" in { - val obj = DetailedWeaponData(0, 8, + val obj = DetailedWeaponData(0, 8, 0, DetailedAmmoBoxData(ObjectClass.bullet_9mm, PlanetSideGUID(1693), 0, DetailedAmmoBoxData(8, 30)) :: DetailedAmmoBoxData(ObjectClass.jammer_cartridge, PlanetSideGUID(1564), 1, DetailedAmmoBoxData(8, 1)) :: Nil diff --git a/common/src/test/scala/objects/ConverterTest.scala b/common/src/test/scala/objects/ConverterTest.scala index f9a22fde..b6ea3619 100644 --- a/common/src/test/scala/objects/ConverterTest.scala +++ b/common/src/test/scala/objects/ConverterTest.scala @@ -37,22 +37,13 @@ class ConverterTest extends Specification { } "Tool" should { - "convert to packet" in { - val tdef = ToolDefinition(1076) - tdef.Size = EquipmentSize.Rifle - tdef.AmmoTypes += GlobalDefinitions.shotgun_shell - tdef.AmmoTypes += GlobalDefinitions.shotgun_shell_AP - tdef.FireModes += new FireModeDefinition - tdef.FireModes.head.AmmoTypeIndices += 0 - tdef.FireModes.head.AmmoTypeIndices += 1 - tdef.FireModes.head.AmmoSlotIndex = 0 - tdef.FireModes.head.Magazine = 30 - val obj : Tool = Tool(tdef) + "convert to packet (1 fire mode slot)" in { + val obj : Tool = Tool(GlobalDefinitions.flechette) obj.AmmoSlot.Box.GUID = PlanetSideGUID(90) obj.Definition.Packet.DetailedConstructorData(obj) match { case Success(pkt) => - pkt mustEqual DetailedWeaponData(4,8, Ammo.shotgun_shell.id, PlanetSideGUID(90), 0, DetailedAmmoBoxData(8, 30)) + pkt mustEqual DetailedWeaponData(4,8, Ammo.shotgun_shell.id, PlanetSideGUID(90), 0, DetailedAmmoBoxData(8, 12)) case _ => ko } @@ -63,6 +54,35 @@ class ConverterTest extends Specification { ko } } + + "convert to packet (2 fire mode slots)" in { + val obj : Tool = Tool(GlobalDefinitions.punisher) + obj.AmmoSlots.head.Box.GUID = PlanetSideGUID(90) + obj.AmmoSlots(1).Box.GUID = PlanetSideGUID(91) + + obj.Definition.Packet.DetailedConstructorData(obj) match { + case Success(pkt) => + pkt mustEqual DetailedWeaponData(4,8, 0, + List( + InternalSlot(Ammo.bullet_9mm.id, PlanetSideGUID(90), 0, DetailedAmmoBoxData(8, 30)), + InternalSlot(Ammo.rocket.id, PlanetSideGUID(91), 1, DetailedAmmoBoxData(8, 1)) + ) + ) + case _ => + ko + } + obj.Definition.Packet.ConstructorData(obj) match { + case Success(pkt) => + pkt mustEqual WeaponData(4,8, 0, + List( + InternalSlot(Ammo.bullet_9mm.id, PlanetSideGUID(90), 0, AmmoBoxData()), + InternalSlot(Ammo.rocket.id, PlanetSideGUID(91), 1, AmmoBoxData()) + ) + ) + case _ => + ko + } + } } "Kit" should { diff --git a/common/src/test/scala/objects/EquipmentTest.scala b/common/src/test/scala/objects/EquipmentTest.scala index 3fbe4501..ff44aab7 100644 --- a/common/src/test/scala/objects/EquipmentTest.scala +++ b/common/src/test/scala/objects/EquipmentTest.scala @@ -114,7 +114,6 @@ class EquipmentTest extends Specification { obj.FireModes.head.AmmoTypeIndices += 1 obj.FireModes.head.AmmoSlotIndex = 0 obj.FireModes.head.Magazine = 18 - obj.FireModes.head.ResetAmmoIndexOnSwap = true obj.FireModes += FireModeDefinition() obj.FireModes(1).AmmoTypeIndices += 0 obj.FireModes(1).AmmoTypeIndices += 1 @@ -132,13 +131,11 @@ class EquipmentTest extends Specification { obj.FireModes.head.AmmoSlotIndex mustEqual 0 obj.FireModes.head.Chamber mustEqual 1 obj.FireModes.head.Magazine mustEqual 18 - obj.FireModes.head.ResetAmmoIndexOnSwap mustEqual true obj.FireModes(1).AmmoTypeIndices.head mustEqual 0 obj.FireModes(1).AmmoTypeIndices(1) mustEqual 1 obj.FireModes(1).AmmoSlotIndex mustEqual 1 obj.FireModes(1).Chamber mustEqual 3 obj.FireModes(1).Magazine mustEqual 18 - obj.FireModes(1).ResetAmmoIndexOnSwap mustEqual false obj.Tile.Width mustEqual InventoryTile.Tile93.Width obj.Tile.Height mustEqual InventoryTile.Tile93.Height } diff --git a/common/src/test/scala/objects/PlayerTest.scala b/common/src/test/scala/objects/PlayerTest.scala index 2e737e96..d082fe2f 100644 --- a/common/src/test/scala/objects/PlayerTest.scala +++ b/common/src/test/scala/objects/PlayerTest.scala @@ -114,15 +114,15 @@ class PlayerTest extends Specification { obj.Slot(1).Size = EquipmentSize.Pistol obj.Slot(1).Equipment = wep2 obj.DrawnSlot mustEqual Player.HandsDownSlot //default value - obj.LastDrawnSlot mustEqual 0 //default value + obj.LastDrawnSlot mustEqual Player.HandsDownSlot //default value obj.DrawnSlot = 1 obj.DrawnSlot mustEqual 1 - obj.LastDrawnSlot mustEqual 0 //default value; sorry + obj.LastDrawnSlot mustEqual 1 obj.DrawnSlot = 0 obj.DrawnSlot mustEqual 0 - obj.LastDrawnSlot mustEqual 1 + obj.LastDrawnSlot mustEqual 0 obj.DrawnSlot = Player.HandsDownSlot obj.DrawnSlot mustEqual Player.HandsDownSlot @@ -130,15 +130,15 @@ class PlayerTest extends Specification { obj.DrawnSlot = 1 obj.DrawnSlot mustEqual 1 - obj.LastDrawnSlot mustEqual 0 + obj.LastDrawnSlot mustEqual 1 obj.DrawnSlot = 0 obj.DrawnSlot mustEqual 0 - obj.LastDrawnSlot mustEqual 1 + obj.LastDrawnSlot mustEqual 0 obj.DrawnSlot = 1 obj.DrawnSlot mustEqual 1 - obj.LastDrawnSlot mustEqual 0 + obj.LastDrawnSlot mustEqual 1 obj.DrawnSlot = Player.HandsDownSlot obj.DrawnSlot mustEqual Player.HandsDownSlot diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index e1127d25..d3c82104 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -62,6 +62,7 @@ class WorldSessionActor extends Actor with MDCContextAware { case Some(tplayer) => tplayer.VehicleSeated match { case Some(vehicle_guid) => + //TODO do this at some other time vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.KickPassenger(tplayer.GUID, 0, true, vehicle_guid)) case None => ; } @@ -146,9 +147,12 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(PacketCoding.CreateGamePacket(0, ArmorChangedMessage(guid, suit, subtype))) } - case AvatarResponse.ChangeAmmo(weapon_guid, weapon_slot, ammo_id, ammo_guid, ammo_data) => + case AvatarResponse.ChangeAmmo(weapon_guid, weapon_slot, previous_guid, ammo_id, ammo_guid, ammo_data) => if(player.GUID != guid) { sendResponse(PacketCoding.CreateGamePacket(0, + ObjectDetachMessage(weapon_guid, previous_guid, Vector3(0,0,0), 0f, 0f, 0f) + )) + sendResponse(PacketCoding.CreateGamePacket(0, ObjectCreateMessage( ammo_id, ammo_guid, @@ -159,6 +163,11 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(PacketCoding.CreateGamePacket(0, ChangeAmmoMessage(weapon_guid, 1))) } + case AvatarResponse.ChangeFireMode(item_guid, mode) => + if(player.GUID != guid) { + sendResponse(PacketCoding.CreateGamePacket(0, ChangeFireModeMessage(item_guid, mode))) + } + case AvatarResponse.ChangeFireState_Start(weapon_guid) => if(player.GUID != guid) { sendResponse(PacketCoding.CreateGamePacket(0, ChangeFireStateMessage_Start(weapon_guid))) @@ -214,7 +223,7 @@ class WorldSessionActor extends Actor with MDCContextAware { case AvatarResponse.ObjectHeld(slot) => if(player.GUID != guid) { - sendResponse(PacketCoding.CreateGamePacket(0, ObjectHeldMessage(guid, slot, true))) + sendResponse(PacketCoding.CreateGamePacket(0, ObjectHeldMessage(guid, slot, false))) } case AvatarResponse.PlanetsideAttribute(attribute_type, attribute_value) => @@ -237,9 +246,10 @@ class WorldSessionActor extends Actor with MDCContextAware { if(spectating || ((distanceSq < 900 || weaponInHand) && time > 200) || (distanceSq < 10000 && time > 500) || - (distanceSq < 160000 && (msg.is_jumping || time < 200)) || - (distanceSq < 160000 && (msg.vel.isEmpty || Vector3.MagnitudeSquared(msg.vel.get).toInt == 0) && time > 2000) || - (distanceSq < 160000 && time > 1000) || + (distanceSq < 160000 && ( + (msg.is_jumping || time < 200)) || + ((msg.vel.isEmpty || Vector3.MagnitudeSquared(msg.vel.get).toInt == 0) && time > 2000) || + (time > 1000)) || (distanceSq > 160000 && time > 5000)) { sendResponse( @@ -976,7 +986,7 @@ class WorldSessionActor extends Actor with MDCContextAware { ) ) ) - if(-1 < slot && slot < 5) { + if(tplayer.VisibleSlots.contains(slot)) { avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.EquipmentInHand(player_guid, slot, item)) } case None => @@ -1124,11 +1134,11 @@ class WorldSessionActor extends Actor with MDCContextAware { AwardBattleExperiencePoints(player, 1000000L) // player.ExoSuit = ExoSuitType.MAX //TODO strange issue; divide number above by 10 when uncommenting player.Slot(0).Equipment = Tool(GlobalDefinitions.StandardPistol(player.Faction)) - player.Slot(2).Equipment = Tool(suppressor) + player.Slot(2).Equipment = Tool(punisher) //suppressor player.Slot(4).Equipment = Tool(GlobalDefinitions.StandardMelee(player.Faction)) - player.Slot(6).Equipment = AmmoBox(bullet_9mm) + player.Slot(6).Equipment = AmmoBox(frag_cartridge) //bullet_9mm player.Slot(9).Equipment = AmmoBox(bullet_9mm) - player.Slot(12).Equipment = AmmoBox(bullet_9mm) + //player.Slot(12).Equipment = AmmoBox(bullet_9mm) player.Slot(33).Equipment = AmmoBox(bullet_9mm_AP) player.Slot(36).Equipment = AmmoBox(GlobalDefinitions.StandardPistolAmmo(player.Faction)) player.Slot(39).Equipment = SimpleItem(remote_electronics_kit) @@ -1347,34 +1357,7 @@ class WorldSessionActor extends Actor with MDCContextAware { case msg @ ChangeAmmoMessage(item_guid, unk1) => log.info("ChangeAmmo: " + msg) - (player.VehicleSeated match { - case Some(vehicle_guid) => //weapon is vehicle turret? - continent.GUID(vehicle_guid) match { - case Some(vehicle : Vehicle) => - vehicle.PassengerInSeat(player) match { - case Some(seat_num) => - vehicle.WeaponControlledFromSeat(seat_num) match { - case Some(item : Tool) => - //TODO check that item controlled by seat is item_guid? - (Some(vehicle), Some(item)) - case _ => ; - (None, None) - } - case None => ; - (None, None) - } - case _ => ; - (None, None) - } - case None => //not in vehicle; weapon in hand? - player.Slot(player.DrawnSlot).Equipment match { - //TODO check that item in hand is item_guid? - case Some(item : Tool) => - (Some(player), Some(item)) - case _ => ; - (None, None) - } - }) match { + FindContainedWeapon match { case (Some(obj), Some(tool : Tool)) => val originalAmmoType = tool.AmmoType val fullMagazine = tool.MaxMagazine @@ -1414,6 +1397,14 @@ class WorldSessionActor extends Actor with MDCContextAware { tool.AmmoSlots(ammoSlotIndex).Box = box //put replacement ammo in tool sendResponse(PacketCoding.CreateGamePacket(0, ObjectAttachMessage(tool.GUID, box.GUID, ammoSlotIndex))) + //announce swapped ammunition box in weapon + val previous_box_guid = previousBox.GUID + val boxDef = box.Definition + val box_guid = box.GUID + val tool_guid = tool.GUID + sendResponse(PacketCoding.CreateGamePacket(0, ChangeAmmoMessage(tool_guid, box.Capacity))) + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ChangeAmmo(player.GUID, tool_guid, ammoSlotIndex,previous_box_guid, boxDef.ObjectId, box.GUID, boxDef.Packet.ConstructorData(box).get)) + //handle inventory contents box.Capacity = (if(sumReloadValue <= fullMagazine) { sumReloadValue @@ -1454,6 +1445,7 @@ class WorldSessionActor extends Actor with MDCContextAware { } previousBox.Capacity = capacity } + if(previousBox.Capacity > 0) { //TODO split previousBox into AmmoBox objects of appropriate max capacity, e.g., 100 9mm -> 2 x 50 9mm obj.Inventory.Fit(previousBox.Definition.Tile) match { @@ -1463,7 +1455,7 @@ class WorldSessionActor extends Actor with MDCContextAware { log.info(s"ChangeAmmo: dropping ammo box $previousBox") val pos = player.Position val orient = player.Orientation - sendResponse(PacketCoding.CreateGamePacket(0, ObjectDetachMessage(Service.defaultPlayerGUID, previousBox.GUID, pos, 0f, 0f, orient.z))) + sendResponse(PacketCoding.CreateGamePacket(0, ObjectDetachMessage(Service.defaultPlayerGUID, previous_box_guid, pos, 0f, 0f, orient.z))) val orient2 = Vector3(0f, 0f, orient.z) continent.Ground ! Zone.DropItemOnGround(previousBox, pos, orient2) val objDef = previousBox.Definition @@ -1473,13 +1465,6 @@ class WorldSessionActor extends Actor with MDCContextAware { else { taskResolver ! GUIDTask.UnregisterObjectTask(previousBox)(continent.GUID) } - - //announce swapped ammunition box in weapon - val boxDef = box.Definition - val box_guid = box.GUID - val tool_guid = tool.GUID - sendResponse(PacketCoding.CreateGamePacket(0, ChangeAmmoMessage(tool_guid, box.Capacity))) - avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ChangeAmmo(player.GUID, tool_guid, ammoSlotIndex, boxDef.ObjectId, box.GUID, boxDef.Packet.ConstructorData(box).get)) } } } @@ -1493,6 +1478,26 @@ class WorldSessionActor extends Actor with MDCContextAware { case msg @ ChangeFireModeMessage(item_guid, fire_mode) => log.info("ChangeFireMode: " + msg) + FindContainedWeapon match { + case (_, Some(tool : Tool)) => + val originalModeIndex = tool.FireModeIndex + tool.NextFireMode + val modeIndex = tool.FireModeIndex + val tool_guid = tool.GUID + if(originalModeIndex != modeIndex) { + log.info(s"ChangeFireMode: changing $tool_guid to fire mode $modeIndex") + sendResponse(PacketCoding.CreateGamePacket(0, ChangeFireModeMessage(tool_guid, modeIndex))) + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ChangeFireMode(player.GUID, tool_guid, modeIndex)) + } + else { + tool.FireModeIndex = originalModeIndex + sendResponse(PacketCoding.CreateGamePacket(0, ChangeFireModeMessage(tool_guid, originalModeIndex))) + } + case (_, Some(_)) => + log.error(s"ChangeFireMode: the object that was found for $item_guid was not a Tool") + case (_, None) => + log.error(s"ChangeFireMode: can not find $item_guid") + } case msg @ ChangeFireStateMessage_Start(item_guid) => log.info("ChangeFireState_Start: " + msg) @@ -1539,34 +1544,7 @@ class WorldSessionActor extends Actor with MDCContextAware { case msg @ ReloadMessage(item_guid, ammo_clip, unk1) => log.info("Reload: " + msg) - (player.VehicleSeated match { - case Some(vehicle_guid) => //weapon is vehicle turret? - continent.GUID(vehicle_guid) match { - case Some(vehicle : Vehicle) => - vehicle.PassengerInSeat(player) match { - case Some(seat_num) => - vehicle.WeaponControlledFromSeat(seat_num) match { - case Some(item : Tool) => - //TODO check that item controlled by seat is item_guid? - (Some(vehicle), Some(item)) - case _ => ; - (None, None) - } - case None => ; - (None, None) - } - case _ => ; - (None, None) - } - case None => //not in vehicle; weapon in hand? - player.Slot(player.DrawnSlot).Equipment match { - //TODO check that item in hand is item_guid? - case Some(item : Tool) => - (Some(player), Some(item)) - case _ => ; - (None, None) - } - }) match { + FindContainedWeapon match { case (Some(obj), Some(tool : Tool)) => val currentMagazine : Int = tool.Magazine val magazineSize : Int = tool.MaxMagazine @@ -1616,7 +1594,7 @@ class WorldSessionActor extends Actor with MDCContextAware { val before = player.DrawnSlot //TODO remove this kludge; explore how to stop BuyExoSuit(Max) sending a tardy ObjectHeldMessage(me, 255) if(player.ExoSuit != ExoSuitType.MAX && (player.DrawnSlot = held_holsters) != before) { - avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.ObjectHeld(player.GUID, held_holsters)) + avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.ObjectHeld(player.GUID, player.LastDrawnSlot)) } case msg @ AvatarJumpMessage(state) => @@ -1913,22 +1891,7 @@ class WorldSessionActor extends Actor with MDCContextAware { case msg @ WeaponDryFireMessage(weapon_guid) => log.info("WeaponDryFireMessage: "+msg) - (player.VehicleSeated match { - case Some(vehicle_guid) => - continent.GUID(vehicle_guid) match { - case Some(obj : Vehicle) => - obj.PassengerInSeat(player) match { - case Some(seat_num) => - obj.WeaponControlledFromSeat(seat_num) - case None => - None - } - case _ => - None - } - case None => - player.Slot(player.DrawnSlot).Equipment - }) match { + FindWeapon match { case Some(tool : Tool) => avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.WeaponDryFire(player.GUID, weapon_guid)) case _ => ; @@ -1936,22 +1899,7 @@ class WorldSessionActor extends Actor with MDCContextAware { case msg @ WeaponFireMessage(seq_time, weapon_guid, projectile_guid, shot_origin, unk1, unk2, unk3, unk4, unk5, unk6, unk7) => log.info("WeaponFire: " + msg) - (player.VehicleSeated match { - case Some(vehicle_guid) => - continent.GUID(vehicle_guid) match { - case Some(obj : Vehicle) => - obj.PassengerInSeat(player) match { - case Some(seat_num) => - obj.WeaponControlledFromSeat(seat_num) - case None => - None - } - case _ => - None - } - case None => - player.Slot(player.DrawnSlot).Equipment - }) match { + FindWeapon match { case Some(tool : Tool) => if(tool.Magazine <= 0) { //safety: enforce ammunition depletion tool.Magazine = 0 @@ -1963,7 +1911,7 @@ class WorldSessionActor extends Actor with MDCContextAware { } else { //shooting tool.Magazine = tool.Magazine - 1 - //TODO other stuff + //TODO other stuff? } case _ => ; } @@ -2654,6 +2602,59 @@ class WorldSessionActor extends Actor with MDCContextAware { }) } + /** + * Check two locations for a controlled piece of equipment that is associated with the `player`.
+ *
+ * The first location is dependent on whether the avatar is in a vehicle. + * Some vehicle seats may have a "controlled weapon" which counts as the first location to be checked. + * The second location is dependent on whether the avatar has a raised hand. + * That is only possible if the player has something in their hand at the moment, hence the second location. + * Players do have a concept called a "last drawn slot" (hand) but that former location is not eligible.
+ *
+ * Along with any discovered item, a containing object such that the statement:
+ * `container.Find(object) = Some(slot)`
+ * ... will return a proper result. + * For a seat controlled weapon, the vehicle is returned. + * For the player's hand, the player is returned. + * @return a `Tuple` of the returned values; + * the first value is a `Container` object; + * the second value is an `Equipment` object in the former + */ + def FindContainedWeapon : (Option[PlanetSideGameObject with Container], Option[Equipment]) = { + player.VehicleSeated match { + case Some(vehicle_guid) => //weapon is vehicle turret? + continent.GUID(vehicle_guid) match { + case Some(vehicle : Vehicle) => + vehicle.PassengerInSeat(player) match { + case Some(seat_num) => + vehicle.WeaponControlledFromSeat(seat_num) match { + case Some(item : Tool) => + (Some(vehicle), Some(item)) + case _ => ; + (None, None) + } + case None => ; + (None, None) + } + case _ => ; + (None, None) + } + case None => //not in vehicle; weapon in hand? + player.Slot(player.DrawnSlot).Equipment match { + case Some(item : Tool) => + (Some(player), Some(item)) + case _ => ; + (None, None) + } + } + } + + /** + * Runs `FindContainedWeapon` but ignores the `Container` object output. + * @return an `Equipment` object + */ + def FindWeapon : Option[Equipment] = FindContainedWeapon._2 + /** * Within a specified `Container`, find the smallest number of `AmmoBox` objects of a certain type of `Ammo` * whose sum capacities is greater than, or equal to, a `desiredAmount`.
diff --git a/pslogin/src/main/scala/services/avatar/AvatarAction.scala b/pslogin/src/main/scala/services/avatar/AvatarAction.scala index 9a09742a..13ece68e 100644 --- a/pslogin/src/main/scala/services/avatar/AvatarAction.scala +++ b/pslogin/src/main/scala/services/avatar/AvatarAction.scala @@ -10,11 +10,11 @@ object AvatarAction { trait Action final case class ArmorChanged(player_guid : PlanetSideGUID, suit : ExoSuitType.Value, subtype : Int) extends Action - final case class ChangeAmmo(player_guid : PlanetSideGUID, weapon_guid : PlanetSideGUID, weapon_slot : Int, ammo_id : Int, ammo_guid : PlanetSideGUID, ammo_data : ConstructorData) extends Action + final case class ChangeAmmo(player_guid : PlanetSideGUID, weapon_guid : PlanetSideGUID, weapon_slot : Int, old_ammo_guid : PlanetSideGUID, ammo_id : Int, ammo_guid : PlanetSideGUID, ammo_data : ConstructorData) extends Action + final case class ChangeFireMode(player_guid : PlanetSideGUID, item_guid : PlanetSideGUID, mode : Int) extends Action final case class ChangeFireState_Start(player_guid : PlanetSideGUID, weapon_guid : PlanetSideGUID) extends Action final case class ChangeFireState_Stop(player_guid : PlanetSideGUID, weapon_guid : PlanetSideGUID) extends Action final case class ConcealPlayer(player_guid : PlanetSideGUID) extends Action - //final case class DropItem(pos : Vector3, orient : Vector3, item : PlanetSideGUID) extends Action final case class EquipmentInHand(player_guid : PlanetSideGUID, slot : Int, item : Equipment) extends Action final case class EquipmentOnGround(player_guid : PlanetSideGUID, pos : Vector3, orient : Vector3, item_id : Int, item_guid : PlanetSideGUID, item_data : ConstructorData) extends Action final case class LoadPlayer(player_guid : PlanetSideGUID, pdata : ConstructorData) extends Action diff --git a/pslogin/src/main/scala/services/avatar/AvatarResponse.scala b/pslogin/src/main/scala/services/avatar/AvatarResponse.scala index 1da7fcba..0ffe562f 100644 --- a/pslogin/src/main/scala/services/avatar/AvatarResponse.scala +++ b/pslogin/src/main/scala/services/avatar/AvatarResponse.scala @@ -10,11 +10,11 @@ object AvatarResponse { trait Response final case class ArmorChanged(suit : ExoSuitType.Value, subtype : Int) extends Response - final case class ChangeAmmo(weapon_guid : PlanetSideGUID, weapon_slot : Int, ammo_id : Int, ammo_guid : PlanetSideGUID, ammo_data : ConstructorData) extends Response + final case class ChangeAmmo(weapon_guid : PlanetSideGUID, weapon_slot : Int, old_ammo_guid : PlanetSideGUID, ammo_id : Int, ammo_guid : PlanetSideGUID, ammo_data : ConstructorData) extends Response + final case class ChangeFireMode(item_guid : PlanetSideGUID, mode : Int) extends Response final case class ChangeFireState_Start(weapon_guid : PlanetSideGUID) extends Response final case class ChangeFireState_Stop(weapon_guid : PlanetSideGUID) extends Response final case class ConcealPlayer() extends Response - //final case class DropItem(pos : Vector3, orient : Vector3, item : PlanetSideGUID) extends Response final case class EquipmentInHand(slot : Int, item : Equipment) extends Response final case class EquipmentOnGround(pos : Vector3, orient : Vector3, item_id : Int, item_guid : PlanetSideGUID, item_data : ConstructorData) extends Response final case class LoadPlayer(pdata : ConstructorData) extends Response diff --git a/pslogin/src/main/scala/services/avatar/AvatarService.scala b/pslogin/src/main/scala/services/avatar/AvatarService.scala index 5e47c3e8..dcf24902 100644 --- a/pslogin/src/main/scala/services/avatar/AvatarService.scala +++ b/pslogin/src/main/scala/services/avatar/AvatarService.scala @@ -39,9 +39,13 @@ class AvatarService extends Actor { AvatarEvents.publish( AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.ArmorChanged(suit, subtype)) ) - case AvatarAction.ChangeAmmo(player_guid, weapon_guid, weapon_slot, ammo_id, ammo_guid, ammo_data) => + case AvatarAction.ChangeAmmo(player_guid, weapon_guid, weapon_slot, old_ammo_guid, ammo_id, ammo_guid, ammo_data) => AvatarEvents.publish( - AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.ChangeAmmo(weapon_guid, weapon_slot, ammo_id, ammo_guid, ammo_data)) + AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.ChangeAmmo(weapon_guid, weapon_slot, old_ammo_guid, ammo_id, ammo_guid, ammo_data)) + ) + case AvatarAction.ChangeFireMode(player_guid, item_guid, mode) => + AvatarEvents.publish( + AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.ChangeFireMode(item_guid, mode)) ) case AvatarAction.ChangeFireState_Start(player_guid, weapon_guid) => AvatarEvents.publish( diff --git a/pslogin/src/main/test/scala/AvatarServiceTest.scala b/pslogin/src/main/test/scala/AvatarServiceTest.scala index 6bbdb9bf..d4b01f89 100644 --- a/pslogin/src/main/test/scala/AvatarServiceTest.scala +++ b/pslogin/src/main/test/scala/AvatarServiceTest.scala @@ -15,7 +15,7 @@ class AvatarService0Test extends ActorTest { } } -class AvatarService1ATest extends ActorTest { +class AvatarService1_1Test extends ActorTest { "AvatarService" should { "subscribe" in { val service = system.actorOf(Props[AvatarService], "service") @@ -25,7 +25,7 @@ class AvatarService1ATest extends ActorTest { } } -class AvatarService1BTest extends ActorTest { +class AvatarService1_2Test extends ActorTest { "AvatarService" should { "subscribe" in { val service = system.actorOf(Props[AvatarService], "service") @@ -36,7 +36,7 @@ class AvatarService1BTest extends ActorTest { } } -class AvatarService1CTest extends ActorTest { +class AvatarService1_3Test extends ActorTest { "AvatarService" should { "subscribe" in { val service = system.actorOf(Props[AvatarService], "service") @@ -183,6 +183,67 @@ class AvatarServiceCTest extends ActorTest { } } +class AvatarServiceDTest extends ActorTest { + val ammoDef = GlobalDefinitions.energy_cell + val ammoBox = AmmoBox(ammoDef) + + "AvatarService" should { + "pass ChangeAmmo" in { + val service = system.actorOf(Props[AvatarService], "service") + service ! Service.Join("test") + service ! AvatarServiceMessage("test", AvatarAction.ChangeAmmo(PlanetSideGUID(10), PlanetSideGUID(40), 0, PlanetSideGUID(40), ammoDef.ObjectId, PlanetSideGUID(41), ammoDef.Packet.ConstructorData(ammoBox).get)) + expectMsg(AvatarServiceResponse("/test/Avatar", PlanetSideGUID(10), AvatarResponse.ChangeAmmo(PlanetSideGUID(40), 0, PlanetSideGUID(40), ammoDef.ObjectId, PlanetSideGUID(41), ammoDef.Packet.ConstructorData(ammoBox).get))) + } + } +} + +class AvatarServiceETest extends ActorTest { + val ammoDef = GlobalDefinitions.energy_cell + val ammoBox = AmmoBox(ammoDef) + + "AvatarService" should { + "pass ChangeFireMode" in { + val service = system.actorOf(Props[AvatarService], "service") + service ! Service.Join("test") + service ! AvatarServiceMessage("test", AvatarAction.ChangeFireMode(PlanetSideGUID(10), PlanetSideGUID(40), 0)) + expectMsg(AvatarServiceResponse("/test/Avatar", PlanetSideGUID(10), AvatarResponse.ChangeFireMode(PlanetSideGUID(40), 0))) + } + } +} + +class AvatarServiceF_1Test extends ActorTest { + "AvatarService" should { + "pass ChangeFireState_Start" in { + val service = system.actorOf(Props[AvatarService], "service") + service ! Service.Join("test") + service ! AvatarServiceMessage("test", AvatarAction.ChangeFireState_Start(PlanetSideGUID(10), PlanetSideGUID(40))) + expectMsg(AvatarServiceResponse("/test/Avatar", PlanetSideGUID(10), AvatarResponse.ChangeFireState_Start(PlanetSideGUID(40)))) + } + } +} + +class AvatarServiceF_2Test extends ActorTest { + "AvatarService" should { + "pass ChangeFireState_Stop" in { + val service = system.actorOf(Props[AvatarService], "service") + service ! Service.Join("test") + service ! AvatarServiceMessage("test", AvatarAction.ChangeFireState_Stop(PlanetSideGUID(10), PlanetSideGUID(40))) + expectMsg(AvatarServiceResponse("/test/Avatar", PlanetSideGUID(10), AvatarResponse.ChangeFireState_Stop(PlanetSideGUID(40)))) + } + } +} + +class AvatarService01Test extends ActorTest { + "AvatarService" should { + "pass WeaponDryFire" in { + val service = system.actorOf(Props[AvatarService], "service") + service ! Service.Join("test") + service ! AvatarServiceMessage("test", AvatarAction.WeaponDryFire(PlanetSideGUID(10), PlanetSideGUID(40))) + expectMsg(AvatarServiceResponse("/test/Avatar", PlanetSideGUID(10), AvatarResponse.WeaponDryFire(PlanetSideGUID(40)))) + } + } +} + object AvatarServiceTest { //decoy } From 065cd0c885359ffc89c9b377c9f92ad4d8661246 Mon Sep 17 00:00:00 2001 From: FateJH Date: Mon, 15 Jan 2018 09:23:51 -0500 Subject: [PATCH 4/7] adding a variety of Punisher ammo for testing --- pslogin/src/main/scala/WorldSessionActor.scala | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index d3c82104..0e6301bd 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -1136,12 +1136,12 @@ class WorldSessionActor extends Actor with MDCContextAware { player.Slot(0).Equipment = Tool(GlobalDefinitions.StandardPistol(player.Faction)) player.Slot(2).Equipment = Tool(punisher) //suppressor player.Slot(4).Equipment = Tool(GlobalDefinitions.StandardMelee(player.Faction)) - player.Slot(6).Equipment = AmmoBox(frag_cartridge) //bullet_9mm - player.Slot(9).Equipment = AmmoBox(bullet_9mm) - //player.Slot(12).Equipment = AmmoBox(bullet_9mm) + player.Slot(6).Equipment = AmmoBox(bullet_9mm, 20) //bullet_9mm + player.Slot(9).Equipment = AmmoBox(rocket, 11) //bullet_9mm + player.Slot(12).Equipment = AmmoBox(frag_cartridge) //bullet_9mm player.Slot(33).Equipment = AmmoBox(bullet_9mm_AP) player.Slot(36).Equipment = AmmoBox(GlobalDefinitions.StandardPistolAmmo(player.Faction)) - player.Slot(39).Equipment = SimpleItem(remote_electronics_kit) + player.Slot(39).Equipment = AmmoBox(plasma_cartridge) //SimpleItem(remote_electronics_kit) player.Slot(5).Equipment.get.asInstanceOf[LockerContainer].Inventory += 0 -> SimpleItem(remote_electronics_kit) //TODO end temp player character auto-loading self ! ListAccountCharacters From 53c59025125639041042accf6369493cd166b0c6 Mon Sep 17 00:00:00 2001 From: FateJH Date: Mon, 15 Jan 2018 19:19:15 -0500 Subject: [PATCH 5/7] suspend fire state change when ammo in weapon is zero --- .../src/main/scala/WorldSessionActor.scala | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index 0e6301bd..17bebda3 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -1478,8 +1478,8 @@ class WorldSessionActor extends Actor with MDCContextAware { case msg @ ChangeFireModeMessage(item_guid, fire_mode) => log.info("ChangeFireMode: " + msg) - FindContainedWeapon match { - case (_, Some(tool : Tool)) => + FindWeapon match { + case Some(tool : Tool) => val originalModeIndex = tool.FireModeIndex tool.NextFireMode val modeIndex = tool.FireModeIndex @@ -1493,18 +1493,26 @@ class WorldSessionActor extends Actor with MDCContextAware { tool.FireModeIndex = originalModeIndex sendResponse(PacketCoding.CreateGamePacket(0, ChangeFireModeMessage(tool_guid, originalModeIndex))) } - case (_, Some(_)) => + case Some(_) => log.error(s"ChangeFireMode: the object that was found for $item_guid was not a Tool") - case (_, None) => + case None => log.error(s"ChangeFireMode: can not find $item_guid") } case msg @ ChangeFireStateMessage_Start(item_guid) => log.info("ChangeFireState_Start: " + msg) if(shooting.isEmpty) { - //TODO check that player can shoot item_guid? - shooting = Some(item_guid) - avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ChangeFireState_Start(player.GUID, item_guid)) + FindWeapon match { + case Some(tool : Tool) => + if(tool.GUID == item_guid && tool.Magazine > 0) { + shooting = Some(item_guid) + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ChangeFireState_Start(player.GUID, item_guid)) + } + case Some(_) => + log.error(s"ChangeFireState_Start: the object that was found for $item_guid was not a Tool") + case None => + log.error(s"ChangeFireState_Start: can not find $item_guid") + } } case msg @ ChangeFireStateMessage_Stop(item_guid) => From 4059d50e72bc3aafb260f65efb479beed285997a Mon Sep 17 00:00:00 2001 From: FateJH Date: Wed, 17 Jan 2018 07:51:42 -0500 Subject: [PATCH 6/7] shotgun pellet fire modes; fire mode for melee weapons; cleanup code for phoenix (decimator) and handling last phoneix rocket fire; temporary cleanup of grenades --- .../psforever/objects/GlobalDefinitions.scala | 64 ++++----- .../scala/net/psforever/objects/Tool.scala | 13 +- .../equipment/FireModeDefinition.scala | 99 ++++++++++---- .../test/scala/objects/EquipmentTest.scala | 77 ++++++----- .../src/test/scala/objects/FireModeTest.scala | 127 ++++++++++++++++++ .../src/main/scala/WorldSessionActor.scala | 52 ++++++- 6 files changed, 336 insertions(+), 96 deletions(-) create mode 100644 common/src/test/scala/objects/FireModeTest.scala diff --git a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala index 03f64231..15c4af9c 100644 --- a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala +++ b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala @@ -1028,55 +1028,47 @@ object GlobalDefinitions { private def init_tools() : Unit = { chainblade.Size = EquipmentSize.Melee chainblade.AmmoTypes += melee_ammo - chainblade.FireModes += new FireModeDefinition + chainblade.FireModes += new InfiniteFireModeDefinition chainblade.FireModes.head.AmmoTypeIndices += 0 chainblade.FireModes.head.AmmoSlotIndex = 0 chainblade.FireModes.head.Magazine = 1 - chainblade.FireModes.head.Chamber = 0 - chainblade.FireModes += new FireModeDefinition + chainblade.FireModes += new InfiniteFireModeDefinition chainblade.FireModes(1).AmmoTypeIndices += 0 chainblade.FireModes(1).AmmoSlotIndex = 0 chainblade.FireModes(1).Magazine = 1 - chainblade.FireModes.head.Chamber = 0 magcutter.Size = EquipmentSize.Melee magcutter.AmmoTypes += melee_ammo - magcutter.FireModes += new FireModeDefinition + magcutter.FireModes += new InfiniteFireModeDefinition magcutter.FireModes.head.AmmoTypeIndices += 0 magcutter.FireModes.head.AmmoSlotIndex = 0 magcutter.FireModes.head.Magazine = 1 - magcutter.FireModes.head.Chamber = 0 - magcutter.FireModes += new FireModeDefinition + magcutter.FireModes += new InfiniteFireModeDefinition magcutter.FireModes(1).AmmoTypeIndices += 0 magcutter.FireModes(1).AmmoSlotIndex = 0 magcutter.FireModes(1).Magazine = 1 - magcutter.FireModes.head.Chamber = 0 forceblade.Size = EquipmentSize.Melee forceblade.AmmoTypes += melee_ammo - forceblade.FireModes += new FireModeDefinition + forceblade.FireModes += new InfiniteFireModeDefinition forceblade.FireModes.head.AmmoTypeIndices += 0 forceblade.FireModes.head.AmmoSlotIndex = 0 forceblade.FireModes.head.Magazine = 1 - forceblade.FireModes.head.Chamber = 0 - forceblade.FireModes += new FireModeDefinition + forceblade.FireModes += new InfiniteFireModeDefinition forceblade.FireModes(1).AmmoTypeIndices += 0 forceblade.FireModes(1).AmmoSlotIndex = 0 forceblade.FireModes(1).Magazine = 1 - forceblade.FireModes(1).Chamber = 0 katana.Size = EquipmentSize.Melee katana.AmmoTypes += melee_ammo - katana.FireModes += new FireModeDefinition + katana.FireModes += new InfiniteFireModeDefinition katana.FireModes.head.AmmoTypeIndices += 0 katana.FireModes.head.AmmoSlotIndex = 0 katana.FireModes.head.Magazine = 1 - katana.FireModes.head.Chamber = 0 - katana.FireModes += new FireModeDefinition + katana.FireModes += new InfiniteFireModeDefinition katana.FireModes(1).AmmoTypeIndices += 0 katana.FireModes(1).AmmoSlotIndex = 0 katana.FireModes(1).Magazine = 1 - katana.FireModes(1).Chamber = 0 frag_grenade.Size = EquipmentSize.Pistol frag_grenade.AmmoTypes += frag_grenade_ammo @@ -1127,11 +1119,12 @@ object GlobalDefinitions { isp.Size = EquipmentSize.Pistol isp.AmmoTypes += shotgun_shell isp.AmmoTypes += shotgun_shell_AP - isp.FireModes += new FireModeDefinition + isp.FireModes += new PelletFireModeDefinition isp.FireModes.head.AmmoTypeIndices += 0 isp.FireModes.head.AmmoTypeIndices += 1 isp.FireModes.head.AmmoSlotIndex = 0 isp.FireModes.head.Magazine = 8 + isp.FireModes.head.Chamber = 6 //8 shells x 6 pellets = 48 isp.Tile = InventoryTile.Tile33 beamer.Size = EquipmentSize.Pistol @@ -1190,11 +1183,12 @@ object GlobalDefinitions { flechette.Size = EquipmentSize.Rifle flechette.AmmoTypes += shotgun_shell flechette.AmmoTypes += shotgun_shell_AP - flechette.FireModes += new FireModeDefinition + flechette.FireModes += new PelletFireModeDefinition flechette.FireModes.head.AmmoTypeIndices += 0 flechette.FireModes.head.AmmoTypeIndices += 1 flechette.FireModes.head.AmmoSlotIndex = 0 - flechette.FireModes.head.Magazine = 12 //12 shells * 8 pellets = 96 + flechette.FireModes.head.Magazine = 12 + flechette.FireModes.head.Chamber = 8 //12 shells * 8 pellets = 96 flechette.Tile = InventoryTile.Tile63 cycler.Size = EquipmentSize.Rifle @@ -1239,7 +1233,6 @@ object GlobalDefinitions { anniversary_guna.FireModes(1).AmmoTypeIndices += 0 anniversary_guna.FireModes(1).AmmoSlotIndex = 0 anniversary_guna.FireModes(1).Magazine = 6 - anniversary_guna.FireModes(1).Chamber = 6 anniversary_guna.Tile = InventoryTile.Tile33 anniversary_gun.Size = EquipmentSize.Pistol @@ -1252,7 +1245,6 @@ object GlobalDefinitions { anniversary_gun.FireModes(1).AmmoTypeIndices += 0 anniversary_gun.FireModes(1).AmmoSlotIndex = 0 anniversary_gun.FireModes(1).Magazine = 6 - anniversary_gun.FireModes(1).Chamber = 6 anniversary_gun.Tile = InventoryTile.Tile33 anniversary_gunb.Size = EquipmentSize.Pistol @@ -1265,7 +1257,6 @@ object GlobalDefinitions { anniversary_gunb.FireModes(1).AmmoTypeIndices += 0 anniversary_gunb.FireModes(1).AmmoSlotIndex = 0 anniversary_gunb.FireModes(1).Magazine = 6 - anniversary_gunb.FireModes(1).Chamber = 6 anniversary_gunb.Tile = InventoryTile.Tile33 spiker.Size = EquipmentSize.Pistol @@ -1275,6 +1266,7 @@ object GlobalDefinitions { spiker.FireModes.head.AmmoSlotIndex = 0 spiker.FireModes.head.Magazine = 25 spiker.Tile = InventoryTile.Tile33 + //TODO the spiker is weird mini_chaingun.Size = EquipmentSize.Rifle mini_chaingun.AmmoTypes += bullet_9mm @@ -1289,17 +1281,18 @@ object GlobalDefinitions { r_shotgun.Size = EquipmentSize.Rifle r_shotgun.AmmoTypes += shotgun_shell r_shotgun.AmmoTypes += shotgun_shell_AP - r_shotgun.FireModes += new FireModeDefinition + r_shotgun.FireModes += new PelletFireModeDefinition r_shotgun.FireModes.head.AmmoTypeIndices += 0 r_shotgun.FireModes.head.AmmoTypeIndices += 1 r_shotgun.FireModes.head.AmmoSlotIndex = 0 - r_shotgun.FireModes.head.Magazine = 16 //16 shells * 8 pellets = 128 - r_shotgun.FireModes += new FireModeDefinition + r_shotgun.FireModes.head.Magazine = 16 + r_shotgun.FireModes.head.Chamber = 8 //16 shells * 8 pellets = 128 + r_shotgun.FireModes += new PelletFireModeDefinition r_shotgun.FireModes(1).AmmoTypeIndices += 0 r_shotgun.FireModes(1).AmmoTypeIndices += 1 r_shotgun.FireModes(1).AmmoSlotIndex = 0 - r_shotgun.FireModes(1).Magazine = 16 //16 shells * 8 pellets = 128 - r_shotgun.FireModes(1).Chamber = 3 + r_shotgun.FireModes(1).Magazine = 16 + r_shotgun.FireModes(1).Chamber = 8 //16 shells * 8 pellets = 128 r_shotgun.Tile = InventoryTile.Tile93 lasher.Size = EquipmentSize.Rifle @@ -1329,6 +1322,7 @@ object GlobalDefinitions { maelstrom.FireModes(2).AmmoSlotIndex = 0 maelstrom.FireModes(2).Magazine = 150 maelstrom.Tile = InventoryTile.Tile93 + //TODO the maelstrom is weird phoenix.Size = EquipmentSize.Rifle phoenix.AmmoTypes += phoenix_missile @@ -1387,7 +1381,6 @@ object GlobalDefinitions { rocklet.FireModes(1).AmmoTypeIndices += 1 rocklet.FireModes(1).AmmoSlotIndex = 0 rocklet.FireModes(1).Magazine = 6 - rocklet.FireModes(1).Chamber = 6 rocklet.Tile = InventoryTile.Tile63 thumper.Size = EquipmentSize.Rifle @@ -1454,12 +1447,11 @@ object GlobalDefinitions { flamethrower.FireModes.head.AmmoTypeIndices += 0 flamethrower.FireModes.head.AmmoSlotIndex = 0 flamethrower.FireModes.head.Magazine = 100 - flamethrower.FireModes.head.Chamber = 5 flamethrower.FireModes += new FireModeDefinition flamethrower.FireModes(1).AmmoTypeIndices += 0 flamethrower.FireModes(1).AmmoSlotIndex = 0 flamethrower.FireModes(1).Magazine = 100 - flamethrower.FireModes(1).Chamber = 50 + flamethrower.FireModes(1).Rounds = 50 flamethrower.Tile = InventoryTile.Tile63 trhev_dualcycler.Size = EquipmentSize.Max @@ -1489,18 +1481,21 @@ object GlobalDefinitions { nchev_scattercannon.Size = EquipmentSize.Max nchev_scattercannon.AmmoTypes += scattercannon_ammo - nchev_scattercannon.FireModes += new FireModeDefinition + nchev_scattercannon.FireModes += new PelletFireModeDefinition nchev_scattercannon.FireModes.head.AmmoTypeIndices += 0 nchev_scattercannon.FireModes.head.AmmoSlotIndex = 0 nchev_scattercannon.FireModes.head.Magazine = 40 - nchev_scattercannon.FireModes += new FireModeDefinition + nchev_scattercannon.FireModes.head.Chamber = 10 + nchev_scattercannon.FireModes += new PelletFireModeDefinition nchev_scattercannon.FireModes(1).AmmoTypeIndices += 0 nchev_scattercannon.FireModes(1).AmmoSlotIndex = 0 nchev_scattercannon.FireModes(1).Magazine = 40 - nchev_scattercannon.FireModes += new FireModeDefinition + nchev_scattercannon.FireModes(1).Chamber = 10 + nchev_scattercannon.FireModes += new PelletFireModeDefinition nchev_scattercannon.FireModes(2).AmmoTypeIndices += 0 nchev_scattercannon.FireModes(2).AmmoSlotIndex = 0 nchev_scattercannon.FireModes(2).Magazine = 40 + nchev_scattercannon.FireModes(2).Chamber = 10 nchev_falcon.Size = EquipmentSize.Max nchev_falcon.AmmoTypes += falcon_ammo @@ -1584,11 +1579,10 @@ object GlobalDefinitions { trek.FireModes.head.AmmoTypeIndices += 0 trek.FireModes.head.AmmoSlotIndex = 0 trek.FireModes.head.Magazine = 4 - trek.FireModes += new FireModeDefinition + trek.FireModes += new InfiniteFireModeDefinition trek.FireModes(1).AmmoTypeIndices += 0 trek.FireModes(1).AmmoSlotIndex = 0 trek.FireModes(1).Magazine = 1 - trek.FireModes(1).Chamber = 0 trek.Tile = InventoryTile.Tile33 flail_targeting_laser.Packet = new CommandDetonaterConverter diff --git a/common/src/main/scala/net/psforever/objects/Tool.scala b/common/src/main/scala/net/psforever/objects/Tool.scala index df6c3df1..2b6d122a 100644 --- a/common/src/main/scala/net/psforever/objects/Tool.scala +++ b/common/src/main/scala/net/psforever/objects/Tool.scala @@ -33,6 +33,7 @@ class Tool(private val toolDef : ToolDefinition) extends Equipment with FireMode def NextFireMode : FireModeDefinition = { FireModeIndex = FireModeIndex + 1 + AmmoSlot.Chamber = FireMode.Chamber FireMode } @@ -59,7 +60,9 @@ class Tool(private val toolDef : ToolDefinition) extends Equipment with FireMode def MaxMagazine : Int = FireMode.Magazine - def NextDischarge : Int = math.min(Magazine, FireMode.Chamber) + def Discharge : Int = { + Magazine = FireMode.Discharge(this) + } def AmmoSlot : Tool.FireModeSlot = ammoSlots(FireMode.AmmoSlotIndex) @@ -127,6 +130,7 @@ object Tool { private var ammoTypeIndex : Int = 0 /** a reference to the actual `AmmoBox` of this slot */ private var box : AmmoBox = AmmoBox(AmmoDefinition, fdef.Magazine) + private var chamber = fdef.Chamber def AmmoTypeIndex : Int = ammoTypeIndex @@ -158,6 +162,13 @@ object Tool { Magazine } + def Chamber : Int = chamber + + def Chamber_=(chmbr : Int) : Int = { + chamber = math.min(math.max(0, chmbr), fdef.Chamber) + Chamber + } + def Box : AmmoBox = box def Box_=(toBox : AmmoBox) : Option[AmmoBox] = { diff --git a/common/src/main/scala/net/psforever/objects/equipment/FireModeDefinition.scala b/common/src/main/scala/net/psforever/objects/equipment/FireModeDefinition.scala index 423154bc..631e5e40 100644 --- a/common/src/main/scala/net/psforever/objects/equipment/FireModeDefinition.scala +++ b/common/src/main/scala/net/psforever/objects/equipment/FireModeDefinition.scala @@ -1,16 +1,25 @@ // Copyright (c) 2017 PSForever package net.psforever.objects.equipment +import net.psforever.objects.Tool + import scala.collection.mutable class FireModeDefinition { - private val ammoTypeIndices : mutable.ListBuffer[Int] = mutable.ListBuffer[Int]() //indices pointing to all ammo types used - private var ammoSlotIndex : Int = 0 //ammunition slot number this fire mode utilizes - private var chamber : Int = 1 //how many rounds are queued to be fired at once, e.g., 3 for the Jackhammer's triple burst - private var magazine : Int = 1 //how many rounds are queued for each reload cycle -// private var target : Any = _ //target designation (self? other?) + /** indices pointing to all ammo types used */ + private val ammoTypeIndices : mutable.ListBuffer[Int] = mutable.ListBuffer[Int]() + /** ammunition slot number this fire mode utilizes */ + private var ammoSlotIndex : Int = 0 + /** how many rounds are replenished each reload cycle */ + private var magazine : Int = 1 + /** how much is subtracted from the magazine each fire cycle; + * most weapons will only fire 1 round per fire cycle; the flamethrower, fire mode 1, fires 50 */ + private var rounds : Int = 1 + /** how many sub-rounds are queued per round fired; + * the flechette fires 8 pellets per shell and generates 8 fire reports before the ammo count goes down */ + private var chamber : Int = 1 - //damage modifiers will follow here ... + //damage modifiers will follow here ... ? def AmmoSlotIndex : Int = ammoSlotIndex @@ -25,13 +34,6 @@ class FireModeDefinition { ammoTypeIndices += index } - def Chamber : Int = chamber - - def Chamber_=(inChamber : Int) : Int = { - chamber = inChamber - Chamber - } - def Magazine : Int = magazine def Magazine_=(inMagazine : Int) : Int = { @@ -39,16 +41,67 @@ class FireModeDefinition { Magazine } -// def Target : Any = target -// -// def Target_+(setAsTarget : Any) : Any = { -// target = setAsTarget -// Target -// } -} + def Rounds : Int = rounds -object FireModeDefinition { - def apply() : FireModeDefinition = { - new FireModeDefinition() + def Rounds_=(round : Int) : Int = { + rounds = round + Rounds + } + + def Chamber : Int = chamber + + def Chamber_=(inChamber : Int) : Int = { + chamber = inChamber + Chamber + } + + /** + * Shoot a weapon, remove an anticipated amount of ammunition. + * @param weapon the weapon + * @return the size of the weapon's magazine after discharge + */ + def Discharge(weapon : Tool) : Int = { + weapon.Magazine - Rounds } } + +class PelletFireModeDefinition extends FireModeDefinition { + /** + * Shoot a weapon, remove an anticipated amount of ammunition.
+ *
+ * For a weapon that has a number of sub-rounds chambered, each will generate unique weapon fire per fire cycle. + * Once all of the sub-rounds have been accounted for, the number of rounds for a single fire cycle will subtract. + * Since all fire cycles will abide by this chambered number of sub-rounds, the count is reset. + * @param weapon the weapon + * @return the size of the weapon's magazine after discharge + */ + override def Discharge(weapon : Tool) : Int = { + val ammoSlot = weapon.AmmoSlot + val magazine = weapon.Magazine + val chamber : Int = ammoSlot.Chamber = ammoSlot.Chamber - 1 + if(chamber <= 0) { + ammoSlot.Chamber = Chamber + magazine - Rounds + } + else { + magazine + } + } +} + +class InfiniteFireModeDefinition extends FireModeDefinition { + /** + * Shoot a weapon, remove an anticipated amount of ammunition.
+ *
+ * No rounds will be subtracted ever. + * The weapon can keep firing as much as the user wants. + * Since the PlanetSide client also has an internal understanding of ammo values in weapons, + * it may interfere with the functionality of this fire mode + * if the size of the magazine is not implicitly set per fire cycle. + * Works well with melee weapons. + * @param weapon the weapon + * @return the size of the weapon's magazine after discharge; + * will always return 1 + */ + override def Discharge(weapon : Tool) : Int = 1 +} diff --git a/common/src/test/scala/objects/EquipmentTest.scala b/common/src/test/scala/objects/EquipmentTest.scala index ff44aab7..fe260a3e 100644 --- a/common/src/test/scala/objects/EquipmentTest.scala +++ b/common/src/test/scala/objects/EquipmentTest.scala @@ -2,11 +2,11 @@ package objects import net.psforever.objects._ -import net.psforever.objects.definition._ import net.psforever.objects.equipment.CItem.{DeployedItem, Unit} import net.psforever.objects.equipment._ import net.psforever.objects.inventory.InventoryTile import net.psforever.objects.GlobalDefinitions._ +import net.psforever.objects.definition._ import org.specs2.mutable._ class EquipmentTest extends Specification { @@ -109,12 +109,12 @@ class EquipmentTest extends Specification { obj.Size = EquipmentSize.Rifle obj.AmmoTypes += GlobalDefinitions.shotgun_shell obj.AmmoTypes += GlobalDefinitions.shotgun_shell_AP - obj.FireModes += FireModeDefinition() + obj.FireModes += new FireModeDefinition obj.FireModes.head.AmmoTypeIndices += 0 obj.FireModes.head.AmmoTypeIndices += 1 obj.FireModes.head.AmmoSlotIndex = 0 obj.FireModes.head.Magazine = 18 - obj.FireModes += FireModeDefinition() + obj.FireModes += new FireModeDefinition obj.FireModes(1).AmmoTypeIndices += 0 obj.FireModes(1).AmmoTypeIndices += 1 obj.FireModes(1).AmmoSlotIndex = 1 @@ -162,47 +162,27 @@ class EquipmentTest extends Specification { } "multiple fire modes" in { - //explanation: sample_weapon has two fire modes; adjusting the FireMode changes between them - val tdef = ToolDefinition(1076) - tdef.Size = EquipmentSize.Rifle - tdef.AmmoTypes += GlobalDefinitions.shotgun_shell - tdef.AmmoTypes += GlobalDefinitions.shotgun_shell_AP - tdef.FireModes += new FireModeDefinition - tdef.FireModes.head.AmmoTypeIndices += 0 - tdef.FireModes.head.AmmoSlotIndex = 0 - tdef.FireModes.head.Magazine = 9 - tdef.FireModes += new FireModeDefinition - tdef.FireModes(1).AmmoTypeIndices += 1 - tdef.FireModes(1).AmmoSlotIndex = 1 - tdef.FireModes(1).Magazine = 18 - val obj : Tool = Tool(tdef) + //explanation: sample_weapon has two fire modes; each fire mode has a different ammunition type + val obj : Tool = Tool(punisher) //fmode = 0 obj.FireModeIndex mustEqual 0 - obj.FireMode.Magazine mustEqual 9 - obj.AmmoType mustEqual Ammo.shotgun_shell + obj.FireMode.Magazine mustEqual 30 + obj.AmmoType mustEqual Ammo.bullet_9mm //fmode -> 1 obj.NextFireMode obj.FireModeIndex mustEqual 1 - obj.FireMode.Magazine mustEqual 18 - obj.AmmoType mustEqual Ammo.shotgun_shell_AP + obj.FireMode.Magazine mustEqual 1 + obj.AmmoType mustEqual Ammo.rocket //fmode -> 0 obj.NextFireMode obj.FireModeIndex mustEqual 0 - obj.FireMode.Magazine mustEqual 9 - obj.AmmoType mustEqual Ammo.shotgun_shell + obj.FireMode.Magazine mustEqual 30 + obj.AmmoType mustEqual Ammo.bullet_9mm } "multiple types of ammunition" in { //explanation: obj has one fire mode and two ammunitions; adjusting the AmmoType changes between them - val tdef = ToolDefinition(1076) - tdef.Size = EquipmentSize.Rifle - tdef.AmmoTypes += GlobalDefinitions.shotgun_shell - tdef.AmmoTypes += GlobalDefinitions.shotgun_shell_AP - tdef.FireModes += new FireModeDefinition - tdef.FireModes.head.AmmoTypeIndices += 0 - tdef.FireModes.head.AmmoTypeIndices += 1 - tdef.FireModes.head.AmmoSlotIndex = 0 - val obj : Tool = Tool(tdef) + val obj : Tool = Tool(flechette) //ammo = 0 obj.AmmoTypeIndex mustEqual 0 obj.AmmoType mustEqual Ammo.shotgun_shell @@ -255,6 +235,39 @@ class EquipmentTest extends Specification { obj.AmmoTypeIndex mustEqual 2 obj.AmmoType mustEqual Ammo.rocket } + + "discharge (1)" in { + val obj = Tool(GlobalDefinitions.punisher) + obj.Magazine mustEqual 30 + obj.Discharge + obj.Magazine mustEqual 29 + obj.Discharge + obj.Discharge + obj.Magazine mustEqual 27 + } + + "chamber" in { + val obj = Tool(GlobalDefinitions.flechette) + obj.Magazine mustEqual 12 + obj.AmmoSlot.Chamber mustEqual 8 + + obj.Discharge + obj.Magazine mustEqual 12 + obj.AmmoSlot.Chamber mustEqual 7 + obj.Discharge + obj.Discharge + obj.Magazine mustEqual 12 + obj.AmmoSlot.Chamber mustEqual 5 + obj.Discharge + obj.Discharge + obj.Discharge + obj.Discharge + obj.Magazine mustEqual 12 + obj.AmmoSlot.Chamber mustEqual 1 + obj.Discharge + obj.Magazine mustEqual 11 + obj.AmmoSlot.Chamber mustEqual 8 + } } "Kit" should { diff --git a/common/src/test/scala/objects/FireModeTest.scala b/common/src/test/scala/objects/FireModeTest.scala new file mode 100644 index 00000000..07342393 --- /dev/null +++ b/common/src/test/scala/objects/FireModeTest.scala @@ -0,0 +1,127 @@ +// Copyright (c) 2017 PSForever +package objects + +import net.psforever.objects.definition.ToolDefinition +import net.psforever.objects.{GlobalDefinitions, Tool} +import net.psforever.objects.equipment.{EquipmentSize, FireModeDefinition, InfiniteFireModeDefinition, PelletFireModeDefinition} +import org.specs2.mutable._ + +class FireModeTest extends Specification { + "FireModeDefinition" should { + "construct" in { + val obj = new FireModeDefinition + obj.AmmoTypeIndices mustEqual Nil + obj.AmmoSlotIndex mustEqual 0 + obj.Magazine mustEqual 1 + obj.Rounds mustEqual 1 + obj.Chamber mustEqual 1 + } + + "test configurations" in { + val tdef = ToolDefinition(1076) //fake object id + tdef.Size = EquipmentSize.Rifle + tdef.AmmoTypes += GlobalDefinitions.bullet_9mm + tdef.AmmoTypes += GlobalDefinitions.shotgun_shell + tdef.FireModes += new FireModeDefinition + tdef.FireModes.head.AmmoTypeIndices += 0 + tdef.FireModes.head.AmmoSlotIndex = 0 + tdef.FireModes.head.Magazine = 18 + tdef.FireModes.head.Rounds = 18 + tdef.FireModes.head.Chamber = 2 + tdef.FireModes += new FireModeDefinition + tdef.FireModes(1).AmmoTypeIndices += 1 + tdef.FireModes(1).AmmoTypeIndices += 2 + tdef.FireModes(1).AmmoSlotIndex = 1 + tdef.FireModes(1).Magazine = 9 + tdef.FireModes(1).Rounds = 2 + tdef.FireModes(1).Chamber = 8 + + tdef.AmmoTypes.toList mustEqual List(GlobalDefinitions.bullet_9mm, GlobalDefinitions.shotgun_shell) + tdef.FireModes.size mustEqual 2 + tdef.FireModes.head.AmmoTypeIndices.toList mustEqual List(0) + tdef.FireModes.head.AmmoSlotIndex mustEqual 0 + tdef.FireModes.head.Magazine mustEqual 18 + tdef.FireModes.head.Rounds mustEqual 18 + tdef.FireModes.head.Chamber mustEqual 2 + tdef.FireModes(1).AmmoTypeIndices.toList mustEqual List(1, 2) + tdef.FireModes(1).AmmoSlotIndex mustEqual 1 + tdef.FireModes(1).Magazine mustEqual 9 + tdef.FireModes(1).Rounds mustEqual 2 + tdef.FireModes(1).Chamber mustEqual 8 + } + + "discharge" in { + val obj = Tool(GlobalDefinitions.beamer) //see EquipmentTest + obj.FireMode.isInstanceOf[FireModeDefinition] mustEqual true + obj.Magazine mustEqual 16 + obj.FireMode.Rounds mustEqual 1 + obj.FireMode.Chamber mustEqual 1 + + obj.Magazine mustEqual 16 + obj.Discharge + obj.Magazine mustEqual 15 + obj.Discharge + obj.Discharge + obj.Magazine mustEqual 13 + } + } + + "PelletFireModeDefinition" should { + "construct" in { + val obj = new PelletFireModeDefinition + obj.AmmoTypeIndices mustEqual Nil + obj.AmmoSlotIndex mustEqual 0 + obj.Magazine mustEqual 1 + obj.Rounds mustEqual 1 + obj.Chamber mustEqual 1 + } + + "discharge" in { + val obj = Tool(GlobalDefinitions.flechette) //see EquipmentTest + obj.FireMode.isInstanceOf[PelletFireModeDefinition] mustEqual true + obj.Magazine mustEqual 12 + obj.FireMode.Rounds mustEqual 1 + obj.FireMode.Chamber mustEqual 8 + + obj.Magazine mustEqual 12 + obj.Discharge //1 + obj.Magazine mustEqual 12 + obj.Discharge //2 + obj.Discharge //3 + obj.Magazine mustEqual 12 + obj.Discharge //4 + obj.Discharge //5 + obj.Discharge //6 + obj.Discharge //7 + obj.Magazine mustEqual 12 + obj.Discharge //8 + obj.Magazine mustEqual 11 + } + } + + "InfiniteFireModeDefinition" should { + "construct" in { + val obj = new InfiniteFireModeDefinition + obj.AmmoTypeIndices mustEqual Nil + obj.AmmoSlotIndex mustEqual 0 + obj.Magazine mustEqual 1 + obj.Rounds mustEqual 1 + obj.Chamber mustEqual 1 + } + + "discharge" in { + val obj = Tool(GlobalDefinitions.magcutter) //see EquipmentTest + obj.FireMode.isInstanceOf[InfiniteFireModeDefinition] mustEqual true + obj.Magazine mustEqual 1 + obj.FireMode.Rounds mustEqual 1 + obj.FireMode.Chamber mustEqual 1 + + obj.Magazine mustEqual 1 + obj.Discharge + obj.Magazine mustEqual 1 + obj.Discharge + obj.Discharge + obj.Magazine mustEqual 1 + } + } +} diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index 17bebda3..8ab2cfcc 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -1116,6 +1116,12 @@ class WorldSessionActor extends Actor with MDCContextAware { player.Certifications += CertificationType.ATV player.Certifications += CertificationType.Harasser // + player.Certifications += CertificationType.InfiltrationSuit + player.Certifications += CertificationType.Sniping + player.Certifications += CertificationType.AntiVehicular + player.Certifications += CertificationType.HeavyAssault + player.Certifications += CertificationType.SpecialAssault + player.Certifications += CertificationType.EliteAssault player.Certifications += CertificationType.GroundSupport player.Certifications += CertificationType.GroundTransport player.Certifications += CertificationType.Flail @@ -1130,7 +1136,6 @@ class WorldSessionActor extends Actor with MDCContextAware { player.Certifications += CertificationType.GalaxyGunship player.Certifications += CertificationType.Phantasm player.Certifications += CertificationType.UniMAX - player.Certifications += CertificationType.InfiltrationSuit AwardBattleExperiencePoints(player, 1000000L) // player.ExoSuit = ExoSuitType.MAX //TODO strange issue; divide number above by 10 when uncommenting player.Slot(0).Equipment = Tool(GlobalDefinitions.StandardPistol(player.Faction)) @@ -1517,9 +1522,31 @@ class WorldSessionActor extends Actor with MDCContextAware { case msg @ ChangeFireStateMessage_Stop(item_guid) => log.info("ChangeFireState_Stop: " + msg) - if(shooting.contains(item_guid)) { + val weapon : Option[Equipment] = if(shooting.contains(item_guid)) { shooting = None avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ChangeFireState_Stop(player.GUID, item_guid)) + FindWeapon + } + else { + //some weapons, e.g., the decimator, do not send a ChangeFireState_Start on the last shot + FindWeapon match { + case Some(tool : Tool) => + if(tool.Definition == GlobalDefinitions.phoenix) { + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ChangeFireState_Start(player.GUID, item_guid)) + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ChangeFireState_Stop(player.GUID, item_guid)) + } + Some(tool) + case _ => + log.warn(s"ChangeFireState_Stop: received an unexpected message about $item_guid") + None + } + } + weapon match { + case Some(tool : Tool) => + if(tool.Magazine == 0) { + FireCycleCleanup(tool) + } + case _ => ; } progressBarUpdate.cancel //TODO independent action? @@ -1918,7 +1945,7 @@ class WorldSessionActor extends Actor with MDCContextAware { avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.WeaponDryFire(player.GUID, weapon_guid)) } else { //shooting - tool.Magazine = tool.Magazine - 1 + tool.Discharge //TODO other stuff? } case _ => ; @@ -2253,7 +2280,7 @@ class WorldSessionActor extends Actor with MDCContextAware { ) ) ) - if(0 <= localIndex && localIndex < 5) { + if(localTarget.VisibleSlots.contains(localIndex)) { localService ! AvatarServiceMessage(continent.Id, AvatarAction.EquipmentInHand(localTarget.GUID, localIndex, localObject)) } } @@ -2398,7 +2425,7 @@ class WorldSessionActor extends Actor with MDCContextAware { override def onSuccess() : Unit = { localAnnounce ! ResponseToSelf(PacketCoding.CreateGamePacket(0, ObjectDeleteMessage(localObjectGUID, 0))) - if(0 <= localIndex && localIndex < 5) { + if(localTarget.VisibleSlots.contains(localIndex)) { localService ! AvatarServiceMessage(localContinent, AvatarAction.ObjectDelete(localTarget.GUID, localObjectGUID)) } } @@ -2827,6 +2854,21 @@ class WorldSessionActor extends Actor with MDCContextAware { ) } + /** + * After a weapon has finished shooting, determine if it needs to be sorted in a special way. + * @param tool a weapon + */ + def FireCycleCleanup(tool : Tool) : Unit = { + //TODO this is temporary and will be replaced by more appropriate functionality in the future. + val tdef = tool.Definition + if(GlobalDefinitions.isGrenade(tdef)) { + taskResolver ! RemoveEquipmentFromSlot(player, tool, player.Find(tool).get) + } + else if(tdef == GlobalDefinitions.phoenix) { + taskResolver ! RemoveEquipmentFromSlot(player, tool, player.Find(tool).get) + } + } + /** * A predicate used to determine if an `InventoryItem` object contains `Equipment` that should be dropped. * Used to filter through lists of object data before it is placed into a player's inventory. From c2bcb63725b61d62ac65b16bee4ce688db4ecc20 Mon Sep 17 00:00:00 2001 From: FateJH Date: Sat, 20 Jan 2018 15:11:11 -0500 Subject: [PATCH 7/7] corrections for the shared magazine fire modes of the Aurora; conditions for character rendering when in a vehicle; WeaponJammedMessage -> WeaponDryFireMessage --- .../psforever/objects/GlobalDefinitions.scala | 4 ++-- .../packet/game/objectcreate/ObjectClass.scala | 4 ++-- .../packet/game/objectcreate/Prefab.scala | 4 ++-- pslogin/src/main/scala/WorldSessionActor.scala | 16 +++++++++++++--- 4 files changed, 19 insertions(+), 9 deletions(-) diff --git a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala index 15c4af9c..c670a868 100644 --- a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala +++ b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala @@ -1736,7 +1736,7 @@ object GlobalDefinitions { aurora_weapon_systema.FireModes.head.Magazine = 12 aurora_weapon_systema.FireModes += new FireModeDefinition aurora_weapon_systema.FireModes(1).AmmoTypeIndices += 0 - aurora_weapon_systema.FireModes(1).AmmoSlotIndex = 1 + aurora_weapon_systema.FireModes(1).AmmoSlotIndex = 0 aurora_weapon_systema.FireModes(1).Magazine = 12 aurora_weapon_systemb.Size = EquipmentSize.VehicleWeapon @@ -1747,7 +1747,7 @@ object GlobalDefinitions { aurora_weapon_systemb.FireModes.head.Magazine = 12 aurora_weapon_systemb.FireModes += new FireModeDefinition aurora_weapon_systemb.FireModes(1).AmmoTypeIndices += 0 - aurora_weapon_systemb.FireModes(1).AmmoSlotIndex = 1 + aurora_weapon_systemb.FireModes(1).AmmoSlotIndex = 0 aurora_weapon_systemb.FireModes(1).Magazine = 12 apc_weapon_systema.Size = EquipmentSize.VehicleWeapon 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 2b1bef29..9ceebca1 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 @@ -797,8 +797,8 @@ object ObjectClass { case ObjectClass.aphelion_starfire => ConstructorData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.aphelion_starfire_left => ConstructorData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.aphelion_starfire_right => ConstructorData.genericCodec(WeaponData.codec, "weapon") - case ObjectClass.aurora_weapon_systema => ConstructorData.genericCodec(WeaponData.codec(2), "weapon") - case ObjectClass.aurora_weapon_systemb => ConstructorData.genericCodec(WeaponData.codec(2), "weapon") + case ObjectClass.aurora_weapon_systema => ConstructorData.genericCodec(WeaponData.codec, "weapon") + case ObjectClass.aurora_weapon_systemb => ConstructorData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.battlewagon_weapon_systema => ConstructorData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.battlewagon_weapon_systemb => ConstructorData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.battlewagon_weapon_systemc => ConstructorData.genericCodec(WeaponData.codec, "weapon") diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/Prefab.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/Prefab.scala index 1a3d8aed..54a6fff4 100644 --- a/common/src/main/scala/net/psforever/packet/game/objectcreate/Prefab.scala +++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/Prefab.scala @@ -106,10 +106,10 @@ object Prefab { VehicleData(CommonFieldData(loc, faction, 0), 0, health, false, false, DriveState.State7, true, false, false, None, Some(InventoryData( InventoryItemData(ObjectClass.aurora_weapon_systema, weapon1_guid, 5, - WeaponData(0x6, 0x8, 0, ObjectClass.fluxpod_ammo, ammo11_guid, 0, AmmoBoxData(0x8), ObjectClass.fluxpod_ammo, ammo12_guid, 1, AmmoBoxData(0x8)) + WeaponData(0x6, 0x8, 0, ObjectClass.fluxpod_ammo, ammo11_guid, 0, AmmoBoxData(0x8)) ) :: InventoryItemData(ObjectClass.aurora_weapon_systemb, weapon2_guid, 6, - WeaponData(0x6, 0x8, 0, ObjectClass.fluxpod_ammo, ammo21_guid, 0, AmmoBoxData(0x8), ObjectClass.fluxpod_ammo, ammo22_guid, 1, AmmoBoxData(0x8)) + WeaponData(0x6, 0x8, 0, ObjectClass.fluxpod_ammo, ammo21_guid, 0, AmmoBoxData(0x8)) ) :: Nil )) )(VehicleFormat.Normal) diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index 8ab2cfcc..e6d2e584 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -234,6 +234,7 @@ class WorldSessionActor extends Actor with MDCContextAware { case AvatarResponse.PlayerState(msg, spectating, weaponInHand) => if(player.GUID != guid) { val now = System.currentTimeMillis() + val (location, time, distanceSq) : (Vector3, Long, Float) = if(spectating) { (Vector3(2, 2, 2), 0L, 0f) } @@ -280,7 +281,7 @@ class WorldSessionActor extends Actor with MDCContextAware { case AvatarResponse.WeaponDryFire(weapon_guid) => if(player.GUID != guid) { - sendResponse(PacketCoding.CreateGamePacket(0, WeaponJammedMessage(weapon_guid))) + sendResponse(PacketCoding.CreateGamePacket(0, WeaponDryFireMessage(weapon_guid))) } case _ => ; @@ -387,6 +388,9 @@ class WorldSessionActor extends Actor with MDCContextAware { case VehicleResponse.VehicleState(vehicle_guid, unk1, pos, ang, vel, unk2, unk3, unk4, wheel_direction, unk5, unk6) => if(player.GUID != guid) { sendResponse(PacketCoding.CreateGamePacket(0, VehicleStateMessage(vehicle_guid, unk1, pos, ang, vel, unk2, unk3, unk4, wheel_direction, unk5, unk6))) + if(player.VehicleSeated.contains(vehicle_guid)) { + player.Position = pos + } } case _ => ; @@ -449,7 +453,7 @@ class WorldSessionActor extends Actor with MDCContextAware { //update mounted weapon belonging to seat weapon.AmmoSlots.foreach(slot => { //update the magazine(s) in the weapon, specifically val magazine = slot.Box - sendResponse(PacketCoding.CreateGamePacket(0, InventoryStateMessage(magazine.GUID, 0, weapon.GUID, magazine.Capacity.toLong))) + sendResponse(PacketCoding.CreateGamePacket(0, InventoryStateMessage(magazine.GUID, weapon.GUID, magazine.Capacity.toLong))) }) case _ => ; //no weapons to update } @@ -1295,6 +1299,7 @@ class WorldSessionActor extends Actor with MDCContextAware { case Some(tool) => if(tool.GUID == object_guid) { //TODO set tool orientation? + player.Orientation = Vector3(0f, pitch, yaw) vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.ChildObjectState(player.GUID, object_guid, pitch, yaw)) } case None => @@ -1315,7 +1320,12 @@ class WorldSessionActor extends Actor with MDCContextAware { case msg @ VehicleStateMessage(vehicle_guid, unk1, pos, ang, vel, unk5, unk6, unk7, wheels, unk9, unkA) => continent.GUID(vehicle_guid) match { case Some(obj : Vehicle) => - if(obj.Seat(0).get.Occupant.contains(player)) { //we're driving the vehicle + val seat = obj.Seat(0).get + if(seat.Occupant.contains(player)) { //we're driving the vehicle + player.Position = pos //convenient + if(seat.ControlledWeapon.isEmpty) { + player.Orientation = Vector3(0f, 0f, ang.z) //convenient + } obj.Position = pos obj.Orientation = ang obj.Velocity = vel