issue where contents of inventory did not match loadout specifications of the exo-suit to which the player was switching

This commit is contained in:
Fate-JH 2024-02-20 19:10:38 -05:00
parent c705a0fb56
commit c4d142d586
4 changed files with 118 additions and 134 deletions

View file

@ -350,6 +350,7 @@ class SessionAvatarHandlers(
sendResponse(ObjectDeleteMessage(objGuid, unk1=0))
TaskWorkflow.execute(GUIDTask.unregisterEquipment(continent.GUID, obj))
}
drops.foreach(item => sendResponse(ObjectDeleteMessage(item.obj.GUID, unk1=0)))
//redraw
if (maxhand) {
TaskWorkflow.execute(HoldNewEquipmentUp(player)(

View file

@ -110,25 +110,34 @@ object Players {
* For any slots that are not yet occupied by an item, search through the `List` and find an item that fits in that slot.
* Add that item to the slot and remove it from the list.
* @param iter the `Iterator` of `EquipmentSlot`s
* @param list a `List` of all `Equipment` that is not yet assigned to a holster slot or an inventory slot
* @return the `List` of all `Equipment` not yet assigned to a holster slot or an inventory slot
* @param list a list of all `Equipment` not assigned to a new slot
* @param slotNum current slot index associated with the value extracted from `iter` param
* @param placedList a list of all `Equipment` reassigned to a slot
* @return two lists:
* all `Equipment` reassigned to a slot, and
* all `Equipment` not assigned to a new slot
*/
@tailrec def fillEmptyHolsters(iter: Iterator[EquipmentSlot], list: List[InventoryItem]): List[InventoryItem] = {
@tailrec def fillEmptyHolsters(
iter: Iterator[EquipmentSlot],
list: List[InventoryItem],
slotNum: Int = 0,
placedList: List[InventoryItem] = Nil
): (List[InventoryItem], List[InventoryItem]) = {
if (!iter.hasNext) {
list
(placedList, list)
} else {
val slot = iter.next()
if (slot.Equipment.isEmpty) {
list.find(item => item.obj.Size == slot.Size) match {
case Some(obj) =>
val index = list.indexOf(obj)
slot.Equipment = obj.obj
fillEmptyHolsters(iter, list.take(index) ++ list.drop(index + 1))
case None =>
fillEmptyHolsters(iter, list)
list.indexWhere(item => item.obj.Size == slot.Size) match {
case -1 =>
fillEmptyHolsters(iter, list, slotNum + 1, placedList)
case index =>
val entry = list(index)
entry.start = slotNum
fillEmptyHolsters(iter, list.take(index) ++ list.drop(index + 1), slotNum + 1, placedList :+ entry)
}
} else {
fillEmptyHolsters(iter, list)
fillEmptyHolsters(iter, list, slotNum + 1, placedList)
}
}
}

View file

@ -381,26 +381,8 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
log.info(s"${player.Name} wants to change equipment loadout to their option #${msg.unk1 + 1}")
val originalSuit = player.ExoSuit
val originalSubtype = Loadout.DetermineSubtype(player)
//sanitize exo-suit for change
val dropPred = ContainableBehavior.DropPredicate(player)
val oldHolsters = Players.clearHolsters(player.Holsters().iterator)
val dropHolsters = oldHolsters.filter(dropPred)
val oldInventory = player.Inventory.Clear()
val dropInventory = oldInventory.filter(dropPred)
val toDeleteOrDrop : List[InventoryItem] = (player.FreeHand.Equipment match {
case Some(obj) =>
val out = InventoryItem(obj, -1)
player.FreeHand.Equipment = None
if (dropPred(out)) {
List(out)
} else {
Nil
}
case _ =>
Nil
}) ++ dropHolsters ++ dropInventory
//a loadout with a prohibited exo-suit type will result in the fallback exo-suit type
//imposed 5min delay on mechanized exo-suit switches
//determine player's next exo-suit
val (nextSuit, nextSubtype) = {
lazy val fallbackSuit = if (Players.CertificationToUseExoSuit(player, originalSuit, originalSubtype)) {
//TODO will we ever need to check for the cooldown status of an original non-MAX exo-suit?
@ -430,20 +412,30 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
fallbackSuit
}
}
if (nextSuit == ExoSuitType.MAX) {
player.ResistArmMotion(PlayerControl.maxRestriction)
} else {
player.ResistArmMotion(Player.neverRestrict)
//sanitize current exo-suit for change
val (dropHolsters, oldHolsters) = Players.clearHolsters(player.Holsters().iterator).partition(dropPred)
val (dropInventory, oldInventory) = player.Inventory.Clear().partition(dropPred)
val (dropHand, deleteHand) = player.FreeHand.Equipment match {
case Some(obj) =>
val out = InventoryItem(obj, -1)
player.FreeHand.Equipment = None
if (dropPred(out)) {
(List(out), Nil)
} else {
(Nil, List(out))
}
case _ =>
(Nil, Nil)
}
//sanitize (incoming) inventory
//TODO equipment permissions; these loops may be expanded upon in future
val curatedHolsters = for {
//these dropped items exist and must be accounted for
val itemsToDrop = dropHand ++ dropHolsters ++ dropInventory
val newHolsters = for {
item <- holsters
//id = item.obj.Definition.ObjectId
//lastTime = player.GetLastUsedTime(id)
if true
} yield item
val curatedInventory = for {
val newInventory = for {
item <- inventory
//id = item.obj.Definition.ObjectId
//lastTime = player.GetLastUsedTime(id)
@ -461,60 +453,55 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
player.Armor = originalArmor
}
}
//ensure arm is down, even if it needs to go back up
if (player.DrawnSlot != Player.HandsDownSlot) {
player.DrawnSlot = Player.HandsDownSlot
}
//a change due to exo-suit permissions mismatch will result in (more) items being re-arranged and/or dropped
//dropped items are not registered and can just be forgotten
val (afterHolsters, afterInventory) = if (nextSuit == exosuit) {
(
//melee slot preservation for MAX
if (nextSuit == ExoSuitType.MAX) {
holsters.filter(_.start == 4)
} else {
curatedHolsters.filterNot(dropPred)
},
curatedInventory.filterNot(dropPred)
)
val (afterHolsters, afterInventory) = if (exosuit == nextSuit) {
//proposed loadout inventory matched the projected exo-suit selection
if (nextSuit == ExoSuitType.MAX) {
//loadout for a MAX
player.ResistArmMotion(PlayerControl.maxRestriction)
player.DrawnSlot = Player.HandsDownSlot
(newHolsters.filter(_.start == 4), newInventory.filterNot(dropPred))
} else {
//loadout for a vanilla exo-suit
player.ResistArmMotion(Player.neverRestrict)
(newHolsters.filterNot(dropPred), newInventory.filterNot(dropPred))
}
} else {
//our exo-suit type was hijacked by changing permissions; we shouldn't even be able to use that loadout(!)
//holsters
val leftoversForInventory = Players.fillEmptyHolsters(
//proposed loadout conforms to a different inventory layout than the projected exo-suit
player.ResistArmMotion(Player.neverRestrict)
//holsters (matching holsters will be inserted, the rest will deposited into the inventory)
val (finalHolsters, leftoversForInventory) = Players.fillEmptyHolsters(
player.Holsters().iterator,
(curatedHolsters ++ curatedInventory).filterNot(dropPred)
(newHolsters.filterNot(_.obj.Size == EquipmentSize.Max) ++ newInventory).filterNot(dropPred)
)
val finalHolsters = player.HolsterItems()
//inventory
//inventory (items will be placed to accommodate the change, or dropped)
val (finalInventory, _) = GridInventory.recoverInventory(leftoversForInventory, player.Inventory)
(finalHolsters, finalInventory)
}
(afterHolsters ++ afterInventory).foreach { entry => entry.obj.Faction = player.Faction }
afterHolsters.foreach {
afterHolsters.collect {
case InventoryItem(citem: ConstructionItem, _) =>
Deployables.initializeConstructionItem(player.avatar.certifications, citem)
case _ => ;
}
toDeleteOrDrop.foreach { entry => entry.obj.Faction = PlanetSideEmpire.NEUTRAL }
//deactivate non-passive implants
avatarActor ! AvatarActor.DeactivateActiveImplants()
player.Zone.AvatarEvents ! AvatarServiceMessage(
player.Zone.id,
val zone = player.Zone
zone.AvatarEvents ! AvatarServiceMessage(
zone.id,
AvatarAction.ChangeLoadout(
player.GUID,
toArmor,
nextSuit,
nextSubtype,
player.LastDrawnSlot,
exosuit == ExoSuitType.MAX,
nextSuit == ExoSuitType.MAX,
oldHolsters.map { case InventoryItem(obj, _) => (obj, obj.GUID) },
afterHolsters,
oldInventory.map { case InventoryItem(obj, _) => (obj, obj.GUID) },
(oldInventory ++ deleteHand).map { case InventoryItem(obj, _) => (obj, obj.GUID) },
afterInventory,
toDeleteOrDrop
itemsToDrop
)
)
player.Zone.AvatarEvents ! AvatarServiceMessage(
zone.AvatarEvents ! AvatarServiceMessage(
player.Name,
AvatarAction.TerminalOrderResult(msg.terminal_guid, msg.transaction_type, result=true)
)
@ -617,84 +604,70 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
}
def setExoSuit(exosuit: ExoSuitType.Value, subtype: Int): Boolean = {
var toDelete : List[InventoryItem] = Nil
val willBecomeMax = exosuit == ExoSuitType.MAX
val originalSuit = player.ExoSuit
val originalSubtype = Loadout.DetermineSubtype(player)
val requestToChangeArmor = originalSuit != exosuit || originalSubtype != subtype
val allowedToChangeArmor = Players.CertificationToUseExoSuit(player, exosuit, subtype) &&
(if (exosuit == ExoSuitType.MAX) {
val changeSuit = originalSuit != exosuit
val changeSubtype = originalSubtype != subtype
val doChangeArmor = (changeSuit || changeSubtype) &&
Players.CertificationToUseExoSuit(player, exosuit, subtype) &&
(if (willBecomeMax) {
val weapon = GlobalDefinitions.MAXArms(subtype, player.Faction)
player.avatar.purchaseCooldown(weapon) match {
case Some(_) =>
false
case None =>
player.avatar.purchaseCooldown(weapon)
.collect(_ => false)
.getOrElse {
avatarActor ! AvatarActor.UpdatePurchaseTime(weapon)
true
}
}
} else {
true
})
if (requestToChangeArmor && allowedToChangeArmor) {
if (doChangeArmor) {
log.info(s"${player.Name} wants to change to a different exo-suit - $exosuit")
val beforeHolsters = Players.clearHolsters(player.Holsters().iterator)
val beforeInventory = player.Inventory.Clear()
//change suit
//update suit internally
val originalArmor = player.Armor
player.ExoSuit = exosuit //changes the value of MaxArmor to reflect the new exo-suit
player.ExoSuit = exosuit
val toMaxArmor = player.MaxArmor
val toArmor = toMaxArmor
if (originalSuit != exosuit || originalArmor != toMaxArmor) {
player.LogActivity(RepairFromExoSuitChange(exosuit, toMaxArmor - originalArmor))
val toArmor = {
if (originalArmor > toMaxArmor) {
player.LogActivity(RepairFromExoSuitChange(exosuit, toMaxArmor - player.Armor))
player.Armor = toMaxArmor
} else {
player.Armor = originalArmor
}
}
player.Armor = toMaxArmor
//ensure arm is down, even if it needs to go back up
if (player.DrawnSlot != Player.HandsDownSlot) {
player.DrawnSlot = Player.HandsDownSlot
}
val normalHolsters = if (originalSuit == ExoSuitType.MAX) {
val (maxWeapons, normalWeapons) = beforeHolsters.partition(elem => elem.obj.Size == EquipmentSize.Max)
toDelete ++= maxWeapons
normalWeapons
}
else {
beforeHolsters
}
//populate holsters
val (afterHolsters, finalInventory) = if (exosuit == ExoSuitType.MAX) {
(
normalHolsters,
Players.fillEmptyHolsters(List(player.Slot(4)).iterator, normalHolsters) ++ beforeInventory
)
}
else if (originalSuit == exosuit) { //note - this will rarely be the situation
(normalHolsters, Players.fillEmptyHolsters(player.Holsters().iterator, normalHolsters))
}
else {
val (afterHolsters, toInventory) =
normalHolsters.partition(elem => elem.obj.Size == player.Slot(elem.start).Size)
afterHolsters.foreach({ elem => player.Slot(elem.start).Equipment = elem.obj })
val remainder = Players.fillEmptyHolsters(player.Holsters().iterator, toInventory ++ beforeInventory)
(
player.HolsterItems(),
remainder
)
}
//put items back into inventory
val (stow, drop) = if (originalSuit == exosuit) {
(finalInventory, Nil)
}
else {
val (a, b) = GridInventory.recoverInventory(finalInventory, player.Inventory)
(
a,
b.map {
InventoryItem(_, -1)
}
)
}
stow.foreach { elem =>
player.Inventory.InsertQuickly(elem.start, elem.obj)
val (toDelete, toDrop, afterHolsters, afterInventory) = if (originalSuit == ExoSuitType.MAX) {
//was max
val (delete, insert) = beforeHolsters.partition(elem => elem.obj.Size == EquipmentSize.Max)
if (willBecomeMax) {
//changing to a different kind(?) of max
(delete, Nil, insert, beforeInventory)
} else {
//changing to a vanilla exo-suit
val (newHolsters, unplacedHolsters) = Players.fillEmptyHolsters(player.Holsters().iterator, insert ++ beforeInventory)
val (inventory, unplacedInventory) = GridInventory.recoverInventory(unplacedHolsters, player.Inventory)
(delete, unplacedInventory.map(InventoryItem(_, -1)), newHolsters, inventory)
}
} else if (willBecomeMax) {
//will be max, drop everything but melee slot
val (melee, other) = beforeHolsters.partition(elem => elem.obj.Size == EquipmentSize.Melee)
val (inventory, unplacedInventory) = GridInventory.recoverInventory(beforeInventory ++ other, player.Inventory)
(Nil, unplacedInventory.map(InventoryItem(_, -1)), melee, inventory)
} else {
//was not a max nor will become a max; vanilla exo-suit to a vanilla-exo-suit
val (insert, unplacedHolsters) = Players.fillEmptyHolsters(player.Holsters().iterator, beforeHolsters ++ beforeInventory)
val (inventory, unplacedInventory) = GridInventory.recoverInventory(unplacedHolsters, player.Inventory)
(Nil, unplacedInventory.map(InventoryItem(_, -1)), insert, inventory)
}
//insert
afterHolsters.foreach(elem => player.Slot(elem.start).Equipment = elem.obj)
afterInventory.foreach(elem => player.Inventory.InsertQuickly(elem.start, elem.obj))
//deactivate non-passive implants
avatarActor ! AvatarActor.DeactivateActiveImplants()
player.Zone.AvatarEvents ! AvatarServiceMessage(
@ -705,18 +678,17 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
exosuit,
subtype,
player.LastDrawnSlot,
exosuit == ExoSuitType.MAX && requestToChangeArmor,
willBecomeMax,
beforeHolsters.map { case InventoryItem(obj, _) => (obj, obj.GUID) },
afterHolsters,
beforeInventory.map { case InventoryItem(obj, _) => (obj, obj.GUID) },
stow,
drop,
afterInventory,
toDrop,
toDelete.map { case InventoryItem(obj, _) => (obj, obj.GUID) }
)
)
true
}
else {
} else {
false
}
}

View file

@ -14,6 +14,8 @@ import net.psforever.types.PlanetSideGUID
class InventoryItem(val obj: Equipment, var start: Int = 0) {
//TODO eventually move this object from storing the item directly to just storing its GUID?
def GUID: PlanetSideGUID = obj.GUID
override def toString: String = s"InventoryItem(${obj.Definition.Name}-$start)"
}
object InventoryItem {