From 98fc9f5fbad7c53331dcaf673ed07601192e7edb Mon Sep 17 00:00:00 2001 From: FateJH Date: Thu, 17 May 2018 00:19:12 -0400 Subject: [PATCH 01/10] more petience with VehicleSeated; added independent DeadState variable (temporary?); changed conditions of vehicle deconstruction scheduler --- pslogin/src/main/scala/Maps.scala | 8 +-- .../src/main/scala/WorldSessionActor.scala | 72 +++++++++++-------- .../support/DelayedDeconstructionActor.scala | 38 ++++++---- 3 files changed, 71 insertions(+), 47 deletions(-) diff --git a/pslogin/src/main/scala/Maps.scala b/pslogin/src/main/scala/Maps.scala index 4c750b620..64f8e22f8 100644 --- a/pslogin/src/main/scala/Maps.scala +++ b/pslogin/src/main/scala/Maps.scala @@ -460,8 +460,6 @@ object Maps { def Building2() : Unit = { //HART building C LocalBuilding(2, FoundationBuilder(Building.Structure(StructureType.Building))) - LocalObject(12, ProximityTerminal.Constructor(repair_silo)) //repair terminal A - LocalObject(13, Terminal.Constructor(repair_silo)) //rearm terminal A //ItemTransaction: ItemTransactionMessage(PlanetSideGUID(2050),Buy,3,25mmbullet,0,PlanetSideGUID(0)) LocalObject(186, Terminal.Constructor(cert_terminal)) LocalObject(187, Terminal.Constructor(cert_terminal)) LocalObject(188, Terminal.Constructor(cert_terminal)) @@ -509,8 +507,6 @@ object Maps { LocalObject(1087, Terminal.Constructor(implant_terminal_interface)) //TODO guid not correct LocalObject(1088, Terminal.Constructor(implant_terminal_interface)) //TODO guid not correct LocalObject(1089, Terminal.Constructor(implant_terminal_interface)) //TODO guid not correct - ObjectToBuilding(12, 2) - ObjectToBuilding(13, 2) ObjectToBuilding(186, 2) ObjectToBuilding(187, 2) ObjectToBuilding(188, 2) @@ -652,11 +648,15 @@ object Maps { def Building51() : Unit = { //air terminal west of HART C + LocalObject(12, ProximityTerminal.Constructor(repair_silo)) //repair terminal + LocalObject(13, Terminal.Constructor(repair_silo)) //rearm terminal LocalBuilding(51, FoundationBuilder(Building.Structure(StructureType.Platform))) LocalObject(304, Terminal.Constructor(dropship_vehicle_terminal)) LocalObject(292, VehicleSpawnPad.Constructor(Vector3(3508.9844f, 2895.961f, 92.296875f), Vector3(0f, 0f, 90.0f)) ) + ObjectToBuilding(12, 51) + ObjectToBuilding(13, 51) ObjectToBuilding(304, 51) ObjectToBuilding(292, 51) TerminalToSpawnPad(304, 292) diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index bc4d076ff..8776e3220 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -73,10 +73,12 @@ class WorldSessionActor extends Actor with MDCContextAware { var delayedProximityTerminalResets : Map[PlanetSideGUID, Cancellable] = Map.empty var controlled : Option[Int] = None //keep track of avatar's ServerVehicleOverride state var traveler : Traveler = null + var deadState : DeadState.Value = DeadState.Dead var clientKeepAlive : Cancellable = DefaultCancellable.obj var progressBarUpdate : Cancellable = DefaultCancellable.obj var reviveTimer : Cancellable = DefaultCancellable.obj + var respawnTimer : Cancellable = DefaultCancellable.obj /** * Convert a boolean value into an integer value. @@ -89,6 +91,7 @@ class WorldSessionActor extends Actor with MDCContextAware { override def postStop() = { clientKeepAlive.cancel reviveTimer.cancel + respawnTimer.cancel PlayerActionsToCancel() localService ! Service.Leave() vehicleService ! Service.Leave() @@ -111,12 +114,7 @@ class WorldSessionActor extends Actor with MDCContextAware { if(player.isAlive) { //actually being alive or manually deconstructing - player.VehicleSeated match { - case Some(vehicle_guid) => - DismountVehicleOnLogOut(vehicle_guid, player_guid) - case None => ; - } - + DismountVehicleOnLogOut() continent.Population ! Zone.Population.Release(avatar) player.Position = Vector3.Zero //save character before doing this avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.ObjectDelete(player_guid, player_guid)) @@ -148,7 +146,7 @@ class WorldSessionActor extends Actor with MDCContextAware { player.Position = Vector3.Zero //save character before doing this avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ObjectDelete(player_guid, player_guid, 0)) taskResolver ! GUIDTask.UnregisterAvatar(player)(continent.GUID) - DismountVehicleOnLogOut(vehicle_guid, player_guid) + DismountVehicleOnLogOut() } } @@ -170,26 +168,27 @@ class WorldSessionActor extends Actor with MDCContextAware { /** * Vehicle cleanup that is specific to log out behavior. - * @param vehicle_guid the vehicle being occupied - * @param player_guid the player */ - def DismountVehicleOnLogOut(vehicle_guid : PlanetSideGUID, player_guid : PlanetSideGUID) : Unit = { - // Use mountable type instead of Vehicle type to ensure mountables such as implant terminals are correctly dismounted on logout - val mountable = continent.GUID(vehicle_guid).get.asInstanceOf[Mountable] - mountable.Seat(mountable.PassengerInSeat(player).get).get.Occupant = None - - // If this is a player constructed vehicle then start deconstruction timer - // todo: Will base guns implement Vehicle type? Don't want those to deconstruct - continent.GUID(vehicle_guid) match { + def DismountVehicleOnLogOut() : Unit = { + //TODO Will base guns implement Vehicle type? Don't want those to deconstruct + (player.VehicleSeated match { + case Some(vehicle_guid) => + continent.GUID(vehicle_guid) + case None => + None + }) match { case Some(vehicle : Vehicle) => - if(vehicle.Seats.values.count(_.isOccupied) == 0) { - vehicleService ! VehicleServiceMessage.DelayedVehicleDeconstruction(vehicle, continent, 600L) //start vehicle decay (10m) - } + vehicle.Seat(vehicle.PassengerInSeat(player).get).get.Occupant = None + if(vehicle.Seats.values.count(_.isOccupied) == 0) { + vehicleService ! VehicleServiceMessage.DelayedVehicleDeconstruction(vehicle, continent, 600L) //start vehicle decay (10m) + } + vehicleService ! Service.Leave(Some(s"${vehicle.Actor}")) + + case Some(mobj : Mountable) => + mobj.Seat(mobj.PassengerInSeat(player).get).get.Occupant = None + case _ => ; } - - vehicleService ! Service.Leave(Some(s"${mountable.Actor}")) - vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.KickPassenger(player_guid, 0, true, vehicle_guid)) } def receive = Initializing @@ -1235,11 +1234,13 @@ class WorldSessionActor extends Actor with MDCContextAware { case Zone.Lattice.SpawnPoint(zone_id, building, spawn_tube) => log.info(s"Zone.Lattice.SpawnPoint: spawn point on $zone_id in ${building.Id} @ ${spawn_tube.GUID.guid} selected") + respawnTimer.cancel reviveTimer.cancel val sameZone = zone_id == continent.Id val backpack = player.isBackpack val respawnTime : Long = if(sameZone) { 10 } else { 0 } //s val respawnTimeMillis = respawnTime * 1000 //ms + deadState = DeadState.RespawnTime sendResponse(AvatarDeadStateMessage(DeadState.RespawnTime, respawnTimeMillis, respawnTimeMillis, Vector3.Zero, player.Faction, true)) val tplayer = if(backpack) { RespawnClone(player) //new player @@ -1279,7 +1280,7 @@ class WorldSessionActor extends Actor with MDCContextAware { } import scala.concurrent.duration._ import scala.concurrent.ExecutionContext.Implicits.global - context.system.scheduler.scheduleOnce(respawnTime seconds, target, msg) + respawnTimer = context.system.scheduler.scheduleOnce(respawnTime seconds, target, msg) case Zone.Lattice.NoValidSpawnPoint(zone_number, None) => log.warn(s"Zone.Lattice.SpawnPoint: zone $zone_number could not be accessed as requested") @@ -1304,6 +1305,9 @@ class WorldSessionActor extends Actor with MDCContextAware { case InterstellarCluster.GiveWorld(zoneId, zone) => log.info(s"Zone $zoneId will now load") + avatarService ! Service.Leave(Some(continent.Id)) + localService ! Service.Leave(Some(continent.Id)) + vehicleService ! Service.Leave(Some(continent.Id)) player.Continent = zoneId continent = zone continent.Population ! Zone.Population.Join(avatar) @@ -1362,6 +1366,7 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(ChangeShortcutBankMessage(guid, 0)) //FavoritesMessage sendResponse(SetChatFilterMessage(ChatChannel.Local, false, ChatChannel.values.toList)) //TODO will not always be "on" like this + deadState = DeadState.Alive sendResponse(AvatarDeadStateMessage(DeadState.Alive, 0,0, tplayer.Position, player.Faction, true)) sendResponse(PlanetsideAttributeMessage(guid, 53, 1)) sendResponse(AvatarSearchCriteriaMessage(guid, List(0,0,0,0,0,0))) @@ -1545,6 +1550,9 @@ class WorldSessionActor extends Actor with MDCContextAware { log.info("Reticulating splines ...") traveler.zone = continent.Id StartBundlingPackets() + avatarService ! Service.Join(continent.Id) + localService ! Service.Join(continent.Id) + vehicleService ! Service.Join(continent.Id) configZone(continent) sendResponse(TimeOfDayMessage(1191182336)) @@ -1619,9 +1627,6 @@ class WorldSessionActor extends Actor with MDCContextAware { } }) StopBundlingPackets() - avatarService ! Service.Join(player.Continent) - localService ! Service.Join(player.Continent) - vehicleService ! Service.Join(player.Continent) self ! SetCurrentAvatar(player) case msg @ PlayerStateMessageUpstream(avatar_guid, pos, vel, yaw, pitch, yaw_upper, seq_time, unk3, is_crouching, is_jumping, unk4, is_cloaking, unk5, unk6) => @@ -1703,6 +1708,7 @@ class WorldSessionActor extends Actor with MDCContextAware { log.info(s"ReleaseAvatarRequest: ${player.GUID} on ${continent.Id} has released") reviveTimer.cancel player.Release + deadState = DeadState.Release sendResponse(AvatarDeadStateMessage(DeadState.Release, 0, 0, player.Position, player.Faction, true)) continent.Population ! Zone.Population.Release(avatar) player.VehicleSeated match { @@ -1808,7 +1814,9 @@ class WorldSessionActor extends Actor with MDCContextAware { } if(messagetype == ChatMessageType.CMT_SUICIDE) { - KillPlayer(player) + if(player.isAlive && deadState != DeadState.Release) { + KillPlayer(player) + } } if(messagetype == ChatMessageType.CMT_DESTROY) { @@ -2404,6 +2412,7 @@ class WorldSessionActor extends Actor with MDCContextAware { PlayerActionsToCancel() CancelAllProximityUnits() player.Release + deadState = DeadState.Release sendResponse(AvatarDeadStateMessage(DeadState.Release, 0, 0, player.Position, player.Faction, true)) continent.Population ! Zone.Population.Release(avatar) @@ -3972,14 +3981,19 @@ class WorldSessionActor extends Actor with MDCContextAware { val pos = tplayer.Position val respawnTimer = 300000 //milliseconds tplayer.Die + deadState = DeadState.Dead sendResponse(PlanetsideAttributeMessage(player_guid, 0, 0)) sendResponse(PlanetsideAttributeMessage(player_guid, 2, 0)) avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(player_guid, 0, 0)) sendResponse(DestroyMessage(player_guid, player_guid, PlanetSideGUID(0), pos)) //how many players get this message? sendResponse(AvatarDeadStateMessage(DeadState.Dead, respawnTimer, respawnTimer, pos, player.Faction, true)) if(tplayer.VehicleSeated.nonEmpty) { + continent.GUID(tplayer.VehicleSeated.get) match { + case Some(obj : Vehicle) => + TotalDriverVehicleControl(obj) + case _ => ; + } //make player invisible (if not, the cadaver sticks out the side in a seated position) - TotalDriverVehicleControl(continent.GUID(tplayer.VehicleSeated.get).get.asInstanceOf[Vehicle]) sendResponse(PlanetsideAttributeMessage(player_guid, 29, 1)) avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(player_guid, 29, 1)) } diff --git a/pslogin/src/main/scala/services/vehicle/support/DelayedDeconstructionActor.scala b/pslogin/src/main/scala/services/vehicle/support/DelayedDeconstructionActor.scala index 2c508ad8d..7756b4153 100644 --- a/pslogin/src/main/scala/services/vehicle/support/DelayedDeconstructionActor.scala +++ b/pslogin/src/main/scala/services/vehicle/support/DelayedDeconstructionActor.scala @@ -33,37 +33,47 @@ class DelayedDeconstructionActor extends Actor { def receive : Receive = { case DelayedDeconstructionActor.ScheduleDeconstruction(vehicle, zone, timeAlive) => trace(s"delayed deconstruction order for $vehicle in $timeAlive") - vehicles = vehicles :+ DelayedDeconstructionActor.VehicleEntry(vehicle, zone, timeAlive * 1000000000L) - if(vehicles.size == 1) { //we were the only entry so the event must be started from scratch - import scala.concurrent.ExecutionContext.Implicits.global - monitor = context.system.scheduler.scheduleOnce(DelayedDeconstructionActor.periodicTest, self, DelayedDeconstructionActor.PeriodicTaskCulling) + val oldHead = vehicles.headOption + val now : Long = System.nanoTime + vehicles = (vehicles :+ DelayedDeconstructionActor.VehicleEntry(vehicle, zone, timeAlive * 1000000000L)) + .sortBy(entry => entry.survivalTime - (now - entry.logTime)) + if(vehicles.size == 1 || oldHead != vehicles.headOption) { //we were the only entry so the event must be started from scratch + RetimePeriodicTest() } case DelayedDeconstructionActor.UnscheduleDeconstruction(vehicle_guid) => //all tasks for this vehicle are cleared from the queue //clear any task that is no longer valid by determination of unregistered GUID val before = vehicles.length - vehicles = vehicles.filter(entry => { !entry.vehicle.HasGUID || entry.vehicle.GUID != vehicle_guid }) + val now : Long = System.nanoTime + vehicles = vehicles.filter(entry => { entry.vehicle.HasGUID && entry.vehicle.GUID != vehicle_guid }) + .sortBy(entry => entry.survivalTime - (now - entry.logTime)) trace(s"attempting to clear deconstruction order for vehicle $vehicle_guid, found ${before - vehicles.length}") - if(vehicles.isEmpty) { - monitor.cancel - } + RetimePeriodicTest() case DelayedDeconstructionActor.PeriodicTaskCulling => //filter the list of deconstruction tasks for any that are need to be triggered monitor.cancel val now : Long = System.nanoTime val (vehiclesToDecon, vehiclesRemain) = vehicles.partition(entry => { now - entry.logTime >= entry.survivalTime }) - vehicles = vehiclesRemain - trace(s"vehicle culling - ${vehiclesToDecon.length} deconstruction tasks found") + vehicles = vehiclesRemain.sortBy(_.survivalTime) + trace(s"vehicle culling - ${vehiclesToDecon.length} deconstruction tasks found; ${vehiclesRemain.length} tasks remain") vehiclesToDecon.foreach(entry => { context.parent ! VehicleServiceMessage.RequestDeleteVehicle(entry.vehicle, entry.zone) }) - if(vehiclesRemain.nonEmpty) { - import scala.concurrent.ExecutionContext.Implicits.global - monitor = context.system.scheduler.scheduleOnce(DelayedDeconstructionActor.periodicTest, self, DelayedDeconstructionActor.PeriodicTaskCulling) - } + RetimePeriodicTest() case _ => ; } + + def RetimePeriodicTest() : Unit = { + monitor.cancel + vehicles.headOption match { + case None => ; + case Some(entry) => + val retime = math.max(1, entry.survivalTime - (System.nanoTime - entry.logTime)) nanoseconds + import scala.concurrent.ExecutionContext.Implicits.global + monitor = context.system.scheduler.scheduleOnce(retime, self, DelayedDeconstructionActor.PeriodicTaskCulling) + } + } } object DelayedDeconstructionActor { From 2aabf9abe73b1ee36229fc90f46ede9974f7c847 Mon Sep 17 00:00:00 2001 From: FateJH Date: Thu, 17 May 2018 11:04:15 -0400 Subject: [PATCH 02/10] moved location of expansion packet; corrected medkit use after corpse update ruined it (also now delete the used medkit and update health) --- .../src/main/scala/WorldSessionActor.scala | 56 +++++++++++++++---- 1 file changed, 46 insertions(+), 10 deletions(-) diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index 8776e3220..db6403e4a 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -74,6 +74,7 @@ class WorldSessionActor extends Actor with MDCContextAware { var controlled : Option[Int] = None //keep track of avatar's ServerVehicleOverride state var traveler : Traveler = null var deadState : DeadState.Value = DeadState.Dead + var whenUsedLastKit : Long = 0 var clientKeepAlive : Cancellable = DefaultCancellable.obj var progressBarUpdate : Cancellable = DefaultCancellable.obj @@ -1297,6 +1298,7 @@ class WorldSessionActor extends Actor with MDCContextAware { LivePlayerList.Add(sessionId, avatar) traveler = new Traveler(self, continent.Id) //PropertyOverrideMessage + sendResponse(ChatMsg(ChatMessageType.CMT_EXPANSIONS, true, "", "1 on", None)) //CC on sendResponse(PlanetsideAttributeMessage(PlanetSideGUID(0), 112, 1)) sendResponse(ReplicationStreamMessage(5, Some(6), Vector(SquadListing()))) //clear squad list sendResponse(FriendsResponse(FriendAction.InitializeFriendList, 0, true, true, Nil)) @@ -1383,7 +1385,6 @@ class WorldSessionActor extends Actor with MDCContextAware { //TacticsMessage StopBundlingPackets() - sendResponse(ChatMsg(ChatMessageType.CMT_EXPANSIONS, true, "", "1 on", None)) //CC on case Zone.ItemFromGround(tplayer, item) => val obj_guid = item.GUID @@ -2336,6 +2337,47 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(UseItemMessage(avatar_guid, unk1, object_guid, unk2, unk3, unk4, unk5, unk6, unk7, unk8, itemType)) accessedContainer = Some(obj) } + else if(!unk3) { //potential kit use + continent.GUID(unk1) match { + case Some(kit : Kit) => + player.Find(kit) match { + case Some(index) => + if(kit.Definition == GlobalDefinitions.medkit) { + if(player.Health == player.MaxHealth) { + sendResponse(ChatMsg(ChatMessageType.UNK_225, false, "", "@HealComplete", None)) + } + else if(System.currentTimeMillis - whenUsedLastKit < 5000) { + sendResponse(ChatMsg(ChatMessageType.UNK_225, false, "", s"@TimeUntilNextUse^${5 - (System.currentTimeMillis - whenUsedLastKit) / 1000}~", None)) + } + else { + player.Find(kit) match { + case Some(index) => + whenUsedLastKit = System.currentTimeMillis + player.Slot(index).Equipment = None //remove from slot immediately; must exist on client for next packet + sendResponse(UseItemMessage(avatar_guid, unk1, object_guid, 0, unk3, unk4, unk5, unk6, unk7, unk8, itemType)) + sendResponse(ObjectDeleteMessage(kit.GUID, 0)) + taskResolver ! GUIDTask.UnregisterEquipment(kit)(continent.GUID) + //TODO better health/damage control workflow + player.Health = player.Health + 25 + sendResponse(PlanetsideAttributeMessage(avatar_guid, 0, player.Health)) + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(avatar_guid, 0, player.Health)) + case None => + log.error(s"UseItem: anticipated a $kit, but can't find it") + } + } + } + else { + log.warn(s"UseItem: $kit behavior not supported") + } + + case None => ; + } + case Some(item) => + log.warn(s"UseItem: looking for Kit to use, but found $item instead") + case None => + log.warn(s"UseItem: anticipated a Kit $unk1, but can't find it") + } + } case Some(obj : Locker) => if(player.Faction == obj.Faction) { @@ -2416,15 +2458,9 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(AvatarDeadStateMessage(DeadState.Release, 0, 0, player.Position, player.Faction, true)) continent.Population ! Zone.Population.Release(avatar) - case Some(obj : PlanetSideGameObject) => - if(itemType != 121) { - sendResponse(UseItemMessage(avatar_guid, unk1, object_guid, unk2, unk3, unk4, unk5, unk6, unk7, unk8, itemType)) - } - else if(itemType == 121 && !unk3) { // TODO : medkit use ?! - sendResponse(UseItemMessage(avatar_guid, unk1, object_guid, 0, unk3, unk4, unk5, unk6, unk7, unk8, itemType)) - sendResponse(PlanetsideAttributeMessage(avatar_guid, 0, 100)) // avatar with 100 hp - sendResponse(ObjectDeleteMessage(PlanetSideGUID(unk1), 2)) - } + case Some(obj) => + log.warn(s"UseItem: don't know how to handle $obj; taking a shot in the dark") + sendResponse(UseItemMessage(avatar_guid, unk1, object_guid, unk2, unk3, unk4, unk5, unk6, unk7, unk8, itemType)) case None => log.error(s"UseItem: can not find object $object_guid") From c838ff3168f9b96dbf96ae3d1f38fd40abfe3515 Mon Sep 17 00:00:00 2001 From: FateJH Date: Thu, 17 May 2018 18:32:18 -0400 Subject: [PATCH 03/10] splitting too big ammo boxes upon changing ammo; also, properly dropping too much ammo after same action --- .../scala/net/psforever/objects/AmmoBox.scala | 5 +- .../src/main/scala/WorldSessionActor.scala | 96 +++++++++++++++---- 2 files changed, 82 insertions(+), 19 deletions(-) diff --git a/common/src/main/scala/net/psforever/objects/AmmoBox.scala b/common/src/main/scala/net/psforever/objects/AmmoBox.scala index 8b8edaa62..0b5e81818 100644 --- a/common/src/main/scala/net/psforever/objects/AmmoBox.scala +++ b/common/src/main/scala/net/psforever/objects/AmmoBox.scala @@ -40,6 +40,8 @@ object AmmoBox { * 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. + * The first element in the returned value is always the same object as the input object. + * Even if the original ammo object is not split, a list comprised of that same original object is returned. * @param box an `AmmoBox` object of unspecified capacity * @return a `List` of `AmmoBox` objects with correct capacities */ @@ -48,7 +50,8 @@ object AmmoBox { val boxCap : Int = box.Capacity val maxCap : Int = ammoDef.Capacity val splitCap : Int = boxCap / maxCap - val list : List[AmmoBox] = List.fill(splitCap)(new AmmoBox(ammoDef)) + box.Capacity = math.min(box.Capacity, maxCap) + val list : List[AmmoBox] = if(splitCap == 0) { Nil } else { box +: List.fill(splitCap - 1)(new AmmoBox(ammoDef)) } val leftover = boxCap - maxCap * splitCap if(leftover > 0) { list :+ AmmoBox(ammoDef, leftover) diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index db6403e4a..b598e867f 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -244,7 +244,7 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(KeepAliveMessage()) case AvatarServiceResponse(_, guid, reply) => - val tplayer_guid = if(player.HasGUID) { player.GUID} else { PlanetSideGUID(0) } + val tplayer_guid = if(player.HasGUID) { player.GUID} else { PlanetSideGUID(-1) } reply match { case AvatarResponse.ArmorChanged(suit, subtype) => if(tplayer_guid != guid) { @@ -298,8 +298,9 @@ class WorldSessionActor extends Actor with MDCContextAware { ) } - case AvatarResponse.EquipmentOnGround(pos, orient, item_id, item_guid, item_data) => + case msg @ AvatarResponse.EquipmentOnGround(pos, orient, item_id, item_guid, item_data) => if(tplayer_guid != guid) { + log.info(s"now dropping a $msg") sendResponse( ObjectCreateMessage( item_id, @@ -1509,7 +1510,7 @@ class WorldSessionActor extends Actor with MDCContextAware { //player.Orientation = Vector3(0f, 0f, 132.1875f) // player.ExoSuit = ExoSuitType.MAX //TODO strange issue; divide number above by 10 when uncommenting player.Slot(0).Equipment = SimpleItem(remote_electronics_kit) //Tool(GlobalDefinitions.StandardPistol(player.Faction)) - player.Slot(2).Equipment = Tool(punisher) //suppressor + player.Slot(2).Equipment = Tool(mini_chaingun) //punisher //suppressor player.Slot(4).Equipment = Tool(GlobalDefinitions.StandardMelee(player.Faction)) player.Slot(6).Equipment = AmmoBox(bullet_9mm, 20) //bullet_9mm player.Slot(9).Equipment = AmmoBox(rocket, 11) //bullet_9mm @@ -1948,21 +1949,27 @@ class WorldSessionActor extends Actor with MDCContextAware { } 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 + //split previousBox into AmmoBox objects of appropriate max capacity, e.g., 100 9mm -> 2 x 50 9mm + obj.Inventory.Fit(previousBox) match { + case Some(index) => stowFunc(index, previousBox) - case None => //drop - log.info(s"ChangeAmmo: dropping ammo box $previousBox") - val pos = player.Position - val orient = player.Orientation - sendResponse( - 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 - avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.EquipmentOnGround(player.GUID, pos, orient2, objDef.ObjectId, previousBox.GUID, objDef.Packet.ConstructorData(previousBox).get)) + case None => + NormalItemDrop(player, continent, avatarService)(previousBox) + } + val dropFunc : (Equipment)=>Unit = NewItemDrop(player, continent, avatarService) + AmmoBox.Split(previousBox) match { + case Nil | _ :: Nil => ; //done (the former case is technically not possible) + case _ :: xs => + modifyFunc(previousBox, 0) //update to changed capacity value + xs.foreach(box => { + obj.Inventory.Fit(box) match { + case Some(index) => + obj.Inventory += index -> box //block early, for purposes of Fit + taskResolver ! stowFuncTask(index, box) + case None => + dropFunc(box) + } + }) } } else { @@ -2370,7 +2377,8 @@ class WorldSessionActor extends Actor with MDCContextAware { log.warn(s"UseItem: $kit behavior not supported") } - case None => ; + case None => + log.error(s"UseItem: anticipated a $kit, but can't find it") } case Some(item) => log.warn(s"UseItem: looking for Kit to use, but found $item instead") @@ -3785,6 +3793,58 @@ class WorldSessionActor extends Actor with MDCContextAware { } } + /** + * Drop an `Equipment` item onto the ground. + * Specifically, instruct the item where it will appear, + * add it to the list of items that are visible to multiple users, + * and then inform others that the item has been dropped. + * @param obj a `Container` object that represents where the item will be dropped; + * curried for callback + * @param zone the continent in which the item is being dropped; + * curried for callback + * @param service a reference to the event system that announces that the item has been dropped on the ground; + * "AvatarService"; + * curried for callback + * @param item the item + */ + def NormalItemDrop(obj : PlanetSideGameObject with Container, zone : Zone, service : ActorRef)(item : Equipment) : Unit = { + val itemGUID = item.GUID + val ang = obj.Orientation.z + val pos = obj.Position + val orient = Vector3(0f, 0f, ang) + item.Position = pos + item.Orientation = orient + zone.Ground ! Zone.DropItemOnGround(item, pos, orient) + val itemDef = item.Definition + service ! AvatarServiceMessage(zone.Id, AvatarAction.EquipmentOnGround(Service.defaultPlayerGUID, pos, orient, itemDef.ObjectId, itemGUID, itemDef.Packet.ConstructorData(item).get)) + } + + /** + * Register an `Equipment` item and then drop it on the ground. + * @see `NormalItemDrop` + * @param obj a `Container` object that represents where the item will be dropped; + * curried for callback + * @param zone the continent in which the item is being dropped; + * curried for callback + * @param service a reference to the event system that announces that the item has been dropped on the ground; + * "AvatarService"; + * curried for callback + * @param item the item + */ + def NewItemDrop(obj : PlanetSideGameObject with Container, zone : Zone, service : ActorRef)(item : Equipment) : Unit = { + taskResolver ! TaskResolver.GiveTask( + new Task() { + private val localItem = item + private val localFunc : (Equipment)=>Unit = NormalItemDrop(obj, zone, service) + + def Execute(resolver : ActorRef) : Unit = { + localFunc(localItem) + resolver ! scala.util.Success(this) + } + }, List(GUIDTask.RegisterEquipment(item)(zone.GUID)) + ) + } + /** * After a weapon has finished shooting, determine if it needs to be sorted in a special way. * @param tool a weapon From e2dcf998e0a9f648c43de563a54583bbcba1653c Mon Sep 17 00:00:00 2001 From: FateJH Date: Fri, 18 May 2018 10:29:46 -0400 Subject: [PATCH 04/10] in an attempt to decipher the pickup sound, it seems every item pickup comes with a 'congratulations' packet --- .../packet/game/ActionResultMessage.scala | 13 ++++- .../scala/game/ActionResultMessageTest.scala | 57 ++++++++++++++----- .../src/main/scala/WorldSessionActor.scala | 1 + 3 files changed, 54 insertions(+), 17 deletions(-) diff --git a/common/src/main/scala/net/psforever/packet/game/ActionResultMessage.scala b/common/src/main/scala/net/psforever/packet/game/ActionResultMessage.scala index e90d52100..fe7f0b44a 100644 --- a/common/src/main/scala/net/psforever/packet/game/ActionResultMessage.scala +++ b/common/src/main/scala/net/psforever/packet/game/ActionResultMessage.scala @@ -1,7 +1,7 @@ // Copyright (c) 2017 PSForever package net.psforever.packet.game -import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket} +import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket} import scodec.Codec import scodec.codecs._ @@ -10,7 +10,8 @@ import scodec.codecs._ * Is sent by the server when the client has performed an action from a menu item * (i.e create character, delete character, etc...) */ -final case class ActionResultMessage(successfull : Boolean, errorCode : Option[Long]) +final case class ActionResultMessage(successful : Boolean, + errorCode : Option[Long]) extends PlanetSideGamePacket { type Packet = ActionResultMessage def opcode = GamePacketOpcode.ActionResultMessage @@ -18,6 +19,14 @@ final case class ActionResultMessage(successfull : Boolean, errorCode : Option[L } object ActionResultMessage extends Marshallable[ActionResultMessage] { + def apply() : ActionResultMessage = { + ActionResultMessage(true, None) + } + + def apply(error : Long) : ActionResultMessage = { + ActionResultMessage(false, Some(error)) + } + implicit val codec : Codec[ActionResultMessage] = ( ("successful" | bool) >>:~ { res => // if not successful, look for an error code diff --git a/common/src/test/scala/game/ActionResultMessageTest.scala b/common/src/test/scala/game/ActionResultMessageTest.scala index c8ee8eb2c..a6bce43c7 100644 --- a/common/src/test/scala/game/ActionResultMessageTest.scala +++ b/common/src/test/scala/game/ActionResultMessageTest.scala @@ -7,27 +7,54 @@ import net.psforever.packet.game._ import scodec.bits._ class ActionResultMessageTest extends Specification { - "decode" in { - PacketCoding.DecodePacket(hex"1f 80").require match { - case ActionResultMessage(okay, code) => - okay === true - code === None - case _ => - ko - } + val string_pass = hex"1f 80" + val string_fail = hex"1f 0080000000" - PacketCoding.DecodePacket((hex"1f".bits ++ bin"0" ++ hex"01000000".bits).toByteVector).require match { + "decode (pass)" in { + PacketCoding.DecodePacket(string_pass).require match { case ActionResultMessage(okay, code) => - okay === false - code === Some(1) + okay mustEqual true + code mustEqual None case _ => ko } } - "encode" in { - PacketCoding.EncodePacket(ActionResultMessage(true, None)).require.toByteVector === hex"1f 80" - PacketCoding.EncodePacket(ActionResultMessage(false, Some(1))).require.toByteVector === - (hex"1f".bits ++ bin"0" ++ hex"01000000".bits).toByteVector + "decode (fail)" in { + PacketCoding.DecodePacket(string_fail).require match { + case ActionResultMessage(okay, code) => + okay mustEqual false + code mustEqual Some(1) + case _ => + ko + } + } + + "encode (pass, full)" in { + val msg = ActionResultMessage(true, None) + val pkt = PacketCoding.EncodePacket(msg).require.toByteVector + + pkt mustEqual string_pass + } + + "encode (pass, minimal)" in { + val msg = ActionResultMessage() + val pkt = PacketCoding.EncodePacket(msg).require.toByteVector + + pkt mustEqual string_pass + } + + "encode (fail, full)" in { + val msg = ActionResultMessage(false, Some(1)) + val pkt = PacketCoding.EncodePacket(msg).require.toByteVector + + pkt mustEqual string_fail + } + + "encode (fail, minimal)" in { + val msg = ActionResultMessage(1) + val pkt = PacketCoding.EncodePacket(msg).require.toByteVector + + pkt mustEqual string_fail } } diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index b598e867f..1a52cb10c 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -1403,6 +1403,7 @@ class WorldSessionActor extends Actor with MDCContextAware { definition.Packet.DetailedConstructorData(item).get ) ) + sendResponse(ActionResultMessage()) if(tplayer.VisibleSlots.contains(slot)) { avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.EquipmentInHand(player_guid, player_guid, slot, item)) } From e14f2817d789fda2b194c90e79325d1d2c4a118c Mon Sep 17 00:00:00 2001 From: FateJH Date: Fri, 18 May 2018 15:33:39 -0400 Subject: [PATCH 05/10] TR MAX mode switch and Anchor mode switch fix; MAX will anchor, display to others as anchored, and properly switch between fire modes in both anchored and normal stances --- .../psforever/objects/ExoSuitDefinition.scala | 61 ++++++++-- .../psforever/objects/GlobalDefinitions.scala | 61 +++++++++- .../scala/net/psforever/objects/Player.scala | 86 +++++++++++++- .../scala/net/psforever/objects/Tool.scala | 10 +- .../objects/definition/ToolDefinition.scala | 2 + .../src/main/scala/WorldSessionActor.scala | 106 ++++++++++++++---- pslogin/src/main/scala/Zones.scala | 2 +- 7 files changed, 286 insertions(+), 42 deletions(-) diff --git a/common/src/main/scala/net/psforever/objects/ExoSuitDefinition.scala b/common/src/main/scala/net/psforever/objects/ExoSuitDefinition.scala index 4eea81599..950560236 100644 --- a/common/src/main/scala/net/psforever/objects/ExoSuitDefinition.scala +++ b/common/src/main/scala/net/psforever/objects/ExoSuitDefinition.scala @@ -11,11 +11,11 @@ import net.psforever.types.ExoSuitType * @param suitType the `Enumeration` corresponding to this exo-suit */ class ExoSuitDefinition(private val suitType : ExoSuitType.Value) { - private var permission : Int = 0 //TODO certification type? - private var maxArmor : Int = 0 - private val holsters : Array[EquipmentSize.Value] = Array.fill[EquipmentSize.Value](5)(EquipmentSize.Blocked) - private var inventoryScale : InventoryTile = InventoryTile.Tile11 //override with custom InventoryTile - private var inventoryOffset : Int = 0 + protected var permission : Int = 0 //TODO certification type? + protected var maxArmor : Int = 0 + protected val holsters : Array[EquipmentSize.Value] = Array.fill[EquipmentSize.Value](5)(EquipmentSize.Blocked) + protected var inventoryScale : InventoryTile = InventoryTile.Tile11 //override with custom InventoryTile + protected var inventoryOffset : Int = 0 def SuitType : ExoSuitType.Value = suitType @@ -60,6 +60,45 @@ class ExoSuitDefinition(private val suitType : ExoSuitType.Value) { EquipmentSize.Blocked } } + + def Use : ExoSuitDefinition = this +} + +class SpecialExoSuitDefinition(private val suitType : ExoSuitType.Value) extends ExoSuitDefinition(suitType) { + private var activatedSpecial : SpecialExoSuitDefinition.Mode.Value = SpecialExoSuitDefinition.Mode.Normal + + def UsingSpecial : SpecialExoSuitDefinition.Mode.Value = activatedSpecial + + def UsingSpecial_=(state : SpecialExoSuitDefinition.Mode.Value) : SpecialExoSuitDefinition.Mode.Value = { + activatedSpecial = state + UsingSpecial + } + + override def Use : ExoSuitDefinition = { + val obj = new SpecialExoSuitDefinition(SuitType) + obj.MaxArmor = MaxArmor + obj.InventoryScale = InventoryScale + obj.InventoryOffset = InventoryOffset + (0 until 5).foreach(index => { obj.Holster(index, Holster(index)) }) + obj + } +} + +object SpecialExoSuitDefinition { + def apply(suitType : ExoSuitType.Value) : SpecialExoSuitDefinition = { + new SpecialExoSuitDefinition(suitType) + } + + object Mode extends Enumeration { + type Type = Value + + val + Normal, + Anchored, + Overdrive, + Shielded + = Value + } } object ExoSuitDefinition { @@ -99,7 +138,7 @@ object ExoSuitDefinition { Infiltration.Holster(0, EquipmentSize.Pistol) Infiltration.Holster(4, EquipmentSize.Melee) - final val MAX = ExoSuitDefinition(ExoSuitType.MAX) + final val MAX = new SpecialExoSuitDefinition(ExoSuitType.MAX) MAX.permission = 1 MAX.MaxArmor = 650 MAX.InventoryScale = InventoryTile.Tile1612 @@ -118,11 +157,11 @@ object ExoSuitDefinition { */ def Select(suit : ExoSuitType.Value) : ExoSuitDefinition = { suit match { - case ExoSuitType.Agile => ExoSuitDefinition.Agile - case ExoSuitType.Infiltration => ExoSuitDefinition.Infiltration - case ExoSuitType.MAX => ExoSuitDefinition.MAX - case ExoSuitType.Reinforced => ExoSuitDefinition.Reinforced - case _ => ExoSuitDefinition.Standard + case ExoSuitType.Agile => ExoSuitDefinition.Agile.Use + case ExoSuitType.Infiltration => ExoSuitDefinition.Infiltration.Use + case ExoSuitType.MAX => ExoSuitDefinition.MAX.Use + case ExoSuitType.Reinforced => ExoSuitDefinition.Reinforced.Use + case _ => ExoSuitDefinition.Standard.Use } } } diff --git a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala index 91f6c77e1..d27965ff1 100644 --- a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala +++ b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala @@ -278,11 +278,38 @@ object GlobalDefinitions { val flamethrower = ToolDefinition(ObjectClass.flamethrower) - val trhev_dualcycler = ToolDefinition(ObjectClass.trhev_dualcycler) + val trhev_dualcycler = new ToolDefinition(ObjectClass.trhev_dualcycler) { + override def NextFireModeIndex(index : Int) : Int = index + } - val trhev_pounder = ToolDefinition(ObjectClass.trhev_pounder) + val trhev_pounder = new ToolDefinition(ObjectClass.trhev_pounder) { + override def NextFireModeIndex(index : Int) : Int = { + //TODO other modes + if(index == 0 || index == 3) { + if(index == 0) { + 3 //3-second fuse + } + else { + 0 //explode on contact + } + } + else if(index == 1 || index == 4) { + if(index == 1) { + 4 //3-second fuse, anchored + } + else { + 1 //explode on contact, anchored + } + } + else { + index + } + } + } - val trhev_burster = ToolDefinition(ObjectClass.trhev_burster) + val trhev_burster = new ToolDefinition(ObjectClass.trhev_burster) { + override def NextFireModeIndex(index : Int) : Int = index + } val nchev_scattercannon = ToolDefinition(ObjectClass.nchev_scattercannon) @@ -1672,18 +1699,42 @@ object GlobalDefinitions { trhev_dualcycler.FireModes.head.AmmoTypeIndices += 0 trhev_dualcycler.FireModes.head.AmmoSlotIndex = 0 trhev_dualcycler.FireModes.head.Magazine = 200 + trhev_dualcycler.FireModes += new FireModeDefinition //anchored + trhev_dualcycler.FireModes(1).AmmoTypeIndices += 0 + trhev_dualcycler.FireModes(1).AmmoSlotIndex = 0 + trhev_dualcycler.FireModes(1).Magazine = 200 + trhev_dualcycler.FireModes += new FireModeDefinition //overdrive? + trhev_dualcycler.FireModes(2).AmmoTypeIndices += 0 + trhev_dualcycler.FireModes(2).AmmoSlotIndex = 0 + trhev_dualcycler.FireModes(2).Magazine = 200 trhev_pounder.Name = "trhev_pounder" trhev_pounder.Size = EquipmentSize.Max trhev_pounder.AmmoTypes += pounder_ammo trhev_pounder.FireModes += new FireModeDefinition - trhev_pounder.FireModes.head.AmmoTypeIndices += 0 + trhev_pounder.FireModes.head.AmmoTypeIndices += 0 //explode on contact trhev_pounder.FireModes.head.AmmoSlotIndex = 0 trhev_pounder.FireModes.head.Magazine = 30 - trhev_pounder.FireModes += new FireModeDefinition + trhev_pounder.FireModes += new FireModeDefinition //explode on contact, anchored trhev_pounder.FireModes(1).AmmoTypeIndices += 0 trhev_pounder.FireModes(1).AmmoSlotIndex = 0 trhev_pounder.FireModes(1).Magazine = 30 + trhev_pounder.FireModes += new FireModeDefinition //explode on contact, overdrive? + trhev_pounder.FireModes(2).AmmoTypeIndices += 0 + trhev_pounder.FireModes(2).AmmoSlotIndex = 0 + trhev_pounder.FireModes(2).Magazine = 30 + trhev_pounder.FireModes += new FireModeDefinition //3-second fuse + trhev_pounder.FireModes(3).AmmoTypeIndices += 0 + trhev_pounder.FireModes(3).AmmoSlotIndex = 0 + trhev_pounder.FireModes(3).Magazine = 30 + trhev_pounder.FireModes += new FireModeDefinition //3-second fuse, anchored + trhev_pounder.FireModes(4).AmmoTypeIndices += 0 + trhev_pounder.FireModes(4).AmmoSlotIndex = 0 + trhev_pounder.FireModes(4).Magazine = 30 + trhev_pounder.FireModes += new FireModeDefinition //3-second fuse, overdrive? + trhev_pounder.FireModes(5).AmmoTypeIndices += 0 + trhev_pounder.FireModes(5).AmmoSlotIndex = 0 + trhev_pounder.FireModes(5).Magazine = 30 trhev_burster.Name = "trhev_burster" trhev_burster.Size = EquipmentSize.Max diff --git a/common/src/main/scala/net/psforever/objects/Player.scala b/common/src/main/scala/net/psforever/objects/Player.scala index 72246fb4c..5735305c7 100644 --- a/common/src/main/scala/net/psforever/objects/Player.scala +++ b/common/src/main/scala/net/psforever/objects/Player.scala @@ -21,7 +21,7 @@ class Player(private val core : Avatar) extends PlanetSideGameObject with Factio private var maxHealth : Int = 100 //TODO affected by empire benefits, territory benefits, and bops private var maxStamina : Int = 100 //does anything affect this? - private var exosuit : ExoSuitType.Value = ExoSuitType.Standard + private var exosuit : ExoSuitDefinition = ExoSuitDefinition.Standard private val freeHand : EquipmentSlot = new OffhandEquipmentSlot(EquipmentSize.Inventory) private val holsters : Array[EquipmentSlot] = Array.fill[EquipmentSlot](5)(new EquipmentSlot) private val inventory : GridInventory = GridInventory() @@ -127,9 +127,9 @@ class Player(private val core : Avatar) extends PlanetSideGameObject with Factio Armor } - def MaxArmor : Int = ExoSuitDefinition.Select(exosuit).MaxArmor + def MaxArmor : Int = exosuit.MaxArmor - def VisibleSlots : Set[Int] = if(exosuit == ExoSuitType.MAX) { Set(0) } else { Set(0,1,2,3,4) } + def VisibleSlots : Set[Int] = if(exosuit.SuitType == ExoSuitType.MAX) { Set(0) } else { Set(0,1,2,3,4) } override def Slot(slot : Int) : EquipmentSlot = { if(inventory.Offset <= slot && slot <= inventory.LastIndex) { @@ -262,10 +262,11 @@ class Player(private val core : Avatar) extends PlanetSideGameObject with Factio def LastDrawnSlot : Int = lastDrawnSlot - def ExoSuit : ExoSuitType.Value = exosuit + def ExoSuit : ExoSuitType.Value = exosuit.SuitType def ExoSuit_=(suit : ExoSuitType.Value) : Unit = { - exosuit = suit + exosuit = ExoSuitDefinition.Select(suit) + ChangeSpecialAbility() } def LoadLoadout(line : Int) : Option[Loadout] = core.LoadLoadout(line) @@ -322,6 +323,81 @@ class Player(private val core : Avatar) extends PlanetSideGameObject with Factio Cloaked } + private var usingSpecial : (SpecialExoSuitDefinition.Mode.Value)=>SpecialExoSuitDefinition.Mode.Value = DefaultUsingSpecial + + private var gettingSpecial : ()=>SpecialExoSuitDefinition.Mode.Value = DefaultGettingSpecial + + private def ChangeSpecialAbility() : Unit = { + if(ExoSuit == ExoSuitType.MAX) { + gettingSpecial = MAXGettingSpecial + usingSpecial = Faction match { + case PlanetSideEmpire.TR => UsingAnchorsOrOverdrive + case PlanetSideEmpire.NC => UsingShield + case _ => DefaultUsingSpecial + } + } + else { + usingSpecial = DefaultUsingSpecial + gettingSpecial = DefaultGettingSpecial + } + } + + def UsingSpecial : SpecialExoSuitDefinition.Mode.Value = { gettingSpecial() } + + def UsingSpecial_=(state : SpecialExoSuitDefinition.Mode.Value) : SpecialExoSuitDefinition.Mode.Value = usingSpecial(state) + + private def DefaultUsingSpecial(state : SpecialExoSuitDefinition.Mode.Value) : SpecialExoSuitDefinition.Mode.Value = SpecialExoSuitDefinition.Mode.Normal + + private def UsingAnchorsOrOverdrive(state : SpecialExoSuitDefinition.Mode.Value) : SpecialExoSuitDefinition.Mode.Value = { + if(ExoSuit == ExoSuitType.MAX && Faction == PlanetSideEmpire.TR) { + val curr = UsingSpecial + val next = if(curr != SpecialExoSuitDefinition.Mode.Normal) { + SpecialExoSuitDefinition.Mode.Normal + } + else if(curr == SpecialExoSuitDefinition.Mode.Normal) { + state + } + else { + SpecialExoSuitDefinition.Mode.Normal + } + MAXUsingSpecial(next) + } + else { + SpecialExoSuitDefinition.Mode.Normal + } + } + + private def UsingShield(state : SpecialExoSuitDefinition.Mode.Value) : SpecialExoSuitDefinition.Mode.Value = { + if(ExoSuit == ExoSuitType.MAX && Faction == PlanetSideEmpire.NC) { + MAXUsingSpecial(state) + } + else { + SpecialExoSuitDefinition.Mode.Normal + } + } + + private def DefaultGettingSpecial() : SpecialExoSuitDefinition.Mode.Value = SpecialExoSuitDefinition.Mode.Normal + + private def MAXUsingSpecial(state : SpecialExoSuitDefinition.Mode.Value) : SpecialExoSuitDefinition.Mode.Value = exosuit match { + case obj : SpecialExoSuitDefinition => + obj.UsingSpecial = state + case _ => + SpecialExoSuitDefinition.Mode.Normal + } + + private def MAXGettingSpecial() : SpecialExoSuitDefinition.Mode.Value = exosuit match { + case obj : SpecialExoSuitDefinition => + obj.UsingSpecial + case _ => + SpecialExoSuitDefinition.Mode.Normal + } + + def isAnchored : Boolean = ExoSuit == ExoSuitType.MAX && Faction == PlanetSideEmpire.NC && UsingSpecial == SpecialExoSuitDefinition.Mode.Anchored + + def isOverdrived : Boolean = ExoSuit == ExoSuitType.MAX && Faction == PlanetSideEmpire.NC && UsingSpecial == SpecialExoSuitDefinition.Mode.Overdrive + + def isShielded : Boolean = ExoSuit == ExoSuitType.MAX && Faction == PlanetSideEmpire.NC && UsingSpecial == SpecialExoSuitDefinition.Mode.Shielded + def AccessingBackpack : Option[PlanetSideGUID] = backpackAccess def AccessingBackpack_=(guid : PlanetSideGUID) : Option[PlanetSideGUID] = { diff --git a/common/src/main/scala/net/psforever/objects/Tool.scala b/common/src/main/scala/net/psforever/objects/Tool.scala index 2b6d122aa..355c5a23b 100644 --- a/common/src/main/scala/net/psforever/objects/Tool.scala +++ b/common/src/main/scala/net/psforever/objects/Tool.scala @@ -32,7 +32,15 @@ class Tool(private val toolDef : ToolDefinition) extends Equipment with FireMode def FireMode : FireModeDefinition = toolDef.FireModes(fireModeIndex) def NextFireMode : FireModeDefinition = { - FireModeIndex = FireModeIndex + 1 + FireModeIndex = toolDef.NextFireModeIndex(FireModeIndex) + AmmoSlot.Chamber = FireMode.Chamber + FireMode + } + + def ToFireMode : Int = toolDef.NextFireModeIndex(FireModeIndex) + + def ToFireMode_=(index : Int) : FireModeDefinition = { + FireModeIndex = index AmmoSlot.Chamber = FireMode.Chamber FireMode } 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 54cda7166..c6a066761 100644 --- a/common/src/main/scala/net/psforever/objects/definition/ToolDefinition.scala +++ b/common/src/main/scala/net/psforever/objects/definition/ToolDefinition.scala @@ -15,6 +15,8 @@ class ToolDefinition(objectId : Int) extends EquipmentDefinition(objectId) { def AmmoTypes : mutable.ListBuffer[AmmoBoxDefinition] = ammoTypes def FireModes : mutable.ListBuffer[FireModeDefinition] = fireModes + + def NextFireModeIndex(index : Int) : Int = index + 1 } object ToolDefinition { diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index 1a52cb10c..fd8767b3e 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -700,13 +700,18 @@ class WorldSessionActor extends Actor with MDCContextAware { order match { case Terminal.BuyExosuit(exosuit, subtype) => //refresh armor points if(tplayer.ExoSuit == exosuit) { - if(Loadout.DetermineSubtype(tplayer) != subtype) { - //special case: MAX suit switching to a different MAX suit; we need to change the main weapon - sendResponse(ArmorChangedMessage(tplayer.GUID, exosuit, subtype)) - avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.ArmorChanged(tplayer.GUID, exosuit, subtype)) - val arms = tplayer.Slot(0).Equipment.get - val putTask = PutEquipmentInSlot(tplayer, Tool(GlobalDefinitions.MAXArms(subtype, tplayer.Faction)), 0) - taskResolver ! DelayedObjectHeld(tplayer, 0, List(TaskResolver.GiveTask(putTask.task, putTask.subs :+ RemoveEquipmentFromSlot(tplayer, arms, 0)))) + if(exosuit == ExoSuitType.MAX) { + //special MAX case - clear any special state + player.UsingSpecial = SpecialExoSuitDefinition.Mode.Normal + player.ExoSuit = exosuit + if(Loadout.DetermineSubtype(tplayer) != subtype) { + //special MAX case - suit switching to a different MAX suit; we need to change the main weapon + sendResponse(ArmorChangedMessage(tplayer.GUID, exosuit, subtype)) + avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.ArmorChanged(tplayer.GUID, exosuit, subtype)) + val arms = tplayer.Slot(0).Equipment.get + val putTask = PutEquipmentInSlot(tplayer, Tool(GlobalDefinitions.MAXArms(subtype, tplayer.Faction)), 0) + taskResolver ! DelayedObjectHeld(tplayer, 0, List(TaskResolver.GiveTask(putTask.task, putTask.subs :+ RemoveEquipmentFromSlot(tplayer, arms, 0)))) + } } //outside of the MAX condition above, we should seldom reach this point through conventional methods tplayer.Armor = tplayer.MaxArmor @@ -714,7 +719,8 @@ class WorldSessionActor extends Actor with MDCContextAware { avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.PlanetsideAttribute(tplayer.GUID, 4, tplayer.Armor)) sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, true)) } - else { //load a complete new exo-suit and shuffle the inventory around + else { + //load a complete new exo-suit and shuffle the inventory around val originalSuit = tplayer.ExoSuit //save inventory before it gets cleared (empty holsters) val dropPred = DropPredicate(tplayer) @@ -749,6 +755,8 @@ class WorldSessionActor extends Actor with MDCContextAware { normalWeapons } else { + tplayer.DrawnSlot = Player.HandsDownSlot + sendResponse(ObjectHeldMessage(tplayer.GUID, Player.HandsDownSlot, true)) avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.ObjectHeld(tplayer.GUID, Player.HandsDownSlot)) beforeHolsters } @@ -839,6 +847,11 @@ class WorldSessionActor extends Actor with MDCContextAware { //TODO optimizations against replacing Equipment with the exact same Equipment and potentially for recycling existing Equipment log.info(s"$tplayer wants to change equipment loadout to their option #${msg.unk1 + 1}") sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Loadout, true)) + //ensure arm is down + tplayer.DrawnSlot = Player.HandsDownSlot + sendResponse(ObjectHeldMessage(tplayer.GUID, Player.HandsDownSlot, true)) + avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.ObjectHeld(tplayer.GUID, Player.HandsDownSlot)) + //load val dropPred = DropPredicate(tplayer) val (dropHolsters, beforeHolsters) = clearHolsters(tplayer.Holsters().iterator).partition(dropPred) val (dropInventory, beforeInventory) = tplayer.Inventory.Clear().partition(dropPred) @@ -1473,7 +1486,7 @@ class WorldSessionActor extends Actor with MDCContextAware { //TODO begin temp player character auto-loading; remove later import net.psforever.objects.GlobalDefinitions._ import net.psforever.types.CertificationType._ - avatar = Avatar("TestCharacter"+sessionId.toString, PlanetSideEmpire.VS, CharacterGender.Female, 41, 1) + avatar = Avatar("TestCharacter"+sessionId.toString, PlanetSideEmpire.TR, CharacterGender.Female, 41, 1) avatar.Certifications += StandardAssault avatar.Certifications += MediumAssault avatar.Certifications += StandardExoSuit @@ -1579,6 +1592,9 @@ class WorldSessionActor extends Actor with MDCContextAware { //load active players in zone continent.LivePlayers.filterNot(_.GUID == player.GUID).foreach(char => { sendResponse(ObjectCreateMessage(ObjectClass.avatar, char.GUID, char.Definition.Packet.ConstructorData(char).get)) + if(char.UsingSpecial == SpecialExoSuitDefinition.Mode.Anchored) { + sendResponse(PlanetsideAttributeMessage(char.GUID, 19, 1)) + } }) //load corpses in zone continent.Corpses.foreach { TurnPlayerIntoCorpse } @@ -2132,16 +2148,22 @@ class WorldSessionActor extends Actor with MDCContextAware { } case msg @ ObjectHeldMessage(avatar_guid, held_holsters, unk1) => - log.info("ObjectHeld: " + msg) + log.info(s"ObjectHeld: $msg") 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, player.LastDrawnSlot)) - if(player.VisibleSlots.contains(held_holsters)) { - usingMedicalTerminal match { - case Some(term_guid) => - StopUsingProximityUnit(continent.GUID(term_guid).get.asInstanceOf[ProximityTerminal]) - case None => ; + if(before != held_holsters) { + if(player.ExoSuit == ExoSuitType.MAX && held_holsters != 0) { + log.info(s"ObjectHeld: $player is denied changing hands to $held_holsters as a MAX") + player.DrawnSlot = 0 + sendResponse(ObjectHeldMessage(avatar_guid, 0, true)) + } + else if((player.DrawnSlot = held_holsters) != before) { + avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.ObjectHeld(player.GUID, player.LastDrawnSlot)) + if(player.VisibleSlots.contains(held_holsters)) { + usingMedicalTerminal match { + case Some(term_guid) => + StopUsingProximityUnit(continent.GUID(term_guid).get.asInstanceOf[ProximityTerminal]) + case None => ; + } } } } @@ -2513,6 +2535,51 @@ class WorldSessionActor extends Actor with MDCContextAware { case msg @ GenericObjectStateMsg(object_guid, unk1) => log.info("GenericObjectState: " + msg) + case msg @ GenericActionMessage(action) => + log.info(s"GenericAction: $msg") + val (toolOpt, definition) = player.Slot(0).Equipment match { + case Some(tool : Tool) => + (Some(tool), tool.Definition) + case _ => + (None, GlobalDefinitions.bullet_9mm) + } + if(action == 15) { //max deployment + log.info(s"GenericObject: $player is anchored") + player.UsingSpecial = SpecialExoSuitDefinition.Mode.Anchored + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(player.GUID, 19, 1)) + definition match { + case GlobalDefinitions.trhev_dualcycler | GlobalDefinitions.trhev_burster => + val tool = toolOpt.get + tool.ToFireMode = 1 + sendResponse(ChangeFireModeMessage(tool.GUID, 1)) + case GlobalDefinitions.trhev_pounder => + val tool = toolOpt.get + val convertFireModeIndex = if(tool.FireModeIndex == 0) { 1 } else { 4 } + tool.ToFireMode = convertFireModeIndex + sendResponse(ChangeFireModeMessage(tool.GUID, convertFireModeIndex)) + case _ => + log.info(s"GenericObject: $player is MAS with an unexpected weapon - ${definition.Name}") + } + } + else if(action == 16) { + log.info(s"GenericObject: $player has released the anchors") + player.UsingSpecial = SpecialExoSuitDefinition.Mode.Normal + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(player.GUID, 19, 0)) + definition match { + case GlobalDefinitions.trhev_dualcycler | GlobalDefinitions.trhev_burster => + val tool = toolOpt.get + tool.ToFireMode = 0 + sendResponse(ChangeFireModeMessage(tool.GUID, 0)) + case GlobalDefinitions.trhev_pounder => + val tool = toolOpt.get + val convertFireModeIndex = if(tool.FireModeIndex == 1) { 0 } else { 3 } + tool.ToFireMode = convertFireModeIndex + sendResponse(ChangeFireModeMessage(tool.GUID, convertFireModeIndex)) + case _ => + log.info(s"GenericObject: $player is MAS with an unexpected weapon - ${definition.Name}") + } + } + case msg @ ItemTransactionMessage(terminal_guid, _, _, _, _, _) => log.info("ItemTransaction: " + msg) continent.GUID(terminal_guid) match { @@ -4108,7 +4175,8 @@ class WorldSessionActor extends Actor with MDCContextAware { * Other players in the same zone must be made aware that the player has stopped as well.
*
* Things whose configuration should not be changed:
- * - if the player is seated + * - if the player is seated
+ * - if anchored */ def PlayerActionsToCancel() : Unit = { progressBarUpdate.cancel diff --git a/pslogin/src/main/scala/Zones.scala b/pslogin/src/main/scala/Zones.scala index 0e0bef787..070dc5fe9 100644 --- a/pslogin/src/main/scala/Zones.scala +++ b/pslogin/src/main/scala/Zones.scala @@ -21,7 +21,7 @@ object Zones { super.Init(context) import net.psforever.types.PlanetSideEmpire - Building(2).get.Faction = PlanetSideEmpire.VS + Building(2).get.Faction = PlanetSideEmpire.TR Building(2).get.ModelId = 20 Building(38).get.ModelId = 0 Building(42).get.ModelId = 0 From cf3bf19d4d1fcc985250f237d599e735afb4cf0d Mon Sep 17 00:00:00 2001 From: FateJH Date: Fri, 18 May 2018 20:48:52 -0400 Subject: [PATCH 06/10] tests for prior MAX changes; correcting a 13mm problem with the Lightning's chaingun ammunition type --- .../psforever/objects/ExoSuitDefinition.scala | 4 +- .../psforever/objects/GlobalDefinitions.scala | 2 +- .../scala/net/psforever/objects/Player.scala | 36 +- .../terminals/VehicleTerminalDefinition.scala | 6 +- .../packet/game/objectcreate/Prefab.scala | 2 +- .../src/test/scala/objects/ExoSuitTest.scala | 145 +++ .../src/test/scala/objects/PlayerTest.scala | 904 ++++++++++-------- .../src/main/scala/WorldSessionActor.scala | 6 +- pslogin/src/main/scala/Zones.scala | 2 +- 9 files changed, 664 insertions(+), 443 deletions(-) create mode 100644 common/src/test/scala/objects/ExoSuitTest.scala diff --git a/common/src/main/scala/net/psforever/objects/ExoSuitDefinition.scala b/common/src/main/scala/net/psforever/objects/ExoSuitDefinition.scala index 950560236..fc49f07ab 100644 --- a/common/src/main/scala/net/psforever/objects/ExoSuitDefinition.scala +++ b/common/src/main/scala/net/psforever/objects/ExoSuitDefinition.scala @@ -36,7 +36,7 @@ class ExoSuitDefinition(private val suitType : ExoSuitType.Value) { def InventoryOffset : Int = inventoryOffset def InventoryOffset_=(offset : Int) : Int = { - inventoryOffset = offset + inventoryOffset = math.min(math.max(0, offset), 65535) InventoryOffset } @@ -138,7 +138,7 @@ object ExoSuitDefinition { Infiltration.Holster(0, EquipmentSize.Pistol) Infiltration.Holster(4, EquipmentSize.Melee) - final val MAX = new SpecialExoSuitDefinition(ExoSuitType.MAX) + final val MAX = SpecialExoSuitDefinition(ExoSuitType.MAX) MAX.permission = 1 MAX.MaxArmor = 650 MAX.InventoryScale = InventoryTile.Tile1612 diff --git a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala index d27965ff1..d3b9c05f9 100644 --- a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala +++ b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala @@ -2131,7 +2131,7 @@ object GlobalDefinitions { lightning_weapon_system.Name = "lightning_weapon_system" lightning_weapon_system.Size = EquipmentSize.VehicleWeapon lightning_weapon_system.AmmoTypes += bullet_75mm - lightning_weapon_system.AmmoTypes += bullet_25mm + lightning_weapon_system.AmmoTypes += bullet_12mm lightning_weapon_system.FireModes += new FireModeDefinition lightning_weapon_system.FireModes.head.AmmoTypeIndices += 0 lightning_weapon_system.FireModes.head.AmmoSlotIndex = 0 diff --git a/common/src/main/scala/net/psforever/objects/Player.scala b/common/src/main/scala/net/psforever/objects/Player.scala index 5735305c7..5c570d1f3 100644 --- a/common/src/main/scala/net/psforever/objects/Player.scala +++ b/common/src/main/scala/net/psforever/objects/Player.scala @@ -349,31 +349,43 @@ class Player(private val core : Avatar) extends PlanetSideGameObject with Factio private def DefaultUsingSpecial(state : SpecialExoSuitDefinition.Mode.Value) : SpecialExoSuitDefinition.Mode.Value = SpecialExoSuitDefinition.Mode.Normal private def UsingAnchorsOrOverdrive(state : SpecialExoSuitDefinition.Mode.Value) : SpecialExoSuitDefinition.Mode.Value = { - if(ExoSuit == ExoSuitType.MAX && Faction == PlanetSideEmpire.TR) { - val curr = UsingSpecial - val next = if(curr != SpecialExoSuitDefinition.Mode.Normal) { - SpecialExoSuitDefinition.Mode.Normal - } - else if(curr == SpecialExoSuitDefinition.Mode.Normal) { + import SpecialExoSuitDefinition.Mode._ + val curr = UsingSpecial + val next = if(curr == Normal) { + if(state == Anchored || state == Overdrive) { state } else { - SpecialExoSuitDefinition.Mode.Normal + Normal } - MAXUsingSpecial(next) + } + else if(state == Normal) { + Normal } else { - SpecialExoSuitDefinition.Mode.Normal + curr } + MAXUsingSpecial(next) } private def UsingShield(state : SpecialExoSuitDefinition.Mode.Value) : SpecialExoSuitDefinition.Mode.Value = { - if(ExoSuit == ExoSuitType.MAX && Faction == PlanetSideEmpire.NC) { - MAXUsingSpecial(state) + import SpecialExoSuitDefinition.Mode._ + val curr = UsingSpecial + val next = if(curr == Normal) { + if(state == Shielded) { + state + } + else { + Normal + } + } + else if(state == Normal) { + Normal } else { - SpecialExoSuitDefinition.Mode.Normal + curr } + MAXUsingSpecial(next) } private def DefaultGettingSpecial() : SpecialExoSuitDefinition.Mode.Value = SpecialExoSuitDefinition.Mode.Normal diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/VehicleTerminalDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/VehicleTerminalDefinition.scala index 8785ae7a0..553d5cc10 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/VehicleTerminalDefinition.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/VehicleTerminalDefinition.scala @@ -326,9 +326,9 @@ abstract class VehicleTerminalDefinition(objId : Int) extends TerminalDefinition ), "lightning" -> VehicleLoadout("default_lightning", List(), List( - SimplifiedEntry(ammo_25mm, 30), - SimplifiedEntry(ammo_25mm, 34), - SimplifiedEntry(ammo_25mm, 38), + SimplifiedEntry(ammo_12mm, 30), + SimplifiedEntry(ammo_12mm, 34), + SimplifiedEntry(ammo_12mm, 38), SimplifiedEntry(ammo_75mm, 90), SimplifiedEntry(ammo_75mm, 94), SimplifiedEntry(ammo_75mm, 98) 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 a424d02bb..acfaa4079 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 @@ -223,7 +223,7 @@ object Prefab { VehicleData(CommonFieldData(loc, faction, 0), 0, health, false, false, DriveState.State7, true, false, false, None, Some(InventoryData( InventoryItemData(ObjectClass.lightning_weapon_system, weapon_guid, 1, - WeaponData(0x4, 0x8, 0, ObjectClass.bullet_75mm, ammo1_guid, 0, AmmoBoxData(0x0), ObjectClass.bullet_25mm, ammo2_guid, 1, AmmoBoxData(0x0)) + WeaponData(0x4, 0x8, 0, ObjectClass.bullet_75mm, ammo1_guid, 0, AmmoBoxData(0x0), ObjectClass.bullet_12mm, ammo2_guid, 1, AmmoBoxData(0x0)) ) :: Nil) ) )(VehicleFormat.Normal) diff --git a/common/src/test/scala/objects/ExoSuitTest.scala b/common/src/test/scala/objects/ExoSuitTest.scala new file mode 100644 index 000000000..484c748c1 --- /dev/null +++ b/common/src/test/scala/objects/ExoSuitTest.scala @@ -0,0 +1,145 @@ +// Copyright (c) 2017 PSForever +package objects + +import net.psforever.objects._ +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 net.psforever.types.ExoSuitType +import org.specs2.mutable._ + +class ExoSuitTest extends Specification { + "ExoSuitDefinition" should { + "construct" in { + val obj = ExoSuitDefinition(ExoSuitType.Standard) + obj.MaxArmor mustEqual 0 + obj.InventoryScale mustEqual InventoryTile.Tile11 + obj.InventoryOffset mustEqual 0 + obj.SuitType mustEqual ExoSuitType.Standard + obj.Holsters.length mustEqual 5 + obj.Holsters.foreach(slot => { if(slot != EquipmentSize.Blocked) { ko } }) + ok + } + + "produce the type of exo-suit that was provided as a clarified type" in { + ExoSuitDefinition(ExoSuitType.Standard).SuitType mustEqual ExoSuitType.Standard + ExoSuitDefinition(ExoSuitType.Agile).SuitType mustEqual ExoSuitType.Agile + } + + "change the maximum armor value" in { + val obj = ExoSuitDefinition(ExoSuitType.Standard) + obj.MaxArmor mustEqual 0 + obj.MaxArmor = 1 + obj.MaxArmor mustEqual 1 + } + + "not change the maximum armor to an invalid value" in { + val obj = ExoSuitDefinition(ExoSuitType.Standard) + obj.MaxArmor mustEqual 0 + obj.MaxArmor = -1 + obj.MaxArmor mustEqual 0 + obj.MaxArmor = 65536 + obj.MaxArmor mustEqual 65535 + } + + "change the size of the inventory" in { + val obj = ExoSuitDefinition(ExoSuitType.Standard) + obj.InventoryScale mustEqual InventoryTile.Tile11 + obj.InventoryScale = InventoryTile.Tile42 + obj.InventoryScale mustEqual InventoryTile.Tile42 + } + + "change the start index of the inventory" in { + val obj = ExoSuitDefinition(ExoSuitType.Standard) + obj.InventoryOffset mustEqual 0 + obj.InventoryOffset = 1 + obj.InventoryOffset mustEqual 1 + } + + "not change the start index of the inventory to an invalid value" in { + val obj = ExoSuitDefinition(ExoSuitType.Standard) + obj.InventoryOffset mustEqual 0 + obj.InventoryOffset = -1 + obj.InventoryOffset mustEqual 0 + obj.InventoryOffset = 65536 + obj.InventoryOffset mustEqual 65535 + } + + "change specific holsters to specific values" in { + val obj = ExoSuitDefinition(ExoSuitType.Standard) + obj.Holster(0) mustEqual EquipmentSize.Blocked + obj.Holster(0, EquipmentSize.Pistol) + obj.Holster(0) mustEqual EquipmentSize.Pistol + obj.Holster(4) mustEqual EquipmentSize.Blocked + obj.Holster(4, EquipmentSize.Rifle) + obj.Holster(4) mustEqual EquipmentSize.Rifle + (0 to 4).foreach { + case 0 => obj.Holsters(0) mustEqual EquipmentSize.Pistol + case 4 => obj.Holsters(4) mustEqual EquipmentSize.Rifle + case x => obj.Holsters(x) mustEqual EquipmentSize.Blocked + } + ok + } + + "can not change any slot that does not exist" in { + val obj = ExoSuitDefinition(ExoSuitType.Standard) + obj.Holster(9) mustEqual EquipmentSize.Blocked + obj.Holster(9, EquipmentSize.Pistol) + obj.Holster(9) mustEqual EquipmentSize.Blocked + } + + "produce a copy of the definition" in { + val obj = ExoSuitDefinition(ExoSuitType.Standard) + val obj2 = obj.Use + obj eq obj2 + } + } + + "SpecialExoSuitDefinition" should { + "construct" in { + val obj = SpecialExoSuitDefinition(ExoSuitType.Standard) + obj.MaxArmor mustEqual 0 + obj.InventoryScale mustEqual InventoryTile.Tile11 + obj.InventoryOffset mustEqual 0 + obj.SuitType mustEqual ExoSuitType.Standard + obj.Holsters.length mustEqual 5 + obj.Holsters.foreach(slot => { if(slot != EquipmentSize.Blocked) { ko } }) + obj.UsingSpecial mustEqual SpecialExoSuitDefinition.Mode.Normal + } + + "configure UsingSpecial to various values" in { + val obj = SpecialExoSuitDefinition(ExoSuitType.Standard) + obj.UsingSpecial = SpecialExoSuitDefinition.Mode.Anchored + obj.UsingSpecial mustEqual SpecialExoSuitDefinition.Mode.Anchored + obj.UsingSpecial = SpecialExoSuitDefinition.Mode.Overdrive + obj.UsingSpecial mustEqual SpecialExoSuitDefinition.Mode.Overdrive + obj.UsingSpecial = SpecialExoSuitDefinition.Mode.Shielded + obj.UsingSpecial mustEqual SpecialExoSuitDefinition.Mode.Shielded + obj.UsingSpecial = SpecialExoSuitDefinition.Mode.Normal + obj.UsingSpecial mustEqual SpecialExoSuitDefinition.Mode.Normal + } + + "produce a separate copy of the definition" in { + val obj = SpecialExoSuitDefinition(ExoSuitType.Standard) + val obj2 = obj.Use + obj ne obj2 + } + } + + "ExoSuitDefinition.Select" should { + "produce common, shared instances of exo suits" in { + ExoSuitDefinition.Select(ExoSuitType.Standard) eq ExoSuitDefinition.Select(ExoSuitType.Standard) + ExoSuitDefinition.Select(ExoSuitType.Agile) eq ExoSuitDefinition.Select(ExoSuitType.Agile) + ExoSuitDefinition.Select(ExoSuitType.Reinforced) eq ExoSuitDefinition.Select(ExoSuitType.Reinforced) + ExoSuitDefinition.Select(ExoSuitType.Infiltration) eq ExoSuitDefinition.Select(ExoSuitType.Infiltration) + } + + "produces unique instances of the mechanized assault exo suit" in { + val obj = ExoSuitDefinition.Select(ExoSuitType.MAX) + obj ne ExoSuitDefinition.Select(ExoSuitType.MAX) + obj.isInstanceOf[SpecialExoSuitDefinition] mustEqual true + } + } +} diff --git a/common/src/test/scala/objects/PlayerTest.scala b/common/src/test/scala/objects/PlayerTest.scala index ff9501be5..560b1c524 100644 --- a/common/src/test/scala/objects/PlayerTest.scala +++ b/common/src/test/scala/objects/PlayerTest.scala @@ -16,448 +16,512 @@ class PlayerTest extends Specification { new Player(Avatar(name, faction, sex, head, voice)) } - "construct" in { - val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) - obj.isAlive mustEqual false - obj.FacingYawUpper mustEqual 0 - obj.Jumping mustEqual false - obj.Crouching mustEqual false - obj.Cloaked mustEqual false + "Player" should { + "construct" in { + val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + obj.isAlive mustEqual false + obj.FacingYawUpper mustEqual 0 + obj.Jumping mustEqual false + obj.Crouching mustEqual false + obj.Cloaked mustEqual false - obj.FacingYawUpper = 1.3f - obj.Jumping = true - obj.Crouching = true - obj.Cloaked = true - obj.FacingYawUpper mustEqual 1.3f - obj.Jumping mustEqual true - obj.Crouching mustEqual true - obj.Cloaked mustEqual true - } - - "different players" in { - (TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) == - TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)) mustEqual true - - (TestPlayer("Chord1", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) == - TestPlayer("Chord2", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)) mustEqual false - - (TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) == - TestPlayer("Chord", PlanetSideEmpire.NC, CharacterGender.Male, 0, 5)) mustEqual false - - (TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) == - TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Female, 0, 5)) mustEqual false - - (TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) == - TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 1, 5)) mustEqual false - - (TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) == - TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 6)) mustEqual false - } - - "(re)spawn" in { - val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) - obj.isAlive mustEqual false - obj.Health mustEqual 0 - obj.Stamina mustEqual 0 - obj.Armor mustEqual 0 - obj.MaxHealth mustEqual 100 - obj.MaxStamina mustEqual 100 - obj.MaxArmor mustEqual 50 - obj.Spawn - obj.isAlive mustEqual true - obj.Health mustEqual 100 - obj.Stamina mustEqual 100 - obj.Armor mustEqual 50 - } - - "will not (re)spawn if not dead" in { - val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) - obj.Spawn - obj.Health mustEqual 100 - obj.Armor mustEqual 50 - obj.isAlive mustEqual true - - obj.Health = 10 - obj.Armor = 10 - obj.Health mustEqual 10 - obj.Armor mustEqual 10 - obj.Spawn - obj.Health mustEqual 10 - obj.Armor mustEqual 10 - } - - "can die" in { - val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) - obj.Spawn - obj.Armor = 35 //50 -> 35 - obj.isAlive mustEqual true - obj.Health mustEqual obj.MaxHealth - obj.Stamina mustEqual obj.MaxStamina - obj.Armor mustEqual 35 - obj.Die - obj.isAlive mustEqual false - obj.Health mustEqual 0 - obj.Stamina mustEqual 0 - obj.Armor mustEqual 35 - } - - "can not become a backpack if alive" in { - val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) - obj.Spawn - obj.isAlive mustEqual true - obj.isBackpack mustEqual false - obj.Release - obj.isAlive mustEqual true - obj.isBackpack mustEqual false - } - - "can become a backpack" in { - val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) - obj.isAlive mustEqual false - obj.isBackpack mustEqual false - obj.Release - obj.isAlive mustEqual false - obj.isBackpack mustEqual true - } - - "set new maximum values (health, stamina)" in { - val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) - obj.MaxHealth mustEqual 100 - obj.MaxStamina mustEqual 100 - obj.MaxHealth = 123 - obj.MaxStamina = 456 - obj.Spawn - obj.Health mustEqual 123 - obj.Stamina mustEqual 456 - } - - "set new values (health, armor, stamina) but only when alive" in { - val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) - obj.Health = 23 - obj.Armor = 34 - obj.Stamina = 45 - obj.Health mustEqual 0 - obj.Armor mustEqual 0 - obj.Stamina mustEqual 0 - - obj.Spawn - obj.Health mustEqual obj.MaxHealth - obj.Armor mustEqual obj.MaxArmor - obj.Stamina mustEqual obj.MaxStamina - obj.Health = 23 - obj.Armor = 34 - obj.Stamina = 45 - obj.Health mustEqual 23 - obj.Armor mustEqual 34 - obj.Stamina mustEqual 45 - } - - "has visible slots" in { - val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) - obj.VisibleSlots mustEqual Set(0,1,2,3,4) - obj.ExoSuit = ExoSuitType.MAX - obj.VisibleSlots mustEqual Set(0) - } - - "init (Standard Exo-Suit)" in { - val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) - obj.ExoSuit mustEqual ExoSuitType.Standard - obj.Slot(0).Size mustEqual EquipmentSize.Pistol - obj.Slot(1).Size mustEqual EquipmentSize.Blocked - obj.Slot(2).Size mustEqual EquipmentSize.Rifle - obj.Slot(3).Size mustEqual EquipmentSize.Blocked - obj.Slot(4).Size mustEqual EquipmentSize.Melee - obj.Inventory.Width mustEqual 9 - obj.Inventory.Height mustEqual 6 - obj.Inventory.Offset mustEqual 6 - } - - "draw equipped holsters only" in { - val wep = SimpleItem(SimpleItemDefinition(149)) - val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) - obj.Slot(1).Size = EquipmentSize.Pistol - obj.Slot(1).Equipment = wep - obj.DrawnSlot mustEqual Player.HandsDownSlot - obj.DrawnSlot = 0 - obj.DrawnSlot mustEqual Player.HandsDownSlot - obj.DrawnSlot = 1 - obj.DrawnSlot mustEqual 1 - } - - "remember the last drawn holster" in { - val wep1 = SimpleItem(SimpleItemDefinition(149)) - val wep2 = SimpleItem(SimpleItemDefinition(149)) - val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) - obj.Slot(0).Size = EquipmentSize.Pistol - obj.Slot(0).Equipment = wep1 - obj.Slot(1).Size = EquipmentSize.Pistol - obj.Slot(1).Equipment = wep2 - obj.DrawnSlot mustEqual Player.HandsDownSlot //default value - obj.LastDrawnSlot mustEqual Player.HandsDownSlot //default value - - obj.DrawnSlot = 1 - obj.DrawnSlot mustEqual 1 - obj.LastDrawnSlot mustEqual 1 - - obj.DrawnSlot = 0 - obj.DrawnSlot mustEqual 0 - obj.LastDrawnSlot mustEqual 0 - - obj.DrawnSlot = Player.HandsDownSlot - obj.DrawnSlot mustEqual Player.HandsDownSlot - obj.LastDrawnSlot mustEqual 0 - - obj.DrawnSlot = 1 - obj.DrawnSlot mustEqual 1 - obj.LastDrawnSlot mustEqual 1 - - obj.DrawnSlot = 0 - obj.DrawnSlot mustEqual 0 - obj.LastDrawnSlot mustEqual 0 - - obj.DrawnSlot = 1 - obj.DrawnSlot mustEqual 1 - obj.LastDrawnSlot mustEqual 1 - - obj.DrawnSlot = Player.HandsDownSlot - obj.DrawnSlot mustEqual Player.HandsDownSlot - obj.LastDrawnSlot mustEqual 1 - } - - "hold something in their free hand" in { - val wep = SimpleItem(SimpleItemDefinition(149)) - val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) - obj.Slot(Player.FreeHandSlot).Equipment = wep - - obj.Slot(Player.FreeHandSlot).Equipment.get.Definition.ObjectId mustEqual 149 - } - - "provide an invalid hand that can not hold anything" in { - val wep = SimpleItem(SimpleItemDefinition(149)) - val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) - obj.Slot(-1).Equipment = wep - - obj.Slot(-1).Equipment mustEqual None - } - - "search for the smallest available slot in which to store equipment" in { - val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) - obj.Inventory.Resize(3,3) //fits one item - - obj.Fit(Tool(GlobalDefinitions.beamer)) mustEqual Some(0) - - obj.Fit(Tool(GlobalDefinitions.suppressor)) mustEqual Some(2) - - val ammo = AmmoBox(GlobalDefinitions.bullet_9mm) - val ammo2 = AmmoBox(GlobalDefinitions.bullet_9mm) - val ammo3 = AmmoBox(GlobalDefinitions.bullet_9mm) - obj.Fit(ammo) mustEqual Some(6) - obj.Slot(6).Equipment = ammo - obj.Fit(ammo2) mustEqual Some(Player.FreeHandSlot) - obj.Slot(Player.FreeHandSlot).Equipment = ammo2 - obj.Fit(ammo3) mustEqual None - } - - "can use their free hand to hold things" in { - val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) - val ammo = AmmoBox(GlobalDefinitions.bullet_9mm) - obj.FreeHand.Equipment mustEqual None - - obj.FreeHand.Equipment = ammo - obj.FreeHand.Equipment mustEqual Some(ammo) - } - - "can access the player's locker-space" in { - val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) - obj.Slot(5).Equipment.get.isInstanceOf[LockerContainer] mustEqual true - } - - "can find equipment" in { - val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) - obj.Slot(0).Equipment = { - val item = Tool(beamer) - item.GUID = PlanetSideGUID(1) - item - } - obj.Slot(4).Equipment = { - val item = Tool(forceblade) - item.GUID = PlanetSideGUID(2) - item - } - obj.Slot(6).Equipment = { - val item = ConstructionItem(ace) - item.GUID = PlanetSideGUID(3) - item - } - obj.Locker.Slot(6).Equipment = { - val item = Kit(medkit) - item.GUID = PlanetSideGUID(4) - item - } - obj.FreeHand.Equipment = { - val item = SimpleItem(remote_electronics_kit) - item.GUID = PlanetSideGUID(5) - item + obj.FacingYawUpper = 1.3f + obj.Jumping = true + obj.Crouching = true + obj.Cloaked = true + obj.FacingYawUpper mustEqual 1.3f + obj.Jumping mustEqual true + obj.Crouching mustEqual true + obj.Cloaked mustEqual true } - obj.Find(PlanetSideGUID(1)) mustEqual Some(0) //holsters - obj.Find(PlanetSideGUID(2)) mustEqual Some(4) //holsters, melee - obj.Find(PlanetSideGUID(3)) mustEqual Some(6) //inventory - obj.Find(PlanetSideGUID(4)) mustEqual None //can not find in locker-space - obj.Find(PlanetSideGUID(5)) mustEqual Some(Player.FreeHandSlot) //free hand - obj.Find(PlanetSideGUID(6)) mustEqual None //not here - } + "different players" in { + (TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) == + TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)) mustEqual true - "does equipment collision checking (are we already holding something there?)" in { - val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) - val item1 = Tool(beamer) - val item2 = Kit(medkit) - val item3 = AmmoBox(GlobalDefinitions.bullet_9mm) - obj.Slot(0).Equipment = item1 - obj.Slot(6).Equipment = item2 - obj.FreeHand.Equipment = item3 + (TestPlayer("Chord1", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) == + TestPlayer("Chord2", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)) mustEqual false - obj.Collisions(0, 1, 1) match { - case Success(List(item)) => - item.obj mustEqual item1 - item.start mustEqual 0 - case _ => - ko - } //holsters + (TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) == + TestPlayer("Chord", PlanetSideEmpire.NC, CharacterGender.Male, 0, 5)) mustEqual false - obj.Collisions(1, 1, 1) match { - case Success(List()) => ; - case _ => - ko - } //holsters, nothing + (TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) == + TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Female, 0, 5)) mustEqual false - obj.Collisions(6, 1, 1)match { - case Success(List(item)) => - item.obj mustEqual item2 - item.start mustEqual 6 - case _ => - ko - } //inventory + (TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) == + TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 1, 5)) mustEqual false - obj.Collisions(Player.FreeHandSlot, 1, 1)match { - case Success(List(item)) => - item.obj mustEqual item3 - item.start mustEqual Player.FreeHandSlot - case _ => - ko - } //free hand - } + (TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) == + TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 6)) mustEqual false + } - "battle experience point values of the avatar" in { - val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) - val player = Player(avatar) + "(re)spawn" in { + val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + obj.isAlive mustEqual false + obj.Health mustEqual 0 + obj.Stamina mustEqual 0 + obj.Armor mustEqual 0 + obj.MaxHealth mustEqual 100 + obj.MaxStamina mustEqual 100 + obj.MaxArmor mustEqual 50 + obj.Spawn + obj.isAlive mustEqual true + obj.Health mustEqual 100 + obj.Stamina mustEqual 100 + obj.Armor mustEqual 50 + } - player.BEP mustEqual avatar.BEP - avatar.BEP = 1002 - player.BEP mustEqual avatar.BEP - } + "will not (re)spawn if not dead" in { + val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + obj.Spawn + obj.Health mustEqual 100 + obj.Armor mustEqual 50 + obj.isAlive mustEqual true - "command experience point values of the avatar" in { - val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) - val player = Player(avatar) + obj.Health = 10 + obj.Armor = 10 + obj.Health mustEqual 10 + obj.Armor mustEqual 10 + obj.Spawn + obj.Health mustEqual 10 + obj.Armor mustEqual 10 + } - player.CEP mustEqual avatar.CEP - avatar.CEP = 1002 - player.CEP mustEqual avatar.CEP - } + "can die" in { + val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + obj.Spawn + obj.Armor = 35 //50 -> 35 + obj.isAlive mustEqual true + obj.Health mustEqual obj.MaxHealth + obj.Stamina mustEqual obj.MaxStamina + obj.Armor mustEqual 35 + obj.Die + obj.isAlive mustEqual false + obj.Health mustEqual 0 + obj.Stamina mustEqual 0 + obj.Armor mustEqual 35 + } - "can get a quick summary of implant slots (default)" in { - val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) - val player = Player(avatar) + "can not become a backpack if alive" in { + val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + obj.Spawn + obj.isAlive mustEqual true + obj.isBackpack mustEqual false + obj.Release + obj.isAlive mustEqual true + obj.isBackpack mustEqual false + } - player.Implants mustEqual Array.empty - } + "can become a backpack" in { + val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + obj.isAlive mustEqual false + obj.isBackpack mustEqual false + obj.Release + obj.isAlive mustEqual false + obj.isBackpack mustEqual true + } - "can get a quick summary of implant slots (two unlocked, one installed)" in { - val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) - val player = Player(avatar) - val temp = new ImplantDefinition(1) - avatar.Implants(0).Unlocked = true - avatar.InstallImplant(new ImplantDefinition(1)) - avatar.Implants(1).Unlocked = true - avatar.InstallImplant(new ImplantDefinition(2)) - avatar.UninstallImplant(temp.Type) + "set new maximum values (health, stamina)" in { + val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + obj.MaxHealth mustEqual 100 + obj.MaxStamina mustEqual 100 + obj.MaxHealth = 123 + obj.MaxStamina = 456 + obj.Spawn + obj.Health mustEqual 123 + obj.Stamina mustEqual 456 + } - val list = player.Implants - //slot 0 - val (implant1, init1, active1) = list(0) - implant1 mustEqual ImplantType.None - init1 mustEqual -1 - active1 mustEqual false - //slot 1 - val (implant2, init2, active2) = list(1) - implant2 mustEqual ImplantType(2) - init2 mustEqual 0 - active2 mustEqual false - } + "set new values (health, armor, stamina) but only when alive" in { + val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + obj.Health = 23 + obj.Armor = 34 + obj.Stamina = 45 + obj.Health mustEqual 0 + obj.Armor mustEqual 0 + obj.Stamina mustEqual 0 - "can get a quick summary of implant slots (all unlocked, first two installed)" in { - val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) - val player = Player(avatar) - avatar.Implants(0).Unlocked = true - avatar.InstallImplant(new ImplantDefinition(1)) - avatar.Implants(0).Initialized = true - avatar.Implants(0).Active = true - avatar.Implants(1).Unlocked = true - avatar.InstallImplant(new ImplantDefinition(2)) - avatar.Implants(1).Initialized = true - avatar.Implants(1).Active = false - avatar.Implants(2).Unlocked = true + obj.Spawn + obj.Health mustEqual obj.MaxHealth + obj.Armor mustEqual obj.MaxArmor + obj.Stamina mustEqual obj.MaxStamina + obj.Health = 23 + obj.Armor = 34 + obj.Stamina = 45 + obj.Health mustEqual 23 + obj.Armor mustEqual 34 + obj.Stamina mustEqual 45 + } - val list = player.Implants - //slot 0 - val (implant1, init1, active1) = list(0) - implant1 mustEqual ImplantType(1) - init1 mustEqual 0 - active1 mustEqual true - //slot 1 - val (implant2, init2, active2) = list(1) - implant2 mustEqual ImplantType(2) - init2 mustEqual 0 - active2 mustEqual false - //slot 2 - val (implant3, init3, active3) = list(2) - implant3 mustEqual ImplantType.None - init3 mustEqual -1 - active3 mustEqual false - } + "has visible slots" in { + val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + obj.VisibleSlots mustEqual Set(0,1,2,3,4) + obj.ExoSuit = ExoSuitType.MAX + obj.VisibleSlots mustEqual Set(0) + } - "seat in a vehicle" in { - val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) - obj.VehicleSeated mustEqual None - obj.VehicleSeated = PlanetSideGUID(65) - obj.VehicleSeated mustEqual Some(PlanetSideGUID(65)) - obj.VehicleSeated = None - obj.VehicleSeated mustEqual None - } + "init (Standard Exo-Suit)" in { + val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + obj.ExoSuit mustEqual ExoSuitType.Standard + obj.Slot(0).Size mustEqual EquipmentSize.Pistol + obj.Slot(1).Size mustEqual EquipmentSize.Blocked + obj.Slot(2).Size mustEqual EquipmentSize.Rifle + obj.Slot(3).Size mustEqual EquipmentSize.Blocked + obj.Slot(4).Size mustEqual EquipmentSize.Melee + obj.Inventory.Width mustEqual 9 + obj.Inventory.Height mustEqual 6 + obj.Inventory.Offset mustEqual 6 + } - "own in a vehicle" in { - val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) - obj.VehicleOwned mustEqual None - obj.VehicleOwned = PlanetSideGUID(65) - obj.VehicleOwned mustEqual Some(PlanetSideGUID(65)) - obj.VehicleOwned = None - obj.VehicleOwned mustEqual None - } + "draw equipped holsters only" in { + val wep = SimpleItem(SimpleItemDefinition(149)) + val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + obj.Slot(1).Size = EquipmentSize.Pistol + obj.Slot(1).Equipment = wep + obj.DrawnSlot mustEqual Player.HandsDownSlot + obj.DrawnSlot = 0 + obj.DrawnSlot mustEqual Player.HandsDownSlot + obj.DrawnSlot = 1 + obj.DrawnSlot mustEqual 1 + } - "remember what zone he is in" in { - val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) - obj.Continent mustEqual "home2" - obj.Continent = "ugd01" - obj.Continent mustEqual "ugd01" - } + "remember the last drawn holster" in { + val wep1 = SimpleItem(SimpleItemDefinition(149)) + val wep2 = SimpleItem(SimpleItemDefinition(149)) + val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + obj.Slot(0).Size = EquipmentSize.Pistol + obj.Slot(0).Equipment = wep1 + obj.Slot(1).Size = EquipmentSize.Pistol + obj.Slot(1).Equipment = wep2 + obj.DrawnSlot mustEqual Player.HandsDownSlot //default value + obj.LastDrawnSlot mustEqual Player.HandsDownSlot //default value - "toString" in { - val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) - obj.toString mustEqual "TR Chord 0/100 0/50" + obj.DrawnSlot = 1 + obj.DrawnSlot mustEqual 1 + obj.LastDrawnSlot mustEqual 1 - obj.GUID = PlanetSideGUID(455) - obj.Continent = "z3" - obj.toString mustEqual "TR Chord z3-455 0/100 0/50" + obj.DrawnSlot = 0 + obj.DrawnSlot mustEqual 0 + obj.LastDrawnSlot mustEqual 0 + + obj.DrawnSlot = Player.HandsDownSlot + obj.DrawnSlot mustEqual Player.HandsDownSlot + obj.LastDrawnSlot mustEqual 0 + + obj.DrawnSlot = 1 + obj.DrawnSlot mustEqual 1 + obj.LastDrawnSlot mustEqual 1 + + obj.DrawnSlot = 0 + obj.DrawnSlot mustEqual 0 + obj.LastDrawnSlot mustEqual 0 + + obj.DrawnSlot = 1 + obj.DrawnSlot mustEqual 1 + obj.LastDrawnSlot mustEqual 1 + + obj.DrawnSlot = Player.HandsDownSlot + obj.DrawnSlot mustEqual Player.HandsDownSlot + obj.LastDrawnSlot mustEqual 1 + } + + "hold something in their free hand" in { + val wep = SimpleItem(SimpleItemDefinition(149)) + val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + obj.Slot(Player.FreeHandSlot).Equipment = wep + + obj.Slot(Player.FreeHandSlot).Equipment.get.Definition.ObjectId mustEqual 149 + } + + "provide an invalid hand that can not hold anything" in { + val wep = SimpleItem(SimpleItemDefinition(149)) + val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + obj.Slot(-1).Equipment = wep + + obj.Slot(-1).Equipment mustEqual None + } + + "search for the smallest available slot in which to store equipment" in { + val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + obj.Inventory.Resize(3,3) //fits one item + + obj.Fit(Tool(GlobalDefinitions.beamer)) mustEqual Some(0) + + obj.Fit(Tool(GlobalDefinitions.suppressor)) mustEqual Some(2) + + val ammo = AmmoBox(GlobalDefinitions.bullet_9mm) + val ammo2 = AmmoBox(GlobalDefinitions.bullet_9mm) + val ammo3 = AmmoBox(GlobalDefinitions.bullet_9mm) + obj.Fit(ammo) mustEqual Some(6) + obj.Slot(6).Equipment = ammo + obj.Fit(ammo2) mustEqual Some(Player.FreeHandSlot) + obj.Slot(Player.FreeHandSlot).Equipment = ammo2 + obj.Fit(ammo3) mustEqual None + } + + "can use their free hand to hold things" in { + val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + val ammo = AmmoBox(GlobalDefinitions.bullet_9mm) + obj.FreeHand.Equipment mustEqual None + + obj.FreeHand.Equipment = ammo + obj.FreeHand.Equipment mustEqual Some(ammo) + } + + "can access the player's locker-space" in { + val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + obj.Slot(5).Equipment.get.isInstanceOf[LockerContainer] mustEqual true + } + + "can find equipment" in { + val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + obj.Slot(0).Equipment = { + val item = Tool(beamer) + item.GUID = PlanetSideGUID(1) + item + } + obj.Slot(4).Equipment = { + val item = Tool(forceblade) + item.GUID = PlanetSideGUID(2) + item + } + obj.Slot(6).Equipment = { + val item = ConstructionItem(ace) + item.GUID = PlanetSideGUID(3) + item + } + obj.Locker.Slot(6).Equipment = { + val item = Kit(medkit) + item.GUID = PlanetSideGUID(4) + item + } + obj.FreeHand.Equipment = { + val item = SimpleItem(remote_electronics_kit) + item.GUID = PlanetSideGUID(5) + item + } + + obj.Find(PlanetSideGUID(1)) mustEqual Some(0) //holsters + obj.Find(PlanetSideGUID(2)) mustEqual Some(4) //holsters, melee + obj.Find(PlanetSideGUID(3)) mustEqual Some(6) //inventory + obj.Find(PlanetSideGUID(4)) mustEqual None //can not find in locker-space + obj.Find(PlanetSideGUID(5)) mustEqual Some(Player.FreeHandSlot) //free hand + obj.Find(PlanetSideGUID(6)) mustEqual None //not here + } + + "does equipment collision checking (are we already holding something there?)" in { + val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + val item1 = Tool(beamer) + val item2 = Kit(medkit) + val item3 = AmmoBox(GlobalDefinitions.bullet_9mm) + obj.Slot(0).Equipment = item1 + obj.Slot(6).Equipment = item2 + obj.FreeHand.Equipment = item3 + + obj.Collisions(0, 1, 1) match { + case Success(List(item)) => + item.obj mustEqual item1 + item.start mustEqual 0 + case _ => + ko + } //holsters + + obj.Collisions(1, 1, 1) match { + case Success(List()) => ; + case _ => + ko + } //holsters, nothing + + obj.Collisions(6, 1, 1)match { + case Success(List(item)) => + item.obj mustEqual item2 + item.start mustEqual 6 + case _ => + ko + } //inventory + + obj.Collisions(Player.FreeHandSlot, 1, 1)match { + case Success(List(item)) => + item.obj mustEqual item3 + item.start mustEqual Player.FreeHandSlot + case _ => + ko + } //free hand + } + + "battle experience point values of the avatar" in { + val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + val player = Player(avatar) + + player.BEP mustEqual avatar.BEP + avatar.BEP = 1002 + player.BEP mustEqual avatar.BEP + } + + "command experience point values of the avatar" in { + val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + val player = Player(avatar) + + player.CEP mustEqual avatar.CEP + avatar.CEP = 1002 + player.CEP mustEqual avatar.CEP + } + + "can get a quick summary of implant slots (default)" in { + val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + val player = Player(avatar) + + player.Implants mustEqual Array.empty + } + + "can get a quick summary of implant slots (two unlocked, one installed)" in { + val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + val player = Player(avatar) + val temp = new ImplantDefinition(1) + avatar.Implants(0).Unlocked = true + avatar.InstallImplant(new ImplantDefinition(1)) + avatar.Implants(1).Unlocked = true + avatar.InstallImplant(new ImplantDefinition(2)) + avatar.UninstallImplant(temp.Type) + + val list = player.Implants + //slot 0 + val (implant1, init1, active1) = list(0) + implant1 mustEqual ImplantType.None + init1 mustEqual -1 + active1 mustEqual false + //slot 1 + val (implant2, init2, active2) = list(1) + implant2 mustEqual ImplantType(2) + init2 mustEqual 0 + active2 mustEqual false + } + + "can get a quick summary of implant slots (all unlocked, first two installed)" in { + val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + val player = Player(avatar) + avatar.Implants(0).Unlocked = true + avatar.InstallImplant(new ImplantDefinition(1)) + avatar.Implants(0).Initialized = true + avatar.Implants(0).Active = true + avatar.Implants(1).Unlocked = true + avatar.InstallImplant(new ImplantDefinition(2)) + avatar.Implants(1).Initialized = true + avatar.Implants(1).Active = false + avatar.Implants(2).Unlocked = true + + val list = player.Implants + //slot 0 + val (implant1, init1, active1) = list(0) + implant1 mustEqual ImplantType(1) + init1 mustEqual 0 + active1 mustEqual true + //slot 1 + val (implant2, init2, active2) = list(1) + implant2 mustEqual ImplantType(2) + init2 mustEqual 0 + active2 mustEqual false + //slot 2 + val (implant3, init3, active3) = list(2) + implant3 mustEqual ImplantType.None + init3 mustEqual -1 + active3 mustEqual false + } + + "seat in a vehicle" in { + val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + obj.VehicleSeated mustEqual None + obj.VehicleSeated = PlanetSideGUID(65) + obj.VehicleSeated mustEqual Some(PlanetSideGUID(65)) + obj.VehicleSeated = None + obj.VehicleSeated mustEqual None + } + + "own in a vehicle" in { + val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + obj.VehicleOwned mustEqual None + obj.VehicleOwned = PlanetSideGUID(65) + obj.VehicleOwned mustEqual Some(PlanetSideGUID(65)) + obj.VehicleOwned = None + obj.VehicleOwned mustEqual None + } + + "remember what zone he is in" in { + val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + obj.Continent mustEqual "home2" + obj.Continent = "ugd01" + obj.Continent mustEqual "ugd01" + } + + "special is typically normal and can not be changed from normal" in { + val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + obj.UsingSpecial mustEqual SpecialExoSuitDefinition.Mode.Normal + obj.UsingSpecial = SpecialExoSuitDefinition.Mode.Shielded + obj.UsingSpecial mustEqual SpecialExoSuitDefinition.Mode.Normal + } + + "a TR MAX can change its special to Overdrive or Anchored" in { + val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + obj.ExoSuit = ExoSuitType.MAX + obj.UsingSpecial mustEqual SpecialExoSuitDefinition.Mode.Normal + obj.UsingSpecial = SpecialExoSuitDefinition.Mode.Anchored + obj.UsingSpecial mustEqual SpecialExoSuitDefinition.Mode.Anchored + obj.UsingSpecial = SpecialExoSuitDefinition.Mode.Normal + obj.UsingSpecial mustEqual SpecialExoSuitDefinition.Mode.Normal + obj.UsingSpecial = SpecialExoSuitDefinition.Mode.Overdrive + obj.UsingSpecial mustEqual SpecialExoSuitDefinition.Mode.Overdrive + //note + obj.UsingSpecial = SpecialExoSuitDefinition.Mode.Anchored + obj.UsingSpecial mustEqual SpecialExoSuitDefinition.Mode.Overdrive + } + + "an NC MAX can change its special to Shielded" in { + val obj = TestPlayer("Chord", PlanetSideEmpire.NC, CharacterGender.Male, 0, 5) + obj.ExoSuit = ExoSuitType.MAX + obj.UsingSpecial mustEqual SpecialExoSuitDefinition.Mode.Normal + obj.UsingSpecial = SpecialExoSuitDefinition.Mode.Shielded + obj.UsingSpecial mustEqual SpecialExoSuitDefinition.Mode.Shielded + obj.UsingSpecial = SpecialExoSuitDefinition.Mode.Normal + obj.UsingSpecial mustEqual SpecialExoSuitDefinition.Mode.Normal + } + + "one faction can not use the other's specials" in { + val objtr = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + objtr.ExoSuit = ExoSuitType.MAX + objtr.UsingSpecial mustEqual SpecialExoSuitDefinition.Mode.Normal + objtr.UsingSpecial = SpecialExoSuitDefinition.Mode.Shielded + objtr.UsingSpecial mustEqual SpecialExoSuitDefinition.Mode.Normal + + val objnc = TestPlayer("Chord", PlanetSideEmpire.NC, CharacterGender.Male, 0, 5) + objnc.ExoSuit = ExoSuitType.MAX + objnc.UsingSpecial mustEqual SpecialExoSuitDefinition.Mode.Normal + objnc.UsingSpecial = SpecialExoSuitDefinition.Mode.Overdrive + objnc.UsingSpecial mustEqual SpecialExoSuitDefinition.Mode.Normal + objnc.UsingSpecial = SpecialExoSuitDefinition.Mode.Anchored + objnc.UsingSpecial mustEqual SpecialExoSuitDefinition.Mode.Normal + } + + "changing exo-suit type resets the special to Normal (and changing back does not revert it again)" in { + val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + obj.ExoSuit = ExoSuitType.MAX + obj.UsingSpecial mustEqual SpecialExoSuitDefinition.Mode.Normal + obj.UsingSpecial = SpecialExoSuitDefinition.Mode.Anchored + obj.UsingSpecial mustEqual SpecialExoSuitDefinition.Mode.Anchored + + val test = obj.UsingSpecial + obj.ExoSuit = ExoSuitType.Standard + obj.UsingSpecial mustEqual SpecialExoSuitDefinition.Mode.Normal + obj.ExoSuit = ExoSuitType.MAX + obj.UsingSpecial != test mustEqual true + } + + "toString" in { + val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + obj.toString mustEqual "TR Chord 0/100 0/50" + + obj.GUID = PlanetSideGUID(455) + obj.Continent = "z3" + obj.toString mustEqual "TR Chord z3-455 0/100 0/50" + } } } diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index fd8767b3e..f400bc9a0 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -1486,7 +1486,7 @@ class WorldSessionActor extends Actor with MDCContextAware { //TODO begin temp player character auto-loading; remove later import net.psforever.objects.GlobalDefinitions._ import net.psforever.types.CertificationType._ - avatar = Avatar("TestCharacter"+sessionId.toString, PlanetSideEmpire.TR, CharacterGender.Female, 41, 1) + avatar = Avatar("TestCharacter"+sessionId.toString, PlanetSideEmpire.VS, CharacterGender.Female, 41, 1) avatar.Certifications += StandardAssault avatar.Certifications += MediumAssault avatar.Certifications += StandardExoSuit @@ -2558,7 +2558,7 @@ class WorldSessionActor extends Actor with MDCContextAware { tool.ToFireMode = convertFireModeIndex sendResponse(ChangeFireModeMessage(tool.GUID, convertFireModeIndex)) case _ => - log.info(s"GenericObject: $player is MAS with an unexpected weapon - ${definition.Name}") + log.info(s"GenericObject: $player is MAX with an unexpected weapon - ${definition.Name}") } } else if(action == 16) { @@ -2576,7 +2576,7 @@ class WorldSessionActor extends Actor with MDCContextAware { tool.ToFireMode = convertFireModeIndex sendResponse(ChangeFireModeMessage(tool.GUID, convertFireModeIndex)) case _ => - log.info(s"GenericObject: $player is MAS with an unexpected weapon - ${definition.Name}") + log.info(s"GenericObject: $player is MAX with an unexpected weapon - ${definition.Name}") } } diff --git a/pslogin/src/main/scala/Zones.scala b/pslogin/src/main/scala/Zones.scala index 070dc5fe9..0e0bef787 100644 --- a/pslogin/src/main/scala/Zones.scala +++ b/pslogin/src/main/scala/Zones.scala @@ -21,7 +21,7 @@ object Zones { super.Init(context) import net.psforever.types.PlanetSideEmpire - Building(2).get.Faction = PlanetSideEmpire.TR + Building(2).get.Faction = PlanetSideEmpire.VS Building(2).get.ModelId = 20 Building(38).get.ModelId = 0 Building(42).get.ModelId = 0 From a3f50cc8a4398f412dc1196bc426290b9a66f47f Mon Sep 17 00:00:00 2001 From: FateJH Date: Sat, 19 May 2018 15:13:36 -0400 Subject: [PATCH 07/10] fixing Infiltration Suit issues as well as streamlining the process of exo-suit switching --- .../psforever/objects/ExoSuitDefinition.scala | 2 +- .../scala/net/psforever/objects/Player.scala | 17 ++++++++--------- common/src/test/scala/objects/LoadoutTest.scala | 2 +- pslogin/src/main/scala/WorldSessionActor.scala | 4 ++-- 4 files changed, 12 insertions(+), 13 deletions(-) diff --git a/common/src/main/scala/net/psforever/objects/ExoSuitDefinition.scala b/common/src/main/scala/net/psforever/objects/ExoSuitDefinition.scala index fc49f07ab..fa36256df 100644 --- a/common/src/main/scala/net/psforever/objects/ExoSuitDefinition.scala +++ b/common/src/main/scala/net/psforever/objects/ExoSuitDefinition.scala @@ -130,7 +130,7 @@ object ExoSuitDefinition { Reinforced.Holster(3, EquipmentSize.Rifle) Reinforced.Holster(4, EquipmentSize.Melee) - final val Infiltration = ExoSuitDefinition(ExoSuitType.Standard) + final val Infiltration = ExoSuitDefinition(ExoSuitType.Infiltration) Infiltration.permission = 1 Infiltration.MaxArmor = 0 Infiltration.InventoryScale = InventoryTile.Tile66 diff --git a/common/src/main/scala/net/psforever/objects/Player.scala b/common/src/main/scala/net/psforever/objects/Player.scala index 5c570d1f3..19cf484be 100644 --- a/common/src/main/scala/net/psforever/objects/Player.scala +++ b/common/src/main/scala/net/psforever/objects/Player.scala @@ -48,7 +48,7 @@ class Player(private val core : Avatar) extends PlanetSideGameObject with Factio /** From PlanetsideAttributeMessage */ var PlanetsideAttribute : Array[Long] = Array.ofDim(120) - Player.SuitSetup(this, ExoSuit) + Player.SuitSetup(this, exosuit) def Name : String = core.name @@ -265,7 +265,9 @@ class Player(private val core : Avatar) extends PlanetSideGameObject with Factio def ExoSuit : ExoSuitType.Value = exosuit.SuitType def ExoSuit_=(suit : ExoSuitType.Value) : Unit = { - exosuit = ExoSuitDefinition.Select(suit) + val eSuit = ExoSuitDefinition.Select(suit) + exosuit = eSuit + Player.SuitSetup(this, eSuit) ChangeSpecialAbility() } @@ -496,16 +498,13 @@ object Player { new Player(core) } - def SuitSetup(player : Player, eSuit : ExoSuitType.Value) : Unit = { - val esuitDef : ExoSuitDefinition = ExoSuitDefinition.Select(eSuit) - //exosuit - player.ExoSuit = eSuit + private def SuitSetup(player : Player, eSuit : ExoSuitDefinition) : Unit = { //inventory player.Inventory.Clear() - player.Inventory.Resize(esuitDef.InventoryScale.Width, esuitDef.InventoryScale.Height) - player.Inventory.Offset = esuitDef.InventoryOffset + player.Inventory.Resize(eSuit.InventoryScale.Width, eSuit.InventoryScale.Height) + player.Inventory.Offset = eSuit.InventoryOffset //holsters - (0 until 5).foreach(index => { player.Slot(index).Size = esuitDef.Holster(index) }) + (0 until 5).foreach(index => { player.Slot(index).Size = eSuit.Holster(index) }) } def Respawn(player : Player) : Player = { diff --git a/common/src/test/scala/objects/LoadoutTest.scala b/common/src/test/scala/objects/LoadoutTest.scala index 67d0f3100..8a6910b20 100644 --- a/common/src/test/scala/objects/LoadoutTest.scala +++ b/common/src/test/scala/objects/LoadoutTest.scala @@ -94,7 +94,7 @@ class LoadoutTest extends Specification { val player = CreatePlayer() val slot = player.Slot(0) slot.Equipment = None //only an unequipped slot can have its Equipment Size changed (Rifle -> Max) - Player.SuitSetup(player, ExoSuitType.MAX) + player.ExoSuit = ExoSuitType.MAX val ldout1 = Loadout.Create(player, "weaponless").asInstanceOf[InfantryLoadout] slot.Equipment = None diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index f400bc9a0..05075da16 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -727,7 +727,7 @@ class WorldSessionActor extends Actor with MDCContextAware { val (dropHolsters, beforeHolsters) = clearHolsters(tplayer.Holsters().iterator).partition(dropPred) val (dropInventory, beforeInventory) = tplayer.Inventory.Clear().partition(dropPred) //change suit (clear inventory and change holster sizes; note: holsters must be empty before this point) - Player.SuitSetup(tplayer, exosuit) + tplayer.ExoSuit = exosuit tplayer.Armor = tplayer.MaxArmor //delete everything not dropped (beforeHolsters ++ beforeInventory).foreach({ elem => @@ -859,7 +859,7 @@ class WorldSessionActor extends Actor with MDCContextAware { val (_, afterInventory) = inventory.partition(dropPred) //dropped items are lost val beforeFreeHand = tplayer.FreeHand.Equipment //change suit (clear inventory and change holster sizes; note: holsters must be empty before this point) - Player.SuitSetup(tplayer, exosuit) + tplayer.ExoSuit = exosuit tplayer.Armor = tplayer.MaxArmor //delete everything (not dropped) beforeHolsters.foreach({ elem => From 54e2e37b5424b1ee5d6aa144027e3d729c76de58 Mon Sep 17 00:00:00 2001 From: FateJH Date: Sat, 19 May 2018 17:40:09 -0400 Subject: [PATCH 08/10] corpse remvoal should now remove corpses --- .../avatar/support/CorpseRemovalActor.scala | 54 ++++++++++--------- 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/pslogin/src/main/scala/services/avatar/support/CorpseRemovalActor.scala b/pslogin/src/main/scala/services/avatar/support/CorpseRemovalActor.scala index 446907e92..1486fb5be 100644 --- a/pslogin/src/main/scala/services/avatar/support/CorpseRemovalActor.scala +++ b/pslogin/src/main/scala/services/avatar/support/CorpseRemovalActor.scala @@ -51,7 +51,8 @@ class CorpseRemovalActor extends Actor { def Processing : Receive = { case CorpseRemovalActor.AddCorpse(corpse, zone, time) => - if(corpse.isBackpack && !buriedCorpses.exists(_.corpse == corpse)) { + import CorpseRemovalActor.SimilarCorpses + if(corpse.isBackpack && !buriedCorpses.exists(entry => SimilarCorpses(entry.corpse, corpse) )) { if(corpses.isEmpty) { //we were the only entry so the event must be started from scratch corpses = List(CorpseRemovalActor.Entry(corpse, zone, time)) @@ -60,7 +61,7 @@ class CorpseRemovalActor extends Actor { else { //unknown number of entries; append, sort, then re-time tasking val oldHead = corpses.head - if(!corpses.exists(_.corpse == corpse)) { + if(!corpses.exists(entry => SimilarCorpses(entry.corpse, corpse))) { corpses = (corpses :+ CorpseRemovalActor.Entry(corpse, zone, time)).sortBy(_.timeAlive) if(oldHead != corpses.head) { RetimeFirstTask() @@ -80,45 +81,44 @@ class CorpseRemovalActor extends Actor { if(targets.size == 1) { log.debug(s"a target corpse submitted for early cleanup: ${targets.head}") //simple selection - CorpseRemovalActor.recursiveFindCorpse(corpses.iterator, targets.head) match { + CorpseRemovalActor.recursiveFindCorpse(corpses.iterator, targets.head) match { case None => ; case Some(index) => decomposition.cancel BurialTask(corpses(index)) buriedCorpses = buriedCorpses :+ corpses(index) - corpses = corpses.take(index) ++ corpses.drop(index+1) + corpses = (corpses.take(index) ++ corpses.drop(index + 1)).sortBy(_.timeAlive) import scala.concurrent.ExecutionContext.Implicits.global decomposition = context.system.scheduler.scheduleOnce(500 milliseconds, self, CorpseRemovalActor.TryDelete()) } } else { log.debug(s"multiple target corpses submitted for early cleanup: $targets") + import CorpseRemovalActor.SimilarCorpses decomposition.cancel //cumbersome partition //a - find targets from corpses val locatedTargets = for { a <- targets b <- corpses - if b.corpse == a && - b.corpse.Continent.equals(a.Continent) && - b.corpse.HasGUID && a.HasGUID && b.corpse.GUID == a.GUID + if b.corpse.HasGUID && a.HasGUID && SimilarCorpses(b.corpse, a) } yield b - locatedTargets.foreach { BurialTask } - buriedCorpses = locatedTargets ++ buriedCorpses - //b - corpses, after the found targets are removed (cull any non-GUID entries while at it) - corpses = (for { - a <- locatedTargets.map { _.corpse } - b <- corpses - if b.corpse.HasGUID && a.HasGUID && - (b.corpse != a || - !b.corpse.Continent.equals(a.Continent) || - !b.corpse.HasGUID || !a.HasGUID || b.corpse.GUID != a.GUID) - } yield b).sortBy(_.timeAlive) - import scala.concurrent.ExecutionContext.Implicits.global - decomposition = context.system.scheduler.scheduleOnce(500 milliseconds, self, CorpseRemovalActor.TryDelete()) - } - RetimeFirstTask() + if(locatedTargets.nonEmpty) { + decomposition.cancel + locatedTargets.foreach { BurialTask } + buriedCorpses = locatedTargets ++ buriedCorpses + import scala.concurrent.ExecutionContext.Implicits.global + decomposition = context.system.scheduler.scheduleOnce(500 milliseconds, self, CorpseRemovalActor.TryDelete()) + //b - corpses, after the found targets are removed (cull any non-GUID entries while at it) + corpses = (for { + a <- locatedTargets + b <- corpses + if b.corpse.HasGUID && a.corpse.HasGUID && !SimilarCorpses(b.corpse, a.corpse) + } yield b).sortBy(_.timeAlive) + } } + RetimeFirstTask() + } case CorpseRemovalActor.StartDelete() => burial.cancel @@ -136,7 +136,9 @@ class CorpseRemovalActor extends Actor { case CorpseRemovalActor.TryDelete() => decomposition.cancel - val (decomposed, rotting) = buriedCorpses.partition(entry => { !entry.zone.Corpses.contains(entry.corpse) }) + val (decomposed, rotting) = buriedCorpses.partition(entry => { + !entry.zone.Corpses.contains(entry.corpse) + }) buriedCorpses = rotting decomposed.foreach { LastRitesTask } if(rotting.nonEmpty) { @@ -214,6 +216,10 @@ object CorpseRemovalActor { private final case class TryDelete() + private def SimilarCorpses(obj1 : Player, obj2 : Player) : Boolean = { + obj1 == obj2 && obj1.Continent.equals(obj2.Continent) && obj1.GUID == obj2.GUID + } + /** * A recursive function that finds and removes a specific player from a list of players. * @param iter an `Iterator` of `CorpseRemovalActor.Entry` objects @@ -228,7 +234,7 @@ object CorpseRemovalActor { } else { val corpse = iter.next.corpse - if(corpse == player && corpse.Continent.equals(player.Continent) && corpse.GUID == player.GUID) { + if(corpse.HasGUID && player.HasGUID && SimilarCorpses(corpse, player)) { Some(index) } else { From 48dde3a4b395114f66de4dc8cade74ffbbe87c13 Mon Sep 17 00:00:00 2001 From: FateJH Date: Mon, 21 May 2018 08:25:11 -0400 Subject: [PATCH 09/10] increased amount of time these tests are allowed to run to mitigate chance of incorrect failures --- .../test/scala/PacketCodingActorTest.scala | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/pslogin/src/test/scala/PacketCodingActorTest.scala b/pslogin/src/test/scala/PacketCodingActorTest.scala index 5cd58b0b7..a2096f014 100644 --- a/pslogin/src/test/scala/PacketCodingActorTest.scala +++ b/pslogin/src/test/scala/PacketCodingActorTest.scala @@ -63,7 +63,7 @@ class PacketCodingActor4Test extends ActorTest { val msg = ActorTest.MDCGamePacket(PacketCoding.CreateGamePacket(0, string_obj)) probe2 ! msg - val reply1 = receiveOne(100 milli) //we get a MdcMsg message back + val reply1 = receiveOne(300 milli) //we get a MdcMsg message back probe2 ! reply1 //by feeding the MdcMsg into the actor, we get normal output on the probe probe1.expectMsg(string_hex) } @@ -83,7 +83,7 @@ class PacketCodingActor5Test extends ActorTest { probe1.receiveOne(100 milli) //consume pca ! string_hex - val reply = probe1.receiveOne(100 milli) + val reply = probe1.receiveOne(300 milli) reply match { case GamePacket(_, _, msg) => ; assert(msg == string_obj) @@ -126,7 +126,7 @@ class PacketCodingActor7Test extends ActorTest { val msg = ActorTest.MDCControlPacket(PacketCoding.CreateControlPacket(string_obj)) probe2 ! msg - val reply1 = receiveOne(100 milli) //we get a MdcMsg message back + val reply1 = receiveOne(300 milli) //we get a MdcMsg message back probe2 ! reply1 //by feeding the MdcMsg into the actor, we get normal output on the probe probe1.expectMsg(string_hex) } @@ -146,7 +146,7 @@ class PacketCodingActor8Test extends ActorTest { probe1.receiveOne(100 milli) //consume pca ! string_hex - val reply = probe1.receiveOne(100 milli) + val reply = probe1.receiveOne(300 milli) reply match { case ControlPacket(_, msg) => ; assert(msg == string_obj) @@ -200,7 +200,7 @@ class PacketCodingActorBTest extends ActorTest { probe1.receiveOne(100 milli) //consume probe2 ! "unhandled message" - val reply1 = receiveOne(100 milli) //we get a MdcMsg message back + val reply1 = receiveOne(300 milli) //we get a MdcMsg message back probe2 ! reply1 //by feeding the MdcMsg into the actor, we get normal output on the probe probe1.expectMsg("unhandled message") } @@ -379,10 +379,10 @@ class PacketCodingActorETest extends ActorTest { probe1.receiveOne(100 milli) //consume pca ! string_hex - val reply = probe1.receiveN(2, 200 milli) + val reply = probe1.receiveN(2, 400 milli) assert(reply.head == string_obj1) assert(reply(1) == string_obj2) - probe1.expectNoMsg(100 milli) + probe1.expectNoMsg(300 milli) } } } @@ -400,10 +400,10 @@ class PacketCodingActorFTest extends ActorTest { probe1.receiveOne(100 milli) //consume pca ! string_hex - val reply = probe1.receiveN(1, 200 milli) + val reply = probe1.receiveN(1, 400 milli) assert(reply.head == string_obj) //the RelatedB message - 00 15 02 98 - is consumed by pca - probe1.expectNoMsg(100 milli) + probe1.expectNoMsg(300 milli) } } } @@ -421,10 +421,10 @@ class PacketCodingActorGTest extends ActorTest { probe1.receiveOne(100 milli) //consume pca ! string_hex - val reply = probe1.receiveN(1, 200 milli) + val reply = probe1.receiveN(1, 400 milli) assert(reply.head == string_obj) //the RelatedA message - 00 11 02 98 - is consumed by pca; should see error log message in console - probe1.expectNoMsg(100 milli) + probe1.expectNoMsg(300 milli) } } } @@ -443,10 +443,10 @@ class PacketCodingActorHTest extends ActorTest { probe1.receiveOne(100 milli) //consume pca ! string_hex - val reply = probe1.receiveN(2, 200 milli) + val reply = probe1.receiveN(2, 400 milli) assert(reply.head == string_obj1) assert(reply(1) == string_obj2) - probe1.expectNoMsg(100 milli) + probe1.expectNoMsg(300 milli) } } } @@ -498,10 +498,10 @@ class PacketCodingActorITest extends ActorTest { probe1.receiveOne(100 milli) //consume probe2 ! pkt - val reply1 = receiveN(1, 200 milli) //we get a MdcMsg message back + val reply1 = receiveN(1, 400 milli) //we get a MdcMsg message back probe1.receiveN(1, 200 milli) //flush contents probe2 ! reply1.head //by feeding the MdcMsg into the actor, we get normal output on the probe - probe1.receiveOne(100 milli) match { + probe1.receiveOne(300 milli) match { case RawPacket(data) => assert(data == string_hex) PacketCoding.DecodePacket(data).require match { @@ -532,10 +532,10 @@ class PacketCodingActorJTest extends ActorTest { probe1.receiveOne(100 milli) //consume probe2 ! pkt - val reply1 = receiveN(1, 200 milli) //we get a MdcMsg message back + val reply1 = receiveN(1, 400 milli) //we get a MdcMsg message back probe1.receiveN(1, 200 milli) //flush contents probe2 ! reply1.head //by feeding the MdcMsg into the actor, we get normal output on the probe - probe1.receiveOne(100 milli) match { + probe1.receiveOne(300 milli) match { case RawPacket(data) => assert(data == string_hex) case e => @@ -600,13 +600,13 @@ class PacketCodingActorKTest extends ActorTest { probe1.receiveOne(100 milli) //consume probe2 ! pkt - val reply1 = receiveN(2, 200 milli) + val reply1 = receiveN(2, 400 milli) probe1.receiveN(1, 200 milli) //flush contents probe2 ! reply1.head //by feeding the MdcMsg into the actor, we get normal output on the probe - val reply3 = probe1.receiveOne(100 milli).asInstanceOf[RawPacket] + val reply3 = probe1.receiveOne(300 milli).asInstanceOf[RawPacket] pca ! reply3 //reconstruct original three packets from the first bundle - val reply4 = probe1.receiveN(3, 200 milli) + val reply4 = probe1.receiveN(3, 400 milli) var i = 0 reply4.foreach{ case GamePacket(_, _, packet) => From 848929fd01af97b2e900af3e4148681b2a3f8474 Mon Sep 17 00:00:00 2001 From: FateJH Date: Mon, 21 May 2018 10:48:32 -0400 Subject: [PATCH 10/10] test fix --- common/src/test/scala/objects/LoadoutTest.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/test/scala/objects/LoadoutTest.scala b/common/src/test/scala/objects/LoadoutTest.scala index 8a6910b20..5ddb448e2 100644 --- a/common/src/test/scala/objects/LoadoutTest.scala +++ b/common/src/test/scala/objects/LoadoutTest.scala @@ -127,7 +127,7 @@ class LoadoutTest extends Specification { player.ExoSuit = ExoSuitType.Infiltration val ldout7 = Loadout.Create(player, "inf").asInstanceOf[InfantryLoadout] - Player.SuitSetup(player, ExoSuitType.MAX) + player.ExoSuit = ExoSuitType.MAX val ldout3 = Loadout.Create(player, "weaponless").asInstanceOf[InfantryLoadout] slot.Equipment = None slot.Equipment = Tool(trhev_dualcycler)