using force psm occlusion to eliminate certain other packets that would be fine if hidden

This commit is contained in:
Fate-JH 2023-06-01 23:13:05 -04:00
parent 6f4ceaee29
commit 0628b988fe
9 changed files with 433 additions and 184 deletions

View file

@ -3,6 +3,7 @@ package net.psforever.actors.session.support
import akka.actor.typed.scaladsl.adapter._
import akka.actor.{ActorContext, typed}
import net.psforever.packet.game.objectcreate.ConstructorData
import net.psforever.services.Service
import scala.collection.mutable
@ -73,9 +74,9 @@ class SessionAvatarHandlers(
canSeeReallyFar
) if isNotSameTarget =>
val pstateToSave = pstate.copy(timestamp = 0)
val (lastMsg, lastTime, lastPosition, wasVisible) = lastSeenStreamMessage.get(guid.guid) match {
case Some(SessionAvatarHandlers.LastUpstream(Some(msg), visible, time)) => (Some(msg), time, msg.pos, visible)
case _ => (None, 0L, Vector3.Zero, false)
val (lastMsg, lastTime, lastPosition, wasVisible, wasShooting) = lastSeenStreamMessage.get(guid.guid) match {
case Some(SessionAvatarHandlers.LastUpstream(Some(msg), visible, shooting, time)) => (Some(msg), time, msg.pos, visible, shooting)
case _ => (None, 0L, Vector3.Zero, false, None)
}
val drawConfig = Config.app.game.playerDraw //m
val maxRange = drawConfig.rangeMax * drawConfig.rangeMax //sq.m
@ -129,10 +130,10 @@ class SessionAvatarHandlers(
isCloaking
)
)
lastSeenStreamMessage.put(guid.guid, SessionAvatarHandlers.LastUpstream(Some(pstateToSave), visible=true, now))
lastSeenStreamMessage.put(guid.guid, SessionAvatarHandlers.LastUpstream(Some(pstateToSave), visible=true, wasShooting, now))
} else {
//is visible, but skip reinforcement
lastSeenStreamMessage.put(guid.guid, SessionAvatarHandlers.LastUpstream(Some(pstateToSave), visible=true, lastTime))
lastSeenStreamMessage.put(guid.guid, SessionAvatarHandlers.LastUpstream(Some(pstateToSave), visible=true, wasShooting, lastTime))
}
} else {
//conditions where the target is not currently visible
@ -151,10 +152,10 @@ class SessionAvatarHandlers(
is_cloaked = isCloaking
)
)
lastSeenStreamMessage.put(guid.guid, SessionAvatarHandlers.LastUpstream(Some(pstateToSave), visible=false, now))
lastSeenStreamMessage.put(guid.guid, SessionAvatarHandlers.LastUpstream(Some(pstateToSave), visible=false, wasShooting, now))
} else {
//skip drawing altogether
lastSeenStreamMessage.put(guid.guid, SessionAvatarHandlers.LastUpstream(Some(pstateToSave), visible=false, lastTime))
lastSeenStreamMessage.put(guid.guid, SessionAvatarHandlers.LastUpstream(Some(pstateToSave), visible=false, wasShooting, lastTime))
}
}
@ -179,10 +180,24 @@ class SessionAvatarHandlers(
case AvatarResponse.ObjectHeld(_, previousSlot) =>
sendResponse(ObjectHeldMessage(guid, previousSlot, unk1=false))
case AvatarResponse.ChangeFireState_Start(weaponGuid) if isNotSameTarget =>
case AvatarResponse.ChangeFireState_Start(weaponGuid)
if isNotSameTarget && lastSeenStreamMessage.get(guid.guid).exists { _.visible } =>
sendResponse(ChangeFireStateMessage_Start(weaponGuid))
val entry = lastSeenStreamMessage(guid.guid)
lastSeenStreamMessage.put(guid.guid, entry.copy(shooting = Some(weaponGuid)))
case AvatarResponse.ChangeFireState_Start(weaponGuid)
if isNotSameTarget =>
sendResponse(ChangeFireStateMessage_Start(weaponGuid))
case AvatarResponse.ChangeFireState_Stop(weaponGuid) if isNotSameTarget =>
case AvatarResponse.ChangeFireState_Stop(weaponGuid)
if isNotSameTarget && lastSeenStreamMessage.get(guid.guid).exists { msg => msg.visible || msg.shooting.nonEmpty } =>
sendResponse(ChangeFireStateMessage_Stop(weaponGuid))
val entry = lastSeenStreamMessage(guid.guid)
lastSeenStreamMessage.put(guid.guid, entry.copy(shooting = None))
case AvatarResponse.ChangeFireState_Stop(weaponGuid)
if isNotSameTarget =>
sendResponse(ChangeFireStateMessage_Stop(weaponGuid))
case AvatarResponse.LoadPlayer(pkt) if isNotSameTarget =>
@ -388,7 +403,8 @@ class SessionAvatarHandlers(
sendResponse(msg)
/* common messages (maybe once every respawn) */
case AvatarResponse.Reload(itemGuid) if isNotSameTarget =>
case AvatarResponse.Reload(itemGuid)
if isNotSameTarget && lastSeenStreamMessage.get(guid.guid).exists { _.visible } =>
sendResponse(ReloadMessage(itemGuid, ammo_clip=1, unk1=0))
case AvatarResponse.Killed(mount) =>
@ -467,18 +483,14 @@ class SessionAvatarHandlers(
)
/* uncommon messages (utility, or once in a while) */
case AvatarResponse.ChangeAmmo(weapon_guid, weapon_slot, previous_guid, ammo_id, ammo_guid, ammo_data)
if isNotSameTarget && lastSeenStreamMessage.get(guid.guid).exists { _.visible } =>
changeAmmoProcedures(weapon_guid, previous_guid, ammo_id, ammo_guid, weapon_slot, ammo_data)
sendResponse(ChangeAmmoMessage(weapon_guid, 1))
case AvatarResponse.ChangeAmmo(weapon_guid, weapon_slot, previous_guid, ammo_id, ammo_guid, ammo_data)
if isNotSameTarget =>
sendResponse(ObjectDetachMessage(weapon_guid, previous_guid, Vector3.Zero, 0))
sendResponse(
ObjectCreateMessage(
ammo_id,
ammo_guid,
ObjectCreateMessageParent(weapon_guid, weapon_slot),
ammo_data
)
)
sendResponse(ChangeAmmoMessage(weapon_guid, 1))
changeAmmoProcedures(weapon_guid, previous_guid, ammo_id, ammo_guid, weapon_slot, ammo_data)
case AvatarResponse.ChangeFireMode(itemGuid, mode) if isNotSameTarget =>
sendResponse(ChangeFireModeMessage(itemGuid, mode))
@ -546,21 +558,45 @@ class SessionAvatarHandlers(
)
)
case AvatarResponse.WeaponDryFire(weaponGuid) if isNotSameTarget =>
case AvatarResponse.WeaponDryFire(weaponGuid)
if isNotSameTarget && lastSeenStreamMessage.get(guid.guid).exists { _.visible } =>
continent.GUID(weaponGuid).collect {
case tool: Tool if tool.Magazine == 0 =>
// check that the magazine is still empty before sending WeaponDryFireMessage
// if it has been reloaded since then, other clients will not see it firing
sendResponse(WeaponDryFireMessage(weaponGuid))
case _ =>
sendResponse(WeaponDryFireMessage(weaponGuid))
}
case _ => ()
}
}
private def changeAmmoProcedures(
weaponGuid: PlanetSideGUID,
previousAmmoGuid: PlanetSideGUID,
ammoTypeId: Int,
ammoGuid: PlanetSideGUID,
ammoSlot: Int,
ammoData: ConstructorData
): Unit = {
sendResponse(ObjectDetachMessage(weaponGuid, previousAmmoGuid, Vector3.Zero, 0))
//TODO? sendResponse(ObjectDeleteMessage(previousAmmoGuid, 0))
sendResponse(
ObjectCreateMessage(
ammoTypeId,
ammoGuid,
ObjectCreateMessageParent(weaponGuid, ammoSlot),
ammoData
)
)
}
}
object SessionAvatarHandlers {
private[support] case class LastUpstream(msg: Option[AvatarResponse.PlayerState], visible: Boolean, time: Long)
private[support] case class LastUpstream(
msg: Option[AvatarResponse.PlayerState],
visible: Boolean,
shooting: Option[PlanetSideGUID],
time: Long
)
}

