mirror of
https://github.com/2revoemag/PSF-BotServer.git
synced 2026-01-20 02:24:45 +00:00
proper remote projectile handling; allowances for the Decimator alt-fire mode; initial DamageMessage packet and tests; initial DamageFeedbackMessage packet and tests
This commit is contained in:
parent
043512d6a3
commit
693a3a5d78
|
|
@ -3076,9 +3076,11 @@ object GlobalDefinitions {
|
|||
phoenix_missile_guided_projectile.ProjectileDamageType = DamageType.Splash
|
||||
phoenix_missile_guided_projectile.InitialVelocity = 0
|
||||
phoenix_missile_guided_projectile.Lifespan = 3f
|
||||
//not naturally a remote projectile, but being governed as one for convenience
|
||||
phoenix_missile_guided_projectile.ExistsOnRemoteClients = true
|
||||
phoenix_missile_guided_projectile.RemoteClientData = (39577, 201) //hunter_seeker_missile_projectile data
|
||||
phoenix_missile_guided_projectile.RemoteClientData = (0,63)
|
||||
phoenix_missile_guided_projectile.Packet = projectileConverter
|
||||
//
|
||||
ProjectileDefinition.CalculateDerivedFields(phoenix_missile_guided_projectile)
|
||||
|
||||
phoenix_missile_projectile.Name = "phoenix_missile_projectile"
|
||||
|
|
|
|||
|
|
@ -3,14 +3,14 @@ package net.psforever.objects.definition.converter
|
|||
|
||||
import net.psforever.objects.ballistics.Projectile
|
||||
import net.psforever.packet.game.PlanetSideGUID
|
||||
import net.psforever.packet.game.objectcreate.{CommonFieldData, CommonFieldDataWithPlacement, FlightPhysics, PlacementData, TrackedProjectileData}
|
||||
import net.psforever.packet.game.objectcreate.{CommonFieldData, CommonFieldDataWithPlacement, FlightPhysics, PlacementData, RemoteProjectileData}
|
||||
|
||||
import scala.util.{Failure, Success, Try}
|
||||
|
||||
class ProjectileConverter extends ObjectCreateConverter[Projectile]() {
|
||||
override def ConstructorData(obj : Projectile) : Try[TrackedProjectileData] = {
|
||||
override def ConstructorData(obj : Projectile) : Try[RemoteProjectileData] = {
|
||||
Success(
|
||||
TrackedProjectileData(
|
||||
RemoteProjectileData(
|
||||
CommonFieldDataWithPlacement(
|
||||
PlacementData(
|
||||
obj.Position,
|
||||
|
|
@ -38,6 +38,6 @@ class ProjectileConverter extends ObjectCreateConverter[Projectile]() {
|
|||
)
|
||||
}
|
||||
|
||||
override def DetailedConstructorData(obj : Projectile) : Try[TrackedProjectileData] =
|
||||
override def DetailedConstructorData(obj : Projectile) : Try[RemoteProjectileData] =
|
||||
Failure(new Exception("ProjectileConverter should not be used to generate detailed projectile data (nothing should)"))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -329,7 +329,7 @@ object GamePacketOpcode extends Enumeration {
|
|||
case 0x08 => game.PlayerStateMessage.decode
|
||||
case 0x09 => game.HitMessage.decode
|
||||
case 0x0a => game.HitHint.decode
|
||||
case 0x0b => noDecoder(DamageMessage)
|
||||
case 0x0b => game.DamageMessage.decode
|
||||
case 0x0c => game.DestroyMessage.decode
|
||||
case 0x0d => game.ReloadMessage.decode
|
||||
case 0x0e => game.MountVehicleMsg.decode
|
||||
|
|
@ -462,7 +462,7 @@ object GamePacketOpcode extends Enumeration {
|
|||
case 0x78 => game.OxygenStateMessage.decode
|
||||
case 0x79 => noDecoder(TradeMessage)
|
||||
case 0x7a => noDecoder(UnknownMessage122)
|
||||
case 0x7b => noDecoder(DamageFeedbackMessage)
|
||||
case 0x7b => game.DamageFeedbackMessage.decode
|
||||
case 0x7c => game.DismountBuildingMsg.decode
|
||||
case 0x7d => noDecoder(UnknownMessage125)
|
||||
case 0x7e => noDecoder(UnknownMessage126)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,72 @@
|
|||
// Copyright (c) 2019 PSForever
|
||||
package net.psforever.packet.game
|
||||
|
||||
import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
|
||||
import scodec.Codec
|
||||
import scodec.codecs._
|
||||
import shapeless.{::, HNil}
|
||||
|
||||
final case class DamageFeedbackMessage(unk1 : Int,
|
||||
unk2 : Boolean,
|
||||
unk2a : Option[PlanetSideGUID],
|
||||
unk2b : Option[String],
|
||||
unk2c : Option[Int],
|
||||
unk3 : Boolean,
|
||||
unk3a : Option[PlanetSideGUID],
|
||||
unk3b : Option[String],
|
||||
unk3c : Option[Int],
|
||||
unk3d : Option[Int],
|
||||
unk4 : Int,
|
||||
unk5 : Long,
|
||||
unk6 : Int)
|
||||
extends PlanetSideGamePacket {
|
||||
assert(
|
||||
if(unk2a.nonEmpty) unk2b.isEmpty && unk2c.isEmpty
|
||||
else if(unk2b.nonEmpty) unk2 && unk2a.isEmpty && unk2c.isEmpty
|
||||
else unk2a.isEmpty && !unk2 && unk2b.isEmpty && unk2c.nonEmpty
|
||||
)
|
||||
assert(
|
||||
if(unk3a.nonEmpty) unk3b.isEmpty && unk3c.isEmpty
|
||||
else if(unk3b.nonEmpty) unk3 && unk3a.isEmpty && unk3c.isEmpty
|
||||
else unk3a.isEmpty && !unk3 && unk3b.isEmpty && unk3c.nonEmpty
|
||||
)
|
||||
assert(unk3a.isEmpty == unk3d.nonEmpty)
|
||||
|
||||
type Packet = DamageFeedbackMessage
|
||||
def opcode = GamePacketOpcode.DamageFeedbackMessage
|
||||
def encode = DamageFeedbackMessage.encode(this)
|
||||
}
|
||||
|
||||
object DamageFeedbackMessage extends Marshallable[DamageFeedbackMessage] {
|
||||
implicit val codec : Codec[DamageFeedbackMessage] = (
|
||||
("unk1" | uint4) ::
|
||||
(bool >>:~ { u2 =>
|
||||
bool >>:~ { u3 =>
|
||||
("unk2a" | conditional(u2, PlanetSideGUID.codec)) ::
|
||||
(("unk2b" | conditional(!u2 && u3, PacketHelpers.encodedWideStringAligned(6))) >>:~ { u2b =>
|
||||
("unk2c" | conditional(!(u2 && u3), uintL(11))) ::
|
||||
(bool >>:~ { u5 =>
|
||||
bool >>:~ { u6 =>
|
||||
("unk3a" | conditional(u5, PlanetSideGUID.codec)) ::
|
||||
("unk3b" | conditional(!u5 && u6, PacketHelpers.encodedWideStringAligned( if(u2b.nonEmpty) 3 else 1 ))) ::
|
||||
("unk3c" | conditional(!(u5 && u6), uintL(11))) ::
|
||||
("unk3d" | conditional(!u5, uint2)) ::
|
||||
("unk4" | uint(3)) ::
|
||||
("unk5" | uint32L) ::
|
||||
("unk6" | uint2)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
).xmap[DamageFeedbackMessage] (
|
||||
{
|
||||
case u1 :: _ :: u2 :: u2a :: u2b :: u2c :: _ :: u3 :: u3a :: u3b :: u3c :: u3d :: u4 :: u5 :: u6 :: HNil =>
|
||||
DamageFeedbackMessage(u1, u2, u2a, u2b, u2c, u3, u3a, u3b, u3c, u3d, u4, u5, u6)
|
||||
},
|
||||
{
|
||||
case DamageFeedbackMessage(u1, u2, u2a, u2b, u2c, u3, u3a, u3b, u3c, u3d, u4, u5, u6) =>
|
||||
u1 :: u2a.nonEmpty :: u2 :: u2a :: u2b :: u2c :: u3a.nonEmpty :: u3 :: u3a :: u3b :: u3c :: u3d :: u4 :: u5 :: u6 :: HNil
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
// Copyright (c) 2019 PSForever
|
||||
package net.psforever.packet.game
|
||||
|
||||
import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket}
|
||||
import net.psforever.types.Angular
|
||||
import scodec.Codec
|
||||
import scodec.codecs._
|
||||
|
||||
final case class DamageMessage(guid1 : PlanetSideGUID,
|
||||
unk1 : Int,
|
||||
guid2 : PlanetSideGUID,
|
||||
unk2 : Boolean)
|
||||
extends PlanetSideGamePacket {
|
||||
type Packet = DamageMessage
|
||||
def opcode = GamePacketOpcode.DamageMessage
|
||||
def encode = DamageMessage.encode(this)
|
||||
}
|
||||
|
||||
object DamageMessage extends Marshallable[DamageMessage] {
|
||||
implicit val codec : Codec[DamageMessage] = (
|
||||
("guid1" | PlanetSideGUID.codec) ::
|
||||
("unk1" | uint8) ::
|
||||
("guid1" | PlanetSideGUID.codec) ::
|
||||
("unk2" | bool)
|
||||
).as[DamageMessage]
|
||||
}
|
||||
|
|
@ -11,7 +11,8 @@ import shapeless.{::, HNil}
|
|||
* @param player_guid1 the player
|
||||
* @param player_guid2 the player(?);
|
||||
* often matches with `player_guid1`
|
||||
* @param unk na
|
||||
* @param unk na;
|
||||
* commonly, zero
|
||||
* @param list list of detected objects;
|
||||
* normally contains at least one element
|
||||
*/
|
||||
|
|
@ -20,7 +21,7 @@ import shapeless.{::, HNil}
|
|||
BETA CLIENT DEBUG INFO:
|
||||
Detector
|
||||
Sender
|
||||
Object Count
|
||||
Object Count (not really)
|
||||
Detected Object[]
|
||||
*/
|
||||
final case class ObjectDetectedMessage(player_guid1 : PlanetSideGUID,
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ import shapeless.{::, HNil}
|
|||
* a discharged controlled projectile will not normally rotate.
|
||||
* A minor loss of lifespan may be levied.
|
||||
* @see `ProjectileDefinition`
|
||||
* @see `TrackedProjectileData`
|
||||
* @see `RemoteProjectileData`
|
||||
* @param projectile_guid when dispatched by the client, the client-specific local unique identifier of the projectile;
|
||||
* when dispatched by the server, the global unique identifier for the synchronized projectile object
|
||||
* @param shot_pos the position of the projectile
|
||||
|
|
@ -42,16 +42,18 @@ import shapeless.{::, HNil}
|
|||
* @param shot_original_orient the orientation of the projectile when it was discharged
|
||||
* @param sequence_num an incrementing index of the packet in this projectile's lifetime;
|
||||
* suggests the "time alive" and indicates a place in packet ordering
|
||||
* @param explode indicates the projectile should explode
|
||||
* @param unk na
|
||||
* @param end indicates the projectile has reached the end of its lifespan;
|
||||
* usually, it should explode
|
||||
* @param hit_target_guid the global unique identifier of the object the projwectile collided with;
|
||||
* will be 0 if it reached the end of its life naturally, without colliding with anything
|
||||
*/
|
||||
final case class ProjectileStateMessage(projectile_guid : PlanetSideGUID,
|
||||
shot_pos : Vector3,
|
||||
shot_vel : Vector3,
|
||||
shot_original_orient : Vector3,
|
||||
sequence_num : Int,
|
||||
explode : Boolean,
|
||||
unk : Int)
|
||||
end : Boolean,
|
||||
hit_target_guid : PlanetSideGUID)
|
||||
extends PlanetSideGamePacket {
|
||||
type Packet = ProjectileStateMessage
|
||||
def opcode = GamePacketOpcode.ProjectileStateMessage
|
||||
|
|
@ -67,8 +69,8 @@ object ProjectileStateMessage extends Marshallable[ProjectileStateMessage] {
|
|||
("pitch" | Angular.codec_pitch) ::
|
||||
("yaw" | Angular.codec_yaw()) ::
|
||||
("sequence_num" | uint8) ::
|
||||
("explode" | bool) ::
|
||||
("unk" | uint16L)
|
||||
("end" | bool) ::
|
||||
("hit_target" | PlanetSideGUID.codec)
|
||||
).xmap[ProjectileStateMessage] (
|
||||
{
|
||||
case guid :: pos :: vel :: roll :: pitch :: yaw :: sequence_num :: explode :: unk :: HNil =>
|
||||
|
|
|
|||
|
|
@ -1223,21 +1223,21 @@ object ObjectClass {
|
|||
case ObjectClass.portable_manned_turret_vs => ConstructorData(OneMannedFieldTurretData.codec, "field turret")
|
||||
case ObjectClass.router_telepad_deployable => DroppedItemData(TelepadDeployableData.codec, "telepad deployable")
|
||||
//projectiles
|
||||
case ObjectClass.hunter_seeker_missile_projectile => ConstructorData(TrackedProjectileData.codec, "projectile")
|
||||
case ObjectClass.meteor_common => ConstructorData(TrackedProjectileData.codec, "meteor")
|
||||
case ObjectClass.meteor_projectile_b_large => ConstructorData(TrackedProjectileData.codec, "meteor")
|
||||
case ObjectClass.meteor_projectile_b_medium => ConstructorData(TrackedProjectileData.codec, "meteor")
|
||||
case ObjectClass.meteor_projectile_b_small => ConstructorData(TrackedProjectileData.codec, "meteor")
|
||||
case ObjectClass.meteor_projectile_large => ConstructorData(TrackedProjectileData.codec, "meteor")
|
||||
case ObjectClass.meteor_projectile_medium => ConstructorData(TrackedProjectileData.codec, "meteor")
|
||||
case ObjectClass.meteor_projectile_small => ConstructorData(TrackedProjectileData.codec, "meteor")
|
||||
case ObjectClass.phoenix_missile_guided_projectile => ConstructorData(TrackedProjectileData.codec, "projectile")
|
||||
case ObjectClass.oicw_little_buddy => ConstructorData(TrackedProjectileData.codec, "projectile")
|
||||
case ObjectClass.oicw_projectile => ConstructorData(TrackedProjectileData.codec, "projectile")
|
||||
case ObjectClass.sparrow_projectile => ConstructorData(TrackedProjectileData.codec, "projectile")
|
||||
case ObjectClass.starfire_projectile => ConstructorData(TrackedProjectileData.codec, "projectile")
|
||||
case ObjectClass.striker_missile_targeting_projectile => ConstructorData(TrackedProjectileData.codec, "projectile")
|
||||
case ObjectClass.wasp_rocket_projectile => ConstructorData(TrackedProjectileData.codec, "projectile")
|
||||
case ObjectClass.hunter_seeker_missile_projectile => ConstructorData(RemoteProjectileData.codec, "projectile")
|
||||
case ObjectClass.meteor_common => ConstructorData(RemoteProjectileData.codec, "meteor")
|
||||
case ObjectClass.meteor_projectile_b_large => ConstructorData(RemoteProjectileData.codec, "meteor")
|
||||
case ObjectClass.meteor_projectile_b_medium => ConstructorData(RemoteProjectileData.codec, "meteor")
|
||||
case ObjectClass.meteor_projectile_b_small => ConstructorData(RemoteProjectileData.codec, "meteor")
|
||||
case ObjectClass.meteor_projectile_large => ConstructorData(RemoteProjectileData.codec, "meteor")
|
||||
case ObjectClass.meteor_projectile_medium => ConstructorData(RemoteProjectileData.codec, "meteor")
|
||||
case ObjectClass.meteor_projectile_small => ConstructorData(RemoteProjectileData.codec, "meteor")
|
||||
case ObjectClass.phoenix_missile_guided_projectile => ConstructorData(RemoteProjectileData.codec, "projectile")
|
||||
case ObjectClass.oicw_little_buddy => ConstructorData(RemoteProjectileData.codec, "projectile")
|
||||
case ObjectClass.oicw_projectile => ConstructorData(RemoteProjectileData.codec, "projectile")
|
||||
case ObjectClass.sparrow_projectile => ConstructorData(RemoteProjectileData.codec, "projectile")
|
||||
case ObjectClass.starfire_projectile => ConstructorData(RemoteProjectileData.codec, "projectile")
|
||||
case ObjectClass.striker_missile_targeting_projectile => ConstructorData(RemoteProjectileData.codec, "projectile")
|
||||
case ObjectClass.wasp_rocket_projectile => ConstructorData(RemoteProjectileData.codec, "projectile")
|
||||
//vehicles
|
||||
case ObjectClass.ams => ConstructorData(VehicleData.codec(VehicleFormat.Utility), "ams")
|
||||
case ObjectClass.ams_destroyed => ConstructorData(DestroyedVehicleData.codec, "wreckage")
|
||||
|
|
|
|||
|
|
@ -0,0 +1,84 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package net.psforever.packet.game.objectcreate
|
||||
|
||||
import net.psforever.packet.{Marshallable, PacketHelpers}
|
||||
import scodec.{Attempt, Codec}
|
||||
import scodec.codecs._
|
||||
import shapeless.{::, HNil}
|
||||
|
||||
object RemoteProjectiles {
|
||||
abstract class Data(val a : Int, val b : Int)
|
||||
|
||||
final case object Meteor extends Data(0, 32)
|
||||
final case object Wasp extends Data(0, 208)
|
||||
final case object Sparrow extends Data(13107, 187)
|
||||
final case object OICW extends Data(13107, 195)
|
||||
final case object Striker extends Data(26214, 134)
|
||||
final case object HunterSeeker extends Data(39577, 201)
|
||||
final case object Starfire extends Data(39577, 249)
|
||||
class OICWLittleBuddy(x : Int, y : Int) extends Data(x, y)
|
||||
}
|
||||
|
||||
object FlightPhysics extends Enumeration {
|
||||
type Type = Value
|
||||
|
||||
//valid (extremely small distance) (requires non-zero unk4, unk5)
|
||||
val State3 = Value(3)
|
||||
//valid (infinite) (if unk4 == 0 unk5 == 0, minimum distance + time)
|
||||
val State4 = Value(4)
|
||||
//valid(infinite)
|
||||
val State5 = Value(5)
|
||||
//valid (uses velocity) (infinite)
|
||||
val State6 = Value(6)
|
||||
//valid (uses velocity) (infinite)
|
||||
val State7 = Value(7)
|
||||
//valid (uses velocity) (time > 0 is infinite) (unk5 == 2)
|
||||
val State15 = Value(15)
|
||||
|
||||
implicit val codec = PacketHelpers.createEnumerationCodec(this, uint4L)
|
||||
}
|
||||
|
||||
/**
|
||||
* A representation of a projectile that the server must intentionally convey to players other than the shooter.
|
||||
* @param common_data common game object information
|
||||
* @param u1 na;
|
||||
* first part of the canned remote projectile data
|
||||
* @param u2 na;
|
||||
* second part of the canned remote projectile data
|
||||
* @param unk3 na;
|
||||
* does something to how the projectile flies
|
||||
* @param unk4 na
|
||||
* @param unk5 na
|
||||
*/
|
||||
final case class RemoteProjectileData(common_data : CommonFieldDataWithPlacement,
|
||||
u1 : Int,
|
||||
u2 : Int,
|
||||
unk3 : FlightPhysics.Value,
|
||||
unk4 : Int,
|
||||
unk5 : Int
|
||||
) extends ConstructorData {
|
||||
override def bitsize : Long = 33L + common_data.bitsize
|
||||
}
|
||||
|
||||
object RemoteProjectileData extends Marshallable[RemoteProjectileData] {
|
||||
implicit val codec : Codec[RemoteProjectileData] = (
|
||||
("data" | CommonFieldDataWithPlacement.codec) ::
|
||||
("u1" | uint16) ::
|
||||
("u2" | uint8) ::
|
||||
("unk3" | FlightPhysics.codec) ::
|
||||
("unk4" | uint(3)) ::
|
||||
("unk5" | uint2)
|
||||
).exmap[RemoteProjectileData] (
|
||||
{
|
||||
case data :: u1 :: u2 :: unk3 :: unk4 :: unk5 :: HNil =>
|
||||
Attempt.successful(RemoteProjectileData(data, u1, u2, unk3, unk4, unk5))
|
||||
|
||||
// case data =>
|
||||
// Attempt.failure(Err(s"invalid projectile data format - $data"))
|
||||
},
|
||||
{
|
||||
case RemoteProjectileData(data, u1, u2, unk3, unk4, unk5) =>
|
||||
Attempt.successful(data :: u1 :: u2 :: unk3 :: unk4 :: unk5 :: HNil)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
@ -1,110 +0,0 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package net.psforever.packet.game.objectcreate
|
||||
|
||||
import net.psforever.packet.{Marshallable, PacketHelpers}
|
||||
import scodec.{Attempt, Codec}
|
||||
import scodec.codecs._
|
||||
import shapeless.{::, HNil}
|
||||
|
||||
object TrackedProjectile extends Enumeration {
|
||||
type Type = Value
|
||||
|
||||
val OICWLittleBuddy = Value(-1) //?, ?
|
||||
val Meteor = Value(32) //0, 32
|
||||
val Wasp = Value(208) //0, 208
|
||||
val Sparrow = Value(3355579) //13107, 187
|
||||
val OICW = Value(3355587) //13107, 195
|
||||
val Striker = Value(6710918) //26214, 134
|
||||
val HunterSeeker = Value(10131913) //39577, 201
|
||||
val Starfire = Value(10131961) //39577, 249
|
||||
|
||||
implicit val codec = PacketHelpers.createEnumerationCodec(this, uint24)
|
||||
}
|
||||
|
||||
object TrackedProjectiles {
|
||||
abstract class Data(val is : TrackedProjectile.Value, val a : Int, val b : Int)
|
||||
|
||||
final case object Meteor extends Data(TrackedProjectile.Meteor, 0, 32)
|
||||
final case object Wasp extends Data(TrackedProjectile.Wasp, 0, 208)
|
||||
final case object Sparrow extends Data(TrackedProjectile.Sparrow, 13107, 187)
|
||||
final case object OICW extends Data(TrackedProjectile.OICW, 13107, 195)
|
||||
final case object Striker extends Data(TrackedProjectile.Striker, 26214, 134)
|
||||
final case object HunterSeeker extends Data(TrackedProjectile.HunterSeeker, 39577, 201)
|
||||
final case object Starfire extends Data(TrackedProjectile.Starfire, 39577, 249)
|
||||
class OICWLittleBuddy(x : Int, y : Int) extends Data(TrackedProjectile.OICWLittleBuddy, x, y)
|
||||
|
||||
val values: Seq[TrackedProjectiles.Data] = Seq(Meteor, Wasp, Sparrow, OICW, Striker, HunterSeeker, Starfire)
|
||||
|
||||
def apply(x : Int, y : Int) : TrackedProjectiles.Data = {
|
||||
values.find(p => p.a == x && p.b == y) match {
|
||||
case Some(projectileData) => projectileData
|
||||
case None =>
|
||||
throw new IllegalArgumentException("no combination of projectile data equates to a defined projectile type")
|
||||
}
|
||||
}
|
||||
|
||||
def apply(is : TrackedProjectile.Value) : TrackedProjectiles.Data = {
|
||||
values.find(p => p.is == is) match {
|
||||
case Some(projectileData) => projectileData
|
||||
case None =>
|
||||
throw new IllegalArgumentException("unknown projectile type")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object FlightPhysics extends Enumeration {
|
||||
type Type = Value
|
||||
|
||||
//valid (extremely small distance) (requires non-zero unk4, unk5)
|
||||
val State3 = Value(3)
|
||||
//valid (infinite) (if unk4 == 0 unk5 == 0, minimum distance + time)
|
||||
val State4 = Value(4)
|
||||
//valid(infinite)
|
||||
val State5 = Value(5)
|
||||
//valid (uses velocity) (infinite)
|
||||
val State6 = Value(6)
|
||||
//valid (uses velocity) (infinite)
|
||||
val State7 = Value(7)
|
||||
//valid (uses velocity) (time > 0 is infinite) (unk5 == 2)
|
||||
val State15 = Value(15)
|
||||
|
||||
implicit val codec = PacketHelpers.createEnumerationCodec(this, uint4L)
|
||||
}
|
||||
|
||||
/**
|
||||
* A representation of a projectile that the server must intentionally convey to players other than the shooter.
|
||||
* @param common_data common game object information
|
||||
* @param unk3 na
|
||||
*/
|
||||
final case class TrackedProjectileData(common_data : CommonFieldDataWithPlacement,
|
||||
u1 : Int,
|
||||
u2 : Int,
|
||||
unk3 : FlightPhysics.Value,
|
||||
unk4 : Int,
|
||||
unk5 : Int
|
||||
) extends ConstructorData {
|
||||
override def bitsize : Long = 33L + common_data.bitsize
|
||||
}
|
||||
|
||||
object TrackedProjectileData extends Marshallable[TrackedProjectileData] {
|
||||
implicit val codec : Codec[TrackedProjectileData] = (
|
||||
("data" | CommonFieldDataWithPlacement.codec) ::
|
||||
("u1" | uint16) ::
|
||||
("u2" | uint8) ::
|
||||
("unk3" | FlightPhysics.codec) ::
|
||||
("unk4" | uint(3)) ::
|
||||
("unk5" | uint2)
|
||||
).exmap[TrackedProjectileData] (
|
||||
{
|
||||
case data :: u1 :: u2 :: unk3 :: unk4 :: unk5 :: HNil =>
|
||||
Attempt.successful(TrackedProjectileData(data, u1, u2, unk3, unk4, unk5))
|
||||
|
||||
// case data =>
|
||||
// Attempt.failure(Err(s"invalid projectile data format - $data"))
|
||||
},
|
||||
{
|
||||
case TrackedProjectileData(data, u1, u2, unk3, unk4, unk5) =>
|
||||
Attempt.successful(data :: u1 :: u2 :: unk3 :: unk4 :: unk5 :: HNil)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
@ -165,17 +165,13 @@ class AvatarService extends Actor {
|
|||
AvatarEvents.publish(
|
||||
AvatarServiceResponse(s"/$forChannel/Avatar", PlanetSideGUID(0), AvatarResponse.ProjectileAutoLockAwareness(mode))
|
||||
)
|
||||
case AvatarAction.ProjectileTrackingAwareness(guid) =>
|
||||
AvatarEvents.publish(
|
||||
AvatarServiceResponse(s"/$forChannel/Avatar", PlanetSideGUID(0), AvatarResponse.ProjectileTrackingAwareness(guid))
|
||||
)
|
||||
case AvatarAction.ProjectileExplodes(player_guid, projectile_guid, projectile) =>
|
||||
AvatarEvents.publish(
|
||||
AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.ProjectileExplodes(projectile_guid, projectile))
|
||||
)
|
||||
case AvatarAction.ProjectileState(player_guid, projectile_guid, shot_pos, shot_vel, shot_orient, unk1, unk2, unk3) =>
|
||||
case AvatarAction.ProjectileState(player_guid, projectile_guid, shot_pos, shot_vel, shot_orient, sequence, end, target) =>
|
||||
AvatarEvents.publish(
|
||||
AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.ProjectileState(projectile_guid, shot_pos, shot_vel, shot_orient, unk1, unk2, unk3))
|
||||
AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.ProjectileState(projectile_guid, shot_pos, shot_vel, shot_orient, sequence, end, target))
|
||||
)
|
||||
case AvatarAction.PickupItem(player_guid, zone, target, slot, item, unk) =>
|
||||
janitor forward RemoverActor.ClearSpecific(List(item), zone)
|
||||
|
|
|
|||
|
|
@ -50,9 +50,8 @@ object AvatarAction {
|
|||
final case class PlayerState(player_guid : PlanetSideGUID, pos : Vector3, vel : Option[Vector3], facingYaw : Float, facingPitch : Float, facingYawUpper : Float, timestamp : Int, is_crouching : Boolean, is_jumping : Boolean, jump_thrust : Boolean, is_cloaked : Boolean, spectator : Boolean, weaponInHand : Boolean) extends Action
|
||||
final case class PickupItem(player_guid : PlanetSideGUID, zone : Zone, target : PlanetSideGameObject with Container, slot : Int, item : Equipment, unk : Int = 0) extends Action
|
||||
final case class ProjectileAutoLockAwareness(mode : Int) extends Action
|
||||
final case class ProjectileTrackingAwareness(projectile_guid : PlanetSideGUID) extends Action
|
||||
final case class ProjectileExplodes(player_guid : PlanetSideGUID, projectile_guid : PlanetSideGUID, projectile : Projectile) extends Action
|
||||
final case class ProjectileState(player_guid : PlanetSideGUID, projectile_guid : PlanetSideGUID, shot_pos : Vector3, shot_vel : Vector3, shot_orient : Vector3, unk1 : Int, unk2 : Boolean, unk3 : Int) extends Action
|
||||
final case class ProjectileState(player_guid : PlanetSideGUID, projectile_guid : PlanetSideGUID, shot_pos : Vector3, shot_vel : Vector3, shot_orient : Vector3, sequence : Int, end : Boolean, hit_target : PlanetSideGUID) extends Action
|
||||
final case class PutDownFDU(player_guid : PlanetSideGUID) extends Action
|
||||
final case class Release(player : Player, zone : Zone, time : Option[FiniteDuration] = None) extends Action
|
||||
final case class Revive(target_guid: PlanetSideGUID) extends Action
|
||||
|
|
|
|||
|
|
@ -42,9 +42,8 @@ object AvatarResponse {
|
|||
final case class PlanetsideAttributeSelf(attribute_type : Int, attribute_value : Long) extends Response
|
||||
final case class PlayerState(pos : Vector3, vel : Option[Vector3], facingYaw : Float, facingPitch : Float, facingYawUpper : Float, timestamp : Int, is_crouching : Boolean, is_jumping : Boolean, jump_thrust : Boolean, is_cloaked : Boolean, spectator : Boolean, weaponInHand : Boolean) extends Response
|
||||
final case class ProjectileAutoLockAwareness(mode : Int) extends Response
|
||||
final case class ProjectileTrackingAwareness(projectile_guid : PlanetSideGUID) extends Response
|
||||
final case class ProjectileExplodes(projectile_guid : PlanetSideGUID, projectile : Projectile) extends Response
|
||||
final case class ProjectileState(projectile_guid : PlanetSideGUID, shot_pos : Vector3, shot_vel : Vector3, shot_orient : Vector3, unk1 : Int, unk2 : Boolean, unk3 : Int) extends Response
|
||||
final case class ProjectileState(projectile_guid : PlanetSideGUID, shot_pos : Vector3, shot_vel : Vector3, shot_orient : Vector3, sequence : Int, end : Boolean, hit_target : PlanetSideGUID) extends Response
|
||||
final case class PutDownFDU(target_guid : PlanetSideGUID) extends Response
|
||||
final case class Release(player : Player) extends Response
|
||||
final case class Reload(weapon_guid : PlanetSideGUID) extends Response
|
||||
|
|
|
|||
68
common/src/test/scala/game/DamageFeedbackMessageTest.scala
Normal file
68
common/src/test/scala/game/DamageFeedbackMessageTest.scala
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package game
|
||||
|
||||
import org.specs2.mutable._
|
||||
import net.psforever.packet._
|
||||
import net.psforever.packet.game._
|
||||
import scodec.bits._
|
||||
|
||||
class DamageFeedbackMessageTest extends Specification {
|
||||
val string = hex"7b 3d842f610b2040000000"
|
||||
|
||||
"decode" in {
|
||||
PacketCoding.DecodePacket(string).require match {
|
||||
case DamageFeedbackMessage(unk1, unk2, unk2a, unk2b, unk2c, unk3, unk3a, unk3b, unk3c, unk3d, unk4, unk5, unk6) =>
|
||||
unk1 mustEqual 3
|
||||
unk2 mustEqual true
|
||||
unk2a.contains(PlanetSideGUID(2913)) mustEqual true
|
||||
unk2b.isEmpty mustEqual true
|
||||
unk2c.isEmpty mustEqual true
|
||||
unk3 mustEqual true
|
||||
unk3a.contains(PlanetSideGUID(2913)) mustEqual true
|
||||
unk3b.isEmpty mustEqual true
|
||||
unk3c.isEmpty mustEqual true
|
||||
unk3d.isEmpty mustEqual true
|
||||
unk4 mustEqual 1
|
||||
unk5 mustEqual 2
|
||||
unk6 mustEqual 0
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"encode" in {
|
||||
val msg = DamageFeedbackMessage(3, true, Some(PlanetSideGUID(2913)), None, None, true, Some(PlanetSideGUID(2913)), None, None, None, 1, 2, 0)
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string
|
||||
}
|
||||
|
||||
"failures" in {
|
||||
//unk2: no parameters
|
||||
DamageFeedbackMessage(3, true, None, None, None, true, Some(PlanetSideGUID(2913)), None, None, None, 1, 2, 0) must throwA[AssertionError]
|
||||
//unk2: two exclusive parameters
|
||||
DamageFeedbackMessage(3, true, Some(PlanetSideGUID(2913)), Some("error"), None, true, Some(PlanetSideGUID(2913)), None, None, None, 1, 2, 0) must throwA[AssertionError]
|
||||
DamageFeedbackMessage(3, true, Some(PlanetSideGUID(2913)), None, Some(5), true, Some(PlanetSideGUID(2913)), None, None, None, 1, 2, 0) must throwA[AssertionError]
|
||||
DamageFeedbackMessage(3, true, None, Some("error"), Some(5), true, Some(PlanetSideGUID(2913)), None, None, None, 1, 2, 0) must throwA[AssertionError]
|
||||
//unk2: all parameters
|
||||
DamageFeedbackMessage(3, true, Some(PlanetSideGUID(2913)), Some("error"), Some(5), true, Some(PlanetSideGUID(2913)), None, None, None, 1, 2, 0) must throwA[AssertionError]
|
||||
//unk2: mismatched flag for strings
|
||||
DamageFeedbackMessage(3, true, None, None, Some(5), true, Some(PlanetSideGUID(2913)), None, None, None, 1, 2, 0) must throwA[AssertionError]
|
||||
DamageFeedbackMessage(3, false, None, Some("error"), None, true, Some(PlanetSideGUID(2913)), None, None, None, 1, 2, 0) must throwA[AssertionError]
|
||||
|
||||
//unk3: no parameters
|
||||
DamageFeedbackMessage(3, true, Some(PlanetSideGUID(2913)), None, None, true, None, None, None, None, 1, 2, 0) must throwA[AssertionError]
|
||||
//unk3: two exclusive parameters
|
||||
DamageFeedbackMessage(3, true, Some(PlanetSideGUID(2913)), None, None, true, Some(PlanetSideGUID(2913)), Some("error"), None, None, 1, 2, 0) must throwA[AssertionError]
|
||||
DamageFeedbackMessage(3, true, Some(PlanetSideGUID(2913)), None, None, true, Some(PlanetSideGUID(2913)), None, Some(5), None, 1, 2, 0) must throwA[AssertionError]
|
||||
DamageFeedbackMessage(3, true, Some(PlanetSideGUID(2913)), None, None, true, None, Some("error"), Some(5), Some(1), 1, 2, 0) must throwA[AssertionError]
|
||||
//unk3: all parameters
|
||||
DamageFeedbackMessage(3, true, Some(PlanetSideGUID(2913)), None, None, true, Some(PlanetSideGUID(2913)), Some("error"), Some(5), None, 1, 2, 0) must throwA[AssertionError]
|
||||
//unk3: mismatched fields
|
||||
DamageFeedbackMessage(3, true, Some(PlanetSideGUID(2913)), None, None, true, Some(PlanetSideGUID(2913)), None, None, Some(5), 1, 2, 0) must throwA[AssertionError]
|
||||
DamageFeedbackMessage(3, true, Some(PlanetSideGUID(2913)), None, None, true, None, Some("Error"), None, None, 1, 2, 0) must throwA[AssertionError]
|
||||
//unk3: mismatched flag for strings
|
||||
DamageFeedbackMessage(3, true, Some(PlanetSideGUID(2913)), None, None, true, None, None, Some(5), None, 1, 2, 0) must throwA[AssertionError]
|
||||
DamageFeedbackMessage(3, true, Some(PlanetSideGUID(2913)), None, None, false, None, Some("error"), None, None, 1, 2, 0) must throwA[AssertionError]
|
||||
}
|
||||
}
|
||||
30
common/src/test/scala/game/DamageMessageTest.scala
Normal file
30
common/src/test/scala/game/DamageMessageTest.scala
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
// Copyright (c) 2019 PSForever
|
||||
package game
|
||||
|
||||
import org.specs2.mutable._
|
||||
import net.psforever.packet._
|
||||
import net.psforever.packet.game._
|
||||
import scodec.bits._
|
||||
|
||||
class DamageMessageTest extends Specification {
|
||||
val string = hex"0b610b02610b00"
|
||||
|
||||
"decode" in {
|
||||
PacketCoding.DecodePacket(string).require match {
|
||||
case DamageMessage(guid1, unk1, guid2, unk2) =>
|
||||
guid1 mustEqual PlanetSideGUID(2913)
|
||||
unk1 mustEqual 2
|
||||
guid2 mustEqual PlanetSideGUID(2913)
|
||||
unk2 mustEqual false
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"encode" in {
|
||||
val msg = DamageMessage(PlanetSideGUID(2913), 2, PlanetSideGUID(2913), false)
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string
|
||||
}
|
||||
}
|
||||
|
|
@ -19,7 +19,7 @@ class ProjectileStateMessageTest extends Specification {
|
|||
orient mustEqual Vector3(0, 22.5f, 146.25f)
|
||||
sequence mustEqual 2
|
||||
explode mustEqual false
|
||||
unk mustEqual 0
|
||||
unk mustEqual PlanetSideGUID(0)
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
|
|
@ -33,7 +33,7 @@ class ProjectileStateMessageTest extends Specification {
|
|||
Vector3(0, 22.5f, 146.25f),
|
||||
2,
|
||||
false,
|
||||
0
|
||||
PlanetSideGUID(0)
|
||||
)
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
|
|
|
|||
|
|
@ -8,11 +8,11 @@ import net.psforever.types.{PlanetSideEmpire, Vector3}
|
|||
import org.specs2.mutable._
|
||||
import scodec.bits._
|
||||
|
||||
class TrackedProjectileDataTest extends Specification {
|
||||
class RemoteProjectileDataTest extends Specification {
|
||||
val string_striker_projectile = hex"17 C5000000 A4B 009D 4C129 0CB0A 9814 00 F5 E3 040000666686400"
|
||||
val string_hunter_seeker_missile_projectile = hex"17 c5000000 ca9 ab9e af127 ec465 3723 00 15 c4 2400009a99c9400"
|
||||
|
||||
"TrackedProjectileData" should {
|
||||
"RemoteProjectileData" should {
|
||||
"decode (striker_missile_targeting_projectile)" in {
|
||||
PacketCoding.DecodePacket(string_striker_projectile).require match {
|
||||
case ObjectCreateMessage(len, cls, guid, parent, data) =>
|
||||
|
|
@ -21,7 +21,7 @@ class TrackedProjectileDataTest extends Specification {
|
|||
guid mustEqual PlanetSideGUID(40192)
|
||||
parent.isDefined mustEqual false
|
||||
data match {
|
||||
case TrackedProjectileData(CommonFieldDataWithPlacement(pos, deploy), unk2, lim, unk3, unk4, unk5) =>
|
||||
case RemoteProjectileData(CommonFieldDataWithPlacement(pos, deploy), unk2, lim, unk3, unk4, unk5) =>
|
||||
pos.coord mustEqual Vector3(4644.5938f, 5472.0938f, 82.375f)
|
||||
pos.orient mustEqual Vector3(0, 30.9375f, 171.5625f)
|
||||
deploy.faction mustEqual PlanetSideEmpire.TR
|
||||
|
|
@ -55,7 +55,7 @@ class TrackedProjectileDataTest extends Specification {
|
|||
guid mustEqual PlanetSideGUID(40619)
|
||||
parent.isDefined mustEqual false
|
||||
data match {
|
||||
case TrackedProjectileData(CommonFieldDataWithPlacement(pos, deploy), unk2, lim, unk3, unk4, unk5) =>
|
||||
case RemoteProjectileData(CommonFieldDataWithPlacement(pos, deploy), unk2, lim, unk3, unk4, unk5) =>
|
||||
pos.coord mustEqual Vector3(3621.3672f, 2701.8438f, 140.85938f)
|
||||
pos.orient mustEqual Vector3(0, 300.9375f, 258.75f)
|
||||
deploy.faction mustEqual PlanetSideEmpire.NC
|
||||
|
|
@ -82,7 +82,7 @@ class TrackedProjectileDataTest extends Specification {
|
|||
}
|
||||
|
||||
"encode (striker_missile_targeting_projectile)" in {
|
||||
val obj = TrackedProjectileData(
|
||||
val obj = RemoteProjectileData(
|
||||
CommonFieldDataWithPlacement(
|
||||
PlacementData(4644.5938f, 5472.0938f, 82.375f, 0f, 30.9375f, 171.5625f),
|
||||
CommonFieldData(PlanetSideEmpire.TR, false, false, true, None, false, None, None, PlanetSideGUID(0))
|
||||
|
|
@ -100,7 +100,7 @@ class TrackedProjectileDataTest extends Specification {
|
|||
}
|
||||
|
||||
"encode (hunter_seeker_missile_projectile)" in {
|
||||
val obj = TrackedProjectileData(
|
||||
val obj = RemoteProjectileData(
|
||||
CommonFieldDataWithPlacement(
|
||||
PlacementData(3621.3672f, 2701.8438f, 140.85938f, 0, 300.9375f, 258.75f),
|
||||
CommonFieldData(PlanetSideEmpire.NC, false, false, true, None, false, None, None, PlanetSideGUID(0))
|
||||
|
|
@ -54,8 +54,9 @@ import services.local.{LocalAction, LocalResponse, LocalServiceMessage, LocalSer
|
|||
import services.chat._
|
||||
import services.vehicle.support.TurretUpgrader
|
||||
import services.vehicle.{VehicleAction, VehicleResponse, VehicleServiceMessage, VehicleServiceResponse}
|
||||
import services.teamwork.{SquadAction => SquadServiceAction, SquadServiceMessage, SquadServiceResponse, SquadResponse, SquadService}
|
||||
import services.teamwork.{SquadResponse, SquadService, SquadServiceMessage, SquadServiceResponse, SquadAction => SquadServiceAction}
|
||||
|
||||
import scala.collection.concurrent.TrieMap
|
||||
import scala.collection.mutable.LongMap
|
||||
import scala.concurrent.duration._
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
|
|
@ -68,6 +69,8 @@ import net.psforever.objects.vehicles.Utility.InternalTelepad
|
|||
import services.local.support.{HackCaptureActor, RouterTelepadActivation}
|
||||
import services.support.SupportActor
|
||||
|
||||
import scala.collection.mutable
|
||||
|
||||
class WorldSessionActor extends Actor with MDCContextAware {
|
||||
import WorldSessionActor._
|
||||
|
||||
|
|
@ -153,9 +156,6 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
lazy val unsignedIntMaxValue : Long = Int.MaxValue.toLong * 2L + 1L
|
||||
var serverTime : Long = 0
|
||||
|
||||
var projectileAutoLockTargets : List[PlanetSideGUID] = Nil
|
||||
var projectileAutoLocksActive : Set[PlanetSideGUID] = Set.empty
|
||||
|
||||
var amsSpawnPoints : List[SpawnPoint] = Nil
|
||||
var clientKeepAlive : Cancellable = DefaultCancellable.obj
|
||||
var progressBarUpdate : Cancellable = DefaultCancellable.obj
|
||||
|
|
@ -165,6 +165,8 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
var cargoDismountTimer : Cancellable = DefaultCancellable.obj
|
||||
var antChargingTick : Cancellable = DefaultCancellable.obj
|
||||
var antDischargingTick : Cancellable = DefaultCancellable.obj
|
||||
var weaponAutoLockDecay : Cancellable = DefaultCancellable.obj
|
||||
|
||||
|
||||
/**
|
||||
* Convert a boolean value into an integer value.
|
||||
|
|
@ -1220,6 +1222,19 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
log.info(s"Received a direct message: $pkt")
|
||||
sendResponse(pkt)
|
||||
|
||||
case LoadedRemoteProjectile(projectile_guid) =>
|
||||
continent.GUID(projectile_guid) match {
|
||||
case Some(obj : Projectile) if obj.profile.ExistsOnRemoteClients =>
|
||||
//spawn projectile on other clients
|
||||
val projectileGlobalUID = obj.GUID
|
||||
val definition = obj.Definition
|
||||
avatarService ! AvatarServiceMessage(
|
||||
continent.Id,
|
||||
AvatarAction.LoadProjectile(player.GUID, definition.ObjectId, obj, definition.Packet.ConstructorData(obj).get)
|
||||
)
|
||||
case _ => ;
|
||||
}
|
||||
|
||||
case default =>
|
||||
log.warn(s"Invalid packet class received: $default from $sender")
|
||||
}
|
||||
|
|
@ -1476,43 +1491,23 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
}
|
||||
}
|
||||
|
||||
case AvatarResponse.ProjectileTrackingAwareness(projectile_guid) =>
|
||||
continent.GUID(projectile_guid) match {
|
||||
case Some(projectile : Projectile) =>
|
||||
val mode = 7 + (projectile.profile == GlobalDefinitions.wasp_rocket_projectile)
|
||||
sendResponse(GenericActionMessage(mode))
|
||||
case _ => ;
|
||||
}
|
||||
|
||||
case AvatarResponse.ProjectileExplodes(projectile_guid, projectile) =>
|
||||
// //turn the projectile into a boomer
|
||||
// sendResponse(
|
||||
// ObjectCreateMessage(
|
||||
// GlobalDefinitions.boomer.ObjectId,
|
||||
// projectile_guid,
|
||||
// CommonFieldDataWithPlacement(
|
||||
// PlacementData(projectile.Position, projectile.Orientation),
|
||||
// CommonFieldData(projectile.owner.Faction,false,false,false,None,false,Some(false),None, PlanetSideGUID(0))
|
||||
// )
|
||||
// )
|
||||
// )
|
||||
// //detonate the boomer, then clean it up
|
||||
// sendResponse(TriggerEffectMessage(projectile_guid, "detonate_boomer"))
|
||||
sendResponse(ProjectileStateMessage(projectile_guid, projectile.Position, Vector3.Zero, projectile.Orientation, 0, true, PlanetSideGUID(0)))
|
||||
sendResponse(ObjectDeleteMessage(projectile_guid, 2))
|
||||
|
||||
case AvatarResponse.ProjectileAutoLockAwareness(mode) =>
|
||||
sendResponse(GenericActionMessage(mode))
|
||||
|
||||
case AvatarResponse.ProjectileState(projectile_guid, shot_pos, shot_vel, shot_orient, unk1, unk2, unk3) =>
|
||||
case AvatarResponse.ProjectileState(projectile_guid, shot_pos, shot_vel, shot_orient, seq, end, target_guid) =>
|
||||
if(tplayer_guid != guid) {
|
||||
sendResponse(ProjectileStateMessage(projectile_guid, shot_pos, shot_vel, shot_orient, unk1, unk2, unk3))
|
||||
sendResponse(ProjectileStateMessage(projectile_guid, shot_pos, shot_vel, shot_orient, seq, end, target_guid))
|
||||
// continent.GUID(projectile_guid) match {
|
||||
// case Some(obj) =>
|
||||
// val definition = obj.Definition
|
||||
// sendResponse(ObjectCreateMessage(definition.ObjectId, projectile_guid, definition.Packet.ConstructorData(obj).get))
|
||||
// case _ => ;
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
case AvatarResponse.PutDownFDU(target) =>
|
||||
if(tplayer_guid != guid) {
|
||||
|
|
@ -3495,11 +3490,8 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
import net.psforever.objects.GlobalDefinitions._
|
||||
import net.psforever.types.CertificationType._
|
||||
|
||||
val faction =
|
||||
if(sessionId % 5 == 0) PlanetSideEmpire.TR
|
||||
else if(sessionId % 3 == 0) PlanetSideEmpire.NC
|
||||
else PlanetSideEmpire.VS
|
||||
val avatar = new Avatar(41605313L+sessionId, s"TestCharacter$sessionId", faction, CharacterGender.Female, 41, CharacterVoice.Voice1)
|
||||
val faction = PlanetSideEmpire.VS
|
||||
val avatar = new Avatar(41605313L+sessionId, s"TestCharacter$sessionId", PlanetSideEmpire.VS, CharacterGender.Female, 41, CharacterVoice.Voice1)
|
||||
avatar.Certifications += StandardAssault
|
||||
avatar.Certifications += MediumAssault
|
||||
avatar.Certifications += StandardExoSuit
|
||||
|
|
@ -3548,14 +3540,13 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
//player.Orientation = Vector3(0f, 0f, 132.1875f)
|
||||
// player.ExoSuit = ExoSuitType.MAX //TODO strange issue; divide number above by 10 when uncommenting
|
||||
player.Slot(0).Equipment = Tool(GlobalDefinitions.StandardPistol(player.Faction))
|
||||
player.Slot(2).Equipment = Tool(hunterseeker) //Tool(suppressor)
|
||||
player.Slot(2).Equipment = Tool(suppressor)
|
||||
player.Slot(4).Equipment = Tool(GlobalDefinitions.StandardMelee(player.Faction))
|
||||
player.Slot(6).Equipment = AmmoBox(hunter_seeker_missile, 65535)
|
||||
//player.Slot(6).Equipment = AmmoBox(bullet_9mm)
|
||||
//player.Slot(9).Equipment = AmmoBox(bullet_9mm)
|
||||
//player.Slot(12).Equipment = AmmoBox(bullet_9mm)
|
||||
//player.Slot(33).Equipment = AmmoBox(bullet_9mm_AP)
|
||||
//player.Slot(36).Equipment = AmmoBox(GlobalDefinitions.StandardPistolAmmo(player.Faction))
|
||||
player.Slot(6).Equipment = AmmoBox(bullet_9mm)
|
||||
player.Slot(9).Equipment = AmmoBox(bullet_9mm)
|
||||
player.Slot(12).Equipment = AmmoBox(bullet_9mm)
|
||||
player.Slot(33).Equipment = AmmoBox(bullet_9mm_AP)
|
||||
player.Slot(36).Equipment = AmmoBox(GlobalDefinitions.StandardPistolAmmo(player.Faction))
|
||||
player.Slot(39).Equipment = SimpleItem(remote_electronics_kit)
|
||||
player.Locker.Inventory += 0 -> SimpleItem(remote_electronics_kit)
|
||||
player.Inventory.Items.foreach { _.obj.Faction = faction }
|
||||
|
|
@ -3896,28 +3887,29 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
|
||||
case msg @ PlayerStateMessageUpstream(avatar_guid, pos, vel, yaw, pitch, yaw_upper, seq_time, unk3, is_crouching, is_jumping, jump_thrust, is_cloaking, unk5, unk6) =>
|
||||
if(deadState == DeadState.Alive) {
|
||||
val time = System.currentTimeMillis()
|
||||
if (timeDL != 0) {
|
||||
if (System.currentTimeMillis() - timeDL > 500) {
|
||||
if (time - timeDL > 500) {
|
||||
player.Stamina = player.Stamina - 1
|
||||
avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.PlanetsideAttributeSelf(player.GUID, 2, player.Stamina))
|
||||
timeDL = System.currentTimeMillis()
|
||||
timeDL = time
|
||||
}
|
||||
}
|
||||
if (timeSurge != 0) {
|
||||
if (System.currentTimeMillis() - timeSurge > 500 && player.ExoSuit == ExoSuitType.Agile) {
|
||||
if (time - timeSurge > 500 && player.ExoSuit == ExoSuitType.Agile) {
|
||||
player.Stamina = player.Stamina - 1
|
||||
avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.PlanetsideAttributeSelf(player.GUID, 2, player.Stamina))
|
||||
timeSurge = System.currentTimeMillis()
|
||||
timeSurge = time
|
||||
}
|
||||
else if (System.currentTimeMillis() - timeSurge > 333 && player.ExoSuit == ExoSuitType.Reinforced) {
|
||||
else if (time - timeSurge > 333 && player.ExoSuit == ExoSuitType.Reinforced) {
|
||||
player.Stamina = player.Stamina - 1
|
||||
avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.PlanetsideAttributeSelf(player.GUID, 2, player.Stamina))
|
||||
timeSurge = System.currentTimeMillis()
|
||||
timeSurge = time
|
||||
}
|
||||
else if (System.currentTimeMillis() - timeSurge > 1000 && ( player.ExoSuit == ExoSuitType.Infiltration || player.ExoSuit == ExoSuitType.Standard )) {
|
||||
else if (time - timeSurge > 1000 && ( player.ExoSuit == ExoSuitType.Infiltration || player.ExoSuit == ExoSuitType.Standard )) {
|
||||
player.Stamina = player.Stamina - 1
|
||||
avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.PlanetsideAttributeSelf(player.GUID, 2, player.Stamina))
|
||||
timeSurge = System.currentTimeMillis()
|
||||
timeSurge = time
|
||||
}
|
||||
}
|
||||
if (player.Stamina == 0) {
|
||||
|
|
@ -3938,10 +3930,6 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
player.Stamina = player.Stamina + 1
|
||||
avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.PlanetsideAttributeSelf(player.GUID, 2, player.Stamina))
|
||||
}
|
||||
|
||||
if(!player.Crouching && is_crouching) {
|
||||
//...
|
||||
}
|
||||
player.Position = pos
|
||||
player.Velocity = vel
|
||||
player.Orientation = Vector3(player.Orientation.x, pitch, yaw)
|
||||
|
|
@ -4048,7 +4036,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
case msg @ VehicleSubStateMessage(vehicle_guid, player_guid, vehicle_pos, vehicle_ang, vel, unk1, unk2) =>
|
||||
//log.info(s"VehicleSubState: $vehicle_guid, $player_guid, $vehicle_pos, $vehicle_ang, $vel, $unk1, $unk2")
|
||||
|
||||
case msg @ ProjectileStateMessage(projectile_guid, shot_pos, shot_vel, shot_orient, unk1, unk2, unk3) =>
|
||||
case msg @ ProjectileStateMessage(projectile_guid, shot_pos, shot_vel, shot_orient, seq, end, target_guid) =>
|
||||
log.info(s"ProjectileState: $msg")
|
||||
projectiles(projectile_guid.guid - Projectile.BaseUID) match {
|
||||
case Some(projectile) if projectile.HasGUID =>
|
||||
|
|
@ -4056,9 +4044,10 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
projectile.Position = shot_pos
|
||||
projectile.Orientation = shot_orient
|
||||
projectile.Velocity = shot_vel
|
||||
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ProjectileState(player.GUID, projectileGlobalUID, shot_pos, shot_vel, shot_orient, unk1, unk2, unk3))
|
||||
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ProjectileState(player.GUID, projectileGlobalUID, shot_pos, shot_vel, shot_orient, seq, end, target_guid))
|
||||
|
||||
case _ =>
|
||||
log.error(s"ProjectileState: the projectile@${projectile_guid.guid} can not be found")
|
||||
log.error(s"ProjectileState: constructed projectile ${projectile_guid.guid} can not be found")
|
||||
}
|
||||
|
||||
case msg @ ReleaseAvatarRequestMessage() =>
|
||||
|
|
@ -4334,9 +4323,15 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
}
|
||||
|
||||
case msg @ ChangeFireStateMessage_Start(item_guid) =>
|
||||
log.info("ChangeFireState_Start: " + msg)
|
||||
log.trace("ChangeFireState_Start: " + msg)
|
||||
if(shooting.isEmpty) {
|
||||
FindEquipment match {
|
||||
//special case - suppress the decimator's alternate fire mode
|
||||
case Some(tool : Tool)
|
||||
if tool.Projectile == GlobalDefinitions.phoenix_missile_guided_projectile &&
|
||||
(tool.Magazine > 0 || prefire.contains(item_guid)) =>
|
||||
prefire = None
|
||||
shooting = Some(item_guid)
|
||||
case Some(tool : Tool) =>
|
||||
if(tool.Magazine > 0 || prefire.contains(item_guid)) {
|
||||
prefire = None
|
||||
|
|
@ -4357,7 +4352,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
}
|
||||
|
||||
case msg @ ChangeFireStateMessage_Stop(item_guid) =>
|
||||
log.info("ChangeFireState_Stop: " + msg)
|
||||
log.trace("ChangeFireState_Stop: " + msg)
|
||||
prefire = None
|
||||
val weapon : Option[Equipment] = if(shooting.contains(item_guid)) {
|
||||
shooting = None
|
||||
|
|
@ -4365,13 +4360,18 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
FindEquipment
|
||||
}
|
||||
else {
|
||||
//some weapons, e.g., the decimator, do not send a ChangeFireState_Start on the last shot
|
||||
FindEquipment match {
|
||||
case Some(tool) =>
|
||||
if(tool.Definition == GlobalDefinitions.phoenix) {
|
||||
case Some(tool : Tool) =>
|
||||
//the decimator does not send a ChangeFireState_Start on the last shot
|
||||
if(tool.Definition == GlobalDefinitions.phoenix &&
|
||||
tool.Projectile != GlobalDefinitions.phoenix_missile_guided_projectile) {
|
||||
//suppress the decimator's alternate fire mode, however
|
||||
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ChangeFireState_Start(player.GUID, item_guid))
|
||||
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ChangeFireState_Stop(player.GUID, item_guid))
|
||||
}
|
||||
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ChangeFireState_Stop(player.GUID, item_guid))
|
||||
Some(tool)
|
||||
case Some(tool) => //permissible, for now
|
||||
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ChangeFireState_Stop(player.GUID, item_guid))
|
||||
Some(tool)
|
||||
case _ =>
|
||||
log.warn(s"ChangeFireState_Stop: received an unexpected message about $item_guid")
|
||||
|
|
@ -4589,6 +4589,10 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
}
|
||||
else {
|
||||
projectile.Miss()
|
||||
if(projectile.profile.ExistsOnRemoteClients && projectile.HasGUID) {
|
||||
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ProjectileExplodes(player.GUID, projectile.GUID, projectile))
|
||||
taskResolver ! UnregisterProjectile(projectile)
|
||||
}
|
||||
}
|
||||
case None =>
|
||||
log.warn(s"RequestDestroy: projectile ${object_guid.guid} has never been fired")
|
||||
|
|
@ -5485,7 +5489,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
}
|
||||
val (angle, attribution, acceptableDistanceToOwner) = obj match {
|
||||
case p : Player =>
|
||||
(p.Orientation, tool.Definition.ObjectId, 10f) //TODO upper body facing
|
||||
(p.Orientation, tool.Definition.ObjectId, 10f + (if(p.Velocity.nonEmpty) { 5f } else { 0f })) //TODO upper body facing
|
||||
case v : Vehicle if v.Definition.CanFly =>
|
||||
(tool.Orientation, obj.Definition.ObjectId, 1000f) //TODO this is too simplistic to find proper angle
|
||||
case _ : Vehicle =>
|
||||
|
|
@ -5514,28 +5518,17 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
log.info("Lazing position: " + pos2.toString)
|
||||
|
||||
case msg @ ObjectDetectedMessage(guid1, guid2, unk, targets) =>
|
||||
//log.info(s"Detection: $msg")
|
||||
FindWeapon match {
|
||||
case Some(weapon) if weapon.Projectile.AutoLock =>
|
||||
//projectile with auto-lock instigates a warning on the target
|
||||
val mode = 7 + (weapon.Projectile == GlobalDefinitions.wasp_rocket_projectile)
|
||||
projectileAutoLockTargets = targets
|
||||
targets
|
||||
.map { continent.GUID }
|
||||
.collect {
|
||||
case Some(obj : Vehicle) if !obj.Cloaked =>
|
||||
//TODO vehicleService ! VehicleServiceMessage(s"${obj.Actor}", VehicleAction.ProjectileAutoLockAwareness(mode))
|
||||
obj.Seats.values
|
||||
.collect { case seat if seat.isOccupied =>
|
||||
avatarService ! AvatarServiceMessage(s"${seat.Occupant.get.Name}", AvatarAction.ProjectileAutoLockAwareness(mode))
|
||||
}
|
||||
case Some(obj : Mountable) =>
|
||||
obj.Seats.values
|
||||
.collect { case seat if seat.isOccupied =>
|
||||
avatarService ! AvatarServiceMessage(s"${seat.Occupant.get.Name}", AvatarAction.ProjectileAutoLockAwareness(mode))
|
||||
}
|
||||
case Some(obj : Player) if obj.ExoSuit == ExoSuitType.MAX =>
|
||||
avatarService ! AvatarServiceMessage(s"${obj.Name}", AvatarAction.ProjectileAutoLockAwareness(mode))
|
||||
val detectedTargets = FindDetectedProjectileTargets(targets)
|
||||
if(detectedTargets.nonEmpty) {
|
||||
val mode = 7 + (weapon.Projectile == GlobalDefinitions.wasp_rocket_projectile)
|
||||
detectedTargets.foreach { target =>
|
||||
avatarService ! AvatarServiceMessage(target, AvatarAction.ProjectileAutoLockAwareness(mode))
|
||||
}
|
||||
}
|
||||
case _ => ;
|
||||
}
|
||||
|
||||
|
|
@ -5565,6 +5558,8 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
log.info(s"Splash: $msg")
|
||||
FindProjectileEntry(projectile_guid) match {
|
||||
case Some(projectile) =>
|
||||
projectile.Position = explosion_pos
|
||||
projectile.Velocity = projectile_vel
|
||||
continent.GUID(direct_victim_uid) match {
|
||||
case Some(target : PlanetSideGameObject with FactionAffinity with Vitality) =>
|
||||
ResolveProjectileEntry(projectile, ProjectileResolution.Splash, target, target.Position) match {
|
||||
|
|
@ -6170,11 +6165,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
TaskResolver.GiveTask(
|
||||
new Task() {
|
||||
private val globalProjectile = obj
|
||||
private val localAnnounce = avatarService
|
||||
private val localMsg = AvatarServiceMessage(
|
||||
continent.Id,
|
||||
AvatarAction.LoadProjectile(player.GUID, definition.ObjectId, obj, definition.Packet.ConstructorData(obj).get)
|
||||
)
|
||||
private val localAnnounce = self
|
||||
|
||||
override def isComplete : Task.Resolution.Value = {
|
||||
if(globalProjectile.HasGUID) {
|
||||
|
|
@ -6186,7 +6177,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
}
|
||||
|
||||
def Execute(resolver : ActorRef) : Unit = {
|
||||
localAnnounce ! localMsg
|
||||
localAnnounce ! LoadedRemoteProjectile(globalProjectile.GUID)
|
||||
resolver ! scala.util.Success(this)
|
||||
}
|
||||
}, List(GUIDTask.RegisterObjectTask(obj)(continent.GUID))
|
||||
|
|
@ -10093,6 +10084,20 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
squadUpdateCounter = (squadUpdateCounter + 1) % queuedSquadActions.length
|
||||
}
|
||||
|
||||
def FindDetectedProjectileTargets(targets : Iterable[PlanetSideGUID]) : Iterable[String] = {
|
||||
targets
|
||||
.map { continent.GUID }
|
||||
.flatMap {
|
||||
case Some(obj : Vehicle) if !obj.Cloaked =>
|
||||
//TODO hint: vehicleService ! VehicleServiceMessage(s"${obj.Actor}", VehicleAction.ProjectileAutoLockAwareness(mode))
|
||||
obj.Seats.values.collect { case seat if seat.isOccupied => seat.Occupant.get.Name }
|
||||
case Some(obj : Mountable) =>
|
||||
obj.Seats.values.collect { case seat if seat.isOccupied => seat.Occupant.get.Name }
|
||||
case Some(obj : Player) if obj.ExoSuit == ExoSuitType.MAX =>
|
||||
Seq(obj.Name)
|
||||
}
|
||||
}
|
||||
|
||||
def failWithError(error : String) = {
|
||||
log.error(error)
|
||||
sendResponse(ConnectionClose())
|
||||
|
|
@ -10256,4 +10261,6 @@ object WorldSessionActor {
|
|||
private final case class NtuDischarging(tplayer: Player, vehicle: Vehicle, silo_guid: PlanetSideGUID)
|
||||
|
||||
private final case class FinalizeDeployable(obj : PlanetSideGameObject with Deployable, tool : ConstructionItem, index : Int)
|
||||
|
||||
private final case class LoadedRemoteProjectile(projectile_guid : PlanetSideGUID)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue