csr's may now see players who are spectating, including csr's who are spectating, including a wide variety of their behavior such as location, weapon management, etc.; removes zoning cancel messages as a required log from certain use item actions

This commit is contained in:
Fate-JH 2024-10-22 20:33:37 -04:00
parent 9e0cc9ad65
commit 186f568c64
19 changed files with 703 additions and 754 deletions

View file

@ -6,8 +6,11 @@ import net.psforever.actors.session.SessionActor
import net.psforever.actors.session.normal.NormalMode
import net.psforever.actors.session.support.{ChatFunctions, ChatOperations, SessionData}
import net.psforever.objects.Session
import net.psforever.objects.zones.Zone
import net.psforever.packet.game.{ChatMsg, SetChatFilterMessage}
import net.psforever.services.chat.DefaultChannel
import net.psforever.services.Service
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
import net.psforever.services.chat.{ChatChannel, DefaultChannel, SpectatorChannel}
import net.psforever.types.ChatMessageType
object ChatLogic {
@ -21,6 +24,9 @@ class ChatLogic(val ops: ChatOperations, implicit val context: ActorContext) ext
ops.SpectatorMode = SpectateAsCustomerServiceRepresentativeMode
private var comms: ChatChannel = DefaultChannel
private var seeSpectatorsIn: Option[Zone] = None
def handleChatMsg(message: ChatMsg): Unit = {
import net.psforever.types.ChatMessageType._
val isAlive = if (player != null) player.isAlive else false
@ -74,40 +80,40 @@ class ChatLogic(val ops: ChatOperations, implicit val context: ActorContext) ext
ops.commandCaptureBase(session, message, contents)
case (CMT_GMBROADCAST | CMT_GMBROADCAST_NC | CMT_GMBROADCAST_VS | CMT_GMBROADCAST_TR, _, _) =>
ops.commandSendToRecipient(session, message, DefaultChannel)
ops.commandSendToRecipient(session, message, comms)
case (CMT_GMTELL, _, _) =>
ops.commandSend(session, message, DefaultChannel)
ops.commandSend(session, message, comms)
case (CMT_GMBROADCASTPOPUP, _, _) =>
ops.commandSendToRecipient(session, message, DefaultChannel)
ops.commandSendToRecipient(session, message, comms)
case (CMT_OPEN, _, _) if !player.silenced =>
ops.commandSendToRecipient(session, message, DefaultChannel)
ops.commandSendToRecipient(session, message, comms)
case (CMT_VOICE, _, contents) =>
ops.commandVoice(session, message, contents, DefaultChannel)
ops.commandVoice(session, message, contents, comms)
case (CMT_TELL, _, _) if !player.silenced =>
ops.commandTellOrIgnore(session, message, DefaultChannel)
ops.commandTellOrIgnore(session, message, comms)
case (CMT_BROADCAST, _, _) if !player.silenced =>
ops.commandSendToRecipient(session, message, DefaultChannel)
ops.commandSendToRecipient(session, message, comms)
case (CMT_PLATOON, _, _) if !player.silenced =>
ops.commandSendToRecipient(session, message, DefaultChannel)
ops.commandSendToRecipient(session, message, comms)
case (CMT_COMMAND, _, _) =>
ops.commandSendToRecipient(session, message, DefaultChannel)
ops.commandSendToRecipient(session, message, comms)
case (CMT_NOTE, _, _) =>
ops.commandSend(session, message, DefaultChannel)
ops.commandSend(session, message, comms)
case (CMT_SILENCE, _, _) =>
ops.commandSend(session, message, DefaultChannel)
ops.commandSend(session, message, comms)
case (CMT_SQUAD, _, _) =>
ops.commandSquad(session, message, DefaultChannel) //todo SquadChannel, but what is the guid
ops.commandSquad(session, message, comms) //todo SquadChannel, but what is the guid
case (CMT_WHO | CMT_WHO_CSR | CMT_WHO_CR | CMT_WHO_PLATOONLEADERS | CMT_WHO_SQUADLEADERS | CMT_WHO_TEAMS, _, _) =>
ops.commandWho(session)
@ -197,6 +203,10 @@ class ChatLogic(val ops: ChatOperations, implicit val context: ActorContext) ext
case "ntu" => ops.customCommandNtu(session, params)
case "zonerotate" => ops.customCommandZonerotate(params)
case "nearby" => ops.customCommandNearby(session)
case "togglespectators" => customCommandToggleSpectators(params)
case "showspectators" => customCommandShowSpectators()
case "hidespectators" => customCommandHideSpectators()
case "sayspectator" => customCommandSpeakAsSpectator(params, message)
case _ =>
// command was not handled
sendResponse(
@ -240,4 +250,68 @@ class ChatLogic(val ops: ChatOperations, implicit val context: ActorContext) ext
}
true
}
private def customCommandToggleSpectators(contents: Seq[String]): Boolean = {
contents
.headOption
.map(_.toLowerCase())
.collect {
case "on" | "o" | "" if !seeSpectatorsIn.contains(continent) =>
customCommandShowSpectators()
case "off" | "of" if seeSpectatorsIn.contains(continent) =>
customCommandHideSpectators()
case _ => ()
}
true
}
private def customCommandShowSpectators(): Boolean = {
val channel = player.Name
val events = continent.AvatarEvents
seeSpectatorsIn = Some(continent)
events ! Service.Join(s"spectator")
continent
.AllPlayers
.filter(_.spectator)
.foreach { spectator =>
val guid = spectator.GUID
val definition = spectator.Definition
events ! AvatarServiceMessage(
channel,
AvatarAction.LoadPlayer(guid, definition.ObjectId, guid, definition.Packet.ConstructorData(spectator).get, None)
)
}
true
}
private def customCommandHideSpectators(): Boolean = {
val channel = player.Name
val events = continent.AvatarEvents
seeSpectatorsIn = None
events ! Service.Leave(Some("spectator"))
continent
.AllPlayers
.filter(_.spectator)
.foreach { spectator =>
val guid = spectator.GUID
events ! AvatarServiceMessage(
channel,
AvatarAction.ObjectDelete(guid, guid)
)
}
true
}
private def customCommandSpeakAsSpectator(params: Seq[String], message: ChatMsg): Boolean = {
comms = SpectatorChannel
handleChatMsg(message.copy(contents = params.mkString(" ")))
comms = DefaultChannel
true
}
override def stop(): Unit = {
super.stop()
seeSpectatorsIn.foreach(_ => customCommandHideSpectators())
seeSpectatorsIn = None
}
}

View file

@ -151,30 +151,33 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
ops.unaccessContainer(container)
case _ => ()
}
if (!player.spectator) {
val channel = if (!player.spectator) {
sessionLogic.updateBlockMap(player, pos)
val eagleEye: Boolean = ops.canSeeReallyFar
val isNotVisible: Boolean = sessionLogic.zoning.zoningStatus == Zoning.Status.Deconstructing ||
(player.isAlive && sessionLogic.zoning.spawn.deadState == DeadState.RespawnTime)
continent.AvatarEvents ! AvatarServiceMessage(
continent.id,
AvatarAction.PlayerState(
avatarGuid,
player.Position,
player.Velocity,
yaw,
pitch,
yawUpper,
seqTime,
isCrouching,
isJumping,
jumpThrust,
isCloaking,
isNotVisible,
eagleEye
)
)
continent.id
} else {
"spectator"
}
val eagleEye: Boolean = ops.canSeeReallyFar
val isNotVisible: Boolean = sessionLogic.zoning.zoningStatus == Zoning.Status.Deconstructing ||
(player.isAlive && sessionLogic.zoning.spawn.deadState == DeadState.RespawnTime)
continent.AvatarEvents ! AvatarServiceMessage(
channel,
AvatarAction.PlayerState(
avatarGuid,
player.Position,
player.Velocity,
yaw,
pitch,
yawUpper,
seqTime,
isCrouching,
isJumping,
jumpThrust,
isCloaking,
isNotVisible,
eagleEye
)
)
sessionLogic.squad.updateSquad()
}
@ -294,55 +297,60 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
case _ =>
None
}
sessionLogic.validObject(pkt.object_guid, decorator = "UseItem") match {
case Some(door: Door) =>
handleUseDoor(door, equipment)
case Some(resourceSilo: ResourceSilo) =>
ops.handleUseResourceSilo(resourceSilo, equipment)
case Some(panel: IFFLock) =>
ops.handleUseGeneralEntity(panel, equipment)
case Some(obj: Player) =>
ops.handleUsePlayer(obj, equipment, pkt)
case Some(locker: Locker) =>
ops.handleUseLocker(locker, equipment, pkt)
case Some(gen: Generator) =>
ops.handleUseGeneralEntity(gen, equipment)
case Some(mech: ImplantTerminalMech) =>
ops.handleUseGeneralEntity(mech, equipment)
case Some(captureTerminal: CaptureTerminal) =>
ops.handleUseCaptureTerminal(captureTerminal, equipment)
case Some(obj: FacilityTurret) =>
ops.handleUseFacilityTurret(obj, equipment, pkt)
case Some(obj: Vehicle) =>
ops.handleUseVehicle(obj, equipment, pkt)
case Some(terminal: Terminal) =>
ops.handleUseTerminal(terminal, equipment, pkt)
case Some(obj: SpawnTube) =>
ops.handleUseSpawnTube(obj, equipment)
case Some(obj: SensorDeployable) =>
ops.handleUseGeneralEntity(obj, equipment)
case Some(obj: TurretDeployable) =>
ops.handleUseGeneralEntity(obj, equipment)
case Some(obj: TrapDeployable) =>
ops.handleUseGeneralEntity(obj, equipment)
case Some(obj: ShieldGeneratorDeployable) =>
ops.handleUseGeneralEntity(obj, equipment)
case Some(obj: TelepadDeployable) if player.spectator =>
ops.handleUseTelepadDeployable(obj, equipment, pkt, ops.useRouterTelepadSystemSecretly)
case Some(obj: Utility.InternalTelepad) if player.spectator =>
ops.handleUseInternalTelepad (obj, pkt, ops.useRouterTelepadSystemSecretly)
case Some(obj: TelepadDeployable) =>
ops.handleUseTelepadDeployable(obj, equipment, pkt, ops.useRouterTelepadSystem)
case Some(obj: Utility.InternalTelepad) =>
ops.handleUseInternalTelepad (obj, pkt, ops.useRouterTelepadSystem)
case Some(obj: CaptureFlag) =>
ops.handleUseCaptureFlag(obj)
case Some(_: WarpGate) =>
ops.handleUseWarpGate(equipment)
case Some(obj) =>
ops.handleUseDefaultEntity(obj, equipment)
case None => ()
}
cancelZoningWhenGeneralHandled(
sessionLogic.validObject(pkt.object_guid, decorator = "UseItem") match {
case Some(door: Door) =>
handleUseDoor(door, equipment)
GeneralOperations.UseItem.Unhandled
case Some(resourceSilo: ResourceSilo) =>
ops.handleUseResourceSilo(resourceSilo, equipment)
case Some(panel: IFFLock) =>
ops.handleUseGeneralEntity(panel, equipment)
case Some(obj: Player) =>
ops.handleUsePlayer(obj, equipment, pkt)
GeneralOperations.UseItem.Unhandled
case Some(locker: Locker) =>
ops.handleUseLocker(locker, equipment, pkt)
case Some(gen: Generator) =>
ops.handleUseGeneralEntity(gen, equipment)
case Some(mech: ImplantTerminalMech) =>
ops.handleUseGeneralEntity(mech, equipment)
case Some(captureTerminal: CaptureTerminal) =>
ops.handleUseCaptureTerminal(captureTerminal, equipment)
case Some(obj: FacilityTurret) =>
ops.handleUseFacilityTurret(obj, equipment, pkt)
case Some(obj: Vehicle) =>
ops.handleUseVehicle(obj, equipment, pkt)
case Some(terminal: Terminal) =>
ops.handleUseTerminal(terminal, equipment, pkt)
case Some(obj: SpawnTube) =>
ops.handleUseSpawnTube(obj, equipment)
case Some(obj: SensorDeployable) =>
ops.handleUseGeneralEntity(obj, equipment)
case Some(obj: TurretDeployable) =>
ops.handleUseGeneralEntity(obj, equipment)
case Some(obj: TrapDeployable) =>
ops.handleUseGeneralEntity(obj, equipment)
case Some(obj: ShieldGeneratorDeployable) =>
ops.handleUseGeneralEntity(obj, equipment)
case Some(obj: TelepadDeployable) if player.spectator =>
ops.handleUseTelepadDeployable(obj, equipment, pkt, ops.useRouterTelepadSystemSecretly)
case Some(obj: Utility.InternalTelepad) if player.spectator =>
ops.handleUseInternalTelepad(obj, pkt, ops.useRouterTelepadSystemSecretly)
case Some(obj: TelepadDeployable) =>
ops.handleUseTelepadDeployable(obj, equipment, pkt, ops.useRouterTelepadSystem)
case Some(obj: Utility.InternalTelepad) =>
ops.handleUseInternalTelepad(obj, pkt, ops.useRouterTelepadSystem)
case Some(obj: CaptureFlag) =>
ops.handleUseCaptureFlag(obj)
case Some(_: WarpGate) =>
ops.handleUseWarpGate(equipment)
case Some(obj) =>
ops.handleUseDefaultEntity(obj, equipment)
case None =>
GeneralOperations.UseItem.Unhandled
}
)
}
def handleUnuseItem(pkt: UnuseItemMessage): Unit = {
@ -767,4 +775,11 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
player.CapacitorState = CapacitorStateType.Idle
}
}
private def cancelZoningWhenGeneralHandled(results: GeneralOperations.UseItem.Behavior): Unit = {
results match {
case GeneralOperations.UseItem.Unhandled => ()
case _ => sessionLogic.zoning.CancelZoningProcess()
}
}
}

