initial AIDamage packet and tests; wrote handling code for the AIDamage packet that transforms it into actionable projectile damage

This commit is contained in:
Fate-JH 2023-12-05 21:59:35 -05:00
parent 9952803883
commit 41462ce540
7 changed files with 145 additions and 15 deletions

View file

@ -518,6 +518,9 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
case packet: LashMessage =>
sessionFuncs.shooting.handleLashHit(packet)
case packet: AIDamage =>
sessionFuncs.shooting.handleAIDamage(packet)
case packet: AvatarFirstTimeEventMessage =>
sessionFuncs.handleAvatarFirstTimeEvent(packet)

View file

@ -2202,7 +2202,7 @@ class SessionData(
/**
* Calculate the amount of damage to be dealt to an active `target`
* using the information reconstructed from a `Resolvedprojectile`
* using the information reconstructed from a `ResolvedProjectile`
* and affect the `target` in a synchronized manner.
* The active `target` and the target of the `DamageResult` do not have be the same.
* While the "tell" for being able to sustain damage is an entity of type `Vitality`,

View file

@ -3,6 +3,7 @@ package net.psforever.actors.session.support
import akka.actor.{ActorContext, typed}
import net.psforever.objects.zones.Zoning
import net.psforever.objects.serverobject.turret.AutomatedTurret
import net.psforever.objects.zones.exp.ToDatabase
import scala.collection.mutable
@ -430,6 +431,47 @@ private[support] class WeaponAndProjectileOperations(
}
}
def handleAIDamage(pkt: AIDamage): Unit = {
val AIDamage(_, attackerGuid, projectileTypeId, _, _) = pkt
sessionData.validObject(attackerGuid, decorator = "AIDamage/Entity")
.collect {
case turret: AutomatedTurret with OwnableByPlayer =>
val owner = sessionData.validObject(turret.OwnerGuid, decorator = "AIDamage/Owner")
.collect { case obj: PlanetSideGameObject with FactionAffinity => SourceEntry(obj) }
.getOrElse { SourceEntry.None }
turret.Weapons
.values
.flatMap { _.Equipment }
.collect { case weapon: Tool => (turret, weapon, owner, weapon.Projectile) }
.find { case (_, _, _, p) => p.ObjectId == projectileTypeId }
}
.collect {
case Some((obj, tool, owner, projectileInfo)) =>
val target = sessionData.validObject(player.VehicleSeated, decorator = "AIDamage/Entity") match {
case Some(tobj: PlanetSideGameObject with FactionAffinity with Vitality) => tobj
case _ => player
}
val angle = Vector3.Unit(target.Position - obj.Position)
val proj = new Projectile(
projectileInfo,
tool.Definition,
tool.FireMode,
None,
owner,
obj.Definition.ObjectId,
obj.Position + Vector3.z(value = 1f),
angle,
Some(angle * projectileInfo.FinalVelocity)
)
val hitPos = target.Position + Vector3.z(value = 1f)
ResolveProjectileInteraction(proj, DamageResolution.Hit, target, hitPos).collect { resprojectile =>
addShotsLanded(resprojectile.cause.attribution, shots = 1)
sessionData.handleDealingDamage(target, resprojectile)
}
}
}
/* support code */
def HandleWeaponFireOperations(

View file

@ -7,7 +7,8 @@ import net.psforever.objects.{Default, Player}
import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.sourcing.{SourceEntry, SourceUniqueness}
import net.psforever.objects.vital.Vitality
import net.psforever.packet.game.{ChangeFireStateMessage_Start, ObjectDetectedMessage}
import net.psforever.objects.zones.Zone
import net.psforever.packet.game.{ChangeFireStateMessage_Start, ChangeFireStateMessage_Stop, ObjectDetectedMessage}
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
import net.psforever.types.{PlanetSideGUID, Vector3}
@ -100,14 +101,8 @@ trait AutomatedTurretBehavior {
private def newDetectedTarget(target: Target): Unit = {
target match {
case target: Player =>
target.Zone.AvatarEvents ! AvatarServiceMessage(
target.Name,
AvatarAction.SendResponse(PlanetSideGUID(0), ObjectDetectedMessage(AutomatedTurretObject.GUID, AutomatedTurretObject.GUID, 0, List(target.GUID)))
)
target.Zone.AvatarEvents ! AvatarServiceMessage(
target.Name,
AvatarAction.SendResponse(PlanetSideGUID(0), ChangeFireStateMessage_Start(AutomatedTurretObject.Weapons.values.head.Equipment.get.GUID))
)
startTrackingTargets(target.Zone, target.Name, List(target.GUID))
startShooting(target.Zone, target.Name, AutomatedTurretObject.Weapons.values.head.Equipment.get.GUID)
case _ => ()
}
}
@ -115,14 +110,40 @@ trait AutomatedTurretBehavior {
private def noLongerDetectedTarget(target: Target): Unit = {
target match {
case target: Player =>
target.Zone.AvatarEvents ! AvatarServiceMessage(
target.Name,
AvatarAction.SendResponse(PlanetSideGUID(0), ObjectDetectedMessage(AutomatedTurretObject.GUID, AutomatedTurretObject.GUID, 0, List(PlanetSideGUID(0))))
)
stopTrackingTargets(target.Zone, target.Name)
stopShooting(target.Zone, target.Name, AutomatedTurretObject.Weapons.values.head.Equipment.get.GUID)
case _ => ()
}
}
def startTrackingTargets(zone: Zone, channel: String, list: List[PlanetSideGUID]): Unit = {
zone.AvatarEvents ! AvatarServiceMessage(
channel,
AvatarAction.SendResponse(PlanetSideGUID(0), ObjectDetectedMessage(AutomatedTurretObject.GUID, AutomatedTurretObject.GUID, 0, list))
)
}
def stopTrackingTargets(zone: Zone, channel: String): Unit = {
zone.AvatarEvents ! AvatarServiceMessage(
channel,
AvatarAction.SendResponse(PlanetSideGUID(0), ObjectDetectedMessage(AutomatedTurretObject.GUID, AutomatedTurretObject.GUID, 0, List(PlanetSideGUID(0))))
)
}
def startShooting(zone: Zone, channel: String, weaponGuid: PlanetSideGUID): Unit = {
zone.AvatarEvents ! AvatarServiceMessage(
channel,
AvatarAction.SendResponse(PlanetSideGUID(0), ChangeFireStateMessage_Start(weaponGuid))
)
}
def stopShooting(zone: Zone, channel: String, weaponGuid: PlanetSideGUID): Unit = {
zone.AvatarEvents ! AvatarServiceMessage(
channel,
AvatarAction.SendResponse(PlanetSideGUID(0), ChangeFireStateMessage_Stop(weaponGuid))
)
}
private def testDistanceCheckQualifications(beforeSize: Int): Unit = {
if (beforeSize > 0 && AutomatedTurretObject.Targets.isEmpty) {
targetDistanceCheck.cancel()

View file

@ -411,7 +411,7 @@ object GamePacketOpcode extends Enumeration {
case 0x59 => noDecoder(UnknownMessage89)
case 0x5a => game.DelayedPathMountMsg.decode
case 0x5b => game.OrbitalShuttleTimeMsg.decode
case 0x5c => noDecoder(AIDamage)
case 0x5c => game.AIDamage.decode
case 0x5d => game.DeployObjectMessage.decode
case 0x5e => game.FavoritesRequest.decode
case 0x5f => noDecoder(FavoritesResponse)

View file

@ -0,0 +1,32 @@
// Copyright (c) 2023 PSForever
package net.psforever.packet.game
import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket}
import net.psforever.types.PlanetSideGUID
import scodec.Codec
import scodec.codecs._
/**
* ...
*/
final case class AIDamage(
target_guid: PlanetSideGUID,
attacker_guid: PlanetSideGUID,
projectile_type: Long,
unk1: Long,
unk2: Long
) extends PlanetSideGamePacket {
type Packet = ActionResultMessage
def opcode = GamePacketOpcode.AIDamage
def encode = AIDamage.encode(this)
}
object AIDamage extends Marshallable[AIDamage] {
implicit val codec: Codec[AIDamage] = (
("target_guid" | PlanetSideGUID.codec) ::
("attacker_guid" | PlanetSideGUID.codec) ::
("projectile_type" | ulongL(bits = 32)) ::
("unk1" | ulongL(bits = 32)) ::
("unk2" | ulongL(bits = 32))
).as[AIDamage]
}

View file

@ -0,0 +1,32 @@
// Copyright (c) 2023 PSForever
package game
import org.specs2.mutable._
import net.psforever.packet._
import net.psforever.packet.game._
import net.psforever.types.PlanetSideGUID
import scodec.bits._
class AIDamageTest extends Specification {
val string1 = hex"5c de10 89e8 38030000 00000000 04020000"
"decode" in {
PacketCoding.decodePacket(string1).require match {
case AIDamage(target_guid, attacker_guid, projectile_type, unk1, unk2) =>
target_guid mustEqual PlanetSideGUID(4318)
attacker_guid mustEqual PlanetSideGUID(59529)
projectile_type mustEqual 824L
unk1 mustEqual 0L
unk2 mustEqual 516L
case _ =>
ko
}
}
"encode" in {
val msg = AIDamage(PlanetSideGUID(4318), PlanetSideGUID(59529), 824L, 0L, 516L)
val pkt = PacketCoding.encodePacket(msg).require.toByteVector
pkt mustEqual string1
}
}