Fixing Tests (#1204)

* fixed about half of the unworking tests, and commented out one

* stubborn tests that pass on their own but don't tend to pass in clusters; also, a certain test that terminates an actor when a mostly unrelated entity has its propertries changed from default, just weird

* reviewing logic and operations pairs to ensure that functionality should have been retained from parent structure; moving handling case from individual player modes to session actor, which makes it much closer to the pattern

* while it's still a dice roll, all tests currently implemented are capable of passing

* deployable vehicles should properly deploy again now that they don't have to fight with themselves for the ability to deploy

* boomers are no longer owned if the trigger is dropped (how long has this been not working?)

* redid DamageFeedbackMessage packet because I thought I could use it for something; didn't use it for anything; boomers are no longer responsive to explosive sympathy

* redid combat engineering explosive logic

* redid (cleaned-up) implant logic

* implant initialization timers now saved to the database; uninitialized implants will appear as uninitialized when the character loads; passive initialized implants will always start as activate

* renaming methods; progress bar calculations change

* accounting for implants that are in the act of being initialized
This commit is contained in:
Fate-JH 2024-06-22 01:42:25 -04:00 committed by GitHub
parent 306e2a63c0
commit 92063ba3a2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
68 changed files with 2454 additions and 2861 deletions

View file

@ -0,0 +1,3 @@
/* New */
ALTER TABLE implant
ADD COLUMN timer SMALLINT NOT NULL DEFAULT 0;

File diff suppressed because it is too large Load diff

View file

@ -1,8 +1,29 @@
// Copyright (c) 2016, 2020, 2024 PSForever // Copyright (c) 2016, 2020, 2024 PSForever
package net.psforever.actors.session package net.psforever.actors.session
import akka.actor.{Actor, Cancellable, MDCContextAware, typed} import akka.actor.{Actor, ActorRef, Cancellable, MDCContextAware, typed}
import net.psforever.actors.session.normal.NormalMode import net.psforever.actors.session.normal.NormalMode
import net.psforever.actors.session.support.ZoningOperations
import net.psforever.objects.TurretDeployable
import net.psforever.objects.serverobject.CommonMessages
import net.psforever.objects.serverobject.containable.Containable
import net.psforever.objects.serverobject.deploy.Deployment
import net.psforever.objects.serverobject.mount.Mountable
import net.psforever.objects.serverobject.terminals.{ProximityUnit, Terminal}
import net.psforever.objects.zones.Zone
import net.psforever.packet.PlanetSideGamePacket
import net.psforever.packet.game.{AIDamage, ActionCancelMessage, AvatarFirstTimeEventMessage, AvatarGrenadeStateMessage, AvatarImplantMessage, AvatarJumpMessage, BattleplanMessage, BeginZoningMessage, BindPlayerMessage, BugReportMessage, ChangeAmmoMessage, ChangeFireModeMessage, ChangeFireStateMessage_Start, ChangeFireStateMessage_Stop, ChangeShortcutBankMessage, CharacterCreateRequestMessage, CharacterRequestMessage, ChatMsg, ChildObjectStateMessage, ConnectToWorldRequestMessage, CreateShortcutMessage, DeployObjectMessage, DeployRequestMessage, DismountVehicleCargoMsg, DismountVehicleMsg, DisplayedAwardMessage, DropItemMessage, DroppodLaunchRequestMessage, EmoteMsg, FacilityBenefitShieldChargeRequestMessage, FavoritesRequest, FrameVehicleStateMessage, FriendsRequest, GenericActionMessage, GenericCollisionMsg, GenericObjectActionAtPositionMessage, GenericObjectActionMessage, GenericObjectStateMsg, HitHint, HitMessage, InvalidTerrainMessage, ItemTransactionMessage, LashMessage, LongRangeProjectileInfoMessage, LootItemMessage, MountVehicleCargoMsg, MountVehicleMsg, MoveItemMessage, ObjectDetectedMessage, ObjectHeldMessage, OutfitRequest, PickupItemMessage, PlanetsideAttributeMessage, PlayerStateMessageUpstream, ProjectileStateMessage, ProximityTerminalUseMessage, ReleaseAvatarRequestMessage, ReloadMessage, RequestDestroyMessage, SetChatFilterMessage, SpawnRequestMessage, SplashHitMessage, SquadDefinitionActionMessage, SquadMembershipRequest, SquadWaypointRequest, TargetingImplantRequest, TradeMessage, UnuseItemMessage, UplinkRequest, UseItemMessage, VehicleStateMessage, VehicleSubStateMessage, VoiceHostInfo, VoiceHostRequest, WarpgateRequest, WeaponDelayFireMessage, WeaponDryFireMessage, WeaponFireMessage, WeaponLazeTargetPositionMessage, ZipLineMessage}
import net.psforever.services.{InterstellarClusterService => ICS}
import net.psforever.services.CavernRotationService
import net.psforever.services.CavernRotationService.SendCavernRotationUpdates
import net.psforever.services.ServiceManager.LookupResult
import net.psforever.services.account.{PlayerToken, ReceiveAccountData}
import net.psforever.services.avatar.AvatarServiceResponse
import net.psforever.services.chat.ChatService
import net.psforever.services.galaxy.GalaxyServiceResponse
import net.psforever.services.local.LocalServiceResponse
import net.psforever.services.teamwork.SquadServiceResponse
import net.psforever.services.vehicle.VehicleServiceResponse
import org.joda.time.LocalDateTime import org.joda.time.LocalDateTime
import org.log4s.MDC import org.log4s.MDC
@ -105,23 +126,18 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
} }
private def inTheGame: Receive = { private def inTheGame: Receive = {
/* used for the game's heartbeat */
case SessionActor.StartHeartbeat => case SessionActor.StartHeartbeat =>
//used for the game's heartbeat
startHeartbeat() startHeartbeat()
case SessionActor.PokeClient => case SessionActor.PokeClient =>
middlewareActor ! MiddlewareActor.Send(KeepAliveMessage()) pokeClient()
case SessionActor.SetMode(newMode) => case SessionActor.SetMode(newMode) =>
if (mode != newMode) { changeMode(newMode)
logic.switchFrom(data.session)
}
mode = newMode
logic = mode.setup(data)
logic.switchTo(data.session)
case packet => case packet =>
logic.parse(sender())(packet) parse(sender())(packet)
} }
private def startHeartbeat(): Unit = { private def startHeartbeat(): Unit = {
@ -135,4 +151,467 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
SessionActor.PokeClient SessionActor.PokeClient
) )
} }
private def pokeClient(): Unit = {
middlewareActor ! MiddlewareActor.Send(KeepAliveMessage())
}
private def changeMode(newMode: PlayerMode): Unit = {
if (mode != newMode) {
logic.switchFrom(data.session)
mode = newMode
logic = mode.setup(data)
}
logic.switchTo(data.session)
}
private def parse(sender: ActorRef): Receive = {
/* really common messages (very frequently, every life) */
case packet: PlanetSideGamePacket =>
handleGamePkt(packet)
case AvatarServiceResponse(toChannel, guid, reply) =>
logic.avatarResponse.handle(toChannel, guid, reply)
case GalaxyServiceResponse(_, reply) =>
logic.galaxy.handle(reply)
case LocalServiceResponse(toChannel, guid, reply) =>
logic.local.handle(toChannel, guid, reply)
case Mountable.MountMessages(tplayer, reply) =>
logic.mountResponse.handle(tplayer, reply)
case SquadServiceResponse(_, excluded, response) =>
logic.squad.handle(response, excluded)
case Terminal.TerminalMessage(tplayer, msg, order) =>
logic.terminals.handle(tplayer, msg, order)
case VehicleServiceResponse(toChannel, guid, reply) =>
logic.vehicleResponse.handle(toChannel, guid, reply)
case ChatService.MessageResponse(fromSession, message, _) =>
logic.chat.handleIncomingMessage(message, fromSession)
case SessionActor.SendResponse(packet) =>
data.sendResponse(packet)
case SessionActor.CharSaved =>
logic.general.handleRenewCharSavedTimer()
case SessionActor.CharSavedMsg =>
logic.general.handleRenewCharSavedTimerMsg()
/* common messages (maybe once every respawn) */
case ICS.SpawnPointResponse(response) =>
data.zoning.handleSpawnPointResponse(response)
case SessionActor.NewPlayerLoaded(tplayer) =>
data.zoning.spawn.handleNewPlayerLoaded(tplayer)
case SessionActor.PlayerLoaded(tplayer) =>
data.zoning.spawn.handlePlayerLoaded(tplayer)
case Zone.Population.PlayerHasLeft(zone, playerOpt) =>
data.zoning.spawn.handlePlayerHasLeft(zone, playerOpt)
case Zone.Population.PlayerCanNotSpawn(zone, tplayer) =>
data.zoning.spawn.handlePlayerCanNotSpawn(zone, tplayer)
case Zone.Population.PlayerAlreadySpawned(zone, tplayer) =>
data.zoning.spawn.handlePlayerAlreadySpawned(zone, tplayer)
case Zone.Vehicle.CanNotSpawn(zone, vehicle, reason) =>
data.zoning.spawn.handleCanNotSpawn(zone, vehicle, reason)
case Zone.Vehicle.CanNotDespawn(zone, vehicle, reason) =>
data.zoning.spawn.handleCanNotDespawn(zone, vehicle, reason)
case ICS.ZoneResponse(Some(zone)) =>
data.zoning.handleZoneResponse(zone)
/* uncommon messages (once a session) */
case ICS.ZonesResponse(zones) =>
data.zoning.handleZonesResponse(zones)
case SessionActor.SetAvatar(avatar) =>
logic.general.handleSetAvatar(avatar)
case PlayerToken.LoginInfo(name, Zone.Nowhere, _) =>
data.zoning.spawn.handleLoginInfoNowhere(name, sender)
case PlayerToken.LoginInfo(name, inZone, optionalSavedData) =>
data.zoning.spawn.handleLoginInfoSomewhere(name, inZone, optionalSavedData, sender)
case PlayerToken.RestoreInfo(playerName, inZone, pos) =>
data.zoning.spawn.handleLoginInfoRestore(playerName, inZone, pos, sender)
case PlayerToken.CanNotLogin(playerName, reason) =>
data.zoning.spawn.handleLoginCanNot(playerName, reason)
case ReceiveAccountData(account) =>
logic.general.handleReceiveAccountData(account)
case AvatarActor.AvatarResponse(avatar) =>
logic.general.handleAvatarResponse(avatar)
case AvatarActor.AvatarLoginResponse(avatar) =>
data.zoning.spawn.avatarLoginResponse(avatar)
case SessionActor.SetCurrentAvatar(tplayer, max_attempts, attempt) =>
data.zoning.spawn.ReadyToSetCurrentAvatar(tplayer, max_attempts, attempt)
case SessionActor.SetConnectionState(state) =>
data.connectionState = state
case SessionActor.AvatarLoadingSync(state) =>
data.zoning.spawn.handleAvatarLoadingSync(state)
/* uncommon messages (utility, or once in a while) */
case ZoningOperations.AvatarAwardMessageBundle(pkts, delay) =>
data.zoning.spawn.performAvatarAwardMessageDelivery(pkts, delay)
case CommonMessages.ProgressEvent(delta, finishedAction, stepAction, tick) =>
data.general.handleProgressChange(delta, finishedAction, stepAction, tick)
case CommonMessages.Progress(rate, finishedAction, stepAction) =>
data.general.setupProgressChange(rate, finishedAction, stepAction)
case CavernRotationService.CavernRotationServiceKey.Listing(listings) =>
listings.head ! SendCavernRotationUpdates(data.context.self)
case LookupResult("propertyOverrideManager", endpoint) =>
data.zoning.propertyOverrideManagerLoadOverrides(endpoint)
case SessionActor.UpdateIgnoredPlayers(msg) =>
logic.galaxy.handleUpdateIgnoredPlayers(msg)
case SessionActor.UseCooldownRenewed(definition, _) =>
logic.general.handleUseCooldownRenew(definition)
case Deployment.CanDeploy(obj, state) =>
logic.vehicles.handleCanDeploy(obj, state)
case Deployment.CanUndeploy(obj, state) =>
logic.vehicles.handleCanUndeploy(obj, state)
case Deployment.CanNotChangeDeployment(obj, state, reason) =>
logic.vehicles.handleCanNotChangeDeployment(obj, state, reason)
/* rare messages */
case ProximityUnit.StopAction(term, _) =>
logic.terminals.ops.LocalStopUsingProximityUnit(term)
case SessionActor.Suicide() =>
data.general.suicide(data.player)
case SessionActor.Recall() =>
data.zoning.handleRecall()
case SessionActor.InstantAction() =>
data.zoning.handleInstantAction()
case SessionActor.Quit() =>
data.zoning.handleQuit()
case ICS.DroppodLaunchDenial(errorCode, _) =>
data.zoning.handleDroppodLaunchDenial(errorCode)
case ICS.DroppodLaunchConfirmation(zone, position) =>
data.zoning.LoadZoneLaunchDroppod(zone, position)
case SessionActor.PlayerFailedToLoad(tplayer) =>
data.zoning.spawn.handlePlayerFailedToLoad(tplayer)
/* csr only */
case SessionActor.SetSpeed(speed) =>
logic.general.handleSetSpeed(speed)
case SessionActor.SetFlying(isFlying) =>
logic.general.handleSetFlying(isFlying)
case SessionActor.SetSpectator(isSpectator) =>
logic.general.handleSetSpectator(isSpectator)
case SessionActor.Kick(player, time) =>
logic.general.handleKick(player, time)
case SessionActor.SetZone(zoneId, position) =>
data.zoning.handleSetZone(zoneId, position)
case SessionActor.SetPosition(position) =>
data.zoning.spawn.handleSetPosition(position)
case SessionActor.SetSilenced(silenced) =>
logic.general.handleSilenced(silenced)
/* catch these messages */
case _: ProximityUnit.Action => ()
case _: Zone.Vehicle.HasSpawned => ()
case _: Zone.Vehicle.HasDespawned => ()
case Zone.Deployable.IsDismissed(obj: TurretDeployable) => //only if target deployable was never fully introduced
logic.local.handleTurretDeployableIsDismissed(obj)
case Zone.Deployable.IsDismissed(obj) => //only if target deployable was never fully introduced
logic.local.handleDeployableIsDismissed(obj)
case msg: Containable.ItemPutInSlot =>
logic.general.handleItemPutInSlot(msg)
case msg: Containable.CanNotPutItemInSlot =>
logic.general.handleCanNotPutItemInSlot(msg)
case default =>
logic.general.handleReceiveDefaultMessage(default, sender)
}
private def handleGamePkt: PlanetSideGamePacket => Unit = {
case packet: ConnectToWorldRequestMessage =>
logic.general.handleConnectToWorldRequest(packet)
case packet: MountVehicleCargoMsg =>
logic.mountResponse.handleMountVehicleCargo(packet)
case packet: DismountVehicleCargoMsg =>
logic.mountResponse.handleDismountVehicleCargo(packet)
case packet: CharacterCreateRequestMessage =>
logic.general.handleCharacterCreateRequest(packet)
case packet: CharacterRequestMessage =>
logic.general.handleCharacterRequest(packet)
case _: KeepAliveMessage =>
data.keepAliveFunc()
case packet: BeginZoningMessage =>
data.zoning.handleBeginZoning(packet)
case packet: PlayerStateMessageUpstream =>
logic.general.handlePlayerStateUpstream(packet)
case packet: ChildObjectStateMessage =>
logic.vehicles.handleChildObjectState(packet)
case packet: VehicleStateMessage =>
logic.vehicles.handleVehicleState(packet)
case packet: VehicleSubStateMessage =>
logic.vehicles.handleVehicleSubState(packet)
case packet: FrameVehicleStateMessage =>
logic.vehicles.handleFrameVehicleState(packet)
case packet: ProjectileStateMessage =>
logic.shooting.handleProjectileState(packet)
case packet: LongRangeProjectileInfoMessage =>
logic.shooting.handleLongRangeProjectileState(packet)
case packet: ReleaseAvatarRequestMessage =>
data.zoning.spawn.handleReleaseAvatarRequest(packet)
case packet: SpawnRequestMessage =>
data.zoning.spawn.handleSpawnRequest(packet)
case packet: ChatMsg =>
logic.chat.handleChatMsg(packet)
case packet: SetChatFilterMessage =>
logic.chat.handleChatFilter(packet)
case packet: VoiceHostRequest =>
logic.general.handleVoiceHostRequest(packet)
case packet: VoiceHostInfo =>
logic.general.handleVoiceHostInfo(packet)
case packet: ChangeAmmoMessage =>
logic.shooting.handleChangeAmmo(packet)
case packet: ChangeFireModeMessage =>
logic.shooting.handleChangeFireMode(packet)
case packet: ChangeFireStateMessage_Start =>
logic.shooting.handleChangeFireStateStart(packet)
case packet: ChangeFireStateMessage_Stop =>
logic.shooting.handleChangeFireStateStop(packet)
case packet: EmoteMsg =>
logic.general.handleEmote(packet)
case packet: DropItemMessage =>
logic.general.handleDropItem(packet)
case packet: PickupItemMessage =>
logic.general.handlePickupItem(packet)
case packet: ReloadMessage =>
logic.shooting.handleReload(packet)
case packet: ObjectHeldMessage =>
logic.general.handleObjectHeld(packet)
case packet: AvatarJumpMessage =>
logic.general.handleAvatarJump(packet)
case packet: ZipLineMessage =>
logic.general.handleZipLine(packet)
case packet: RequestDestroyMessage =>
logic.general.handleRequestDestroy(packet)
case packet: MoveItemMessage =>
logic.general.handleMoveItem(packet)
case packet: LootItemMessage =>
logic.general.handleLootItem(packet)
case packet: AvatarImplantMessage =>
logic.general.handleAvatarImplant(packet)
case packet: UseItemMessage =>
logic.general.handleUseItem(packet)
case packet: UnuseItemMessage =>
logic.general.handleUnuseItem(packet)
case packet: ProximityTerminalUseMessage =>
logic.terminals.handleProximityTerminalUse(packet)
case packet: DeployObjectMessage =>
logic.general.handleDeployObject(packet)
case packet: GenericObjectActionMessage =>
logic.general.handleGenericObjectAction(packet)
case packet: GenericObjectActionAtPositionMessage =>
logic.general.handleGenericObjectActionAtPosition(packet)
case packet: GenericObjectStateMsg =>
logic.general.handleGenericObjectState(packet)
case packet: GenericActionMessage =>
logic.general.handleGenericAction(packet)
case packet: ItemTransactionMessage =>
logic.terminals.handleItemTransaction(packet)
case packet: FavoritesRequest =>
logic.terminals.handleFavoritesRequest(packet)
case packet: WeaponDelayFireMessage =>
logic.shooting.handleWeaponDelayFire(packet)
case packet: WeaponDryFireMessage =>
logic.shooting.handleWeaponDryFire(packet)
case packet: WeaponFireMessage =>
logic.shooting.handleWeaponFire(packet)
case packet: WeaponLazeTargetPositionMessage =>
logic.shooting.handleWeaponLazeTargetPosition(packet)
case _: UplinkRequest => ()
case packet: HitMessage =>
logic.shooting.handleDirectHit(packet)
case packet: SplashHitMessage =>
logic.shooting.handleSplashHit(packet)
case packet: LashMessage =>
logic.shooting.handleLashHit(packet)
case packet: AIDamage =>
logic.shooting.handleAIDamage(packet)
case packet: AvatarFirstTimeEventMessage =>
logic.general.handleAvatarFirstTimeEvent(packet)
case packet: WarpgateRequest =>
data.zoning.handleWarpgateRequest(packet)
case packet: MountVehicleMsg =>
logic.mountResponse.handleMountVehicle(packet)
case packet: DismountVehicleMsg =>
logic.mountResponse.handleDismountVehicle(packet)
case packet: DeployRequestMessage =>
logic.vehicles.handleDeployRequest(packet)
case packet: AvatarGrenadeStateMessage =>
logic.shooting.handleAvatarGrenadeState(packet)
case packet: SquadDefinitionActionMessage =>
logic.squad.handleSquadDefinitionAction(packet)
case packet: SquadMembershipRequest =>
logic.squad.handleSquadMemberRequest(packet)
case packet: SquadWaypointRequest =>
logic.squad.handleSquadWaypointRequest(packet)
case packet: GenericCollisionMsg =>
logic.general.handleGenericCollision(packet)
case packet: BugReportMessage =>
logic.general.handleBugReport(packet)
case packet: BindPlayerMessage =>
logic.general.handleBindPlayer(packet)
case packet: PlanetsideAttributeMessage =>
logic.general.handlePlanetsideAttribute(packet)
case packet: FacilityBenefitShieldChargeRequestMessage =>
logic.general.handleFacilityBenefitShieldChargeRequest(packet)
case packet: BattleplanMessage =>
logic.general.handleBattleplan(packet)
case packet: CreateShortcutMessage =>
logic.general.handleCreateShortcut(packet)
case packet: ChangeShortcutBankMessage =>
logic.general.handleChangeShortcutBank(packet)
case packet: FriendsRequest =>
logic.general.handleFriendRequest(packet)
case packet: DroppodLaunchRequestMessage =>
data.zoning.handleDroppodLaunchRequest(packet)
case packet: InvalidTerrainMessage =>
logic.general.handleInvalidTerrain(packet)
case packet: ActionCancelMessage =>
logic.general.handleActionCancel(packet)
case packet: TradeMessage =>
logic.general.handleTrade(packet)
case packet: DisplayedAwardMessage =>
logic.general.handleDisplayedAward(packet)
case packet: ObjectDetectedMessage =>
logic.general.handleObjectDetected(packet)
case packet: TargetingImplantRequest =>
logic.general.handleTargetingImplantRequest(packet)
case packet: HitHint =>
logic.general.handleHitHint(packet)
case _: OutfitRequest => ()
case pkt =>
data.log.warn(s"Unhandled GamePacket $pkt")
}
} }

View file