View file

@ -7,6 +7,7 @@ import net.psforever.objects.serverobject.ServerObject
import net.psforever.objects.{Session, Vehicle}
import net.psforever.packet.PlanetSidePacket
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
import net.psforever.services.chat.SpectatorChannel
import net.psforever.services.teamwork.{SquadAction, SquadServiceMessage}
import net.psforever.types.{ChatMessageType, SquadRequestType}
//
@ -52,6 +53,7 @@ class SpectatorCSRModeLogic(data: SessionData) extends ModeLogic {
player.spectator = true
//player.bops = true
player.allowInteraction = false
data.chat.JoinChannel(SpectatorChannel)
continent.actor ! ZoneActor.RemoveFromBlockMap(player)
continent.AvatarEvents ! AvatarServiceMessage(continent.id, AvatarAction.ObjectDelete(pguid, pguid))
sendResponse(ChatMsg(ChatMessageType.UNK_225, "CSR SPECTATOR MODE ON"))
@ -67,6 +69,7 @@ class SpectatorCSRModeLogic(data: SessionData) extends ModeLogic {
player.spectator = false
player.bops = false
player.allowInteraction = true
data.chat.LeaveChannel(SpectatorChannel)
data.continent.actor ! ZoneActor.AddToBlockMap(player, player.Position)
continent.AvatarEvents ! AvatarServiceMessage(
continent.id,

View file

@ -5,16 +5,11 @@ import akka.actor.{ActorContext, typed}
import net.psforever.actors.session.AvatarActor
import net.psforever.actors.session.support.{SessionData, SessionTerminalHandlers, TerminalHandlerFunctions}
import net.psforever.login.WorldSession.{BuyNewEquipmentPutInInventory, SellEquipmentFromInventory}
import net.psforever.objects.definition.VehicleDefinition
import net.psforever.objects.{GlobalDefinitions, Player, Vehicle}
import net.psforever.objects.{Player, Vehicle}
import net.psforever.objects.guid.TaskWorkflow
import net.psforever.objects.serverobject.pad.VehicleSpawnPad
import net.psforever.objects.serverobject.terminals.{OrderTerminalDefinition, Terminal}
import net.psforever.objects.sourcing.AmenitySource
import net.psforever.objects.vital.TerminalUsedActivity
import net.psforever.packet.game.{FavoritesRequest, ItemTransactionMessage, ItemTransactionResultMessage, ProximityTerminalUseMessage, UnuseItemMessage}
import net.psforever.types.{TransactionType, Vector3}
import net.psforever.util.DefinitionUtil
import net.psforever.packet.game.{FavoritesRequest, ItemTransactionMessage, ItemTransactionResultMessage, ProximityTerminalUseMessage}
import net.psforever.types.TransactionType
object TerminalHandlerLogic {
def apply(ops: SessionTerminalHandlers): TerminalHandlerLogic = {
@ -28,7 +23,7 @@ class TerminalHandlerLogic(val ops: SessionTerminalHandlers, implicit val contex
private val avatarActor: typed.ActorRef[AvatarActor.Command] = ops.avatarActor
def handleItemTransaction(pkt: ItemTransactionMessage): Unit = {
if ( player.spectator) {
if (player.spectator) {
val ItemTransactionMessage(terminal_guid, _, _, _, _, _) = pkt
sessionLogic.zoning.CancelZoningProcess()
continent

View file

@ -3,21 +3,13 @@ package net.psforever.actors.session.csr
import akka.actor.ActorContext
import net.psforever.actors.session.support.{SessionData, WeaponAndProjectileFunctions, WeaponAndProjectileOperations}
import net.psforever.login.WorldSession.{CountGrenades, FindEquipmentStock, FindToolThatUses, RemoveOldEquipmentFromInventory}
import net.psforever.objects.equipment.ChargeFireModeDefinition
import net.psforever.objects.inventory.Container
import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject}
import net.psforever.objects.{BoomerDeployable, BoomerTrigger, GlobalDefinitions, Player, SpecialEmp, Tool, Tools, Vehicle}
import net.psforever.objects.serverobject.turret.{FacilityTurret, VanuSentry}
import net.psforever.objects.{BoomerDeployable, BoomerTrigger, Player, SpecialEmp, Tool, Vehicle}
import net.psforever.objects.vital.base.{DamageResolution, DamageType}
import net.psforever.objects.zones.{Zone, ZoneProjectile}
import net.psforever.packet.game.{AIDamage, AvatarGrenadeStateMessage, ChangeAmmoMessage, ChangeFireModeMessage, ChangeFireStateMessage_Start, ChangeFireStateMessage_Stop, HitMessage, LashMessage, LongRangeProjectileInfoMessage, ProjectileStateMessage, QuantityUpdateMessage, ReloadMessage, SplashHitMessage, UplinkRequest, WeaponDelayFireMessage, WeaponDryFireMessage, WeaponFireMessage, WeaponLazeTargetPositionMessage}
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
import net.psforever.types.{PlanetSideGUID, Vector3}
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._
import net.psforever.packet.game.{AIDamage, AvatarGrenadeStateMessage, ChangeAmmoMessage, ChangeFireModeMessage, ChangeFireStateMessage_Start, ChangeFireStateMessage_Stop, HitMessage, LashMessage, LongRangeProjectileInfoMessage, ProjectileStateMessage, ReloadMessage, SplashHitMessage, UplinkRequest, WeaponDelayFireMessage, WeaponDryFireMessage, WeaponFireMessage, WeaponLazeTargetPositionMessage}
import net.psforever.types.Vector3
object WeaponAndProjectileLogic {
def apply(ops: WeaponAndProjectileOperations): WeaponAndProjectileLogic = {
@ -55,15 +47,15 @@ class WeaponAndProjectileLogic(val ops: WeaponAndProjectileOperations, implicit
if (ops.shooting.isEmpty) {
sessionLogic.findEquipment(item_guid) match {
case Some(tool: Tool) if player.VehicleSeated.isEmpty =>
fireStateStartWhenPlayer(tool, item_guid)
ops.fireStateStartWhenPlayer(tool, item_guid)
case Some(tool: Tool) =>
fireStateStartWhenMounted(tool, item_guid)
ops.fireStateStartWhenMounted(tool, item_guid)
case Some(_) if player.VehicleSeated.isEmpty =>
fireStateStartSetup(item_guid)
fireStateStartPlayerMessages(item_guid)
ops.fireStateStartSetup(item_guid)
ops.fireStateStartPlayerMessages(item_guid)
case Some(_) =>
fireStateStartSetup(item_guid)
fireStateStartMountedMessages(item_guid)
ops.fireStateStartSetup(item_guid)
ops.fireStateStartMountedMessages(item_guid)
case None =>
log.warn(s"ChangeFireState_Start: can not find $item_guid")
}
@ -78,9 +70,9 @@ class WeaponAndProjectileLogic(val ops: WeaponAndProjectileOperations, implicit
ops.shooting -= item_guid
sessionLogic.findEquipment(item_guid) match {
case Some(tool: Tool) if player.VehicleSeated.isEmpty =>
fireStateStopWhenPlayer(tool, item_guid)
ops.fireStateStopWhenPlayer(tool, item_guid)
case Some(tool: Tool) =>
fireStateStopWhenMounted(tool, item_guid)
ops.fireStateStopWhenMounted(tool, item_guid)
case Some(trigger: BoomerTrigger) =>
ops.fireStateStopPlayerMessages(item_guid)
continent.GUID(trigger.Companion).collect {
@ -102,9 +94,9 @@ class WeaponAndProjectileLogic(val ops: WeaponAndProjectileOperations, implicit
val ReloadMessage(item_guid, _, unk1) = pkt
ops.FindContainedWeapon match {
case (Some(obj: Player), tools) =>
handleReloadWhenPlayer(item_guid, obj, tools, unk1)
ops.handleReloadWhenPlayer(item_guid, obj, tools, unk1)
case (Some(obj: PlanetSideServerObject with Container), tools) =>
handleReloadWhenMountable(item_guid, obj, tools, unk1)
ops.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")
}
@ -213,204 +205,4 @@ class WeaponAndProjectileLogic(val ops: WeaponAndProjectileOperations, implicit
}
}
}
/* support code */
/**
* After a weapon has finished shooting, determine if it needs to be sorted in a special way.
* @param tool a weapon
*/
private def FireCycleCleanup(tool: Tool): Unit = {
//TODO replaced by more appropriate functionality in the future
val tdef = tool.Definition
if (GlobalDefinitions.isGrenade(tdef)) {
val ammoType = tool.AmmoType
FindEquipmentStock(player, FindToolThatUses(ammoType), 3, CountGrenades).reverse match { //do not search sidearm holsters
case Nil =>
RemoveOldEquipmentFromInventory(player)(tool)
case x :: xs => //this is similar to ReloadMessage
val box = x.obj.asInstanceOf[Tool]
val tailReloadValue: Int = if (xs.isEmpty) { 0 }
else { xs.map(_.obj.asInstanceOf[Tool].Magazine).sum }
val sumReloadValue: Int = box.Magazine + tailReloadValue
val actualReloadValue = if (sumReloadValue <= 3) {
RemoveOldEquipmentFromInventory(player)(x.obj)
sumReloadValue
} else {
ops.modifyAmmunition(player)(box.AmmoSlot.Box, 3 - tailReloadValue)
3
}
ops.modifyAmmunition(player)(
tool.AmmoSlot.Box,
-actualReloadValue
) //grenade item already in holster (negative because empty)
xs.foreach(item => { RemoveOldEquipmentFromInventory(player)(item.obj) })
}
} else if (tdef == GlobalDefinitions.phoenix) {
RemoveOldEquipmentFromInventory(player)(tool)
}
}
/*
used by ChangeFireStateMessage_Start handling
*/
private def fireStateStartSetup(itemGuid: PlanetSideGUID): Unit = {
ops.prefire -= itemGuid
ops.shooting += itemGuid
ops.shootingStart += itemGuid -> System.currentTimeMillis()
}
private def fireStateStartChargeMode(tool: Tool): Unit = {
//charge ammunition drain
tool.FireMode match {
case mode: ChargeFireModeDefinition =>
sessionLogic.general.progressBarValue = Some(0f)
sessionLogic.general.progressBarUpdate = context.system.scheduler.scheduleOnce(
(mode.Time + mode.DrainInterval) milliseconds,
context.self,
CommonMessages.ProgressEvent(1f, () => {}, Tools.ChargeFireMode(player, tool), mode.DrainInterval)
)
case _ => ()
}
}
private def fireStateStartPlayerMessages(itemGuid: PlanetSideGUID): Unit = {
if (!player.spectator) {
continent.AvatarEvents ! AvatarServiceMessage(
continent.id,
AvatarAction.ChangeFireState_Start(player.GUID, itemGuid)
)
}
}
private def fireStateStartMountedMessages(itemGuid: PlanetSideGUID): Unit = {
sessionLogic.findContainedEquipment()._1.collect {
case turret: FacilityTurret if continent.map.cavern =>
turret.Actor ! VanuSentry.ChangeFireStart
}
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 || ops.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"
)
ops.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 = {
tool.FireMode match {
case _: ChargeFireModeDefinition =>
sendResponse(QuantityUpdateMessage(tool.AmmoSlot.Box.GUID, tool.Magazine))
case _ => ()
}
if (tool.Magazine == 0) {
FireCycleCleanup(tool)
}
}
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)
ops.fireStateStopPlayerMessages(itemGuid)
}
private def fireStateStopWhenMounted(tool: Tool, itemGuid: PlanetSideGUID): Unit = {
fireStateStopUpdateChargeAndCleanup(tool)
ops.fireStateStopMountedMessages(itemGuid)
}
/*
used by ReloadMessage handling
*/
private def reloadPlayerMessages(itemGuid: PlanetSideGUID): Unit = {
if (!player.spectator) {
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 handleReloadWhenPlayer(
itemGuid: PlanetSideGUID,
obj: Player,
tools: Set[Tool],
unk1: Int
): Unit = {
ops.handleReloadProcedure(
itemGuid,
obj,
tools,
unk1,
RemoveOldEquipmentFromInventory(obj)(_),
ops.modifyAmmunition(obj)(_, _),
reloadPlayerMessages
)
}
private def handleReloadWhenMountable(
itemGuid: PlanetSideGUID,
obj: PlanetSideServerObject with Container,
tools: Set[Tool],
unk1: Int
): Unit = {
ops.handleReloadProcedure(
itemGuid,
obj,
tools,
unk1,
RemoveOldEquipmentFromInventory(obj)(_),
ops.modifyAmmunitionInMountable(obj)(_, _),
reloadVehicleMessages
)
}
}

View file

@ -333,51 +333,56 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
case _ =>
None
}
sessionLogic.validObject(pkt.object_guid, decorator = "UseItem") match {
case Some(door: Door) =>
ops.handleUseDoor(door, equipment)
case Some(resourceSilo: ResourceSilo) =>
ops.handleUseResourceSilo(resourceSilo, equipment)
case Some(panel: IFFLock) =>
ops.handleUseGeneralEntity(panel, equipment)
case Some(obj: Player) =>
ops.handleUsePlayer(obj, equipment, pkt)
case Some(locker: Locker) =>
ops.handleUseLocker(locker, equipment, pkt)
case Some(gen: Generator) =>
ops.handleUseGeneralEntity(gen, equipment)
case Some(mech: ImplantTerminalMech) =>
ops.handleUseGeneralEntity(mech, equipment)
case Some(captureTerminal: CaptureTerminal) =>
ops.handleUseCaptureTerminal(captureTerminal, equipment)
case Some(obj: FacilityTurret) =>
ops.handleUseFacilityTurret(obj, equipment, pkt)
case Some(obj: Vehicle) =>
ops.handleUseVehicle(obj, equipment, pkt)
case Some(terminal: Terminal) =>
ops.handleUseTerminal(terminal, equipment, pkt)
case Some(obj: SpawnTube) =>
ops.handleUseSpawnTube(obj, equipment)
case Some(obj: SensorDeployable) =>
ops.handleUseGeneralEntity(obj, equipment)
case Some(obj: TurretDeployable) =>
ops.handleUseGeneralEntity(obj, equipment)
case Some(obj: TrapDeployable) =>
ops.handleUseGeneralEntity(obj, equipment)
case Some(obj: ShieldGeneratorDeployable) =>
ops.handleUseGeneralEntity(obj, equipment)
case Some(obj: TelepadDeployable) =>
ops.handleUseTelepadDeployable(obj, equipment, pkt, ops.useRouterTelepadSystem)
case Some(obj: Utility.InternalTelepad) =>
ops.handleUseInternalTelepad(obj, pkt, ops.useRouterTelepadSystem)
case Some(obj: CaptureFlag) =>
ops.handleUseCaptureFlag(obj)
case Some(_: WarpGate) =>
ops.handleUseWarpGate(equipment)
case Some(obj) =>
ops.handleUseDefaultEntity(obj, equipment)
case None => ()
}
cancelZoningWhenGeneralHandled(
sessionLogic.validObject(pkt.object_guid, decorator = "UseItem") match {
case Some(door: Door) =>
ops.handleUseDoor(door, equipment)
GeneralOperations.UseItem.Unhandled
case Some(resourceSilo: ResourceSilo) =>
ops.handleUseResourceSilo(resourceSilo, equipment)
case Some(panel: IFFLock) =>
ops.handleUseGeneralEntity(panel, equipment)
case Some(obj: Player) =>
ops.handleUsePlayer(obj, equipment, pkt)
GeneralOperations.UseItem.Unhandled
case Some(locker: Locker) =>
ops.handleUseLocker(locker, equipment, pkt)
case Some(gen: Generator) =>
ops.handleUseGeneralEntity(gen, equipment)
case Some(mech: ImplantTerminalMech) =>
ops.handleUseGeneralEntity(mech, equipment)
case Some(captureTerminal: CaptureTerminal) =>
ops.handleUseCaptureTerminal(captureTerminal, equipment)
case Some(obj: FacilityTurret) =>
ops.handleUseFacilityTurret(obj, equipment, pkt)
case Some(obj: Vehicle) =>
ops.handleUseVehicle(obj, equipment, pkt)
case Some(terminal: Terminal) =>
ops.handleUseTerminal(terminal, equipment, pkt)
case Some(obj: SpawnTube) =>
ops.handleUseSpawnTube(obj, equipment)
case Some(obj: SensorDeployable) =>
ops.handleUseGeneralEntity(obj, equipment)
case Some(obj: TurretDeployable) =>
ops.handleUseGeneralEntity(obj, equipment)
case Some(obj: TrapDeployable) =>
ops.handleUseGeneralEntity(obj, equipment)
case Some(obj: ShieldGeneratorDeployable) =>
ops.handleUseGeneralEntity(obj, equipment)
case Some(obj: TelepadDeployable) =>
ops.handleUseTelepadDeployable(obj, equipment, pkt, ops.useRouterTelepadSystem)
case Some(obj: Utility.InternalTelepad) =>
ops.handleUseInternalTelepad(obj, pkt, ops.useRouterTelepadSystem)
case Some(obj: CaptureFlag) =>
ops.handleUseCaptureFlag(obj)
case Some(_: WarpGate) =>
ops.handleUseWarpGate(equipment)
case Some(obj) =>
ops.handleUseDefaultEntity(obj, equipment)
case None =>
GeneralOperations.UseItem.Unhandled
}
)
}
def handleUnuseItem(pkt: UnuseItemMessage): Unit = {
@ -971,4 +976,14 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
)
)
}
private def cancelZoningWhenGeneralHandled(results: GeneralOperations.UseItem.Behavior): Unit = {
results match {
case GeneralOperations.UseItem.Handled =>
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
case GeneralOperations.UseItem.HandledPassive =>
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel")
case _ => ()
}
}
}

View file

@ -2,24 +2,14 @@
package net.psforever.actors.session.normal
import akka.actor.ActorContext
//import akka.actor.typed
//import net.psforever.actors.session.AvatarActor
import net.psforever.actors.session.support.{SessionData, WeaponAndProjectileFunctions, WeaponAndProjectileOperations}
import net.psforever.login.WorldSession.{CountGrenades, FindEquipmentStock, FindToolThatUses, RemoveOldEquipmentFromInventory}
import net.psforever.objects.equipment.ChargeFireModeDefinition
import net.psforever.objects.inventory.Container
import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject}
import net.psforever.objects.{BoomerDeployable, BoomerTrigger, GlobalDefinitions, Player, SpecialEmp, Tool, Tools, Vehicle}
import net.psforever.objects.serverobject.turret.{FacilityTurret, VanuSentry}
import net.psforever.objects.{BoomerDeployable, BoomerTrigger, Player, SpecialEmp, Tool, Vehicle}
import net.psforever.objects.vital.base.{DamageResolution, DamageType}
import net.psforever.objects.zones.{Zone, ZoneProjectile}
import net.psforever.packet.game.{AIDamage, AvatarGrenadeStateMessage, ChangeAmmoMessage, ChangeFireModeMessage, ChangeFireStateMessage_Start, ChangeFireStateMessage_Stop, HitMessage, LashMessage, LongRangeProjectileInfoMessage, ProjectileStateMessage, QuantityUpdateMessage, ReloadMessage, SplashHitMessage, UplinkRequest, WeaponDelayFireMessage, WeaponDryFireMessage, WeaponFireMessage, WeaponLazeTargetPositionMessage}
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
import net.psforever.types.{PlanetSideGUID, Vector3}
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._
import net.psforever.packet.game.{AIDamage, AvatarGrenadeStateMessage, ChangeAmmoMessage, ChangeFireModeMessage, ChangeFireStateMessage_Start, ChangeFireStateMessage_Stop, HitMessage, LashMessage, LongRangeProjectileInfoMessage, ProjectileStateMessage, ReloadMessage, SplashHitMessage, UplinkRequest, WeaponDelayFireMessage, WeaponDryFireMessage, WeaponFireMessage, WeaponLazeTargetPositionMessage}
import net.psforever.types.Vector3
object WeaponAndProjectileLogic {
def apply(ops: WeaponAndProjectileOperations): WeaponAndProjectileLogic = {
@ -74,15 +64,15 @@ class WeaponAndProjectileLogic(val ops: WeaponAndProjectileOperations, implicit
if (ops.shooting.isEmpty) {
sessionLogic.findEquipment(item_guid) match {
case Some(tool: Tool) if player.VehicleSeated.isEmpty =>
fireStateStartWhenPlayer(tool, item_guid)
ops.fireStateStartWhenPlayer(tool, item_guid)
case Some(tool: Tool) =>
fireStateStartWhenMounted(tool, item_guid)
ops.fireStateStartWhenMounted(tool, item_guid)
case Some(_) if player.VehicleSeated.isEmpty =>
fireStateStartSetup(item_guid)
fireStateStartPlayerMessages(item_guid)
ops.fireStateStartSetup(item_guid)
ops.fireStateStartPlayerMessages(item_guid)
case Some(_) =>
fireStateStartSetup(item_guid)
fireStateStartMountedMessages(item_guid)
ops.fireStateStartSetup(item_guid)
ops.fireStateStartMountedMessages(item_guid)
case None =>
log.warn(s"ChangeFireState_Start: can not find $item_guid")
}
@ -97,9 +87,9 @@ class WeaponAndProjectileLogic(val ops: WeaponAndProjectileOperations, implicit
ops.shooting -= item_guid
sessionLogic.findEquipment(item_guid) match {
case Some(tool: Tool) if player.VehicleSeated.isEmpty =>
fireStateStopWhenPlayer(tool, item_guid)
ops.fireStateStopWhenPlayer(tool, item_guid)
case Some(tool: Tool) =>
fireStateStopWhenMounted(tool, item_guid)
ops.fireStateStopWhenMounted(tool, item_guid)
case Some(trigger: BoomerTrigger) =>
ops.fireStateStopPlayerMessages(item_guid)
continent.GUID(trigger.Companion).collect {
@ -121,9 +111,9 @@ class WeaponAndProjectileLogic(val ops: WeaponAndProjectileOperations, implicit
val ReloadMessage(item_guid, _, unk1) = pkt
ops.FindContainedWeapon match {
case (Some(obj: Player), tools) =>
handleReloadWhenPlayer(item_guid, obj, tools, unk1)
ops.handleReloadWhenPlayer(item_guid, obj, tools, unk1)
case (Some(obj: PlanetSideServerObject with Container), tools) =>
handleReloadWhenMountable(item_guid, obj, tools, unk1)
ops.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")
}
@ -239,198 +229,4 @@ class WeaponAndProjectileLogic(val ops: WeaponAndProjectileOperations, implicit
}
}
}
/* support code */
/**
* After a weapon has finished shooting, determine if it needs to be sorted in a special way.
* @param tool a weapon
*/
private def FireCycleCleanup(tool: Tool): Unit = {
//TODO replaced by more appropriate functionality in the future
val tdef = tool.Definition
if (GlobalDefinitions.isGrenade(tdef)) {
val ammoType = tool.AmmoType
FindEquipmentStock(player, FindToolThatUses(ammoType), 3, CountGrenades).reverse match { //do not search sidearm holsters
case Nil =>
log.info(s"${player.Name} has no more $ammoType grenades to throw")
RemoveOldEquipmentFromInventory(player)(tool)
case x :: xs => //this is similar to ReloadMessage
val box = x.obj.asInstanceOf[Tool]
val tailReloadValue: Int = if (xs.isEmpty) { 0 }
else { xs.map(_.obj.asInstanceOf[Tool].Magazine).sum }
val sumReloadValue: Int = box.Magazine + tailReloadValue
val actualReloadValue = if (sumReloadValue <= 3) {
RemoveOldEquipmentFromInventory(player)(x.obj)
sumReloadValue
} else {
ops.modifyAmmunition(player)(box.AmmoSlot.Box, 3 - tailReloadValue)
3
}
log.info(s"${player.Name} found $actualReloadValue more $ammoType grenades to throw")
ops.modifyAmmunition(player)(
tool.AmmoSlot.Box,
-actualReloadValue
) //grenade item already in holster (negative because empty)
xs.foreach(item => { RemoveOldEquipmentFromInventory(player)(item.obj) })
}
} else if (tdef == GlobalDefinitions.phoenix) {
RemoveOldEquipmentFromInventory(player)(tool)
}
}
/*
used by ChangeFireStateMessage_Start handling
*/
private def fireStateStartSetup(itemGuid: PlanetSideGUID): Unit = {
ops.prefire -= itemGuid
ops.shooting += itemGuid
ops.shootingStart += itemGuid -> System.currentTimeMillis()
}
private def fireStateStartChargeMode(tool: Tool): Unit = {
//charge ammunition drain
tool.FireMode match {
case mode: ChargeFireModeDefinition =>
sessionLogic.general.progressBarValue = Some(0f)
sessionLogic.general.progressBarUpdate = context.system.scheduler.scheduleOnce(
(mode.Time + mode.DrainInterval) milliseconds,
context.self,
CommonMessages.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 = {
sessionLogic.findContainedEquipment()._1.collect {
case turret: FacilityTurret if continent.map.cavern =>
turret.Actor ! VanuSentry.ChangeFireStart
}
continent.VehicleEvents ! VehicleServiceMessage(
continent.id,
VehicleAction.ChangeFireState_Start(player.GUID, 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"
)
ops.emptyMagazine(itemGuid, tool)
}
private def fireStateStartWhenPlayer(tool: Tool, itemGuid: PlanetSideGUID): Unit = {
if (ops.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 (ops.allowFireStateChangeStart(tool, itemGuid)) {
fireStateStartSetup(itemGuid)
fireStateStartMountedMessages(itemGuid)
fireStateStartChargeMode(tool)
} else {
enforceEmptyMagazine(tool, itemGuid)
}
}
/*
used by ChangeFireStateMessage_Stop handling
*/
private def fireStateStopUpdateChargeAndCleanup(tool: Tool): Unit = {
tool.FireMode match {
case _: ChargeFireModeDefinition =>
sendResponse(QuantityUpdateMessage(tool.AmmoSlot.Box.GUID, tool.Magazine))
case _ => ()
}
if (tool.Magazine == 0) {
FireCycleCleanup(tool)
}
}
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)
ops.fireStateStopPlayerMessages(itemGuid)
}
private def fireStateStopWhenMounted(tool: Tool, itemGuid: PlanetSideGUID): Unit = {
fireStateStopUpdateChargeAndCleanup(tool)
ops.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 handleReloadWhenPlayer(
itemGuid: PlanetSideGUID,
obj: Player,
tools: Set[Tool],
unk1: Int
): Unit = {
ops.handleReloadProcedure(
itemGuid,
obj,
tools,
unk1,
RemoveOldEquipmentFromInventory(obj)(_),
ops.modifyAmmunition(obj)(_, _),
reloadPlayerMessages
)
}
private def handleReloadWhenMountable(
itemGuid: PlanetSideGUID,
obj: PlanetSideServerObject with Container,
tools: Set[Tool],
unk1: Int
): Unit = {
ops.handleReloadProcedure(
itemGuid,
obj,
tools,
unk1,
RemoveOldEquipmentFromInventory(obj)(_),
ops.modifyAmmunitionInMountable(obj)(_, _),
reloadVehicleMessages
)
}
}

View file

@ -221,7 +221,7 @@ class AvatarHandlerLogic(val ops: SessionAvatarHandlers, implicit val context: A
case AvatarResponse.HitHint(sourceGuid) if player.isAlive =>
sendResponse(HitHint(sourceGuid, guid))
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_dmg")
sessionLogic.zoning.CancelZoningProcess()
case AvatarResponse.Destroy(victim, killer, weapon, pos) =>
// guid = victim // killer = killer
@ -447,7 +447,7 @@ class AvatarHandlerLogic(val ops: SessionAvatarHandlers, implicit val context: A
)
sessionLogic.shooting.shotsWhileDead = 0
}
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason(msg = "cancel")
sessionLogic.zoning.CancelZoningProcess()
//player state changes
AvatarActor.updateToolDischargeFor(avatar)
@ -510,7 +510,7 @@ class AvatarHandlerLogic(val ops: SessionAvatarHandlers, implicit val context: A
case AvatarResponse.EnvironmentalDamage(_, _, _) =>
//TODO damage marker?
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_dmg")
sessionLogic.zoning.CancelZoningProcess()
case AvatarResponse.DropItem(pkt) if isNotSameTarget =>
sendResponse(pkt)

View file

@ -55,19 +55,19 @@ class ChatLogic(val ops: ChatOperations, implicit val context: ActorContext) ext
commandToggleSpectatorMode(contents = "off")
case (CMT_OPEN, _, _) =>
ops.commandSendToRecipient(session, message, SpectatorChannel)
ops.commandSendToRecipient(session, spectatorColoredMessage(message), SpectatorChannel)
case (CMT_VOICE, _, contents) =>
ops.commandVoice(session, message, contents, SpectatorChannel)
case (CMT_TELL, _, _) =>
ops.commandTellOrIgnore(session, message, SpectatorChannel)
ops.commandTellOrIgnore(session, spectatorColoredMessage(message), SpectatorChannel)
case (CMT_BROADCAST, _, _) =>
ops.commandSendToRecipient(session, message, SpectatorChannel)
ops.commandSendToRecipient(session, spectatorColoredMessage(message), SpectatorChannel)
case (CMT_PLATOON, _, _) =>
ops.commandSendToRecipient(session, message, SpectatorChannel)
ops.commandSendToRecipient(session, spectatorColoredMessage(message), SpectatorChannel)
case (CMT_GMTELL, _, _) =>
ops.commandSend(session, message, SpectatorChannel)
@ -114,6 +114,16 @@ class ChatLogic(val ops: ChatOperations, implicit val context: ActorContext) ext
}
}
private def spectatorColoredMessage(message: ChatMsg): ChatMsg = {
if (message.contents.nonEmpty) {
val colorlessText = message.contents.replaceAll("//#\\d", "").trim
val colorCodedText = s"/#5$colorlessText/#0"
message.copy(recipient = s"<spectator:${message.recipient}>", contents = colorCodedText)
} else {
message
}
}
private def customCommandMessages(
message: ChatMsg,
session: Session

View file

@ -47,7 +47,7 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
yaw,
pitch,
yawUpper,
_/*seqTime*/,
seqTime,
_,
isCrouching,
isJumping,
@ -69,6 +69,24 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
player.Crouching = isCrouching
player.Jumping = isJumping
player.Cloaked = player.ExoSuit == ExoSuitType.Infiltration && isCloaking
continent.AvatarEvents ! AvatarServiceMessage(
"spectator",
AvatarAction.PlayerState(
avatarGuid,
player.Position,
player.Velocity,
yaw,
pitch,
yawUpper,
seqTime,
isCrouching,
isJumping,
jump_thrust = false,
is_cloaked = isCloaking,
spectator = false,
weaponInHand = false
)
)
if (player.death_by == -1) {
sessionLogic.kickedByAdministration()
}

View file

@ -118,6 +118,7 @@ class SpectatorModeLogic(data: SessionData) extends ModeLogic {
data.chat.commandIncomingSilence(session, ChatMsg(ChatMessageType.CMT_SILENCE, "player 0"))
}
//
player.spectator = true
data.chat.JoinChannel(SpectatorChannel)
val newPlayer = SpectatorModeLogic.spectatorCharacter(player)
newPlayer.LogActivity(player.History.headOption)
@ -151,6 +152,7 @@ class SpectatorModeLogic(data: SessionData) extends ModeLogic {
val pguid = player.GUID
val sendResponse: PlanetSidePacket => Unit = data.sendResponse
//
player.spectator = false
data.general.stop()
player.avatar.shortcuts.slice(1, 4)
.zipWithIndex
@ -176,7 +178,7 @@ object SpectatorModeLogic {
private def spectatorCharacter(player: Player): Player = {
val avatar = player.avatar
val newAvatar = avatar.copy(
basic = avatar.basic.copy(name = "spectator"),
basic = avatar.basic,
bep = BattleRank.BR18.experience,
cep = CommandRank.CR5.experience,
certifications = Set(),

View file

@ -3,13 +3,9 @@ package net.psforever.actors.session.spectator
import akka.actor.ActorContext
import net.psforever.actors.session.support.{SessionData, WeaponAndProjectileFunctions, WeaponAndProjectileOperations}
import net.psforever.login.WorldSession.{CountGrenades, FindEquipmentStock, FindToolThatUses, RemoveOldEquipmentFromInventory}
import net.psforever.objects.equipment.ChargeFireModeDefinition
import net.psforever.objects.serverobject.CommonMessages
import net.psforever.objects.{BoomerDeployable, BoomerTrigger, GlobalDefinitions, Tool}
import net.psforever.packet.game.{AIDamage, AvatarGrenadeStateMessage, ChangeAmmoMessage, ChangeFireModeMessage, ChangeFireStateMessage_Start, ChangeFireStateMessage_Stop, HitMessage, LashMessage, LongRangeProjectileInfoMessage, ProjectileStateMessage, QuantityUpdateMessage, ReloadMessage, SplashHitMessage, UplinkRequest, UplinkRequestType, UplinkResponse, WeaponDelayFireMessage, WeaponDryFireMessage, WeaponFireMessage, WeaponLazeTargetPositionMessage}
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
import net.psforever.types.PlanetSideGUID
import net.psforever.objects.{BoomerDeployable, BoomerTrigger, Tool}
import net.psforever.packet.game.{AIDamage, AvatarGrenadeStateMessage, ChangeAmmoMessage, ChangeFireModeMessage, ChangeFireStateMessage_Start, ChangeFireStateMessage_Stop, HitMessage, LashMessage, LongRangeProjectileInfoMessage, ProjectileStateMessage, ReloadMessage, SplashHitMessage, UplinkRequest, UplinkRequestType, UplinkResponse, WeaponDelayFireMessage, WeaponDryFireMessage, WeaponFireMessage, WeaponLazeTargetPositionMessage}
object WeaponAndProjectileLogic {
def apply(ops: WeaponAndProjectileOperations): WeaponAndProjectileLogic = {
@ -57,9 +53,9 @@ class WeaponAndProjectileLogic(val ops: WeaponAndProjectileOperations, implicit
ops.shooting -= item_guid
sessionLogic.findEquipment(item_guid) match {
case Some(tool: Tool) if player.VehicleSeated.isEmpty =>
fireStateStopWhenPlayer(tool, item_guid)
ops.fireStateStopWhenPlayer(tool, item_guid)
case Some(tool: Tool) =>
fireStateStopWhenMounted(tool, item_guid)
ops.fireStateStopWhenMounted(tool, item_guid)
case Some(trigger: BoomerTrigger) =>
ops.fireStateStopPlayerMessages(item_guid)
continent.GUID(trigger.Companion).collect {
@ -95,83 +91,4 @@ class WeaponAndProjectileLogic(val ops: WeaponAndProjectileOperations, implicit
def handleLashHit(pkt: LashMessage): Unit = { /* intentionally blank */ }
def handleAIDamage(pkt: AIDamage): Unit = { /* intentionally blank */ }
/* support code */
/**
* After a weapon has finished shooting, determine if it needs to be sorted in a special way.
* @param tool a weapon
*/
private def FireCycleCleanup(tool: Tool): Unit = {
//TODO replaced by more appropriate functionality in the future
val tdef = tool.Definition
if (GlobalDefinitions.isGrenade(tdef)) {
val ammoType = tool.AmmoType
FindEquipmentStock(player, FindToolThatUses(ammoType), 3, CountGrenades).reverse match { //do not search sidearm holsters
case Nil =>
log.info(s"${player.Name} has no more $ammoType grenades to throw")
RemoveOldEquipmentFromInventory(player)(tool)
case x :: xs => //this is similar to ReloadMessage
val box = x.obj.asInstanceOf[Tool]
val tailReloadValue: Int = if (xs.isEmpty) { 0 }
else { xs.map(_.obj.asInstanceOf[Tool].Magazine).sum }
val sumReloadValue: Int = box.Magazine + tailReloadValue
val actualReloadValue = if (sumReloadValue <= 3) {
RemoveOldEquipmentFromInventory(player)(x.obj)
sumReloadValue
} else {
ops.modifyAmmunition(player)(box.AmmoSlot.Box, 3 - tailReloadValue)
3
}
log.info(s"${player.Name} found $actualReloadValue more $ammoType grenades to throw")
ops.modifyAmmunition(player)(
tool.AmmoSlot.Box,
-actualReloadValue
) //grenade item already in holster (negative because empty)
xs.foreach(item => { RemoveOldEquipmentFromInventory(player)(item.obj) })
}
} else if (tdef == GlobalDefinitions.phoenix) {
RemoveOldEquipmentFromInventory(player)(tool)
}
}
private def fireStateStartPlayerMessages(itemGuid: PlanetSideGUID): Unit = {
continent.AvatarEvents ! AvatarServiceMessage(
continent.id,
AvatarAction.ChangeFireState_Start(player.GUID, itemGuid)
)
}
/*
used by ChangeFireStateMessage_Stop handling
*/
private def fireStateStopUpdateChargeAndCleanup(tool: Tool): Unit = {
tool.FireMode match {
case _: ChargeFireModeDefinition =>
sendResponse(QuantityUpdateMessage(tool.AmmoSlot.Box.GUID, tool.Magazine))
case _ => ()
}
if (tool.Magazine == 0) {
FireCycleCleanup(tool)
}
}
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)
ops.fireStateStopPlayerMessages(itemGuid)
}
private def fireStateStopWhenMounted(tool: Tool, itemGuid: PlanetSideGUID): Unit = {
fireStateStopUpdateChargeAndCleanup(tool)
ops.fireStateStopMountedMessages(itemGuid)
}
}

View file

@ -1081,7 +1081,7 @@ class GeneralOperations(
TaskWorkflow.execute(CallBackForTask(tasking, zone.Deployables, zoneBuildCommand, context.self))
}
def handleUseDoor(door: Door, equipment: Option[Equipment]): Unit = {
def handleUseDoor(door: Door, equipment: Option[Equipment]): GeneralOperations.UseItem.Behavior = {
equipment match {
case Some(tool: Tool) if tool.Definition == GlobalDefinitions.medicalapplicator =>
val distance: Float = math.max(
@ -1092,72 +1092,88 @@ class GeneralOperations(
case _ =>
door.Actor ! CommonMessages.Use(player)
}
GeneralOperations.UseItem.Handled
}
def handleUseResourceSilo(resourceSilo: ResourceSilo, equipment: Option[Equipment]): Unit = {
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
def handleUseResourceSilo(resourceSilo: ResourceSilo, equipment: Option[Equipment]): GeneralOperations.UseItem.Behavior = {
val vehicleOpt = continent.GUID(player.avatar.vehicle)
(vehicleOpt, equipment) match {
case (Some(vehicle: Vehicle), Some(item))
if GlobalDefinitions.isBattleFrameVehicle(vehicle.Definition) &&
GlobalDefinitions.isBattleFrameNTUSiphon(item.Definition) =>
resourceSilo.Actor ! CommonMessages.Use(player, Some(vehicle))
GeneralOperations.UseItem.Handled
case (Some(vehicle: Vehicle), _)
if vehicle.Definition == GlobalDefinitions.ant &&
vehicle.DeploymentState == DriveState.Deployed &&
Vector3.DistanceSquared(resourceSilo.Position.xy, vehicle.Position.xy) < math.pow(resourceSilo.Definition.UseRadius, 2) =>
resourceSilo.Actor ! CommonMessages.Use(player, Some(vehicle))
case _ => ()
GeneralOperations.UseItem.Handled
case _ =>
GeneralOperations.UseItem.Unhandled
}
}
def handleUsePlayer(obj: Player, equipment: Option[Equipment], msg: UseItemMessage): Unit = {
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
def handleUsePlayer(obj: Player, equipment: Option[Equipment], msg: UseItemMessage): GeneralOperations.UseItem.Behavior = {
if (obj.isBackpack) {
if (equipment.isEmpty) {
log.info(s"${player.Name} is looting the corpse of ${obj.Name}")
sendResponse(msg)
accessContainer(obj)
GeneralOperations.UseItem.Handled
} else {
GeneralOperations.UseItem.Unhandled
}
} else if (!msg.unk3 && player.isAlive) { //potential kit use
(continent.GUID(msg.item_used_guid), kitToBeUsed) match {
case (Some(kit: Kit), None) =>
kitToBeUsed = Some(msg.item_used_guid)
player.Actor ! CommonMessages.Use(player, Some(kit))
GeneralOperations.UseItem.Handled
case (Some(_: Kit), Some(_)) | (None, Some(_)) =>
//a kit is already queued to be used; ignore this request
sendResponse(ChatMsg(ChatMessageType.UNK_225, wideContents=false, "", "Please wait ...", None))
GeneralOperations.UseItem.Unhandled
case (Some(item), _) =>
log.error(s"UseItem: ${player.Name} looking for Kit to use, but found $item instead")
GeneralOperations.UseItem.Unhandled
case (None, None) =>
log.warn(s"UseItem: anticipated a Kit ${msg.item_used_guid} for ${player.Name}, but can't find it") }
log.warn(s"UseItem: anticipated a Kit ${msg.item_used_guid} for ${player.Name}, but can't find it")
GeneralOperations.UseItem.Unhandled
}
} else if (msg.object_id == ObjectClass.avatar && msg.unk3) {
equipment match {
case Some(tool: Tool) if tool.Definition == GlobalDefinitions.bank =>
obj.Actor ! CommonMessages.Use(player, equipment)
GeneralOperations.UseItem.Handled
case Some(tool: Tool) if tool.Definition == GlobalDefinitions.medicalapplicator =>
obj.Actor ! CommonMessages.Use(player, equipment)
case _ => ()
GeneralOperations.UseItem.Handled
case _ =>
GeneralOperations.UseItem.Unhandled
}
} else {
GeneralOperations.UseItem.Unhandled
}
}
def handleUseLocker(locker: Locker, equipment: Option[Equipment], msg: UseItemMessage): Unit = {
def handleUseLocker(locker: Locker, equipment: Option[Equipment], msg: UseItemMessage): GeneralOperations.UseItem.Behavior = {
equipment match {
case Some(item) =>
sendUseGeneralEntityMessage(locker, item)
GeneralOperations.UseItem.Unhandled
case None if locker.Faction == player.Faction || locker.HackedBy.nonEmpty =>
log.info(s"${player.Name} is accessing a locker")
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
val playerLocker = player.avatar.locker
sendResponse(msg.copy(object_guid = playerLocker.GUID, object_id = 456))
accessContainer(playerLocker)
case _ => ()
GeneralOperations.UseItem.Handled
case _ =>
GeneralOperations.UseItem.Unhandled
}
}
def handleUseCaptureTerminal(captureTerminal: CaptureTerminal, equipment: Option[Equipment]): Unit = {
def handleUseCaptureTerminal(captureTerminal: CaptureTerminal, equipment: Option[Equipment]): GeneralOperations.UseItem.Behavior = {
equipment match {
case Some(item) =>
sendUseGeneralEntityMessage(captureTerminal, item)
@ -1166,25 +1182,33 @@ class GeneralOperations(
case Some(llu: CaptureFlag) =>
if (llu.Target.GUID == captureTerminal.Owner.GUID) {
continent.LocalEvents ! LocalServiceMessage(continent.id, LocalAction.LluCaptured(llu))
GeneralOperations.UseItem.Handled
} else {
log.info(
s"LLU target is not this base. Target GUID: ${llu.Target.GUID} This base: ${captureTerminal.Owner.GUID}"
)
}
case _ => log.warn("Item in specialItemSlotGuid is not registered with continent or is not a LLU")
GeneralOperations.UseItem.Unhandled
case _ =>
log.warn("Item in specialItemSlotGuid is not registered with continent or is not a LLU")
GeneralOperations.UseItem.Unhandled
}
case _ => ()
case _ =>
GeneralOperations.UseItem.Unhandled
}
}
def handleUseFacilityTurret(obj: FacilityTurret, equipment: Option[Equipment], msg: UseItemMessage): Unit = {
equipment.foreach { item =>
sendUseGeneralEntityMessage(obj, item)
obj.Actor ! CommonMessages.Use(player, Some((item, msg.unk2.toInt))) //try upgrade path
}
def handleUseFacilityTurret(obj: FacilityTurret, equipment: Option[Equipment], msg: UseItemMessage): GeneralOperations.UseItem.Behavior = {
equipment
.collect { item =>
sendUseGeneralEntityMessage(obj, item)
obj.Actor ! CommonMessages.Use(player, Some((item, msg.unk2.toInt))) //try upgrade path
GeneralOperations.UseItem.Handled
}
.getOrElse(GeneralOperations.UseItem.Unhandled)
}
def handleUseVehicle(obj: Vehicle, equipment: Option[Equipment], msg: UseItemMessage): Unit = {
def handleUseVehicle(obj: Vehicle, equipment: Option[Equipment], msg: UseItemMessage): GeneralOperations.UseItem.Behavior = {
equipment match {
case Some(item) =>
sendUseGeneralEntityMessage(obj, item)
@ -1196,29 +1220,33 @@ class GeneralOperations(
.contains(player.GUID))
) {
log.info(s"${player.Name} is looking in the ${obj.Definition.Name}'s trunk")
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
obj.AccessingTrunk = player.GUID
accessContainer(obj)
sendResponse(msg)
GeneralOperations.UseItem.Handled
} else {
GeneralOperations.UseItem.Unhandled
}
case _ => ()
case _ =>
GeneralOperations.UseItem.Unhandled
}
}
def handleUseTerminal(terminal: Terminal, equipment: Option[Equipment], msg: UseItemMessage): Unit = {
def handleUseTerminal(terminal: Terminal, equipment: Option[Equipment], msg: UseItemMessage): GeneralOperations.UseItem.Behavior = {
equipment match {
case Some(item) =>
sendUseGeneralEntityMessage(terminal, item)
GeneralOperations.UseItem.Handled
case None
if terminal.Owner == Building.NoBuilding || terminal.Faction == player.Faction ||
terminal.HackedBy.nonEmpty || terminal.Faction == PlanetSideEmpire.NEUTRAL =>
val tdef = terminal.Definition
if (tdef.isInstanceOf[MatrixTerminalDefinition]) {
//TODO matrix spawn point; for now, just blindly bind to show work (and hope nothing breaks)
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
sendResponse(
BindPlayerMessage(BindStatus.Bind, "", display_icon=true, logging=true, SpawnGroup.Sanctuary, 0, 0, terminal.Position)
)
GeneralOperations.UseItem.Handled
} else if (
tdef == GlobalDefinitions.multivehicle_rearm_terminal || tdef == GlobalDefinitions.bfr_rearm_terminal ||
tdef == GlobalDefinitions.air_rearm_terminal || tdef == GlobalDefinitions.ground_rearm_terminal
@ -1230,45 +1258,49 @@ class GeneralOperations(
)
sendResponse(msg)
sendResponse(msg.copy(object_guid = vehicle.GUID, object_id = vehicle.Definition.ObjectId))
GeneralOperations.UseItem.Handled
case None =>
log.error(s"UseItem: Expecting a seated vehicle, ${player.Name} found none")
GeneralOperations.UseItem.Unhandled
}
} else if (tdef == GlobalDefinitions.teleportpad_terminal) {
//explicit request
log.info(s"${player.Name} is purchasing a router telepad")
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
terminal.Actor ! Terminal.Request(
player,
ItemTransactionMessage(msg.object_guid, TransactionType.Buy, 0, "router_telepad", 0, PlanetSideGUID(0))
)
GeneralOperations.UseItem.Handled
} else if (tdef == GlobalDefinitions.targeting_laser_dispenser) {
//explicit request
log.info(s"${player.Name} is purchasing a targeting laser")
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
terminal.Actor ! Terminal.Request(
player,
ItemTransactionMessage(msg.object_guid, TransactionType.Buy, 0, "flail_targeting_laser", 0, PlanetSideGUID(0))
)
GeneralOperations.UseItem.Handled
} else {
log.info(s"${player.Name} is accessing a ${terminal.Definition.Name}")
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
sendResponse(msg)
GeneralOperations.UseItem.Handled
}
case _ => ()
case _ =>
GeneralOperations.UseItem.Unhandled
}
}
def handleUseSpawnTube(obj: SpawnTube, equipment: Option[Equipment]): Unit = {
def handleUseSpawnTube(obj: SpawnTube, equipment: Option[Equipment]): GeneralOperations.UseItem.Behavior = {
equipment match {
case Some(item) =>
sendUseGeneralEntityMessage(obj, item)
case None if player.Faction == obj.Faction =>
//deconstruction
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
sessionLogic.actionsToCancel()
sessionLogic.terminals.CancelAllProximityUnits()
sessionLogic.zoning.spawn.startDeconstructing(obj)
case _ => ()
GeneralOperations.UseItem.Handled
case _ =>
GeneralOperations.UseItem.Unhandled
}
}
@ -1277,7 +1309,7 @@ class GeneralOperations(
equipment: Option[Equipment],
msg: UseItemMessage,
useTelepadFunc: (Vehicle, InternalTelepad, TelepadDeployable, PlanetSideGameObject with TelepadLike, PlanetSideGameObject with TelepadLike) => Unit
): Unit = {
): GeneralOperations.UseItem.Behavior = {
if (equipment.isEmpty) {
(continent.GUID(obj.Router) match {
case Some(vehicle: Vehicle) => Some((vehicle, vehicle.Utility(UtilityType.internal_router_telepad_deployable)))
@ -1285,20 +1317,25 @@ class GeneralOperations(
case None => None
}) match {
case Some((vehicle: Vehicle, Some(util: Utility.InternalTelepad))) =>
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel")
player.WhichSide = vehicle.WhichSide
useTelepadFunc(vehicle, util, obj, obj, util)
GeneralOperations.UseItem.HandledPassive
case Some((vehicle: Vehicle, None)) =>
log.error(
s"telepad@${msg.object_guid.guid} is not linked to a router - ${vehicle.Definition.Name}"
)
GeneralOperations.UseItem.Unhandled
case Some((o, _)) =>
log.error(
s"telepad@${msg.object_guid.guid} is linked to wrong kind of object - ${o.Definition.Name}, ${obj.Router}"
)
obj.Actor ! Deployable.Deconstruct()
case _ => ()
GeneralOperations.UseItem.Unhandled
case _ =>
GeneralOperations.UseItem.Unhandled
}
} else {
GeneralOperations.UseItem.Unhandled
}
}
@ -1306,16 +1343,19 @@ class GeneralOperations(
obj: InternalTelepad,
msg: UseItemMessage,
useTelepadFunc: (Vehicle, InternalTelepad, TelepadDeployable, PlanetSideGameObject with TelepadLike, PlanetSideGameObject with TelepadLike) => Unit
): Unit = {
): GeneralOperations.UseItem.Behavior = {
continent.GUID(obj.Telepad) match {
case Some(pad: TelepadDeployable) =>
player.WhichSide = pad.WhichSide
useTelepadFunc(obj.Owner.asInstanceOf[Vehicle], obj, pad, obj, pad)
GeneralOperations.UseItem.HandledPassive
case Some(o) =>
log.error(
s"internal telepad@${msg.object_guid.guid} is not linked to a remote telepad - ${o.Definition.Name}@${o.GUID.guid}"
)
case None => ()
GeneralOperations.UseItem.Unhandled
case None =>
GeneralOperations.UseItem.Unhandled
}
}
@ -1393,55 +1433,63 @@ class GeneralOperations(
recentTeleportAttempt = time
}
def handleUseCaptureFlag(obj: CaptureFlag): Unit = {
def handleUseCaptureFlag(obj: CaptureFlag): GeneralOperations.UseItem.Behavior = {
// LLU can normally only be picked up the faction that owns it
specialItemSlotGuid match {
case None if obj.Faction == player.Faction =>
specialItemSlotGuid = Some(obj.GUID)
player.Carrying = SpecialCarry.CaptureFlag
continent.LocalEvents ! CaptureFlagManager.PickupFlag(obj, player)
GeneralOperations.UseItem.Handled
case None =>
log.warn(s"${player.Faction} player ${player.toString} tried to pick up a ${obj.Faction} LLU - ${obj.GUID}")
GeneralOperations.UseItem.Unhandled
case Some(guid) if guid != obj.GUID =>
// Ignore duplicate pickup requests
log.warn(
s"${player.Faction} player ${player.toString} tried to pick up a ${obj.Faction} LLU, but their special slot already contains $guid"
)
case _ => ()
GeneralOperations.UseItem.Unhandled
case _ =>
GeneralOperations.UseItem.Unhandled
}
}
def handleUseWarpGate(equipment: Option[Equipment]): Unit = {
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
def handleUseWarpGate(equipment: Option[Equipment]): GeneralOperations.UseItem.Behavior = {
(continent.GUID(player.VehicleSeated), equipment) match {
case (Some(vehicle: Vehicle), Some(item))
if GlobalDefinitions.isBattleFrameVehicle(vehicle.Definition) &&
GlobalDefinitions.isBattleFrameNTUSiphon(item.Definition) =>
vehicle.Actor ! CommonMessages.Use(player, equipment)
case _ => ()
GeneralOperations.UseItem.Handled
case _ =>
GeneralOperations.UseItem.Unhandled
}
}
def handleUseGeneralEntity(obj: PlanetSideServerObject, equipment: Option[Equipment]): Unit = {
equipment.foreach { item =>
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
obj.Actor ! CommonMessages.Use(player, Some(item))
}
def handleUseGeneralEntity(obj: PlanetSideServerObject, equipment: Option[Equipment]): GeneralOperations.UseItem.Behavior = {
equipment
.collect { item =>
obj.Actor ! CommonMessages.Use(player, Some(item))
GeneralOperations.UseItem.Handled
}
.getOrElse(GeneralOperations.UseItem.Unhandled)
}
def sendUseGeneralEntityMessage(obj: PlanetSideServerObject, equipment: Equipment): Unit = {
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
def sendUseGeneralEntityMessage(obj: PlanetSideServerObject, equipment: Equipment): GeneralOperations.UseItem.Behavior = {
obj.Actor ! CommonMessages.Use(player, Some(equipment))
GeneralOperations.UseItem.Handled
}
def handleUseDefaultEntity(obj: PlanetSideGameObject, equipment: Option[Equipment]): Unit = {
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
def handleUseDefaultEntity(obj: PlanetSideGameObject, equipment: Option[Equipment]): GeneralOperations.UseItem.Behavior = {
equipment match {
case Some(item)
if GlobalDefinitions.isBattleFrameArmorSiphon(item.Definition) ||
GlobalDefinitions.isBattleFrameNTUSiphon(item.Definition) => ()
GeneralOperations.UseItem.Handled
case _ =>
log.warn(s"UseItem: ${player.Name} does not know how to handle $obj")
GeneralOperations.UseItem.Unhandled
}
}
@ -1488,6 +1536,13 @@ class GeneralOperations(
}
object GeneralOperations {
object UseItem {
sealed trait Behavior
case object Handled extends Behavior
case object HandledPassive extends Behavior
case object Unhandled extends Behavior
}
object ItemDropState {
sealed trait Behavior
case object Dropped extends Behavior

View file

@ -2,13 +2,13 @@
package net.psforever.actors.session.support
import akka.actor.{ActorContext, typed}
import net.psforever.login.WorldSession.{CountAmmunition, FindAmmoBoxThatUses, FindEquipmentStock, PutEquipmentInInventoryOrDrop, PutNewEquipmentInInventoryOrDrop}
import net.psforever.login.WorldSession.{CountAmmunition, CountGrenades, FindAmmoBoxThatUses, FindEquipmentStock, FindToolThatUses, PutEquipmentInInventoryOrDrop, PutNewEquipmentInInventoryOrDrop, RemoveOldEquipmentFromInventory}
import net.psforever.objects.ballistics.ProjectileQuality
import net.psforever.objects.definition.{ProjectileDefinition, SpecialExoSuitDefinition}
import net.psforever.objects.entity.SimpleWorldEntity
import net.psforever.objects.equipment.{ChargeFireModeDefinition, Equipment, FireModeSwitch}
import net.psforever.objects.guid.{GUIDTask, TaskBundle, TaskWorkflow}
import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject}
import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.serverobject.doors.InteriorDoorPassage
import net.psforever.objects.serverobject.interior.Sidedness
@ -347,7 +347,7 @@ class WeaponAndProjectileOperations(
}
sendResponse(ChangeFireModeMessage(item_guid, modeIndex))
continent.AvatarEvents ! AvatarServiceMessage(
continent.id,
sessionLogic.zoning.zoneChannel,
AvatarAction.ChangeFireMode(player.GUID, item_guid, modeIndex)
)
}
@ -882,32 +882,220 @@ class WeaponAndProjectileOperations(
*/
def FindWeapon: Set[Tool] = FindContainedWeapon._2
/*
used by ChangeFireStateMessage_Start handling
*/
def fireStateStartSetup(itemGuid: PlanetSideGUID): Unit = {
prefire -= itemGuid
shooting += itemGuid
shootingStart += itemGuid -> System.currentTimeMillis()
}
def fireStateStartChargeMode(tool: Tool): Unit = {
//charge ammunition drain
tool.FireMode match {
case mode: ChargeFireModeDefinition =>
sessionLogic.general.progressBarValue = Some(0f)
sessionLogic.general.progressBarUpdate = context.system.scheduler.scheduleOnce(
(mode.Time + mode.DrainInterval) milliseconds,
context.self,
CommonMessages.ProgressEvent(1f, () => {}, Tools.ChargeFireMode(player, tool), mode.DrainInterval)
)
case _ => ()
}
}
def allowFireStateChangeStart(tool: Tool, itemGuid: PlanetSideGUID): Boolean = {
tool.FireMode.RoundsPerShot == 0 || tool.Magazine > 0 || prefire.contains(itemGuid)
}
def fireStateStopPlayerMessages(itemGuid: PlanetSideGUID): Unit = {
if (!player.spectator) {
continent.AvatarEvents ! AvatarServiceMessage(
continent.id,
AvatarAction.ChangeFireState_Stop(player.GUID, itemGuid)
)
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)
}
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)
}
}
def fireStateStopMountedMessages(itemGuid: PlanetSideGUID): Unit = {
if (!player.spectator) {
sessionLogic.findContainedEquipment()._1.collect {
case turret: FacilityTurret if continent.map.cavern =>
turret.Actor ! VanuSentry.ChangeFireStop
}
continent.VehicleEvents ! VehicleServiceMessage(
continent.id,
VehicleAction.ChangeFireState_Stop(player.GUID, itemGuid)
)
def fireStateStartWhenMounted(tool: Tool, itemGuid: PlanetSideGUID): Unit = {
if (allowFireStateChangeStart(tool, itemGuid)) {
fireStateStartSetup(itemGuid)
fireStateStartMountedMessages(itemGuid)
fireStateStartChargeMode(tool)
} else {
enforceEmptyMagazine(tool, itemGuid)
}
}
def fireStateStartPlayerMessages(itemGuid: PlanetSideGUID): Unit = {
continent.AvatarEvents ! AvatarServiceMessage(
sessionLogic.zoning.zoneChannel,
AvatarAction.ChangeFireState_Start(player.GUID, itemGuid)
)
}
def fireStateStartMountedMessages(itemGuid: PlanetSideGUID): Unit = {
sessionLogic.findContainedEquipment()._1.collect {
case turret: FacilityTurret if continent.map.cavern =>
turret.Actor ! VanuSentry.ChangeFireStart
}
continent.VehicleEvents ! VehicleServiceMessage(
continent.id,
VehicleAction.ChangeFireState_Start(player.GUID, itemGuid)
)
}
/*
used by ChangeFireStateMessage_Stop handling
*/
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)
}
def fireStateStopWhenMounted(tool: Tool, itemGuid: PlanetSideGUID): Unit = {
fireStateStopUpdateChargeAndCleanup(tool)
fireStateStopMountedMessages(itemGuid)
}
def fireStateStopPlayerMessages(itemGuid: PlanetSideGUID): Unit = {
continent.AvatarEvents ! AvatarServiceMessage(
sessionLogic.zoning.zoneChannel,
AvatarAction.ChangeFireState_Stop(player.GUID, itemGuid)
)
}
def fireStateStopMountedMessages(itemGuid: PlanetSideGUID): Unit = {
sessionLogic.findContainedEquipment()._1.collect {
case turret: FacilityTurret if continent.map.cavern =>
turret.Actor ! VanuSentry.ChangeFireStop
}
continent.VehicleEvents ! VehicleServiceMessage(
continent.id,
VehicleAction.ChangeFireState_Stop(player.GUID, itemGuid)
)
}
/**
* After a weapon has finished shooting, determine if it needs to be sorted in a special way.
* @param tool a weapon
*/
def fireCycleCleanup(tool: Tool): Unit = {
//TODO replaced by more appropriate functionality in the future
val tdef = tool.Definition
if (GlobalDefinitions.isGrenade(tdef)) {
val ammoType = tool.AmmoType
FindEquipmentStock(player, FindToolThatUses(ammoType), 3, CountGrenades).reverse match { //do not search sidearm holsters
case Nil =>
RemoveOldEquipmentFromInventory(player)(tool)
case x :: xs => //this is similar to ReloadMessage
val box = x.obj.asInstanceOf[Tool]
val tailReloadValue: Int = if (xs.isEmpty) { 0 }
else { xs.map(_.obj.asInstanceOf[Tool].Magazine).sum }
val sumReloadValue: Int = box.Magazine + tailReloadValue
val actualReloadValue = if (sumReloadValue <= 3) {
RemoveOldEquipmentFromInventory(player)(x.obj)
sumReloadValue
} else {
modifyAmmunition(player)(box.AmmoSlot.Box, 3 - tailReloadValue)
3
}
modifyAmmunition(player)(
tool.AmmoSlot.Box,
-actualReloadValue
) //grenade item already in holster (negative because empty)
xs.foreach(item => { RemoveOldEquipmentFromInventory(player)(item.obj) })
}
} else if (tdef == GlobalDefinitions.phoenix) {
RemoveOldEquipmentFromInventory(player)(tool)
}
}
def fireStateStopUpdateChargeAndCleanup(tool: Tool): Unit = {
tool.FireMode match {
case _: ChargeFireModeDefinition =>
sendResponse(QuantityUpdateMessage(tool.AmmoSlot.Box.GUID, tool.Magazine))
case _ => ()
}
if (tool.Magazine == 0) {
fireCycleCleanup(tool)
}
}
/*
used by ReloadMessage handling
*/
def reloadPlayerMessages(itemGuid: PlanetSideGUID): Unit = {
continent.AvatarEvents ! AvatarServiceMessage(
sessionLogic.zoning.zoneChannel,
AvatarAction.Reload(player.GUID, itemGuid)
)
}
def reloadVehicleMessages(itemGuid: PlanetSideGUID): Unit = {
continent.VehicleEvents ! VehicleServiceMessage(
continent.id,
VehicleAction.Reload(player.GUID, itemGuid)
)
}
def handleReloadWhenPlayer(
itemGuid: PlanetSideGUID,
obj: Player,
tools: Set[Tool],
unk1: Int
): Unit = {
handleReloadProcedure(
itemGuid,
obj,
tools,
unk1,
RemoveOldEquipmentFromInventory(obj)(_),
modifyAmmunition(obj)(_, _),
reloadPlayerMessages
)
}
def handleReloadWhenMountable(
itemGuid: PlanetSideGUID,
obj: PlanetSideServerObject with Container,
tools: Set[Tool],
unk1: Int
): Unit = {
handleReloadProcedure(
itemGuid,
obj,
tools,
unk1,
RemoveOldEquipmentFromInventory(obj)(_),
modifyAmmunitionInMountable(obj)(_, _),
reloadVehicleMessages
)
}
private def addShotsFired(weaponId: Int, shots: Int): Unit = {
addShotsToMap(shotsFired, weaponId, shots)
}
@ -1045,7 +1233,7 @@ class WeaponAndProjectileOperations(
val boxDef = box.Definition
sendResponse(ChangeAmmoMessage(tool_guid, box.Capacity))
continent.AvatarEvents ! AvatarServiceMessage(
continent.id,
sessionLogic.zoning.zoneChannel,
AvatarAction.ChangeAmmo(
player.GUID,
tool_guid,

View file

@ -194,6 +194,8 @@ class ZoningOperations(
private[session] val spawn: SpawnOperations = new SpawnOperations()
private[session] var maintainInitialGmState: Boolean = false
private[session] var zoneChannel: String = Zone.Nowhere.id
private var loadConfZone: Boolean = false
private var instantActionFallbackDestination: Option[Zoning.InstantAction.Located] = None
private var zoningType: Zoning.Method = Zoning.Method.None
@ -2129,6 +2131,7 @@ class ZoningOperations(
val map = zone.map
val mapName = map.name
log.info(s"${tplayer.Name} has spawned into $id")
sessionLogic.zoning.zoneChannel = Players.ZoneChannelIfSpectating(tplayer, zone.id)
sessionLogic.oldRefsMap.clear()
sessionLogic.persist = UpdatePersistenceAndRefs
tplayer.avatar = avatar
@ -2442,7 +2445,13 @@ class ZoningOperations(
case _ if player.spectator =>
player.VehicleSeated = None
val definition = player.avatar.definition
val guid = player.GUID
sendResponse(OCM.detailed(player))
continent.AvatarEvents ! AvatarServiceMessage(
s"spectator",
AvatarAction.LoadPlayer(guid, definition.ObjectId, guid, definition.Packet.ConstructorData(player).get, None)
)
case _ =>
player.VehicleSeated = None
@ -2869,7 +2878,6 @@ class ZoningOperations(
)
)
nextSpawnPoint = physSpawnPoint
prevSpawnPoint = physSpawnPoint
shiftPosition = Some(pos)
shiftOrientation = Some(ori)
val toZoneNumber = if (continent.id.equals(zoneId)) {
@ -3019,6 +3027,7 @@ class ZoningOperations(
case _ =>
NormalKeepAlive
}
prevSpawnPoint = nextSpawnPoint
nextSpawnPoint = None
}
//if not the condition above, player has started playing normally

View file

@ -488,4 +488,35 @@ object Players {
}
player.HistoryAndContributions()
}
/**
* Select the player's zone channel.
* If the player is spectating, then that is their channel instead.
* The resulting channel name should never be used for subscribing - only for publishing.
* @param player player in a zone
* @return channel name
*/
def ZoneChannelIfSpectating(player: Player): String = {
if (player.spectator) {
"spectator"
} else {
player.Zone.id
}
}
/**
* Select the player's zone channel.
* If the player is spectating, then that is their channel instead.
* The resulting channel name should never be used for subscribing - only for publishing.
* @param player player in a zone
* @param zoneid custom zone name to be used as the channel name
* @return channel name
*/
def ZoneChannelIfSpectating(player: Player, zoneid: String): String = {
if (player.spectator) {
"spectator"
} else {
zoneid
}
}
}

View file

@ -320,7 +320,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
} else if ((!resistance && before != slot && (player.DrawnSlot = slot) != before) && ItemSwapSlot != before) {
val mySlot = if (updateMyHolsterArm) slot else -1 //use as a short-circuit
events ! AvatarServiceMessage(
player.Continent,
Players.ZoneChannelIfSpectating(player),
AvatarAction.ObjectHeld(player.GUID, mySlot, player.LastDrawnSlot)
)
val isHolsters = player.VisibleSlots.contains(slot)
@ -332,11 +332,11 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
if (unholsteredItem.Definition == GlobalDefinitions.remote_electronics_kit) {
//rek beam/icon colour must match the player's correct hack level
events ! AvatarServiceMessage(
player.Continent,
Players.ZoneChannelIfSpectating(player),
AvatarAction.PlanetsideAttribute(unholsteredItem.GUID, 116, player.avatar.hackingSkillLevel())
)
}
case None => ;
case None => ()
}
} else {
equipment match {
@ -479,7 +479,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
avatarActor ! AvatarActor.DeactivateActiveImplants
val zone = player.Zone
zone.AvatarEvents ! AvatarServiceMessage(
zone.id,
Players.ZoneChannelIfSpectating(player),
AvatarAction.ChangeLoadout(
player.GUID,
toArmor,
@ -674,7 +674,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
//deactivate non-passive implants
avatarActor ! AvatarActor.DeactivateActiveImplants
player.Zone.AvatarEvents ! AvatarServiceMessage(
player.Zone.id,
Players.ZoneChannelIfSpectating(player),
AvatarAction.ChangeExosuit(
player.GUID,
toArmor,

View file

@ -65,8 +65,14 @@ object AvatarConverter {
*/
def MakeAppearanceData(obj: Player): Int => CharacterAppearanceData = {
val alt_model_flag: Boolean = obj.isBackpack
val avatar = obj.avatar
val tempAvatarInfo = if (obj.spectator) {
avatar.basic.copy(name = s"<spectator:${avatar.basic.name}>")
} else {
avatar.basic
}
val aa: Int => CharacterAppearanceA = CharacterAppearanceA(
obj.avatar.basic,
tempAvatarInfo,
CommonFieldData(
obj.Faction,
bops = obj.bops,
@ -106,7 +112,7 @@ object AvatarConverter {
unk7 = false,
on_zipline = None
)
CharacterAppearanceData(aa, ab, obj.avatar.decoration.ribbonBars)
CharacterAppearanceData(aa, ab, avatar.decoration.ribbonBars)
}
def MakeCharacterData(obj: Player): (Boolean, Boolean) => CharacterData = {

View file

@ -2,11 +2,34 @@
package net.psforever.packet.game
import net.psforever.newcodecs._
import net.psforever.packet.GamePacketOpcode.Type
import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
import net.psforever.types.ChatMessageType
import scodec.Codec
import scodec.bits.BitVector
import scodec.{Attempt, Codec}
import scodec.codecs._
/*
For colors, type '/#n' before text, where `n` is one of the following hexadecimal numbers:
0 white
1 black
2 cyan
3 yellow
4 green
5 light blue
6 brown
7 violet
8 magneta
9 purple
a purple
b yellow green
c blue
d light pink
e light green
f beige
All other options result in white text.
*/
/**
* Instructs client to display and/or process a chat message/command when sent server to client.
* Instructs server to route and/or process a chat message/command when sent client to server.
@ -35,8 +58,8 @@ final case class ChatMsg(
assert(note.isEmpty, "Note contents found, but message type isnt Note")
type Packet = ChatMsg
def opcode = GamePacketOpcode.ChatMsg
def encode = ChatMsg.encode(this)
def opcode: Type = GamePacketOpcode.ChatMsg
def encode: Attempt[BitVector] = ChatMsg.encode(this)
}
object ChatMsg extends Marshallable[ChatMsg] {