mirror of
https://github.com/2revoemag/PSF-BotServer.git
synced 2026-01-20 02:24:45 +00:00
Exclude Equipment from Loadouts (#249)
* routines for eliminating equipment and configurations from loadout availability depending on the terminal type * fix to FavoritesMessage use of exo-suit type and subtype causing entries to identify incorrectly; standard is the fallback exo-suit type should a player try to load a suit type they no longer have certed * factored subtype value into exo-suit fallback selection * when in a vehicle, and accessing a terminal, the item purchased will attempt to be placed in the vehicle's trunk before testing the player's free hand; the item will not go into the player's backpack
This commit is contained in:
parent
c91b3caa99
commit
6399963e68
|
|
@ -5803,6 +5803,7 @@ object GlobalDefinitions {
|
|||
order_terminala.Tab += 2 -> OrderTerminalDefinition.EquipmentPage(EquipmentTerminalDefinition.supportAmmunition ++ EquipmentTerminalDefinition.supportWeapons)
|
||||
order_terminala.Tab += 3 -> OrderTerminalDefinition.EquipmentPage(EquipmentTerminalDefinition.vehicleAmmunition)
|
||||
order_terminala.Tab += 4 -> OrderTerminalDefinition.InfantryLoadoutPage()
|
||||
order_terminala.Tab(4).asInstanceOf[OrderTerminalDefinition.InfantryLoadoutPage].Exclude = ExoSuitType.MAX
|
||||
order_terminala.SellEquipmentByDefault = true
|
||||
|
||||
order_terminalb.Name = "order_terminalb"
|
||||
|
|
@ -5811,6 +5812,7 @@ object GlobalDefinitions {
|
|||
order_terminalb.Tab += 2 -> OrderTerminalDefinition.EquipmentPage(EquipmentTerminalDefinition.supportAmmunition ++ EquipmentTerminalDefinition.supportWeapons)
|
||||
order_terminalb.Tab += 3 -> OrderTerminalDefinition.EquipmentPage(EquipmentTerminalDefinition.vehicleAmmunition)
|
||||
order_terminalb.Tab += 4 -> OrderTerminalDefinition.InfantryLoadoutPage()
|
||||
order_terminalb.Tab(4).asInstanceOf[OrderTerminalDefinition.InfantryLoadoutPage].Exclude = ExoSuitType.MAX
|
||||
order_terminalb.SellEquipmentByDefault = true
|
||||
|
||||
cert_terminal.Name = "cert_terminal"
|
||||
|
|
|
|||
|
|
@ -94,6 +94,7 @@ class SpecialExoSuitDefinition(private val suitType : ExoSuitType.Value) extends
|
|||
|
||||
override def Use : ExoSuitDefinition = {
|
||||
val obj = new SpecialExoSuitDefinition(SuitType)
|
||||
obj.Permissions = Permissions
|
||||
obj.MaxArmor = MaxArmor
|
||||
obj.InventoryScale = InventoryScale
|
||||
obj.InventoryOffset = InventoryOffset
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package net.psforever.objects.loadouts
|
||||
|
||||
import net.psforever.types.ExoSuitType
|
||||
import net.psforever.types.{CertificationType, ExoSuitType}
|
||||
|
||||
/**
|
||||
* A blueprint of a player's uniform, their holster items, and their inventory items, saved in a specific state.
|
||||
|
|
@ -99,4 +99,19 @@ object InfantryLoadout {
|
|||
case ExoSuitType.Infiltration => 7
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assuming the exo-suit is a mechanized assault type,
|
||||
* use the subtype to determine what certifications would be valid for permitted access to that specific exo-suit.
|
||||
* The "C" does not stand for "certification."
|
||||
* @see `CertificationType`
|
||||
* @param subtype the numeric subtype
|
||||
* @return a `Set` of all certifications that would grant access to the mechanized assault exo-suit subtype
|
||||
*/
|
||||
def DetermineSubtypeC(subtype : Int) : Set[CertificationType.Value] = subtype match {
|
||||
case 1 => Set(CertificationType.AIMAX, CertificationType.UniMAX)
|
||||
case 2 => Set(CertificationType.AVMAX, CertificationType.UniMAX)
|
||||
case 3 => Set(CertificationType.AAMAX, CertificationType.UniMAX)
|
||||
case _ => Set.empty[CertificationType.Value]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -136,7 +136,7 @@ object OrderTerminalDefinition {
|
|||
Terminal.BuyExosuit(suit, subtype)
|
||||
case _ =>
|
||||
items.get(msg.item_name) match {
|
||||
case Some(item : (()=>Equipment)) =>
|
||||
case Some(item) =>
|
||||
Terminal.BuyEquipment(item())
|
||||
case _ =>
|
||||
Terminal.NoDeal()
|
||||
|
|
@ -180,7 +180,7 @@ object OrderTerminalDefinition {
|
|||
final case class EquipmentPage(stock : Map[String, ()=>Equipment]) extends Tab {
|
||||
override def Buy(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = {
|
||||
stock.get(msg.item_name) match {
|
||||
case Some(item : (()=>Equipment)) =>
|
||||
case Some(item) =>
|
||||
Terminal.BuyEquipment(item())
|
||||
case _ =>
|
||||
Terminal.NoDeal()
|
||||
|
|
@ -216,23 +216,56 @@ object OrderTerminalDefinition {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The base class for "loadout" type tabs.
|
||||
* Defines logic for enumerating items and entities that should be eliminated from being loaded.
|
||||
* The method for filtering those excluded items, if applicable,
|
||||
* and management of the resulting loadout object
|
||||
* is the responsibility of the specific tab that is instantiated.
|
||||
*/
|
||||
abstract class LoadoutTab extends Tab {
|
||||
private var contraband : Seq[Any] = Nil
|
||||
|
||||
def Exclude : Seq[Any] = contraband
|
||||
|
||||
def Exclude_=(equipment : Any) : Seq[Any] = {
|
||||
contraband = Seq(equipment)
|
||||
Exclude
|
||||
}
|
||||
|
||||
def Exclude_=(equipmentList : Seq[Any]) : Seq[Any] = {
|
||||
contraband = equipmentList
|
||||
Exclude
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The tab used to select which custom loadout the player is using.
|
||||
* Player loadouts are defined by an exo-suit to be worn by the player
|
||||
* and equipment in the holsters and the inventory.
|
||||
* In this case, the reference to the player that is a parameter of the functions maintains information about the loadouts;
|
||||
* no extra information specific to this page is necessary.
|
||||
* If an exo-suit type is considered excluded, the whole loadout is blocked.
|
||||
* If the exclusion is written as a `Tuple` object `(A, B)`,
|
||||
* `A` will be expected as an exo-suit type, and `B` will be expected as its subtype,
|
||||
* and the pair must both match to block the whole loadout.
|
||||
* If any of the player's inventory is considered excluded, only those items will be filtered.
|
||||
* @see `ExoSuitType`
|
||||
* @see `Equipment`
|
||||
* @see `InfantryLoadout`
|
||||
* @see `Loadout`
|
||||
*/
|
||||
final case class InfantryLoadoutPage() extends Tab {
|
||||
//TODO block equipment by blocking ammunition type
|
||||
final case class InfantryLoadoutPage() extends LoadoutTab {
|
||||
override def Buy(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = {
|
||||
player.LoadLoadout(msg.unk1) match {
|
||||
case Some(loadout : InfantryLoadout) =>
|
||||
val holsters = loadout.visible_slots.map(entry => { InventoryItem(BuildSimplifiedPattern(entry.item), entry.index) })
|
||||
val inventory = loadout.inventory.map(entry => { InventoryItem(BuildSimplifiedPattern(entry.item), entry.index) })
|
||||
case Some(loadout : InfantryLoadout) if !Exclude.contains(loadout.exosuit) && !Exclude.contains((loadout.exosuit, loadout.subtype)) =>
|
||||
val holsters = loadout.visible_slots
|
||||
.map(entry => { InventoryItem(BuildSimplifiedPattern(entry.item), entry.index) })
|
||||
.filterNot { entry => Exclude.contains(entry.obj.Definition) }
|
||||
val inventory = loadout.inventory
|
||||
.map(entry => { InventoryItem(BuildSimplifiedPattern(entry.item), entry.index) })
|
||||
.filterNot { entry => Exclude.contains(entry.obj.Definition) }
|
||||
Terminal.InfantryLoadout(loadout.exosuit, loadout.subtype, holsters, inventory)
|
||||
case _ =>
|
||||
Terminal.NoDeal()
|
||||
|
|
@ -246,16 +279,21 @@ object OrderTerminalDefinition {
|
|||
* and equipment in the trunk.
|
||||
* In this case, the reference to the player that is a parameter of the functions maintains information about the loadouts;
|
||||
* no extra information specific to this page is necessary.
|
||||
* If a vehicle type (by definition) is considered excluded, the whole loadout is blocked.
|
||||
* If any of the vehicle's inventory is considered excluded, only those items will be filtered.
|
||||
* @see `Equipment`
|
||||
* @see `Loadout`
|
||||
* @see `VehicleLoadout`
|
||||
*/
|
||||
final case class VehicleLoadoutPage() extends Tab {
|
||||
final case class VehicleLoadoutPage() extends LoadoutTab {
|
||||
override def Buy(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = {
|
||||
player.LoadLoadout(msg.unk1 + 10) match {
|
||||
case Some(loadout : VehicleLoadout) =>
|
||||
val weapons = loadout.visible_slots.map(entry => { InventoryItem(BuildSimplifiedPattern(entry.item), entry.index) })
|
||||
val inventory = loadout.inventory.map(entry => { InventoryItem(BuildSimplifiedPattern(entry.item), entry.index) })
|
||||
case Some(loadout : VehicleLoadout) if !Exclude.contains(loadout.vehicle_definition) =>
|
||||
val weapons = loadout.visible_slots
|
||||
.map(entry => { InventoryItem(BuildSimplifiedPattern(entry.item), entry.index) })
|
||||
val inventory = loadout.inventory
|
||||
.map(entry => { InventoryItem(BuildSimplifiedPattern(entry.item), entry.index) })
|
||||
.filterNot { entry => Exclude.contains(entry.obj.Definition) }
|
||||
Terminal.VehicleLoadout(loadout.vehicle_definition, weapons, inventory)
|
||||
case _ =>
|
||||
Terminal.NoDeal()
|
||||
|
|
|
|||
|
|
@ -1535,13 +1535,34 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
lastTerminalOrderFulfillment = true
|
||||
|
||||
case Terminal.BuyEquipment(item) =>
|
||||
tplayer.Fit(item) match {
|
||||
case Some(index) =>
|
||||
item.Faction = tplayer.Faction
|
||||
sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, true))
|
||||
taskResolver ! PutEquipmentInSlot(tplayer, item, index)
|
||||
case None =>
|
||||
sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, false))
|
||||
continent.GUID(tplayer.VehicleSeated) match {
|
||||
//vehicle trunk
|
||||
case Some(vehicle : Vehicle) =>
|
||||
vehicle.Fit(item) match {
|
||||
case Some(index) =>
|
||||
item.Faction = tplayer.Faction
|
||||
sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, true))
|
||||
taskResolver ! StowNewEquipmentInVehicle(vehicle)(index, item)
|
||||
case None => //player free hand?
|
||||
tplayer.FreeHand.Equipment match {
|
||||
case None =>
|
||||
item.Faction = tplayer.Faction
|
||||
sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, true))
|
||||
taskResolver ! PutEquipmentInSlot(tplayer, item, Player.FreeHandSlot)
|
||||
case Some(_) =>
|
||||
sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, false))
|
||||
}
|
||||
}
|
||||
//player backpack or free hand
|
||||
case _ =>
|
||||
tplayer.Fit(item) match {
|
||||
case Some(index) =>
|
||||
item.Faction = tplayer.Faction
|
||||
sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, true))
|
||||
taskResolver ! PutEquipmentInSlot(tplayer, item, index)
|
||||
case None =>
|
||||
sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, false))
|
||||
}
|
||||
}
|
||||
lastTerminalOrderFulfillment = true
|
||||
|
||||
|
|
@ -1559,24 +1580,42 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
|
||||
case Terminal.InfantryLoadout(exosuit, subtype, holsters, inventory) =>
|
||||
log.info(s"$tplayer wants to change equipment loadout to their option #${msg.unk1 + 1}")
|
||||
//TODO check exo-suit permissions
|
||||
sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Loadout, true))
|
||||
//prepare lists of valid objects
|
||||
val beforeFreeHand = tplayer.FreeHand.Equipment
|
||||
val dropPred = DropPredicate(tplayer)
|
||||
val (dropHolsters, beforeHolsters) = clearHolsters(tplayer.Holsters().iterator).partition(dropPred)
|
||||
val (dropInventory, beforeInventory) = tplayer.Inventory.Clear().partition(dropPred)
|
||||
val (_, afterHolsters) = holsters.partition(dropPred) //dropped items are forgotten
|
||||
val (_, afterInventory) = inventory.partition(dropPred) //dropped items are forgotten
|
||||
//change suit (clear inventory and change holster sizes; holsters must be empty before this point)
|
||||
tplayer.FreeHand.Equipment = None //terminal and inventory will close, so prematurely dropping should be fine
|
||||
//sanitize exo-suit for change
|
||||
val originalSuit = player.ExoSuit
|
||||
val originalSubtype = Loadout.DetermineSubtype(tplayer)
|
||||
val fallbackSuit = ExoSuitType.Standard
|
||||
val fallbackSubtype = 0
|
||||
//a loadout with a prohibited exo-suit type will result in a fallback exo-suit type
|
||||
val (nextSuit : ExoSuitType.Value, nextSubtype : Int) =
|
||||
if(ExoSuitDefinition.Select(exosuit).Permissions match {
|
||||
case Nil =>
|
||||
true
|
||||
case permissions if subtype != 0 =>
|
||||
val certs = tplayer.Certifications
|
||||
certs.intersect(permissions.toSet).nonEmpty &&
|
||||
certs.intersect(InfantryLoadout.DetermineSubtypeC(subtype)).nonEmpty
|
||||
case permissions =>
|
||||
tplayer.Certifications.intersect(permissions.toSet).nonEmpty
|
||||
}) {
|
||||
(exosuit, subtype)
|
||||
}
|
||||
else {
|
||||
log.warn(s"$tplayer no longer has permission to wear the exo-suit type $exosuit; will wear $fallbackSuit instead")
|
||||
(fallbackSuit, fallbackSubtype)
|
||||
}
|
||||
//update suit interally (holsters must be empty before this point)
|
||||
val originalArmor = player.Armor
|
||||
tplayer.ExoSuit = exosuit
|
||||
tplayer.ExoSuit = nextSuit
|
||||
val toMaxArmor = tplayer.MaxArmor
|
||||
if(originalSuit != exosuit || originalSubtype != subtype || originalArmor > toMaxArmor) {
|
||||
tplayer.History(HealFromExoSuitChange(PlayerSource(tplayer), exosuit))
|
||||
if(originalSuit != nextSuit || originalSubtype != nextSubtype || originalArmor > toMaxArmor) {
|
||||
tplayer.History(HealFromExoSuitChange(PlayerSource(tplayer), nextSuit))
|
||||
tplayer.Armor = toMaxArmor
|
||||
sendResponse(PlanetsideAttributeMessage(tplayer.GUID, 4, toMaxArmor))
|
||||
avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.PlanetsideAttribute(tplayer.GUID, 4, toMaxArmor))
|
||||
|
|
@ -1590,6 +1629,44 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
sendResponse(ObjectHeldMessage(tplayer.GUID, Player.HandsDownSlot, true))
|
||||
avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.ObjectHeld(tplayer.GUID, tplayer.LastDrawnSlot))
|
||||
}
|
||||
//a change due to exo-suit permissions mismatch will result in (more) items being re-arranged and/or dropped
|
||||
//dropped items can be forgotten safely
|
||||
val (afterHolsters, afterInventory) = if(nextSuit == exosuit) {
|
||||
(
|
||||
holsters.filterNot(dropPred),
|
||||
inventory.filterNot(dropPred)
|
||||
)
|
||||
}
|
||||
else {
|
||||
val newSuitDef = ExoSuitDefinition.Select(nextSuit)
|
||||
val (afterInventory, extra) = GridInventory.recoverInventory(
|
||||
inventory.filterNot(dropPred),
|
||||
tplayer.Inventory
|
||||
)
|
||||
val afterHolsters = {
|
||||
val preservedHolsters = if(exosuit == ExoSuitType.MAX) {
|
||||
holsters.filter(_.start == 4) //melee slot perservation
|
||||
}
|
||||
else {
|
||||
holsters
|
||||
.filterNot(dropPred)
|
||||
.collect {
|
||||
case item @ InventoryItem(obj, index) if newSuitDef.Holster(index) == obj.Size => item
|
||||
}
|
||||
}
|
||||
val size = newSuitDef.Holsters.size
|
||||
val indexMap = preservedHolsters.map { entry => entry.start }
|
||||
preservedHolsters ++ (extra.map { obj =>
|
||||
tplayer.Fit(obj) match {
|
||||
case Some(index : Int) if index < size && !indexMap.contains(index) =>
|
||||
InventoryItem(obj, index)
|
||||
case _ =>
|
||||
InventoryItem(obj, -1)
|
||||
}
|
||||
}).filterNot(entry => entry.start == -1)
|
||||
}
|
||||
(afterHolsters, afterInventory)
|
||||
}
|
||||
//delete everything (not dropped)
|
||||
beforeHolsters.foreach({ elem =>
|
||||
avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.ObjectDelete(tplayer.GUID, elem.obj.GUID))
|
||||
|
|
@ -1599,9 +1676,9 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
taskResolver ! GUIDTask.UnregisterEquipment(elem.obj)(continent.GUID)
|
||||
})
|
||||
//report change
|
||||
sendResponse(ArmorChangedMessage(tplayer.GUID, exosuit, subtype))
|
||||
avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.ArmorChanged(tplayer.GUID, exosuit, subtype))
|
||||
if(exosuit == ExoSuitType.MAX) {
|
||||
sendResponse(ArmorChangedMessage(tplayer.GUID, nextSuit, nextSubtype))
|
||||
avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.ArmorChanged(tplayer.GUID, nextSuit, nextSubtype))
|
||||
if(nextSuit == ExoSuitType.MAX) {
|
||||
val (maxWeapons, otherWeapons) = afterHolsters.partition(entry => { entry.obj.Size == EquipmentSize.Max })
|
||||
taskResolver ! DelayedObjectHeld(tplayer, 0, List(PutEquipmentInSlot(tplayer, maxWeapons.head.obj, 0)))
|
||||
otherWeapons
|
||||
|
|
@ -2622,7 +2699,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
val (inf, veh) = avatar.Loadouts.partition { case (index, _) => index < 10 }
|
||||
inf.foreach {
|
||||
case (index, loadout : InfantryLoadout) =>
|
||||
sendResponse(FavoritesMessage(LoadoutType.Infantry, guid, index, loadout.label, loadout.exosuit.id + loadout.subtype))
|
||||
sendResponse(FavoritesMessage(LoadoutType.Infantry, guid, index, loadout.label, InfantryLoadout.DetermineSubtypeB(loadout.exosuit, loadout.subtype)))
|
||||
}
|
||||
veh.foreach {
|
||||
case (index, loadout : VehicleLoadout) =>
|
||||
|
|
@ -5622,7 +5699,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
* @see `ChangeAmmoMessage`
|
||||
* @param obj the `Container` object
|
||||
* @param index an index in `obj`'s inventory
|
||||
* @param item an `AmmoBox`
|
||||
* @param item the `Equipment` item
|
||||
* @return a `TaskResolver.GiveTask` chain that executes the action
|
||||
*/
|
||||
def StowNewEquipment(obj : PlanetSideGameObject with Container)(index : Int, item : Equipment) : TaskResolver.GiveTask = {
|
||||
|
|
@ -5637,7 +5714,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
* @see `ChangeAmmoMessage`
|
||||
* @param obj the `Container` object
|
||||
* @param index an index in `obj`'s inventory
|
||||
* @param item an `AmmoBox`
|
||||
* @param item the `Equipment` item
|
||||
* @return a `TaskResolver.GiveTask` chain that executes the action
|
||||
*/
|
||||
def StowNewEquipmentInVehicle(obj : Vehicle)(index : Int, item : Equipment) : TaskResolver.GiveTask = {
|
||||
|
|
|
|||
Loading…
Reference in a new issue