From 3ea51d404ed4ed087a4759f81a31c8b336413bdc Mon Sep 17 00:00:00 2001 From: Fate-JH Date: Wed, 10 Jun 2020 09:23:52 -0400 Subject: [PATCH] Graverobbing (#490) * added corpse control to manage (only) looting; swapping controls in ZonePopulationActor * making test timing more forgiving; fixing case conditions for corpse addition --- .../objects/avatar/CorpseControl.scala | 76 ++++++++++++++++ .../objects/avatar/PlayerControl.scala | 89 +++++++++---------- .../objects/zones/ZonePopulationActor.scala | 28 +++--- common/src/test/scala/objects/ZoneTest.scala | 12 +-- 4 files changed, 136 insertions(+), 69 deletions(-) create mode 100644 common/src/main/scala/net/psforever/objects/avatar/CorpseControl.scala diff --git a/common/src/main/scala/net/psforever/objects/avatar/CorpseControl.scala b/common/src/main/scala/net/psforever/objects/avatar/CorpseControl.scala new file mode 100644 index 000000000..6e6d937f3 --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/avatar/CorpseControl.scala @@ -0,0 +1,76 @@ +// Copyright (c) 2020 PSForever +package net.psforever.objects.avatar + +import akka.actor.Actor +import net.psforever.objects.Player +import net.psforever.objects.equipment.Equipment +import net.psforever.objects.serverobject.containable.{Containable, ContainableBehavior} +import net.psforever.packet.game.{ObjectAttachMessage, ObjectCreateDetailedMessage, ObjectDetachMessage} +import net.psforever.packet.game.objectcreate.ObjectCreateMessageParent +import net.psforever.types.{PlanetSideEmpire, Vector3} +import services.Service +import services.avatar.{AvatarAction, AvatarServiceMessage} + +class CorpseControl(player : Player) extends Actor + with ContainableBehavior { + def ContainerObject = player + + //private [this] val log = org.log4s.getLogger(player.Name) + + def receive : Receive = containerBehavior.orElse { case _ => ; } + + def MessageDeferredCallback(msg : Any) : Unit = { + msg match { + case Containable.MoveItem(_, item, _) => + //momentarily put item back where it was originally + val obj = ContainerObject + obj.Find(item) match { + case Some(slot) => + obj.Zone.AvatarEvents ! AvatarServiceMessage( + player.Zone.Id, + AvatarAction.SendResponse(Service.defaultPlayerGUID, ObjectAttachMessage(obj.GUID, item.GUID, slot)) + ) + case None => ; + } + case _ => ; + } + } + + def RemoveItemFromSlotCallback(item : Equipment, slot : Int) : Unit = { + val obj = ContainerObject + val zone = obj.Zone + val events = zone.AvatarEvents + item.Faction = PlanetSideEmpire.NEUTRAL + events ! AvatarServiceMessage(zone.Id, AvatarAction.ObjectDelete(Service.defaultPlayerGUID, item.GUID)) + } + + def PutItemInSlotCallback(item : Equipment, slot : Int) : Unit = { + val obj = ContainerObject + val zone = obj.Zone + val events = zone.AvatarEvents + val definition = item.Definition + events ! AvatarServiceMessage( + zone.Id, + AvatarAction.SendResponse( + Service.defaultPlayerGUID, + ObjectCreateDetailedMessage( + definition.ObjectId, + item.GUID, + ObjectCreateMessageParent(obj.GUID, slot), + definition.Packet.DetailedConstructorData(item).get + ) + ) + ) + } + + def SwapItemCallback(item : Equipment) : Unit = { + val obj = ContainerObject + val zone = obj.Zone + zone.AvatarEvents ! AvatarServiceMessage(zone.Id, + AvatarAction.SendResponse( + Service.defaultPlayerGUID, + ObjectDetachMessage(obj.GUID, item.GUID, Vector3.Zero, 0f) + ) + ) + } +} diff --git a/common/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala b/common/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala index b5027cd57..f864527b3 100644 --- a/common/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala +++ b/common/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala @@ -595,9 +595,8 @@ class PlayerControl(player : Player) extends Actor def RemoveItemFromSlotCallback(item : Equipment, slot : Int) : Unit = { val obj = ContainerObject val zone = obj.Zone - val name = player.Name - val toChannel = if(obj.VisibleSlots.contains(slot) || obj.isBackpack) zone.Id else name val events = zone.AvatarEvents + val toChannel = if(obj.VisibleSlots.contains(slot)) zone.Id else player.Name item.Faction = PlanetSideEmpire.NEUTRAL if(slot == obj.DrawnSlot) { obj.DrawnSlot = Player.HandsDownSlot @@ -612,55 +611,51 @@ class PlayerControl(player : Player) extends Actor val events = zone.AvatarEvents val name = player.Name val definition = item.Definition - val msg = AvatarAction.SendResponse( - Service.defaultPlayerGUID, - ObjectCreateDetailedMessage( - definition.ObjectId, - item.GUID, - ObjectCreateMessageParent(guid, slot), - definition.Packet.DetailedConstructorData(item).get + val faction = obj.Faction + item.Faction = faction + events ! AvatarServiceMessage( + name, + AvatarAction.SendResponse( + Service.defaultPlayerGUID, + ObjectCreateDetailedMessage( + definition.ObjectId, + item.GUID, + ObjectCreateMessageParent(guid, slot), + definition.Packet.DetailedConstructorData(item).get + ) ) ) - if(obj.isBackpack) { - item.Faction = PlanetSideEmpire.NEUTRAL - events ! AvatarServiceMessage(zone.Id, msg) + if(obj.VisibleSlots.contains(slot)) { + events ! AvatarServiceMessage(zone.Id, AvatarAction.EquipmentInHand(guid, guid, slot, item)) } - else { - val faction = obj.Faction - item.Faction = faction - events ! AvatarServiceMessage(name, msg) - if(obj.VisibleSlots.contains(slot)) { - events ! AvatarServiceMessage(zone.Id, AvatarAction.EquipmentInHand(guid, guid, slot, item)) - } - //handle specific types of items - item match { - case trigger : BoomerTrigger => - //pick up the trigger, own the boomer; make certain whole faction is aware of that - (zone.GUID(trigger.Companion), zone.Players.find { _.name == name }) match { - case (Some(boomer : BoomerDeployable), Some(avatar)) - if !boomer.OwnerName.contains(name) || boomer.Faction != faction => - val bguid = boomer.GUID - val faction = player.Faction - val factionChannel = faction.toString - if(avatar.Deployables.Add(boomer)) { - boomer.Faction = faction - boomer.AssignOwnership(player) - avatar.Deployables.UpdateUIElement(boomer.Definition.Item).foreach { case (currElem, curr, maxElem, max) => - events ! AvatarServiceMessage(name, AvatarAction.PlanetsideAttributeToAll(Service.defaultPlayerGUID, maxElem, max)) - events ! AvatarServiceMessage(name, AvatarAction.PlanetsideAttributeToAll(Service.defaultPlayerGUID, currElem, curr)) - } - zone.LocalEvents ! LocalServiceMessage.Deployables(RemoverActor.ClearSpecific(List(boomer), zone)) - events ! AvatarServiceMessage(factionChannel, AvatarAction.SetEmpire(Service.defaultPlayerGUID, bguid, faction)) - zone.LocalEvents ! LocalServiceMessage(factionChannel, - LocalAction.DeployableMapIcon(Service.defaultPlayerGUID, DeploymentAction.Build, - DeployableInfo(bguid, DeployableIcon.Boomer, boomer.Position, boomer.Owner.getOrElse(PlanetSideGUID(0))) - ) - ) + //handle specific types of items + item match { + case trigger : BoomerTrigger => + //pick up the trigger, own the boomer; make certain whole faction is aware of that + (zone.GUID(trigger.Companion), zone.Players.find { _.name == name }) match { + case (Some(boomer : BoomerDeployable), Some(avatar)) + if !boomer.OwnerName.contains(name) || boomer.Faction != faction => + val bguid = boomer.GUID + val faction = player.Faction + val factionChannel = faction.toString + if(avatar.Deployables.Add(boomer)) { + boomer.Faction = faction + boomer.AssignOwnership(player) + avatar.Deployables.UpdateUIElement(boomer.Definition.Item).foreach { case (currElem, curr, maxElem, max) => + events ! AvatarServiceMessage(name, AvatarAction.PlanetsideAttributeToAll(Service.defaultPlayerGUID, maxElem, max)) + events ! AvatarServiceMessage(name, AvatarAction.PlanetsideAttributeToAll(Service.defaultPlayerGUID, currElem, curr)) } - case _ => ; //pointless trigger? - } - case _ => ; - } + zone.LocalEvents ! LocalServiceMessage.Deployables(RemoverActor.ClearSpecific(List(boomer), zone)) + events ! AvatarServiceMessage(factionChannel, AvatarAction.SetEmpire(Service.defaultPlayerGUID, bguid, faction)) + zone.LocalEvents ! LocalServiceMessage(factionChannel, + LocalAction.DeployableMapIcon(Service.defaultPlayerGUID, DeploymentAction.Build, + DeployableInfo(bguid, DeployableIcon.Boomer, boomer.Position, boomer.Owner.getOrElse(PlanetSideGUID(0))) + ) + ) + } + case _ => ; //pointless trigger? + } + case _ => ; } } diff --git a/common/src/main/scala/net/psforever/objects/zones/ZonePopulationActor.scala b/common/src/main/scala/net/psforever/objects/zones/ZonePopulationActor.scala index d6225590c..3af173e53 100644 --- a/common/src/main/scala/net/psforever/objects/zones/ZonePopulationActor.scala +++ b/common/src/main/scala/net/psforever/objects/zones/ZonePopulationActor.scala @@ -2,7 +2,7 @@ package net.psforever.objects.zones import akka.actor.{Actor, ActorRef, Props} -import net.psforever.objects.avatar.PlayerControl +import net.psforever.objects.avatar.{CorpseControl, PlayerControl} import net.psforever.objects.{Avatar, Default, Player} import scala.annotation.tailrec @@ -65,22 +65,18 @@ class ZonePopulationActor(zone : Zone, playerMap : TrieMap[Avatar, Option[Player case Zone.Corpse.Add(player) => //player can be a corpse if they are in the current zone or are not in any zone //player is "found" if their avatar can be matched by name within this zone and it has a character - val (canBeCorpse, playerNotFound) = if(player.Zone == zone) { - playerMap.find { case (a, _) => a.name == player.Name } match { - case Some((a, Some(p))) if p eq player => - PopulationRelease(a, playerMap) - (true, false) - case Some((a, None)) => - (true, true) - case _ => - (false, false) - } + val (canBeCorpse, control) = playerMap.find { case (a, _) => a.name == player.Name } match { + case Some((a, Some(p))) if p eq player => + PopulationRelease(a, playerMap) + context.stop(player.Actor) + (true, Some(player.Actor)) + case Some((_, None)) => + (true, None) + case _ => + (player.Zone == Zone.Nowhere || player.Zone == zone, None) } - else { - (player.Zone == Zone.Nowhere, true) - } - if(canBeCorpse && CorpseAdd(player, corpseList) && playerNotFound) { - player.Actor = context.actorOf(Props(classOf[PlayerControl], player), name = s"corpse_of_${GetPlayerControlName(player, None)}") + if(canBeCorpse && CorpseAdd(player, corpseList)) { + player.Actor = context.actorOf(Props(classOf[CorpseControl], player), name = s"corpse_of_${GetPlayerControlName(player, control)}") player.Zone = zone } diff --git a/common/src/test/scala/objects/ZoneTest.scala b/common/src/test/scala/objects/ZoneTest.scala index 533c5c6e2..3fdc0538a 100644 --- a/common/src/test/scala/objects/ZoneTest.scala +++ b/common/src/test/scala/objects/ZoneTest.scala @@ -453,7 +453,7 @@ class ZonePopulationTest extends ActorTest { assert(zone.Corpses.isEmpty) zone.Population ! Zone.Corpse.Add(player) - expectNoMessage(Duration.create(100, "ms")) + expectNoMessage(Duration.create(500, "ms")) assert(zone.Corpses.size == 1) assert(zone.Corpses.head == player) } @@ -466,12 +466,12 @@ class ZonePopulationTest extends ActorTest { zone.Actor ! Zone.Init() expectNoMessage(200 milliseconds) zone.Population ! Zone.Corpse.Add(player) - expectNoMessage(Duration.create(100, "ms")) + expectNoMessage(Duration.create(500, "ms")) assert(zone.Corpses.size == 1) assert(zone.Corpses.head == player) zone.Population ! Zone.Corpse.Remove(player) - expectNoMessage(Duration.create(100, "ms")) + expectNoMessage(Duration.create(200, "ms")) assert(zone.Corpses.isEmpty) } @@ -489,14 +489,14 @@ class ZonePopulationTest extends ActorTest { zone.Population ! Zone.Corpse.Add(player1) zone.Population ! Zone.Corpse.Add(player2) zone.Population ! Zone.Corpse.Add(player3) - expectNoMessage(Duration.create(100, "ms")) + expectNoMessage(Duration.create(500, "ms")) assert(zone.Corpses.size == 3) assert(zone.Corpses.head == player1) assert(zone.Corpses(1) == player2) assert(zone.Corpses(2) == player3) zone.Population ! Zone.Corpse.Remove(player2) - expectNoMessage(Duration.create(100, "ms")) + expectNoMessage(Duration.create(200, "ms")) assert(zone.Corpses.size == 2) assert(zone.Corpses.head == player1) assert(zone.Corpses(1) == player3) @@ -512,7 +512,7 @@ class ZonePopulationTest extends ActorTest { assert(zone.Corpses.isEmpty) zone.Population ! Zone.Corpse.Add(player) - expectNoMessage(Duration.create(100, "ms")) + expectNoMessage(Duration.create(200, "ms")) assert(zone.Corpses.isEmpty) } }