mirror of
https://github.com/psforever/PSF-LoginServer.git
synced 2026-03-09 07:20:30 +00:00
added initial AvatarStatisticsMessage packet and tests; gave an Enumeration type to AvtarImplant action field
This commit is contained in:
parent
2475b468b8
commit
d965b5b3c8
8 changed files with 254 additions and 44 deletions
|
|
@ -16,7 +16,7 @@ import net.psforever.packet.game.objectcreate.ObjectClass
|
|||
*/
|
||||
class ImplantTerminalInterfaceDefinition extends TerminalDefinition(ObjectClass.implant_terminal_interface) {
|
||||
Packet = new ImplantTerminalInterfaceConverter
|
||||
Name = "implante_terminal_interface"
|
||||
Name = "implant_terminal_interface"
|
||||
|
||||
private val implants : Map[String, ImplantDefinition] = Map (
|
||||
"advanced_regen" -> GlobalDefinitions.advanced_regen,
|
||||
|
|
|
|||
|
|
@ -468,7 +468,7 @@ object GamePacketOpcode extends Enumeration {
|
|||
case 0x7c => game.DismountBuildingMsg.decode
|
||||
case 0x7d => noDecoder(UnknownMessage125)
|
||||
case 0x7e => noDecoder(UnknownMessage126)
|
||||
case 0x7f => noDecoder(AvatarStatisticsMessage)
|
||||
case 0x7f => game.AvatarStatisticsMessage.decode
|
||||
|
||||
// OPCODES 0x80-8f
|
||||
case 0x80 => noDecoder(GenericObjectAction2Message)
|
||||
|
|
|
|||
|
|
@ -1,40 +1,48 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package net.psforever.packet.game
|
||||
|
||||
import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket}
|
||||
import net.psforever.types.ImplantType
|
||||
import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
|
||||
import scodec.Codec
|
||||
import scodec.codecs._
|
||||
|
||||
/**
|
||||
* Change the state of the implant.<br>
|
||||
* An `Enumeration` for all the actions that can be applied to implants and implant slots.
|
||||
*/
|
||||
object ImplantAction extends Enumeration {
|
||||
type Type = Value
|
||||
|
||||
val
|
||||
Add,
|
||||
Remove,
|
||||
Initialization,
|
||||
Activation,
|
||||
UnlockMessage,
|
||||
OutOfStamina
|
||||
= Value
|
||||
|
||||
implicit val codec = PacketHelpers.createEnumerationCodec(this, uintL(3))
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the state of the implant.
|
||||
* Spawn messages for certain implant-related events.<br>
|
||||
* <br>
|
||||
* The implant Second Wind is technically an invalid `ImplantType` for this packet.
|
||||
* This owes to the unique activation trigger for that implant - a near-death experience of ~0HP.
|
||||
* @see `ImplantType`
|
||||
* @param player_guid the player
|
||||
* @param action
|
||||
* 0 : add implant
|
||||
* with status = 0 to 9 (from ImplantType)
|
||||
* 1 : remove implant
|
||||
* seems work with any value in status
|
||||
* 2 : init implant
|
||||
* status : 0 to "uninit"
|
||||
* status : 1 to init
|
||||
* 3 : activate implant
|
||||
* status : 0 to desactivate
|
||||
* status : 1 to activate
|
||||
* 4 : number of implant slots unlocked
|
||||
* status : 0 = no implant slot
|
||||
* status : 1 = first implant slot + "implant message"
|
||||
* status : 2 or 3 = unlock second & third slots
|
||||
* 5 : out of stamina message
|
||||
* status : 0 to stop the lock
|
||||
* status : 1 to active the lock
|
||||
* @param action how to affect the implant or the slot
|
||||
* @param implantSlot : from 0 to 2
|
||||
* @param status : see action
|
||||
* @param status : a value that depends on context from `ImplantAction`:<br>
|
||||
* `Add` - 0-9 depending on the `ImplantType`<br>
|
||||
* `Remove` - any valid value; field is not significant to this action<br>
|
||||
* `Initialization` - 0 to revoke slot; 1 to allocate implant slot<br>
|
||||
* `Activation` - 0 to deactivate implant; 1 to activate implant<br>
|
||||
* `UnlockMessage` - 0-3 as an unlocked implant slot; display a message<br>
|
||||
* `OutOfStamina` - lock implant; 0 to lock; 1 to unlock; display a message
|
||||
*/
|
||||
final case class AvatarImplantMessage(player_guid : PlanetSideGUID,
|
||||
action : Int,
|
||||
action : ImplantAction.Value,
|
||||
implantSlot : Int,
|
||||
status : Int)
|
||||
extends PlanetSideGamePacket {
|
||||
|
|
@ -46,7 +54,7 @@ final case class AvatarImplantMessage(player_guid : PlanetSideGUID,
|
|||
object AvatarImplantMessage extends Marshallable[AvatarImplantMessage] {
|
||||
implicit val codec : Codec[AvatarImplantMessage] = (
|
||||
("player_guid" | PlanetSideGUID.codec) ::
|
||||
("action" | uintL(3)) ::
|
||||
("action" | ImplantAction.codec) ::
|
||||
("implantSlot" | uint2L) ::
|
||||
("status" | uint4L)
|
||||
).as[AvatarImplantMessage]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,105 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package net.psforever.packet.game
|
||||
|
||||
import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
|
||||
import scodec.{Attempt, Codec, Err}
|
||||
import scodec.codecs._
|
||||
import shapeless.{::, HNil}
|
||||
|
||||
import scala.annotation.switch
|
||||
|
||||
/**
|
||||
* na
|
||||
* @param unk1 na
|
||||
* @param unk2 na
|
||||
* @param unk3 na
|
||||
*/
|
||||
final case class Statistics(unk1 : Option[Int],
|
||||
unk2 : Option[Int],
|
||||
unk3 : List[Long])
|
||||
|
||||
/**
|
||||
* na
|
||||
* @param unk na
|
||||
* @param stats na
|
||||
*/
|
||||
final case class AvatarStatisticsMessage(unk : Int,
|
||||
stats : Statistics)
|
||||
extends PlanetSideGamePacket {
|
||||
type Packet = AvatarStatisticsMessage
|
||||
def opcode = GamePacketOpcode.AvatarStatisticsMessage
|
||||
def encode = AvatarStatisticsMessage.encode(this)
|
||||
}
|
||||
|
||||
object AvatarStatisticsMessage extends Marshallable[AvatarStatisticsMessage] {
|
||||
/**
|
||||
* na
|
||||
*/
|
||||
private val longCodec : Codec[Statistics] = ulong(32).hlist.exmap (
|
||||
{
|
||||
case n :: HNil =>
|
||||
Attempt.Successful(Statistics(None,None, List(n)))
|
||||
},
|
||||
{
|
||||
case Statistics(_, _, Nil) =>
|
||||
Attempt.Failure(Err("missing value (32-bit)"))
|
||||
|
||||
case Statistics(_, _, n) =>
|
||||
Attempt.Successful(n.head :: HNil)
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* na
|
||||
*/
|
||||
private val complexCodec : Codec[Statistics] = (
|
||||
uint(5) ::
|
||||
uintL(11) ::
|
||||
PacketHelpers.listOfNSized(8, uint32L)
|
||||
).exmap[Statistics] (
|
||||
{
|
||||
case a :: b :: c :: HNil =>
|
||||
Attempt.Successful(Statistics(Some(a), Some(b), c))
|
||||
},
|
||||
{
|
||||
case Statistics(None, _, _) =>
|
||||
Attempt.Failure(Err("missing value (5-bit)"))
|
||||
|
||||
case Statistics(_, None, _) =>
|
||||
Attempt.Failure(Err("missing value (11-bit)"))
|
||||
|
||||
case Statistics(a, b, c) =>
|
||||
if(c.length != 8) {
|
||||
Attempt.Failure(Err("list must have 8 entries"))
|
||||
}
|
||||
else {
|
||||
Attempt.Successful(a.get :: b.get :: c :: HNil)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* na
|
||||
* @param n na
|
||||
* @return na
|
||||
*/
|
||||
private def selectCodec(n : Int) : Codec[Statistics] = (n : @switch) match {
|
||||
case 2 | 3 =>
|
||||
longCodec
|
||||
case _ =>
|
||||
complexCodec
|
||||
}
|
||||
|
||||
implicit val codec : Codec[AvatarStatisticsMessage] = (
|
||||
("unk" | uint(3)) >>:~ { unk =>
|
||||
("stats" | selectCodec(unk)).hlist
|
||||
}).as[AvatarStatisticsMessage]
|
||||
}
|
||||
|
||||
object Statistics {
|
||||
def apply(unk : Long) : Statistics =
|
||||
Statistics(None, None, List(unk))
|
||||
|
||||
def apply(unk1 : Int, unk2 : Int, unk3 : List[Long]) : Statistics =
|
||||
Statistics(Some(unk1), Some(unk2), unk3)
|
||||
}
|
||||
|
|
@ -4,7 +4,6 @@ package game
|
|||
import org.specs2.mutable._
|
||||
import net.psforever.packet._
|
||||
import net.psforever.packet.game._
|
||||
import net.psforever.types.ImplantType
|
||||
import scodec.bits._
|
||||
|
||||
class AvatarImplantMessageTest extends Specification {
|
||||
|
|
@ -14,7 +13,7 @@ class AvatarImplantMessageTest extends Specification {
|
|||
PacketCoding.DecodePacket(string).require match {
|
||||
case AvatarImplantMessage(player_guid, unk1, unk2, implant) =>
|
||||
player_guid mustEqual PlanetSideGUID(3171)
|
||||
unk1 mustEqual 3
|
||||
unk1 mustEqual ImplantAction.Activation
|
||||
unk2 mustEqual 1
|
||||
implant mustEqual 1
|
||||
case _ =>
|
||||
|
|
@ -23,7 +22,7 @@ class AvatarImplantMessageTest extends Specification {
|
|||
}
|
||||
|
||||
"encode" in {
|
||||
val msg = AvatarImplantMessage(PlanetSideGUID(3171), 3, 1, 1)
|
||||
val msg = AvatarImplantMessage(PlanetSideGUID(3171), ImplantAction.Activation, 1, 1)
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string
|
||||
|
|
|
|||
79
common/src/test/scala/game/AvatarStatisticsMessageTest.scala
Normal file
79
common/src/test/scala/game/AvatarStatisticsMessageTest.scala
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package game
|
||||
|
||||
import org.specs2.mutable._
|
||||
import net.psforever.packet._
|
||||
import net.psforever.packet.game._
|
||||
import scodec.bits._
|
||||
|
||||
class AvatarStatisticsMessageTest extends Specification {
|
||||
val string_long = hex"7F 4 00000000 0"
|
||||
val string_complex = hex"7F 01 3C 40 20 00 00 00 C0 00 00 00 00 00 00 00 20 00 00 00 20 00 00 00 40 00 00 00 00 00 00 00 00 00 00 00"
|
||||
|
||||
"decode (long)" in {
|
||||
PacketCoding.DecodePacket(string_long).require match {
|
||||
case AvatarStatisticsMessage(unk, stats) =>
|
||||
unk mustEqual 2
|
||||
stats.unk1 mustEqual None
|
||||
stats.unk2 mustEqual None
|
||||
stats.unk3.length mustEqual 1
|
||||
stats.unk3.head mustEqual 0
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"decode (complex)" in {
|
||||
PacketCoding.DecodePacket(string_complex).require match {
|
||||
case AvatarStatisticsMessage(unk, stats) =>
|
||||
unk mustEqual 0
|
||||
stats.unk1 mustEqual Some(1)
|
||||
stats.unk2 mustEqual Some(572)
|
||||
stats.unk3.length mustEqual 8
|
||||
stats.unk3.head mustEqual 1
|
||||
stats.unk3(1) mustEqual 6
|
||||
stats.unk3(2) mustEqual 0
|
||||
stats.unk3(3) mustEqual 1
|
||||
stats.unk3(4) mustEqual 1
|
||||
stats.unk3(5) mustEqual 2
|
||||
stats.unk3(6) mustEqual 0
|
||||
stats.unk3(7) mustEqual 0
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"encode (long)" in {
|
||||
val msg = AvatarStatisticsMessage(2, Statistics(0L))
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string_long
|
||||
}
|
||||
|
||||
"encode (complex)" in {
|
||||
val msg = AvatarStatisticsMessage(0, Statistics(1, 572, List[Long](1,6,0,1,1,2,0,0)))
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string_complex
|
||||
}
|
||||
|
||||
"encode (failure; long; missing value)" in {
|
||||
val msg = AvatarStatisticsMessage(0, Statistics(None, None,List(0L)))
|
||||
PacketCoding.EncodePacket(msg).isFailure mustEqual true
|
||||
}
|
||||
|
||||
"encode (failure; complex; missing value (5-bit))" in {
|
||||
val msg = AvatarStatisticsMessage(0, Statistics(None, Some(572), List[Long](1,6,0,1,1,2,0,0)))
|
||||
PacketCoding.EncodePacket(msg).isFailure mustEqual true
|
||||
}
|
||||
|
||||
"encode (failure; complex; missing value (11-bit))" in {
|
||||
val msg = AvatarStatisticsMessage(0, Statistics(Some(1), None, List[Long](1,6,0,1,1,2,0,0)))
|
||||
PacketCoding.EncodePacket(msg).isFailure mustEqual true
|
||||
}
|
||||
|
||||
"encode (failure; complex; wrong number of list entries)" in {
|
||||
val msg = AvatarStatisticsMessage(0, Statistics(Some(1), None, List[Long](1,6,0,1)))
|
||||
PacketCoding.EncodePacket(msg).isFailure mustEqual true
|
||||
}
|
||||
}
|
||||
|
|
@ -56,10 +56,11 @@ object Maps {
|
|||
LocalObject(ServerObjectBuilder(373, Door.Constructor))
|
||||
|
||||
LocalObject(ServerObjectBuilder(520, ImplantTerminalMech.Constructor)) //Hart B
|
||||
LocalObject(ServerObjectBuilder(1081, Terminal.Constructor(implant_terminal_interface))) //tube 520
|
||||
LocalObject(ServerObjectBuilder(853, Terminal.Constructor(order_terminal)))
|
||||
LocalObject(ServerObjectBuilder(855, Terminal.Constructor(order_terminal)))
|
||||
LocalObject(ServerObjectBuilder(860, Terminal.Constructor(order_terminal)))
|
||||
LocalObject(ServerObjectBuilder(1081, Terminal.Constructor(implant_terminal_interface))) //tube 520
|
||||
TerminalToInterface(520, 1081)
|
||||
|
||||
LocalBuilding(2, FoundationBuilder(Building.Structure)) //HART building C
|
||||
LocalObject(ServerObjectBuilder(186, Terminal.Constructor(cert_terminal)))
|
||||
|
|
@ -128,7 +129,14 @@ object Maps {
|
|||
ObjectToBuilding(843, 2)
|
||||
ObjectToBuilding(844, 2)
|
||||
ObjectToBuilding(845, 2)
|
||||
TerminalToInterface(520, 1081)
|
||||
ObjectToBuilding(1082, 2)
|
||||
ObjectToBuilding(1083, 2)
|
||||
ObjectToBuilding(1084, 2)
|
||||
ObjectToBuilding(1085, 2)
|
||||
ObjectToBuilding(1086, 2)
|
||||
ObjectToBuilding(1087, 2)
|
||||
ObjectToBuilding(1088, 2)
|
||||
ObjectToBuilding(1089, 2)
|
||||
TerminalToInterface(522, 1082)
|
||||
TerminalToInterface(523, 1083)
|
||||
TerminalToInterface(524, 1084)
|
||||
|
|
@ -178,8 +186,6 @@ object Maps {
|
|||
ObjectToBuilding(706, 77)
|
||||
TerminalToSpawnPad(1063, 706)
|
||||
|
||||
//ObjectToBuilding(1081, ?)
|
||||
//ObjectToBuilding(520, ?)
|
||||
ObjectToBuilding(853, 2) //TODO check building_id
|
||||
ObjectToBuilding(855, 2) //TODO check building_id
|
||||
ObjectToBuilding(860, 2) //TODO check building_id
|
||||
|
|
|
|||
|
|
@ -801,7 +801,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
if(interface.contains(terminal_guid.guid) && slotNumber.isDefined) {
|
||||
val slot = slotNumber.get
|
||||
log.info(s"$message - put in slot $slot")
|
||||
sendResponse(AvatarImplantMessage(tplayer.GUID, 0, slot, implant_type.id))
|
||||
sendResponse(AvatarImplantMessage(tplayer.GUID, ImplantAction.Add, slot, implant_type.id))
|
||||
sendResponse(ItemTransactionResultMessage(terminal_guid, TransactionType.Learn, true))
|
||||
}
|
||||
else {
|
||||
|
|
@ -836,7 +836,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
if(interface.contains(terminal_guid.guid) && slotNumber.isDefined) {
|
||||
val slot = slotNumber.get
|
||||
log.info(s"$tplayer is selling $implant_type - take from slot $slot")
|
||||
sendResponse(AvatarImplantMessage(tplayer.GUID, 1, slot, 0))
|
||||
sendResponse(AvatarImplantMessage(tplayer.GUID, ImplantAction.Remove, slot, 0))
|
||||
sendResponse(ItemTransactionResultMessage(terminal_guid, TransactionType.Sell, true))
|
||||
}
|
||||
else {
|
||||
|
|
@ -982,10 +982,10 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
case Zone.ClientInitialization(zone) =>
|
||||
val continentNumber = zone.Number
|
||||
val poplist = LivePlayerList.ZonePopulation(continentNumber, _ => true)
|
||||
val popBO = 0 //TODO black ops test (partition)
|
||||
val popTR = poplist.count(_.Faction == PlanetSideEmpire.TR)
|
||||
val popNC = poplist.count(_.Faction == PlanetSideEmpire.NC)
|
||||
val popVS = poplist.count(_.Faction == PlanetSideEmpire.VS)
|
||||
val popBO = poplist.size - popTR - popNC - popVS
|
||||
|
||||
zone.Buildings.foreach({ case(id, building) => initBuilding(continentNumber, id, building) })
|
||||
sendResponse(ZonePopulationUpdateMessage(continentNumber, 414, 138, popTR, 138, popNC, 138, popVS, 138, popBO))
|
||||
|
|
@ -1020,18 +1020,30 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
val guid = tplayer.GUID
|
||||
LivePlayerList.Assign(continent.Number, sessionId, guid)
|
||||
sendResponse(SetCurrentAvatarMessage(guid,0,0))
|
||||
|
||||
(0 until DetailedCharacterData.numberOfImplantSlots(tplayer.BEP)).foreach(slot => {
|
||||
sendResponse(AvatarImplantMessage(guid, ImplantAction.Initialization, slot, 1)) //init implant slot
|
||||
sendResponse(AvatarImplantMessage(guid, ImplantAction.Activation, slot, 0)) //deactivate implant
|
||||
//TODO: if this implant is Installed but does not have shortcut, add to a free slot or write over slot 61/62/63
|
||||
})
|
||||
|
||||
sendResponse(PlanetsideAttributeMessage(PlanetSideGUID(0), 82, 0))
|
||||
//TODO: if Medkit does not have shortcut, add to a free slot or write over slot 64
|
||||
sendResponse(CreateShortcutMessage(guid, 1, 0, true, Shortcut.MEDKIT))
|
||||
sendResponse(ChangeShortcutBankMessage(guid, 0))
|
||||
//FavoritesMessage
|
||||
sendResponse(SetChatFilterMessage(ChatChannel.Local, false, ChatChannel.values.toList)) //TODO will not always be "on"
|
||||
sendResponse(SetChatFilterMessage(ChatChannel.Local, false, ChatChannel.values.toList)) //TODO will not always be "on" like this
|
||||
sendResponse(AvatarDeadStateMessage(DeadState.Nothing, 0,0, tplayer.Position, 0, true))
|
||||
sendResponse(PlanetsideAttributeMessage(guid, 53, 1))
|
||||
sendResponse(AvatarSearchCriteriaMessage(guid, List(0,0,0,0,0,0)))
|
||||
(1 to 73).foreach( i => {
|
||||
(1 to 73).foreach(i => {
|
||||
sendResponse(PlanetsideAttributeMessage(PlanetSideGUID(i), 67, 0))
|
||||
})
|
||||
//AvatarStatisticsMessage
|
||||
(0 to 30).foreach(i => { //TODO 30 for a new character only?
|
||||
sendResponse(AvatarStatisticsMessage(2, Statistics(0L)))
|
||||
})
|
||||
//AvatarAwardMessage
|
||||
//DisplayAwardMessage
|
||||
//SquadDefinitionActionMessage and SquadDetailDefinitionUpdateMessage
|
||||
//MapObjectStateBlockMessage and ObjectCreateMessage
|
||||
//TacticsMessage
|
||||
|
|
@ -2023,11 +2035,12 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
log.info("ItemTransaction: " + msg)
|
||||
continent.GUID(terminal_guid) match {
|
||||
case Some(term : Terminal) =>
|
||||
if(player.Faction == term.Faction) {
|
||||
term.Actor ! Terminal.Request(player, msg)
|
||||
}
|
||||
case Some(obj : PlanetSideGameObject) => ;
|
||||
case None => ;
|
||||
log.info(s"ItemTransaction: ${term.Definition.Name} found")
|
||||
term.Actor ! Terminal.Request(player, msg)
|
||||
case Some(obj : PlanetSideGameObject) =>
|
||||
log.error(s"ItemTransaction: $obj is not a terminal")
|
||||
case _ =>
|
||||
log.error(s"ItemTransaction: $terminal_guid does not exist")
|
||||
}
|
||||
|
||||
case msg @ FavoritesRequest(player_guid, unk, action, line, label) =>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue