diff --git a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala
index 21bfbd07..4fccc47f 100644
--- a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala
+++ b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala
@@ -6,7 +6,7 @@ import net.psforever.objects.definition.converter.{CommandDetonaterConverter, Lo
import net.psforever.objects.equipment.CItem.DeployedItem
import net.psforever.objects.equipment._
import net.psforever.objects.inventory.InventoryTile
-import net.psforever.objects.terminals.OrderTerminalDefinition
+import net.psforever.objects.terminals.{CertTerminalDefinition, OrderTerminalDefinition}
import net.psforever.packet.game.objectcreate.ObjectClass
import net.psforever.types.PlanetSideEmpire
@@ -1239,5 +1239,7 @@ object GlobalDefinitions {
fury.TrunkOffset = 30
val
- orderTerminal = new OrderTerminalDefinition
+ order_terminal = new OrderTerminalDefinition
+ val
+ cert_terminal = new CertTerminalDefinition
}
diff --git a/common/src/main/scala/net/psforever/objects/terminals/CertTerminalDefinition.scala b/common/src/main/scala/net/psforever/objects/terminals/CertTerminalDefinition.scala
new file mode 100644
index 00000000..cc617ecb
--- /dev/null
+++ b/common/src/main/scala/net/psforever/objects/terminals/CertTerminalDefinition.scala
@@ -0,0 +1,100 @@
+// Copyright (c) 2017 PSForever
+package net.psforever.objects.terminals
+
+import net.psforever.objects.Player
+import net.psforever.packet.game.ItemTransactionMessage
+import net.psforever.types.CertificationType
+
+/**
+ * The definition for any `Terminal` that is of a type "cert_terminal" (certification terminal).
+ * `Learn` and `Sell` `CertificationType` entries, gaining access to different `Equipment` and `Vehicles`.
+ */
+class CertTerminalDefinition extends TerminalDefinition(171) {
+ Name = "cert_terminal"
+
+ /**
+ * The certifications available.
+ * All entries are listed on page (tab) number 0.
+ */
+ private val certificationList : Map[String, (CertificationType.Value, Int)] = Map(
+ "medium_assault" -> (CertificationType.MediumAssault, 2),
+ "reinforced_armo" -> (CertificationType.ReinforcedExoSuit, 3),
+ "quad_all" -> (CertificationType.ATV, 1),
+ "switchblade" -> (CertificationType.Switchblade, 1),
+ "harasser" -> (CertificationType.Harasser, 1),
+ "anti_vehicular" -> (CertificationType.AntiVehicular, 3),
+ "heavy_assault" -> (CertificationType.HeavyAssault, 4),
+ "sniper" -> (CertificationType.Sniping, 3),
+ "special_assault" -> (CertificationType.SpecialAssault, 3),
+ "special_assault_2" -> (CertificationType.EliteAssault, 1),
+ "infiltration_suit" -> (CertificationType.InfiltrationSuit, 2),
+ "max_anti_personnel" -> (CertificationType.AIMAX, 3),
+ "max_anti_vehicular" -> (CertificationType.AVMAX, 3),
+ "max_anti_aircraft" -> (CertificationType.AAMAX, 2),
+ "max_all" -> (CertificationType.UniMAX, 6),
+ "air_cavalry_scout" -> (CertificationType.AirCavalryScout, 3),
+ "air_cavalry_assault" -> (CertificationType.AirCavalryAssault, 2),
+ "air_cavalry_interceptor" -> (CertificationType.AirCavalryInterceptor, 2),
+ "air_support" -> (CertificationType.AirSupport, 3),
+ "gunship" -> (CertificationType.GalaxyGunship, 2),
+ "phantasm" -> (CertificationType.Phantasm, 3),
+ "armored_assault1" -> (CertificationType.ArmoredAssault1, 2),
+ "armored_assault2" -> (CertificationType.ArmoredAssault2, 1),
+ "flail" -> (CertificationType.Flail, 1),
+ "assault_buggy" -> (CertificationType.AssaultBuggy, 3),
+ "ground_support" -> (CertificationType.GroundSupport, 2),
+ "ground_transport" -> (CertificationType.GroundTransport, 2),
+ "light_scout" -> (CertificationType.LightScout, 5),
+ "Repair" -> (CertificationType.Engineering, 3),
+ "combat_engineering" -> (CertificationType.CombatEngineering, 2),
+ "ce_offense" -> (CertificationType.AssaultEngineering, 3),
+ "ce_defense" -> (CertificationType.FortificationEngineering, 3),
+ "ce_advanced" -> (CertificationType.AdvancedEngineering, 5),
+ "Hacking" -> (CertificationType.Hacking, 3),
+ "advanced_hacking" -> (CertificationType.AdvancedHacking, 2),
+ "expert_hacking" -> (CertificationType.ExpertHacking, 2),
+ "virus_hacking" -> (CertificationType.DataCorruption, 3),
+ "electronics_expert" -> (CertificationType.ElectronicsExpert, 4),
+ "Medical" -> (CertificationType.Medical, 3),
+ "advanced_medical" -> (CertificationType.AdvancedMedical, 2)
+ //TODO bfr certification entries
+ )
+
+ /**
+ * Process a `TransactionType.Learn` action by the user.
+ * @param player the player
+ * @param msg the original packet carrying the request
+ * @return an actionable message that explains how to process the request
+ */
+ def Buy(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = { //Learn
+ certificationList.get(msg.item_name) match {
+ case Some((cert, cost)) =>
+ Terminal.LearnCertification(cert, cost)
+ case None =>
+ Terminal.NoDeal()
+ }
+ }
+
+ /**
+ * Process a `TransactionType.Sell` action by the user.
+ * @param player the player
+ * @param msg the original packet carrying the request
+ * @return an actionable message that explains how to process the request
+ */
+ def Sell(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = {
+ certificationList.get(msg.item_name) match {
+ case Some((cert, cost)) =>
+ Terminal.SellCertification(cert, cost)
+ case None =>
+ Terminal.NoDeal()
+ }
+ }
+
+ /**
+ * This action is not supported by this type of `Terminal`.
+ * @param player the player
+ * @param msg the original packet carrying the request
+ * @return `Terminal.NoEvent` always
+ */
+ def Loadout(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = Terminal.NoDeal()
+}
diff --git a/common/src/main/scala/net/psforever/objects/terminals/OrderTerminalDefinition.scala b/common/src/main/scala/net/psforever/objects/terminals/OrderTerminalDefinition.scala
index ee431ff7..0eb171fc 100644
--- a/common/src/main/scala/net/psforever/objects/terminals/OrderTerminalDefinition.scala
+++ b/common/src/main/scala/net/psforever/objects/terminals/OrderTerminalDefinition.scala
@@ -10,6 +10,11 @@ import net.psforever.packet.game.ItemTransactionMessage
import scala.annotation.switch
+/**
+ * The definition for any `Terminal` that is of a type "order_terminal".
+ * `Buy` and `Sell` `Equipment` items and `AmmoBox` items.
+ * Change `ExoSuitType` and retrieve `InfantryLoadout` entries.
+ */
class OrderTerminalDefinition extends TerminalDefinition(612) {
Name = "order_terminal"
@@ -21,10 +26,10 @@ class OrderTerminalDefinition extends TerminalDefinition(612) {
/**
* Process a `TransactionType.Buy` action by the user.
+ * Either attempt to purchase equipment or attempt to switch directly to a different exo-suit.
* @param player the player
* @param msg the original packet carrying the request
- * @return an actionable message that explains how to process the request;
- * either you attempt to purchase equipment or attempt to switch directly to a different exo-suit
+ * @return an actionable message that explains how to process the request
*/
def Buy(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = {
(msg.item_page : @switch) match {
@@ -64,6 +69,7 @@ class OrderTerminalDefinition extends TerminalDefinition(612) {
/**
* Process a `TransactionType.Sell` action by the user.
* There is no specific `order_terminal` tab associated with this action.
+ * Additionally, the equipment to be sold ia almost always in the player's `FreeHand` slot.
* Selling `Equipment` is always permitted.
* @param player the player
* @param msg the original packet carrying the request
@@ -81,7 +87,7 @@ class OrderTerminalDefinition extends TerminalDefinition(612) {
* @param msg the original packet carrying the request
* @return an actionable message that explains how to process the request
*/
- def InfantryLoadout(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = {
+ def Loadout(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = {
if(msg.item_page == 4) { //Favorites tab
player.LoadLoadout(msg.unk1) match {
case Some(loadout) =>
diff --git a/common/src/main/scala/net/psforever/objects/terminals/Terminal.scala b/common/src/main/scala/net/psforever/objects/terminals/Terminal.scala
index 81f464fe..f5d0bd53 100644
--- a/common/src/main/scala/net/psforever/objects/terminals/Terminal.scala
+++ b/common/src/main/scala/net/psforever/objects/terminals/Terminal.scala
@@ -66,14 +66,14 @@ class Terminal(tdef : TerminalDefinition) extends PlanetSideGameObject {
*/
def Request(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = {
msg.transaction_type match {
- case TransactionType.Buy =>
+ case TransactionType.Buy | TransactionType.Learn =>
tdef.Buy(player, msg)
case TransactionType.Sell =>
tdef.Sell(player, msg)
case TransactionType.InfantryLoadout =>
- tdef.InfantryLoadout(player, msg)
+ tdef.Loadout(player, msg)
case _ =>
Terminal.NoDeal()
@@ -132,6 +132,12 @@ object Terminal {
*/
//TODO if there are exceptions, find them
final case class SellEquipment() extends Exchange
+
+ import net.psforever.types.CertificationType
+ final case class LearnCertification(cert : CertificationType.Value, cost : Int) extends Exchange
+
+ final case class SellCertification(cert : CertificationType.Value, cost : Int) extends Exchange
+
/**
* Recover a former exo-suit and `Equipment` configuration that the `Player` possessed.
* A result of a processed request.
diff --git a/common/src/main/scala/net/psforever/objects/terminals/TerminalDefinition.scala b/common/src/main/scala/net/psforever/objects/terminals/TerminalDefinition.scala
index a82fb739..a14836fa 100644
--- a/common/src/main/scala/net/psforever/objects/terminals/TerminalDefinition.scala
+++ b/common/src/main/scala/net/psforever/objects/terminals/TerminalDefinition.scala
@@ -7,8 +7,6 @@ import net.psforever.objects.equipment.Equipment
import net.psforever.packet.game.ItemTransactionMessage
import net.psforever.types.ExoSuitType
-import scala.collection.immutable.HashMap
-
/**
* The definition for any `Terminal`.
* @param objectId the object's identifier number
@@ -17,7 +15,7 @@ abstract class TerminalDefinition(objectId : Int) extends ObjectDefinition(objec
Name = "terminal"
/**
- * The unimplemented functionality for this `Terminal`'s `TransactionType.Buy` activity.
+ * The unimplemented functionality for this `Terminal`'s `TransactionType.Buy` and `TransactionType.Learn` activity.
*/
def Buy(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange
@@ -29,7 +27,7 @@ abstract class TerminalDefinition(objectId : Int) extends ObjectDefinition(objec
/**
* The unimplemented functionality for this `Terminal`'s `TransactionType.InfantryLoadout` activity.
*/
- def InfantryLoadout(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange
+ def Loadout(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange
/**
* A `Map` of information for changing exo-suits.
@@ -49,7 +47,7 @@ abstract class TerminalDefinition(objectId : Int) extends ObjectDefinition(objec
* key - an identification string sent by the client
* value - a curried function that builds the object
*/
- protected val infantryAmmunition : HashMap[String, ()=>Equipment] = HashMap(
+ protected val infantryAmmunition : Map[String, ()=>Equipment] = Map(
"9mmbullet" -> MakeAmmoBox(bullet_9mm),
"9mmbullet_AP" -> MakeAmmoBox(bullet_9mm_AP),
"shotgun_shell" -> MakeAmmoBox(shotgun_shell),
@@ -75,7 +73,7 @@ abstract class TerminalDefinition(objectId : Int) extends ObjectDefinition(objec
* key - an identification string sent by the client
* value - a curried function that builds the object
*/
- protected val supportAmmunition : HashMap[String, ()=>Equipment] = HashMap(
+ protected val supportAmmunition : Map[String, ()=>Equipment] = Map(
"health_canister" -> MakeAmmoBox(health_canister),
"armor_canister" -> MakeAmmoBox(armor_canister),
"upgrade_canister" -> MakeAmmoBox(upgrade_canister)
@@ -86,7 +84,7 @@ abstract class TerminalDefinition(objectId : Int) extends ObjectDefinition(objec
* key - an identification string sent by the client
* value - a curried function that builds the object
*/
- protected val vehicleAmmunition : HashMap[String, ()=>Equipment] = HashMap(
+ protected val vehicleAmmunition : Map[String, ()=>Equipment] = Map(
"35mmbullet" -> MakeAmmoBox(bullet_35mm),
"hellfire_ammo" -> MakeAmmoBox(hellfire_ammo),
"liberator_bomb" -> MakeAmmoBox(liberator_bomb),
@@ -129,7 +127,7 @@ abstract class TerminalDefinition(objectId : Int) extends ObjectDefinition(objec
* key - an identification string sent by the client
* value - a curried function that builds the object
*/
- protected val infantryWeapons : HashMap[String, ()=>Equipment] = HashMap(
+ protected val infantryWeapons : Map[String, ()=>Equipment] = Map(
"ilc9" -> MakeTool(ilc9, bullet_9mm),
"repeater" -> MakeTool(repeater, bullet_9mm),
"isp" -> MakeTool(isp, shotgun_shell), //amp
@@ -173,7 +171,7 @@ abstract class TerminalDefinition(objectId : Int) extends ObjectDefinition(objec
* key - an identification string sent by the client
* value - a curried function that builds the object
*/
- protected val supportWeapons : HashMap[String, ()=>Equipment] = HashMap(
+ protected val supportWeapons : Map[String, ()=>Equipment] = Map(
"medkit" -> MakeKit(medkit),
"super_medkit" -> MakeKit(super_medkit),
"super_armorkit" -> MakeKit(super_armorkit),
diff --git a/common/src/main/scala/net/psforever/packet/game/PlanetsideAttributeMessage.scala b/common/src/main/scala/net/psforever/packet/game/PlanetsideAttributeMessage.scala
index 125e521d..0b640c68 100644
--- a/common/src/main/scala/net/psforever/packet/game/PlanetsideAttributeMessage.scala
+++ b/common/src/main/scala/net/psforever/packet/game/PlanetsideAttributeMessage.scala
@@ -20,7 +20,7 @@ import scodec.codecs._
* `17 - BEP. Value seems to be the same as BattleExperienceMessage`
* `18 - CEP.`
* `19 - Anchors. Value is 0 to disengage, 1 to engage.`
- * `24 - Certifications with value :`
+ * `24 - Learn certifications with value :`
* 01 : Medium Assault
* 02 : Heavy Assault
* 03 : Special Assault
@@ -66,6 +66,7 @@ import scodec.codecs._
* 43 : Fortification Engineering
* 44 : Assault Engineering
* 45 : Advanced Engineering (= Fortification Engineering + Assault Engineering) Must have Combat Engineering
+ * `25 - Forget certifications (same order as 24)`
* `29 - Visible ?! That's not the cloaked effect, Maybe for spectator mode ?. Value is 0 to visible, 1 to invisible.`
* `31 - Info under avatar name : 0 = LFS, 1 = Looking For Squad Members`
* `32 - Info under avatar name : 0 = Looking For Squad Members, 1 = LFS`
diff --git a/common/src/main/scala/net/psforever/types/CertificationType.scala b/common/src/main/scala/net/psforever/types/CertificationType.scala
index d06e2d19..ea4ca3ab 100644
--- a/common/src/main/scala/net/psforever/types/CertificationType.scala
+++ b/common/src/main/scala/net/psforever/types/CertificationType.scala
@@ -30,9 +30,9 @@ object CertificationType extends Enumeration {
AntiVehicular,
Sniping,
EliteAssault,
- AirCalvaryScout,
- AirCalvaryInterceptor,
- AirCalvaryAssault,
+ AirCavalryScout,
+ AirCavalryInterceptor,
+ AirCavalryAssault,
//10
AirSupport,
ATV,
diff --git a/pslogin/src/main/scala/PsLogin.scala b/pslogin/src/main/scala/PsLogin.scala
index c1403fc4..f71cce11 100644
--- a/pslogin/src/main/scala/PsLogin.scala
+++ b/pslogin/src/main/scala/PsLogin.scala
@@ -222,9 +222,12 @@ object PsLogin {
def createContinents() : List[Zone] = {
val map13 = new ZoneMap("map13") {
import net.psforever.objects.GlobalDefinitions._
- LocalObject(TerminalObjectBuilder(orderTerminal, 853))
- LocalObject(TerminalObjectBuilder(orderTerminal, 855))
- LocalObject(TerminalObjectBuilder(orderTerminal, 860))
+ LocalObject(TerminalObjectBuilder(cert_terminal, 186))
+ LocalObject(TerminalObjectBuilder(cert_terminal, 187))
+ LocalObject(TerminalObjectBuilder(cert_terminal, 188))
+ LocalObject(TerminalObjectBuilder(order_terminal, 853))
+ LocalObject(TerminalObjectBuilder(order_terminal, 855))
+ LocalObject(TerminalObjectBuilder(order_terminal, 860))
}
val home3 = Zone("home3", map13, 13)
diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala
index 07393e4c..d759a8f3 100644
--- a/pslogin/src/main/scala/WorldSessionActor.scala
+++ b/pslogin/src/main/scala/WorldSessionActor.scala
@@ -312,6 +312,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.EquipmentOnGround(tplayer.GUID, pos, orient, obj))
})
}
+ sendResponse(PacketCoding.CreateGamePacket(0, ItemTransactionResultMessage (msg.terminal_guid, TransactionType.Buy, true)))
case Terminal.BuyEquipment(item) => ;
tplayer.Fit(item) match {
@@ -382,6 +383,31 @@ class WorldSessionActor extends Actor with MDCContextAware {
PutEquipmentInSlot(tplayer, entry.obj, entry.start)
})
//TODO drop items on ground
+ sendResponse(PacketCoding.CreateGamePacket(0, ItemTransactionResultMessage (msg.terminal_guid, TransactionType.InfantryLoadout, true)))
+
+ case Terminal.LearnCertification(cert, cost) =>
+ if(!player.Certifications.contains(cert)) {
+ log.info(s"$tplayer is learning the $cert certification for $cost points")
+ tplayer.Certifications += cert
+ sendResponse(PacketCoding.CreateGamePacket(0, PlanetsideAttributeMessage(tplayer.GUID, 24, cert.id.toLong)))
+ sendResponse(PacketCoding.CreateGamePacket(0, ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Learn, true)))
+ }
+ else {
+ log.warn(s"$tplayer already knows the $cert certification, so he can't learn it")
+ sendResponse(PacketCoding.CreateGamePacket(0, ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Learn, false)))
+ }
+
+ case Terminal.SellCertification(cert, cost) =>
+ if(player.Certifications.contains(cert)) {
+ log.info(s"$tplayer is forgetting the $cert certification for $cost points")
+ tplayer.Certifications -= cert
+ sendResponse(PacketCoding.CreateGamePacket(0, PlanetsideAttributeMessage(tplayer.GUID, 25, cert.id.toLong)))
+ sendResponse(PacketCoding.CreateGamePacket(0, ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Sell, true)))
+ }
+ else {
+ log.warn(s"$tplayer doesn't know what a $cert certification is, so he can't forget it")
+ sendResponse(PacketCoding.CreateGamePacket(0, ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Learn, false)))
+ }
case Terminal.NoDeal() =>
log.warn(s"$tplayer made a request but the terminal rejected the order $msg")
@@ -477,6 +503,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
LivePlayerList.Assign(continent.Number, sessionId, guid)
sendResponse(PacketCoding.CreateGamePacket(0, SetCurrentAvatarMessage(guid,0,0)))
sendResponse(PacketCoding.CreateGamePacket(0, CreateShortcutMessage(guid, 1, 0, true, Shortcut.MEDKIT)))
+ sendResponse(PacketCoding.CreateGamePacket(0, ChatMsg(ChatMessageType.CMT_EXPANSIONS, true, "", "1 on", None))) //CC on
case Zone.ItemFromGround(tplayer, item) =>
val obj_guid = item.GUID