Graverobbing (#490)

* added corpse control to manage (only) looting; swapping controls in ZonePopulationActor

* making test timing more forgiving; fixing case conditions for corpse addition
This commit is contained in:
Fate-JH 2020-06-10 09:23:52 -04:00 committed by GitHub
parent cf64a0ba7f
commit 3ea51d404e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 136 additions and 69 deletions

View file

@ -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)
)
)
}
}

View file

@ -595,9 +595,8 @@ class PlayerControl(player : Player) extends Actor
def RemoveItemFromSlotCallback(item : Equipment, slot : Int) : Unit = { def RemoveItemFromSlotCallback(item : Equipment, slot : Int) : Unit = {
val obj = ContainerObject val obj = ContainerObject
val zone = obj.Zone 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 events = zone.AvatarEvents
val toChannel = if(obj.VisibleSlots.contains(slot)) zone.Id else player.Name
item.Faction = PlanetSideEmpire.NEUTRAL item.Faction = PlanetSideEmpire.NEUTRAL
if(slot == obj.DrawnSlot) { if(slot == obj.DrawnSlot) {
obj.DrawnSlot = Player.HandsDownSlot obj.DrawnSlot = Player.HandsDownSlot
@ -612,55 +611,51 @@ class PlayerControl(player : Player) extends Actor
val events = zone.AvatarEvents val events = zone.AvatarEvents
val name = player.Name val name = player.Name
val definition = item.Definition val definition = item.Definition
val msg = AvatarAction.SendResponse( val faction = obj.Faction
Service.defaultPlayerGUID, item.Faction = faction
ObjectCreateDetailedMessage( events ! AvatarServiceMessage(
definition.ObjectId, name,
item.GUID, AvatarAction.SendResponse(
ObjectCreateMessageParent(guid, slot), Service.defaultPlayerGUID,
definition.Packet.DetailedConstructorData(item).get ObjectCreateDetailedMessage(
definition.ObjectId,
item.GUID,
ObjectCreateMessageParent(guid, slot),
definition.Packet.DetailedConstructorData(item).get
)
) )
) )
if(obj.isBackpack) { if(obj.VisibleSlots.contains(slot)) {
item.Faction = PlanetSideEmpire.NEUTRAL events ! AvatarServiceMessage(zone.Id, AvatarAction.EquipmentInHand(guid, guid, slot, item))
events ! AvatarServiceMessage(zone.Id, msg)
} }
else { //handle specific types of items
val faction = obj.Faction item match {
item.Faction = faction case trigger : BoomerTrigger =>
events ! AvatarServiceMessage(name, msg) //pick up the trigger, own the boomer; make certain whole faction is aware of that
if(obj.VisibleSlots.contains(slot)) { (zone.GUID(trigger.Companion), zone.Players.find { _.name == name }) match {
events ! AvatarServiceMessage(zone.Id, AvatarAction.EquipmentInHand(guid, guid, slot, item)) case (Some(boomer : BoomerDeployable), Some(avatar))
} if !boomer.OwnerName.contains(name) || boomer.Faction != faction =>
//handle specific types of items val bguid = boomer.GUID
item match { val faction = player.Faction
case trigger : BoomerTrigger => val factionChannel = faction.toString
//pick up the trigger, own the boomer; make certain whole faction is aware of that if(avatar.Deployables.Add(boomer)) {
(zone.GUID(trigger.Companion), zone.Players.find { _.name == name }) match { boomer.Faction = faction
case (Some(boomer : BoomerDeployable), Some(avatar)) boomer.AssignOwnership(player)
if !boomer.OwnerName.contains(name) || boomer.Faction != faction => avatar.Deployables.UpdateUIElement(boomer.Definition.Item).foreach { case (currElem, curr, maxElem, max) =>
val bguid = boomer.GUID events ! AvatarServiceMessage(name, AvatarAction.PlanetsideAttributeToAll(Service.defaultPlayerGUID, maxElem, max))
val faction = player.Faction events ! AvatarServiceMessage(name, AvatarAction.PlanetsideAttributeToAll(Service.defaultPlayerGUID, currElem, curr))
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)))
)
)
} }
case _ => ; //pointless trigger? zone.LocalEvents ! LocalServiceMessage.Deployables(RemoverActor.ClearSpecific(List(boomer), zone))
} events ! AvatarServiceMessage(factionChannel, AvatarAction.SetEmpire(Service.defaultPlayerGUID, bguid, faction))
case _ => ; 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 _ => ;
} }
} }

View file