View file

@ -7,7 +7,7 @@ import net.psforever.objects.equipment.{Equipment, JammableMountedWeapons, Jamma
import net.psforever.objects.guid.{GUIDTask, TaskWorkflow}
import net.psforever.objects.serverobject.mount.Mountable
import net.psforever.objects.serverobject.pad.VehicleSpawnPad
import net.psforever.objects.{GlobalDefinitions, Player, Vehicle, Vehicles}
import net.psforever.objects.{GlobalDefinitions, Player, Tool, Vehicle, Vehicles}
import net.psforever.packet.game.objectcreate.ObjectCreateMessageParent
import net.psforever.packet.game._
import net.psforever.services.Service
@ -79,6 +79,36 @@ class SessionVehicleHandlers(
if isNotSameTarget =>
sendResponse(FrameVehicleStateMessage(vguid, u1, pos, oient, vel, u2, u3, u4, is_crouched, u6, u7, u8, u9, uA))
case VehicleResponse.ChangeFireState_Start(weaponGuid) if isNotSameTarget =>
sendResponse(ChangeFireStateMessage_Start(weaponGuid))
case VehicleResponse.ChangeFireState_Stop(weaponGuid) if isNotSameTarget =>
sendResponse(ChangeFireStateMessage_Stop(weaponGuid))
case VehicleResponse.Reload(itemGuid) if isNotSameTarget =>
sendResponse(ReloadMessage(itemGuid, ammo_clip=1, unk1=0))
case VehicleResponse.ChangeAmmo(weapon_guid, weapon_slot, previous_guid, ammo_id, ammo_guid, ammo_data) if isNotSameTarget =>
sendResponse(ObjectDetachMessage(weapon_guid, previous_guid, Vector3.Zero, 0))
//TODO? sendResponse(ObjectDeleteMessage(previousAmmoGuid, 0))
sendResponse(
ObjectCreateMessage(
ammo_id,
ammo_guid,
ObjectCreateMessageParent(weapon_guid, weapon_slot),
ammo_data
)
)
sendResponse(ChangeAmmoMessage(weapon_guid, 1))
case VehicleResponse.WeaponDryFire(weaponGuid) if isNotSameTarget =>
continent.GUID(weaponGuid).collect {
case tool: Tool if tool.Magazine == 0 =>
// check that the magazine is still empty before sending WeaponDryFireMessage
// if it has been reloaded since then, other clients will not see it firing
sendResponse(WeaponDryFireMessage(weaponGuid))
}
case VehicleResponse.DismountVehicle(bailType, wasKickedByDriver) if isNotSameTarget =>
sendResponse(DismountVehicleMsg(guid, bailType, wasKickedByDriver))
@ -330,10 +360,10 @@ class SessionVehicleHandlers(
}
private def changeLoadoutDeleteOldEquipment(
vehicle: Vehicle,
oldWeapons: Iterable[(Equipment, PlanetSideGUID)],
oldInventory: Iterable[(Equipment, PlanetSideGUID)]
): Unit = {
vehicle: Vehicle,
oldWeapons: Iterable[(Equipment, PlanetSideGUID)],
oldInventory: Iterable[(Equipment, PlanetSideGUID)]
): Unit = {
vehicle.PassengerInSeat(player) match {
case Some(seatNum) =>
//participant: observe changes to equipment

View file

@ -271,10 +271,6 @@ class VehicleOperations(
sessionData.validObject(mountable_guid, decorator = "MountVehicle").collect {
case obj: Mountable =>
obj.Actor ! Mountable.TryMount(player, entry_point)
case _: Mountable =>
log.warn(
s"DismountVehicleMsg: ${player.Name} can not mount while server has asserted control; please wait"
)
case _ =>
log.error(s"MountVehicleMsg: object ${mountable_guid.guid} not a mountable thing, ${player.Name}")
}

View file

@ -74,19 +74,28 @@ private[support] class WeaponAndProjectileOperations(
def handleWeaponDryFire(pkt: WeaponDryFireMessage): Unit = {
val WeaponDryFireMessage(weapon_guid) = pkt
FindWeapon
val (containerOpt, tools) = FindContainedWeapon
tools
.find { _.GUID == weapon_guid }
.orElse { continent.GUID(weapon_guid) } match {
case Some(_: Equipment) =>
continent.AvatarEvents ! AvatarServiceMessage(
continent.id,
AvatarAction.WeaponDryFire(player.GUID, weapon_guid)
)
case _ =>
.orElse { continent.GUID(weapon_guid) }
.collect {
case _: Equipment if containerOpt.exists(_.isInstanceOf[Player]) =>
continent.AvatarEvents ! AvatarServiceMessage(
continent.id,
AvatarAction.WeaponDryFire(player.GUID, weapon_guid)
)
case _: Equipment =>
continent.VehicleEvents ! VehicleServiceMessage(
continent.id,
VehicleAction.WeaponDryFire(player.GUID, weapon_guid)
)
}
.orElse {
log.warn(
s"WeaponDryFire: ${player.Name}'s weapon ${weapon_guid.guid} is either not a weapon or does not exist"
)
}
None
}
}
def handleWeaponLazeTargetPosition(pkt: WeaponLazeTargetPositionMessage): Unit = {
@ -111,45 +120,16 @@ private[support] class WeaponAndProjectileOperations(
val ChangeFireStateMessage_Start(item_guid) = pkt
if (shooting.isEmpty) {
sessionData.findEquipment(item_guid) match {
case Some(tool: Tool) if player.VehicleSeated.isEmpty =>
fireStateStartWhenPlayer(tool, item_guid)
case Some(tool: Tool) =>
if (tool.FireMode.RoundsPerShot == 0 || tool.Magazine > 0 || prefire.contains(item_guid)) {
prefire -= item_guid
shooting += item_guid
shootingStart += item_guid -> System.currentTimeMillis()
ongoingShotsFired = 0
//special case - suppress the decimator's alternate fire mode, by projectile
if (tool.Projectile != GlobalDefinitions.phoenix_missile_guided_projectile) {
continent.AvatarEvents ! AvatarServiceMessage(
continent.id,
AvatarAction.ChangeFireState_Start(player.GUID, item_guid)
)
}
//charge ammunition drain
tool.FireMode match {
case mode: ChargeFireModeDefinition =>
sessionData.progressBarValue = Some(0f)
sessionData.progressBarUpdate = context.system.scheduler.scheduleOnce(
(mode.Time + mode.DrainInterval) milliseconds,
context.self,
SessionActor.ProgressEvent(1f, () => {}, Tools.ChargeFireMode(player, tool), mode.DrainInterval)
)
case _ => ;
}
} else {
log.warn(
s"ChangeFireState_Start: ${player.Name}'s ${tool.Definition.Name} magazine was empty before trying to shoot"
)
EmptyMagazine(item_guid, tool)
}
case Some(_) => //permissible, for now
prefire -= item_guid
shooting += item_guid
shootingStart += item_guid -> System.currentTimeMillis()
ongoingShotsFired = 0
continent.AvatarEvents ! AvatarServiceMessage(
continent.id,
AvatarAction.ChangeFireState_Start(player.GUID, item_guid)
)
fireStateStartWhenMounted(tool, item_guid)
case Some(_) if player.VehicleSeated.isEmpty =>
fireStateStartSetup(item_guid)
fireStateStartPlayerMessages(item_guid)
case Some(_) =>
fireStateStartSetup(item_guid)
fireStateStartMountedMessages(item_guid)
case None =>
log.warn(s"ChangeFireState_Start: can not find $item_guid")
}
@ -162,48 +142,23 @@ private[support] class WeaponAndProjectileOperations(
prefire -= item_guid
shootingStop += item_guid -> now
shooting -= item_guid
val pguid = player.GUID
sessionData.findEquipment(item_guid) match {
case Some(tool: Tool) if player.VehicleSeated.isEmpty =>
fireStateStopWhenPlayer(tool, item_guid)
case Some(tool: Tool) =>
//the decimator does not send a ChangeFireState_Start on the last shot; heaven knows why
if (
tool.Definition == GlobalDefinitions.phoenix &&
tool.Projectile != GlobalDefinitions.phoenix_missile_guided_projectile
) {
//suppress the decimator's alternate fire mode, however
continent.AvatarEvents ! AvatarServiceMessage(
continent.id,
AvatarAction.ChangeFireState_Start(pguid, item_guid)
)
shootingStart += item_guid -> (now - 1L)
}
avatarActor ! AvatarActor.UpdateToolDischarge(EquipmentStat(tool.Definition.ObjectId,ongoingShotsFired,0,0))
tool.FireMode match {
case _: ChargeFireModeDefinition =>
sendResponse(QuantityUpdateMessage(tool.AmmoSlot.Box.GUID, tool.Magazine))
case _ => ;
}
if (tool.Magazine == 0) {
FireCycleCleanup(tool)
}
continent.AvatarEvents ! AvatarServiceMessage(
continent.id,
AvatarAction.ChangeFireState_Stop(pguid, item_guid)
)
fireStateStopWhenMounted(tool, item_guid)
case Some(trigger: BoomerTrigger) =>
continent.AvatarEvents ! AvatarServiceMessage(
continent.id,
AvatarAction.ChangeFireState_Start(pguid, item_guid)
)
continent.GUID(trigger.Companion) match {
case Some(boomer: BoomerDeployable) =>
fireStateStopPlayerMessages(item_guid)
continent.GUID(trigger.Companion).collect {
case boomer: BoomerDeployable =>
boomer.Actor ! CommonMessages.Use(player, Some(trigger))
case Some(_) | None => ;
}
case _ => ;
//log.warn(s"ChangeFireState_Stop: ${player.Name} never started firing item ${item_guid.guid} in the first place?")
case Some(_) if player.VehicleSeated.isEmpty =>
fireStateStopPlayerMessages(item_guid)
case Some(_) =>
fireStateStopMountedMessages(item_guid)
case _ => ()
log.warn(s"ChangeFireState_Stop: can not find $item_guid")
}
sessionData.progressBarUpdate.cancel()
sessionData.progressBarValue = None
@ -212,54 +167,10 @@ private[support] class WeaponAndProjectileOperations(
def handleReload(pkt: ReloadMessage): Unit = {
val ReloadMessage(item_guid, _, unk1) = pkt
FindContainedWeapon match {
case (Some(obj: Player), tools) =>
handleReloadWhenPlayer(item_guid, obj, tools, unk1)
case (Some(obj: PlanetSideServerObject with Container), tools) =>
tools.filter { _.GUID == item_guid }.foreach { tool =>
val currentMagazine : Int = tool.Magazine
val magazineSize : Int = tool.MaxMagazine
val reloadValue : Int = magazineSize - currentMagazine
if (magazineSize > 0 && reloadValue > 0) {
FindEquipmentStock(obj, FindAmmoBoxThatUses(tool.AmmoType), reloadValue, CountAmmunition).reverse match {
case Nil => ;
case x :: xs =>
val (deleteFunc, modifyFunc) : (Equipment => Future[Any], (AmmoBox, Int) => Unit) = obj match {
case veh : Vehicle =>
(RemoveOldEquipmentFromInventory(veh)(_), ModifyAmmunitionInVehicle(veh)(_, _))
case _ =>
(RemoveOldEquipmentFromInventory(obj)(_), ModifyAmmunition(obj)(_, _))
}
xs.foreach { item => deleteFunc(item.obj) }
val box = x.obj.asInstanceOf[AmmoBox]
val tailReloadValue : Int = if (xs.isEmpty) {
0
}
else {
xs.map(_.obj.asInstanceOf[AmmoBox].Capacity).sum
}
val sumReloadValue : Int = box.Capacity + tailReloadValue
val actualReloadValue = if (sumReloadValue <= reloadValue) {
deleteFunc(box)
sumReloadValue
}
else {
modifyFunc(box, reloadValue - tailReloadValue)
reloadValue
}
val finalReloadValue = actualReloadValue + currentMagazine
log.info(
s"${player.Name} successfully reloaded $reloadValue ${tool.AmmoType} into ${tool.Definition.Name}"
)
tool.Magazine = finalReloadValue
sendResponse(ReloadMessage(item_guid, finalReloadValue, unk1))
continent.AvatarEvents ! AvatarServiceMessage(
continent.id,
AvatarAction.Reload(player.GUID, item_guid)
)
}
} else {
//the weapon can not reload due to full magazine; the UI for the magazine is obvious bugged, so fix it
sendResponse(QuantityUpdateMessage(tool.AmmoSlot.Box.GUID, magazineSize))
}
}
handleReloadWhenMountable(item_guid, obj, tools, unk1)
case (_, _) =>
log.warn(s"ReloadMessage: either can not find $item_guid or the object found was not a Tool")
}
@ -272,17 +183,19 @@ private[support] class WeaponAndProjectileOperations(
log.warn(s"ChangeAmmo: either can not find $item_guid or the object found was not Equipment")
} else {
equipment foreach {
case obj : ConstructionItem =>
case obj: ConstructionItem =>
if (Deployables.performConstructionItemAmmoChange(player.avatar.certifications, obj, obj.AmmoTypeIndex)) {
log.info(
s"${player.Name} switched ${player.Sex.possessive} ${obj.Definition.Name} to construct ${obj.AmmoType} (option #${obj.FireModeIndex})"
)
sendResponse(ChangeAmmoMessage(obj.GUID, obj.AmmoTypeIndex))
}
case tool : Tool =>
case tool: Tool =>
thing match {
case Some(correctThing: PlanetSideServerObject with Container) =>
PerformToolAmmoChange(tool, correctThing)
case Some(player: Player) =>
PerformToolAmmoChange(tool, player, ModifyAmmunition(player))
case Some(mountable: PlanetSideServerObject with Container) =>
PerformToolAmmoChange(tool, mountable, ModifyAmmunitionInMountable(mountable))
case _ =>
log.warn(s"ChangeAmmo: the ${thing.get.Definition.Name} in ${player.Name}'s is not the correct type")
}
@ -804,7 +717,7 @@ private[support] class WeaponAndProjectileOperations(
* @param reloadValue the value to modify the `AmmoBox`;
* subtracted from the current `Capacity` of `Box`
*/
def ModifyAmmunitionInVehicle(obj: Vehicle)(box: AmmoBox, reloadValue: Int): Unit = {
def ModifyAmmunitionInMountable(obj: PlanetSideServerObject with Container)(box: AmmoBox, reloadValue: Int): Unit = {
ModifyAmmunition(obj)(box, reloadValue)
obj.Find(box) match {
case Some(index) =>
@ -827,19 +740,19 @@ private[support] class WeaponAndProjectileOperations(
* @param tool na
* @param obj na
*/
def PerformToolAmmoChange(tool: Tool, obj: PlanetSideServerObject with Container): Unit = {
def PerformToolAmmoChange(
tool: Tool,
obj: PlanetSideServerObject with Container,
modifyFunc: (AmmoBox, Int) => Unit
): Unit = {
val originalAmmoType = tool.AmmoType
do {
val requestedAmmoType = tool.NextAmmoType
val fullMagazine = tool.MaxMagazine
if (requestedAmmoType != tool.AmmoSlot.Box.AmmoType) {
FindEquipmentStock(obj, FindAmmoBoxThatUses(requestedAmmoType), fullMagazine, CountAmmunition).reverse match {
case Nil => ;
case Nil => ()
case x :: xs =>
val modifyFunc: (AmmoBox, Int) => Unit = obj match {
case veh: Vehicle => ModifyAmmunitionInVehicle(veh)
case _ => ModifyAmmunition(obj)
}
val stowNewFunc: Equipment => TaskBundle = PutNewEquipmentInInventoryOrDrop(obj)
val stowFunc: Equipment => Future[Any] = PutEquipmentInInventoryOrDrop(obj)
@ -1231,13 +1144,233 @@ private[support] class WeaponAndProjectileOperations(
*/
def FindWeapon: Set[Tool] = FindContainedWeapon._2
/*
used by ChangeFireStateMessage_Start handling
*/
private def fireStateStartSetup(itemGuid: PlanetSideGUID): Unit = {
prefire -= itemGuid
shooting += itemGuid
shootingStart += itemGuid -> System.currentTimeMillis()
ongoingShotsFired = 0
}
private def fireStateStartChargeMode(tool: Tool): Unit = {
//charge ammunition drain
tool.FireMode match {
case mode: ChargeFireModeDefinition =>
sessionData.progressBarValue = Some(0f)
sessionData.progressBarUpdate = context.system.scheduler.scheduleOnce(
(mode.Time + mode.DrainInterval) milliseconds,
context.self,
SessionActor.ProgressEvent(1f, () => {}, Tools.ChargeFireMode(player, tool), mode.DrainInterval)
)
case _ => ()
}
}
private def fireStateStartPlayerMessages(itemGuid: PlanetSideGUID): Unit = {
continent.AvatarEvents ! AvatarServiceMessage(
continent.id,
AvatarAction.ChangeFireState_Start(player.GUID, itemGuid)
)
}
private def fireStateStartMountedMessages(itemGuid: PlanetSideGUID): Unit = {
continent.VehicleEvents ! VehicleServiceMessage(
continent.id,
VehicleAction.ChangeFireState_Start(player.GUID, itemGuid)
)
}
private def allowFireStateChangeStart(tool: Tool, itemGuid: PlanetSideGUID): Boolean = {
tool.FireMode.RoundsPerShot == 0 || tool.Magazine > 0 || prefire.contains(itemGuid)
}
private def enforceEmptyMagazine(tool: Tool, itemGuid: PlanetSideGUID): Unit = {
log.warn(
s"ChangeFireState_Start: ${player.Name}'s ${tool.Definition.Name} magazine was empty before trying to shoot"
)
EmptyMagazine(itemGuid, tool)
}
private def fireStateStartWhenPlayer(tool: Tool, itemGuid: PlanetSideGUID): Unit = {
if (allowFireStateChangeStart(tool, itemGuid)) {
fireStateStartSetup(itemGuid)
//special case - suppress the decimator's alternate fire mode, by projectile
if (tool.Projectile != GlobalDefinitions.phoenix_missile_guided_projectile) {
fireStateStartPlayerMessages(itemGuid)
}
fireStateStartChargeMode(tool)
} else {
enforceEmptyMagazine(tool, itemGuid)
}
}
private def fireStateStartWhenMounted(tool: Tool, itemGuid: PlanetSideGUID): Unit = {
if (allowFireStateChangeStart(tool, itemGuid)) {
fireStateStartSetup(itemGuid)
fireStateStartMountedMessages(itemGuid)
fireStateStartChargeMode(tool)
} else {
enforceEmptyMagazine(tool, itemGuid)
}
}
/*
used by ChangeFireStateMessage_Stop handling
*/
private def fireStateStopUpdateChargeAndCleanup(tool: Tool): Unit = {
avatarActor ! AvatarActor.UpdateToolDischarge(EquipmentStat(tool.Definition.ObjectId, ongoingShotsFired, 0, 0))
tool.FireMode match {
case _: ChargeFireModeDefinition =>
sendResponse(QuantityUpdateMessage(tool.AmmoSlot.Box.GUID, tool.Magazine))
case _ => ;
}
if (tool.Magazine == 0) {
FireCycleCleanup(tool)
}
}
private def fireStateStopPlayerMessages(itemGuid: PlanetSideGUID): Unit = {
continent.AvatarEvents ! AvatarServiceMessage(
continent.id,
AvatarAction.ChangeFireState_Stop(player.GUID, itemGuid)
)
}
private def fireStateStopMountedMessages(itemGuid: PlanetSideGUID): Unit = {
continent.VehicleEvents ! VehicleServiceMessage(
continent.id,
VehicleAction.ChangeFireState_Stop(player.GUID, itemGuid)
)
}
private def fireStateStopWhenPlayer(tool: Tool, itemGuid: PlanetSideGUID): Unit = {
//the decimator does not send a ChangeFireState_Start on the last shot; heaven knows why
//suppress the decimator's alternate fire mode, however
if (
tool.Definition == GlobalDefinitions.phoenix &&
tool.Projectile != GlobalDefinitions.phoenix_missile_guided_projectile
) {
fireStateStartPlayerMessages(itemGuid)
}
fireStateStopUpdateChargeAndCleanup(tool)
fireStateStopPlayerMessages(itemGuid)
}
private def fireStateStopWhenMounted(tool: Tool, itemGuid: PlanetSideGUID): Unit = {
fireStateStopUpdateChargeAndCleanup(tool)
fireStateStopMountedMessages(itemGuid)
}
/*
used by ReloadMessage handling
*/
private def reloadPlayerMessages(itemGuid: PlanetSideGUID): Unit = {
continent.AvatarEvents ! AvatarServiceMessage(
continent.id,
AvatarAction.Reload(player.GUID, itemGuid)
)
}
private def reloadVehicleMessages(itemGuid: PlanetSideGUID): Unit = {
continent.VehicleEvents ! VehicleServiceMessage(
continent.id,
VehicleAction.Reload(player.GUID, itemGuid)
)
}
private def handleReloadProcedure(
itemGuid: PlanetSideGUID,
obj: PlanetSideGameObject with Container,
tools: Set[Tool],
unk1: Int,
deleteFunc: Equipment => Future[Any],
modifyFunc: (AmmoBox, Int) => Unit,
messageFunc: PlanetSideGUID => Unit
): Unit = {
tools
.filter { _.GUID == itemGuid }
.foreach { tool =>
val currentMagazine : Int = tool.Magazine
val magazineSize : Int = tool.MaxMagazine
val reloadValue : Int = magazineSize - currentMagazine
if (magazineSize > 0 && reloadValue > 0) {
FindEquipmentStock(obj, FindAmmoBoxThatUses(tool.AmmoType), reloadValue, CountAmmunition).reverse match {
case Nil => ()
case x :: xs =>
xs.foreach { item => deleteFunc(item.obj) }
val box = x.obj.asInstanceOf[AmmoBox]
val tailReloadValue : Int = if (xs.isEmpty) {
0
}
else {
xs.map(_.obj.asInstanceOf[AmmoBox].Capacity).sum
}
val sumReloadValue : Int = box.Capacity + tailReloadValue
val actualReloadValue = if (sumReloadValue <= reloadValue) {
deleteFunc(box)
sumReloadValue
}
else {
modifyFunc(box, reloadValue - tailReloadValue)
reloadValue
}
val finalReloadValue = actualReloadValue + currentMagazine
log.info(
s"${player.Name} successfully reloaded $reloadValue ${tool.AmmoType} into ${tool.Definition.Name}"
)
tool.Magazine = finalReloadValue
sendResponse(ReloadMessage(itemGuid, finalReloadValue, unk1))
messageFunc(itemGuid)
}
} else {
//the weapon can not reload due to full magazine; the UI for the magazine is obvious bugged, so fix it
sendResponse(QuantityUpdateMessage(tool.AmmoSlot.Box.GUID, magazineSize))
}
}
}
private def handleReloadWhenPlayer(
itemGuid: PlanetSideGUID,
obj: Player,
tools: Set[Tool],
unk1: Int
): Unit = {
handleReloadProcedure(
itemGuid,
obj,
tools,
unk1,
RemoveOldEquipmentFromInventory(obj)(_),
ModifyAmmunition(obj)(_, _),
reloadPlayerMessages
)
}
private def handleReloadWhenMountable(
itemGuid: PlanetSideGUID,
obj: PlanetSideServerObject with Container,
tools: Set[Tool],
unk1: Int
): Unit = {
handleReloadProcedure(
itemGuid,
obj,
tools,
unk1,
RemoveOldEquipmentFromInventory(obj)(_),
ModifyAmmunitionInMountable(obj)(_, _),
reloadVehicleMessages
)
}
override protected[session] def stop(): Unit = {
if (player != null && player.HasGUID) {
(prefire ++ shooting).foreach { guid =>
continent.AvatarEvents ! AvatarServiceMessage(
continent.id,
AvatarAction.ChangeFireState_Stop(player.GUID, guid)
)
//do I need to do this? (maybe)
fireStateStopPlayerMessages(guid)
fireStateStopMountedMessages(guid)
}
projectiles.indices.foreach { projectiles.update(_, None) }
}

View file

@ -8156,7 +8156,7 @@ object GlobalDefinitions {
lightgunship.DrownAtMaxDepth = true
lightgunship.MaxDepth = 2 //flying vehicles will automatically disable
lightgunship.Geometry = GeometryForm.representByCylinder(radius = 2.375f, height = 1.98438f)
lightgunship.collision.avatarCollisionDamageMax = 750
lightgunship.collision.avatarCollisionDamageMax = 75
lightgunship.collision.xy = CollisionXYData(Array((0.1f, 1), (0.25f, 60), (0.5f, 120), (0.75f, 180), (1f, 250)))
lightgunship.collision.z = CollisionZData(Array((3f, 1), (9f, 30), (15f, 60), (18f, 90), (19.5f, 125)))
lightgunship.maxForwardSpeed = 104f

View file

@ -17,7 +17,7 @@ class AvatarService(zone: Zone) extends Actor {
val AvatarEvents = new GenericEventBus[AvatarServiceResponse] //AvatarEventBus
def receive = {
def receive: Receive = {
case Service.Join(channel) =>
val path = s"/$channel/Avatar"
val who = sender()

View file

@ -16,7 +16,7 @@ class VehicleService(zone: Zone) extends Actor {
val VehicleEvents = new GenericEventBus[VehicleServiceResponse]
def receive = {
def receive: Receive = {
case Service.Join(channel) =>
val path = s"/$channel/Vehicle"
VehicleEvents.subscribe(sender(), path)
@ -33,6 +33,26 @@ class VehicleService(zone: Zone) extends Actor {
case VehicleServiceMessage(forChannel, action) =>
action match {
case VehicleAction.ChangeAmmo(player_guid, weapon_guid, weapon_slot, old_ammo_guid, ammo_id, ammo_guid, ammo_data) =>
VehicleEvents.publish(
VehicleServiceResponse(
s"/$forChannel/Vehicle",
player_guid,
VehicleResponse.ChangeAmmo(weapon_guid, weapon_slot, old_ammo_guid, ammo_id, ammo_guid, ammo_data)
)
)
case VehicleAction.ChangeFireState_Start(player_guid, weapon_guid) =>
VehicleEvents.publish(
VehicleServiceResponse(
s"/$forChannel/Vehicle",
player_guid,
VehicleResponse.ChangeFireState_Start(weapon_guid)
)
)
case VehicleAction.ChangeFireState_Stop(player_guid, weapon_guid) =>
VehicleEvents.publish(
VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.ChangeFireState_Stop(weapon_guid))
)
case VehicleAction.ChildObjectState(player_guid, object_guid, pitch, yaw) =>
VehicleEvents.publish(
VehicleServiceResponse(
@ -176,6 +196,11 @@ class VehicleService(zone: Zone) extends Actor {
VehicleResponse.PlanetsideAttribute(target_guid, attribute_type, attribute_value)
)
)
case VehicleAction.Reload(player_guid, weapon_guid) =>
VehicleEvents.publish(
VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.Reload(weapon_guid))
)
case VehicleAction.SeatPermissions(player_guid, vehicle_guid, seat_group, permission) =>
VehicleEvents.publish(
VehicleServiceResponse(
@ -199,6 +224,10 @@ class VehicleService(zone: Zone) extends Actor {
)
)
)
case VehicleAction.WeaponDryFire(player_guid, weapon_guid) =>
VehicleEvents.publish(
VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.WeaponDryFire(weapon_guid))
)
case VehicleAction.UnloadVehicle(player_guid, vehicle, vehicle_guid) =>
VehicleEvents.publish(
VehicleServiceResponse(

View file

@ -23,6 +23,17 @@ object VehicleServiceMessage {
object VehicleAction {
trait Action
final case class ChangeAmmo(
player_guid: PlanetSideGUID,
weapon_guid: PlanetSideGUID,
weapon_slot: Int,
old_ammo_guid: PlanetSideGUID,
ammo_id: Int,
ammo_guid: PlanetSideGUID,
ammo_data: ConstructorData
) extends Action
final case class ChangeFireState_Start(player_guid: PlanetSideGUID, weapon_guid: PlanetSideGUID) extends Action
final case class ChangeFireState_Stop(player_guid: PlanetSideGUID, weapon_guid: PlanetSideGUID) extends Action
final case class ChildObjectState(player_guid: PlanetSideGUID, object_guid: PlanetSideGUID, pitch: Float, yaw: Float)
extends Action
final case class DeployRequest(
@ -89,6 +100,7 @@ object VehicleAction {
attribute_type: Int,
attribute_value: Long
) extends Action
final case class Reload(player_guid: PlanetSideGUID, weapon_guid: PlanetSideGUID) extends Action
final case class SeatPermissions(
player_guid: PlanetSideGUID,
vehicle_guid: PlanetSideGUID,
@ -118,6 +130,7 @@ object VehicleAction {
unk6: Boolean
) extends Action
final case class SendResponse(player_guid: PlanetSideGUID, msg: PlanetSideGamePacket) extends Action
final case class WeaponDryFire(player_guid: PlanetSideGUID, weapon_guid: PlanetSideGUID) extends Action
final case class UpdateAmsSpawnPoint(zone: Zone) extends Action
final case class TransferPassengerChannel(

View file

@ -22,6 +22,16 @@ final case class VehicleServiceResponse(
object VehicleResponse {
trait Response
final case class ChangeAmmo(
weapon_guid: PlanetSideGUID,
weapon_slot: Int,
old_ammo_guid: PlanetSideGUID,
ammo_id: Int,
ammo_guid: PlanetSideGUID,
ammo_data: ConstructorData
) extends Response
final case class ChangeFireState_Start(weapon_guid: PlanetSideGUID) extends Response
final case class ChangeFireState_Stop(weapon_guid: PlanetSideGUID) extends Response
final case class ChildObjectState(object_guid: PlanetSideGUID, pitch: Float, yaw: Float) extends Response
final case class ConcealPlayer(player_guid: PlanetSideGUID) extends Response
final case class DeployRequest(
@ -66,8 +76,9 @@ object VehicleResponse {
final case class Ownership(vehicle_guid: PlanetSideGUID) extends Response
final case class PlanetsideAttribute(vehicle_guid: PlanetSideGUID, attribute_type: Int, attribute_value: Long)
extends Response
final case class RevealPlayer(player_guid: PlanetSideGUID) extends Response
final case class SeatPermissions(vehicle_guid: PlanetSideGUID, seat_group: Int, permission: Long) extends Response
final case class Reload(weapon_guid: PlanetSideGUID) extends Response
final case class RevealPlayer(player_guid: PlanetSideGUID) extends Response
final case class SeatPermissions(vehicle_guid: PlanetSideGUID, seat_group: Int, permission: Long) extends Response
final case class StowEquipment(
vehicle_guid: PlanetSideGUID,
slot: Int,
@ -75,6 +86,7 @@ object VehicleResponse {
iguid: PlanetSideGUID,
idata: ConstructorData
) extends Response
final case class WeaponDryFire(weapon_guid: PlanetSideGUID) extends Response
final case class UnloadVehicle(vehicle: Vehicle, vehicle_guid: PlanetSideGUID) extends Response
final case class UnstowEquipment(item_guid: PlanetSideGUID) extends Response
final case class VehicleState(