@ -3,6 +3,8 @@ package net.psforever.actors.session.normal
import akka.actor.{ActorContext, typed} import akka.actor.{ActorContext, typed}
import net.psforever.actors.session.support.AvatarHandlerFunctions import net.psforever.actors.session.support.AvatarHandlerFunctions
import net.psforever.packet.game.{AvatarImplantMessage, CreateShortcutMessage, ImplantAction}
import net.psforever.types.ImplantType
import scala.concurrent.duration._ import scala.concurrent.duration._
// //
@ -155,6 +157,37 @@ class AvatarHandlerLogic(val ops: SessionAvatarHandlers, implicit val context: A
} }
} }
case AvatarResponse.AvatarImplant(ImplantAction.Add, implant_slot, value)
if value == ImplantType.SecondWind.value =>
sendResponse(AvatarImplantMessage(resolvedPlayerGuid, ImplantAction.Add, implant_slot, 7))
//second wind does not normally load its icon into the shortcut hotbar
avatar
.shortcuts
.zipWithIndex
.find { case (s, _) => s.isEmpty}
.foreach { case (_, index) =>
sendResponse(CreateShortcutMessage(resolvedPlayerGuid, index + 1, Some(ImplantType.SecondWind.shortcut)))
}
case AvatarResponse.AvatarImplant(ImplantAction.Remove, implant_slot, value)
if value == ImplantType.SecondWind.value =>
sendResponse(AvatarImplantMessage(resolvedPlayerGuid, ImplantAction.Remove, implant_slot, value))
//second wind does not normally unload its icon from the shortcut hotbar
val shortcut = {
val imp = ImplantType.SecondWind.shortcut
net.psforever.objects.avatar.Shortcut(imp.code, imp.tile) //case class
}
avatar
.shortcuts
.zipWithIndex
.find { case (s, _) => s.contains(shortcut) }
.foreach { case (_, index) =>
sendResponse(CreateShortcutMessage(resolvedPlayerGuid, index + 1, None))
}
case AvatarResponse.AvatarImplant(action, implant_slot, value) =>
sendResponse(AvatarImplantMessage(resolvedPlayerGuid, action, implant_slot, value))
case AvatarResponse.ObjectHeld(slot, _) case AvatarResponse.ObjectHeld(slot, _)
if isSameTarget && player.VisibleSlots.contains(slot) => if isSameTarget && player.VisibleSlots.contains(slot) =>
sendResponse(ObjectHeldMessage(guid, slot, unk1=true)) sendResponse(ObjectHeldMessage(guid, slot, unk1=true))
@ -469,7 +502,8 @@ class AvatarHandlerLogic(val ops: SessionAvatarHandlers, implicit val context: A
case AvatarResponse.Release(tplayer) if isNotSameTarget => case AvatarResponse.Release(tplayer) if isNotSameTarget =>
sessionLogic.zoning.spawn.DepictPlayerAsCorpse(tplayer) sessionLogic.zoning.spawn.DepictPlayerAsCorpse(tplayer)
case AvatarResponse.Revive(revivalTargetGuid) if resolvedPlayerGuid == revivalTargetGuid => case AvatarResponse.Revive(revivalTargetGuid)
if resolvedPlayerGuid == revivalTargetGuid =>
log.info(s"No time for rest, ${player.Name}. Back on your feet!") log.info(s"No time for rest, ${player.Name}. Back on your feet!")
sessionLogic.zoning.spawn.reviveTimer.cancel() sessionLogic.zoning.spawn.reviveTimer.cancel()
sessionLogic.zoning.spawn.deadState = DeadState.Alive sessionLogic.zoning.spawn.deadState = DeadState.Alive

View file

@ -65,12 +65,13 @@ class GalaxyHandlerLogic(val ops: SessionGalaxyHandlers, implicit val context: A
case GalaxyResponse.LockedZoneUpdate(zone, time) => case GalaxyResponse.LockedZoneUpdate(zone, time) =>
sendResponse(ZoneInfoMessage(zone.Number, empire_status=false, lock_time=time)) sendResponse(ZoneInfoMessage(zone.Number, empire_status=false, lock_time=time))
case GalaxyResponse.UnlockedZoneUpdate(zone) => ; case GalaxyResponse.UnlockedZoneUpdate(zone) =>
sendResponse(ZoneInfoMessage(zone.Number, empire_status=true, lock_time=0L)) sendResponse(ZoneInfoMessage(zone.Number, empire_status=true, lock_time=0L))
val popBO = 0 val popBO = 0
val popTR = zone.Players.count(_.faction == PlanetSideEmpire.TR) val pop = zone.LivePlayers.distinctBy(_.CharId)
val popNC = zone.Players.count(_.faction == PlanetSideEmpire.NC) val popTR = pop.count(_.Faction == PlanetSideEmpire.TR)
val popVS = zone.Players.count(_.faction == PlanetSideEmpire.VS) val popNC = pop.count(_.Faction == PlanetSideEmpire.NC)
val popVS = pop.count(_.Faction == PlanetSideEmpire.VS)
sendResponse(ZonePopulationUpdateMessage(zone.Number, 414, 138, popTR, 138, popNC, 138, popVS, 138, popBO)) sendResponse(ZonePopulationUpdateMessage(zone.Number, 414, 138, popTR, 138, popNC, 138, popVS, 138, popBO))
case GalaxyResponse.LogStatusChange(name) if avatar.people.friend.exists(_.name.equals(name)) => case GalaxyResponse.LogStatusChange(name) if avatar.people.friend.exists(_.name.equals(name)) =>

View file

@ -2,7 +2,7 @@
package net.psforever.actors.session.normal package net.psforever.actors.session.normal
import akka.actor.typed.scaladsl.adapter._ import akka.actor.typed.scaladsl.adapter._
import akka.actor.{ActorContext, typed} import akka.actor.{ActorContext, ActorRef, typed}
import net.psforever.actors.session.{AvatarActor, SessionActor} import net.psforever.actors.session.{AvatarActor, SessionActor}
import net.psforever.actors.session.support.{GeneralFunctions, GeneralOperations, SessionData} import net.psforever.actors.session.support.{GeneralFunctions, GeneralOperations, SessionData}
import net.psforever.login.WorldSession.{CallBackForTask, ContainableMoveItem, DropEquipmentFromInventory, PickUpEquipmentFromGround, RemoveOldEquipmentFromInventory} import net.psforever.login.WorldSession.{CallBackForTask, ContainableMoveItem, DropEquipmentFromInventory, PickUpEquipmentFromGround, RemoveOldEquipmentFromInventory}
@ -17,6 +17,7 @@ import net.psforever.objects.guid.{GUIDTask, TaskBundle, TaskWorkflow}
import net.psforever.objects.inventory.Container import net.psforever.objects.inventory.Container
import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject, ServerObject} import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject, ServerObject}
import net.psforever.objects.serverobject.affinity.FactionAffinity import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.serverobject.containable.Containable
import net.psforever.objects.serverobject.doors.Door import net.psforever.objects.serverobject.doors.Door
import net.psforever.objects.serverobject.generator.Generator import net.psforever.objects.serverobject.generator.Generator
import net.psforever.objects.serverobject.llu.CaptureFlag import net.psforever.objects.serverobject.llu.CaptureFlag
@ -39,7 +40,7 @@ import net.psforever.objects.vital.interaction.DamageInteraction
import net.psforever.objects.zones.{Zone, ZoneProjectile, Zoning} import net.psforever.objects.zones.{Zone, ZoneProjectile, Zoning}
import net.psforever.packet.PlanetSideGamePacket import net.psforever.packet.PlanetSideGamePacket
import net.psforever.packet.game.objectcreate.ObjectClass import net.psforever.packet.game.objectcreate.ObjectClass
import net.psforever.packet.game.{ActionCancelMessage, ActionResultMessage, AvatarFirstTimeEventMessage, AvatarImplantMessage, AvatarJumpMessage, BattleplanMessage, BindPlayerMessage, BindStatus, BugReportMessage, ChangeFireModeMessage, ChangeShortcutBankMessage, CharacterCreateRequestMessage, CharacterRequestAction, CharacterRequestMessage, ChatMsg, CollisionIs, ConnectToWorldRequestMessage, CreateShortcutMessage, DeadState, DeployObjectMessage, DisplayedAwardMessage, DropItemMessage, EmoteMsg, FacilityBenefitShieldChargeRequestMessage, FriendsRequest, GenericAction, GenericActionMessage, GenericCollisionMsg, GenericObjectActionAtPositionMessage, GenericObjectActionMessage, GenericObjectStateMsg, HitHint, ImplantAction, InvalidTerrainMessage, ItemTransactionMessage, LootItemMessage, MoveItemMessage, ObjectDeleteMessage, ObjectDetectedMessage, ObjectHeldMessage, PickupItemMessage, PlanetsideAttributeMessage, PlayerStateMessageUpstream, PlayerStateShiftMessage, RequestDestroyMessage, ShiftState, TargetInfo, TargetingImplantRequest, TargetingInfoMessage, TerrainCondition, TradeMessage, UnuseItemMessage, UseItemMessage, VoiceHostInfo, VoiceHostKill, VoiceHostRequest, ZipLineMessage} import net.psforever.packet.game.{ActionCancelMessage, ActionResultMessage, AvatarFirstTimeEventMessage, AvatarImplantMessage, AvatarJumpMessage, BattleplanMessage, BindPlayerMessage, BindStatus, BugReportMessage, ChangeFireModeMessage, ChangeShortcutBankMessage, CharacterCreateRequestMessage, CharacterRequestAction, CharacterRequestMessage, ChatMsg, CollisionIs, ConnectToWorldRequestMessage, CreateShortcutMessage, DeadState, DeployObjectMessage, DisplayedAwardMessage, DropItemMessage, EmoteMsg, FacilityBenefitShieldChargeRequestMessage, FriendsRequest, GenericAction, GenericActionMessage, GenericCollisionMsg, GenericObjectActionAtPositionMessage, GenericObjectActionMessage, GenericObjectStateMsg, HitHint, ImplantAction, InvalidTerrainMessage, ItemTransactionMessage, LootItemMessage, MoveItemMessage, ObjectDeleteMessage, ObjectDetectedMessage, ObjectHeldMessage, PickupItemMessage, PlanetsideAttributeMessage, PlayerStateMessageUpstream, PlayerStateShiftMessage, RequestDestroyMessage, ShiftState, Shortcut, TargetInfo, TargetingImplantRequest, TargetingInfoMessage, TerrainCondition, TradeMessage, UnuseItemMessage, UseItemMessage, VoiceHostInfo, VoiceHostRequest, ZipLineMessage}
import net.psforever.services.RemoverActor import net.psforever.services.RemoverActor
import net.psforever.services.account.{AccountPersistenceService, RetrieveAccountData} import net.psforever.services.account.{AccountPersistenceService, RetrieveAccountData}
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage} import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
@ -116,9 +117,10 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
} }
} }
ops.fallHeightTracker(pos.z) ops.fallHeightTracker(pos.z)
// if (isCrouching && !player.Crouching) { if (isCrouching && !player.Crouching) {
// //dev stuff goes here //dev stuff goes here
// } sendResponse(CreateShortcutMessage(player.GUID, 2, Some(Shortcut.Implant("second_wind"))))
}
player.Position = pos player.Position = pos
player.Velocity = vel player.Velocity = vel
player.Orientation = Vector3(player.Orientation.x, pitch, yaw) player.Orientation = Vector3(player.Orientation.x, pitch, yaw)
@ -161,8 +163,7 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
case None => () case None => ()
} }
val eagleEye: Boolean = ops.canSeeReallyFar val eagleEye: Boolean = ops.canSeeReallyFar
val isNotVisible: Boolean = player.spectator || val isNotVisible: Boolean = sessionLogic.zoning.zoningStatus == Zoning.Status.Deconstructing ||
sessionLogic.zoning.zoningStatus == Zoning.Status.Deconstructing ||
(player.isAlive && sessionLogic.zoning.spawn.deadState == DeadState.RespawnTime) (player.isAlive && sessionLogic.zoning.spawn.deadState == DeadState.RespawnTime)
continent.AvatarEvents ! AvatarServiceMessage( continent.AvatarEvents ! AvatarServiceMessage(
continent.id, continent.id,
@ -190,19 +191,11 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
} }
def handleVoiceHostRequest(pkt: VoiceHostRequest): Unit = { def handleVoiceHostRequest(pkt: VoiceHostRequest): Unit = {
log.debug(s"$pkt") ops.noVoicedChat(pkt)
sendResponse(VoiceHostKill())
sendResponse(
ChatMsg(ChatMessageType.CMT_OPEN, wideContents=false, "", "Try our Discord at https://discord.gg/0nRe5TNbTYoUruA4", None)
)
} }
def handleVoiceHostInfo(pkt: VoiceHostInfo): Unit = { def handleVoiceHostInfo(pkt: VoiceHostInfo): Unit = {
log.debug(s"$pkt") ops.noVoicedChat(pkt)
sendResponse(VoiceHostKill())
sendResponse(
ChatMsg(ChatMessageType.CMT_OPEN, wideContents=false, "", "Try our Discord at https://discord.gg/0nRe5TNbTYoUruA4", None)
)
} }
def handleEmote(pkt: EmoteMsg): Unit = { def handleEmote(pkt: EmoteMsg): Unit = {
@ -269,7 +262,7 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
//travel along the zipline in the direction specified //travel along the zipline in the direction specified
sendResponse(ZipLineMessage(playerGuid, forwards, action, pathId, pos)) sendResponse(ZipLineMessage(playerGuid, forwards, action, pathId, pos))
case 1 => case 1 =>
//disembark from zipline at destination! //disembark from zipline at destination
sendResponse(ZipLineMessage(playerGuid, forwards, action, 0, pos)) sendResponse(ZipLineMessage(playerGuid, forwards, action, 0, pos))
case 2 => case 2 =>
//get off by force //get off by force
@ -572,11 +565,10 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
//aphelion_laser discharge (no target) //aphelion_laser discharge (no target)
sessionLogic.shooting.HandleWeaponFireAccountability(objectGuid, PlanetSideGUID(Projectile.baseUID)) sessionLogic.shooting.HandleWeaponFireAccountability(objectGuid, PlanetSideGUID(Projectile.baseUID))
} else { } else {
sessionLogic.validObject(player.VehicleSeated, decorator = "GenericObjectAction/Vehicle") match { sessionLogic.validObject(player.VehicleSeated, decorator = "GenericObjectAction/Vehicle") collect {
case Some(vehicle: Vehicle) case vehicle: Vehicle
if vehicle.OwnerName.contains(player.Name) => if vehicle.OwnerName.contains(player.Name) =>
vehicle.Actor ! ServerObject.GenericObjectAction(objectGuid, code, Some(tool)) vehicle.Actor ! ServerObject.GenericObjectAction(objectGuid, code, Some(tool))
case _ =>
} }
} }
case _ => case _ =>
@ -724,7 +716,7 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
val (target1, target2, bailProtectStatus, velocity) = (ctype, sessionLogic.validObject(p, decorator = "GenericCollision/Primary")) match { val (target1, target2, bailProtectStatus, velocity) = (ctype, sessionLogic.validObject(p, decorator = "GenericCollision/Primary")) match {
case (CollisionIs.OfInfantry, out @ Some(user: Player)) case (CollisionIs.OfInfantry, out @ Some(user: Player))
if user == player => if user == player =>
val bailStatus = session.flying || player.spectator || session.speed > 1f || player.BailProtection val bailStatus = session.flying || session.speed > 1f || player.BailProtection
player.BailProtection = false player.BailProtection = false
val v = if (player.avatar.implants.exists { val v = if (player.avatar.implants.exists {
case Some(implant) => implant.definition.implantType == ImplantType.Surge && implant.active case Some(implant) => implant.definition.implantType == ImplantType.Surge && implant.active
@ -946,6 +938,20 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
/* messages */ /* messages */
def handleRenewCharSavedTimer(): Unit = {
ops.renewCharSavedTimer(
Config.app.game.savedMsg.interruptedByAction.fixed,
Config.app.game.savedMsg.interruptedByAction.variable
)
}
def handleRenewCharSavedTimerMsg(): Unit = {
ops.displayCharSavedMsgThenRenewTimer(
Config.app.game.savedMsg.interruptedByAction.fixed,
Config.app.game.savedMsg.interruptedByAction.variable
)
}
def handleSetAvatar(avatar: Avatar): Unit = { def handleSetAvatar(avatar: Avatar): Unit = {
session = session.copy(avatar = avatar) session = session.copy(avatar = avatar)
if (session.player != null) { if (session.player != null) {
@ -991,6 +997,18 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
player.silenced = isSilenced player.silenced = isSilenced
} }
def handleItemPutInSlot(msg: Containable.ItemPutInSlot): Unit = {
log.debug(s"ItemPutInSlot: $msg")
}
def handleCanNotPutItemInSlot(msg: Containable.CanNotPutItemInSlot): Unit = {
log.debug(s"CanNotPutItemInSlot: $msg")
}
def handleReceiveDefaultMessage(default: Any, sender: ActorRef): Unit = {
log.warn(s"Invalid packet class received: $default from $sender")
}
/* supporting functions */ /* supporting functions */
private def handleUseDoor(door: Door, equipment: Option[Equipment]): Unit = { private def handleUseDoor(door: Door, equipment: Option[Equipment]): Unit = {

View file

@ -20,6 +20,16 @@ object LocalHandlerLogic {
class LocalHandlerLogic(val ops: SessionLocalHandlers, implicit val context: ActorContext) extends LocalHandlerFunctions { class LocalHandlerLogic(val ops: SessionLocalHandlers, implicit val context: ActorContext) extends LocalHandlerFunctions {
def sessionLogic: SessionData = ops.sessionLogic def sessionLogic: SessionData = ops.sessionLogic
/* messages */
def handleTurretDeployableIsDismissed(obj: TurretDeployable): Unit = {
ops.handleTurretDeployableIsDismissed(obj)
}
def handleDeployableIsDismissed(obj: Deployable): Unit = {
ops.handleDeployableIsDismissed(obj)
}
/* response handlers */ /* response handlers */
/** /**

View file

@ -81,7 +81,7 @@ class MountHandlerLogic(val ops: SessionMountHandlers, implicit val context: Act
v.SeatPermissionGroup(seat_num).contains(AccessPermissionGroup.Driver) && v.SeatPermissionGroup(seat_num).contains(AccessPermissionGroup.Driver) &&
v.isFlying => v.isFlying =>
v.Actor ! Vehicle.Deconstruct(None) //immediate deconstruction v.Actor ! Vehicle.Deconstruct(None) //immediate deconstruction
case _ => ; case _ => ()
} }
case None => case None =>
@ -450,7 +450,7 @@ class MountHandlerLogic(val ops: SessionMountHandlers, implicit val context: Act
val playerGuid: PlanetSideGUID = tplayer.GUID val playerGuid: PlanetSideGUID = tplayer.GUID
val objGuid: PlanetSideGUID = obj.GUID val objGuid: PlanetSideGUID = obj.GUID
sessionLogic.actionsToCancel() sessionLogic.actionsToCancel()
avatarActor ! AvatarActor.DeactivateActiveImplants() avatarActor ! AvatarActor.DeactivateActiveImplants
avatarActor ! AvatarActor.SuspendStaminaRegeneration(3.seconds) avatarActor ! AvatarActor.SuspendStaminaRegeneration(3.seconds)
sendResponse(ObjectAttachMessage(objGuid, playerGuid, seatNum)) sendResponse(ObjectAttachMessage(objGuid, playerGuid, seatNum))
continent.VehicleEvents ! VehicleServiceMessage( continent.VehicleEvents ! VehicleServiceMessage(

View file

@ -1,36 +1,8 @@
// Copyright (c) 2024 PSForever // Copyright (c) 2024 PSForever
package net.psforever.actors.session.normal package net.psforever.actors.session.normal
import akka.actor.Actor.Receive
import akka.actor.ActorRef
import net.psforever.actors.session.support.{ChatFunctions, GeneralFunctions, LocalHandlerFunctions, MountHandlerFunctions, SquadHandlerFunctions, TerminalHandlerFunctions, VehicleFunctions, VehicleHandlerFunctions, WeaponAndProjectileFunctions} import net.psforever.actors.session.support.{ChatFunctions, GeneralFunctions, LocalHandlerFunctions, MountHandlerFunctions, SquadHandlerFunctions, TerminalHandlerFunctions, VehicleFunctions, VehicleHandlerFunctions, WeaponAndProjectileFunctions}
import net.psforever.objects.Players import net.psforever.actors.session.support.{ModeLogic, PlayerMode, SessionData}
import net.psforever.packet.game.UplinkRequest
import net.psforever.services.chat.ChatService
//
import net.psforever.actors.session.{AvatarActor, SessionActor}
import net.psforever.actors.session.support.{ModeLogic, PlayerMode, SessionData, ZoningOperations}
import net.psforever.objects.TurretDeployable
import net.psforever.objects.guid.{GUIDTask, TaskWorkflow}
import net.psforever.objects.serverobject.CommonMessages
import net.psforever.objects.serverobject.containable.Containable
import net.psforever.objects.serverobject.deploy.Deployment
import net.psforever.objects.serverobject.mount.Mountable
import net.psforever.objects.serverobject.terminals.{ProximityUnit, Terminal}
import net.psforever.objects.zones.Zone
import net.psforever.packet.PlanetSideGamePacket
import net.psforever.packet.game.{AIDamage, ActionCancelMessage, AvatarFirstTimeEventMessage, AvatarGrenadeStateMessage, AvatarImplantMessage, AvatarJumpMessage, BattleplanMessage, BeginZoningMessage, BindPlayerMessage, BugReportMessage, ChangeAmmoMessage, ChangeFireModeMessage, ChangeFireStateMessage_Start, ChangeFireStateMessage_Stop, ChangeShortcutBankMessage, CharacterCreateRequestMessage, CharacterRequestMessage, ChatMsg, ChildObjectStateMessage, ConnectToWorldRequestMessage, CreateShortcutMessage, DeployObjectMessage, DeployRequestMessage, DismountVehicleCargoMsg, DismountVehicleMsg, DisplayedAwardMessage, DropItemMessage, DroppodLaunchRequestMessage, EmoteMsg, FacilityBenefitShieldChargeRequestMessage, FavoritesRequest, FrameVehicleStateMessage, FriendsRequest, GenericActionMessage, GenericCollisionMsg, GenericObjectActionAtPositionMessage, GenericObjectActionMessage, GenericObjectStateMsg, HitHint, HitMessage, InvalidTerrainMessage, ItemTransactionMessage, KeepAliveMessage, LashMessage, LongRangeProjectileInfoMessage, LootItemMessage, MountVehicleCargoMsg, MountVehicleMsg, MoveItemMessage, ObjectDetectedMessage, ObjectHeldMessage, OutfitRequest, PickupItemMessage, PlanetsideAttributeMessage, PlayerStateMessageUpstream, ProjectileStateMessage, ProximityTerminalUseMessage, ReleaseAvatarRequestMessage, ReloadMessage, RequestDestroyMessage, SetChatFilterMessage, SpawnRequestMessage, SplashHitMessage, SquadDefinitionActionMessage, SquadMembershipRequest, SquadWaypointRequest, TargetingImplantRequest, TradeMessage, UnuseItemMessage, UseItemMessage, VehicleStateMessage, VehicleSubStateMessage, VoiceHostInfo, VoiceHostRequest, WarpgateRequest, WeaponDelayFireMessage, WeaponDryFireMessage, WeaponFireMessage, WeaponLazeTargetPositionMessage, ZipLineMessage}
import net.psforever.services.{InterstellarClusterService => ICS}
import net.psforever.services.CavernRotationService
import net.psforever.services.CavernRotationService.SendCavernRotationUpdates
import net.psforever.services.ServiceManager.LookupResult
import net.psforever.services.account.{PlayerToken, ReceiveAccountData}
import net.psforever.services.avatar.AvatarServiceResponse
import net.psforever.services.galaxy.GalaxyServiceResponse
import net.psforever.services.local.LocalServiceResponse
import net.psforever.services.teamwork.SquadServiceResponse
import net.psforever.services.vehicle.VehicleServiceResponse
import net.psforever.util.Config
class NormalModeLogic(data: SessionData) extends ModeLogic { class NormalModeLogic(data: SessionData) extends ModeLogic {
val avatarResponse: AvatarHandlerLogic = AvatarHandlerLogic(data.avatarResponse) val avatarResponse: AvatarHandlerLogic = AvatarHandlerLogic(data.avatarResponse)
@ -44,473 +16,6 @@ class NormalModeLogic(data: SessionData) extends ModeLogic {
val terminals: TerminalHandlerFunctions = TerminalHandlerLogic(data.terminals) val terminals: TerminalHandlerFunctions = TerminalHandlerLogic(data.terminals)
val vehicles: VehicleFunctions = VehicleLogic(data.vehicles) val vehicles: VehicleFunctions = VehicleLogic(data.vehicles)
val vehicleResponse: VehicleHandlerFunctions = VehicleHandlerLogic(data.vehicleResponseOperations) val vehicleResponse: VehicleHandlerFunctions = VehicleHandlerLogic(data.vehicleResponseOperations)
def parse(sender: ActorRef): Receive = {
/* really common messages (very frequently, every life) */
case packet: PlanetSideGamePacket =>
handleGamePkt(packet)
case AvatarServiceResponse(toChannel, guid, reply) =>
avatarResponse.handle(toChannel, guid, reply)
case GalaxyServiceResponse(_, reply) =>
galaxy.handle(reply)
case LocalServiceResponse(toChannel, guid, reply) =>
local.handle(toChannel, guid, reply)
case Mountable.MountMessages(tplayer, reply) =>
mountResponse.handle(tplayer, reply)
case SquadServiceResponse(_, excluded, response) =>
squad.handle(response, excluded)
case Terminal.TerminalMessage(tplayer, msg, order) =>
terminals.handle(tplayer, msg, order)
case VehicleServiceResponse(toChannel, guid, reply) =>
vehicleResponse.handle(toChannel, guid, reply)
case ChatService.MessageResponse(fromSession, message, _) =>
chat.handleIncomingMessage(message, fromSession)
case SessionActor.SendResponse(packet) =>
data.sendResponse(packet)
case SessionActor.CharSaved =>
general.ops.renewCharSavedTimer(
Config.app.game.savedMsg.interruptedByAction.fixed,
Config.app.game.savedMsg.interruptedByAction.variable
)
case SessionActor.CharSavedMsg =>
general.ops.displayCharSavedMsgThenRenewTimer(
Config.app.game.savedMsg.renewal.fixed,
Config.app.game.savedMsg.renewal.variable
)
/* common messages (maybe once every respawn) */
case ICS.SpawnPointResponse(response) =>
data.zoning.handleSpawnPointResponse(response)
case SessionActor.NewPlayerLoaded(tplayer) =>
data.zoning.spawn.handleNewPlayerLoaded(tplayer)
case SessionActor.PlayerLoaded(tplayer) =>
data.zoning.spawn.handlePlayerLoaded(tplayer)
case Zone.Population.PlayerHasLeft(zone, None) =>
data.log.debug(s"PlayerHasLeft: ${data.player.Name} does not have a body on ${zone.id}")
case Zone.Population.PlayerHasLeft(zone, Some(tplayer)) =>
if (tplayer.isAlive) {
data.log.info(s"${tplayer.Name} has left zone ${zone.id}")
}
case Zone.Population.PlayerCanNotSpawn(zone, tplayer) =>
data.log.warn(s"${tplayer.Name} can not spawn in zone ${zone.id}; why?")
case Zone.Population.PlayerAlreadySpawned(zone, tplayer) =>
data.log.warn(s"${tplayer.Name} is already spawned on zone ${zone.id}; is this a clerical error?")
case Zone.Vehicle.CanNotSpawn(zone, vehicle, reason) =>
data.log.warn(
s"${data.player.Name}'s ${vehicle.Definition.Name} can not spawn in ${zone.id} because $reason"
)
case Zone.Vehicle.CanNotDespawn(zone, vehicle, reason) =>
data.log.warn(
s"${data.player.Name}'s ${vehicle.Definition.Name} can not deconstruct in ${zone.id} because $reason"
)
case ICS.ZoneResponse(Some(zone)) =>
data.zoning.handleZoneResponse(zone)
/* uncommon messages (once a session) */
case ICS.ZonesResponse(zones) =>
data.zoning.handleZonesResponse(zones)
case SessionActor.SetAvatar(avatar) =>
general.handleSetAvatar(avatar)
case PlayerToken.LoginInfo(name, Zone.Nowhere, _) =>
data.zoning.spawn.handleLoginInfoNowhere(name, sender)
case PlayerToken.LoginInfo(name, inZone, optionalSavedData) =>
data.zoning.spawn.handleLoginInfoSomewhere(name, inZone, optionalSavedData, sender)
case PlayerToken.RestoreInfo(playerName, inZone, pos) =>
data.zoning.spawn.handleLoginInfoRestore(playerName, inZone, pos, sender)
case PlayerToken.CanNotLogin(playerName, reason) =>
data.zoning.spawn.handleLoginCanNot(playerName, reason)
case ReceiveAccountData(account) =>
general.handleReceiveAccountData(account)
case AvatarActor.AvatarResponse(avatar) =>
general.handleAvatarResponse(avatar)
case AvatarActor.AvatarLoginResponse(avatar) =>
data.zoning.spawn.avatarLoginResponse(avatar)
case SessionActor.SetCurrentAvatar(tplayer, max_attempts, attempt) =>
data.zoning.spawn.ReadyToSetCurrentAvatar(tplayer, max_attempts, attempt)
case SessionActor.SetConnectionState(state) =>
data.connectionState = state
case SessionActor.AvatarLoadingSync(state) =>
data.zoning.spawn.handleAvatarLoadingSync(state)
/* uncommon messages (utility, or once in a while) */
case ZoningOperations.AvatarAwardMessageBundle(pkts, delay) =>
data.zoning.spawn.performAvatarAwardMessageDelivery(pkts, delay)
case CommonMessages.ProgressEvent(delta, finishedAction, stepAction, tick) =>
general.ops.handleProgressChange(delta, finishedAction, stepAction, tick)
case CommonMessages.Progress(rate, finishedAction, stepAction) =>
general.ops.setupProgressChange(rate, finishedAction, stepAction)
case CavernRotationService.CavernRotationServiceKey.Listing(listings) =>
listings.head ! SendCavernRotationUpdates(data.context.self)
case LookupResult("propertyOverrideManager", endpoint) =>
data.zoning.propertyOverrideManagerLoadOverrides(endpoint)
case SessionActor.UpdateIgnoredPlayers(msg) =>
galaxy.handleUpdateIgnoredPlayers(msg)
case SessionActor.UseCooldownRenewed(definition, _) =>
general.handleUseCooldownRenew(definition)
case Deployment.CanDeploy(obj, state) =>
vehicles.handleCanDeploy(obj, state)
case Deployment.CanUndeploy(obj, state) =>
vehicles.handleCanUndeploy(obj, state)
case Deployment.CanNotChangeDeployment(obj, state, reason) =>
vehicles.handleCanNotChangeDeployment(obj, state, reason)
/* rare messages */
case ProximityUnit.StopAction(term, _) =>
terminals.ops.LocalStopUsingProximityUnit(term)
case SessionActor.Suicide() =>
general.ops.suicide(data.player)
case SessionActor.Recall() =>
data.zoning.handleRecall()
case SessionActor.InstantAction() =>
data.zoning.handleInstantAction()
case SessionActor.Quit() =>
data.zoning.handleQuit()
case ICS.DroppodLaunchDenial(errorCode, _) =>
data.zoning.handleDroppodLaunchDenial(errorCode)
case ICS.DroppodLaunchConfirmation(zone, position) =>
data.zoning.LoadZoneLaunchDroppod(zone, position)
case SessionActor.PlayerFailedToLoad(tplayer) =>
data.failWithError(s"${tplayer.Name} failed to load anywhere")
/* csr only */
case SessionActor.SetSpeed(speed) =>
general.handleSetSpeed(speed)
case SessionActor.SetFlying(isFlying) =>
general.handleSetFlying(isFlying)
case SessionActor.SetSpectator(isSpectator) =>
general.handleSetSpectator(isSpectator)
case SessionActor.Kick(player, time) =>
general.handleKick(player, time)
case SessionActor.SetZone(zoneId, position) =>
data.zoning.handleSetZone(zoneId, position)
case SessionActor.SetPosition(position) =>
data.zoning.spawn.handleSetPosition(position)
case SessionActor.SetSilenced(silenced) =>
general.handleSilenced(silenced)
/* catch these messages */
case _: ProximityUnit.Action => ;
case _: Zone.Vehicle.HasSpawned => ;
case _: Zone.Vehicle.HasDespawned => ;
case Zone.Deployable.IsDismissed(obj: TurretDeployable) => //only if target deployable was never fully introduced
Players.buildCooldownReset(data.continent, data.player.Name, obj)
TaskWorkflow.execute(GUIDTask.unregisterDeployableTurret(data.continent.GUID, obj))
case Zone.Deployable.IsDismissed(obj) => //only if target deployable was never fully introduced
Players.buildCooldownReset(data.continent, data.player.Name, obj)
TaskWorkflow.execute(GUIDTask.unregisterObject(data.continent.GUID, obj))
case msg: Containable.ItemPutInSlot =>
data.log.debug(s"ItemPutInSlot: $msg")
case msg: Containable.CanNotPutItemInSlot =>
data.log.debug(s"CanNotPutItemInSlot: $msg")
case default =>
data.log.warn(s"Invalid packet class received: $default from $sender")
}
private def handleGamePkt: PlanetSideGamePacket => Unit = {
case packet: ConnectToWorldRequestMessage =>
general.handleConnectToWorldRequest(packet)
case packet: MountVehicleCargoMsg =>
mountResponse.handleMountVehicleCargo(packet)
case packet: DismountVehicleCargoMsg =>
mountResponse.handleDismountVehicleCargo(packet)
case packet: CharacterCreateRequestMessage =>
general.handleCharacterCreateRequest(packet)
case packet: CharacterRequestMessage =>
general.handleCharacterRequest(packet)
case _: KeepAliveMessage =>
data.keepAliveFunc()
case packet: BeginZoningMessage =>
data.zoning.handleBeginZoning(packet)
case packet: PlayerStateMessageUpstream =>
general.handlePlayerStateUpstream(packet)
case packet: ChildObjectStateMessage =>
vehicles.handleChildObjectState(packet)
case packet: VehicleStateMessage =>
vehicles.handleVehicleState(packet)
case packet: VehicleSubStateMessage =>
vehicles.handleVehicleSubState(packet)
case packet: FrameVehicleStateMessage =>
vehicles.handleFrameVehicleState(packet)
case packet: ProjectileStateMessage =>
shooting.handleProjectileState(packet)
case packet: LongRangeProjectileInfoMessage =>
shooting.handleLongRangeProjectileState(packet)
case packet: ReleaseAvatarRequestMessage =>
data.zoning.spawn.handleReleaseAvatarRequest(packet)
case packet: SpawnRequestMessage =>
data.zoning.spawn.handleSpawnRequest(packet)
case packet: ChatMsg =>
chat.handleChatMsg(packet)
case packet: SetChatFilterMessage =>
chat.handleChatFilter(packet)
case packet: VoiceHostRequest =>
general.handleVoiceHostRequest(packet)
case packet: VoiceHostInfo =>
general.handleVoiceHostInfo(packet)
case packet: ChangeAmmoMessage =>
shooting.handleChangeAmmo(packet)
case packet: ChangeFireModeMessage =>
shooting.handleChangeFireMode(packet)
case packet: ChangeFireStateMessage_Start =>
shooting.handleChangeFireStateStart(packet)
case packet: ChangeFireStateMessage_Stop =>
shooting.handleChangeFireStateStop(packet)
case packet: EmoteMsg =>
general.handleEmote(packet)
case packet: DropItemMessage =>
general.handleDropItem(packet)
case packet: PickupItemMessage =>
general.handlePickupItem(packet)
case packet: ReloadMessage =>
shooting.handleReload(packet)
case packet: ObjectHeldMessage =>
general.handleObjectHeld(packet)
case packet: AvatarJumpMessage =>
general.handleAvatarJump(packet)
case packet: ZipLineMessage =>
general.handleZipLine(packet)
case packet: RequestDestroyMessage =>
general.handleRequestDestroy(packet)
case packet: MoveItemMessage =>
general.handleMoveItem(packet)
case packet: LootItemMessage =>
general.handleLootItem(packet)
case packet: AvatarImplantMessage =>
general.handleAvatarImplant(packet)
case packet: UseItemMessage =>
general.handleUseItem(packet)
case packet: UnuseItemMessage =>
general.handleUnuseItem(packet)
case packet: ProximityTerminalUseMessage =>
terminals.handleProximityTerminalUse(packet)
case packet: DeployObjectMessage =>
general.handleDeployObject(packet)
case packet: GenericObjectActionMessage =>
general.handleGenericObjectAction(packet)
case packet: GenericObjectActionAtPositionMessage =>
general.handleGenericObjectActionAtPosition(packet)
case packet: GenericObjectStateMsg =>
general.handleGenericObjectState(packet)
case packet: GenericActionMessage =>
general.handleGenericAction(packet)
case packet: ItemTransactionMessage =>
terminals.handleItemTransaction(packet)
case packet: FavoritesRequest =>
terminals.handleFavoritesRequest(packet)
case packet: WeaponDelayFireMessage =>
shooting.handleWeaponDelayFire(packet)
case packet: WeaponDryFireMessage =>
shooting.handleWeaponDryFire(packet)
case packet: WeaponFireMessage =>
shooting.handleWeaponFire(packet)
case packet: WeaponLazeTargetPositionMessage =>
shooting.handleWeaponLazeTargetPosition(packet)
case _: UplinkRequest => ()
case packet: HitMessage =>
shooting.handleDirectHit(packet)
case packet: SplashHitMessage =>
shooting.handleSplashHit(packet)
case packet: LashMessage =>
shooting.handleLashHit(packet)
case packet: AIDamage =>
shooting.handleAIDamage(packet)
case packet: AvatarFirstTimeEventMessage =>
general.handleAvatarFirstTimeEvent(packet)
case packet: WarpgateRequest =>
data.zoning.handleWarpgateRequest(packet)
case packet: MountVehicleMsg =>
mountResponse.handleMountVehicle(packet)
case packet: DismountVehicleMsg =>
mountResponse.handleDismountVehicle(packet)
case packet: DeployRequestMessage =>
vehicles.handleDeployRequest(packet)
case packet: AvatarGrenadeStateMessage =>
shooting.handleAvatarGrenadeState(packet)
case packet: SquadDefinitionActionMessage =>
squad.handleSquadDefinitionAction(packet)
case packet: SquadMembershipRequest =>
squad.handleSquadMemberRequest(packet)
case packet: SquadWaypointRequest =>
squad.handleSquadWaypointRequest(packet)
case packet: GenericCollisionMsg =>
general.handleGenericCollision(packet)
case packet: BugReportMessage =>
general.handleBugReport(packet)
case packet: BindPlayerMessage =>
general.handleBindPlayer(packet)
case packet: PlanetsideAttributeMessage =>
general.handlePlanetsideAttribute(packet)
case packet: FacilityBenefitShieldChargeRequestMessage =>
general.handleFacilityBenefitShieldChargeRequest(packet)
case packet: BattleplanMessage =>
general.handleBattleplan(packet)
case packet: CreateShortcutMessage =>
general.handleCreateShortcut(packet)
case packet: ChangeShortcutBankMessage =>
general.handleChangeShortcutBank(packet)
case packet: FriendsRequest =>
general.handleFriendRequest(packet)
case packet: DroppodLaunchRequestMessage =>
data.zoning.handleDroppodLaunchRequest(packet)
case packet: InvalidTerrainMessage =>
general.handleInvalidTerrain(packet)
case packet: ActionCancelMessage =>
general.handleActionCancel(packet)
case packet: TradeMessage =>
general.handleTrade(packet)
case packet: DisplayedAwardMessage =>
general.handleDisplayedAward(packet)
case packet: ObjectDetectedMessage =>
general.handleObjectDetected(packet)
case packet: TargetingImplantRequest =>
general.handleTargetingImplantRequest(packet)
case packet: HitHint =>
general.handleHitHint(packet)
case _: OutfitRequest => ()
case pkt =>
data.log.warn(s"Unhandled GamePacket $pkt")
}
} }
case object NormalMode extends PlayerMode { case object NormalMode extends PlayerMode {

View file

@ -112,7 +112,7 @@ class TerminalHandlerLogic(val ops: SessionTerminalHandlers, implicit val contex
ops.lastTerminalOrderFulfillment = true ops.lastTerminalOrderFulfillment = true
case Terminal.BuyVehicle(vehicle, _, _) case Terminal.BuyVehicle(vehicle, _, _)
if tplayer.avatar.purchaseCooldown(vehicle.Definition).nonEmpty || tplayer.spectator => if tplayer.avatar.purchaseCooldown(vehicle.Definition).nonEmpty =>
sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, success = false)) sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, success = false))
ops.lastTerminalOrderFulfillment = true ops.lastTerminalOrderFulfillment = true

View file

@ -5,7 +5,7 @@ import akka.actor.{ActorContext, typed}
import net.psforever.actors.session.AvatarActor import net.psforever.actors.session.AvatarActor
import net.psforever.actors.session.support.{SessionData, VehicleFunctions, VehicleOperations} import net.psforever.actors.session.support.{SessionData, VehicleFunctions, VehicleOperations}
import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.{PlanetSideGameObject, Player, Vehicle, Vehicles} import net.psforever.objects.{Vehicle, Vehicles}
import net.psforever.objects.serverobject.deploy.Deployment import net.psforever.objects.serverobject.deploy.Deployment
import net.psforever.objects.serverobject.mount.Mountable import net.psforever.objects.serverobject.mount.Mountable
import net.psforever.objects.vehicles.control.BfrFlight import net.psforever.objects.vehicles.control.BfrFlight
@ -41,7 +41,7 @@ class VehicleLogic(val ops: VehicleOperations, implicit val context: ActorContex
is_decelerating, is_decelerating,
is_cloaked is_cloaked
) = pkt ) = pkt
GetVehicleAndSeat() match { ops.GetVehicleAndSeat() match {
case (Some(obj), Some(0)) => case (Some(obj), Some(0)) =>
//we're driving the vehicle //we're driving the vehicle
sessionLogic.persist() sessionLogic.persist()
@ -100,7 +100,7 @@ class VehicleLogic(val ops: VehicleOperations, implicit val context: ActorContex
log.error( log.error(
s"VehicleState: ${player.Name} should not be dispatching this kind of packet from vehicle ${vehicle_guid.guid} when not the driver (actually, seat $index)" s"VehicleState: ${player.Name} should not be dispatching this kind of packet from vehicle ${vehicle_guid.guid} when not the driver (actually, seat $index)"
) )
case _ => ; case _ => ()
} }
if (player.death_by == -1) { if (player.death_by == -1) {
sessionLogic.kickedByAdministration() sessionLogic.kickedByAdministration()
@ -124,7 +124,7 @@ class VehicleLogic(val ops: VehicleOperations, implicit val context: ActorContex
unk9, unk9,
unkA unkA
) = pkt ) = pkt
GetVehicleAndSeat() match { ops.GetVehicleAndSeat() match {
case (Some(obj), Some(0)) => case (Some(obj), Some(0)) =>
//we're driving the vehicle //we're driving the vehicle
sessionLogic.persist() sessionLogic.persist()
@ -198,7 +198,7 @@ class VehicleLogic(val ops: VehicleOperations, implicit val context: ActorContex
log.error( log.error(
s"VehicleState: ${player.Name} should not be dispatching this kind of packet from vehicle ${vehicle_guid.guid} when not the driver (actually, seat $index)" s"VehicleState: ${player.Name} should not be dispatching this kind of packet from vehicle ${vehicle_guid.guid} when not the driver (actually, seat $index)"
) )
case _ => ; case _ => ()
} }
if (player.death_by == -1) { if (player.death_by == -1) {
sessionLogic.kickedByAdministration() sessionLogic.kickedByAdministration()
@ -213,7 +213,7 @@ class VehicleLogic(val ops: VehicleOperations, implicit val context: ActorContex
case Some(mount: Mountable) => (o, mount.PassengerInSeat(player)) case Some(mount: Mountable) => (o, mount.PassengerInSeat(player))
case _ => (None, None) case _ => (None, None)
}) match { }) match {
case (None, None) | (_, None) | (Some(_: Vehicle), Some(0)) => ; case (None, None) | (_, None) | (Some(_: Vehicle), Some(0)) => ()
case _ => case _ =>
sessionLogic.persist() sessionLogic.persist()
sessionLogic.turnCounterFunc(player.GUID) sessionLogic.turnCounterFunc(player.GUID)
@ -241,8 +241,9 @@ class VehicleLogic(val ops: VehicleOperations, implicit val context: ActorContex
def handleVehicleSubState(pkt: VehicleSubStateMessage): Unit = { def handleVehicleSubState(pkt: VehicleSubStateMessage): Unit = {
val VehicleSubStateMessage(vehicle_guid, _, pos, ang, vel, unk1, _) = pkt val VehicleSubStateMessage(vehicle_guid, _, pos, ang, vel, unk1, _) = pkt
sessionLogic.validObject(vehicle_guid, decorator = "VehicleSubState") match { sessionLogic.validObject(vehicle_guid, decorator = "VehicleSubState")
case Some(obj: Vehicle) => .collect {
case obj: Vehicle =>
import net.psforever.login.WorldSession.boolToInt import net.psforever.login.WorldSession.boolToInt
obj.Position = pos obj.Position = pos
obj.Orientation = ang obj.Orientation = ang
@ -266,7 +267,6 @@ class VehicleLogic(val ops: VehicleOperations, implicit val context: ActorContex
obj.Cloaked obj.Cloaked
) )
) )
case _ => ()
} }
} }
@ -282,7 +282,6 @@ class VehicleLogic(val ops: VehicleOperations, implicit val context: ActorContex
log.warn(s"${player.Name} must be mounted as the driver to request a deployment change") log.warn(s"${player.Name} must be mounted as the driver to request a deployment change")
} else { } else {
log.info(s"${player.Name} is requesting a deployment change for ${obj.Definition.Name} - $deploy_state") log.info(s"${player.Name} is requesting a deployment change for ${obj.Definition.Name} - $deploy_state")
obj.Actor ! Deployment.TryDeploymentChange(deploy_state)
continent.Transport ! Zone.Vehicle.TryDeploymentChange(obj, deploy_state) continent.Transport ! Zone.Vehicle.TryDeploymentChange(obj, deploy_state)
} }
obj obj
@ -329,46 +328,6 @@ class VehicleLogic(val ops: VehicleOperations, implicit val context: ActorContex
/* support functions */ /* support functions */
/**
* If the player is mounted in some entity, find that entity and get the mount index number at which the player is sat.
* The priority of object confirmation is `direct` then `occupant.VehicleSeated`.
* Once an object is found, the remainder are ignored.
* @param direct a game object in which the player may be sat
* @param occupant the player who is sat and may have specified the game object in which mounted
* @return a tuple consisting of a vehicle reference and a mount index
* if and only if the vehicle is known to this client and the `WorldSessioNActor`-global `player` occupies it;
* `(None, None)`, otherwise (even if the vehicle can be determined)
*/
private def GetMountableAndSeat(
direct: Option[PlanetSideGameObject with Mountable],
occupant: Player,
zone: Zone
): (Option[PlanetSideGameObject with Mountable], Option[Int]) =
direct.orElse(zone.GUID(occupant.VehicleSeated)) match {
case Some(obj: PlanetSideGameObject with Mountable) =>
obj.PassengerInSeat(occupant) match {
case index @ Some(_) =>
(Some(obj), index)
case None =>
(None, None)
}
case _ =>
(None, None)
}
/**
* If the player is seated in a vehicle, find that vehicle and get the mount index number at which the player is sat.
* @see `GetMountableAndSeat`
* @return a tuple consisting of a vehicle reference and a mount index
* if and only if the vehicle is known to this client and the `WorldSessioNActor`-global `player` occupies it;
* `(None, None)`, otherwise (even if the vehicle can be determined)
*/
private def GetVehicleAndSeat(): (Option[Vehicle], Option[Int]) =
GetMountableAndSeat(None, player, continent) match {
case (Some(v: Vehicle), Some(seat)) => (Some(v), Some(seat))
case _ => (None, None)
}
/** /**
* Common reporting behavior when a `Deployment` object fails to properly transition between states. * Common reporting behavior when a `Deployment` object fails to properly transition between states.
* @param obj the game object that could not * @param obj the game object that could not

View file

@ -88,6 +88,7 @@ object WeaponAndProjectileLogic {
Seq(start + endStart * (sqrt - b), start + endStart * (b + sqrt) * -1f) Seq(start + endStart * (sqrt - b), start + endStart * (b + sqrt) * -1f)
}.filter(p => Vector3.DistanceSquared(start, p) <= a) }.filter(p => Vector3.DistanceSquared(start, p) <= a)
} }
/** /**
* Preparation for explosion damage that utilizes the Scorpion's little buddy sub-projectiles. * Preparation for explosion damage that utilizes the Scorpion's little buddy sub-projectiles.
* The main difference from "normal" server-side explosion * The main difference from "normal" server-side explosion
@ -497,10 +498,8 @@ class WeaponAndProjectileLogic(val ops: WeaponAndProjectileOperations, implicit
} }
if (profile.ExistsOnRemoteClients && projectile.HasGUID) { if (profile.ExistsOnRemoteClients && projectile.HasGUID) {
//cleanup //cleanup
if (projectile.HasGUID) {
continent.Projectile ! ZoneProjectile.Remove(projectile.GUID) continent.Projectile ! ZoneProjectile.Remove(projectile.GUID)
} }
}
case None => () case None => ()
} }
} }

View file

@ -65,7 +65,7 @@ class GalaxyHandlerLogic(val ops: SessionGalaxyHandlers, implicit val context: A
case GalaxyResponse.LockedZoneUpdate(zone, time) => case GalaxyResponse.LockedZoneUpdate(zone, time) =>
sendResponse(ZoneInfoMessage(zone.Number, empire_status=false, lock_time=time)) sendResponse(ZoneInfoMessage(zone.Number, empire_status=false, lock_time=time))
case GalaxyResponse.UnlockedZoneUpdate(zone) => ; case GalaxyResponse.UnlockedZoneUpdate(zone) =>
sendResponse(ZoneInfoMessage(zone.Number, empire_status=true, lock_time=0L)) sendResponse(ZoneInfoMessage(zone.Number, empire_status=true, lock_time=0L))
val popBO = 0 val popBO = 0
val popTR = zone.Players.count(_.faction == PlanetSideEmpire.TR) val popTR = zone.Players.count(_.faction == PlanetSideEmpire.TR)

View file

@ -1,7 +1,7 @@
// Copyright (c) 2024 PSForever // Copyright (c) 2024 PSForever
package net.psforever.actors.session.spectator package net.psforever.actors.session.spectator
import akka.actor.{ActorContext, typed} import akka.actor.{ActorContext, ActorRef, typed}
import net.psforever.actors.session.AvatarActor import net.psforever.actors.session.AvatarActor
import net.psforever.actors.session.support.{GeneralFunctions, GeneralOperations, SessionData} import net.psforever.actors.session.support.{GeneralFunctions, GeneralOperations, SessionData}
import net.psforever.objects.{Account, GlobalDefinitions, LivePlayerList, PlanetSideGameObject, Player, TelepadDeployable, Tool, Vehicle} import net.psforever.objects.{Account, GlobalDefinitions, LivePlayerList, PlanetSideGameObject, Player, TelepadDeployable, Tool, Vehicle}
@ -11,15 +11,16 @@ import net.psforever.objects.ce.{Deployable, TelepadLike}
import net.psforever.objects.definition.{BasicDefinition, KitDefinition, SpecialExoSuitDefinition} import net.psforever.objects.definition.{BasicDefinition, KitDefinition, SpecialExoSuitDefinition}
import net.psforever.objects.equipment.Equipment import net.psforever.objects.equipment.Equipment
import net.psforever.objects.serverobject.CommonMessages import net.psforever.objects.serverobject.CommonMessages
import net.psforever.objects.serverobject.containable.Containable
import net.psforever.objects.serverobject.doors.Door import net.psforever.objects.serverobject.doors.Door
import net.psforever.objects.vehicles.{Utility, UtilityType} import net.psforever.objects.vehicles.{Utility, UtilityType}
import net.psforever.objects.vehicles.Utility.InternalTelepad import net.psforever.objects.vehicles.Utility.InternalTelepad
import net.psforever.objects.zones.ZoneProjectile import net.psforever.objects.zones.ZoneProjectile
import net.psforever.packet.PlanetSideGamePacket import net.psforever.packet.PlanetSideGamePacket
import net.psforever.packet.game.{ActionCancelMessage, AvatarFirstTimeEventMessage, AvatarImplantMessage, AvatarJumpMessage, BattleplanMessage, BindPlayerMessage, BugReportMessage, ChangeFireModeMessage, ChangeShortcutBankMessage, CharacterCreateRequestMessage, CharacterRequestMessage, ChatMsg, ConnectToWorldRequestMessage, CreateShortcutMessage, DeployObjectMessage, DisplayedAwardMessage, DropItemMessage, EmoteMsg, FacilityBenefitShieldChargeRequestMessage, FriendsRequest, GenericAction, GenericActionMessage, GenericCollisionMsg, GenericObjectActionAtPositionMessage, GenericObjectActionMessage, GenericObjectStateMsg, HitHint, ImplantAction, InvalidTerrainMessage, LootItemMessage, MoveItemMessage, ObjectDetectedMessage, ObjectHeldMessage, PickupItemMessage, PlanetsideAttributeMessage, PlayerStateMessageUpstream, PlayerStateShiftMessage, RequestDestroyMessage, ShiftState, TargetInfo, TargetingImplantRequest, TargetingInfoMessage, TradeMessage, UnuseItemMessage, UseItemMessage, VoiceHostInfo, VoiceHostKill, VoiceHostRequest, ZipLineMessage} import net.psforever.packet.game.{ActionCancelMessage, AvatarFirstTimeEventMessage, AvatarImplantMessage, AvatarJumpMessage, BattleplanMessage, BindPlayerMessage, BugReportMessage, ChangeFireModeMessage, ChangeShortcutBankMessage, CharacterCreateRequestMessage, CharacterRequestMessage, ConnectToWorldRequestMessage, CreateShortcutMessage, DeployObjectMessage, DisplayedAwardMessage, DropItemMessage, EmoteMsg, FacilityBenefitShieldChargeRequestMessage, FriendsRequest, GenericAction, GenericActionMessage, GenericCollisionMsg, GenericObjectActionAtPositionMessage, GenericObjectActionMessage, GenericObjectStateMsg, HitHint, ImplantAction, InvalidTerrainMessage, LootItemMessage, MoveItemMessage, ObjectDetectedMessage, ObjectHeldMessage, PickupItemMessage, PlanetsideAttributeMessage, PlayerStateMessageUpstream, PlayerStateShiftMessage, RequestDestroyMessage, ShiftState, TargetInfo, TargetingImplantRequest, TargetingInfoMessage, TradeMessage, UnuseItemMessage, UseItemMessage, VoiceHostInfo, VoiceHostRequest, ZipLineMessage}
import net.psforever.services.account.AccountPersistenceService import net.psforever.services.account.AccountPersistenceService
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage} import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
import net.psforever.types.{ChatMessageType, DriveState, ExoSuitType, PlanetSideGUID, Vector3} import net.psforever.types.{DriveState, ExoSuitType, PlanetSideGUID, Vector3}
import net.psforever.util.Config import net.psforever.util.Config
object GeneralLogic { object GeneralLogic {
@ -79,19 +80,11 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
} }
def handleVoiceHostRequest(pkt: VoiceHostRequest): Unit = { def handleVoiceHostRequest(pkt: VoiceHostRequest): Unit = {
log.debug(s"$pkt") ops.noVoicedChat(pkt)
sendResponse(VoiceHostKill())
sendResponse(
ChatMsg(ChatMessageType.CMT_OPEN, wideContents=false, "", "Try our Discord at https://discord.gg/0nRe5TNbTYoUruA4", None)
)
} }
def handleVoiceHostInfo(pkt: VoiceHostInfo): Unit = { def handleVoiceHostInfo(pkt: VoiceHostInfo): Unit = {
log.debug(s"$pkt") ops.noVoicedChat(pkt)
sendResponse(VoiceHostKill())
sendResponse(
ChatMsg(ChatMessageType.CMT_OPEN, wideContents=false, "", "Try our Discord at https://discord.gg/0nRe5TNbTYoUruA4", None)
)
} }
def handleEmote(pkt: EmoteMsg): Unit = { def handleEmote(pkt: EmoteMsg): Unit = {
@ -410,6 +403,10 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
/* messages */ /* messages */
def handleRenewCharSavedTimer(): Unit = { /* intentionally blank */ }
def handleRenewCharSavedTimerMsg(): Unit = { /* intentionally blank */ }
def handleSetAvatar(avatar: Avatar): Unit = { def handleSetAvatar(avatar: Avatar): Unit = {
session = session.copy(avatar = avatar) session = session.copy(avatar = avatar)
if (session.player != null) { if (session.player != null) {
@ -455,6 +452,12 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
player.silenced = isSilenced player.silenced = isSilenced
} }
def handleItemPutInSlot(msg: Containable.ItemPutInSlot): Unit = { /* intentionally blank */ }
def handleCanNotPutItemInSlot(msg: Containable.CanNotPutItemInSlot): Unit = { /* intentionally blank */ }
def handleReceiveDefaultMessage(default: Any, sender: ActorRef): Unit = { /* intentionally blank */ }
/* supporting functions */ /* supporting functions */
private def handleUseDoor(door: Door, equipment: Option[Equipment]): Unit = { private def handleUseDoor(door: Door, equipment: Option[Equipment]): Unit = {

View file

@ -4,6 +4,7 @@ package net.psforever.actors.session.spectator
import akka.actor.ActorContext import akka.actor.ActorContext
import net.psforever.actors.session.support.{LocalHandlerFunctions, SessionData, SessionLocalHandlers} import net.psforever.actors.session.support.{LocalHandlerFunctions, SessionData, SessionLocalHandlers}
import net.psforever.objects.ce.Deployable import net.psforever.objects.ce.Deployable
import net.psforever.objects.guid.{GUIDTask, TaskWorkflow}
import net.psforever.objects.vehicles.MountableWeapons import net.psforever.objects.vehicles.MountableWeapons
import net.psforever.objects.{BoomerDeployable, ExplosiveDeployable, TelepadDeployable, Tool, TurretDeployable} import net.psforever.objects.{BoomerDeployable, ExplosiveDeployable, TelepadDeployable, Tool, TurretDeployable}
import net.psforever.packet.game.{ChatMsg, DeployableObjectsInfoMessage, GenericActionMessage, GenericObjectActionMessage, GenericObjectStateMsg, HackMessage, HackState, InventoryStateMessage, ObjectAttachMessage, ObjectCreateMessage, ObjectDeleteMessage, ObjectDetachMessage, OrbitalShuttleTimeMsg, PadAndShuttlePair, PlanetsideAttributeMessage, ProximityTerminalUseMessage, SetEmpireMessage, TriggerEffectMessage, TriggerSoundMessage, TriggeredSound, VehicleStateMessage} import net.psforever.packet.game.{ChatMsg, DeployableObjectsInfoMessage, GenericActionMessage, GenericObjectActionMessage, GenericObjectStateMsg, HackMessage, HackState, InventoryStateMessage, ObjectAttachMessage, ObjectCreateMessage, ObjectDeleteMessage, ObjectDetachMessage, OrbitalShuttleTimeMsg, PadAndShuttlePair, PlanetsideAttributeMessage, ProximityTerminalUseMessage, SetEmpireMessage, TriggerEffectMessage, TriggerSoundMessage, TriggeredSound, VehicleStateMessage}
@ -20,6 +21,16 @@ object LocalHandlerLogic {
class LocalHandlerLogic(val ops: SessionLocalHandlers, implicit val context: ActorContext) extends LocalHandlerFunctions { class LocalHandlerLogic(val ops: SessionLocalHandlers, implicit val context: ActorContext) extends LocalHandlerFunctions {
def sessionLogic: SessionData = ops.sessionLogic def sessionLogic: SessionData = ops.sessionLogic
/* messages */
def handleTurretDeployableIsDismissed(obj: TurretDeployable): Unit = {
TaskWorkflow.execute(GUIDTask.unregisterDeployableTurret(continent.GUID, obj))
}
def handleDeployableIsDismissed(obj: Deployable): Unit = {
TaskWorkflow.execute(GUIDTask.unregisterObject(continent.GUID, obj))
}
/* response handlers */ /* response handlers */
/** /**

View file

@ -66,7 +66,7 @@ class MountHandlerLogic(val ops: SessionMountHandlers, implicit val context: Act
v.SeatPermissionGroup(seat_num).contains(AccessPermissionGroup.Driver) && v.SeatPermissionGroup(seat_num).contains(AccessPermissionGroup.Driver) &&
v.isFlying => v.isFlying =>
v.Actor ! Vehicle.Deconstruct(None) //immediate deconstruction v.Actor ! Vehicle.Deconstruct(None) //immediate deconstruction
case _ => ; case _ => ()
} }
case None => case None =>

View file

@ -1,8 +1,6 @@
// Copyright (c) 2024 PSForever // Copyright (c) 2024 PSForever
package net.psforever.actors.session.spectator package net.psforever.actors.session.spectator
import akka.actor.Actor.Receive
import akka.actor.ActorRef
import net.psforever.actors.session.support.{AvatarHandlerFunctions, ChatFunctions, GalaxyHandlerFunctions, GeneralFunctions, LocalHandlerFunctions, MountHandlerFunctions, SquadHandlerFunctions, TerminalHandlerFunctions, VehicleFunctions, VehicleHandlerFunctions, WeaponAndProjectileFunctions} import net.psforever.actors.session.support.{AvatarHandlerFunctions, ChatFunctions, GalaxyHandlerFunctions, GeneralFunctions, LocalHandlerFunctions, MountHandlerFunctions, SquadHandlerFunctions, TerminalHandlerFunctions, VehicleFunctions, VehicleHandlerFunctions, WeaponAndProjectileFunctions}
import net.psforever.actors.zone.ZoneActor import net.psforever.actors.zone.ZoneActor
import net.psforever.objects.avatar.{BattleRank, CommandRank, DeployableToolbox, FirstTimeEvents, Implant, ProgressDecoration, Shortcut => AvatarShortcut} import net.psforever.objects.avatar.{BattleRank, CommandRank, DeployableToolbox, FirstTimeEvents, Implant, ProgressDecoration, Shortcut => AvatarShortcut}
@ -14,32 +12,14 @@ import net.psforever.packet.game.{DeployableInfo, DeployableObjectsInfoMessage,
import net.psforever.packet.game.objectcreate.{ObjectClass, ObjectCreateMessageParent, RibbonBars} import net.psforever.packet.game.objectcreate.{ObjectClass, ObjectCreateMessageParent, RibbonBars}
import net.psforever.services.Service import net.psforever.services.Service
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage} import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
import net.psforever.services.chat.{ChatService, SpectatorChannel} import net.psforever.services.chat.SpectatorChannel
import net.psforever.services.teamwork.{SquadAction, SquadServiceMessage} import net.psforever.services.teamwork.{SquadAction, SquadServiceMessage}
import net.psforever.types.{CapacitorStateType, ChatMessageType, ExoSuitType, MeritCommendation, SquadRequestType} import net.psforever.types.{CapacitorStateType, ChatMessageType, ExoSuitType, MeritCommendation, SquadRequestType}
// //
import net.psforever.actors.session.{AvatarActor, SessionActor} import net.psforever.actors.session.AvatarActor
import net.psforever.actors.session.support.{ModeLogic, PlayerMode, SessionData, ZoningOperations} import net.psforever.actors.session.support.{ModeLogic, PlayerMode, SessionData}
import net.psforever.objects.TurretDeployable
import net.psforever.objects.guid.{GUIDTask, TaskWorkflow}
import net.psforever.objects.serverobject.CommonMessages
import net.psforever.objects.serverobject.containable.Containable
import net.psforever.objects.serverobject.deploy.Deployment
import net.psforever.objects.serverobject.mount.Mountable
import net.psforever.objects.serverobject.terminals.{ProximityUnit, Terminal} import net.psforever.objects.serverobject.terminals.{ProximityUnit, Terminal}
import net.psforever.objects.zones.Zone import net.psforever.packet.game.{ChatMsg, CreateShortcutMessage, UnuseItemMessage}
import net.psforever.packet.PlanetSideGamePacket
import net.psforever.packet.game.{AIDamage, ActionCancelMessage, AvatarFirstTimeEventMessage, AvatarGrenadeStateMessage, AvatarImplantMessage, AvatarJumpMessage, BattleplanMessage, BeginZoningMessage, BindPlayerMessage, BugReportMessage, ChangeAmmoMessage, ChangeFireModeMessage, ChangeFireStateMessage_Start, ChangeFireStateMessage_Stop, ChangeShortcutBankMessage, CharacterCreateRequestMessage, CharacterRequestMessage, ChatMsg, ChildObjectStateMessage, ConnectToWorldRequestMessage, CreateShortcutMessage, DeployObjectMessage, DeployRequestMessage, DismountVehicleCargoMsg, DismountVehicleMsg, DisplayedAwardMessage, DropItemMessage, DroppodLaunchRequestMessage, EmoteMsg, FacilityBenefitShieldChargeRequestMessage, FavoritesRequest, FrameVehicleStateMessage, FriendsRequest, GenericActionMessage, GenericCollisionMsg, GenericObjectActionAtPositionMessage, GenericObjectActionMessage, GenericObjectStateMsg, HitHint, HitMessage, InvalidTerrainMessage, ItemTransactionMessage, KeepAliveMessage, LashMessage, LongRangeProjectileInfoMessage, LootItemMessage, MountVehicleCargoMsg, MountVehicleMsg, MoveItemMessage, ObjectDetectedMessage, ObjectHeldMessage, OutfitRequest, PickupItemMessage, PlanetsideAttributeMessage, PlayerStateMessageUpstream, ProjectileStateMessage, ProximityTerminalUseMessage, ReleaseAvatarRequestMessage, ReloadMessage, RequestDestroyMessage, SetChatFilterMessage, SpawnRequestMessage, SplashHitMessage, SquadDefinitionActionMessage, SquadMembershipRequest, SquadWaypointRequest, TargetingImplantRequest, TradeMessage, UnuseItemMessage, UplinkRequest, UseItemMessage, VehicleStateMessage, VehicleSubStateMessage, VoiceHostInfo, VoiceHostRequest, WarpgateRequest, WeaponDelayFireMessage, WeaponDryFireMessage, WeaponFireMessage, WeaponLazeTargetPositionMessage, ZipLineMessage}
import net.psforever.services.{InterstellarClusterService => ICS}
import net.psforever.services.CavernRotationService
import net.psforever.services.CavernRotationService.SendCavernRotationUpdates
import net.psforever.services.ServiceManager.LookupResult
import net.psforever.services.account.{PlayerToken, ReceiveAccountData}
import net.psforever.services.avatar.AvatarServiceResponse
import net.psforever.services.galaxy.GalaxyServiceResponse
import net.psforever.services.local.LocalServiceResponse
import net.psforever.services.teamwork.SquadServiceResponse
import net.psforever.services.vehicle.VehicleServiceResponse
class SpectatorModeLogic(data: SessionData) extends ModeLogic { class SpectatorModeLogic(data: SessionData) extends ModeLogic {
val avatarResponse: AvatarHandlerFunctions = AvatarHandlerLogic(data.avatarResponse) val avatarResponse: AvatarHandlerFunctions = AvatarHandlerLogic(data.avatarResponse)
@ -186,463 +166,6 @@ class SpectatorModeLogic(data: SessionData) extends ModeLogic {
zoning.zoneReload = true zoning.zoneReload = true
zoning.spawn.randomRespawn(0.seconds) //to sanctuary zoning.spawn.randomRespawn(0.seconds) //to sanctuary
} }
def parse(sender: ActorRef): Receive = {
/* really common messages (very frequently, every life) */
case packet: PlanetSideGamePacket =>
handleGamePkt(packet)
case AvatarServiceResponse(toChannel, guid, reply) =>
avatarResponse.handle(toChannel, guid, reply)
case GalaxyServiceResponse(_, reply) =>
galaxy.handle(reply)
case LocalServiceResponse(toChannel, guid, reply) =>
local.handle(toChannel, guid, reply)
case Mountable.MountMessages(tplayer, reply) =>
mountResponse.handle(tplayer, reply)
case SquadServiceResponse(_, excluded, response) =>
squad.handle(response, excluded)
case Terminal.TerminalMessage(tplayer, msg, order) =>
terminals.handle(tplayer, msg, order)
case VehicleServiceResponse(toChannel, guid, reply) =>
vehicleResponse.handle(toChannel, guid, reply)
case ChatService.MessageResponse(fromSession, message, _) =>
chat.handleIncomingMessage(message, fromSession)
case SessionActor.SendResponse(packet) =>
data.sendResponse(packet)
case SessionActor.CharSaved => ()
case SessionActor.CharSavedMsg => ()
/* common messages (maybe once every respawn) */
case ICS.SpawnPointResponse(response) =>
data.zoning.handleSpawnPointResponse(response)
case SessionActor.NewPlayerLoaded(tplayer) =>
data.zoning.spawn.handleNewPlayerLoaded(tplayer)
case SessionActor.PlayerLoaded(tplayer) =>
data.zoning.spawn.handlePlayerLoaded(tplayer)
case Zone.Population.PlayerHasLeft(zone, None) =>
data.log.debug(s"PlayerHasLeft: ${data.player.Name} does not have a body on ${zone.id}")
case Zone.Population.PlayerHasLeft(zone, Some(tplayer)) =>
if (tplayer.isAlive) {
data.log.info(s"${tplayer.Name} has left zone ${zone.id}")
}
case Zone.Population.PlayerCanNotSpawn(zone, tplayer) =>
data.log.warn(s"${tplayer.Name} can not spawn in zone ${zone.id}; why?")
case Zone.Population.PlayerAlreadySpawned(zone, tplayer) =>
data.log.warn(s"${tplayer.Name} is already spawned on zone ${zone.id}; is this a clerical error?")
case Zone.Vehicle.CanNotSpawn(zone, vehicle, reason) =>
data.log.warn(
s"${data.player.Name}'s ${vehicle.Definition.Name} can not spawn in ${zone.id} because $reason"
)
case Zone.Vehicle.CanNotDespawn(zone, vehicle, reason) =>
data.log.warn(
s"${data.player.Name}'s ${vehicle.Definition.Name} can not deconstruct in ${zone.id} because $reason"
)
case ICS.ZoneResponse(Some(zone)) =>
data.zoning.handleZoneResponse(zone)
/* uncommon messages (once a session) */
case ICS.ZonesResponse(zones) =>
data.zoning.handleZonesResponse(zones)
case SessionActor.SetAvatar(avatar) =>
general.handleSetAvatar(avatar)
case PlayerToken.LoginInfo(name, Zone.Nowhere, _) =>
data.zoning.spawn.handleLoginInfoNowhere(name, sender)
case PlayerToken.LoginInfo(name, inZone, optionalSavedData) =>
data.zoning.spawn.handleLoginInfoSomewhere(name, inZone, optionalSavedData, sender)
case PlayerToken.RestoreInfo(playerName, inZone, pos) =>
data.zoning.spawn.handleLoginInfoRestore(playerName, inZone, pos, sender)
case PlayerToken.CanNotLogin(playerName, reason) =>
data.zoning.spawn.handleLoginCanNot(playerName, reason)
case ReceiveAccountData(account) =>
general.handleReceiveAccountData(account)
case AvatarActor.AvatarResponse(avatar) =>
general.handleAvatarResponse(avatar)
case AvatarActor.AvatarLoginResponse(avatar) =>
data.zoning.spawn.avatarLoginResponse(avatar)
case SessionActor.SetCurrentAvatar(tplayer, max_attempts, attempt) =>
data.zoning.spawn.ReadyToSetCurrentAvatar(tplayer, max_attempts, attempt)
case SessionActor.SetConnectionState(state) =>
data.connectionState = state
case SessionActor.AvatarLoadingSync(state) =>
data.zoning.spawn.handleAvatarLoadingSync(state)
/* uncommon messages (utility, or once in a while) */
case ZoningOperations.AvatarAwardMessageBundle(pkts, delay) =>
data.zoning.spawn.performAvatarAwardMessageDelivery(pkts, delay)
case CommonMessages.ProgressEvent(delta, finishedAction, stepAction, tick) =>
general.ops.handleProgressChange(delta, finishedAction, stepAction, tick)
case CommonMessages.Progress(rate, finishedAction, stepAction) =>
general.ops.setupProgressChange(rate, finishedAction, stepAction)
case CavernRotationService.CavernRotationServiceKey.Listing(listings) =>
listings.head ! SendCavernRotationUpdates(data.context.self)
case LookupResult("propertyOverrideManager", endpoint) =>
data.zoning.propertyOverrideManagerLoadOverrides(endpoint)
case SessionActor.UpdateIgnoredPlayers(msg) =>
galaxy.handleUpdateIgnoredPlayers(msg)
case SessionActor.UseCooldownRenewed(definition, _) =>
general.handleUseCooldownRenew(definition)
case Deployment.CanDeploy(obj, state) =>
vehicles.handleCanDeploy(obj, state)
case Deployment.CanUndeploy(obj, state) =>
vehicles.handleCanUndeploy(obj, state)
case Deployment.CanNotChangeDeployment(obj, state, reason) =>
vehicles.handleCanNotChangeDeployment(obj, state, reason)
/* rare messages */
case ProximityUnit.StopAction(term, _) =>
terminals.ops.LocalStopUsingProximityUnit(term)
case SessionActor.Suicide() =>
general.ops.suicide(data.player)
case SessionActor.Recall() =>
data.zoning.handleRecall()
case SessionActor.InstantAction() =>
data.zoning.handleInstantAction()
case SessionActor.Quit() =>
data.zoning.handleQuit()
case ICS.DroppodLaunchDenial(errorCode, _) =>
data.zoning.handleDroppodLaunchDenial(errorCode)
case ICS.DroppodLaunchConfirmation(zone, position) =>
data.zoning.LoadZoneLaunchDroppod(zone, position)
case SessionActor.PlayerFailedToLoad(tplayer) =>
data.failWithError(s"${tplayer.Name} failed to load anywhere")
/* csr only */
case SessionActor.SetSpeed(speed) =>
general.handleSetSpeed(speed)
case SessionActor.SetFlying(isFlying) =>
general.handleSetFlying(isFlying)
case SessionActor.SetSpectator(isSpectator) =>
general.handleSetSpectator(isSpectator)
case SessionActor.Kick(player, time) =>
general.handleKick(player, time)
case SessionActor.SetZone(zoneId, position) =>
data.zoning.handleSetZone(zoneId, position)
case SessionActor.SetPosition(position) =>
data.zoning.spawn.handleSetPosition(position)
case SessionActor.SetSilenced(silenced) =>
general.handleSilenced(silenced)
/* catch these messages */
case _: ProximityUnit.Action => ;
case _: Zone.Vehicle.HasSpawned => ;
case _: Zone.Vehicle.HasDespawned => ;
case Zone.Deployable.IsDismissed(obj: TurretDeployable) => //only if target deployable was never fully introduced
TaskWorkflow.execute(GUIDTask.unregisterDeployableTurret(data.continent.GUID, obj))
case Zone.Deployable.IsDismissed(obj) => //only if target deployable was never fully introduced
TaskWorkflow.execute(GUIDTask.unregisterObject(data.continent.GUID, obj))
case msg: Containable.ItemPutInSlot =>
data.log.debug(s"ItemPutInSlot: $msg")
case msg: Containable.CanNotPutItemInSlot =>
data.log.debug(s"CanNotPutItemInSlot: $msg")
case _ => ()
}
private def handleGamePkt: PlanetSideGamePacket => Unit = {
case packet: ConnectToWorldRequestMessage =>
general.handleConnectToWorldRequest(packet)
case packet: MountVehicleCargoMsg =>
mountResponse.handleMountVehicleCargo(packet)
case packet: DismountVehicleCargoMsg =>
mountResponse.handleDismountVehicleCargo(packet)
case packet: CharacterCreateRequestMessage =>
general.handleCharacterCreateRequest(packet)
case packet: CharacterRequestMessage =>
general.handleCharacterRequest(packet)
case _: KeepAliveMessage =>
data.keepAliveFunc()
case packet: BeginZoningMessage =>
data.zoning.handleBeginZoning(packet)
case packet: PlayerStateMessageUpstream =>
general.handlePlayerStateUpstream(packet)
case packet: ChildObjectStateMessage =>
vehicles.handleChildObjectState(packet)
case packet: VehicleStateMessage =>
vehicles.handleVehicleState(packet)
case packet: VehicleSubStateMessage =>
vehicles.handleVehicleSubState(packet)
case packet: FrameVehicleStateMessage =>
vehicles.handleFrameVehicleState(packet)
case packet: ProjectileStateMessage =>
shooting.handleProjectileState(packet)
case packet: LongRangeProjectileInfoMessage =>
shooting.handleLongRangeProjectileState(packet)
case packet: ReleaseAvatarRequestMessage =>
data.zoning.spawn.handleReleaseAvatarRequest(packet)
case packet: SpawnRequestMessage =>
data.zoning.spawn.handleSpawnRequest(packet)
case packet: ChatMsg =>
chat.handleChatMsg(packet)
case packet: SetChatFilterMessage =>
chat.handleChatFilter(packet)
case packet: VoiceHostRequest =>
general.handleVoiceHostRequest(packet)
case packet: VoiceHostInfo =>
general.handleVoiceHostInfo(packet)
case packet: ChangeAmmoMessage =>
shooting.handleChangeAmmo(packet)
case packet: ChangeFireModeMessage =>
shooting.handleChangeFireMode(packet)
case packet: ChangeFireStateMessage_Start =>
shooting.handleChangeFireStateStart(packet)
case packet: ChangeFireStateMessage_Stop =>
shooting.handleChangeFireStateStop(packet)
case packet: EmoteMsg =>
general.handleEmote(packet)
case packet: DropItemMessage =>
general.handleDropItem(packet)
case packet: PickupItemMessage =>
general.handlePickupItem(packet)
case packet: ReloadMessage =>
shooting.handleReload(packet)
case packet: ObjectHeldMessage =>
general.handleObjectHeld(packet)
case packet: AvatarJumpMessage =>
general.handleAvatarJump(packet)
case packet: ZipLineMessage =>
general.handleZipLine(packet)
case packet: RequestDestroyMessage =>
general.handleRequestDestroy(packet)
case packet: MoveItemMessage =>
general.handleMoveItem(packet)
case packet: LootItemMessage =>
general.handleLootItem(packet)
case packet: AvatarImplantMessage =>
general.handleAvatarImplant(packet)
case packet: UseItemMessage =>
general.handleUseItem(packet)
case packet: UnuseItemMessage =>
general.handleUnuseItem(packet)
case packet: ProximityTerminalUseMessage =>
terminals.handleProximityTerminalUse(packet)
case packet: DeployObjectMessage =>
general.handleDeployObject(packet)
case packet: GenericObjectActionMessage =>
general.handleGenericObjectAction(packet)
case packet: GenericObjectActionAtPositionMessage =>
general.handleGenericObjectActionAtPosition(packet)
case packet: GenericObjectStateMsg =>
general.handleGenericObjectState(packet)
case packet: GenericActionMessage =>
general.handleGenericAction(packet)
case packet: ItemTransactionMessage =>
terminals.handleItemTransaction(packet)
case packet: FavoritesRequest =>
terminals.handleFavoritesRequest(packet)
case packet: WeaponDelayFireMessage =>
shooting.handleWeaponDelayFire(packet)
case packet: WeaponDryFireMessage =>
shooting.handleWeaponDryFire(packet)
case packet: WeaponFireMessage =>
shooting.handleWeaponFire(packet)
case packet: WeaponLazeTargetPositionMessage =>
shooting.handleWeaponLazeTargetPosition(packet)
case packet: UplinkRequest =>
shooting.handleUplinkRequest(packet)
case packet: HitMessage =>
shooting.handleDirectHit(packet)
case packet: SplashHitMessage =>
shooting.handleSplashHit(packet)
case packet: LashMessage =>
shooting.handleLashHit(packet)
case packet: AIDamage =>
shooting.handleAIDamage(packet)
case packet: AvatarFirstTimeEventMessage =>
general.handleAvatarFirstTimeEvent(packet)
case packet: WarpgateRequest =>
data.zoning.handleWarpgateRequest(packet)
case packet: MountVehicleMsg =>
mountResponse.handleMountVehicle(packet)
case packet: DismountVehicleMsg =>
mountResponse.handleDismountVehicle(packet)
case packet: DeployRequestMessage =>
vehicles.handleDeployRequest(packet)
case packet: AvatarGrenadeStateMessage =>
shooting.handleAvatarGrenadeState(packet)
case packet: SquadDefinitionActionMessage =>
squad.handleSquadDefinitionAction(packet)
case packet: SquadMembershipRequest =>
squad.handleSquadMemberRequest(packet)
case packet: SquadWaypointRequest =>
squad.handleSquadWaypointRequest(packet)
case packet: GenericCollisionMsg =>
general.handleGenericCollision(packet)
case packet: BugReportMessage =>
general.handleBugReport(packet)
case packet: BindPlayerMessage =>
general.handleBindPlayer(packet)
case packet: PlanetsideAttributeMessage =>
general.handlePlanetsideAttribute(packet)
case packet: FacilityBenefitShieldChargeRequestMessage =>
general.handleFacilityBenefitShieldChargeRequest(packet)
case packet: BattleplanMessage =>
general.handleBattleplan(packet)
case packet: CreateShortcutMessage =>
general.handleCreateShortcut(packet)
case packet: ChangeShortcutBankMessage =>
general.handleChangeShortcutBank(packet)
case packet: FriendsRequest =>
general.handleFriendRequest(packet)
case packet: DroppodLaunchRequestMessage =>
data.zoning.handleDroppodLaunchRequest(packet)
case packet: InvalidTerrainMessage =>
general.handleInvalidTerrain(packet)
case packet: ActionCancelMessage =>
general.handleActionCancel(packet)
case packet: TradeMessage =>
general.handleTrade(packet)
case packet: DisplayedAwardMessage =>
general.handleDisplayedAward(packet)
case packet: ObjectDetectedMessage =>
general.handleObjectDetected(packet)
case packet: TargetingImplantRequest =>
general.handleTargetingImplantRequest(packet)
case packet: HitHint =>
general.handleHitHint(packet)
case _: OutfitRequest => ()
case pkt =>
data.log.warn(s"Unhandled GamePacket $pkt")
}
} }
object SpectatorModeLogic { object SpectatorModeLogic {

View file

@ -5,11 +5,10 @@ import akka.actor.{ActorContext, typed}
import net.psforever.actors.session.AvatarActor import net.psforever.actors.session.AvatarActor
import net.psforever.actors.session.support.{SessionData, VehicleFunctions, VehicleOperations} import net.psforever.actors.session.support.{SessionData, VehicleFunctions, VehicleOperations}
import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.{PlanetSideGameObject, Player, Vehicle, Vehicles} import net.psforever.objects.{Vehicle, Vehicles}
import net.psforever.objects.serverobject.deploy.Deployment import net.psforever.objects.serverobject.deploy.Deployment
import net.psforever.objects.serverobject.mount.Mountable import net.psforever.objects.serverobject.mount.Mountable
import net.psforever.objects.vehicles.control.BfrFlight import net.psforever.objects.vehicles.control.BfrFlight
import net.psforever.objects.zones.Zone
import net.psforever.packet.game.{ChildObjectStateMessage, DeployRequestMessage, FrameVehicleStateMessage, VehicleStateMessage, VehicleSubStateMessage} import net.psforever.packet.game.{ChildObjectStateMessage, DeployRequestMessage, FrameVehicleStateMessage, VehicleStateMessage, VehicleSubStateMessage}
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage} import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
import net.psforever.types.{DriveState, Vector3} import net.psforever.types.{DriveState, Vector3}
@ -41,7 +40,7 @@ class VehicleLogic(val ops: VehicleOperations, implicit val context: ActorContex
is_decelerating, is_decelerating,
is_cloaked is_cloaked
) = pkt ) = pkt
GetVehicleAndSeat() match { ops.GetVehicleAndSeat() match {
case (Some(obj), Some(0)) => case (Some(obj), Some(0)) =>
//we're driving the vehicle //we're driving the vehicle
sessionLogic.persist() sessionLogic.persist()
@ -100,7 +99,7 @@ class VehicleLogic(val ops: VehicleOperations, implicit val context: ActorContex
log.error( log.error(
s"VehicleState: ${player.Name} should not be dispatching this kind of packet from vehicle ${vehicle_guid.guid} when not the driver (actually, seat $index)" s"VehicleState: ${player.Name} should not be dispatching this kind of packet from vehicle ${vehicle_guid.guid} when not the driver (actually, seat $index)"
) )
case _ => ; case _ => ()
} }
if (player.death_by == -1) { if (player.death_by == -1) {
sessionLogic.kickedByAdministration() sessionLogic.kickedByAdministration()
@ -124,7 +123,7 @@ class VehicleLogic(val ops: VehicleOperations, implicit val context: ActorContex
unk9, unk9,
unkA unkA
) = pkt ) = pkt
GetVehicleAndSeat() match { ops.GetVehicleAndSeat() match {
case (Some(obj), Some(0)) => case (Some(obj), Some(0)) =>
//we're driving the vehicle //we're driving the vehicle
sessionLogic.persist() sessionLogic.persist()
@ -296,46 +295,6 @@ class VehicleLogic(val ops: VehicleOperations, implicit val context: ActorContex
/* support functions */ /* support functions */
/**
* If the player is mounted in some entity, find that entity and get the mount index number at which the player is sat.
* The priority of object confirmation is `direct` then `occupant.VehicleSeated`.
* Once an object is found, the remainder are ignored.
* @param direct a game object in which the player may be sat
* @param occupant the player who is sat and may have specified the game object in which mounted
* @return a tuple consisting of a vehicle reference and a mount index
* if and only if the vehicle is known to this client and the `WorldSessioNActor`-global `player` occupies it;
* `(None, None)`, otherwise (even if the vehicle can be determined)
*/
private def GetMountableAndSeat(
direct: Option[PlanetSideGameObject with Mountable],
occupant: Player,
zone: Zone
): (Option[PlanetSideGameObject with Mountable], Option[Int]) =
direct.orElse(zone.GUID(occupant.VehicleSeated)) match {
case Some(obj: PlanetSideGameObject with Mountable) =>
obj.PassengerInSeat(occupant) match {
case index @ Some(_) =>
(Some(obj), index)
case None =>
(None, None)
}
case _ =>
(None, None)
}
/**
* If the player is seated in a vehicle, find that vehicle and get the mount index number at which the player is sat.
* @see `GetMountableAndSeat`
* @return a tuple consisting of a vehicle reference and a mount index
* if and only if the vehicle is known to this client and the `WorldSessioNActor`-global `player` occupies it;
* `(None, None)`, otherwise (even if the vehicle can be determined)
*/
private def GetVehicleAndSeat(): (Option[Vehicle], Option[Int]) =
GetMountableAndSeat(None, player, continent) match {
case (Some(v: Vehicle), Some(seat)) => (Some(v), Some(seat))
case _ => (None, None)
}
/** /**
* Common reporting behavior when a `Deployment` object fails to properly transition between states. * Common reporting behavior when a `Deployment` object fails to properly transition between states.
* @param obj the game object that could not * @param obj the game object that could not

View file

@ -2,6 +2,7 @@
package net.psforever.actors.session.support package net.psforever.actors.session.support
import akka.actor.{ActorContext, ActorRef, Cancellable, typed} import akka.actor.{ActorContext, ActorRef, Cancellable, typed}
import net.psforever.objects.serverobject.containable.Containable
import net.psforever.objects.sourcing.PlayerSource import net.psforever.objects.sourcing.PlayerSource
import scala.collection.mutable import scala.collection.mutable
@ -122,6 +123,10 @@ trait GeneralFunctions extends CommonSessionInterfacingFunctionality {
/* messages */ /* messages */
def handleRenewCharSavedTimer(): Unit
def handleRenewCharSavedTimerMsg(): Unit
def handleSetAvatar(avatar: Avatar): Unit def handleSetAvatar(avatar: Avatar): Unit
def handleReceiveAccountData(account: Account): Unit def handleReceiveAccountData(account: Account): Unit
@ -139,6 +144,12 @@ trait GeneralFunctions extends CommonSessionInterfacingFunctionality {
def handleKick(player: Player, time: Option[Long]): Unit def handleKick(player: Player, time: Option[Long]): Unit
def handleSilenced(isSilenced: Boolean): Unit def handleSilenced(isSilenced: Boolean): Unit
def handleItemPutInSlot(msg: Containable.ItemPutInSlot): Unit
def handleCanNotPutItemInSlot(msg: Containable.CanNotPutItemInSlot): Unit
def handleReceiveDefaultMessage(default: Any, sender: ActorRef): Unit
} }
class GeneralOperations( class GeneralOperations(
@ -741,6 +752,14 @@ class GeneralOperations(
sendResponse(ChatMsg(ChatMessageType.UNK_227, wideContents=false, "", "@charsaved", None)) sendResponse(ChatMsg(ChatMessageType.UNK_227, wideContents=false, "", "@charsaved", None))
} }
def noVoicedChat(pkt: PlanetSideGamePacket): Unit = {
log.debug(s"$pkt")
sendResponse(VoiceHostKill())
sendResponse(
ChatMsg(ChatMessageType.CMT_OPEN, wideContents=false, "", "Try our Discord at https://discord.gg/0nRe5TNbTYoUruA4", None)
)
}
override protected[session] def actionsToCancel(): Unit = { override protected[session] def actionsToCancel(): Unit = {
progressBarValue = None progressBarValue = None
kitToBeUsed = None kitToBeUsed = None

View file

@ -1,8 +1,6 @@
// Copyright (c) 2024 PSForever // Copyright (c) 2024 PSForever
package net.psforever.actors.session.support package net.psforever.actors.session.support
import akka.actor.Actor.Receive
import akka.actor.ActorRef
import net.psforever.objects.Session import net.psforever.objects.Session
trait ModeLogic { trait ModeLogic {
@ -21,8 +19,6 @@ trait ModeLogic {
def switchTo(session: Session): Unit = { /* to override */ } def switchTo(session: Session): Unit = { /* to override */ }
def switchFrom(session: Session): Unit = { /* to override */ } def switchFrom(session: Session): Unit = { /* to override */ }
def parse(sender: ActorRef): Receive
} }
trait PlayerMode { trait PlayerMode {

View file

@ -2,16 +2,35 @@
package net.psforever.actors.session.support package net.psforever.actors.session.support
import akka.actor.ActorContext import akka.actor.ActorContext
import net.psforever.objects.{Players, TurretDeployable}
import net.psforever.objects.ce.Deployable
import net.psforever.objects.guid.{GUIDTask, TaskWorkflow}
import net.psforever.services.local.LocalResponse import net.psforever.services.local.LocalResponse
import net.psforever.types.PlanetSideGUID import net.psforever.types.PlanetSideGUID
trait LocalHandlerFunctions extends CommonSessionInterfacingFunctionality { trait LocalHandlerFunctions extends CommonSessionInterfacingFunctionality {
def ops: SessionLocalHandlers def ops: SessionLocalHandlers
def handleTurretDeployableIsDismissed(obj: TurretDeployable): Unit
def handleDeployableIsDismissed(obj: Deployable): Unit
def handle(toChannel: String, guid: PlanetSideGUID, reply: LocalResponse.Response): Unit def handle(toChannel: String, guid: PlanetSideGUID, reply: LocalResponse.Response): Unit
} }
class SessionLocalHandlers( class SessionLocalHandlers(
val sessionLogic: SessionData, val sessionLogic: SessionData,
implicit val context: ActorContext implicit val context: ActorContext
) extends CommonSessionInterfacingFunctionality ) extends CommonSessionInterfacingFunctionality {
def handleTurretDeployableIsDismissed(obj: TurretDeployable): Unit = {
Players.buildCooldownReset(continent, player.Name, obj)
TaskWorkflow.execute(GUIDTask.unregisterDeployableTurret(continent.GUID, obj))
}
def handleDeployableIsDismissed(obj: Deployable): Unit = {
Players.buildCooldownReset(continent, player.Name, obj)
TaskWorkflow.execute(GUIDTask.unregisterObject(continent.GUID, obj))
}
}

View file

@ -74,7 +74,7 @@ class SessionSquadHandlers(
sendResponse( sendResponse(
SquadDefinitionActionMessage(PlanetSideGUID(0), index, SquadAction.ListSquadFavorite(loadout.task)) SquadDefinitionActionMessage(PlanetSideGUID(0), index, SquadAction.ListSquadFavorite(loadout.task))
) )
case (None, _) => ; case (None, _) => ()
} }
//non-squad GUID-0 counts as the settings when not joined with a squad //non-squad GUID-0 counts as the settings when not joined with a squad
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SquadAction.IdentifyAsSquadLeader())) sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SquadAction.IdentifyAsSquadLeader()))

View file

@ -68,6 +68,26 @@ class SessionTerminalHandlers(
) )
} }
/**
* Construct tasking that adds a completed and registered vehicle into the scene.
* Use this function to renew the globally unique identifiers on a vehicle that has already been added to the scene once.
* @param vehicle the `Vehicle` object
* @see `RegisterVehicleFromSpawnPad`
* @return a `TaskBundle` message
*/
def registerVehicle(vehicle: Vehicle): TaskBundle = {
TaskBundle(
new StraightforwardTask() {
private val localVehicle = vehicle
override def description(): String = s"register a ${localVehicle.Definition.Name}"
def action(): Future[Any] = Future(true)
},
List(GUIDTask.registerVehicle(continent.GUID, vehicle))
)
}
/** /**
* na * na
* @param terminal na * @param terminal na
@ -188,26 +208,6 @@ class SessionTerminalHandlers(
} }
} }
/**
* Construct tasking that adds a completed and registered vehicle into the scene.
* Use this function to renew the globally unique identifiers on a vehicle that has already been added to the scene once.
* @param vehicle the `Vehicle` object
* @see `RegisterVehicleFromSpawnPad`
* @return a `TaskBundle` message
*/
def registerVehicle(vehicle: Vehicle): TaskBundle = {
TaskBundle(
new StraightforwardTask() {
private val localVehicle = vehicle
override def description(): String = s"register a ${localVehicle.Definition.Name}"
def action(): Future[Any] = Future(true)
},
List(GUIDTask.registerVehicle(continent.GUID, vehicle))
)
}
override protected[session] def actionsToCancel(): Unit = { override protected[session] def actionsToCancel(): Unit = {
lastTerminalOrderFulfillment = true lastTerminalOrderFulfillment = true
usingMedicalTerminal = None usingMedicalTerminal = None

View file

@ -66,6 +66,19 @@ class VehicleOperations(
(None, None) (None, None)
} }
/**
* If the player is seated in a vehicle, find that vehicle and get the mount index number at which the player is sat.
* @see `GetMountableAndSeat`
* @return a tuple consisting of a vehicle reference and a mount index
* if and only if the vehicle is known to this client and the `WorldSessioNActor`-global `player` occupies it;
* `(None, None)`, otherwise (even if the vehicle can be determined)
*/
def GetVehicleAndSeat(): (Option[Vehicle], Option[Int]) =
GetMountableAndSeat(None, player, continent) match {
case (Some(v: Vehicle), Some(seat)) => (Some(v), Some(seat))
case _ => (None, None)
}
/** /**
* If the player is seated in a vehicle, find that vehicle and get the mount index number at which the player is sat.<br> * If the player is seated in a vehicle, find that vehicle and get the mount index number at which the player is sat.<br>
* <br> * <br>
@ -86,19 +99,6 @@ class VehicleOperations(
case _ => (None, None) case _ => (None, None)
} }
/**
* If the player is seated in a vehicle, find that vehicle and get the mount index number at which the player is sat.
* @see `GetMountableAndSeat`
* @return a tuple consisting of a vehicle reference and a mount index
* if and only if the vehicle is known to this client and the `WorldSessioNActor`-global `player` occupies it;
* `(None, None)`, otherwise (even if the vehicle can be determined)
*/
def GetVehicleAndSeat(): (Option[Vehicle], Option[Int]) =
GetMountableAndSeat(None, player, continent) match {
case (Some(v: Vehicle), Some(seat)) => (Some(v), Some(seat))
case _ => (None, None)
}
/** /**
* Place the current vehicle under the control of the driver's commands, * Place the current vehicle under the control of the driver's commands,
* but leave it in a cancellable auto-drive. * but leave it in a cancellable auto-drive.

View file

@ -288,7 +288,7 @@ class ZoningOperations(
obj.Definition.DeployCategory == DeployableCategory.Sensors && obj.Definition.DeployCategory == DeployableCategory.Sensors &&
!obj.Destroyed && !obj.Destroyed &&
(obj match { (obj match {
case jObj: JammableUnit => !jObj.Jammed; case jObj: JammableUnit => !jObj.Jammed
case _ => true case _ => true
}) })
) )
@ -449,7 +449,7 @@ class ZoningOperations(
if (vehicle.Shields > 0) { if (vehicle.Shields > 0) {
sendResponse(PlanetsideAttributeMessage(vguid, vehicle.Definition.shieldUiAttribute, vehicle.Shields)) sendResponse(PlanetsideAttributeMessage(vguid, vehicle.Definition.shieldUiAttribute, vehicle.Shields))
} }
case _ => ; //no vehicle case _ => () //no vehicle
} }
//vehicle wreckages //vehicle wreckages
wreckages.foreach(vehicle => { wreckages.foreach(vehicle => {
@ -487,7 +487,7 @@ class ZoningOperations(
sendResponse(PlanetsideAttributeMessage(silo.GUID, 49, 1)) // silo orb particle effect sendResponse(PlanetsideAttributeMessage(silo.GUID, 49, 1)) // silo orb particle effect
case Some(_: WarpGate) => case Some(_: WarpGate) =>
sendResponse(PlanetsideAttributeMessage(obj.GUID, 49, 1)) // ant orb particle effect sendResponse(PlanetsideAttributeMessage(obj.GUID, 49, 1)) // ant orb particle effect
case _ => ; case _ => ()
} }
} }
deployedVehicles.filter(_.Definition == GlobalDefinitions.router).foreach { obj => deployedVehicles.filter(_.Definition == GlobalDefinitions.router).foreach { obj =>
@ -518,7 +518,7 @@ class ZoningOperations(
objDef.Packet.ConstructorData(obj).get objDef.Packet.ConstructorData(obj).get
) )
) )
case _ => ; case _ => ()
} }
//mount terminal occupants //mount terminal occupants
continent.GUID(terminal_guid) match { continent.GUID(terminal_guid) match {
@ -534,9 +534,9 @@ class ZoningOperations(
targetDefinition.Packet.ConstructorData(targetPlayer).get targetDefinition.Packet.ConstructorData(targetPlayer).get
) )
) )
case _ => ; case _ => ()
} }
case _ => ; case _ => ()
} }
}) })
//facility turrets //facility turrets
@ -558,7 +558,7 @@ class ZoningOperations(
objDef.Packet.ConstructorData(obj).get objDef.Packet.ConstructorData(obj).get
) )
) )
case _ => ; case _ => ()
} }
} }
//reserved ammunition? //reserved ammunition?
@ -575,7 +575,7 @@ class ZoningOperations(
targetDefinition.Packet.ConstructorData(targetPlayer).get targetDefinition.Packet.ConstructorData(targetPlayer).get
) )
) )
case _ => ; case _ => ()
} }
turret.Target.collect { turret.Target.collect {
target => target =>
@ -918,7 +918,7 @@ class ZoningOperations(
def beginZoningCountdown(runnable: Runnable): Unit = { def beginZoningCountdown(runnable: Runnable): Unit = {
val descriptor = zoningType.toString.toLowerCase val descriptor = zoningType.toString.toLowerCase
if (zoningStatus == Zoning.Status.Request) { if (zoningStatus == Zoning.Status.Request) {
avatarActor ! AvatarActor.DeactivateActiveImplants() avatarActor ! AvatarActor.DeactivateActiveImplants
zoningStatus = Zoning.Status.Countdown zoningStatus = Zoning.Status.Countdown
val (time, origin) = ZoningStartInitialMessageAndTimer() val (time, origin) = ZoningStartInitialMessageAndTimer()
zoningCounter = time zoningCounter = time
@ -1071,7 +1071,7 @@ class ZoningOperations(
) )
) )
} }
case _ => ; case _ => ()
} }
} }
@ -1092,7 +1092,7 @@ class ZoningOperations(
case Some(obj) if obj.Condition == PlanetSideGeneratorState.Destroyed || building.NtuLevel == 0 => case Some(obj) if obj.Condition == PlanetSideGeneratorState.Destroyed || building.NtuLevel == 0 =>
sendResponse(PlanetsideAttributeMessage(guid, 48, 1)) //amenities disabled; red warning lights sendResponse(PlanetsideAttributeMessage(guid, 48, 1)) //amenities disabled; red warning lights
sendResponse(PlanetsideAttributeMessage(guid, 38, 0)) //disable spawn target on deployment map sendResponse(PlanetsideAttributeMessage(guid, 38, 0)) //disable spawn target on deployment map
case _ => ; case _ => ()
} }
// capitol force dome state // capitol force dome state
if (building.IsCapitol && building.ForceDomeActive) { if (building.IsCapitol && building.ForceDomeActive) {
@ -1177,7 +1177,7 @@ class ZoningOperations(
LocalAction.SendPacket(ObjectAttachMessage(llu.Carrier.get.GUID, llu.GUID, 252)) LocalAction.SendPacket(ObjectAttachMessage(llu.Carrier.get.GUID, llu.GUID, 252))
) )
} }
case _ => ; case _ => ()
} }
} }
@ -1495,7 +1495,7 @@ class ZoningOperations(
// remove owner // remove owner
vehicle.Actor ! Vehicle.Ownership(None) vehicle.Actor ! Vehicle.Ownership(None)
case _ => ; case _ => ()
} }
avatarActor ! AvatarActor.SetVehicle(None) avatarActor ! AvatarActor.SetVehicle(None)
} }
@ -1736,7 +1736,7 @@ class ZoningOperations(
case Success(overrides: List[Any]) => case Success(overrides: List[Any]) =>
//safe to cast like this //safe to cast like this
sendResponse(PropertyOverrideMessage(overrides.map { _.asInstanceOf[PropertyOverrideMessage.GamePropertyScope] })) sendResponse(PropertyOverrideMessage(overrides.map { _.asInstanceOf[PropertyOverrideMessage.GamePropertyScope] }))
case _ => ; case _ => ()
} }
} }
@ -2047,8 +2047,6 @@ class ZoningOperations(
sessionLogic.persist = UpdatePersistenceAndRefs sessionLogic.persist = UpdatePersistenceAndRefs
tplayer.avatar = avatar tplayer.avatar = avatar
session = session.copy(player = tplayer) session = session.copy(player = tplayer)
avatarActor ! AvatarActor.CreateImplants()
avatarActor ! AvatarActor.InitializeImplants()
//LoadMapMessage causes the client to send BeginZoningMessage, eventually leading to SetCurrentAvatar //LoadMapMessage causes the client to send BeginZoningMessage, eventually leading to SetCurrentAvatar
val weaponsEnabled = !(mapName.equals("map11") || mapName.equals("map12") || mapName.equals("map13")) val weaponsEnabled = !(mapName.equals("map11") || mapName.equals("map12") || mapName.equals("map13"))
sendResponse(LoadMapMessage(mapName, id, 40100, 25, weaponsEnabled, map.checksum)) sendResponse(LoadMapMessage(mapName, id, 40100, 25, weaponsEnabled, map.checksum))
@ -2119,6 +2117,40 @@ class ZoningOperations(
} }
} }
def handlePlayerHasLeft(zone: Zone, playerOpt: Option[Player]): Unit = {
playerOpt match {
case None =>
log.debug(s"PlayerHasLeft: ${player.Name} does not have a body on ${zone.id}")
case Some(tplayer) if tplayer.isAlive =>
log.info(s"${tplayer.Name} has left zone ${zone.id}")
case _ => ()
}
}
def handlePlayerCanNotSpawn(zone: Zone, tplayer: Player): Unit = {
log.warn(s"${tplayer.Name} can not spawn in zone ${zone.id}; why?")
}
def handlePlayerAlreadySpawned(zone: Zone, tplayer: Player): Unit = {
log.warn(s"${tplayer.Name} is already spawned on zone ${zone.id}; is this a clerical error?")
}
def handleCanNotSpawn(zone: Zone, vehicle: Vehicle, reason: String): Unit = {
log.warn(
s"${player.Name}'s ${vehicle.Definition.Name} can not spawn in ${zone.id} because $reason"
)
}
def handleCanNotDespawn(zone: Zone, vehicle: Vehicle, reason: String): Unit = {
log.warn(
s"${player.Name}'s ${vehicle.Definition.Name} can not deconstruct in ${zone.id} because $reason"
)
}
def handlePlayerFailedToLoad(tplayer: Player): Unit = {
sessionLogic.failWithError(s"${tplayer.Name} failed to load anywhere")
}
/* support functions */ /* support functions */
private def dropMedicalApplicators(p: Player): Unit = { private def dropMedicalApplicators(p: Player): Unit = {
@ -2230,7 +2262,7 @@ class ZoningOperations(
val armor = player.Armor val armor = player.Armor
val events = continent.VehicleEvents val events = continent.VehicleEvents
val zoneid = continent.id val zoneid = continent.id
avatarActor ! AvatarActor.ResetImplants() avatarActor ! AvatarActor.SoftResetImplants
player.Spawn() player.Spawn()
if (health != 0) { if (health != 0) {
player.Health = health player.Health = health
@ -2289,7 +2321,7 @@ class ZoningOperations(
carrierInfo match { carrierInfo match {
case (Some(carrier), Some((index, _))) => case (Some(carrier), Some((index, _))) =>
CargoMountBehaviorForUs(carrier, vehicle, index) CargoMountBehaviorForUs(carrier, vehicle, index)
case _ => ; case _ => ()
} }
data data
} }
@ -2458,7 +2490,7 @@ class ZoningOperations(
// workaround to make sure player is spawned with full stamina // workaround to make sure player is spawned with full stamina
player.avatar = player.avatar.copy(stamina = avatar.maxStamina) player.avatar = player.avatar.copy(stamina = avatar.maxStamina)
avatarActor ! AvatarActor.RestoreStamina(avatar.maxStamina) avatarActor ! AvatarActor.RestoreStamina(avatar.maxStamina)
avatarActor ! AvatarActor.ResetImplants() avatarActor ! AvatarActor.DeinitializeImplants
zones.exp.ToDatabase.reportRespawns(tplayer.CharId, ScoreCard.reviveCount(player.avatar.scorecard.CurrentLife)) zones.exp.ToDatabase.reportRespawns(tplayer.CharId, ScoreCard.reviveCount(player.avatar.scorecard.CurrentLife))
val obj = Player.Respawn(tplayer) val obj = Player.Respawn(tplayer)
DefinitionUtil.applyDefaultLoadout(obj) DefinitionUtil.applyDefaultLoadout(obj)
@ -2476,7 +2508,7 @@ class ZoningOperations(
def FriskDeadBody(obj: Player): Unit = { def FriskDeadBody(obj: Player): Unit = {
if (!obj.isAlive) { if (!obj.isAlive) {
obj.Slot(4).Equipment match { obj.Slot(4).Equipment match {
case None => ; case None => ()
case Some(knife) => case Some(knife) =>
RemoveOldEquipmentFromInventory(obj)(knife) RemoveOldEquipmentFromInventory(obj)(knife)
} }
@ -2485,7 +2517,7 @@ class ZoningOperations(
if (GlobalDefinitions.isMaxArms(arms.Definition)) { if (GlobalDefinitions.isMaxArms(arms.Definition)) {
RemoveOldEquipmentFromInventory(obj)(arms) RemoveOldEquipmentFromInventory(obj)(arms)
} }
case _ => ; case _ => ()
} }
//disown boomers and drop triggers //disown boomers and drop triggers
val boomers = avatar.deployables.ClearDeployable(DeployedItem.boomer) val boomers = avatar.deployables.ClearDeployable(DeployedItem.boomer)
@ -2493,7 +2525,7 @@ class ZoningOperations(
continent.GUID(boomer) match { continent.GUID(boomer) match {
case Some(obj: BoomerDeployable) => case Some(obj: BoomerDeployable) =>
obj.Actor ! Deployable.Ownership(None) obj.Actor ! Deployable.Ownership(None)
case Some(_) | None => ; case Some(_) | None => ()
} }
}) })
removeBoomerTriggersFromInventory().foreach(trigger => { sessionLogic.general.normalItemDrop(obj, continent)(trigger) }) removeBoomerTriggersFromInventory().foreach(trigger => { sessionLogic.general.normalItemDrop(obj, continent)(trigger) })
@ -2760,9 +2792,9 @@ class ZoningOperations(
// new player is spawning // new player is spawning
val newPlayer = RespawnClone(player) val newPlayer = RespawnClone(player)
newPlayer.LogActivity(SpawningActivity(PlayerSource(newPlayer), toZoneNumber, toSpawnPoint)) newPlayer.LogActivity(SpawningActivity(PlayerSource(newPlayer), toZoneNumber, toSpawnPoint))
LoadZoneAsPlayUsing(newPlayer, pos, ori, toSide, zoneId) LoadZoneAsPlayerUsing(newPlayer, pos, ori, toSide, zoneId)
} else { } else {
avatarActor ! AvatarActor.DeactivateActiveImplants() avatarActor ! AvatarActor.DeactivateActiveImplants
val betterSpawnPoint = physSpawnPoint.collect { case o: PlanetSideGameObject with FactionAffinity with InGameHistory => o } val betterSpawnPoint = physSpawnPoint.collect { case o: PlanetSideGameObject with FactionAffinity with InGameHistory => o }
interstellarFerry.orElse(continent.GUID(player.VehicleSeated)) match { interstellarFerry.orElse(continent.GUID(player.VehicleSeated)) match {
case Some(vehicle: Vehicle) => // driver or passenger in vehicle using a warp gate, or a droppod case Some(vehicle: Vehicle) => // driver or passenger in vehicle using a warp gate, or a droppod
@ -2779,11 +2811,11 @@ class ZoningOperations(
AvatarAction.ObjectDelete(player_guid, player_guid, 4) AvatarAction.ObjectDelete(player_guid, player_guid, 4)
) )
InGameHistory.SpawnReconstructionActivity(player, toZoneNumber, betterSpawnPoint) InGameHistory.SpawnReconstructionActivity(player, toZoneNumber, betterSpawnPoint)
LoadZoneAsPlayUsing(player, pos, ori, toSide, zoneId) LoadZoneAsPlayerUsing(player, pos, ori, toSide, zoneId)
case _ => //player is logging in case _ => //player is logging in
InGameHistory.SpawnReconstructionActivity(player, toZoneNumber, betterSpawnPoint) InGameHistory.SpawnReconstructionActivity(player, toZoneNumber, betterSpawnPoint)
LoadZoneAsPlayUsing(player, pos, ori, toSide, zoneId) LoadZoneAsPlayerUsing(player, pos, ori, toSide, zoneId)
} }
} }
} }
@ -2797,7 +2829,7 @@ class ZoningOperations(
* @param onThisSide description of the containing environment * @param onThisSide description of the containing environment
* @param goingToZone common designation for the zone * @param goingToZone common designation for the zone
*/ */
private def LoadZoneAsPlayUsing( private def LoadZoneAsPlayerUsing(
target: Player, target: Player,
position: Vector3, position: Vector3,
orientation: Vector3, orientation: Vector3,
@ -2924,9 +2956,9 @@ class ZoningOperations(
tplayer.Actor ! JammableUnit.ClearJammeredStatus() tplayer.Actor ! JammableUnit.ClearJammeredStatus()
tplayer.Actor ! JammableUnit.ClearJammeredSound() tplayer.Actor ! JammableUnit.ClearJammeredSound()
} }
avatarActor ! AvatarActor.SoftResetImplants
val originalDeadState = deadState val originalDeadState = deadState
deadState = DeadState.Alive deadState = DeadState.Alive
avatarActor ! AvatarActor.ResetImplants()
sendResponse(PlanetsideAttributeMessage(PlanetSideGUID(0), 82, 0)) sendResponse(PlanetsideAttributeMessage(PlanetSideGUID(0), 82, 0))
initializeShortcutsAndBank(guid, tavatar.shortcuts) initializeShortcutsAndBank(guid, tavatar.shortcuts)
//Favorites lists //Favorites lists
@ -3035,7 +3067,7 @@ class ZoningOperations(
case (Some(vehicle), _) => case (Some(vehicle), _) =>
//passenger //passenger
vehicle.Actor ! Vehicle.UpdateZoneInteractionProgressUI(tplayer) vehicle.Actor ! Vehicle.UpdateZoneInteractionProgressUI(tplayer)
case _ => ; case _ => ()
} }
interstellarFerryTopLevelGUID = None interstellarFerryTopLevelGUID = None
if (loadConfZone && sessionLogic.connectionState == 100) { if (loadConfZone && sessionLogic.connectionState == 100) {
@ -3329,7 +3361,7 @@ class ZoningOperations(
sendResponse(ObjectAttachMessage(vguid, pguid, seat)) sendResponse(ObjectAttachMessage(vguid, pguid, seat))
sessionLogic.general.accessContainer(vehicle) sessionLogic.general.accessContainer(vehicle)
sessionLogic.keepAliveFunc = sessionLogic.keepAlivePersistence sessionLogic.keepAliveFunc = sessionLogic.keepAlivePersistence
case _ => ; case _ => ()
//we can't find a vehicle? and we're still here? that's bad //we can't find a vehicle? and we're still here? that's bad
player.VehicleSeated = None player.VehicleSeated = None
} }
@ -3517,7 +3549,7 @@ class ZoningOperations(
delay: Long delay: Long
): Unit = { ): Unit = {
messageBundles match { messageBundles match {
case Nil => ; case Nil => ()
case x :: Nil => case x :: Nil =>
x.foreach { x.foreach {
sendResponse sendResponse
@ -3554,10 +3586,7 @@ class ZoningOperations(
def startDeconstructing(obj: SpawnTube): Unit = { def startDeconstructing(obj: SpawnTube): Unit = {
log.info(s"${player.Name} is deconstructing at the ${obj.Owner.Definition.Name}'s spawns") log.info(s"${player.Name} is deconstructing at the ${obj.Owner.Definition.Name}'s spawns")
avatar.implants.collect { avatarActor ! AvatarActor.DeactivateActiveImplants
case Some(implant) if implant.active && !implant.definition.Passive =>
avatarActor ! AvatarActor.DeactivateImplant(implant.definition.implantType)
}
if (player.ExoSuit != ExoSuitType.MAX) { if (player.ExoSuit != ExoSuitType.MAX) {
player.Actor ! PlayerControl.ObjectHeld(Player.HandsDownSlot, updateMyHolsterArm = true) player.Actor ! PlayerControl.ObjectHeld(Player.HandsDownSlot, updateMyHolsterArm = true)
} }
@ -3581,10 +3610,11 @@ class ZoningOperations(
} }
def randomRespawn(time: FiniteDuration = 300.seconds): Unit = { def randomRespawn(time: FiniteDuration = 300.seconds): Unit = {
val faction = player.Faction
reviveTimer = context.system.scheduler.scheduleOnce(time) { reviveTimer = context.system.scheduler.scheduleOnce(time) {
cluster ! ICS.GetRandomSpawnPoint( cluster ! ICS.GetRandomSpawnPoint(
Zones.sanctuaryZoneNumber(player.Faction), Zones.sanctuaryZoneNumber(faction),
player.Faction, faction,
Seq(SpawnGroup.Sanctuary), Seq(SpawnGroup.Sanctuary),
context.self context.self
) )

View file

@ -4,8 +4,10 @@ package net.psforever.objects
import akka.actor.{ActorContext, Props} import akka.actor.{ActorContext, Props}
import net.psforever.objects.ce.{Deployable, DeployedItem} import net.psforever.objects.ce.{Deployable, DeployedItem}
import net.psforever.objects.guid.{GUIDTask, TaskWorkflow} import net.psforever.objects.guid.{GUIDTask, TaskWorkflow}
import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject} import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject}
import net.psforever.objects.sourcing.{PlayerSource, SourceEntry} import net.psforever.objects.sourcing.{PlayerSource, SourceEntry}
import net.psforever.objects.vital.Vitality
import net.psforever.objects.vital.etc.TriggerUsedReason import net.psforever.objects.vital.etc.TriggerUsedReason
import net.psforever.objects.vital.interaction.DamageInteraction import net.psforever.objects.vital.interaction.DamageInteraction
import net.psforever.objects.zones.Zone import net.psforever.objects.zones.Zone
@ -36,7 +38,8 @@ class BoomerDeployable(cdef: ExplosiveDeployableDefinition)
} }
} }
class BoomerDeployableDefinition(private val objectId: Int) extends ExplosiveDeployableDefinition(objectId) { class BoomerDeployableDefinition(private val objectId: Int)
extends ExplosiveDeployableDefinition(objectId) {
override def Initialize(obj: Deployable, context: ActorContext): Unit = { override def Initialize(obj: Deployable, context: ActorContext): Unit = {
obj.Actor = obj.Actor =
context.actorOf(Props(classOf[BoomerDeployableControl], obj), PlanetSideServerObject.UniqueActorName(obj)) context.actorOf(Props(classOf[BoomerDeployableControl], obj), PlanetSideServerObject.UniqueActorName(obj))
@ -58,8 +61,7 @@ class BoomerDeployableControl(mine: BoomerDeployable)
case CommonMessages.Use(player, Some(trigger: BoomerTrigger)) if mine.Trigger.contains(trigger) => case CommonMessages.Use(player, Some(trigger: BoomerTrigger)) if mine.Trigger.contains(trigger) =>
// the trigger damages the mine, which sets it off, which causes an explosion // the trigger damages the mine, which sets it off, which causes an explosion
// think of this as an initiator to the proper explosion // think of this as an initiator to the proper explosion
mine.Destroyed = true HandleDamage(
ExplosiveDeployableControl.DamageResolution(
mine, mine,
DamageInteraction( DamageInteraction(
SourceEntry(mine), SourceEntry(mine),
@ -68,8 +70,7 @@ class BoomerDeployableControl(mine: BoomerDeployable)
).calculate()(mine), ).calculate()(mine),
damage = 0 damage = 0
) )
case _ => ()
case _ => ;
} }
def loseOwnership(@unused faction: PlanetSideEmpire.Value): Unit = { def loseOwnership(@unused faction: PlanetSideEmpire.Value): Unit = {
@ -97,14 +98,27 @@ class BoomerDeployableControl(mine: BoomerDeployable)
container.Slot(index).Equipment = None container.Slot(index).Equipment = None
case Some(Zone.EquipmentIs.OnGround()) => case Some(Zone.EquipmentIs.OnGround()) =>
zone.Ground ! Zone.Ground.RemoveItem(guid) zone.Ground ! Zone.Ground.RemoveItem(guid)
case _ => ; case _ => ()
} }
zone.AvatarEvents! AvatarServiceMessage( zone.AvatarEvents! AvatarServiceMessage(
zone.id, zone.id,
AvatarAction.ObjectDelete(Service.defaultPlayerGUID, guid) AvatarAction.ObjectDelete(Service.defaultPlayerGUID, guid)
) )
TaskWorkflow.execute(GUIDTask.unregisterObject(zone.GUID, trigger)) TaskWorkflow.execute(GUIDTask.unregisterObject(zone.GUID, trigger))
case None => ; case None => ()
} }
} }
/**
* Boomers are not bothered by explosive sympathy
* but can still be affected by sources of jammering.
* @param obj the entity being damaged
* @param damage the amount of damage
* @param data historical information about the damage
* @return `true`, if the target can be affected;
* `false`, otherwise
*/
override def CanDetonate(obj: Vitality with FactionAffinity, damage: Int, data: DamageInteraction): Boolean = {
super.CanDetonate(obj, damage, data) || data.cause.isInstanceOf[TriggerUsedReason]
}
} }

View file

@ -1,7 +1,7 @@
// Copyright (c) 2018 PSForever // Copyright (c) 2018 PSForever
package net.psforever.objects package net.psforever.objects
import akka.actor.{Actor, ActorContext, ActorRef, Props} import akka.actor.Actor
import net.psforever.objects.ce._ import net.psforever.objects.ce._
import net.psforever.objects.definition.DeployableDefinition import net.psforever.objects.definition.DeployableDefinition
import net.psforever.objects.definition.converter.SmallDeployableConverter import net.psforever.objects.definition.converter.SmallDeployableConverter
@ -11,8 +11,6 @@ import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.damage.{Damageable, DamageableEntity} import net.psforever.objects.serverobject.damage.{Damageable, DamageableEntity}
import net.psforever.objects.serverobject.damage.Damageable.Target import net.psforever.objects.serverobject.damage.Damageable.Target
import net.psforever.objects.sourcing.{DeployableSource, PlayerSource, SourceEntry}
import net.psforever.objects.vital.etc.TrippedMineReason
import net.psforever.objects.vital.resolution.ResolutionCalculations.Output import net.psforever.objects.vital.resolution.ResolutionCalculations.Output
import net.psforever.objects.vital.{SimpleResolutions, Vitality} import net.psforever.objects.vital.{SimpleResolutions, Vitality}
import net.psforever.objects.vital.interaction.{DamageInteraction, DamageResult} import net.psforever.objects.vital.interaction.{DamageInteraction, DamageResult}
@ -23,6 +21,7 @@ import net.psforever.services.Service
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage} import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
import net.psforever.services.local.{LocalAction, LocalServiceMessage} import net.psforever.services.local.{LocalAction, LocalServiceMessage}
import scala.annotation.unused
import scala.concurrent.duration._ import scala.concurrent.duration._
class ExplosiveDeployable(cdef: ExplosiveDeployableDefinition) class ExplosiveDeployable(cdef: ExplosiveDeployableDefinition)
@ -36,7 +35,7 @@ object ExplosiveDeployable {
final case class TriggeredBy(obj: PlanetSideServerObject) final case class TriggeredBy(obj: PlanetSideServerObject)
} }
class ExplosiveDeployableDefinition(private val objectId: Int) abstract class ExplosiveDeployableDefinition(private val objectId: Int)
extends DeployableDefinition(objectId) { extends DeployableDefinition(objectId) {
Name = "explosive_deployable" Name = "explosive_deployable"
DeployCategory = DeployableCategory.Mines DeployCategory = DeployableCategory.Mines
@ -45,6 +44,8 @@ class ExplosiveDeployableDefinition(private val objectId: Int)
private var detonateOnJamming: Boolean = true private var detonateOnJamming: Boolean = true
private var stability: Boolean = false
var triggerRadius: Float = 0f var triggerRadius: Float = 0f
def DetonateOnJamming: Boolean = detonateOnJamming def DetonateOnJamming: Boolean = detonateOnJamming
@ -54,15 +55,11 @@ class ExplosiveDeployableDefinition(private val objectId: Int)
DetonateOnJamming DetonateOnJamming
} }
override def Initialize(obj: Deployable, context: ActorContext): Unit = { def Stable: Boolean = stability
obj.Actor =
context.actorOf(Props(classOf[MineDeployableControl], obj), PlanetSideServerObject.UniqueActorName(obj))
}
}
object ExplosiveDeployableDefinition { def Stable_=(stableState: Boolean): Boolean = {
def apply(dtype: DeployedItem.Value): ExplosiveDeployableDefinition = { stability = stableState
new ExplosiveDeployableDefinition(dtype.id) Stable
} }
} }
@ -90,14 +87,36 @@ abstract class ExplosiveDeployableControl(mine: ExplosiveDeployable)
val originalHealth = mine.Health val originalHealth = mine.Health
val cause = applyDamageTo(mine) val cause = applyDamageTo(mine)
val damage = originalHealth - mine.Health val damage = originalHealth - mine.Health
if (CanDetonate(mine, damage, cause.interaction)) { if (Interaction(mine, damage, cause.interaction)) {
ExplosiveDeployableControl.DamageResolution(mine, cause, damage) HandleDamage(mine, cause, damage)
} else { } else {
mine.Health = originalHealth mine.Health = originalHealth
} }
} }
} }
final def HandleDamage(target: ExplosiveDeployable, cause: DamageResult, damage: Int): Unit = {
target.LogActivity(cause)
if (CanDetonate(target, damage, cause.interaction)) {
ExplosiveDeployableControl.doExplosion(target, cause)
} else if (target.Health == 0) {
ExplosiveDeployableControl.DestructionAwareness(target, cause)
} else {
ExplosiveDeployableControl.DamageAwareness(target, cause, damage)
}
}
def Interaction(obj: Vitality with FactionAffinity, damage: Int, data: DamageInteraction): Boolean = {
val actualDamage: Int = if (!mine.Definition.Stable && data.cause.source.SympatheticExplosion) {
math.max(damage, 1)
} else {
damage
}
!mine.Destroyed &&
Damageable.adversarialOrHackableChecks(obj, data) &&
(CanDetonate(obj, actualDamage, data) || Damageable.CanDamage(obj, actualDamage, data))
}
/** /**
* A supplement for checking target susceptibility * A supplement for checking target susceptibility
* to account for sympathetic explosives even if there is no damage. * to account for sympathetic explosives even if there is no damage.
@ -110,31 +129,33 @@ abstract class ExplosiveDeployableControl(mine: ExplosiveDeployable)
* @return `true`, if the target can be affected; * @return `true`, if the target can be affected;
* `false`, otherwise * `false`, otherwise
*/ */
def CanDetonate(obj: Vitality with FactionAffinity, damage: Int, data: DamageInteraction): Boolean = { def CanDetonate(obj: Vitality with FactionAffinity, @unused damage: Int, data: DamageInteraction): Boolean = {
!mine.Destroyed && (if (damage == 0 && data.cause.source.SympatheticExplosion) { val sourceDef = data.cause.source
Damageable.CanDamageOrJammer(mine, damage = 1, data) val mineDef = mine.Definition
} else { val explodeFromSympathy: Boolean = sourceDef.SympatheticExplosion && !mineDef.Stable
Damageable.CanDamageOrJammer(mine, damage, data) val explodeFromJammer: Boolean = ExplosiveDeployableControl.CanJammer(mine, data)
}) !mine.Destroyed && (explodeFromSympathy || explodeFromJammer)
} }
} }
object ExplosiveDeployableControl { object ExplosiveDeployableControl {
def CanJammer(mine: ExplosiveDeployable, data: DamageInteraction): Boolean = {
Damageable.adversarialOrHackableChecks(mine, data) &&
data.cause.source.AdditionalEffect &&
mine.Definition.DetonateOnJamming
}
/** /**
* na * na
* @param target na * @param target na
* @param cause na * @param cause na
* @param damage na * @param damage na
*/ */
def DamageResolution(target: ExplosiveDeployable, cause: DamageResult, damage: Int): Unit = { def DamageAwareness(target: ExplosiveDeployable, cause: DamageResult, damage: Int): Unit = {
target.LogActivity(cause) if (
if (cause.interaction.cause.source.SympatheticExplosion) { !target.Jammed &&
explodes(target, cause) CanJammer(target, cause.interaction) &&
DestructionAwareness(target, cause) {
} else if (target.Health == 0) {
DestructionAwareness(target, cause)
} else if (!target.Jammed && Damageable.CanJammer(target, cause.interaction)) {
if ( {
target.Jammed = cause.interaction.cause match { target.Jammed = cause.interaction.cause match {
case o: ProjectileReason => case o: ProjectileReason =>
val radius = o.projectile.profile.DamageRadius val radius = o.projectile.profile.DamageRadius
@ -150,7 +171,6 @@ object ExplosiveDeployableControl {
DestructionAwareness(target, cause) DestructionAwareness(target, cause)
} }
} }
}
/** /**
* na * na
@ -158,6 +178,7 @@ object ExplosiveDeployableControl {
* @param cause na * @param cause na
*/ */
def explodes(target: Damageable.Target, cause: DamageResult): Unit = { def explodes(target: Damageable.Target, cause: DamageResult): Unit = {
target.Destroyed = true
target.Health = 1 // short-circuit logic in DestructionAwareness target.Health = 1 // short-circuit logic in DestructionAwareness
val zone = target.Zone val zone = target.Zone
zone.Activity ! Zone.HotSpot.Activity(cause) zone.Activity ! Zone.HotSpot.Activity(cause)
@ -170,6 +191,11 @@ object ExplosiveDeployableControl {
) )
} }
def doExplosion(target: ExplosiveDeployable, cause: DamageResult): Unit = {
explodes(target, cause)
DestructionAwareness(target, cause)
}
/** /**
* na * na
* @param target na * @param target na
@ -252,109 +278,3 @@ object ExplosiveDeployableControl {
) <= maxDistance ) <= maxDistance
} }
} }
class MineDeployableControl(mine: ExplosiveDeployable)
extends ExplosiveDeployableControl(mine) {
def receive: Receive =
commonMineBehavior
.orElse {
case ExplosiveDeployable.TriggeredBy(obj) =>
setTriggered(Some(obj), delay = 200)
case MineDeployableControl.Triggered() =>
explodes(testForTriggeringTarget(
mine,
mine.Definition.innateDamage.map { _.DamageRadius }.getOrElse(mine.Definition.triggerRadius)
))
case _ => ;
}
override def finalizeDeployable(callback: ActorRef): Unit = {
super.finalizeDeployable(callback)
//initial triggering upon build
setTriggered(testForTriggeringTarget(mine, mine.Definition.triggerRadius), delay = 1000)
}
def testForTriggeringTarget(mine: ExplosiveDeployable, range: Float): Option[PlanetSideServerObject] = {
val position = mine.Position
val faction = mine.Faction
val range2 = range * range
val sector = mine.Zone.blockMap.sector(position, range)
(sector.livePlayerList ++ sector.vehicleList)
.find { thing => thing.Faction != faction && Vector3.DistanceSquared(thing.Position, position) < range2 }
}
def setTriggered(instigator: Option[PlanetSideServerObject], delay: Long): Unit = {
instigator match {
case Some(_) if isConstructed.contains(true) && setup.isCancelled =>
//re-use the setup timer here
import scala.concurrent.ExecutionContext.Implicits.global
setup = context.system.scheduler.scheduleOnce(delay milliseconds, self, MineDeployableControl.Triggered())
case _ => ;
}
}
def explodes(instigator: Option[PlanetSideServerObject]): Unit = {
instigator match {
case Some(_) =>
//explosion
mine.Destroyed = true
ExplosiveDeployableControl.DamageResolution(
mine,
DamageInteraction(
SourceEntry(mine),
MineDeployableControl.trippedMineReason(mine),
mine.Position
).calculate()(mine),
damage = 0
)
case None =>
//reset
setup = Default.Cancellable
}
}
}
object MineDeployableControl {
private case class Triggered()
def trippedMineReason(mine: ExplosiveDeployable): TrippedMineReason = {
lazy val deployableSource = DeployableSource(mine)
val zone = mine.Zone
val ownerName = mine.OwnerName
val blame = zone
.Players
.find(a => ownerName.contains(a.name))
.collect { a =>
val name = a.name
assignBlameToFrom(name, zone.LivePlayers)
.orElse(assignBlameToFrom(name, zone.Corpses))
.getOrElse {
val player = PlayerSource(name, mine.Faction, mine.Position) //might report minor inconsistencies, e.g., exo-suit type
player.copy(unique = player.unique.copy(charId = a.id), progress = a.scorecard.CurrentLife)
}
}
.getOrElse(deployableSource)
TrippedMineReason(deployableSource, blame)
}
/**
* Find a player with a given name from this list of possible players.
* If the player is seated, attach a shallow copy of the mounting information.
* @param name player name
* @param blameList possible players in which to find the player name
* @return discovered player as a reference, or `None` if not found
*/
private def assignBlameToFrom(name: String, blameList: List[Player]): Option[SourceEntry] = {
blameList
.find(_.Name.equals(name))
.map { player =>
PlayerSource
.mountableAndSeat(player)
.map { case (mount, seat) => PlayerSource.inSeat(player, mount, seat) }
.getOrElse { PlayerSource(player) }
}
}
}

View file

@ -1019,9 +1019,9 @@ object GlobalDefinitions {
*/ */
val boomer: BoomerDeployableDefinition = BoomerDeployableDefinition(DeployedItem.boomer) val boomer: BoomerDeployableDefinition = BoomerDeployableDefinition(DeployedItem.boomer)
val he_mine: ExplosiveDeployableDefinition = ExplosiveDeployableDefinition(DeployedItem.he_mine) val he_mine: MineDeployableDefinition = MineDeployableDefinition(DeployedItem.he_mine)
val jammer_mine: ExplosiveDeployableDefinition = ExplosiveDeployableDefinition(DeployedItem.jammer_mine) val jammer_mine: MineDeployableDefinition = MineDeployableDefinition(DeployedItem.jammer_mine)
val spitfire_turret: TurretDeployableDefinition = TurretDeployableDefinition(DeployedItem.spitfire_turret) val spitfire_turret: TurretDeployableDefinition = TurretDeployableDefinition(DeployedItem.spitfire_turret)

View file

@ -0,0 +1,137 @@
// Copyright (c) 2024 PSForever
package net.psforever.objects
import akka.actor.{ActorContext, ActorRef, Props}
import net.psforever.objects.ce.{Deployable, DeployedItem}
import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.sourcing.{DeployableSource, PlayerSource, SourceEntry}
import net.psforever.objects.vital.Vitality
import net.psforever.objects.vital.etc.TrippedMineReason
import net.psforever.objects.vital.interaction.DamageInteraction
import net.psforever.types.Vector3
import scala.concurrent.duration._
class MineDeployableDefinition(private val objectId: Int)
extends ExplosiveDeployableDefinition(objectId) {
override def Initialize(obj: Deployable, context: ActorContext): Unit = {
obj.Actor =
context.actorOf(Props(classOf[MineDeployableControl], obj), PlanetSideServerObject.UniqueActorName(obj))
}
}
object MineDeployableDefinition {
def apply(dtype: DeployedItem.Value): MineDeployableDefinition = {
new MineDeployableDefinition(dtype.id)
}
}
class MineDeployableControl(mine: ExplosiveDeployable)
extends ExplosiveDeployableControl(mine) {
def receive: Receive =
commonMineBehavior
.orElse {
case ExplosiveDeployable.TriggeredBy(obj) =>
setTriggered(Some(obj), delay = 200)
case MineDeployableControl.Triggered() =>
explodes(testForTriggeringTarget(
mine,
mine.Definition.innateDamage.map { _.DamageRadius }.getOrElse(mine.Definition.triggerRadius)
))
case _ => ()
}
override def finalizeDeployable(callback: ActorRef): Unit = {
super.finalizeDeployable(callback)
//initial triggering upon build
setTriggered(testForTriggeringTarget(mine, mine.Definition.triggerRadius), delay = 1000)
}
def testForTriggeringTarget(mine: ExplosiveDeployable, range: Float): Option[PlanetSideServerObject] = {
val position = mine.Position
val faction = mine.Faction
val range2 = range * range
val sector = mine.Zone.blockMap.sector(position, range)
(sector.livePlayerList ++ sector.vehicleList)
.find { thing => thing.Faction != faction && Vector3.DistanceSquared(thing.Position, position) < range2 }
}
def setTriggered(instigator: Option[PlanetSideServerObject], delay: Long): Unit = {
instigator
.collect {
case _ if isConstructed.contains(true) && setup.isCancelled =>
//re-use the setup timer here
import scala.concurrent.ExecutionContext.Implicits.global
setup = context.system.scheduler.scheduleOnce(delay milliseconds, self, MineDeployableControl.Triggered())
}
}
override def CanDetonate(obj: Vitality with FactionAffinity, damage: Int, data: DamageInteraction): Boolean = {
super.CanDetonate(obj, damage, data) || data.cause.isInstanceOf[TrippedMineReason]
}
def explodes(instigator: Option[PlanetSideServerObject]): Unit = {
//reset
setup = Default.Cancellable
instigator
.collect {
case _ =>
//explosion
HandleDamage(
mine,
DamageInteraction(
SourceEntry(mine),
MineDeployableControl.trippedMineReason(mine),
mine.Position
).calculate()(mine),
damage = 0
)
}
}
}
object MineDeployableControl {
private case class Triggered()
def trippedMineReason(mine: ExplosiveDeployable): TrippedMineReason = {
lazy val deployableSource = DeployableSource(mine)
val zone = mine.Zone
val ownerName = mine.OwnerName
val blame = zone
.Players
.find(a => ownerName.contains(a.name))
.collect { a =>
val name = a.name
assignBlameToFrom(name, zone.LivePlayers)
.orElse(assignBlameToFrom(name, zone.Corpses))
.getOrElse {
val player = PlayerSource(name, mine.Faction, mine.Position) //might report minor inconsistencies, e.g., exo-suit type
player.copy(unique = player.unique.copy(charId = a.id), progress = a.scorecard.CurrentLife)
}
}
.getOrElse(deployableSource)
TrippedMineReason(deployableSource, blame)
}
/**
* Find a player with a given name from this list of possible players.
* If the player is seated, attach a shallow copy of the mounting information.
* @param name player name
* @param blameList possible players in which to find the player name
* @return discovered player as a reference, or `None` if not found
*/
private def assignBlameToFrom(name: String, blameList: List[Player]): Option[SourceEntry] = {
blameList
.find(_.Name.equals(name))
.map { player =>
PlayerSource
.mountableAndSeat(player)
.map { case (mount, seat) => PlayerSource.inSeat(player, mount, seat) }
.getOrElse { PlayerSource(player) }
}
}
}

View file

@ -6,11 +6,19 @@ import net.psforever.packet.game.objectcreate.ImplantEntry
case class Implant( case class Implant(
definition: ImplantDefinition, definition: ImplantDefinition,
active: Boolean = false, active: Boolean = false,
initialized: Boolean = false initialized: Boolean = false,
//initializationTime: FiniteDuration timer: Long = 0L
) { ) {
def toEntry: ImplantEntry = { def toEntry: ImplantEntry = {
// TODO initialization time? val initState = if (!initialized) {
new ImplantEntry(definition.implantType, None, active) if (timer > 0) {
Some(math.max(0, ((timer - System.currentTimeMillis()) / 1000L).toInt))
} else {
Some(definition.InitializationDuration.toInt)
}
} else {
None
}
new ImplantEntry(definition.implantType, initState, active)
} }
} }

View file

@ -476,7 +476,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
Deployables.initializeConstructionItem(player.avatar.certifications, citem) Deployables.initializeConstructionItem(player.avatar.certifications, citem)
} }
//deactivate non-passive implants //deactivate non-passive implants
avatarActor ! AvatarActor.DeactivateActiveImplants() avatarActor ! AvatarActor.DeactivateActiveImplants
val zone = player.Zone val zone = player.Zone
zone.AvatarEvents ! AvatarServiceMessage( zone.AvatarEvents ! AvatarServiceMessage(
zone.id, zone.id,
@ -659,7 +659,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
afterHolsters.foreach(elem => player.Slot(elem.start).Equipment = elem.obj) afterHolsters.foreach(elem => player.Slot(elem.start).Equipment = elem.obj)
afterInventory.foreach(elem => player.Inventory.InsertQuickly(elem.start, elem.obj)) afterInventory.foreach(elem => player.Inventory.InsertQuickly(elem.start, elem.obj))
//deactivate non-passive implants //deactivate non-passive implants
avatarActor ! AvatarActor.DeactivateActiveImplants() avatarActor ! AvatarActor.DeactivateActiveImplants
player.Zone.AvatarEvents ! AvatarServiceMessage( player.Zone.AvatarEvents ! AvatarServiceMessage(
player.Zone.id, player.Zone.id,
AvatarAction.ChangeExosuit( AvatarAction.ChangeExosuit(
@ -944,7 +944,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
CancelJammeredSound(target) CancelJammeredSound(target)
super.CancelJammeredStatus(target) super.CancelJammeredStatus(target)
//uninitialize implants //uninitialize implants
avatarActor ! AvatarActor.DeinitializeImplants() avatarActor ! AvatarActor.DeinitializeImplants
//log historical event //log historical event
target.LogActivity(cause) target.LogActivity(cause)
@ -1073,13 +1073,13 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
* @param dur the duration of the timer, in milliseconds * @param dur the duration of the timer, in milliseconds
*/ */
override def StartJammeredStatus(target: Any, dur: Int): Unit = { override def StartJammeredStatus(target: Any, dur: Int): Unit = {
avatarActor ! AvatarActor.DeinitializeImplants() avatarActor ! AvatarActor.DeinitializeImplants
avatarActor ! AvatarActor.SuspendStaminaRegeneration(5 seconds) avatarActor ! AvatarActor.SuspendStaminaRegeneration(5 seconds)
super.StartJammeredStatus(target, dur) super.StartJammeredStatus(target, dur)
} }
override def CancelJammeredStatus(target: Any): Unit = { override def CancelJammeredStatus(target: Any): Unit = {
avatarActor ! AvatarActor.InitializeImplants() avatarActor ! AvatarActor.SoftResetImplants
super.CancelJammeredStatus(target) super.CancelJammeredStatus(target)
} }

View file

@ -69,7 +69,7 @@ trait DeployableBehavior {
if DeployableObject.OwnerGuid.nonEmpty => if DeployableObject.OwnerGuid.nonEmpty =>
val obj = DeployableObject val obj = DeployableObject
if (constructed.contains(true)) { if (constructed.contains(true)) {
loseOwnership(obj, obj.Faction) loseOwnership(obj, PlanetSideEmpire.NEUTRAL)
} else { } else {
obj.OwnerGuid = None obj.OwnerGuid = None
} }
@ -103,9 +103,9 @@ trait DeployableBehavior {
* may also affect deployable operation * may also affect deployable operation
*/ */
def loseOwnership(obj: Deployable, toFaction: PlanetSideEmpire.Value): Unit = { def loseOwnership(obj: Deployable, toFaction: PlanetSideEmpire.Value): Unit = {
DeployableBehavior.changeOwership( DeployableBehavior.changeOwnership(
obj, obj,
obj.Faction, toFaction,
DeployableInfo(obj.GUID, Deployable.Icon.apply(obj.Definition.Item), obj.Position, Service.defaultPlayerGUID) DeployableInfo(obj.GUID, Deployable.Icon.apply(obj.Definition.Item), obj.Position, Service.defaultPlayerGUID)
) )
startOwnerlessDecay() startOwnerlessDecay()
@ -140,7 +140,7 @@ trait DeployableBehavior {
val obj = DeployableObject val obj = DeployableObject
obj.AssignOwnership(player) obj.AssignOwnership(player)
decay.cancel() decay.cancel()
DeployableBehavior.changeOwership( DeployableBehavior.changeOwnership(
obj, obj,
toFaction, toFaction,
DeployableInfo(obj.GUID, Deployable.Icon.apply(obj.Definition.Item), obj.Position, obj.OwnerGuid.get) DeployableInfo(obj.GUID, Deployable.Icon.apply(obj.Definition.Item), obj.Position, obj.OwnerGuid.get)
@ -290,12 +290,12 @@ object DeployableBehavior {
* @param toFaction na * @param toFaction na
* @param info na * @param info na
*/ */
def changeOwership(obj: Deployable, toFaction: PlanetSideEmpire.Value, info: DeployableInfo): Unit = { def changeOwnership(obj: Deployable, toFaction: PlanetSideEmpire.Value, info: DeployableInfo): Unit = {
val originalFaction = obj.Faction
if (originalFaction != toFaction) {
val guid = obj.GUID val guid = obj.GUID
val zone = obj.Zone val zone = obj.Zone
val localEvents = zone.LocalEvents val localEvents = zone.LocalEvents
val originalFaction = obj.Faction
if (originalFaction != toFaction) {
obj.Faction = toFaction obj.Faction = toFaction
//visual tells in regards to ownership by faction //visual tells in regards to ownership by faction
zone.AvatarEvents ! AvatarServiceMessage( zone.AvatarEvents ! AvatarServiceMessage(

View file

@ -83,5 +83,6 @@ class ImplantDefinition(val implantType: ImplantType) extends BasicDefinition {
def GetCostIntervalByExoSuit(exosuit: ExoSuitType.Value): Int = def GetCostIntervalByExoSuit(exosuit: ExoSuitType.Value): Int =
costIntervalByExoSuit.getOrElse(exosuit, CostIntervalDefault) costIntervalByExoSuit.getOrElse(exosuit, CostIntervalDefault)
def CostIntervalByExoSuitHashMap: mutable.Map[ExoSuitType.Value, Int] = costIntervalByExoSuit def CostIntervalByExoSuitHashMap: mutable.Map[ExoSuitType.Value, Int] = costIntervalByExoSuit
} }

View file

@ -23,14 +23,14 @@ class LockerContainerConverter extends ObjectCreateConverter[LockerEquipment]()
if (obj.Inventory.Size > 0) { if (obj.Inventory.Size > 0) {
Success( Success(
DetailedLockerContainerData( DetailedLockerContainerData(
CommonFieldData(PlanetSideEmpire.NEUTRAL, false, false, true, None, false, None, None, PlanetSideGUID(0)), CommonFieldData(PlanetSideEmpire.NEUTRAL, false, false, true, None, true, None, None, PlanetSideGUID(0)),
Some(InventoryData(MakeDetailedInventory(obj.Inventory))) Some(InventoryData(MakeDetailedInventory(obj.Inventory)))
) )
) )
} else { } else {
Success( Success(
DetailedLockerContainerData( DetailedLockerContainerData(
CommonFieldData(PlanetSideEmpire.NEUTRAL, false, false, true, None, false, None, None, PlanetSideGUID(0)), CommonFieldData(PlanetSideEmpire.NEUTRAL, false, false, false, None, false, None, None, PlanetSideGUID(0)),
None None
) )
) )

View file

@ -19,7 +19,7 @@ class ShieldGeneratorConverter extends ObjectCreateConverter[ShieldGeneratorDepl
obj.Faction, obj.Faction,
bops = false, bops = false,
alternate = false, alternate = false,
v1 = false, v1 = true,
v2 = None, v2 = None,
jammered = obj.Jammed, jammered = obj.Jammed,
None, None,

View file

@ -34,7 +34,7 @@ object GlobalDefinitionsDeployable {
boomer.Name = "boomer" boomer.Name = "boomer"
boomer.Descriptor = "Boomers" boomer.Descriptor = "Boomers"
boomer.MaxHealth = 100 boomer.MaxHealth = 50
boomer.Damageable = true boomer.Damageable = true
boomer.DamageableByFriendlyFire = false boomer.DamageableByFriendlyFire = false
boomer.Repairable = false boomer.Repairable = false
@ -42,6 +42,7 @@ object GlobalDefinitionsDeployable {
boomer.DeployTime = Duration.create(1000, "ms") boomer.DeployTime = Duration.create(1000, "ms")
boomer.deployAnimation = DeployAnimation.Standard boomer.deployAnimation = DeployAnimation.Standard
boomer.interference = InterferenceRange(main = 0.2f) boomer.interference = InterferenceRange(main = 0.2f)
boomer.Stable = true
boomer.innateDamage = new DamageWithPosition { boomer.innateDamage = new DamageWithPosition {
CausesDamageType = DamageType.Splash CausesDamageType = DamageType.Splash
SympatheticExplosion = true SympatheticExplosion = true
@ -58,7 +59,7 @@ object GlobalDefinitionsDeployable {
he_mine.Name = "he_mine" he_mine.Name = "he_mine"
he_mine.Descriptor = "Mines" he_mine.Descriptor = "Mines"
he_mine.MaxHealth = 100 he_mine.MaxHealth = 25
he_mine.Damageable = true he_mine.Damageable = true
he_mine.DamageableByFriendlyFire = false he_mine.DamageableByFriendlyFire = false
he_mine.Repairable = false he_mine.Repairable = false
@ -82,7 +83,7 @@ object GlobalDefinitionsDeployable {
jammer_mine.Name = "jammer_mine" jammer_mine.Name = "jammer_mine"
jammer_mine.Descriptor = "JammerMines" jammer_mine.Descriptor = "JammerMines"
jammer_mine.MaxHealth = 100 jammer_mine.MaxHealth = 50
jammer_mine.Damageable = true jammer_mine.Damageable = true
jammer_mine.DamageableByFriendlyFire = false jammer_mine.DamageableByFriendlyFire = false
jammer_mine.Repairable = false jammer_mine.Repairable = false
@ -91,12 +92,12 @@ object GlobalDefinitionsDeployable {
jammer_mine.interference = InterferenceRange(main = 7f, sharedGroupId = 1, shared = 7f, deployables = 0.1f) jammer_mine.interference = InterferenceRange(main = 7f, sharedGroupId = 1, shared = 7f, deployables = 0.1f)
jammer_mine.DetonateOnJamming = false jammer_mine.DetonateOnJamming = false
jammer_mine.triggerRadius = 3f jammer_mine.triggerRadius = 3f
jammer_mine.Stable = true
jammer_mine.innateDamage = new DamageWithPosition { jammer_mine.innateDamage = new DamageWithPosition {
CausesDamageType = DamageType.Splash CausesDamageType = DamageType.Splash
Damage0 = 0 Damage0 = 0
DamageRadius = 10f DamageRadius = 10f
DamageAtEdge = 1.0f DamageAtEdge = 1.0f
AdditionalEffect = true
JammedEffectDuration += TargetValidation( JammedEffectDuration += TargetValidation(
EffectTarget.Category.Player, EffectTarget.Category.Player,
EffectTarget.Validation.Player EffectTarget.Validation.Player

View file

@ -94,12 +94,12 @@ object Damageable {
* `false`, otherwise * `false`, otherwise
*/ */
def CanJammer(obj: Vitality with FactionAffinity, data: DamageInteraction): Boolean = { def CanJammer(obj: Vitality with FactionAffinity, data: DamageInteraction): Boolean = {
data.cause.source.HasJammedEffectDuration && (data.cause.source.HasJammedEffectDuration || data.cause.source.AdditionalEffect) &&
obj.isInstanceOf[JammableUnit] && obj.isInstanceOf[JammableUnit] &&
adversarialOrHackableChecks(obj, data) adversarialOrHackableChecks(obj, data)
} }
private def adversarialOrHackableChecks(obj: Vitality with FactionAffinity, data: DamageInteraction): Boolean = { def adversarialOrHackableChecks(obj: Vitality with FactionAffinity, data: DamageInteraction): Boolean = {
(data.adversarial match { (data.adversarial match {
case Some(adversarial) => adversarial.attacker.Faction != adversarial.defender.Faction case Some(adversarial) => adversarial.attacker.Faction != adversarial.defender.Faction
case None => true case None => true

View file

@ -75,10 +75,8 @@ object DamageableMountable {
*/ */
def DestructionAwareness(target: Damageable.Target with Mountable, cause: DamageResult): Unit = { def DestructionAwareness(target: Damageable.Target with Mountable, cause: DamageResult): Unit = {
val interaction = cause.interaction val interaction = cause.interaction
target.Seats val targets = target.Seats.values.flatMap(_.occupant).filter(_.isAlive)
.values targets.foreach { player =>
.flatMap { _.occupant }
.collect { case player if player.isAlive =>
//make llu visible to others in zone if passenger is carrying one //make llu visible to others in zone if passenger is carrying one
player.Zone.AvatarEvents ! AvatarServiceMessage(player.Name, AvatarAction.DropSpecialItem()) player.Zone.AvatarEvents ! AvatarServiceMessage(player.Name, AvatarAction.DropSpecialItem())
//player.LogActivity(cause) //player.LogActivity(cause)

View file

@ -1,7 +1,8 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.deploy package net.psforever.objects.serverobject.deploy
import akka.actor.Actor import akka.actor.{Actor, Cancellable}
import net.psforever.objects.Default
import net.psforever.types.{DriveState, Vector3} import net.psforever.types.{DriveState, Vector3}
import net.psforever.services.Service import net.psforever.services.Service
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage} import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
@ -22,8 +23,14 @@ import scala.concurrent.duration._
trait DeploymentBehavior { trait DeploymentBehavior {
_: Actor => _: Actor =>
private var deploymentTimer: Cancellable = Default.Cancellable
def DeploymentObject: Deployment.DeploymentObject def DeploymentObject: Deployment.DeploymentObject
def deploymentPostStop(): Unit = {
deploymentTimer.cancel()
}
val deployBehavior: Receive = { val deployBehavior: Receive = {
case Deployment.TryDeploymentChange(state) => case Deployment.TryDeploymentChange(state) =>
sender() ! TryDeploymentStateChange(state) sender() ! TryDeploymentStateChange(state)
@ -98,9 +105,10 @@ trait DeploymentBehavior {
obj.Velocity = Some(Vector3.Zero) //no velocity obj.Velocity = Some(Vector3.Zero) //no velocity
zone.VehicleEvents ! VehicleServiceMessage( zone.VehicleEvents ! VehicleServiceMessage(
zoneChannel, zoneChannel,
VehicleAction.DeployRequest(GUID0, guid, state, 0, false, Vector3.Zero) VehicleAction.DeployRequest(GUID0, guid, state, 0, unk2=false, Vector3.Zero)
) )
context.system.scheduler.scheduleOnce( deploymentTimer.cancel()
deploymentTimer = context.system.scheduler.scheduleOnce(
obj.DeployTime milliseconds, obj.DeployTime milliseconds,
obj.Actor, obj.Actor,
Deployment.TryDeploy(DriveState.Deployed) Deployment.TryDeploy(DriveState.Deployed)
@ -110,7 +118,7 @@ trait DeploymentBehavior {
obj.Velocity = Some(Vector3.Zero) //no velocity obj.Velocity = Some(Vector3.Zero) //no velocity
zone.VehicleEvents ! VehicleServiceMessage( zone.VehicleEvents ! VehicleServiceMessage(
zoneChannel, zoneChannel,
VehicleAction.DeployRequest(GUID0, guid, state, 0, false, Vector3.Zero) VehicleAction.DeployRequest(GUID0, guid, state, 0, unk2=false, Vector3.Zero)
) )
state state
} else { } else {
@ -130,10 +138,11 @@ trait DeploymentBehavior {
if (state == DriveState.Undeploying) { if (state == DriveState.Undeploying) {
zone.VehicleEvents ! VehicleServiceMessage( zone.VehicleEvents ! VehicleServiceMessage(
zoneChannel, zoneChannel,
VehicleAction.DeployRequest(GUID0, guid, state, 0, false, Vector3.Zero) VehicleAction.DeployRequest(GUID0, guid, state, 0, unk2=false, Vector3.Zero)
) )
import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.ExecutionContext.Implicits.global
context.system.scheduler.scheduleOnce( deploymentTimer.cancel()
deploymentTimer = context.system.scheduler.scheduleOnce(
obj.UndeployTime milliseconds, obj.UndeployTime milliseconds,
obj.Actor, obj.Actor,
Deployment.TryUndeploy(DriveState.Mobile) Deployment.TryUndeploy(DriveState.Mobile)
@ -142,7 +151,7 @@ trait DeploymentBehavior {
} else if (state == DriveState.Mobile) { } else if (state == DriveState.Mobile) {
zone.VehicleEvents ! VehicleServiceMessage( zone.VehicleEvents ! VehicleServiceMessage(
zoneChannel, zoneChannel,
VehicleAction.DeployRequest(GUID0, guid, state, 0, false, Vector3.Zero) VehicleAction.DeployRequest(GUID0, guid, state, 0, unk2=false, Vector3.Zero)
) )
state state
} else { } else {

View file

@ -17,9 +17,8 @@ object EnvironmentAttribute {
/** water can only interact with objects that are negatively affected by being exposed to water; /** water can only interact with objects that are negatively affected by being exposed to water;
* it's better this way */ * it's better this way */
def canInteractWith(obj: PlanetSideGameObject): Boolean = { def canInteractWith(obj: PlanetSideGameObject): Boolean = {
obj.Definition.DrownAtMaxDepth || (obj.Definition.DrownAtMaxDepth || obj.Definition.DisableAtMaxDepth) &&
obj.Definition.DisableAtMaxDepth || canInteractWithPlayersAndVehicles(obj) &&
canInteractWithPlayersAndVehicles(obj) ||
(obj match { (obj match {
case p: Player => p.VehicleSeated.isEmpty case p: Player => p.VehicleSeated.isEmpty
case v: Vehicle => v.MountedIn.isEmpty case v: Vehicle => v.MountedIn.isEmpty

View file

@ -6,7 +6,7 @@ import net.psforever.objects.serverobject.environment.{EnvironmentAttribute, Env
import net.psforever.objects.sourcing.SourceEntry import net.psforever.objects.sourcing.SourceEntry
import net.psforever.objects.vital.etc.SuicideReason import net.psforever.objects.vital.etc.SuicideReason
import net.psforever.objects.vital.interaction.DamageInteraction import net.psforever.objects.vital.interaction.DamageInteraction
import net.psforever.objects.vital.{ReconstructionActivity, Vitality} import net.psforever.objects.vital.{IncarnationActivity, ReconstructionActivity, Vitality}
import net.psforever.objects.zones.InteractsWithZone import net.psforever.objects.zones.InteractsWithZone
import scala.annotation.unused import scala.annotation.unused
@ -29,7 +29,7 @@ class WithDeath()
@unused data: Option[Any] @unused data: Option[Any]
): Unit = { ): Unit = {
if (!obj.Destroyed) { if (!obj.Destroyed) {
obj.History.findLast { entry => entry.isInstanceOf[ReconstructionActivity] } match { obj.History.findLast { entry => entry.isInstanceOf[IncarnationActivity] } match {
case Some(entry) if System.currentTimeMillis() - entry.time > 4000L => case Some(entry) if System.currentTimeMillis() - entry.time > 4000L =>
obj.Actor ! Vitality.Damage( obj.Actor ! Vitality.Damage(
DamageInteraction( DamageInteraction(

View file

@ -65,8 +65,8 @@ abstract class Amenity
override def Zone: Zone = { override def Zone: Zone = {
if (super.Zone != World.Nowhere) { if (super.Zone != World.Nowhere) {
super.Zone super.Zone
} else if (Owner.Zone != World.Nowhere) { } else if (owner.Zone != World.Nowhere) {
Owner.Zone owner.Zone
} else { } else {
log.warn(s"Amenity $GUID tried to access it's Zone, but doesn't have one.") log.warn(s"Amenity $GUID tried to access it's Zone, but doesn't have one.")
World.Nowhere World.Nowhere

View file

@ -22,6 +22,11 @@ class DeployingVehicleControl(vehicle: Vehicle)
with DeploymentBehavior { with DeploymentBehavior {
def DeploymentObject: Vehicle = vehicle def DeploymentObject: Vehicle = vehicle
override def postStop(): Unit = {
super.postStop()
deploymentPostStop()
}
override def commonEnabledBehavior : Receive = super.commonEnabledBehavior.orElse(deployBehavior) override def commonEnabledBehavior : Receive = super.commonEnabledBehavior.orElse(deployBehavior)
/** /**

View file

@ -75,15 +75,6 @@ class VehicleControl(vehicle: Vehicle)
def CargoObject: Vehicle = vehicle def CargoObject: Vehicle = vehicle
def AffectedObject: Vehicle = vehicle def AffectedObject: Vehicle = vehicle
// SetInteraction(EnvironmentAttribute.Water, doInteractingWithWater)
// SetInteraction(EnvironmentAttribute.Lava, doInteractingWithLava)
// SetInteraction(EnvironmentAttribute.Death, doInteractingWithDeath)
// SetInteraction(EnvironmentAttribute.MovementFieldTrigger, doInteractingWithMovementTrigger)
// if (!GlobalDefinitions.isFlightVehicle(vehicle.Definition)) {
// //can recover from sinking disability
// SetInteractionStop(EnvironmentAttribute.Water, stopInteractingWithWater)
// }
/** cheap flag for whether the vehicle is decaying */ /** cheap flag for whether the vehicle is decaying */
var decaying : Boolean = false var decaying : Boolean = false
/** primary vehicle decay timer */ /** primary vehicle decay timer */

View file

@ -50,7 +50,6 @@ final case class TriggerUsedReason(user: PlayerSource, item_guid: PlanetSideGUID
object TriggerUsedReason { object TriggerUsedReason {
private val triggered = new DamageProperties { private val triggered = new DamageProperties {
Damage0 = 1 //token damage Damage0 = 1 //token damage
SympatheticExplosion = true //sets off a boomer
} }
/** basic damage, no resisting, quick and simple */ /** basic damage, no resisting, quick and simple */

View file

@ -34,7 +34,8 @@ trait DamageProperties
* also used to produce staged projectiles */ * also used to produce staged projectiles */
private var damageProxy: List[Int] = Nil private var damageProxy: List[Int] = Nil
/** na; /** na;
* currently used with jammer properties only */ * currently used with jammer properties only;
* used sepcifically to indicate jammering effect targets explosive deployables */
private var additionalEffect: Boolean = false private var additionalEffect: Boolean = false
/** confers aggravated damage burn to its target */ /** confers aggravated damage burn to its target */
private var aggravatedDamage: Option[AggravatedDamage] = None private var aggravatedDamage: Option[AggravatedDamage] = None

View file

@ -13,25 +13,14 @@ import shapeless.{::, HNil}
* The parameters `purpose` and `tile` are closely related. * The parameters `purpose` and `tile` are closely related.
* These two fields are consistent for all shortcuts of the same type. * These two fields are consistent for all shortcuts of the same type.
* `purpose` indicates the purpose of the shortcut. * `purpose` indicates the purpose of the shortcut.
* The medkit icon is 0, chat shortcuts are 1, and implants are 2.
* `tile` is related to what kind of graphic is displayed in this shortcut's slot on the hotbar based on its purpose. * `tile` is related to what kind of graphic is displayed in this shortcut's slot on the hotbar based on its purpose.
* The parameters `effect1` and `effect2` are exclusive to text macro shortcuts and are defaulted to empty `String`s.<br> * The medkit tile use "medkit", chat shortcuts use "shortcut_macro", and implants are the internal name of the implant.<br>
* <br> * <br>
* The parameters `effect1` and `effect2` are exclusive to text macro shortcuts and are defaulted to empty `String`s.
* The `shortcut_macro` setting displays a word bubble superimposed by the (first three letters of) `effect1` text.<br> * The `shortcut_macro` setting displays a word bubble superimposed by the (first three letters of) `effect1` text.<br>
* Implants and the medkit should have self-explanatory graphics. * Implants and the medkit should have self-explanatory graphics.
* <br> * The implant second wind does not have a graphic shortcut icon.
* Tile - Code<br>
* `advanced_regen` (regeneration) - 2<br>
* `audio_amplifier` - 2<br>
* `darklight_vision` - 2<br>
* `medkit` - 0<br>
* `melee_booster` - 2<br>
* `personal_shield` - 2<br>
* `range_magnifier` - 2<br>
* `second_wind` - 2<br>
* `shortcut_macro` - 1<br>
* `silent_run` (sensor shield) - 2<br>
* `surge` - 2<br>
* `targeting` (enhanced targeting) - 2
* @param code the primary use of this shortcut * @param code the primary use of this shortcut
*/ */
abstract class Shortcut(val code: Int) { abstract class Shortcut(val code: Int) {

View file

@ -1,20 +1,24 @@
// Copyright (c) 2019 PSForever // Copyright (c) 2019 PSForever
package net.psforever.packet.game package net.psforever.packet.game
import net.psforever.packet.GamePacketOpcode.Type
import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket} import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
import net.psforever.types.PlanetSideGUID import net.psforever.types.PlanetSideGUID
import scodec.Codec import scodec.bits.BitVector
import scodec.{Attempt, Codec}
import scodec.codecs._ import scodec.codecs._
import shapeless.{::, HNil} import shapeless.{::, HNil}
/** /**
* na * na
* @param unk1 na * @param unk1 na
* @param unk2 if no global unique identifier (below), the alternate identification for the entity * @param unk2 a modifier that customizes one of the values for the `unk2...` determination values;
* when not using the GUID field, `true` when using the string field
* @param unk2a the global unique identifier of the entity inflicting the damage * @param unk2a the global unique identifier of the entity inflicting the damage
* @param unk2b if no global unique identifier (above), the name of the entity inflicting the damage * @param unk2b if no global unique identifier (above), the name of the entity inflicting the damage
* @param unk2c if no global unique identifier (above), the object type of the entity inflicting the damage * @param unk2c if no global unique identifier (above), the object type of the entity inflicting the damage
* @param unk3 if no global unique identifier (below), the alternate identification for the entity * @param unk3 a modifier that customizes one of the values for the `unk3...` determination values;
* when not using the GUID field, `true` when using the string field
* @param unk3a the global unique identifier of the entity absorbing the damage * @param unk3a the global unique identifier of the entity absorbing the damage
* @param unk3b if no global unique identifier (above), the name of the entity absorbing the damage * @param unk3b if no global unique identifier (above), the name of the entity absorbing the damage
* @param unk3c if no global unique identifier (above), the object type of the entity absorbing the damage * @param unk3c if no global unique identifier (above), the object type of the entity absorbing the damage
@ -25,11 +29,11 @@ import shapeless.{::, HNil}
*/ */
final case class DamageFeedbackMessage( final case class DamageFeedbackMessage(
unk1: Int, unk1: Int,
unk2: Boolean, unk2: Option[Boolean],
unk2a: Option[PlanetSideGUID], unk2a: Option[PlanetSideGUID],
unk2b: Option[String], unk2b: Option[String],
unk2c: Option[Int], unk2c: Option[Int],
unk3: Boolean, unk3: Option[Boolean],
unk3a: Option[PlanetSideGUID], unk3a: Option[PlanetSideGUID],
unk3b: Option[String], unk3b: Option[String],
unk3c: Option[Int], unk3c: Option[Int],
@ -43,9 +47,9 @@ final case class DamageFeedbackMessage(
val unk2aEmpty = unk2a.isEmpty val unk2aEmpty = unk2a.isEmpty
val unk2bEmpty = unk2b.isEmpty val unk2bEmpty = unk2b.isEmpty
val unk2cEmpty = unk2c.isEmpty val unk2cEmpty = unk2c.isEmpty
if (unk2a.nonEmpty) unk2bEmpty && unk2cEmpty if (!unk2aEmpty) unk2bEmpty && unk2cEmpty
else if (unk2b.nonEmpty) unk2 && unk2aEmpty && unk2cEmpty else if (!unk2bEmpty) unk2aEmpty && unk2cEmpty
else unk2aEmpty && !unk2 && unk2bEmpty && unk2c.nonEmpty else unk2aEmpty && unk2bEmpty && !unk2cEmpty
} }
) )
assert( assert(
@ -53,58 +57,104 @@ final case class DamageFeedbackMessage(
val unk3aEmpty = unk3a.isEmpty val unk3aEmpty = unk3a.isEmpty
val unk3bEmpty = unk3b.isEmpty val unk3bEmpty = unk3b.isEmpty
val unk3cEmpty = unk3c.isEmpty val unk3cEmpty = unk3c.isEmpty
if (unk3a.nonEmpty) unk3bEmpty && unk3cEmpty if (!unk3aEmpty) unk3bEmpty && unk3cEmpty
else if (unk3b.nonEmpty) unk3 && unk3aEmpty && unk3cEmpty else if (!unk3bEmpty) unk3aEmpty && unk3cEmpty
else unk3aEmpty && !unk3 && unk3bEmpty && unk3c.nonEmpty else unk3aEmpty && unk3bEmpty && !unk3cEmpty
} }
) )
assert(unk3a.isEmpty == unk3d.nonEmpty) assert(unk3a.isEmpty == unk3d.nonEmpty)
type Packet = DamageFeedbackMessage type Packet = DamageFeedbackMessage
def opcode = GamePacketOpcode.DamageFeedbackMessage def opcode: Type = GamePacketOpcode.DamageFeedbackMessage
def encode = DamageFeedbackMessage.encode(this) def encode: Attempt[BitVector] = DamageFeedbackMessage.encode(this)
} }
object DamageFeedbackMessage extends Marshallable[DamageFeedbackMessage] { object DamageFeedbackMessage extends Marshallable[DamageFeedbackMessage] {
def apply(unk1: Int, def apply(
unk1: Int,
unk2a: Option[PlanetSideGUID],
unk2b: Option[String],
unk2c: Option[Int],
unk3a: Option[PlanetSideGUID],
unk3b: Option[String],
unk3c: Option[Int],
unk3d: Option[Int],
unk4: Int,
unk5: Long
): DamageFeedbackMessage = {
DamageFeedbackMessage(unk1, None, unk2a, unk2b, unk2c, None, unk3a, unk3b, unk3c, unk3d, unk4, unk5, 0)
}
def apply(
unk1: Int,
unk2: PlanetSideGUID, unk2: PlanetSideGUID,
unk3: PlanetSideGUID, unk3: PlanetSideGUID,
unk4: Int, unk4: Int,
unk5: Long): DamageFeedbackMessage = unk5: Long
DamageFeedbackMessage(unk1, true, Some(unk2), None, None, true, Some(unk3), None, None, None, unk4, unk5, 0) ): DamageFeedbackMessage = {
DamageFeedbackMessage(unk1, None, Some(unk2), None, None, None, Some(unk3), None, None, None, unk4, unk5, 0)
}
implicit val codec: Codec[DamageFeedbackMessage] = ( private case class EntryFields(
("unk1" | uint4) :: usesGuid: Boolean,
(bool >>:~ { u2 => usesStr: Boolean,
bool >>:~ { u3 => guidOpt: Option[PlanetSideGUID],
("unk2a" | conditional(u2, PlanetSideGUID.codec)) :: strOpt: Option[String],
(("unk2b" | conditional(!u2 && u3, PacketHelpers.encodedWideStringAligned(6))) >>:~ { u2b => intOpt: Option[Int]
("unk2c" | conditional(!u2 && !u3, uintL(11))) :: )
(bool >>:~ { u5 =>
bool >>:~ { u6 => /**
("unk3a" | conditional(u5, PlanetSideGUID.codec)) :: * na
("unk3b" | conditional( * @param adjustment na;
!u5 && u6, * can not be a negative number
PacketHelpers.encodedWideStringAligned(if (u2b.nonEmpty) 3 else 1) * @return na
)) :: */
("unk3c" | conditional(!u5 && !u6, uintL(11))) :: private def entityFieldFormatCodec(adjustment: Int): Codec[EntryFields] = {
("unk3d" | conditional(!u5, uint2)) :: ((bool :: bool) >>:~ { case usesGuid :: usesString :: HNil =>
("unk4" | uint(3)) :: conditional(usesGuid, PlanetSideGUID.codec) ::
("unk5" | uint32L) :: conditional(!usesGuid && usesString, PacketHelpers.encodedWideStringAligned(adjustment)) ::
("unk6" | uint2) conditional(!usesGuid && !usesString, uintL(bits = 11))
} }).xmap[EntryFields](
})
})
}
})
).xmap[DamageFeedbackMessage](
{ {
case u1 :: _ :: u2 :: u2a :: u2b :: u2c :: _ :: u3 :: u3a :: u3b :: u3c :: u3d :: u4 :: u5 :: u6 :: HNil => case (a :: b :: HNil) :: c :: d :: e :: HNil => EntryFields(a, b, c, d, e)
DamageFeedbackMessage(u1, u2, u2a, u2b, u2c, u3, u3a, u3b, u3c, u3d, u4, u5, u6)
}, },
{ {
case DamageFeedbackMessage(u1, u2, u2a, u2b, u2c, u3, u3a, u3b, u3c, u3d, u4, u5, u6) => case EntryFields(a, b, c, d, e) => (a :: b :: HNil) :: c :: d :: e :: HNil
u1 :: u2a.nonEmpty :: u2 :: u2a :: u2b :: u2c :: u3a.nonEmpty :: u3 :: u3a :: u3b :: u3c :: u3d :: u4 :: u5 :: u6 :: HNil }
)
}
implicit val codec: Codec[DamageFeedbackMessage] = (
("unk1" | uint4) :: {
entityFieldFormatCodec(adjustment = 4) >>:~ { fieldsA =>
val offset = if (fieldsA.usesGuid) { 0 } else if (fieldsA.usesStr) { 6 } else { 5 }
entityFieldFormatCodec(offset) >>:~ { fieldsB =>
("unk3d" | conditional(!fieldsB.usesGuid, uint2)) ::
("unk4" | uint(bits = 3)) ::
("unk5" | uint32L) ::
("unk6" | uint2)
}
}
}).xmap[DamageFeedbackMessage](
{
case u1 :: EntryFields(_, u2, u2a, u2b, u2c) :: EntryFields(_, u3, u3a, u3b, u3c) :: u3d :: u4 :: u5 :: u6 :: HNil =>
val u2False = if (u2a.nonEmpty && !u2) { Some(false) } else { None }
val u3False = if (u3a.nonEmpty && !u3) { Some(false) } else { None }
DamageFeedbackMessage(u1, u2False, u2a, u2b, u2c, u3False, u3a, u3b, u3c, u3d, u4, u5, u6)
},
{
case DamageFeedbackMessage(u1, u2, u2a, u2b, u2c, u3, u3a, u3b, u3c, u3d, u4, u5, u6) =>
val(u2Boola, u2Boolb) = if (u2a.nonEmpty) {
(true, u2.getOrElse(true))
} else {
(false, u2b.nonEmpty)
}
val(u3Boola, u3Boolb) = if (u3a.nonEmpty) {
(true, u3.getOrElse(true))
} else {
(false, u3b.nonEmpty)
}
u1 :: EntryFields(u2Boola, u2Boolb, u2a, u2b, u2c) :: EntryFields(u3Boola, u3Boolb, u3a, u3b, u3c) :: u3d :: u4 :: u5 :: u6 :: HNil
} }
) )
} }

View file

@ -5,7 +5,8 @@ import net.psforever.objects.serverobject.terminals.implant.ImplantTerminalDefin
case class Implant( case class Implant(
name: String, name: String,
avatarId: Int avatarId: Int,
timer: Int = 0 //seconds to initialize
) { ) {
def toImplantDefinition: ImplantDefinition = { def toImplantDefinition: ImplantDefinition = {
ImplantTerminalDefinition.implants(name) ImplantTerminalDefinition.implants(name)

View file

@ -39,6 +39,10 @@ class AvatarService(zone: Zone) extends Actor {
AvatarEvents.publish( AvatarEvents.publish(
AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.ArmorChanged(suit, subtype)) AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.ArmorChanged(suit, subtype))
) )
case AvatarAction.AvatarImplant(player_guid, action, implantSlot, status) =>
AvatarEvents.publish(
AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.AvatarImplant(action, implantSlot, status))
)
case AvatarAction.ChangeAmmo( case AvatarAction.ChangeAmmo(
player_guid, player_guid,
weapon_guid, weapon_guid,

View file

@ -11,6 +11,7 @@ import net.psforever.objects.serverobject.environment.interaction.common.Watery.
import net.psforever.objects.sourcing.SourceEntry import net.psforever.objects.sourcing.SourceEntry
import net.psforever.objects.zones.Zone import net.psforever.objects.zones.Zone
import net.psforever.packet.PlanetSideGamePacket import net.psforever.packet.PlanetSideGamePacket
import net.psforever.packet.game.ImplantAction
import net.psforever.packet.game.objectcreate.{ConstructorData, ObjectCreateMessageParent} import net.psforever.packet.game.objectcreate.{ConstructorData, ObjectCreateMessageParent}
import net.psforever.types.{ExoSuitType, ExperienceType, PlanetSideEmpire, PlanetSideGUID, TransactionType, Vector3} import net.psforever.types.{ExoSuitType, ExperienceType, PlanetSideEmpire, PlanetSideGUID, TransactionType, Vector3}
@ -27,6 +28,7 @@ object AvatarAction {
sealed trait Action sealed trait Action
final case class ArmorChanged(player_guid: PlanetSideGUID, suit: ExoSuitType.Value, subtype: Int) extends Action final case class ArmorChanged(player_guid: PlanetSideGUID, suit: ExoSuitType.Value, subtype: Int) extends Action
final case class AvatarImplant(player_guid: PlanetSideGUID, action: ImplantAction.Value, implantSlot: Int, status: Int) extends Action
final case class ChangeAmmo( final case class ChangeAmmo(
player_guid: PlanetSideGUID, player_guid: PlanetSideGUID,
weapon_guid: PlanetSideGUID, weapon_guid: PlanetSideGUID,

View file

@ -10,7 +10,7 @@ import net.psforever.objects.serverobject.environment.interaction.common.Watery.
import net.psforever.objects.sourcing.SourceEntry import net.psforever.objects.sourcing.SourceEntry
import net.psforever.packet.PlanetSideGamePacket import net.psforever.packet.PlanetSideGamePacket
import net.psforever.packet.game.objectcreate.ConstructorData import net.psforever.packet.game.objectcreate.ConstructorData
import net.psforever.packet.game.ObjectCreateMessage import net.psforever.packet.game.{ImplantAction, ObjectCreateMessage}
import net.psforever.types.{ExoSuitType, ExperienceType, PlanetSideEmpire, PlanetSideGUID, TransactionType, Vector3} import net.psforever.types.{ExoSuitType, ExperienceType, PlanetSideEmpire, PlanetSideGUID, TransactionType, Vector3}
import net.psforever.services.GenericEventBusMsg import net.psforever.services.GenericEventBusMsg
@ -24,6 +24,7 @@ object AvatarResponse {
sealed trait Response sealed trait Response
final case class ArmorChanged(suit: ExoSuitType.Value, subtype: Int) extends Response final case class ArmorChanged(suit: ExoSuitType.Value, subtype: Int) extends Response
final case class AvatarImplant(action: ImplantAction.Value, implantSlot: Int, status: Int) extends Response
final case class ChangeAmmo( final case class ChangeAmmo(
weapon_guid: PlanetSideGUID, weapon_guid: PlanetSideGUID,
weapon_slot: Int, weapon_slot: Int,

View file

@ -0,0 +1,2 @@
{
}

View file

@ -13,13 +13,11 @@ class DamageFeedbackMessageTest extends Specification {
"decode (string 1)" in { "decode (string 1)" in {
PacketCoding.decodePacket(string).require match { PacketCoding.decodePacket(string).require match {
case DamageFeedbackMessage(unk1, unk2, unk2a, unk2b, unk2c, unk3, unk3a, unk3b, unk3c, unk3d, unk4, unk5, unk6) => case DamageFeedbackMessage(unk1, _, unk2a, unk2b, unk2c, _, unk3a, unk3b, unk3c, unk3d, unk4, unk5, unk6) =>
unk1 mustEqual 3 unk1 mustEqual 3
unk2 mustEqual true
unk2a.contains(PlanetSideGUID(2913)) mustEqual true unk2a.contains(PlanetSideGUID(2913)) mustEqual true
unk2b.isEmpty mustEqual true unk2b.isEmpty mustEqual true
unk2c.isEmpty mustEqual true unk2c.isEmpty mustEqual true
unk3 mustEqual true
unk3a.contains(PlanetSideGUID(2913)) mustEqual true unk3a.contains(PlanetSideGUID(2913)) mustEqual true
unk3b.isEmpty mustEqual true unk3b.isEmpty mustEqual true
unk3c.isEmpty mustEqual true unk3c.isEmpty mustEqual true
@ -34,13 +32,11 @@ class DamageFeedbackMessageTest extends Specification {
"decode (string 2)" in { "decode (string 2)" in {
PacketCoding.decodePacket(string_2).require match { PacketCoding.decodePacket(string_2).require match {
case DamageFeedbackMessage(unk1, unk2, unk2a, unk2b, unk2c, unk3, unk3a, unk3b, unk3c, unk3d, unk4, unk5, unk6) => case DamageFeedbackMessage(unk1, _, unk2a, unk2b, unk2c, _, unk3a, unk3b, unk3c, unk3d, unk4, unk5, unk6) =>
unk1 mustEqual 5 unk1 mustEqual 5
unk2 mustEqual true
unk2a.contains(PlanetSideGUID(2454)) mustEqual true unk2a.contains(PlanetSideGUID(2454)) mustEqual true
unk2b.isEmpty mustEqual true unk2b.isEmpty mustEqual true
unk2c.isEmpty mustEqual true unk2c.isEmpty mustEqual true
unk3 mustEqual false
unk3a.contains(PlanetSideGUID(216)) mustEqual true unk3a.contains(PlanetSideGUID(216)) mustEqual true
unk3b.isEmpty mustEqual true unk3b.isEmpty mustEqual true
unk3c.isEmpty mustEqual true unk3c.isEmpty mustEqual true
@ -56,18 +52,15 @@ class DamageFeedbackMessageTest extends Specification {
"encode (string 1)" in { "encode (string 1)" in {
val msg = DamageFeedbackMessage( val msg = DamageFeedbackMessage(
3, 3,
true,
Some(PlanetSideGUID(2913)), Some(PlanetSideGUID(2913)),
None, None,
None, None,
true,
Some(PlanetSideGUID(2913)), Some(PlanetSideGUID(2913)),
None, None,
None, None,
None, None,
1, 1,
2, 2
0
) )
val pkt = PacketCoding.encodePacket(msg).require.toByteVector val pkt = PacketCoding.encodePacket(msg).require.toByteVector
@ -77,11 +70,11 @@ class DamageFeedbackMessageTest extends Specification {
"encode (string 2)" in { "encode (string 2)" in {
val msg = DamageFeedbackMessage( val msg = DamageFeedbackMessage(
5, 5,
true, None,
Some(PlanetSideGUID(2454)), Some(PlanetSideGUID(2454)),
None, None,
None, None,
false, Some(false),
Some(PlanetSideGUID(216)), Some(PlanetSideGUID(216)),
None, None,
None, None,
@ -99,252 +92,129 @@ class DamageFeedbackMessageTest extends Specification {
//unk2: no parameters //unk2: no parameters
DamageFeedbackMessage( DamageFeedbackMessage(
3, 3,
true,
None, None,
None, None,
None, None,
true,
Some(PlanetSideGUID(2913)), Some(PlanetSideGUID(2913)),
None, None,
None, None,
None, None,
1, 1,
2, 2
0
) must throwA[AssertionError] ) must throwA[AssertionError]
//unk2: two exclusive parameters //unk2: two exclusive parameters
DamageFeedbackMessage( DamageFeedbackMessage(
3, 3,
true,
Some(PlanetSideGUID(2913)), Some(PlanetSideGUID(2913)),
Some("error"), Some("error"),
None, None,
true,
Some(PlanetSideGUID(2913)), Some(PlanetSideGUID(2913)),
None, None,
None, None,
None, None,
1, 1,
2, 2
0
) must throwA[AssertionError] ) must throwA[AssertionError]
DamageFeedbackMessage( DamageFeedbackMessage(
3, 3,
true,
Some(PlanetSideGUID(2913)), Some(PlanetSideGUID(2913)),
None, None,
Some(5), Some(5),
true,
Some(PlanetSideGUID(2913)), Some(PlanetSideGUID(2913)),
None, None,
None, None,
None, None,
1, 1,
2, 2
0
) must throwA[AssertionError] ) must throwA[AssertionError]
DamageFeedbackMessage( DamageFeedbackMessage(
3, 3,
true,
None, None,
Some("error"), Some("error"),
Some(5), Some(5),
true,
Some(PlanetSideGUID(2913)), Some(PlanetSideGUID(2913)),
None, None,
None, None,
None, None,
1, 1,
2, 2
0
) must throwA[AssertionError] ) must throwA[AssertionError]
//unk2: all parameters //unk2: all parameters
DamageFeedbackMessage( DamageFeedbackMessage(
3, 3,
true,
Some(PlanetSideGUID(2913)), Some(PlanetSideGUID(2913)),
Some("error"), Some("error"),
Some(5), Some(5),
true,
Some(PlanetSideGUID(2913)), Some(PlanetSideGUID(2913)),
None, None,
None, None,
None, None,
1, 1,
2, 2
0
) must throwA[AssertionError]
//unk2: mismatched flag for strings
DamageFeedbackMessage(
3,
true,
None,
None,
Some(5),
true,
Some(PlanetSideGUID(2913)),
None,
None,
None,
1,
2,
0
) must throwA[AssertionError]
DamageFeedbackMessage(
3,
false,
None,
Some("error"),
None,
true,
Some(PlanetSideGUID(2913)),
None,
None,
None,
1,
2,
0
) must throwA[AssertionError] ) must throwA[AssertionError]
//unk3: no parameters //unk3: no parameters
DamageFeedbackMessage( DamageFeedbackMessage(
3, 3,
true,
Some(PlanetSideGUID(2913)), Some(PlanetSideGUID(2913)),
None, None,
None, None,
true,
None, None,
None, None,
None, None,
None, None,
1, 1,
2, 2
0
) must throwA[AssertionError] ) must throwA[AssertionError]
//unk3: two exclusive parameters //unk3: two exclusive parameters
DamageFeedbackMessage( DamageFeedbackMessage(
3, 3,
true,
Some(PlanetSideGUID(2913)), Some(PlanetSideGUID(2913)),
None, None,
None, None,
true,
Some(PlanetSideGUID(2913)), Some(PlanetSideGUID(2913)),
Some("error"), Some("error"),
None, None,
None, None,
1, 1,
2, 2
0
) must throwA[AssertionError] ) must throwA[AssertionError]
DamageFeedbackMessage( DamageFeedbackMessage(
3, 3,
true,
Some(PlanetSideGUID(2913)), Some(PlanetSideGUID(2913)),
None, None,
None, None,
true,
Some(PlanetSideGUID(2913)), Some(PlanetSideGUID(2913)),
None, None,
Some(5), Some(5),
None, None,
1, 1,
2, 2
0
) must throwA[AssertionError] ) must throwA[AssertionError]
DamageFeedbackMessage( DamageFeedbackMessage(
3, 3,
true,
Some(PlanetSideGUID(2913)), Some(PlanetSideGUID(2913)),
None, None,
None, None,
true,
None, None,
Some("error"), Some("error"),
Some(5), Some(5),
Some(1), Some(1),
1, 1,
2, 2
0
) must throwA[AssertionError] ) must throwA[AssertionError]
//unk3: all parameters //unk3: all parameters
DamageFeedbackMessage( DamageFeedbackMessage(
3, 3,
true,
Some(PlanetSideGUID(2913)), Some(PlanetSideGUID(2913)),
None, None,
None, None,
true,
Some(PlanetSideGUID(2913)), Some(PlanetSideGUID(2913)),
Some("error"), Some("error"),
Some(5), Some(5),
None, None,
1, 1,
2, 2
0
) must throwA[AssertionError]
//unk3: mismatched fields
DamageFeedbackMessage(
3,
true,
Some(PlanetSideGUID(2913)),
None,
None,
true,
Some(PlanetSideGUID(2913)),
None,
None,
Some(5),
1,
2,
0
) must throwA[AssertionError]
DamageFeedbackMessage(
3,
true,
Some(PlanetSideGUID(2913)),
None,
None,
true,
None,
Some("Error"),
None,
None,
1,
2,
0
) must throwA[AssertionError]
//unk3: mismatched flag for strings
DamageFeedbackMessage(
3,
true,
Some(PlanetSideGUID(2913)),
None,
None,
true,
None,
None,
Some(5),
None,
1,
2,
0
) must throwA[AssertionError]
DamageFeedbackMessage(
3,
true,
Some(PlanetSideGUID(2913)),
None,
None,
false,
None,
Some("error"),
None,
None,
1,
2,
0
) must throwA[AssertionError] ) must throwA[AssertionError]
} }
} }

View file

@ -46,9 +46,17 @@ class AegisShieldGeneratorDataTest extends Specification {
val obj = AegisShieldGeneratorData( val obj = AegisShieldGeneratorData(
CommonFieldDataWithPlacement( CommonFieldDataWithPlacement(
PlacementData(Vector3(3571.2266f, 3278.0938f, 114.0f), Vector3(0, 0, 90)), PlacementData(Vector3(3571.2266f, 3278.0938f, 114.0f), Vector3(0, 0, 90)),
CommonFieldData(
PlanetSideEmpire.VS, PlanetSideEmpire.VS,
2, bops = false,
alternate = false,
v1 = true,
v2 = None,
jammered = false,
v4 = None,
v5 = None,
PlanetSideGUID(2366) PlanetSideGUID(2366)
)
), ),
255 255
) )

View file

@ -287,7 +287,7 @@ class WeaponDataTest extends Specification {
ObjectClass.energy_cell, ObjectClass.energy_cell,
PlanetSideGUID(3548), PlanetSideGUID(3548),
0, 0,
CommonFieldData(PlanetSideEmpire.NEUTRAL, 2)(false) CommonFieldData(PlanetSideEmpire.NEUTRAL, false, false, true, None, false, Some(false), None, PlanetSideGUID(0))
) )
) )
) )
@ -311,13 +311,13 @@ class WeaponDataTest extends Specification {
ObjectClass.bullet_9mm, ObjectClass.bullet_9mm,
PlanetSideGUID(3918), PlanetSideGUID(3918),
0, 0,
CommonFieldData(PlanetSideEmpire.NEUTRAL, 2)(false) CommonFieldData(PlanetSideEmpire.NEUTRAL, false, false, true, None, false, Some(false), None, PlanetSideGUID(0))
), ),
AmmoBoxData( AmmoBoxData(
ObjectClass.rocket, ObjectClass.rocket,
PlanetSideGUID(3941), PlanetSideGUID(3941),
1, 1,
CommonFieldData(PlanetSideEmpire.NEUTRAL, 2)(false) CommonFieldData(PlanetSideEmpire.NEUTRAL, false, false, true, None, false, Some(false), None, PlanetSideGUID(0))
) )
) )
) )
@ -337,7 +337,7 @@ class WeaponDataTest extends Specification {
WeaponData( WeaponData(
CommonFieldData(PlanetSideEmpire.VS, false, false, false, None, false, None, None, PlanetSideGUID(0)), CommonFieldData(PlanetSideEmpire.VS, false, false, false, None, false, None, None, PlanetSideGUID(0)),
0, 0,
List(InternalSlot(ObjectClass.energy_cell, PlanetSideGUID(3268), 0, CommonFieldData()(false))) List(InternalSlot(ObjectClass.energy_cell, PlanetSideGUID(3268), 0, CommonFieldData(PlanetSideEmpire.NEUTRAL, 0)(false)))
) )
) )
val msg = ObjectCreateMessage(ObjectClass.lasher, PlanetSideGUID(3074), obj) val msg = ObjectCreateMessage(ObjectClass.lasher, PlanetSideGUID(3074), obj)
@ -352,8 +352,8 @@ class WeaponDataTest extends Specification {
CommonFieldData(PlanetSideEmpire.NC, false, false, false, None, false, None, None, PlanetSideGUID(0)), CommonFieldData(PlanetSideEmpire.NC, false, false, false, None, false, None, None, PlanetSideGUID(0)),
0, 0,
List( List(
AmmoBoxData(ObjectClass.bullet_9mm, PlanetSideGUID(3528), 0, CommonFieldData()(false)), AmmoBoxData(ObjectClass.bullet_9mm, PlanetSideGUID(3528), 0, CommonFieldData(PlanetSideEmpire.NEUTRAL, 0)(false)),
AmmoBoxData(ObjectClass.rocket, PlanetSideGUID(3031), 1, CommonFieldData()(false)) AmmoBoxData(ObjectClass.rocket, PlanetSideGUID(3031), 1, CommonFieldData(PlanetSideEmpire.NEUTRAL, 0)(false))
) )
) )
) )

View file

@ -1655,7 +1655,10 @@ class DetailedCharacterDataTest extends Specification {
List(InternalSlot(ObjectClass.melee_ammo, PlanetSideGUID(81), 0, DetailedAmmoBoxData(8, 1))) List(InternalSlot(ObjectClass.melee_ammo, PlanetSideGUID(81), 0, DetailedAmmoBoxData(8, 1)))
) )
), ),
InventoryItemData(ObjectClass.locker_container, PlanetSideGUID(82), 5, DetailedLockerContainerData(8)), InventoryItemData(ObjectClass.locker_container, PlanetSideGUID(82), 5, DetailedLockerContainerData(
CommonFieldData(PlanetSideEmpire.NEUTRAL, false, false, true, None, false, None, None, PlanetSideGUID(0)),
None
)),
InventoryItemData(ObjectClass.bullet_9mm, PlanetSideGUID(83), 6, DetailedAmmoBoxData(8, 50)), InventoryItemData(ObjectClass.bullet_9mm, PlanetSideGUID(83), 6, DetailedAmmoBoxData(8, 50)),
InventoryItemData(ObjectClass.bullet_9mm, PlanetSideGUID(84), 9, DetailedAmmoBoxData(8, 50)), InventoryItemData(ObjectClass.bullet_9mm, PlanetSideGUID(84), 9, DetailedAmmoBoxData(8, 50)),
InventoryItemData(ObjectClass.bullet_9mm, PlanetSideGUID(85), 12, DetailedAmmoBoxData(8, 50)), InventoryItemData(ObjectClass.bullet_9mm, PlanetSideGUID(85), 12, DetailedAmmoBoxData(8, 50)),
@ -1837,7 +1840,10 @@ class DetailedCharacterDataTest extends Specification {
List(InternalSlot(ObjectClass.melee_ammo, PlanetSideGUID(81), 0, DetailedAmmoBoxData(8, 1))) List(InternalSlot(ObjectClass.melee_ammo, PlanetSideGUID(81), 0, DetailedAmmoBoxData(8, 1)))
) )
), ),
InventoryItemData(ObjectClass.locker_container, PlanetSideGUID(82), 5, DetailedLockerContainerData(8)), InventoryItemData(ObjectClass.locker_container, PlanetSideGUID(82), 5, DetailedLockerContainerData(
CommonFieldData(PlanetSideEmpire.NEUTRAL, false, false, true, None, false, None, None, PlanetSideGUID(0)),
None
)),
InventoryItemData(ObjectClass.bullet_9mm, PlanetSideGUID(83), 6, DetailedAmmoBoxData(8, 50)), InventoryItemData(ObjectClass.bullet_9mm, PlanetSideGUID(83), 6, DetailedAmmoBoxData(8, 50)),
InventoryItemData(ObjectClass.bullet_9mm, PlanetSideGUID(84), 9, DetailedAmmoBoxData(8, 50)), InventoryItemData(ObjectClass.bullet_9mm, PlanetSideGUID(84), 9, DetailedAmmoBoxData(8, 50)),
InventoryItemData(ObjectClass.bullet_9mm, PlanetSideGUID(85), 12, DetailedAmmoBoxData(8, 50)), InventoryItemData(ObjectClass.bullet_9mm, PlanetSideGUID(85), 12, DetailedAmmoBoxData(8, 50)),

View file

@ -275,7 +275,7 @@ class BattleframeRoboticsTest extends Specification {
CommonFieldData(PlanetSideEmpire.NEUTRAL, false, false, true, None, false, Some(false), None, PlanetSideGUID(0)), CommonFieldData(PlanetSideEmpire.NEUTRAL, false, false, true, None, false, Some(false), None, PlanetSideGUID(0)),
0, 0,
List( List(
InternalSlot(ObjectClass.aphelion_ppa_ammo, PlanetSideGUID(340), 0, CommonFieldData(PlanetSideEmpire.NEUTRAL, 2)(false)) InternalSlot(ObjectClass.aphelion_ppa_ammo, PlanetSideGUID(340), 0, CommonFieldData(PlanetSideEmpire.NEUTRAL, false, false, true, None, false, Some(false), None, PlanetSideGUID(0)))
) )
) )
), ),
@ -284,7 +284,7 @@ class BattleframeRoboticsTest extends Specification {
CommonFieldData(PlanetSideEmpire.NEUTRAL, false, false, true, None, false, Some(false), None, PlanetSideGUID(0)), CommonFieldData(PlanetSideEmpire.NEUTRAL, false, false, true, None, false, Some(false), None, PlanetSideGUID(0)),
0, 0,
List( List(
InternalSlot(ObjectClass.aphelion_ppa_ammo, PlanetSideGUID(342), 0, CommonFieldData(PlanetSideEmpire.NEUTRAL, 2)(false)) InternalSlot(ObjectClass.aphelion_ppa_ammo, PlanetSideGUID(342), 0, CommonFieldData(PlanetSideEmpire.NEUTRAL, false, false, true, None, false, Some(false), None, PlanetSideGUID(0)))
) )
) )
), ),
@ -293,7 +293,7 @@ class BattleframeRoboticsTest extends Specification {
CommonFieldData(PlanetSideEmpire.NEUTRAL, false, false, true, None, false, Some(false), None, PlanetSideGUID(0)), CommonFieldData(PlanetSideEmpire.NEUTRAL, false, false, true, None, false, Some(false), None, PlanetSideGUID(0)),
0, 0,
List( List(
InternalSlot(ObjectClass.aphelion_plasma_rocket_ammo, PlanetSideGUID(359), 0, CommonFieldData(PlanetSideEmpire.NEUTRAL, 2)(false)) InternalSlot(ObjectClass.aphelion_plasma_rocket_ammo, PlanetSideGUID(359), 0, CommonFieldData(PlanetSideEmpire.NEUTRAL, false, false, true, None, false, Some(false), None, PlanetSideGUID(0)))
) )
) )
) )
@ -325,7 +325,7 @@ class BattleframeRoboticsTest extends Specification {
CommonFieldData(PlanetSideEmpire.NEUTRAL, false, false, true, None, false, Some(false), None, PlanetSideGUID(0)), CommonFieldData(PlanetSideEmpire.NEUTRAL, false, false, true, None, false, Some(false), None, PlanetSideGUID(0)),
0, 0,
List( List(
InternalSlot(ObjectClass.aphelion_ppa_ammo, PlanetSideGUID(371), 0, CommonFieldData(PlanetSideEmpire.NEUTRAL, 2)(false)) InternalSlot(ObjectClass.aphelion_ppa_ammo, PlanetSideGUID(371), 0, CommonFieldData(PlanetSideEmpire.NEUTRAL, false, false, true, None, false, Some(false), None, PlanetSideGUID(0)))
) )
) )
), ),
@ -334,7 +334,7 @@ class BattleframeRoboticsTest extends Specification {
CommonFieldData(PlanetSideEmpire.NEUTRAL, false, false, true, None, false, Some(false), None, PlanetSideGUID(0)), CommonFieldData(PlanetSideEmpire.NEUTRAL, false, false, true, None, false, Some(false), None, PlanetSideGUID(0)),
0, 0,
List( List(
InternalSlot(ObjectClass.aphelion_ppa_ammo, PlanetSideGUID(376), 0, CommonFieldData(PlanetSideEmpire.NEUTRAL, 2)(false)) InternalSlot(ObjectClass.aphelion_ppa_ammo, PlanetSideGUID(376), 0, CommonFieldData(PlanetSideEmpire.NEUTRAL, false, false, true, None, false, Some(false), None, PlanetSideGUID(0)))
) )
) )
) )

View file

@ -22,7 +22,7 @@ class NonstandardVehiclesTest extends Specification {
guid mustEqual PlanetSideGUID(3595) guid mustEqual PlanetSideGUID(3595)
parent.isDefined mustEqual false parent.isDefined mustEqual false
data match { data match {
case DroppodData(basic, health, burn, unk) => case DroppodData(basic, health, burn, _) =>
basic.pos.coord mustEqual Vector3(5108.0f, 6164.0f, 1023.9844f) basic.pos.coord mustEqual Vector3(5108.0f, 6164.0f, 1023.9844f)
basic.pos.orient mustEqual Vector3.z(90.0f) basic.pos.orient mustEqual Vector3.z(90.0f)
@ -88,7 +88,17 @@ class NonstandardVehiclesTest extends Specification {
val obj = DroppodData( val obj = DroppodData(
CommonFieldDataWithPlacement( CommonFieldDataWithPlacement(
PlacementData(5108.0f, 6164.0f, 1023.9844f, 0f, 0f, 90.0f), PlacementData(5108.0f, 6164.0f, 1023.9844f, 0f, 0f, 90.0f),
CommonFieldData(PlanetSideEmpire.VS, 2) CommonFieldData(
PlanetSideEmpire.VS,
bops = false,
alternate = false,
v1 = true,
v2 = None,
jammered = false,
v4 = None,
v5 = None,
PlanetSideGUID(0)
)
) )
) )
val msg = ObjectCreateMessage(ObjectClass.droppod, PlanetSideGUID(3595), obj) val msg = ObjectCreateMessage(ObjectClass.droppod, PlanetSideGUID(3595), obj)

View file

@ -325,14 +325,14 @@ class NormalVehiclesTest extends Specification {
PlanetSideGUID(400), PlanetSideGUID(400),
1, 1,
WeaponData( WeaponData(
CommonFieldData(PlanetSideEmpire.NEUTRAL, 2), CommonFieldData(PlanetSideEmpire.NEUTRAL, false, false, true, None, false, None, None, PlanetSideGUID(0)),
0, 0,
List( List(
InternalSlot( InternalSlot(
ObjectClass.hellfire_ammo, ObjectClass.hellfire_ammo,
PlanetSideGUID(432), PlanetSideGUID(432),
0, 0,
CommonFieldData(PlanetSideEmpire.NEUTRAL, 2)(false) CommonFieldData(PlanetSideEmpire.NEUTRAL, false, false, true, None, false, Some(false), None, PlanetSideGUID(0))
) )
) )
) )
@ -368,11 +368,11 @@ class NormalVehiclesTest extends Specification {
PlanetSideGUID(91), PlanetSideGUID(91),
1, 1,
WeaponData( WeaponData(
CommonFieldData(PlanetSideEmpire.VS, 2), CommonFieldData(PlanetSideEmpire.VS, false, false, true, None, false, None, None, PlanetSideGUID(0)),
0, 0,
List( List(
InternalSlot(ObjectClass.bullet_75mm, PlanetSideGUID(92), 0, CommonFieldData()(false)), InternalSlot(ObjectClass.bullet_75mm, PlanetSideGUID(92), 0, CommonFieldData(PlanetSideEmpire.NEUTRAL, false, false, false, None, false, Some(false), None, PlanetSideGUID(0))),
InternalSlot(ObjectClass.bullet_25mm, PlanetSideGUID(93), 1, CommonFieldData()(false)) InternalSlot(ObjectClass.bullet_25mm, PlanetSideGUID(93), 1, CommonFieldData(PlanetSideEmpire.NEUTRAL, false, false, false, None, false, Some(false), None, PlanetSideGUID(0)))
) )
) )
) )
@ -407,14 +407,14 @@ class NormalVehiclesTest extends Specification {
PlanetSideGUID(383), PlanetSideGUID(383),
5, 5,
WeaponData( WeaponData(
CommonFieldData(PlanetSideEmpire.NEUTRAL, 2), CommonFieldData(PlanetSideEmpire.NEUTRAL, false, false, true, None, false, None, None, PlanetSideGUID(0)),
0, 0,
List( List(
InternalSlot( InternalSlot(
ObjectClass.bullet_20mm, ObjectClass.bullet_20mm,
PlanetSideGUID(420), PlanetSideGUID(420),
0, 0,
CommonFieldData(PlanetSideEmpire.NEUTRAL, 2)(false) CommonFieldData(PlanetSideEmpire.NEUTRAL, false, false, true, None, false, Some(false), None, PlanetSideGUID(0))
) )
) )
) )
@ -424,14 +424,14 @@ class NormalVehiclesTest extends Specification {
PlanetSideGUID(556), PlanetSideGUID(556),
6, 6,
WeaponData( WeaponData(
CommonFieldData(PlanetSideEmpire.NEUTRAL, 2), CommonFieldData(PlanetSideEmpire.NEUTRAL, false, false, true, None, false, None, None, PlanetSideGUID(0)),
0, 0,
List( List(
InternalSlot( InternalSlot(
ObjectClass.bullet_20mm, ObjectClass.bullet_20mm,
PlanetSideGUID(575), PlanetSideGUID(575),
0, 0,
CommonFieldData(PlanetSideEmpire.NEUTRAL, 2)(false) CommonFieldData(PlanetSideEmpire.NEUTRAL, false, false, true, None, false, Some(false), None, PlanetSideGUID(0))
) )
) )
) )

View file

@ -140,13 +140,13 @@ class VariantVehiclesTest extends Specification {
ObjectClass.ancient_ammo_vehicle, ObjectClass.ancient_ammo_vehicle,
PlanetSideGUID(366), PlanetSideGUID(366),
0, 0,
CommonFieldData(PlanetSideEmpire.NEUTRAL, 2)(false) CommonFieldData(PlanetSideEmpire.NEUTRAL, false, false, true, None, false, Some(false), None, PlanetSideGUID(0))
), ),
InternalSlot( InternalSlot(
ObjectClass.ancient_ammo_vehicle, ObjectClass.ancient_ammo_vehicle,
PlanetSideGUID(385), PlanetSideGUID(385),
1, 1,
CommonFieldData(PlanetSideEmpire.NEUTRAL, 2)(false) CommonFieldData(PlanetSideEmpire.NEUTRAL, false, false, true, None, false, Some(false), None, PlanetSideGUID(0))
) )
) )
) )

View file

@ -371,7 +371,7 @@ class ConverterTest extends Specification {
PlanetSideEmpire.TR, PlanetSideEmpire.TR,
false, false,
false, false,
false, true,
None, None,
false, false,
Some(true), Some(true),
@ -547,8 +547,17 @@ class ConverterTest extends Specification {
pkt mustEqual AegisShieldGeneratorData( pkt mustEqual AegisShieldGeneratorData(
CommonFieldDataWithPlacement( CommonFieldDataWithPlacement(
PlacementData(Vector3.Zero, Vector3.Zero), PlacementData(Vector3.Zero, Vector3.Zero),
CommonFieldData(
PlanetSideEmpire.TR, PlanetSideEmpire.TR,
0 bops = false,
alternate = false,
v1 = true,
v2 = None,
jammered = false,
v4 = None,
v5 = None,
PlanetSideGUID(0)
)
), ),
255 255
) )
@ -742,7 +751,7 @@ class ConverterTest extends Specification {
obj.Definition.Packet.DetailedConstructorData(obj) match { obj.Definition.Packet.DetailedConstructorData(obj) match {
case Success(pkt) => case Success(pkt) =>
pkt mustEqual DetailedLockerContainerData( pkt mustEqual DetailedLockerContainerData(
CommonFieldData(PlanetSideEmpire.NEUTRAL, false, false, true, None, false, None, None, PlanetSideGUID(0)), CommonFieldData(PlanetSideEmpire.NEUTRAL, false, false, false, None, false, None, None, PlanetSideGUID(0)),
None None
) )
case _ => case _ =>

File diff suppressed because it is too large Load diff

View file

@ -9,7 +9,7 @@ import net.psforever.objects.ballistics._
import net.psforever.objects.ce.{Deployable, DeployedItem} import net.psforever.objects.ce.{Deployable, DeployedItem}
import net.psforever.objects.guid.NumberPoolHub import net.psforever.objects.guid.NumberPoolHub
import net.psforever.objects.guid.source.MaxNumberSource import net.psforever.objects.guid.source.MaxNumberSource
import net.psforever.objects.serverobject.mount.{MountInfo, Mountable} import net.psforever.objects.serverobject.mount.{MountInfo, Mountable, SeatDefinition}
import net.psforever.objects.vital.Vitality import net.psforever.objects.vital.Vitality
import net.psforever.objects.zones.{Zone, ZoneDeployableActor, ZoneMap} import net.psforever.objects.zones.{Zone, ZoneDeployableActor, ZoneMap}
import net.psforever.objects.{TurretDeployable, _} import net.psforever.objects.{TurretDeployable, _}
@ -25,7 +25,6 @@ import net.psforever.objects.vital.interaction.DamageInteraction
import net.psforever.objects.vital.projectile.ProjectileReason import net.psforever.objects.vital.projectile.ProjectileReason
import akka.actor.typed.scaladsl.adapter._ import akka.actor.typed.scaladsl.adapter._
import net.psforever.objects.sourcing.{PlayerSource, SourceEntry} import net.psforever.objects.sourcing.{PlayerSource, SourceEntry}
import net.psforever.services.local.LocalAction.DeployableMapIcon
import scala.collection.mutable import scala.collection.mutable
import scala.concurrent.duration._ import scala.concurrent.duration._
@ -39,12 +38,13 @@ class DeployableTest extends Specification {
obj.OwnerGuid.contains(PlanetSideGUID(10)) mustEqual true obj.OwnerGuid.contains(PlanetSideGUID(10)) mustEqual true
} }
"know its owner by GUID" in { "know its owner by name" in {
// val obj = new ExplosiveDeployable(GlobalDefinitions.he_mine) val obj = new ExplosiveDeployable(GlobalDefinitions.he_mine)
// obj.OwnerName.isEmpty mustEqual true val owner = Player(Avatar(1, "TestCharacter", PlanetSideEmpire.TR, CharacterSex.Male, 1, CharacterVoice.Mute))
// obj.OwnerName = "TestCharacter" owner.GUID = PlanetSideGUID(1)
// obj.OwnerName.contains("TestCharacter") mustEqual true owner.Spawn()
ko obj.AssignOwnership(owner)
obj.OwnerName.contains("TestCharacter") mustEqual true
} }
"know its faction allegiance" in { "know its faction allegiance" in {
@ -449,17 +449,16 @@ class ExplosiveDeployableJammerExplodeTest extends ActorTest {
assert(!h_mine.Destroyed) assert(!h_mine.Destroyed)
h_mine.Actor ! Vitality.Damage(applyDamageToH) h_mine.Actor ! Vitality.Damage(applyDamageToH)
val eventMsgs = eventsProbe.receiveN(4, 200 milliseconds) val p1Msgs = player1Probe.receiveN(1, 5000 milliseconds)
val p1Msgs = player1Probe.receiveN(1, 200 milliseconds) val eventMsgs = eventsProbe.receiveN(3, 5000 milliseconds)
val p2Msgs = player2Probe.receiveN(1, 200 milliseconds)
eventMsgs.head match { eventMsgs.head match {
case Zone.HotSpot.Conflict(target, attacker, _) case Zone.HotSpot.Conflict(target, attacker, _)
if (target.Definition eq h_mine.Definition) && (attacker eq pSource) => ; if (target.Definition eq h_mine.Definition) && (attacker eq pSource) => ()
case _ => assert(false, "") case _ => assert(false, "")
} }
eventMsgs(1) match { eventMsgs(1) match {
case LocalServiceMessage("test", LocalAction.Detonate(PlanetSideGUID(2), target)) case LocalServiceMessage("test", LocalAction.Detonate(PlanetSideGUID(2), target))
if target eq h_mine => ; if target eq h_mine => ()
case _ => assert(false, "") case _ => assert(false, "")
} }
eventMsgs(2) match { eventMsgs(2) match {
@ -473,19 +472,8 @@ class ExplosiveDeployableJammerExplodeTest extends ActorTest {
) => ; ) => ;
case _ => assert(false, "") case _ => assert(false, "")
} }
eventMsgs(3) match {
case AvatarServiceMessage(
"test",
AvatarAction.Destroy(PlanetSideGUID(2), PlanetSideGUID(3), Service.defaultPlayerGUID, Vector3.Zero)
) => ;
case _ => assert(false, "")
}
p1Msgs.head match { p1Msgs.head match {
case Vitality.Damage(_) => ; case Vitality.Damage(_) => ()
case _ => assert(false, "")
}
p2Msgs.head match {
case Player.LoseDeployable(_) => ;
case _ => assert(false, "") case _ => assert(false, "")
} }
assert(h_mine.Destroyed) assert(h_mine.Destroyed)
@ -529,8 +517,7 @@ class ExplosiveDeployableDestructionTest extends ActorTest {
guid.register(player2, 4) guid.register(player2, 4)
guid.register(weapon, 5) guid.register(weapon, 5)
h_mine.Zone = zone h_mine.Zone = zone
h_mine.OwnerGuid = player2 h_mine.AssignOwnership(player2)
//h_mine.OwnerName = player2.Name
h_mine.Faction = PlanetSideEmpire.NC h_mine.Faction = PlanetSideEmpire.NC
h_mine.Actor = system.actorOf(Props(classOf[MineDeployableControl], h_mine), "h-mine-control") h_mine.Actor = system.actorOf(Props(classOf[MineDeployableControl], h_mine), "h-mine-control")
@ -748,6 +735,7 @@ class TurretControlBetrayalMountTest extends ActorTest {
val obj = new TurretDeployable( val obj = new TurretDeployable(
new TurretDeployableDefinition(685) { new TurretDeployableDefinition(685) {
MountPoints += 1 -> MountInfo(0, Vector3.Zero) MountPoints += 1 -> MountInfo(0, Vector3.Zero)
Seats += 0 -> new SeatDefinition()
FactionLocked = false FactionLocked = false
} //required (defaults to true) } //required (defaults to true)
) { ) {

View file

@ -102,26 +102,32 @@ class FacilityTurretControl1Test extends ActorTest {
} }
class FacilityTurretControl2Test extends ActorTest { class FacilityTurretControl2Test extends ActorTest {
val player = Player(Avatar(0, "", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)) //todo why does the terminal actor terminate when the building faction is set to a different value?
val obj = FacilityTurret(GlobalDefinitions.manned_turret) val zone = new Zone("test", new ZoneMap("test"), 0) {
obj.GUID = PlanetSideGUID(1)
obj.Zone = new Zone("test", new ZoneMap("test"), 0) {
override def SetupNumberPools() = {} override def SetupNumberPools() = {}
this.actor = new TestProbe(system).ref.toTyped[ZoneActor.Command] this.actor = new TestProbe(system).ref.toTyped[ZoneActor.Command]
} }
val player = Player(Avatar(0, "", PlanetSideEmpire.NEUTRAL, CharacterSex.Male, 0, CharacterVoice.Mute))
player.Spawn()
player.Zone = zone
player.GUID = PlanetSideGUID(2)
val obj = FacilityTurret(GlobalDefinitions.manned_turret)
obj.GUID = PlanetSideGUID(1)
obj.Zone = zone
obj.Actor = system.actorOf(Props(classOf[FacilityTurretControl], obj), "turret-control") obj.Actor = system.actorOf(Props(classOf[FacilityTurretControl], obj), "turret-control")
val bldg = Building("Building", guid = 0, map_id = 0, Zone.Nowhere, StructureType.Building) val bldg = Building("Building", guid = 0, map_id = 0, zone, StructureType.Building)
bldg.Amenities = obj bldg.Amenities = obj
bldg.Faction = PlanetSideEmpire.TR bldg.Zone = zone
//bldg.Faction = PlanetSideEmpire.TR
val resultProbe = TestProbe()
"FacilityTurretControl" should { "FacilityTurretControl" should {
"mount on faction affiliation when FactionLock is true" in { "mount on faction affiliation when FactionLock is true" in {
assert(player.Faction == PlanetSideEmpire.TR) //assert(player.Faction == obj.Faction)
assert(obj.Faction == PlanetSideEmpire.TR)
assert(obj.Definition.FactionLocked) assert(obj.Definition.FactionLocked)
obj.Actor ! Mountable.TryMount(player, 1) obj.Actor.tell(Mountable.TryMount(player, 1), resultProbe.ref)
val reply = receiveOne(300 milliseconds) val reply = resultProbe.receiveOne(5000 milliseconds)
reply match { reply match {
case msg: Mountable.MountMessages => case msg: Mountable.MountMessages =>
assert(msg.response.isInstanceOf[Mountable.CanMount]) assert(msg.response.isInstanceOf[Mountable.CanMount])
@ -133,12 +139,22 @@ class FacilityTurretControl2Test extends ActorTest {
} }
class FacilityTurretControl3Test extends ActorTest { class FacilityTurretControl3Test extends ActorTest {
val zone = new Zone("test", new ZoneMap("test"), 0) {
override def SetupNumberPools() = {}
this.actor = new TestProbe(system).ref.toTyped[ZoneActor.Command]
}
val player = Player(Avatar(0, "", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)) val player = Player(Avatar(0, "", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute))
player.Spawn()
player.Zone = zone
player.GUID = PlanetSideGUID(1)
val obj = FacilityTurret(GlobalDefinitions.manned_turret) val obj = FacilityTurret(GlobalDefinitions.manned_turret)
obj.GUID = PlanetSideGUID(1) obj.GUID = PlanetSideGUID(2)
obj.Zone = zone
obj.Actor = system.actorOf(Props(classOf[FacilityTurretControl], obj), "turret-control") obj.Actor = system.actorOf(Props(classOf[FacilityTurretControl], obj), "turret-control")
val bldg = Building("Building", guid = 0, map_id = 0, Zone.Nowhere, StructureType.Building) val bldg = Building("Building", guid = 0, map_id = 0, zone, StructureType.Building)
bldg.Amenities = obj bldg.Amenities = obj
bldg.Zone = zone
val resultProbe = TestProbe()
"FacilityTurretControl" should { "FacilityTurretControl" should {
"block seating on mismatched faction affiliation when FactionLock is true" in { "block seating on mismatched faction affiliation when FactionLock is true" in {
@ -146,8 +162,8 @@ class FacilityTurretControl3Test extends ActorTest {
assert(obj.Faction == PlanetSideEmpire.NEUTRAL) assert(obj.Faction == PlanetSideEmpire.NEUTRAL)
assert(obj.Definition.FactionLocked) assert(obj.Definition.FactionLocked)
obj.Actor ! Mountable.TryMount(player, 1) obj.Actor.tell(Mountable.TryMount(player, 1), resultProbe.ref)
val reply = receiveOne(300 milliseconds) val reply = resultProbe.receiveOne(5000 milliseconds)
reply match { reply match {
case msg: Mountable.MountMessages => case msg: Mountable.MountMessages =>
assert(msg.response.isInstanceOf[Mountable.CanNotMount]) assert(msg.response.isInstanceOf[Mountable.CanNotMount])

View file

@ -1,26 +1,32 @@
package objects package objects
import akka.actor.ActorRef
import akka.testkit.TestProbe import akka.testkit.TestProbe
import base.ActorTest import base.ActorTest
import net.psforever.objects.definition.ObjectDefinition import net.psforever.objects.Player
import net.psforever.objects.avatar.Avatar
import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.aura.{Aura, AuraEffectBehavior}
import net.psforever.objects.serverobject.environment._ import net.psforever.objects.serverobject.environment._
import net.psforever.objects.serverobject.environment.interaction.{EscapeFromEnvironment, InteractWithEnvironment, InteractingWithEnvironment} import net.psforever.objects.serverobject.environment.interaction.RespondsToZoneEnvironment
import net.psforever.objects.vital.{Vitality, VitalityDefinition} import net.psforever.objects.vital.Vitality
import net.psforever.objects.zones.{InteractsWithZone, Zone, ZoneMap} import net.psforever.objects.zones.{InteractsWithZone, Zone, ZoneMap}
import net.psforever.types.{PlanetSideEmpire, Vector3} import net.psforever.types.{CharacterSex, CharacterVoice, PlanetSideEmpire, PlanetSideGUID, Vector3}
import scala.concurrent.duration._ import scala.concurrent.duration._
class InteractsWithZoneEnvironmentTest extends ActorTest { class InteractsWithZoneEnvironmentTest extends ActorTest {
val pool1 = Pool(EnvironmentAttribute.Water, DeepSquare(-1, 10, 10, 0, 0)) val pool1: Pool = Pool(EnvironmentAttribute.Water, DeepSquare(5, 10, 10, 0, 0))
val pool2 = Pool(EnvironmentAttribute.Water, DeepSquare(-1, 10, 15, 5, 10)) val pool2: Pool = Pool(EnvironmentAttribute.Water, DeepSquare(5, 10, 15, 5, 10))
val pool3 = Pool(EnvironmentAttribute.Lava, DeepSquare(-1, 15, 10, 10, 5)) val pool3: Pool = Pool(EnvironmentAttribute.Lava, DeepSquare(5, 15, 10, 10, 5))
val testZone = { val zoneEvents: TestProbe = TestProbe()
val testZone: Zone = {
val testMap = new ZoneMap(name = "test-map") { val testMap = new ZoneMap(name = "test-map") {
environment = List(pool1, pool2, pool3) environment = List(pool1, pool2, pool3)
} }
new Zone("test-zone", testMap, zoneNumber = 0) new Zone("test-zone", testMap, zoneNumber = 0) {
override def AvatarEvents: ActorRef = zoneEvents.ref
}
} }
testZone.blockMap.addTo(pool1) testZone.blockMap.addTo(pool1)
testZone.blockMap.addTo(pool2) testZone.blockMap.addTo(pool2)
@ -32,8 +38,8 @@ class InteractsWithZoneEnvironmentTest extends ActorTest {
val obj = InteractsWithZoneEnvironmentTest.testObject() val obj = InteractsWithZoneEnvironmentTest.testObject()
obj.Zone = testZone obj.Zone = testZone
obj.Actor = testProbe.ref obj.Actor = testProbe.ref
obj.Position = Vector3(0,0,50)
assert(obj.Position == Vector3.Zero)
obj.zoneInteractions() obj.zoneInteractions()
testProbe.expectNoMessage(max = 500 milliseconds) testProbe.expectNoMessage(max = 500 milliseconds)
} }
@ -44,12 +50,12 @@ class InteractsWithZoneEnvironmentTest extends ActorTest {
obj.Zone = testZone obj.Zone = testZone
obj.Actor = testProbe.ref obj.Actor = testProbe.ref
obj.Position = Vector3(1,1,-2) obj.Position = Vector3(1,1,2)
obj.zoneInteractions() obj.zoneInteractions()
val msg = testProbe.receiveOne(max = 250 milliseconds) val msg = testProbe.receiveOne(max = 250 milliseconds)
assert( assert(
msg match { msg match {
case InteractingWithEnvironment(b, _) => (b eq pool1) case RespondsToZoneEnvironment.Timer(EnvironmentAttribute.Water, _, _, _) => true
case _ => false case _ => false
} }
) )
@ -57,28 +63,28 @@ class InteractsWithZoneEnvironmentTest extends ActorTest {
testProbe.expectNoMessage(max = 500 milliseconds) testProbe.expectNoMessage(max = 500 milliseconds)
} }
"acknowledge ceasation of interaction when moved out of a previous occupied the critical region (just once)" in { "acknowledge cessation of interaction when moved out of a previous occupied the critical region (just once)" in {
val testProbe = TestProbe() val testProbe = TestProbe()
val obj = InteractsWithZoneEnvironmentTest.testObject() val obj = InteractsWithZoneEnvironmentTest.testObject()
obj.Zone = testZone obj.Zone = testZone
obj.Actor = testProbe.ref obj.Actor = testProbe.ref
obj.Position = Vector3(1,1,-2) obj.Position = Vector3(1,1,2)
obj.zoneInteractions() obj.zoneInteractions()
val msg1 = testProbe.receiveOne(max = 250 milliseconds) val msg1 = testProbe.receiveOne(max = 250 milliseconds)
assert( assert(
msg1 match { msg1 match {
case InteractingWithEnvironment(b, _) => (b eq pool1) case RespondsToZoneEnvironment.Timer(EnvironmentAttribute.Water, _, _, _) => true
case _ => false case _ => false
} }
) )
obj.Position = Vector3(1,1,1) obj.Position = Vector3(1,1,50)
obj.zoneInteractions() obj.zoneInteractions()
val msg2 = testProbe.receiveOne(max = 250 milliseconds) val msg2 = testProbe.receiveOne(max = 250 milliseconds)
assert( assert(
msg2 match { msg2 match {
case EscapeFromEnvironment(b, _) => (b eq pool1) case RespondsToZoneEnvironment.Timer(EnvironmentAttribute.Water, _, _, _) => true
case _ => false case _ => false
} }
) )
@ -92,26 +98,25 @@ class InteractsWithZoneEnvironmentTest extends ActorTest {
obj.Zone = testZone obj.Zone = testZone
obj.Actor = testProbe.ref obj.Actor = testProbe.ref
obj.Position = Vector3(7,7,-2) obj.Position = Vector3(7,7,2)
obj.zoneInteractions() obj.zoneInteractions()
val msg1 = testProbe.receiveOne(max = 250 milliseconds) val msg1 = testProbe.receiveOne(max = 250 milliseconds)
assert( assert(
msg1 match { msg1 match {
case InteractingWithEnvironment(b, _) => (b eq pool1) case RespondsToZoneEnvironment.Timer(EnvironmentAttribute.Water, _, _, _) => true
case _ => false case _ => false
} }
) )
obj.Position = Vector3(12,7,-2) obj.Position = Vector3(12,7,2)
obj.zoneInteractions() obj.zoneInteractions()
val msg2 = testProbe.receiveOne(max = 250 milliseconds) val msg2 = testProbe.receiveOne(max = 250 milliseconds)
assert( assert(
msg2 match { msg2 match {
case InteractingWithEnvironment(b, _) => (b eq pool1) case RespondsToZoneEnvironment.Timer(EnvironmentAttribute.Water, _, _, _) => true
case _ => false case _ => false
} }
) )
assert(pool1.attribute == pool2.attribute)
} }
"transition between two different critical regions when the regions have different attributes" in { "transition between two different critical regions when the regions have different attributes" in {
@ -120,32 +125,37 @@ class InteractsWithZoneEnvironmentTest extends ActorTest {
obj.Zone = testZone obj.Zone = testZone
obj.Actor = testProbe.ref obj.Actor = testProbe.ref
obj.Position = Vector3(7,7,-2) obj.Position = Vector3(7,7,2)
obj.zoneInteractions() obj.zoneInteractions()
val msg1 = testProbe.receiveOne(max = 250 milliseconds) val msg1 = testProbe.receiveOne(max = 250 milliseconds)
assert( assert(
msg1 match { msg1 match {
case InteractingWithEnvironment(b, _) => (b eq pool1) case RespondsToZoneEnvironment.Timer(EnvironmentAttribute.Water, _, _, _) => true
case _ => false case _ => false
} }
) )
obj.Position = Vector3(7,12,-2) obj.Position = Vector3(7,12,2)
obj.zoneInteractions() obj.zoneInteractions()
val msgs = testProbe.receiveN(2, max = 250 milliseconds) val msgs = testProbe.receiveN(3, max = 250 milliseconds)
assert( assert(
msgs.head match { msgs.head match {
case EscapeFromEnvironment(b, _) => (b eq pool1) case Vitality.Damage(_) => true
case _ => false case _ => false
} }
) )
assert( assert(
msgs(1) match { msgs(1) match {
case InteractingWithEnvironment(b, _) => (b eq pool1) case AuraEffectBehavior.StartEffect(Aura.Fire, _) => true
case _ => false
}
)
assert(
msgs(2) match {
case RespondsToZoneEnvironment.Timer(EnvironmentAttribute.Lava, _, _, _) => true
case _ => false case _ => false
} }
) )
assert(pool1.attribute != pool3.attribute)
} }
} }
@ -155,22 +165,24 @@ class InteractsWithZoneEnvironmentTest extends ActorTest {
obj.Zone = testZone obj.Zone = testZone
obj.Actor = testProbe.ref obj.Actor = testProbe.ref
obj.Position = Vector3(1,1,-2) obj.Position = Vector3(1,1,2)
obj.zoneInteractions() obj.zoneInteractions()
val msg1 = testProbe.receiveOne(max = 250 milliseconds) val msg1 = testProbe.receiveOne(max = 250 milliseconds)
assert( assert(
msg1 match { msg1 match {
case InteractingWithEnvironment(b, _) => (b eq pool1) case RespondsToZoneEnvironment.Timer(EnvironmentAttribute.Water, _, _, _) => true
case _ => false case _ => false
} }
) )
obj.allowInteraction = false obj.allowInteraction = false
val msg2 = testProbe.receiveOne(max = 250 milliseconds) val msg2 = testProbe.receiveOne(max = 250 milliseconds)
assert(
msg2 match { msg2 match {
case EscapeFromEnvironment(b, _) => (b eq pool1) case RespondsToZoneEnvironment.Timer(EnvironmentAttribute.Water, _, _, _) => true
case _ => assert( false) case _ => false
} }
)
obj.zoneInteractions() obj.zoneInteractions()
testProbe.expectNoMessage(max = 500 milliseconds) testProbe.expectNoMessage(max = 500 milliseconds)
} }
@ -182,7 +194,7 @@ class InteractsWithZoneEnvironmentTest extends ActorTest {
obj.Actor = testProbe.ref obj.Actor = testProbe.ref
obj.allowInteraction = false obj.allowInteraction = false
obj.Position = Vector3(1,1,-2) obj.Position = Vector3(1,1,2)
obj.zoneInteractions() obj.zoneInteractions()
testProbe.expectNoMessage(max = 500 milliseconds) testProbe.expectNoMessage(max = 500 milliseconds)
@ -190,7 +202,7 @@ class InteractsWithZoneEnvironmentTest extends ActorTest {
val msg1 = testProbe.receiveOne(max = 250 milliseconds) val msg1 = testProbe.receiveOne(max = 250 milliseconds)
assert( assert(
msg1 match { msg1 match {
case InteractingWithEnvironment(b, _) => (b eq pool1) case RespondsToZoneEnvironment.Timer(EnvironmentAttribute.Water, _, _, _) => true
case _ => false case _ => false
} }
) )
@ -199,15 +211,9 @@ class InteractsWithZoneEnvironmentTest extends ActorTest {
object InteractsWithZoneEnvironmentTest { object InteractsWithZoneEnvironmentTest {
def testObject(): PlanetSideServerObject with InteractsWithZone = { def testObject(): PlanetSideServerObject with InteractsWithZone = {
new PlanetSideServerObject val p = new Player(Avatar(1, "test", PlanetSideEmpire.VS, CharacterSex.Male, 1, CharacterVoice.Mute))
with InteractsWithZone { p.GUID = PlanetSideGUID(1)
interaction(new InteractWithEnvironment()) p.Spawn()
def Faction: PlanetSideEmpire.Value = PlanetSideEmpire.VS p
def DamageModel = null
def Definition: ObjectDefinition with VitalityDefinition = new ObjectDefinition(objectId = 0) with VitalityDefinition {
Damageable = true
DrownAtMaxDepth = true
}
}
} }
} }

View file

@ -1,20 +1,20 @@
// Copyright (c) 2020 PSForever // Copyright (c) 2020 PSForever
package objects package objects
import akka.actor.{ActorRef => ClassicActorRef}
import akka.actor.typed.ActorRef import akka.actor.typed.ActorRef
import akka.actor.{ActorSystem, Props} import akka.actor.{ActorSystem, Props}
import akka.actor.typed.scaladsl.adapter._
import akka.testkit.TestProbe import akka.testkit.TestProbe
import base.ActorTest import base.ActorTest
import net.psforever.actors.session.AvatarActor import net.psforever.actors.session.AvatarActor
import net.psforever.actors.zone.ZoneActor
import net.psforever.objects.avatar.{Avatar, Certification, PlayerControl} import net.psforever.objects.avatar.{Avatar, Certification, PlayerControl}
import net.psforever.objects.ballistics._ import net.psforever.objects.ballistics._
import net.psforever.objects.guid.NumberPoolHub import net.psforever.objects.guid.NumberPoolHub
import net.psforever.objects.guid.source.MaxNumberSource import net.psforever.objects.guid.source.MaxNumberSource
import net.psforever.objects.vital.Vitality import net.psforever.objects.vital.{SpawningActivity, Vitality}
import net.psforever.objects.zones.{Zone, ZoneMap} import net.psforever.objects.zones.{Zone, ZoneMap}
import net.psforever.objects._ import net.psforever.objects._
import net.psforever.objects.definition.ProjectileDefinition
import net.psforever.objects.serverobject.CommonMessages import net.psforever.objects.serverobject.CommonMessages
import net.psforever.objects.serverobject.environment.interaction.common.Watery.OxygenStateTarget import net.psforever.objects.serverobject.environment.interaction.common.Watery.OxygenStateTarget
import net.psforever.objects.serverobject.environment.{DeepSquare, EnvironmentAttribute, Pool} import net.psforever.objects.serverobject.environment.{DeepSquare, EnvironmentAttribute, Pool}
@ -22,6 +22,7 @@ import net.psforever.objects.sourcing.{PlayerSource, SourceEntry}
import net.psforever.objects.vital.base.DamageResolution import net.psforever.objects.vital.base.DamageResolution
import net.psforever.objects.vital.interaction.DamageInteraction import net.psforever.objects.vital.interaction.DamageInteraction
import net.psforever.objects.vital.projectile.ProjectileReason import net.psforever.objects.vital.projectile.ProjectileReason
import net.psforever.objects.vital.resolution.ResolutionCalculations.Output
import net.psforever.packet.game._ import net.psforever.packet.game._
import net.psforever.types._ import net.psforever.types._
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage} import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
@ -29,17 +30,17 @@ import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
import scala.concurrent.duration._ import scala.concurrent.duration._
class PlayerControlHealTest extends ActorTest { class PlayerControlHealTest extends ActorTest {
val player1 = val player1: Player =
Player(Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)) //guid=1 Player(Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)) //guid=1
val player2 = val player2: Player =
Player(Avatar(1, "TestCharacter2", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)) //guid=2 Player(Avatar(1, "TestCharacter2", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)) //guid=2
val avatarProbe = TestProbe() val avatarProbe: TestProbe = TestProbe()
val guid = new NumberPoolHub(new MaxNumberSource(15)) val guid = new NumberPoolHub(new MaxNumberSource(15))
val zone = new Zone("test", new ZoneMap("test"), 0) { val zone: Zone = new Zone("test", new ZoneMap("test"), 0) {
override def SetupNumberPools() = {} override def SetupNumberPools(): Unit = {}
GUID(guid) GUID(guid)
override def LivePlayers = List(player1, player2) override def LivePlayers: List[Player] = List(player1, player2)
override def AvatarEvents = avatarProbe.ref override def AvatarEvents: ClassicActorRef = avatarProbe.ref
} }
player1.Zone = zone player1.Zone = zone
@ -52,7 +53,7 @@ class PlayerControlHealTest extends ActorTest {
guid.register(player2.avatar.locker, 6) guid.register(player2.avatar.locker, 6)
player2.Actor = system.actorOf(Props(classOf[PlayerControl], player2, null), "player2-control") player2.Actor = system.actorOf(Props(classOf[PlayerControl], player2, null), "player2-control")
val tool = Tool(GlobalDefinitions.medicalapplicator) //guid=3 & 4 val tool: Tool = Tool(GlobalDefinitions.medicalapplicator) //guid=3 & 4
guid.register(player1, 1) guid.register(player1, 1)
guid.register(player2, 2) guid.register(player2, 2)
guid.register(tool, 3) guid.register(tool, 3)
@ -114,15 +115,15 @@ class PlayerControlHealTest extends ActorTest {
} }
} }
class PlayerControlHealSelfTest extends ActorTest { class PlayerControlHealSelfTest extends ActorTest {
val player1 = val player1: Player =
Player(Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)) //guid=1 Player(Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)) //guid=1
val avatarProbe = TestProbe() val avatarProbe: TestProbe = TestProbe()
val guid = new NumberPoolHub(new MaxNumberSource(15)) val guid = new NumberPoolHub(new MaxNumberSource(15))
val zone = new Zone("test", new ZoneMap("test"), 0) { val zone: Zone = new Zone("test", new ZoneMap("test"), 0) {
override def SetupNumberPools() = {} override def SetupNumberPools(): Unit = {}
GUID(guid) GUID(guid)
override def LivePlayers = List(player1) override def LivePlayers: List[Player] = List(player1)
override def AvatarEvents = avatarProbe.ref override def AvatarEvents: ClassicActorRef = avatarProbe.ref
} }
player1.Zone = zone player1.Zone = zone
@ -131,7 +132,7 @@ class PlayerControlHealSelfTest extends ActorTest {
guid.register(player1.avatar.locker, 5) guid.register(player1.avatar.locker, 5)
player1.Actor = system.actorOf(Props(classOf[PlayerControl], player1, null), "player1-control") player1.Actor = system.actorOf(Props(classOf[PlayerControl], player1, null), "player1-control")
val tool = Tool(GlobalDefinitions.medicalapplicator) //guid=3 & 4 val tool: Tool = Tool(GlobalDefinitions.medicalapplicator) //guid=3 & 4
guid.register(player1, 1) guid.register(player1, 1)
guid.register(tool, 3) guid.register(tool, 3)
guid.register(tool.AmmoSlot.Box, 4) guid.register(tool.AmmoSlot.Box, 4)
@ -189,18 +190,18 @@ class PlayerControlHealSelfTest extends ActorTest {
} }
class PlayerControlRepairTest extends ActorTest { class PlayerControlRepairTest extends ActorTest {
val avatar = Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute) val avatar: Avatar = Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)
.copy(certifications = Set(Certification.Engineering)) .copy(certifications = Set(Certification.Engineering))
val player1 = Player(avatar) //guid=1 val player1: Player = Player(avatar) //guid=1
val player2 = val player2: Player =
Player(Avatar(1, "TestCharacter2", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)) //guid=2 Player(Avatar(1, "TestCharacter2", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)) //guid=2
val avatarProbe = TestProbe() val avatarProbe: TestProbe = TestProbe()
val guid = new NumberPoolHub(new MaxNumberSource(15)) val guid = new NumberPoolHub(new MaxNumberSource(15))
val zone = new Zone("test", new ZoneMap("test"), 0) { val zone: Zone = new Zone("test", new ZoneMap("test"), 0) {
override def SetupNumberPools() = {} override def SetupNumberPools(): Unit = {}
GUID(guid) GUID(guid)
override def LivePlayers = List(player1, player2) override def LivePlayers: List[Player] = List(player1, player2)
override def AvatarEvents = avatarProbe.ref override def AvatarEvents: ClassicActorRef = avatarProbe.ref
} }
player1.Zone = zone player1.Zone = zone
@ -213,7 +214,7 @@ class PlayerControlRepairTest extends ActorTest {
guid.register(player2.avatar.locker, 6) guid.register(player2.avatar.locker, 6)
player2.Actor = system.actorOf(Props(classOf[PlayerControl], player2, null), "player2-control") player2.Actor = system.actorOf(Props(classOf[PlayerControl], player2, null), "player2-control")
val tool = Tool(GlobalDefinitions.bank) //guid=3 & 4 val tool: Tool = Tool(GlobalDefinitions.bank) //guid=3 & 4
guid.register(player1, 1) guid.register(player1, 1)
guid.register(player2, 2) guid.register(player2, 2)
guid.register(tool, 3) guid.register(tool, 3)
@ -286,16 +287,16 @@ class PlayerControlRepairTest extends ActorTest {
} }
class PlayerControlRepairSelfTest extends ActorTest { class PlayerControlRepairSelfTest extends ActorTest {
val avatar = Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute) val avatar: Avatar = Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)
.copy(certifications = Set(Certification.Engineering)) .copy(certifications = Set(Certification.Engineering))
val player1 = Player(avatar) //guid=1 val player1: Player = Player(avatar) //guid=1
val avatarProbe = TestProbe() val avatarProbe: TestProbe = TestProbe()
val guid = new NumberPoolHub(new MaxNumberSource(15)) val guid = new NumberPoolHub(new MaxNumberSource(15))
val zone = new Zone("test", new ZoneMap("test"), 0) { val zone: Zone = new Zone("test", new ZoneMap("test"), 0) {
override def SetupNumberPools() = {} override def SetupNumberPools(): Unit = {}
GUID(guid) GUID(guid)
override def LivePlayers = List(player1) override def LivePlayers: List[Player] = List(player1)
override def AvatarEvents = avatarProbe.ref override def AvatarEvents: ClassicActorRef = avatarProbe.ref
} }
player1.Zone = zone player1.Zone = zone
@ -304,7 +305,7 @@ class PlayerControlRepairSelfTest extends ActorTest {
guid.register(player1.avatar.locker, 5) guid.register(player1.avatar.locker, 5)
player1.Actor = system.actorOf(Props(classOf[PlayerControl], player1, null), "player1-control") player1.Actor = system.actorOf(Props(classOf[PlayerControl], player1, null), "player1-control")
val tool = Tool(GlobalDefinitions.bank) //guid=3 & 4 val tool: Tool = Tool(GlobalDefinitions.bank) //guid=3 & 4
guid.register(player1, 1) guid.register(player1, 1)
guid.register(tool, 3) guid.register(tool, 3)
guid.register(tool.AmmoSlot.Box, 4) guid.register(tool.AmmoSlot.Box, 4)
@ -362,19 +363,19 @@ class PlayerControlRepairSelfTest extends ActorTest {
} }
class PlayerControlDamageTest extends ActorTest { class PlayerControlDamageTest extends ActorTest {
val player1 = val player1: Player =
Player(Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)) //guid=1 Player(Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)) //guid=1
val player2 = val player2: Player =
Player(Avatar(1, "TestCharacter2", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)) //guid=2 Player(Avatar(1, "TestCharacter2", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)) //guid=2
val avatarProbe = TestProbe() val avatarProbe: TestProbe = TestProbe()
val activityProbe = TestProbe() val activityProbe: TestProbe = TestProbe()
val guid = new NumberPoolHub(new MaxNumberSource(15)) val guid = new NumberPoolHub(new MaxNumberSource(15))
val zone = new Zone("test", new ZoneMap("test"), 0) { val zone: Zone = new Zone("test", new ZoneMap("test"), 0) {
override def SetupNumberPools() = {} override def SetupNumberPools(): Unit = {}
GUID(guid) GUID(guid)
override def LivePlayers = List(player1, player2) override def LivePlayers: List[Player] = List(player1, player2)
override def AvatarEvents = avatarProbe.ref override def AvatarEvents: ClassicActorRef = avatarProbe.ref
override def Activity = activityProbe.ref override def Activity: ClassicActorRef = activityProbe.ref
} }
player1.Zone = zone player1.Zone = zone
@ -388,10 +389,10 @@ class PlayerControlDamageTest extends ActorTest {
val (probe, avatarActor) = PlayerControlTest.DummyAvatar(system) val (probe, avatarActor) = PlayerControlTest.DummyAvatar(system)
player2.Actor = system.actorOf(Props(classOf[PlayerControl], player2, avatarActor), name = "player2-control") player2.Actor = system.actorOf(Props(classOf[PlayerControl], player2, avatarActor), name = "player2-control")
val tool = Tool(GlobalDefinitions.suppressor) //guid 3 & 4 val tool: Tool = Tool(GlobalDefinitions.suppressor) //guid 3 & 4
val projectile = tool.Projectile val projectile: ProjectileDefinition = tool.Projectile
val player1Source = PlayerSource(player1) val player1Source: PlayerSource = PlayerSource(player1)
val resolved = DamageInteraction( val resolved: DamageInteraction = DamageInteraction(
SourceEntry(player2), SourceEntry(player2),
ProjectileReason( ProjectileReason(
DamageResolution.Hit, DamageResolution.Hit,
@ -408,7 +409,7 @@ class PlayerControlDamageTest extends ActorTest {
), ),
Vector3(1, 0, 0) Vector3(1, 0, 0)
) )
val applyDamageTo = resolved.calculate() val applyDamageTo: Output = resolved.calculate()
guid.register(player1, 1) guid.register(player1, 1)
guid.register(player2, 2) guid.register(player2, 2)
guid.register(tool, 3) guid.register(tool, 3)
@ -467,19 +468,19 @@ class PlayerControlDamageTest extends ActorTest {
} }
class PlayerControlDeathStandingTest extends ActorTest { class PlayerControlDeathStandingTest extends ActorTest {
val player1 = val player1: Player =
Player(Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)) //guid=1 Player(Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)) //guid=1
val player2 = val player2: Player =
Player(Avatar(1, "TestCharacter2", PlanetSideEmpire.NC, CharacterSex.Male, 0, CharacterVoice.Mute)) //guid=2 Player(Avatar(1, "TestCharacter2", PlanetSideEmpire.NC, CharacterSex.Male, 0, CharacterVoice.Mute)) //guid=2
val avatarProbe = TestProbe() val avatarProbe: TestProbe = TestProbe()
val activityProbe = TestProbe() val activityProbe: TestProbe = TestProbe()
val guid = new NumberPoolHub(new MaxNumberSource(15)) val guid = new NumberPoolHub(new MaxNumberSource(15))
val zone = new Zone("test", new ZoneMap("test"), 0) { val zone: Zone = new Zone("test", new ZoneMap("test"), 0) {
override def SetupNumberPools() = {} override def SetupNumberPools(): Unit = {}
GUID(guid) GUID(guid)
override def LivePlayers = List(player1, player2) override def LivePlayers: List[Player] = List(player1, player2)
override def AvatarEvents = avatarProbe.ref override def AvatarEvents: ClassicActorRef = avatarProbe.ref
override def Activity = activityProbe.ref override def Activity: ClassicActorRef = activityProbe.ref
} }
player1.Zone = zone player1.Zone = zone
@ -493,10 +494,10 @@ class PlayerControlDeathStandingTest extends ActorTest {
val (probe, avatarActor) = PlayerControlTest.DummyAvatar(system) val (probe, avatarActor) = PlayerControlTest.DummyAvatar(system)
player2.Actor = system.actorOf(Props(classOf[PlayerControl], player2, avatarActor), name = "player2-control") player2.Actor = system.actorOf(Props(classOf[PlayerControl], player2, avatarActor), name = "player2-control")
val tool = Tool(GlobalDefinitions.suppressor) //guid 3 & 4 val tool: Tool = Tool(GlobalDefinitions.suppressor) //guid 3 & 4
val projectile = tool.Projectile val projectile: ProjectileDefinition = tool.Projectile
val player1Source = PlayerSource(player1) val player1Source: PlayerSource = PlayerSource(player1)
val resolved = DamageInteraction( val resolved: DamageInteraction = DamageInteraction(
SourceEntry(player2), SourceEntry(player2),
ProjectileReason( ProjectileReason(
DamageResolution.Hit, DamageResolution.Hit,
@ -513,7 +514,7 @@ class PlayerControlDeathStandingTest extends ActorTest {
), ),
Vector3(1, 0, 0) Vector3(1, 0, 0)
) )
val applyDamageTo = resolved.calculate() val applyDamageTo: Output = resolved.calculate()
guid.register(player1, 1) guid.register(player1, 1)
guid.register(player2, 2) guid.register(player2, 2)
guid.register(tool, 3) guid.register(tool, 3)
@ -543,7 +544,7 @@ class PlayerControlDeathStandingTest extends ActorTest {
) )
assert( assert(
msg_stamina match { msg_stamina match {
case AvatarActor.DeinitializeImplants() => true case AvatarActor.DeinitializeImplants => true
case _ => false case _ => false
} }
) )
@ -684,7 +685,7 @@ class PlayerControlDeathStandingTest extends ActorTest {
// activityProbe.expectNoMessage(200 milliseconds) // activityProbe.expectNoMessage(200 milliseconds)
// assert( // assert(
// msg_stamina match { // msg_stamina match {
// case AvatarActor.DeinitializeImplants() => true // case AvatarActor.DeinitializeImplants => true
// case _ => false // case _ => false
// } // }
// ) // )
@ -774,22 +775,22 @@ class PlayerControlDeathStandingTest extends ActorTest {
//} //}
class PlayerControlInteractWithWaterTest extends ActorTest { class PlayerControlInteractWithWaterTest extends ActorTest {
val player1 = val player1: Player =
Player(Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)) //guid=1 Player(Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)) //guid=1
val avatarProbe = TestProbe() val avatarProbe: TestProbe = TestProbe()
val guid = new NumberPoolHub(new MaxNumberSource(15)) val guid = new NumberPoolHub(new MaxNumberSource(15))
val pool = Pool(EnvironmentAttribute.Water, DeepSquare(-1, 10, 10, 0, 0)) val pool: Pool = Pool(EnvironmentAttribute.Water, DeepSquare(-1, 10, 10, 0, 0))
val zone = new Zone( val zone: Zone = new Zone(
id = "test", id = "test",
new ZoneMap(name = "test-map") { new ZoneMap(name = "test-map") {
environment = List(pool) environment = List(pool)
}, },
zoneNumber = 0 zoneNumber = 0
) { ) {
override def SetupNumberPools() = {} override def SetupNumberPools(): Unit = {}
GUID(guid) GUID(guid)
override def LivePlayers = List(player1) override def LivePlayers: List[Player] = List(player1)
override def AvatarEvents = avatarProbe.ref override def AvatarEvents: ClassicActorRef = avatarProbe.ref
} }
zone.blockMap.addTo(player1) zone.blockMap.addTo(player1)
zone.blockMap.addTo(pool) zone.blockMap.addTo(pool)
@ -828,22 +829,22 @@ class PlayerControlInteractWithWaterTest extends ActorTest {
} }
class PlayerControlStopInteractWithWaterTest extends ActorTest { class PlayerControlStopInteractWithWaterTest extends ActorTest {
val player1 = val player1: Player =
Player(Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)) //guid=1 Player(Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)) //guid=1
val avatarProbe = TestProbe() val avatarProbe: TestProbe = TestProbe()
val guid = new NumberPoolHub(new MaxNumberSource(15)) val guid = new NumberPoolHub(new MaxNumberSource(15))
val pool = Pool(EnvironmentAttribute.Water, DeepSquare(-1, 10, 10, 0, 0)) val pool: Pool = Pool(EnvironmentAttribute.Water, DeepSquare(-1, 10, 10, 0, 0))
val zone = new Zone( val zone: Zone = new Zone(
id = "test", id = "test",
new ZoneMap(name = "test-map") { new ZoneMap(name = "test-map") {
environment = List(pool) environment = List(pool)
}, },
zoneNumber = 0 zoneNumber = 0
) { ) {
override def SetupNumberPools() = {} override def SetupNumberPools(): Unit = {}
GUID(guid) GUID(guid)
override def LivePlayers = List(player1) override def LivePlayers: List[Player] = List(player1)
override def AvatarEvents = avatarProbe.ref override def AvatarEvents: ClassicActorRef = avatarProbe.ref
} }
zone.blockMap.addTo(player1) zone.blockMap.addTo(player1)
zone.blockMap.addTo(pool) zone.blockMap.addTo(pool)
@ -893,23 +894,23 @@ class PlayerControlStopInteractWithWaterTest extends ActorTest {
} }
class PlayerControlInteractWithLavaTest extends ActorTest { class PlayerControlInteractWithLavaTest extends ActorTest {
val player1 = val player1: Player =
Player(Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)) //guid=1 Player(Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)) //guid=1
val avatarProbe = TestProbe() val avatarProbe: TestProbe = TestProbe()
val guid = new NumberPoolHub(new MaxNumberSource(15)) val guid = new NumberPoolHub(new MaxNumberSource(15))
val pool = Pool(EnvironmentAttribute.Lava, DeepSquare(-1, 10, 10, 0, 0)) val pool: Pool = Pool(EnvironmentAttribute.Lava, DeepSquare(-1, 10, 10, 0, 0))
val zone = new Zone( val zone: Zone = new Zone(
id = "test-map", id = "test-map",
new ZoneMap(name = "test-map") { new ZoneMap(name = "test-map") {
environment = List(pool) environment = List(pool)
}, },
zoneNumber = 0 zoneNumber = 0
) { ) {
override def SetupNumberPools() = {} override def SetupNumberPools(): Unit = {}
GUID(guid) GUID(guid)
override def LivePlayers = List(player1) override def LivePlayers: List[Player] = List(player1)
override def AvatarEvents = avatarProbe.ref override def AvatarEvents: ClassicActorRef = avatarProbe.ref
override def Activity = TestProbe().ref override def Activity: ClassicActorRef = TestProbe().ref
} }
zone.blockMap.addTo(player1) zone.blockMap.addTo(player1)
zone.blockMap.addTo(pool) zone.blockMap.addTo(pool)
@ -948,49 +949,49 @@ class PlayerControlInteractWithLavaTest extends ActorTest {
} }
) )
assert(player1.Health > 0) //still alive? assert(player1.Health > 0) //still alive?
probe.receiveOne(65 seconds) //wait until player1's implants deinitialize probe.receiveOne(65 seconds)
assert(player1.Health == 0) //ded assert(player1.Health == 0) //ded
} }
} }
} }
class PlayerControlInteractWithDeathTest extends ActorTest { class PlayerControlInteractWithDeathTest extends ActorTest {
val player1 = val player1: Player =
Player(Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)) //guid=1 Player(Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)) //guid=1
val avatarProbe = TestProbe() val avatarProbe: TestProbe = TestProbe()
val guid = new NumberPoolHub(new MaxNumberSource(15)) val guid = new NumberPoolHub(new MaxNumberSource(15))
val pool = Pool(EnvironmentAttribute.Death, DeepSquare(-1, 10, 10, 0, 0)) val pool: Pool = Pool(EnvironmentAttribute.Death, DeepSquare(10, 10, 10, 0, 0))
val zone = new Zone( val zone: Zone = new Zone(
id = "test-map", id = "test-map",
new ZoneMap(name = "test-map") { new ZoneMap(name = "test-map") {
environment = List(pool) environment = List(pool)
}, },
zoneNumber = 0 zoneNumber = 0
) { ) {
override def SetupNumberPools() = {} override def SetupNumberPools(): Unit = {}
GUID(guid) GUID(guid)
override def LivePlayers = List(player1) override def LivePlayers: List[Player] = List(player1)
override def AvatarEvents = avatarProbe.ref override def Activity: ClassicActorRef = TestProbe().ref
override def Activity = TestProbe().ref override def AvatarEvents: ClassicActorRef = avatarProbe.ref
} }
zone.blockMap.addTo(player1) guid.register(player1, 1)
zone.blockMap.addTo(pool) guid.register(player1.avatar.locker, 5)
player1.Zone = zone player1.Zone = zone
player1.Spawn() player1.Spawn()
guid.register(player1.avatar.locker, 5) player1.Position = Vector3(5,5,3) //right in the pool
zone.blockMap.addTo(player1)
zone.blockMap.addTo(pool)
val (probe, avatarActor) = PlayerControlTest.DummyAvatar(system) val (probe, avatarActor) = PlayerControlTest.DummyAvatar(system)
player1.Actor = system.actorOf(Props(classOf[PlayerControl], player1, avatarActor), "player1-control") player1.Actor = system.actorOf(Props(classOf[PlayerControl], player1, avatarActor), "player1-control")
player1.LogActivity(SpawningActivity(PlayerSource(player1), 0, None))
guid.register(player1, 1)
"PlayerControl" should { "PlayerControl" should {
"take continuous damage if player steps into a pool of death" in { "kill the player if that player steps into a pool of death" in {
assert(player1.Health == 100) //alive assert(player1.Health == 100) //alive
player1.Position = Vector3(5,5,-3) //right in the pool probe.expectNoMessage(5.seconds)
player1.zoneInteractions() //trigger player1.zoneInteractions() //trigger
probe.receiveOne(3.seconds)
probe.receiveOne(250 milliseconds) //wait until oplayer1's implants deinitialize
assert(player1.Health == 0) //ded assert(player1.Health == 0) //ded
} }
} }

View file

@ -14,12 +14,13 @@ import net.psforever.objects.guid.NumberPoolHub
import net.psforever.objects.guid.source.MaxNumberSource import net.psforever.objects.guid.source.MaxNumberSource
import net.psforever.objects.serverobject.CommonMessages import net.psforever.objects.serverobject.CommonMessages
import net.psforever.objects.serverobject.environment._ import net.psforever.objects.serverobject.environment._
import net.psforever.objects.serverobject.environment.interaction.{EscapeFromEnvironment, InteractingWithEnvironment} import net.psforever.objects.serverobject.environment.interaction.{EscapeFromEnvironment, InteractingWithEnvironment, RespondsToZoneEnvironment}
import net.psforever.objects.serverobject.environment.interaction.common.Watery.OxygenStateTarget import net.psforever.objects.serverobject.environment.interaction.common.Watery.OxygenStateTarget
import net.psforever.objects.serverobject.mount.Mountable import net.psforever.objects.serverobject.mount.Mountable
import net.psforever.objects.sourcing.VehicleSource
import net.psforever.objects.vehicles.VehicleLockState import net.psforever.objects.vehicles.VehicleLockState
import net.psforever.objects.vehicles.control.VehicleControl import net.psforever.objects.vehicles.control.VehicleControl
import net.psforever.objects.vital.{ShieldCharge, Vitality} import net.psforever.objects.vital.{ShieldCharge, SpawningActivity, Vitality}
import net.psforever.objects.zones.{Zone, ZoneMap} import net.psforever.objects.zones.{Zone, ZoneMap}
import net.psforever.packet.game._ import net.psforever.packet.game._
import net.psforever.services.ServiceManager import net.psforever.services.ServiceManager
@ -634,9 +635,10 @@ class VehicleControlInteractWithWaterTest extends ActorTest {
val player1 = val player1 =
Player(Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)) //guid=1 Player(Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)) //guid=1
val avatarProbe = TestProbe() val avatarProbe = TestProbe()
val playerProbe = TestProbe()
val vehicleProbe = TestProbe() val vehicleProbe = TestProbe()
val guid = new NumberPoolHub(new MaxNumberSource(15)) val guid = new NumberPoolHub(new MaxNumberSource(15))
val pool = Pool(EnvironmentAttribute.Water, DeepSquare(-1, 10, 10, 0, 0)) val pool = Pool(EnvironmentAttribute.Water, DeepSquare(10, 10, 10, 0, 0))
val zone = new Zone( val zone = new Zone(
id = "test-zone", id = "test-zone",
new ZoneMap(name = "test-map") { new ZoneMap(name = "test-map") {
@ -649,7 +651,7 @@ class VehicleControlInteractWithWaterTest extends ActorTest {
override def LivePlayers = List(player1) override def LivePlayers = List(player1)
override def Vehicles = List(vehicle) override def Vehicles = List(vehicle)
override def AvatarEvents = avatarProbe.ref override def AvatarEvents = avatarProbe.ref
override def VehicleEvents = vehicleProbe.ref override def VehicleEvents = avatarProbe.ref
this.actor = new TestProbe(system).ref.toTyped[ZoneActor.Command] this.actor = new TestProbe(system).ref.toTyped[ZoneActor.Command]
} }
@ -666,38 +668,24 @@ class VehicleControlInteractWithWaterTest extends ActorTest {
vehicle.Seats(0).mount(player1) vehicle.Seats(0).mount(player1)
player1.VehicleSeated = vehicle.GUID player1.VehicleSeated = vehicle.GUID
val (probe, avatarActor) = PlayerControlTest.DummyAvatar(system) val (probe, avatarActor) = PlayerControlTest.DummyAvatar(system)
player1.Actor = system.actorOf(Props(classOf[PlayerControl], player1, avatarActor), "player1-control") player1.Actor = playerProbe.ref
vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-control") vehicle.Actor = vehicleProbe.ref
"VehicleControl" should { "VehicleControl" should {
"causes disability when the vehicle drives too deep in water" in { "causes disability when the vehicle drives too deep in water" in {
vehicle.Position = Vector3(5,5,-3) //right in the pool vehicle.Position = Vector3(5,5,3) //right in the pool
vehicle.zoneInteractions() //trigger vehicle.zoneInteractions() //trigger
val msg_drown = avatarProbe.receiveOne(250 milliseconds) val msg_drown = playerProbe.receiveOne(250 milliseconds)
assert( assert(msg_drown match {
msg_drown match { case InteractingWithEnvironment(body, _) => body eq pool
case AvatarServiceMessage(
"TestCharacter1",
AvatarAction.OxygenState(
OxygenStateTarget(PlanetSideGUID(1), _, OxygenState.Suffocation, 100f),
Some(OxygenStateTarget(PlanetSideGUID(2), _, OxygenState.Suffocation, 100f))
)
) => true
case _ => false case _ => false
} })
) val msg_disable = vehicleProbe.receiveOne(10 seconds)
//player will die in 60s assert(msg_disable match {
//vehicle will disable in 5s; driver will be kicked case RespondsToZoneEnvironment.Timer(EnvironmentAttribute.Water, _, _, VehicleControl.Disable(true)) => true
val msg_kick = vehicleProbe.receiveOne(10 seconds) case _ => false
msg_kick match { })
case VehicleServiceMessage(
"test-zone",
VehicleAction.KickPassenger(PlanetSideGUID(1), 4, _, PlanetSideGUID(2))
) => assert(true)
case _ => assert(false)
}
//player will die, but detailing players death messages is not necessary for this test
} }
} }
} }
@ -835,7 +823,7 @@ class VehicleControlInteractWithDeathTest extends ActorTest {
val player1 = val player1 =
Player(Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)) //guid=1 Player(Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)) //guid=1
val guid = new NumberPoolHub(new MaxNumberSource(15)) val guid = new NumberPoolHub(new MaxNumberSource(15))
val pool = Pool(EnvironmentAttribute.Death, DeepSquare(-1, 10, 10, 0, 0)) val pool = Pool(EnvironmentAttribute.Death, DeepSquare(5, 10, 10, 0, 0))
val zone = new Zone( val zone = new Zone(
id = "test-zone", id = "test-zone",
new ZoneMap(name = "test-map") { new ZoneMap(name = "test-map") {
@ -866,15 +854,17 @@ class VehicleControlInteractWithDeathTest extends ActorTest {
val (probe, avatarActor) = PlayerControlTest.DummyAvatar(system) val (probe, avatarActor) = PlayerControlTest.DummyAvatar(system)
player1.Actor = system.actorOf(Props(classOf[PlayerControl], player1, avatarActor), "player1-control") player1.Actor = system.actorOf(Props(classOf[PlayerControl], player1, avatarActor), "player1-control")
vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-control") vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-control")
vehicle.LogActivity(SpawningActivity(VehicleSource(vehicle), 0, None))
"VehicleControl" should { "VehicleControl" should {
"take continuous damage if vehicle drives into a pool of death" in { "take continuous damage if vehicle drives into a pool of death" in {
assert(vehicle.Health > 0) //alive assert(vehicle.Health > 0) //alive
assert(player1.Health == 100) //alive assert(player1.Health == 100) //alive
vehicle.Position = Vector3(5,5,-3) //right in the pool vehicle.Position = Vector3(5,5,1) //right in the pool
probe.expectNoMessage(5 seconds)
vehicle.zoneInteractions() //trigger vehicle.zoneInteractions() //trigger
probe.receiveOne(2 seconds) //wait until player1's implants deinitialize probe.receiveOne(2 seconds)
assert(vehicle.Health == 0) //ded assert(vehicle.Health == 0) //ded
assert(player1.Health == 0) //ded assert(player1.Health == 0) //ded
} }