Merge pull request #201 from Fate-JH/move-item

Move item
This commit is contained in:
Fate-JH 2018-05-08 22:22:48 -04:00 committed by GitHub
commit ddf2f53d8a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 171 additions and 70 deletions

View file

@ -20,4 +20,8 @@ object InventoryItem {
def apply(obj : Equipment, start : Int) : InventoryItem = {
new InventoryItem(obj, start)
}
def unapply(entry : InventoryItem) : Option[(Equipment, Int)] = {
Some((entry.obj, entry.start))
}
}

View file

@ -273,14 +273,14 @@ class WorldSessionActor extends Actor with MDCContextAware {
sendResponse(GenericObjectActionMessage(guid, 36))
}
case AvatarResponse.EquipmentInHand(slot, item) =>
case AvatarResponse.EquipmentInHand(target, slot, item) =>
if(tplayer_guid != guid) {
val definition = item.Definition
sendResponse(
ObjectCreateMessage(
definition.ObjectId,
item.GUID,
ObjectCreateMessageParent(guid, slot),
ObjectCreateMessageParent(target, slot),
definition.Packet.ConstructorData(item).get
)
)
@ -369,6 +369,19 @@ class WorldSessionActor extends Actor with MDCContextAware {
sendResponse(ReloadMessage(item_guid, 1, 0))
}
case AvatarResponse.StowEquipment(target, slot, item) =>
if(tplayer_guid != guid) {
val definition = item.Definition
sendResponse(
ObjectCreateDetailedMessage(
definition.ObjectId,
item.GUID,
ObjectCreateMessageParent(target, slot),
definition.Packet.DetailedConstructorData(item).get
)
)
}
case AvatarResponse.WeaponDryFire(weapon_guid) =>
if(tplayer_guid != guid) {
sendResponse(WeaponDryFireMessage(weapon_guid))
@ -589,7 +602,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
val player_guid : PlanetSideGUID = tplayer.GUID
val obj_guid : PlanetSideGUID = obj.GUID
log.info(s"MountVehicleMsg: $player_guid mounts $obj @ $seat_num")
//tplayer.VehicleSeated = Some(obj_guid)
PlayerActionsToCancel()
sendResponse(PlanetsideAttributeMessage(obj_guid, 0, 1000L)) //health of mech
sendResponse(ObjectAttachMessage(obj_guid, player_guid, seat_num))
vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.MountVehicle(player_guid, obj_guid, seat_num))
@ -599,7 +612,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
val player_guid : PlanetSideGUID = tplayer.GUID
log.info(s"MountVehicleMsg: $player_guid mounts $obj_guid @ $seat_num")
vehicleService ! VehicleServiceMessage.UnscheduleDeconstruction(obj_guid) //clear all deconstruction timers
//tplayer.VehicleSeated = Some(obj_guid)
PlayerActionsToCancel()
if(seat_num == 0) { //simplistic vehicle ownership management
obj.Owner match {
case Some(owner_guid) =>
@ -739,7 +752,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
definition.Packet.DetailedConstructorData(obj).get
)
)
avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.EquipmentInHand(player.GUID, index, obj))
avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.EquipmentInHand(player.GUID, player.GUID, index, obj))
case None => ;
}
})
@ -1296,7 +1309,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
)
)
if(tplayer.VisibleSlots.contains(slot)) {
avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.EquipmentInHand(player_guid, slot, item))
avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.EquipmentInHand(player_guid, player_guid, slot, item))
}
case None =>
continent.Ground ! Zone.DropItemOnGround(item, item.Position, item.Orientation) //restore
@ -2027,8 +2040,8 @@ class WorldSessionActor extends Actor with MDCContextAware {
case Some(obj : Equipment) =>
val findFunc : PlanetSideGameObject with Container => Option[(PlanetSideGameObject with Container, Option[Int])] = FindInLocalContainer(object_guid)
findFunc(player)
.orElse(findFunc(player.Locker))
findFunc(player.Locker)
.orElse(findFunc(player))
.orElse(accessedContainer match {
case Some(parent) =>
findFunc(parent)
@ -2056,100 +2069,159 @@ class WorldSessionActor extends Actor with MDCContextAware {
sendResponse(ObjectDeleteMessage(object_guid, 0))
log.info("ObjectDelete: " + msg)
case msg @ MoveItemMessage(item_guid, source_guid, destination_guid, dest, unk1) =>
case msg @ MoveItemMessage(item_guid, source_guid, destination_guid, dest, _) =>
log.info(s"MoveItem: $msg")
(continent.GUID(source_guid), continent.GUID(destination_guid), continent.GUID(item_guid)) match {
case (Some(source : Container), Some(destination : Container), Some(item : Equipment)) =>
source.Find(item_guid) match {
case Some(index) =>
val indexSlot = source.Slot(index)
val destSlot = destination.Slot(dest)
val destItem = destSlot.Equipment
val tile = item.Definition.Tile
val destinationCollisionTest = destination.Collisions(dest, tile.Width, tile.Height)
val destItemEntry = destinationCollisionTest match {
case Success(entry :: Nil) =>
Some(entry)
case _ =>
None
}
if( {
val tile = item.Definition.Tile
destination.Collisions(dest, tile.Width, tile.Height) match {
case Success(Nil) =>
destItem.isEmpty //no item swap; abort if encountering an unexpected item
case Success(entry :: Nil) =>
destItem.contains(entry.obj) //one item to swap; abort if destination item is missing or is wrong
case Success(_) | scala.util.Failure(_) =>
destinationCollisionTest match {
case Success(Nil) | Success(_ :: Nil) =>
true //no item or one item to swap
case _ =>
false //abort when too many items at destination or other failure case
}
} && indexSlot.Equipment.contains(item)) {
log.info(s"MoveItem: $item_guid moved from $source_guid @ $index to $destination_guid @ $dest")
val player_guid = player.GUID
val sourceIsNotDestination : Boolean = source != destination //if source is destination, OCDM style is not required
//remove item from source
indexSlot.Equipment = None
destItem match { //do we have a swap item?
case Some(item2) => //yes, swap
destSlot.Equipment = None //remove item2 to make room for item
destSlot.Equipment = item
source match {
case obj : Vehicle =>
vehicleService ! VehicleServiceMessage(s"${obj.Actor}", VehicleAction.UnstowEquipment(player_guid, item_guid))
case obj : Player =>
if(obj.isBackpack || source.VisibleSlots.contains(index)) { //corpse being looted, or item was in hands
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ObjectDelete(player_guid, item_guid))
}
case _ => ;
}
destItemEntry match { //do we have a swap item in the destination slot?
case Some(InventoryItem(item2, destIndex)) => //yes, swap
//cleanly shuffle items around to avoid losing icons
//the next ObjectDetachMessage is necessary to avoid icons being lost, but only as part of this swap
sendResponse(ObjectDetachMessage(source_guid, item_guid, Vector3.Zero, 0f, 0f, 0f))
val item2_guid = item2.GUID
destination.Slot(destIndex).Equipment = None //remove the swap item from destination
(indexSlot.Equipment = item2) match {
case Some(_) => //item and item2 swapped places successfully
log.info(s"MoveItem: ${item2.GUID} swapped to $source_guid @ $index")
//cleanly shuffle items around to avoid losing icons
sendResponse(ObjectDetachMessage(source_guid, item_guid, Vector3(0f, 0f, 0f), 0f, 0f, 0f)) //ground; A -> C
sendResponse(ObjectAttachMessage(source_guid, item2.GUID, index)) //B -> A
source match {
case (obj : Vehicle) =>
val player_guid = player.GUID
vehicleService ! VehicleServiceMessage(s"${obj.Actor}", VehicleAction.UnstowEquipment(player_guid, item_guid))
vehicleService ! VehicleServiceMessage(s"${obj.Actor}", VehicleAction.StowEquipment(player_guid, source_guid, index, item2))
//TODO visible slot verification, in the case of BFR arms
case (obj : Player) =>
if(source.VisibleSlots.contains(index)) {
avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.EquipmentInHand(source_guid, index, item2))
log.info(s"MoveItem: $item2_guid swapped to $source_guid @ $index")
//remove item2 from destination
sendResponse(ObjectDetachMessage(destination_guid, item2_guid, Vector3.Zero, 0f, 0f, 0f))
destination match {
case obj : Vehicle =>
vehicleService ! VehicleServiceMessage(s"${obj.Actor}", VehicleAction.UnstowEquipment(player_guid, item2_guid))
case obj : Player =>
if(obj.isBackpack || destination.VisibleSlots.contains(dest)) { //corpse being looted, or item was in hands
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ObjectDelete(player_guid, item2_guid))
}
case _ => ;
}
//display item2 in source
if(sourceIsNotDestination && player == source) {
val objDef = item2.Definition
sendResponse(
ObjectCreateDetailedMessage(
objDef.ObjectId,
item2_guid,
ObjectCreateMessageParent(source_guid, index),
objDef.Packet.DetailedConstructorData(item2).get
)
)
}
else {
sendResponse(ObjectAttachMessage(source_guid, item2_guid, index))
}
source match {
case obj : Vehicle =>
vehicleService ! VehicleServiceMessage(s"${obj.Actor}", VehicleAction.StowEquipment(player_guid, source_guid, index, item2))
case obj : Player =>
if(source.VisibleSlots.contains(index)) { //item is put in hands
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.EquipmentInHand(player_guid, source_guid, index, item2))
}
else if(obj.isBackpack) { //corpse being given item
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.StowEquipment(player_guid, source_guid, index, item2))
}
case _ => ;
//TODO something?
}
case None => //item2 does not fit; drop on ground
log.info(s"MoveItem: $item2_guid can not fit in swap location; dropping on ground @ ${source.Position}")
val pos = source.Position
val sourceOrientZ = source.Orientation.z
val orient : Vector3 = Vector3(0f, 0f, sourceOrientZ)
continent.Actor ! Zone.DropItemOnGround(item2, pos, orient)
sendResponse(ObjectDetachMessage(source_guid, item2.GUID, pos, 0f, 0f, sourceOrientZ)) //ground
sendResponse(ObjectDetachMessage(destination_guid, item2_guid, pos, 0f, 0f, sourceOrientZ)) //ground
val objDef = item2.Definition
avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.EquipmentOnGround(player.GUID, pos, orient, objDef.ObjectId, item2.GUID, objDef.Packet.ConstructorData(item2).get))
}
case None => //just move item over
destSlot.Equipment = item
source match {
case (obj : Vehicle) =>
vehicleService ! VehicleServiceMessage(s"${obj.Actor}", VehicleAction.UnstowEquipment(player.GUID, item_guid))
//TODO visible slot verification, in the case of BFR arms
case _ => ;
//TODO something?
destination match {
case obj : Vehicle =>
vehicleService ! VehicleServiceMessage(s"${obj.Actor}", VehicleAction.UnstowEquipment(player_guid, item2_guid))
case _ => ;
//Player does not require special case; the act of dropping forces the item and icon to change
}
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.EquipmentOnGround(player_guid, pos, orient, objDef.ObjectId, item2_guid, objDef.Packet.ConstructorData(item2).get))
}
case None => ;
}
//move item into destination slot
destination.Slot(dest).Equipment = item
if(sourceIsNotDestination && player == destination) {
val objDef = item.Definition
sendResponse(
ObjectCreateDetailedMessage(
objDef.ObjectId,
item_guid,
ObjectCreateMessageParent(destination_guid, dest),
objDef.Packet.DetailedConstructorData(item).get
)
)
}
else {
sendResponse(ObjectAttachMessage(destination_guid, item_guid, dest))
}
sendResponse(ObjectAttachMessage(destination_guid, item_guid, dest))
destination match {
case (obj : Vehicle) =>
vehicleService ! VehicleServiceMessage(s"${obj.Actor}", VehicleAction.StowEquipment(player.GUID, destination_guid, dest, item))
//TODO visible slot verification, in the case of BFR arms
case (_ : Player) =>
if(destination.VisibleSlots.contains(dest)) {
avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.EquipmentInHand(destination_guid, dest, item))
case obj : Vehicle =>
vehicleService ! VehicleServiceMessage(s"${obj.Actor}", VehicleAction.StowEquipment(player_guid, destination_guid, dest, item))
case obj : Player =>
if(destination.VisibleSlots.contains(dest)) { //item is put in hands
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.EquipmentInHand(player_guid, destination_guid, dest, item))
}
else if(obj.isBackpack) { //corpse being given item
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.StowEquipment(player_guid, destination_guid, dest, item))
}
case _ => ;
//TODO something?
}
}
else if(indexSlot.Equipment.nonEmpty) {
log.error(s"MoveItem: wanted to move $item_guid, but unexpected item ${indexSlot.Equipment.get} at origin")
else if(!indexSlot.Equipment.contains(item)) {
log.error(s"MoveItem: wanted to move $item_guid, but found unexpected ${indexSlot.Equipment.get} at source location")
}
else {
log.error(s"MoveItem: wanted to move $item_guid, but unexpected item(s) at destination")
destinationCollisionTest match {
case Success(_) =>
log.error(s"MoveItem: wanted to move $item_guid, but multiple unexpected items at destination blocked progress")
case scala.util.Failure(err) =>
log.error(s"MoveItem: wanted to move $item_guid, but $err")
}
}
case _ =>
log.error(s"MoveItem: wanted to move $item_guid, but could not find it")
}
case (None, _, _) =>
log.error(s"MoveItem: wanted to move $item_guid from $source_guid, but could not find source")
log.error(s"MoveItem: wanted to move $item_guid from $source_guid, but could not find source object")
case (_, None, _) =>
log.error(s"MoveItem: wanted to move $item_guid from $source_guid to $destination_guid, but could not find destination")
log.error(s"MoveItem: wanted to move $item_guid to $destination_guid, but could not find destination object")
case (_, _, None) =>
log.error(s"MoveItem: wanted to move $item_guid, but could not find it")
case _ =>
@ -2680,7 +2752,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
)
)
if(localTarget.VisibleSlots.contains(localIndex)) {
localService ! AvatarServiceMessage(continent.Id, AvatarAction.EquipmentInHand(localTarget.GUID, localIndex, localObject))
localService ! AvatarServiceMessage(continent.Id, AvatarAction.EquipmentInHand(localTarget.GUID, localTarget.GUID, localIndex, localObject))
}
}
})
@ -3554,7 +3626,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
/**
* An event has occurred that would cause the player character to stop certain stateful activities.
* These activities include shooting, hacking, accessing (a container), flying, and running.
* These activities include shooting, weapon drawing, hacking, accessing (a container), flying, and running.
* Other players in the same zone must be made aware that the player has stopped as well.<br>
* <br>
* Things whose configuration should not be changed:<br>
@ -3583,6 +3655,11 @@ class WorldSessionActor extends Actor with MDCContextAware {
shooting = None
case None => ;
}
if(player != null && player.isAlive && player.VisibleSlots.contains(player.DrawnSlot)) {
player.DrawnSlot = Player.HandsDownSlot
sendResponse(ObjectHeldMessage(player.GUID, Player.HandsDownSlot, true))
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ObjectHeld(player.GUID, player.LastDrawnSlot))
}
if(flying) {
sendResponse(ChatMsg(ChatMessageType.CMT_FLY, false, "", "off", None))
flying = false

View file

@ -17,7 +17,7 @@ object AvatarAction {
final case class ChangeFireState_Start(player_guid : PlanetSideGUID, weapon_guid : PlanetSideGUID) extends Action
final case class ChangeFireState_Stop(player_guid : PlanetSideGUID, weapon_guid : PlanetSideGUID) extends Action
final case class ConcealPlayer(player_guid : PlanetSideGUID) extends Action
final case class EquipmentInHand(player_guid : PlanetSideGUID, slot : Int, item : Equipment) extends Action
final case class EquipmentInHand(player_guid : PlanetSideGUID, target_guid : PlanetSideGUID, slot : Int, item : Equipment) extends Action
final case class EquipmentOnGround(player_guid : PlanetSideGUID, pos : Vector3, orient : Vector3, item_id : Int, item_guid : PlanetSideGUID, item_data : ConstructorData) extends Action
final case class LoadPlayer(player_guid : PlanetSideGUID, pdata : ConstructorData) extends Action
// final case class LoadMap(msg : PlanetSideGUID) extends Action
@ -28,6 +28,7 @@ object AvatarAction {
final case class PlayerState(player_guid : PlanetSideGUID, msg : PlayerStateMessageUpstream, spectator : Boolean, weaponInHand : Boolean) extends Action
final case class Release(player : Player, zone : Zone, time : Option[Long] = None) extends Action
final case class Reload(player_guid : PlanetSideGUID, weapon_guid : PlanetSideGUID) extends Action
final case class StowEquipment(player_guid : PlanetSideGUID, target_guid : PlanetSideGUID, slot : Int, item : Equipment) extends Action
final case class WeaponDryFire(player_guid : PlanetSideGUID, weapon_guid : PlanetSideGUID) extends Action
// final case class PlayerStateShift(killer : PlanetSideGUID, victim : PlanetSideGUID) extends Action
// final case class DestroyDisplay(killer : PlanetSideGUID, victim : PlanetSideGUID) extends Action

View file

@ -16,7 +16,7 @@ object AvatarResponse {
final case class ChangeFireState_Start(weapon_guid : PlanetSideGUID) extends Response
final case class ChangeFireState_Stop(weapon_guid : PlanetSideGUID) extends Response
final case class ConcealPlayer() extends Response
final case class EquipmentInHand(slot : Int, item : Equipment) extends Response
final case class EquipmentInHand(target_guid : PlanetSideGUID, slot : Int, item : Equipment) extends Response
final case class EquipmentOnGround(pos : Vector3, orient : Vector3, item_id : Int, item_guid : PlanetSideGUID, item_data : ConstructorData) extends Response
final case class LoadPlayer(pdata : ConstructorData) extends Response
// final case class unLoadMap() extends Response
@ -27,6 +27,7 @@ object AvatarResponse {
final case class PlayerState(msg : PlayerStateMessageUpstream, spectator : Boolean, weaponInHand : Boolean) extends Response
final case class Release(player : Player) extends Response
final case class Reload(weapon_guid : PlanetSideGUID) extends Response
final case class StowEquipment(target_guid : PlanetSideGUID, slot : Int, item : Equipment) extends Response
final case class WeaponDryFire(weapon_guid : PlanetSideGUID) extends Response
// final case class PlayerStateShift(itemID : PlanetSideGUID) extends Response
// final case class DestroyDisplay(itemID : PlanetSideGUID) extends Response

View file

@ -62,9 +62,9 @@ class AvatarService extends Actor {
AvatarEvents.publish(
AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.ConcealPlayer())
)
case AvatarAction.EquipmentInHand(player_guid, slot, obj) =>
case AvatarAction.EquipmentInHand(player_guid, target_guid, slot, obj) =>
AvatarEvents.publish(
AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.EquipmentInHand(slot, obj))
AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.EquipmentInHand(target_guid, slot, obj))
)
case AvatarAction.EquipmentOnGround(player_guid, pos, orient, item_id, item_guid, item_data) =>
AvatarEvents.publish(
@ -102,6 +102,10 @@ class AvatarService extends Actor {
AvatarEvents.publish(
AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.Reload(weapon_guid))
)
case AvatarAction.StowEquipment(player_guid, target_guid, slot, obj) =>
AvatarEvents.publish(
AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.StowEquipment(target_guid, slot, obj))
)
case AvatarAction.WeaponDryFire(player_guid, weapon_guid) =>
AvatarEvents.publish(
AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.WeaponDryFire(weapon_guid))

View file

@ -100,8 +100,8 @@ class EquipmentInHandTest extends ActorTest {
ServiceManager.boot(system)
val service = system.actorOf(Props[AvatarService], AvatarServiceTest.TestName)
service ! Service.Join("test")
service ! AvatarServiceMessage("test", AvatarAction.EquipmentInHand(PlanetSideGUID(10), 2, tool))
expectMsg(AvatarServiceResponse("/test/Avatar", PlanetSideGUID(10), AvatarResponse.EquipmentInHand(2, tool)))
service ! AvatarServiceMessage("test", AvatarAction.EquipmentInHand(PlanetSideGUID(10), PlanetSideGUID(11), 2, tool))
expectMsg(AvatarServiceResponse("/test/Avatar", PlanetSideGUID(10), AvatarResponse.EquipmentInHand(PlanetSideGUID(11), 2, tool)))
}
}
}
@ -271,6 +271,20 @@ class WeaponDryFireTest extends ActorTest {
}
}
class AvatarStowEquipmentTest extends ActorTest {
val tool = Tool(GlobalDefinitions.beamer)
"AvatarService" should {
"pass StowEquipment" in {
ServiceManager.boot(system)
val service = system.actorOf(Props[AvatarService], AvatarServiceTest.TestName)
service ! Service.Join("test")
service ! AvatarServiceMessage("test", AvatarAction.StowEquipment(PlanetSideGUID(10), PlanetSideGUID(11), 2, tool))
expectMsg(AvatarServiceResponse("/test/Avatar", PlanetSideGUID(10), AvatarResponse.StowEquipment(PlanetSideGUID(11), 2, tool)))
}
}
}
/*
Preparation for these three Release tests is involved.
The ServiceManager must not only be set up correctly, but must be given a TaskResolver.