@ -2,7 +2,7 @@
package net.psforever.objects.zones package net.psforever.objects.zones
import akka.actor.{Actor, ActorRef, Props} 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 net.psforever.objects.{Avatar, Default, Player}
import scala.annotation.tailrec import scala.annotation.tailrec
@ -65,22 +65,18 @@ class ZonePopulationActor(zone : Zone, playerMap : TrieMap[Avatar, Option[Player
case Zone.Corpse.Add(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 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 //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) { val (canBeCorpse, control) = playerMap.find { case (a, _) => a.name == player.Name } match {
playerMap.find { case (a, _) => a.name == player.Name } match { case Some((a, Some(p))) if p eq player =>
case Some((a, Some(p))) if p eq player => PopulationRelease(a, playerMap)
PopulationRelease(a, playerMap) context.stop(player.Actor)
(true, false) (true, Some(player.Actor))
case Some((a, None)) => case Some((_, None)) =>
(true, true) (true, None)
case _ => case _ =>
(false, false) (player.Zone == Zone.Nowhere || player.Zone == zone, None)
}
} }
else { if(canBeCorpse && CorpseAdd(player, corpseList)) {
(player.Zone == Zone.Nowhere, true) player.Actor = context.actorOf(Props(classOf[CorpseControl], player), name = s"corpse_of_${GetPlayerControlName(player, control)}")
}
if(canBeCorpse && CorpseAdd(player, corpseList) && playerNotFound) {
player.Actor = context.actorOf(Props(classOf[PlayerControl], player), name = s"corpse_of_${GetPlayerControlName(player, None)}")
player.Zone = zone player.Zone = zone
} }

View file

@ -453,7 +453,7 @@ class ZonePopulationTest extends ActorTest {
assert(zone.Corpses.isEmpty) assert(zone.Corpses.isEmpty)
zone.Population ! Zone.Corpse.Add(player) zone.Population ! Zone.Corpse.Add(player)
expectNoMessage(Duration.create(100, "ms")) expectNoMessage(Duration.create(500, "ms"))
assert(zone.Corpses.size == 1) assert(zone.Corpses.size == 1)
assert(zone.Corpses.head == player) assert(zone.Corpses.head == player)
} }
@ -466,12 +466,12 @@ class ZonePopulationTest extends ActorTest {
zone.Actor ! Zone.Init() zone.Actor ! Zone.Init()
expectNoMessage(200 milliseconds) expectNoMessage(200 milliseconds)
zone.Population ! Zone.Corpse.Add(player) zone.Population ! Zone.Corpse.Add(player)
expectNoMessage(Duration.create(100, "ms")) expectNoMessage(Duration.create(500, "ms"))
assert(zone.Corpses.size == 1) assert(zone.Corpses.size == 1)
assert(zone.Corpses.head == player) assert(zone.Corpses.head == player)
zone.Population ! Zone.Corpse.Remove(player) zone.Population ! Zone.Corpse.Remove(player)
expectNoMessage(Duration.create(100, "ms")) expectNoMessage(Duration.create(200, "ms"))
assert(zone.Corpses.isEmpty) assert(zone.Corpses.isEmpty)
} }
@ -489,14 +489,14 @@ class ZonePopulationTest extends ActorTest {
zone.Population ! Zone.Corpse.Add(player1) zone.Population ! Zone.Corpse.Add(player1)
zone.Population ! Zone.Corpse.Add(player2) zone.Population ! Zone.Corpse.Add(player2)
zone.Population ! Zone.Corpse.Add(player3) zone.Population ! Zone.Corpse.Add(player3)
expectNoMessage(Duration.create(100, "ms")) expectNoMessage(Duration.create(500, "ms"))
assert(zone.Corpses.size == 3) assert(zone.Corpses.size == 3)
assert(zone.Corpses.head == player1) assert(zone.Corpses.head == player1)
assert(zone.Corpses(1) == player2) assert(zone.Corpses(1) == player2)
assert(zone.Corpses(2) == player3) assert(zone.Corpses(2) == player3)
zone.Population ! Zone.Corpse.Remove(player2) zone.Population ! Zone.Corpse.Remove(player2)
expectNoMessage(Duration.create(100, "ms")) expectNoMessage(Duration.create(200, "ms"))
assert(zone.Corpses.size == 2) assert(zone.Corpses.size == 2)
assert(zone.Corpses.head == player1) assert(zone.Corpses.head == player1)
assert(zone.Corpses(1) == player3) assert(zone.Corpses(1) == player3)
@ -512,7 +512,7 @@ class ZonePopulationTest extends ActorTest {
assert(zone.Corpses.isEmpty) assert(zone.Corpses.isEmpty)
zone.Population ! Zone.Corpse.Add(player) zone.Population ! Zone.Corpse.Add(player)
expectNoMessage(Duration.create(100, "ms")) expectNoMessage(Duration.create(200, "ms"))
assert(zone.Corpses.isEmpty) assert(zone.Corpses.isEmpty)
} }
} }