mirror of
https://github.com/psforever/PSF-LoginServer.git
synced 2026-01-19 18:44:45 +00:00
Merge branch 'master' into fix-some-commands
This commit is contained in:
commit
097ce9c7fd
|
|
@ -27,7 +27,7 @@ world {
|
|||
|
||||
# How the server is displayed in the server browser.
|
||||
# One of: released beta development
|
||||
server-type = released
|
||||
server-type = development
|
||||
}
|
||||
|
||||
# Admin API configuration
|
||||
|
|
@ -408,8 +408,14 @@ game {
|
|||
base = 25
|
||||
}
|
||||
{
|
||||
name = "router"
|
||||
name = "router-driver"
|
||||
base = 15
|
||||
shots-multiplier = 1.0
|
||||
}
|
||||
{
|
||||
name = "telepad-use"
|
||||
base = 20
|
||||
shots-multiplier = 1.0
|
||||
}
|
||||
{
|
||||
name = "hotdrop"
|
||||
|
|
|
|||
|
|
@ -1131,6 +1131,11 @@ class AvatarActor(
|
|||
var supportExperienceTimer: Cancellable = Default.Cancellable
|
||||
var experienceDebt: Long = 0L
|
||||
|
||||
private def setSession(newSession: Session): Unit = {
|
||||
session = Some(newSession)
|
||||
_avatar = Option(newSession.avatar)
|
||||
}
|
||||
|
||||
def avatar: Avatar = _avatar.get
|
||||
|
||||
def avatar_=(avatar: Avatar): Unit = {
|
||||
|
|
@ -1156,7 +1161,7 @@ class AvatarActor(
|
|||
postLoginBehaviour()
|
||||
|
||||
case SetSession(newSession) =>
|
||||
session = Some(newSession)
|
||||
setSession(newSession)
|
||||
postLoginBehaviour()
|
||||
|
||||
case other =>
|
||||
|
|
@ -1176,7 +1181,7 @@ class AvatarActor(
|
|||
Behaviors
|
||||
.receiveMessage[Command] {
|
||||
case SetSession(newSession) =>
|
||||
session = Some(newSession)
|
||||
setSession(newSession)
|
||||
Behaviors.same
|
||||
|
||||
case SetLookingForSquad(lfs) =>
|
||||
|
|
@ -1329,7 +1334,7 @@ class AvatarActor(
|
|||
Behaviors
|
||||
.receiveMessagePartial[Command] {
|
||||
case SetSession(newSession) =>
|
||||
session = Some(newSession)
|
||||
setSession(newSession)
|
||||
Behaviors.same
|
||||
|
||||
case ReplaceAvatar(newAvatar) =>
|
||||
|
|
|
|||
|
|
@ -160,7 +160,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
|||
if (mode != newMode) {
|
||||
logic.switchFrom(data.session)
|
||||
mode = newMode
|
||||
logic = mode.setup(data)
|
||||
logic = newMode.setup(data)
|
||||
}
|
||||
logic.switchTo(data.session)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,577 @@
|
|||
// Copyright (c) 2024 PSForever
|
||||
package net.psforever.actors.session.csr
|
||||
|
||||
import akka.actor.{ActorContext, typed}
|
||||
import net.psforever.actors.session.SessionActor
|
||||
import net.psforever.actors.session.normal.NormalMode
|
||||
import net.psforever.actors.session.support.AvatarHandlerFunctions
|
||||
import net.psforever.login.WorldSession.PutLoadoutEquipmentInInventory
|
||||
import net.psforever.objects.PlanetSideGameObject
|
||||
import net.psforever.objects.inventory.Container
|
||||
import net.psforever.objects.serverobject.containable.ContainableBehavior
|
||||
import net.psforever.objects.serverobject.mount.Mountable
|
||||
import net.psforever.packet.game.{AvatarImplantMessage, CreateShortcutMessage, ImplantAction}
|
||||
import net.psforever.services.avatar.AvatarServiceResponse
|
||||
import net.psforever.types.ImplantType
|
||||
|
||||
//
|
||||
import net.psforever.actors.session.AvatarActor
|
||||
import net.psforever.actors.session.support.{SessionAvatarHandlers, SessionData}
|
||||
import net.psforever.login.WorldSession.{DropEquipmentFromInventory, DropLeftovers, HoldNewEquipmentUp}
|
||||
import net.psforever.objects.{GlobalDefinitions, Player, Tool, Vehicle}
|
||||
import net.psforever.objects.guid.{GUIDTask, TaskWorkflow}
|
||||
import net.psforever.objects.inventory.InventoryItem
|
||||
import net.psforever.objects.serverobject.terminals.{ProximityUnit, Terminal}
|
||||
import net.psforever.objects.zones.Zoning
|
||||
import net.psforever.packet.game.objectcreate.ObjectCreateMessageParent
|
||||
import net.psforever.packet.game.{ArmorChangedMessage, ChangeAmmoMessage, ChangeFireModeMessage, ChangeFireStateMessage_Start, ChangeFireStateMessage_Stop, ChatMsg, DestroyMessage, DrowningTarget, GenericActionMessage, GenericObjectActionMessage, HitHint, ItemTransactionResultMessage, ObjectCreateDetailedMessage, ObjectCreateMessage, ObjectDeleteMessage, ObjectHeldMessage, OxygenStateMessage, PlanetsideAttributeMessage, PlayerStateMessage, ProjectileStateMessage, ReloadMessage, SetEmpireMessage, UseItemMessage, WeaponDryFireMessage}
|
||||
import net.psforever.services.avatar.AvatarResponse
|
||||
import net.psforever.services.Service
|
||||
import net.psforever.types.{ChatMessageType, PlanetSideGUID, TransactionType, Vector3}
|
||||
import net.psforever.util.Config
|
||||
|
||||
object AvatarHandlerLogic {
|
||||
def apply(ops: SessionAvatarHandlers): AvatarHandlerLogic = {
|
||||
new AvatarHandlerLogic(ops, ops.context)
|
||||
}
|
||||
}
|
||||
|
||||
class AvatarHandlerLogic(val ops: SessionAvatarHandlers, implicit val context: ActorContext) extends AvatarHandlerFunctions {
|
||||
def sessionLogic: SessionData = ops.sessionLogic
|
||||
|
||||
private val avatarActor: typed.ActorRef[AvatarActor.Command] = ops.avatarActor
|
||||
|
||||
/**
|
||||
* na
|
||||
* @param toChannel na
|
||||
* @param guid na
|
||||
* @param reply na
|
||||
*/
|
||||
def handle(toChannel: String, guid: PlanetSideGUID, reply: AvatarResponse.Response): Unit = {
|
||||
val resolvedPlayerGuid = if (player != null && player.HasGUID) {
|
||||
player.GUID
|
||||
} else {
|
||||
Service.defaultPlayerGUID
|
||||
}
|
||||
val isNotSameTarget = resolvedPlayerGuid != guid
|
||||
val isSameTarget = !isNotSameTarget
|
||||
reply match {
|
||||
/* special messages */
|
||||
case AvatarResponse.TeardownConnection() if player.spectator =>
|
||||
context.self ! SessionActor.SetMode(CustomerServiceRepresentativeMode)
|
||||
context.self.forward(AvatarServiceResponse(toChannel, guid, reply))
|
||||
|
||||
case AvatarResponse.TeardownConnection() =>
|
||||
context.self ! SessionActor.SetMode(NormalMode)
|
||||
context.self.forward(AvatarServiceResponse(toChannel, guid, reply))
|
||||
|
||||
/* really common messages (very frequently, every life) */
|
||||
case pstate @ AvatarResponse.PlayerState(
|
||||
pos,
|
||||
vel,
|
||||
yaw,
|
||||
pitch,
|
||||
yawUpper,
|
||||
_,
|
||||
isCrouching,
|
||||
isJumping,
|
||||
jumpThrust,
|
||||
isCloaking,
|
||||
isNotRendered,
|
||||
canSeeReallyFar
|
||||
) if isNotSameTarget =>
|
||||
val pstateToSave = pstate.copy(timestamp = 0)
|
||||
val (lastMsg, lastTime, lastPosition, wasVisible, wasShooting) = ops.lastSeenStreamMessage.get(guid.guid) match {
|
||||
case Some(SessionAvatarHandlers.LastUpstream(Some(msg), visible, shooting, time)) => (Some(msg), time, msg.pos, visible, shooting)
|
||||
case _ => (None, 0L, Vector3.Zero, false, None)
|
||||
}
|
||||
val drawConfig = Config.app.game.playerDraw //m
|
||||
val maxRange = drawConfig.rangeMax * drawConfig.rangeMax //sq.m
|
||||
val ourPosition = player.Position //xyz
|
||||
val currentDistance = Vector3.DistanceSquared(ourPosition, pos) //sq.m
|
||||
val inDrawableRange = currentDistance <= maxRange
|
||||
val now = System.currentTimeMillis() //ms
|
||||
if (
|
||||
sessionLogic.zoning.zoningStatus != Zoning.Status.Deconstructing &&
|
||||
!isNotRendered && inDrawableRange
|
||||
) {
|
||||
//conditions where visibility is assured
|
||||
val durationSince = now - lastTime //ms
|
||||
lazy val previouslyInDrawableRange = Vector3.DistanceSquared(ourPosition, lastPosition) <= maxRange
|
||||
lazy val targetDelay = {
|
||||
val populationOver = math.max(
|
||||
0,
|
||||
sessionLogic.localSector.livePlayerList.size - drawConfig.populationThreshold
|
||||
)
|
||||
val distanceAdjustment = math.pow(populationOver / drawConfig.populationStep * drawConfig.rangeStep, 2) //sq.m
|
||||
val adjustedDistance = currentDistance + distanceAdjustment //sq.m
|
||||
drawConfig.ranges.lastIndexWhere { dist => adjustedDistance > dist * dist } match {
|
||||
case -1 => 1
|
||||
case index => drawConfig.delays(index)
|
||||
}
|
||||
} //ms
|
||||
if (!wasVisible ||
|
||||
!previouslyInDrawableRange ||
|
||||
durationSince > drawConfig.delayMax ||
|
||||
(!lastMsg.contains(pstateToSave) &&
|
||||
(canSeeReallyFar ||
|
||||
currentDistance < drawConfig.rangeMin * drawConfig.rangeMin ||
|
||||
sessionLogic.general.canSeeReallyFar ||
|
||||
durationSince > targetDelay
|
||||
)
|
||||
)
|
||||
) {
|
||||
//must draw
|
||||
sendResponse(
|
||||
PlayerStateMessage(
|
||||
guid,
|
||||
pos,
|
||||
vel,
|
||||
yaw,
|
||||
pitch,
|
||||
yawUpper,
|
||||
timestamp = 0, //is this okay?
|
||||
isCrouching,
|
||||
isJumping,
|
||||
jumpThrust,
|
||||
isCloaking
|
||||
)
|
||||
)
|
||||
ops.lastSeenStreamMessage.put(guid.guid, SessionAvatarHandlers.LastUpstream(Some(pstateToSave), visible=true, wasShooting, now))
|
||||
} else {
|
||||
//is visible, but skip reinforcement
|
||||
ops.lastSeenStreamMessage.put(guid.guid, SessionAvatarHandlers.LastUpstream(Some(pstateToSave), visible=true, wasShooting, lastTime))
|
||||
}
|
||||
} else {
|
||||
//conditions where the target is not currently visible
|
||||
if (wasVisible) {
|
||||
//the target was JUST PREVIOUSLY visible; one last draw to move target beyond a renderable distance
|
||||
val lat = (1 + ops.hidingPlayerRandomizer.nextInt(continent.map.scale.height.toInt)).toFloat
|
||||
sendResponse(
|
||||
PlayerStateMessage(
|
||||
guid,
|
||||
Vector3(1f, lat, 1f),
|
||||
vel=None,
|
||||
facingYaw=0f,
|
||||
facingPitch=0f,
|
||||
facingYawUpper=0f,
|
||||
timestamp=0, //is this okay?
|
||||
is_cloaked = isCloaking
|
||||
)
|
||||
)
|
||||
ops.lastSeenStreamMessage.put(guid.guid, SessionAvatarHandlers.LastUpstream(Some(pstateToSave), visible=false, wasShooting, now))
|
||||
} else {
|
||||
//skip drawing altogether
|
||||
ops.lastSeenStreamMessage.put(guid.guid, SessionAvatarHandlers.LastUpstream(Some(pstateToSave), visible=false, wasShooting, lastTime))
|
||||
}
|
||||
}
|
||||
|
||||
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, _)
|
||||
if isSameTarget && player.VisibleSlots.contains(slot) =>
|
||||
sendResponse(ObjectHeldMessage(guid, slot, unk1=true))
|
||||
//Stop using proximity terminals if player unholsters a weapon
|
||||
continent.GUID(sessionLogic.terminals.usingMedicalTerminal).collect {
|
||||
case term: Terminal with ProximityUnit => sessionLogic.terminals.StopUsingProximityUnit(term)
|
||||
}
|
||||
if (sessionLogic.zoning.zoningStatus == Zoning.Status.Deconstructing) {
|
||||
sessionLogic.zoning.spawn.stopDeconstructing()
|
||||
}
|
||||
|
||||
case AvatarResponse.ObjectHeld(slot, _)
|
||||
if isSameTarget && slot > -1 =>
|
||||
sendResponse(ObjectHeldMessage(guid, slot, unk1=true))
|
||||
|
||||
case AvatarResponse.ObjectHeld(_, _)
|
||||
if isSameTarget => ()
|
||||
|
||||
case AvatarResponse.ObjectHeld(_, previousSlot) =>
|
||||
sendResponse(ObjectHeldMessage(guid, previousSlot, unk1=false))
|
||||
|
||||
case AvatarResponse.ChangeFireState_Start(weaponGuid)
|
||||
if isNotSameTarget && ops.lastSeenStreamMessage.get(guid.guid).exists { _.visible } =>
|
||||
sendResponse(ChangeFireStateMessage_Start(weaponGuid))
|
||||
val entry = ops.lastSeenStreamMessage(guid.guid)
|
||||
ops.lastSeenStreamMessage.put(guid.guid, entry.copy(shooting = Some(weaponGuid)))
|
||||
|
||||
case AvatarResponse.ChangeFireState_Start(weaponGuid)
|
||||
if isNotSameTarget =>
|
||||
sendResponse(ChangeFireStateMessage_Start(weaponGuid))
|
||||
|
||||
case AvatarResponse.ChangeFireState_Stop(weaponGuid)
|
||||
if isNotSameTarget && ops.lastSeenStreamMessage.get(guid.guid).exists { msg => msg.visible || msg.shooting.nonEmpty } =>
|
||||
sendResponse(ChangeFireStateMessage_Stop(weaponGuid))
|
||||
val entry = ops.lastSeenStreamMessage(guid.guid)
|
||||
ops.lastSeenStreamMessage.put(guid.guid, entry.copy(shooting = None))
|
||||
|
||||
case AvatarResponse.ChangeFireState_Stop(weaponGuid)
|
||||
if isNotSameTarget =>
|
||||
sendResponse(ChangeFireStateMessage_Stop(weaponGuid))
|
||||
|
||||
case AvatarResponse.LoadPlayer(pkt) if isNotSameTarget =>
|
||||
sendResponse(pkt)
|
||||
|
||||
case AvatarResponse.EquipmentInHand(pkt) if isNotSameTarget =>
|
||||
sendResponse(pkt)
|
||||
|
||||
case AvatarResponse.PlanetsideAttribute(attributeType, attributeValue) if isNotSameTarget =>
|
||||
sendResponse(PlanetsideAttributeMessage(guid, attributeType, attributeValue))
|
||||
|
||||
case AvatarResponse.PlanetsideAttributeToAll(attributeType, attributeValue) =>
|
||||
sendResponse(PlanetsideAttributeMessage(guid, attributeType, attributeValue))
|
||||
|
||||
case AvatarResponse.PlanetsideAttributeSelf(attributeType, attributeValue) if isSameTarget =>
|
||||
sendResponse(PlanetsideAttributeMessage(guid, attributeType, attributeValue))
|
||||
|
||||
case AvatarResponse.GenericObjectAction(objectGuid, actionCode) if isNotSameTarget =>
|
||||
sendResponse(GenericObjectActionMessage(objectGuid, actionCode))
|
||||
|
||||
case AvatarResponse.HitHint(sourceGuid) if player.isAlive =>
|
||||
sendResponse(HitHint(sourceGuid, guid))
|
||||
sessionLogic.zoning.CancelZoningProcess()
|
||||
|
||||
case AvatarResponse.Destroy(victim, killer, weapon, pos) =>
|
||||
// guid = victim // killer = killer
|
||||
sendResponse(DestroyMessage(victim, killer, weapon, pos))
|
||||
|
||||
case AvatarResponse.DestroyDisplay(killer, victim, method, unk) =>
|
||||
sendResponse(ops.destroyDisplayMessage(killer, victim, method, unk))
|
||||
|
||||
case AvatarResponse.TerminalOrderResult(terminalGuid, action, result)
|
||||
if result && (action == TransactionType.Buy || action == TransactionType.Loadout) =>
|
||||
sendResponse(ItemTransactionResultMessage(terminalGuid, action, result))
|
||||
sessionLogic.terminals.lastTerminalOrderFulfillment = true
|
||||
AvatarActor.savePlayerData(player)
|
||||
|
||||
case AvatarResponse.TerminalOrderResult(terminalGuid, action, result) =>
|
||||
sendResponse(ItemTransactionResultMessage(terminalGuid, action, result))
|
||||
sessionLogic.terminals.lastTerminalOrderFulfillment = true
|
||||
|
||||
case AvatarResponse.ChangeExosuit(
|
||||
target,
|
||||
armor,
|
||||
exosuit,
|
||||
subtype,
|
||||
_,
|
||||
maxhand,
|
||||
oldHolsters,
|
||||
holsters,
|
||||
oldInventory,
|
||||
inventory,
|
||||
drop,
|
||||
delete
|
||||
) if resolvedPlayerGuid == target =>
|
||||
sendResponse(ArmorChangedMessage(target, exosuit, subtype))
|
||||
sendResponse(PlanetsideAttributeMessage(target, attribute_type=4, armor))
|
||||
//happening to this player
|
||||
//cleanup
|
||||
sendResponse(ObjectHeldMessage(target, Player.HandsDownSlot, unk1=false))
|
||||
(oldHolsters ++ oldInventory ++ delete).foreach {
|
||||
case (_, dguid) => sendResponse(ObjectDeleteMessage(dguid, unk1=0))
|
||||
}
|
||||
//functionally delete
|
||||
delete.foreach { case (obj, _) => TaskWorkflow.execute(GUIDTask.unregisterEquipment(continent.GUID, obj)) }
|
||||
//redraw
|
||||
if (maxhand) {
|
||||
TaskWorkflow.execute(HoldNewEquipmentUp(player)(
|
||||
Tool(GlobalDefinitions.MAXArms(subtype, player.Faction)),
|
||||
0
|
||||
))
|
||||
}
|
||||
//draw free hand
|
||||
player.FreeHand.Equipment.foreach { obj =>
|
||||
val definition = obj.Definition
|
||||
sendResponse(
|
||||
ObjectCreateDetailedMessage(
|
||||
definition.ObjectId,
|
||||
obj.GUID,
|
||||
ObjectCreateMessageParent(target, Player.FreeHandSlot),
|
||||
definition.Packet.DetailedConstructorData(obj).get
|
||||
)
|
||||
)
|
||||
}
|
||||
//draw holsters and inventory
|
||||
(holsters ++ inventory).foreach {
|
||||
case InventoryItem(obj, index) =>
|
||||
val definition = obj.Definition
|
||||
sendResponse(
|
||||
ObjectCreateDetailedMessage(
|
||||
definition.ObjectId,
|
||||
obj.GUID,
|
||||
ObjectCreateMessageParent(target, index),
|
||||
definition.Packet.DetailedConstructorData(obj).get
|
||||
)
|
||||
)
|
||||
}
|
||||
DropLeftovers(player)(drop)
|
||||
|
||||
case AvatarResponse.ChangeExosuit(target, armor, exosuit, subtype, slot, _, oldHolsters, holsters, _, _, drop, delete) =>
|
||||
sendResponse(ArmorChangedMessage(target, exosuit, subtype))
|
||||
sendResponse(PlanetsideAttributeMessage(target, attribute_type=4, armor))
|
||||
//happening to some other player
|
||||
sendResponse(ObjectHeldMessage(target, slot, unk1 = false))
|
||||
//cleanup
|
||||
val dropPred = ContainableBehavior.DropPredicate(player)
|
||||
val deleteFromDrop = drop.filterNot(dropPred)
|
||||
(oldHolsters ++ delete ++ deleteFromDrop.map(f =>(f.obj, f.GUID)))
|
||||
.distinctBy(_._2)
|
||||
.foreach { case (_, guid) => sendResponse(ObjectDeleteMessage(guid, unk1=0)) }
|
||||
//draw holsters
|
||||
holsters.foreach {
|
||||
case InventoryItem(obj, index) =>
|
||||
val definition = obj.Definition
|
||||
sendResponse(
|
||||
ObjectCreateMessage(
|
||||
definition.ObjectId,
|
||||
obj.GUID,
|
||||
ObjectCreateMessageParent(target, index),
|
||||
definition.Packet.ConstructorData(obj).get
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
case AvatarResponse.ChangeLoadout(
|
||||
target,
|
||||
armor,
|
||||
exosuit,
|
||||
subtype,
|
||||
_,
|
||||
maxhand,
|
||||
oldHolsters,
|
||||
holsters,
|
||||
oldInventory,
|
||||
inventory,
|
||||
drops
|
||||
) if resolvedPlayerGuid == target =>
|
||||
sendResponse(ArmorChangedMessage(target, exosuit, subtype))
|
||||
sendResponse(PlanetsideAttributeMessage(target, attribute_type=4, armor))
|
||||
//happening to this player
|
||||
sendResponse(ObjectHeldMessage(target, Player.HandsDownSlot, unk1=true))
|
||||
//cleanup
|
||||
(oldHolsters ++ oldInventory).foreach {
|
||||
case (obj, objGuid) =>
|
||||
sendResponse(ObjectDeleteMessage(objGuid, unk1=0))
|
||||
TaskWorkflow.execute(GUIDTask.unregisterEquipment(continent.GUID, obj))
|
||||
}
|
||||
drops.foreach(item => sendResponse(ObjectDeleteMessage(item.obj.GUID, unk1=0)))
|
||||
//redraw
|
||||
if (maxhand) {
|
||||
sendResponse(PlanetsideAttributeMessage(target, attribute_type=7, player.Capacitor.toLong))
|
||||
TaskWorkflow.execute(HoldNewEquipmentUp(player)(
|
||||
Tool(GlobalDefinitions.MAXArms(subtype, player.Faction)),
|
||||
slot = 0
|
||||
))
|
||||
}
|
||||
(holsters ++ inventory).foreach { case InventoryItem(item, slot) =>
|
||||
TaskWorkflow.execute(PutLoadoutEquipmentInInventory(player)(item, slot))
|
||||
}
|
||||
DropLeftovers(player)(drops)
|
||||
|
||||
case AvatarResponse.ChangeLoadout(target, armor, exosuit, subtype, slot, _, oldHolsters, _, _, _, _) =>
|
||||
//redraw handled by callbacks
|
||||
sendResponse(ArmorChangedMessage(target, exosuit, subtype))
|
||||
sendResponse(PlanetsideAttributeMessage(target, attribute_type=4, armor))
|
||||
//happening to some other player
|
||||
sendResponse(ObjectHeldMessage(target, slot, unk1=false))
|
||||
//cleanup
|
||||
oldHolsters.foreach { case (_, guid) => sendResponse(ObjectDeleteMessage(guid, unk1=0)) }
|
||||
|
||||
case AvatarResponse.UseKit(kguid, kObjId) =>
|
||||
sendResponse(
|
||||
UseItemMessage(
|
||||
resolvedPlayerGuid,
|
||||
kguid,
|
||||
resolvedPlayerGuid,
|
||||
unk2 = 4294967295L,
|
||||
unk3 = false,
|
||||
unk4 = Vector3.Zero,
|
||||
unk5 = Vector3.Zero,
|
||||
unk6 = 126,
|
||||
unk7 = 0, //sequence time?
|
||||
unk8 = 137,
|
||||
kObjId
|
||||
)
|
||||
)
|
||||
sendResponse(ObjectDeleteMessage(kguid, unk1=0))
|
||||
|
||||
case AvatarResponse.KitNotUsed(_, "") =>
|
||||
sessionLogic.general.kitToBeUsed = None
|
||||
|
||||
case AvatarResponse.KitNotUsed(_, msg) =>
|
||||
sessionLogic.general.kitToBeUsed = None
|
||||
sendResponse(ChatMsg(ChatMessageType.UNK_225, msg))
|
||||
|
||||
case AvatarResponse.SendResponse(msg) =>
|
||||
sendResponse(msg)
|
||||
|
||||
case AvatarResponse.SendResponseTargeted(targetGuid, msg) if resolvedPlayerGuid == targetGuid =>
|
||||
sendResponse(msg)
|
||||
|
||||
/* common messages (maybe once every respawn) */
|
||||
case AvatarResponse.Reload(itemGuid)
|
||||
if isNotSameTarget && ops.lastSeenStreamMessage.get(guid.guid).exists { _.visible } =>
|
||||
sendResponse(ReloadMessage(itemGuid, ammo_clip=1, unk1=0))
|
||||
|
||||
case AvatarResponse.Killed(_, mount) =>
|
||||
//pure logic
|
||||
sessionLogic.shooting.shotsWhileDead = 0
|
||||
sessionLogic.zoning.CancelZoningProcess()
|
||||
sessionLogic.keepAliveFunc = sessionLogic.zoning.NormalKeepAlive
|
||||
sessionLogic.zoning.zoningStatus = Zoning.Status.None
|
||||
continent.GUID(mount).collect {
|
||||
case obj: Vehicle if obj.Destroyed =>
|
||||
ops.killedWhileMounted(obj, resolvedPlayerGuid)
|
||||
sessionLogic.vehicles.ConditionalDriverVehicleControl(obj)
|
||||
sessionLogic.general.unaccessContainer(obj)
|
||||
|
||||
case obj: Vehicle =>
|
||||
ops.killedWhileMounted(obj, resolvedPlayerGuid)
|
||||
sessionLogic.vehicles.ConditionalDriverVehicleControl(obj)
|
||||
|
||||
case obj: PlanetSideGameObject with Mountable with Container if obj.Destroyed =>
|
||||
ops.killedWhileMounted(obj, resolvedPlayerGuid)
|
||||
sessionLogic.general.unaccessContainer(obj)
|
||||
|
||||
case obj: PlanetSideGameObject with Mountable with Container =>
|
||||
ops.killedWhileMounted(obj, resolvedPlayerGuid)
|
||||
|
||||
case obj: PlanetSideGameObject with Mountable =>
|
||||
ops.killedWhileMounted(obj, resolvedPlayerGuid)
|
||||
}
|
||||
//player state changes
|
||||
sessionLogic.general.dropSpecialSlotItem()
|
||||
sessionLogic.general.toggleMaxSpecialState(enable = false)
|
||||
player.FreeHand.Equipment.foreach(DropEquipmentFromInventory(player)(_))
|
||||
AvatarActor.updateToolDischargeFor(avatar)
|
||||
AvatarActor.savePlayerLocation(player)
|
||||
ops.revive(player.GUID)
|
||||
avatarActor ! AvatarActor.InitializeImplants
|
||||
//render
|
||||
CustomerServiceRepresentativeMode.renderPlayer(sessionLogic, continent, player)
|
||||
|
||||
case AvatarResponse.Release(tplayer) if isNotSameTarget =>
|
||||
sessionLogic.zoning.spawn.DepictPlayerAsCorpse(tplayer)
|
||||
|
||||
case AvatarResponse.Revive(revivalTargetGuid)
|
||||
if resolvedPlayerGuid == revivalTargetGuid =>
|
||||
ops.revive(revivalTargetGuid)
|
||||
|
||||
/* uncommon messages (utility, or once in a while) */
|
||||
case AvatarResponse.ChangeAmmo(weapon_guid, weapon_slot, previous_guid, ammo_id, ammo_guid, ammo_data)
|
||||
if isNotSameTarget && ops.lastSeenStreamMessage.get(guid.guid).exists { _.visible } =>
|
||||
ops.changeAmmoProcedures(weapon_guid, previous_guid, ammo_id, ammo_guid, weapon_slot, ammo_data)
|
||||
sendResponse(ChangeAmmoMessage(weapon_guid, 1))
|
||||
|
||||
case AvatarResponse.ChangeAmmo(weapon_guid, weapon_slot, previous_guid, ammo_id, ammo_guid, ammo_data)
|
||||
if isNotSameTarget =>
|
||||
ops.changeAmmoProcedures(weapon_guid, previous_guid, ammo_id, ammo_guid, weapon_slot, ammo_data)
|
||||
|
||||
case AvatarResponse.ChangeFireMode(itemGuid, mode) if isNotSameTarget =>
|
||||
sendResponse(ChangeFireModeMessage(itemGuid, mode))
|
||||
|
||||
case AvatarResponse.ConcealPlayer() =>
|
||||
sendResponse(GenericObjectActionMessage(guid, code=9))
|
||||
|
||||
case AvatarResponse.EnvironmentalDamage(_, _, _) =>
|
||||
//TODO damage marker?
|
||||
sessionLogic.zoning.CancelZoningProcess()
|
||||
|
||||
case AvatarResponse.DropItem(pkt) if isNotSameTarget =>
|
||||
sendResponse(pkt)
|
||||
|
||||
case AvatarResponse.ObjectDelete(itemGuid, unk) if isNotSameTarget =>
|
||||
sendResponse(ObjectDeleteMessage(itemGuid, unk))
|
||||
|
||||
/* rare messages */
|
||||
case AvatarResponse.SetEmpire(objectGuid, faction) if isNotSameTarget =>
|
||||
sendResponse(SetEmpireMessage(objectGuid, faction))
|
||||
|
||||
case AvatarResponse.DropSpecialItem() =>
|
||||
sessionLogic.general.dropSpecialSlotItem()
|
||||
|
||||
case AvatarResponse.OxygenState(player, vehicle) =>
|
||||
sendResponse(OxygenStateMessage(
|
||||
DrowningTarget(player.guid, player.progress, player.state),
|
||||
vehicle.flatMap { vinfo => Some(DrowningTarget(vinfo.guid, vinfo.progress, vinfo.state)) }
|
||||
))
|
||||
|
||||
case AvatarResponse.LoadProjectile(pkt) if isNotSameTarget =>
|
||||
sendResponse(pkt)
|
||||
|
||||
case AvatarResponse.ProjectileState(projectileGuid, shotPos, shotVel, shotOrient, seq, end, targetGuid) if isNotSameTarget =>
|
||||
sendResponse(ProjectileStateMessage(projectileGuid, shotPos, shotVel, shotOrient, seq, end, targetGuid))
|
||||
|
||||
case AvatarResponse.ProjectileExplodes(projectileGuid, projectile) =>
|
||||
sendResponse(
|
||||
ProjectileStateMessage(
|
||||
projectileGuid,
|
||||
projectile.Position,
|
||||
shot_vel = Vector3.Zero,
|
||||
projectile.Orientation,
|
||||
sequence_num=0,
|
||||
end=true,
|
||||
hit_target_guid=PlanetSideGUID(0)
|
||||
)
|
||||
)
|
||||
sendResponse(ObjectDeleteMessage(projectileGuid, unk1=2))
|
||||
|
||||
case AvatarResponse.ProjectileAutoLockAwareness(mode) =>
|
||||
sendResponse(GenericActionMessage(mode))
|
||||
|
||||
case AvatarResponse.PutDownFDU(target) if isNotSameTarget =>
|
||||
sendResponse(GenericObjectActionMessage(target, code=53))
|
||||
|
||||
case AvatarResponse.StowEquipment(target, slot, item) if isNotSameTarget =>
|
||||
val definition = item.Definition
|
||||
sendResponse(
|
||||
ObjectCreateDetailedMessage(
|
||||
definition.ObjectId,
|
||||
item.GUID,
|
||||
ObjectCreateMessageParent(target, slot),
|
||||
definition.Packet.DetailedConstructorData(item).get
|
||||
)
|
||||
)
|
||||
|
||||
case AvatarResponse.WeaponDryFire(weaponGuid)
|
||||
if isNotSameTarget && ops.lastSeenStreamMessage.get(guid.guid).exists { _.visible } =>
|
||||
continent.GUID(weaponGuid).collect {
|
||||
case tool: Tool if tool.Magazine == 0 =>
|
||||
// check that the magazine is still empty before sending WeaponDryFireMessage
|
||||
// if it has been reloaded since then, other clients will not see it firing
|
||||
sendResponse(WeaponDryFireMessage(weaponGuid))
|
||||
}
|
||||
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
}
|
||||
419
src/main/scala/net/psforever/actors/session/csr/ChatLogic.scala
Normal file
419
src/main/scala/net/psforever/actors/session/csr/ChatLogic.scala
Normal file
|
|
@ -0,0 +1,419 @@
|
|||
// Copyright (c) 2024 PSForever
|
||||
package net.psforever.actors.session.csr
|
||||
|
||||
import akka.actor.ActorContext
|
||||
import net.psforever.actors.session.SessionActor
|
||||
import net.psforever.actors.session.normal.NormalMode
|
||||
import net.psforever.actors.session.support.{ChatFunctions, ChatOperations, SessionData}
|
||||
import net.psforever.objects.{GlobalDefinitions, PlanetSideGameObject, Session, TurretDeployable}
|
||||
import net.psforever.objects.ce.{Deployable, DeployableCategory}
|
||||
import net.psforever.objects.serverobject.affinity.FactionAffinity
|
||||
import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject}
|
||||
import net.psforever.objects.serverobject.hackable.Hackable
|
||||
import net.psforever.objects.serverobject.structures.Building
|
||||
import net.psforever.objects.zones.Zone
|
||||
import net.psforever.packet.game.{ChatMsg, SetChatFilterMessage}
|
||||
import net.psforever.services.Service
|
||||
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||
import net.psforever.services.chat.{ChatChannel, DefaultChannel, SpectatorChannel}
|
||||
import net.psforever.types.ChatMessageType.{CMT_TOGGLESPECTATORMODE, CMT_TOGGLE_GM}
|
||||
import net.psforever.types.{ChatMessageType, PlanetSideEmpire}
|
||||
|
||||
import scala.util.Success
|
||||
|
||||
object ChatLogic {
|
||||
def apply(ops: ChatOperations): ChatLogic = {
|
||||
new ChatLogic(ops, ops.context)
|
||||
}
|
||||
}
|
||||
|
||||
class ChatLogic(val ops: ChatOperations, implicit val context: ActorContext) extends ChatFunctions {
|
||||
ops.transitoryCommandEntered match {
|
||||
case Some(CMT_TOGGLESPECTATORMODE) =>
|
||||
//we are transitioning down from csr spectator mode to normal mode, continue to block transitory messages
|
||||
()
|
||||
case _ =>
|
||||
//correct player mode
|
||||
ops.transitoryCommandEntered = None
|
||||
}
|
||||
|
||||
def sessionLogic: SessionData = ops.sessionLogic
|
||||
|
||||
ops.CurrentSpectatorMode = SpectateAsCustomerServiceRepresentativeMode
|
||||
|
||||
private var comms: ChatChannel = DefaultChannel
|
||||
private var seeSpectatorsIn: Option[Zone] = None
|
||||
|
||||
def handleChatMsg(message: ChatMsg): Unit = {
|
||||
import net.psforever.types.ChatMessageType._
|
||||
val isAlive = if (player != null) player.isAlive else false
|
||||
(message.messageType, message.recipient.trim, message.contents.trim) match {
|
||||
/** Messages starting with ! are custom chat commands */
|
||||
case (_, _, contents) if contents.startsWith("!") &&
|
||||
customCommandMessages(message, session) => ()
|
||||
|
||||
case (CMT_FLY, recipient, contents) =>
|
||||
ops.commandFly(contents, recipient)
|
||||
|
||||
case (CMT_ANONYMOUS, _, _) => ()
|
||||
|
||||
case (CMT_TOGGLE_GM, _, contents) =>
|
||||
customCommandModerator(contents)
|
||||
|
||||
case (CMT_CULLWATERMARK, _, contents) =>
|
||||
ops.commandWatermark(contents)
|
||||
|
||||
case (CMT_SPEED, _, contents) =>
|
||||
ops.commandSpeed(message, contents)
|
||||
|
||||
case (CMT_TOGGLESPECTATORMODE, _, contents) if isAlive =>
|
||||
commandToggleSpectatorMode(contents)
|
||||
|
||||
case (CMT_RECALL, _, _) =>
|
||||
ops.commandRecall(session)
|
||||
|
||||
case (CMT_INSTANTACTION, _, _) =>
|
||||
ops.commandInstantAction(session)
|
||||
|
||||
case (CMT_QUIT, _, _) =>
|
||||
ops.commandQuit(session)
|
||||
|
||||
case (CMT_SUICIDE, _, _) =>
|
||||
ops.commandSuicide(session)
|
||||
|
||||
case (CMT_DESTROY, _, contents) if contents.matches("\\d+") =>
|
||||
ops.commandDestroy(session, message, contents)
|
||||
|
||||
case (CMT_SETBASERESOURCES, _, contents) =>
|
||||
ops.commandSetBaseResources(session, contents)
|
||||
|
||||
case (CMT_ZONELOCK, _, contents) =>
|
||||
ops.commandZoneLock(contents)
|
||||
|
||||
case (U_CMT_ZONEROTATE, _, _) =>
|
||||
ops.commandZoneRotate()
|
||||
|
||||
case (CMT_CAPTUREBASE, _, contents) =>
|
||||
ops.commandCaptureBase(session, message, contents)
|
||||
|
||||
case (CMT_GMBROADCAST | CMT_GMBROADCAST_NC | CMT_GMBROADCAST_VS | CMT_GMBROADCAST_TR, _, _) =>
|
||||
ops.commandSendToRecipient(session, message, comms)
|
||||
|
||||
case (CMT_GMTELL, _, _) =>
|
||||
ops.commandSend(session, message, comms)
|
||||
|
||||
case (CMT_GMBROADCASTPOPUP, _, _) =>
|
||||
ops.commandSendToRecipient(session, message, comms)
|
||||
|
||||
case (CMT_OPEN, _, _) if !player.silenced =>
|
||||
ops.commandSendToRecipient(session, message, comms)
|
||||
|
||||
case (CMT_VOICE, _, contents) =>
|
||||
ops.commandVoice(session, message, contents, comms)
|
||||
|
||||
case (CMT_TELL, _, _) if !player.silenced =>
|
||||
ops.commandTellOrIgnore(session, message, comms)
|
||||
|
||||
case (CMT_BROADCAST, _, _) if !player.silenced =>
|
||||
ops.commandSendToRecipient(session, message, comms)
|
||||
|
||||
case (CMT_PLATOON, _, _) if !player.silenced =>
|
||||
ops.commandSendToRecipient(session, message, comms)
|
||||
|
||||
case (CMT_COMMAND, _, _) =>
|
||||
ops.commandSendToRecipient(session, message, comms)
|
||||
|
||||
case (CMT_NOTE, _, _) =>
|
||||
ops.commandSend(session, message, comms)
|
||||
|
||||
case (CMT_SILENCE, _, _) =>
|
||||
ops.commandSend(session, message, comms)
|
||||
|
||||
case (CMT_SQUAD, _, _) =>
|
||||
ops.commandSquad(session, message, comms) //todo SquadChannel, but what is the guid
|
||||
|
||||
case (CMT_WHO | CMT_WHO_CSR | CMT_WHO_CR | CMT_WHO_PLATOONLEADERS | CMT_WHO_SQUADLEADERS | CMT_WHO_TEAMS, _, _) =>
|
||||
ops.commandWho(session)
|
||||
|
||||
case (CMT_ZONE, _, contents) =>
|
||||
ops.commandZone(message, contents)
|
||||
|
||||
case (CMT_WARP, _, contents) =>
|
||||
ops.commandWarp(session, message, contents)
|
||||
|
||||
case (CMT_SETBATTLERANK, _, contents) =>
|
||||
ops.commandSetBattleRank(session, message, contents)
|
||||
|
||||
case (CMT_SETCOMMANDRANK, _, contents) =>
|
||||
ops.commandSetCommandRank(session, message, contents)
|
||||
|
||||
case (CMT_ADDBATTLEEXPERIENCE, _, contents) =>
|
||||
ops.commandAddBattleExperience(message, contents)
|
||||
|
||||
case (CMT_ADDCOMMANDEXPERIENCE, _, contents) =>
|
||||
ops.commandAddCommandExperience(message, contents)
|
||||
|
||||
case (CMT_TOGGLE_HAT, _, contents) =>
|
||||
ops.commandToggleHat(session, message, contents)
|
||||
|
||||
case (CMT_HIDE_HELMET | CMT_TOGGLE_SHADES | CMT_TOGGLE_EARPIECE, _, contents) =>
|
||||
ops.commandToggleCosmetics(session, message, contents)
|
||||
|
||||
case (CMT_ADDCERTIFICATION, _, contents) =>
|
||||
ops.commandAddCertification(session, message, contents)
|
||||
|
||||
case (CMT_KICK, _, contents) =>
|
||||
ops.commandKick(session, message, contents)
|
||||
|
||||
case _ =>
|
||||
log.warn(s"Unhandled chat message $message")
|
||||
}
|
||||
}
|
||||
|
||||
def handleChatFilter(pkt: SetChatFilterMessage): Unit = {
|
||||
val SetChatFilterMessage(_, _, _) = pkt
|
||||
}
|
||||
|
||||
def handleIncomingMessage(message: ChatMsg, fromSession: Session): Unit = {
|
||||
import ChatMessageType._
|
||||
message.messageType match {
|
||||
case CMT_BROADCAST | CMT_SQUAD | CMT_PLATOON | CMT_COMMAND | CMT_NOTE =>
|
||||
ops.commandIncomingSendAllIfOnline(session, message)
|
||||
|
||||
case CMT_OPEN =>
|
||||
ops.commandIncomingSendToLocalIfOnline(session, fromSession, message)
|
||||
|
||||
case CMT_TELL | U_CMT_TELLFROM |
|
||||
CMT_GMOPEN | CMT_GMBROADCAST | CMT_GMBROADCAST_NC | CMT_GMBROADCAST_TR | CMT_GMBROADCAST_VS |
|
||||
CMT_GMBROADCASTPOPUP | CMT_GMTELL | U_CMT_GMTELLFROM | UNK_45 | UNK_71 | UNK_227 | UNK_229 =>
|
||||
ops.commandIncomingSend(message)
|
||||
|
||||
case CMT_VOICE =>
|
||||
ops.commandIncomingVoice(session, fromSession, message)
|
||||
|
||||
case CMT_SILENCE =>
|
||||
ops.commandIncomingSilence(session, message)
|
||||
|
||||
case _ =>
|
||||
log.warn(s"Unexpected messageType $message")
|
||||
}
|
||||
}
|
||||
|
||||
private def customCommandMessages(
|
||||
message: ChatMsg,
|
||||
session: Session
|
||||
): Boolean = {
|
||||
val contents = message.contents
|
||||
if (contents.startsWith("!")) {
|
||||
val (command, params) = ops.cliTokenization(contents.drop(1)) match {
|
||||
case a :: b => (a, b)
|
||||
case _ => ("", Seq(""))
|
||||
}
|
||||
command match {
|
||||
case "loc" => ops.customCommandLoc(session, message)
|
||||
case "suicide" => ops.customCommandSuicide(session)
|
||||
case "grenade" => ops.customCommandGrenade(session, log)
|
||||
case "macro" => ops.customCommandMacro(session, params)
|
||||
case "progress" => ops.customCommandProgress(session, params)
|
||||
case "whitetext" => ops.customCommandWhitetext(session, params)
|
||||
case "list" => ops.customCommandList(session, params, message)
|
||||
case "ntu" => ops.customCommandNtu(session, params)
|
||||
case "zonerotate" => ops.customCommandZonerotate(params)
|
||||
case "nearby" => ops.customCommandNearby(session)
|
||||
case "togglespectators" => customCommandToggleSpectators(params)
|
||||
case "showspectators" => customCommandShowSpectators()
|
||||
case "hidespectators" => customCommandHideSpectators()
|
||||
case "sayspectator" => customCommandSpeakAsSpectator(params, message)
|
||||
case "setempire" => customCommandSetEmpire(params)
|
||||
case _ =>
|
||||
// command was not handled
|
||||
sendResponse(
|
||||
ChatMsg(
|
||||
ChatMessageType.CMT_GMOPEN, // CMT_GMTELL
|
||||
message.wideContents,
|
||||
"Server",
|
||||
s"Unknown command !$command",
|
||||
message.note
|
||||
)
|
||||
)
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
private def commandToggleSpectatorMode(contents: String): Unit = {
|
||||
contents.toLowerCase() match {
|
||||
case "on" | "o" | "" if !player.spectator =>
|
||||
context.self ! SessionActor.SetMode(SpectateAsCustomerServiceRepresentativeMode)
|
||||
case "off" | "of" if player.spectator =>
|
||||
context.self ! SessionActor.SetMode(CustomerServiceRepresentativeMode)
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
|
||||
private def customCommandModerator(contents: String): Boolean = {
|
||||
if (sessionLogic.zoning.maintainInitialGmState) {
|
||||
sessionLogic.zoning.maintainInitialGmState = false
|
||||
true
|
||||
} else {
|
||||
ops.transitoryCommandEntered
|
||||
.collect {
|
||||
case CMT_TOGGLE_GM => true
|
||||
case CMT_TOGGLESPECTATORMODE => false
|
||||
}
|
||||
.getOrElse {
|
||||
contents.toLowerCase() match {
|
||||
case "off" | "of" if player.spectator =>
|
||||
ops.transitoryCommandEntered = Some(CMT_TOGGLESPECTATORMODE)
|
||||
context.self ! SessionActor.SetMode(CustomerServiceRepresentativeMode)
|
||||
context.self ! SessionActor.SetMode(NormalMode)
|
||||
true
|
||||
case "off" | "of" =>
|
||||
ops.transitoryCommandEntered = Some(CMT_TOGGLE_GM)
|
||||
context.self ! SessionActor.SetMode(NormalMode)
|
||||
true
|
||||
case _ =>
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def customCommandToggleSpectators(contents: Seq[String]): Boolean = {
|
||||
contents
|
||||
.headOption
|
||||
.map(_.toLowerCase())
|
||||
.collect {
|
||||
case "on" | "o" | "" if !seeSpectatorsIn.contains(continent) =>
|
||||
customCommandShowSpectators()
|
||||
case "off" | "of" if seeSpectatorsIn.contains(continent) =>
|
||||
customCommandHideSpectators()
|
||||
case _ => ()
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
private def customCommandShowSpectators(): Boolean = {
|
||||
val channel = player.Name
|
||||
val events = continent.AvatarEvents
|
||||
seeSpectatorsIn = Some(continent)
|
||||
events ! Service.Join(s"spectator")
|
||||
continent
|
||||
.AllPlayers
|
||||
.filter(_.spectator)
|
||||
.foreach { spectator =>
|
||||
val guid = spectator.GUID
|
||||
val definition = spectator.Definition
|
||||
events ! AvatarServiceMessage(
|
||||
channel,
|
||||
AvatarAction.LoadPlayer(guid, definition.ObjectId, guid, definition.Packet.ConstructorData(spectator).get, None)
|
||||
)
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
private def customCommandHideSpectators(): Boolean = {
|
||||
val channel = player.Name
|
||||
val events = continent.AvatarEvents
|
||||
seeSpectatorsIn = None
|
||||
events ! Service.Leave(Some("spectator"))
|
||||
continent
|
||||
.AllPlayers
|
||||
.filter(_.spectator)
|
||||
.foreach { spectator =>
|
||||
val guid = spectator.GUID
|
||||
events ! AvatarServiceMessage(
|
||||
channel,
|
||||
AvatarAction.ObjectDelete(guid, guid)
|
||||
)
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
private def customCommandSpeakAsSpectator(params: Seq[String], message: ChatMsg): Boolean = {
|
||||
comms = SpectatorChannel
|
||||
handleChatMsg(message.copy(contents = params.mkString(" ")))
|
||||
comms = DefaultChannel
|
||||
true
|
||||
}
|
||||
|
||||
private def customCommandSetEmpire(params: Seq[String]): Boolean = {
|
||||
var postUsage: Boolean = false
|
||||
val (entityOpt, foundFaction) = (params.headOption, params.lift(1)) match {
|
||||
case (Some(guid), Some(faction)) if guid.toIntOption.nonEmpty =>
|
||||
try {
|
||||
(continent.GUID(guid.toInt), PlanetSideEmpire.apply(faction))
|
||||
} catch {
|
||||
case _: Exception =>
|
||||
(None, PlanetSideEmpire.NEUTRAL)
|
||||
}
|
||||
case (Some(guid), None) if guid.toIntOption.nonEmpty =>
|
||||
(continent.GUID(guid.toInt), player.Faction)
|
||||
case _ =>
|
||||
postUsage = true
|
||||
(None, PlanetSideEmpire.NEUTRAL)
|
||||
}
|
||||
entityOpt
|
||||
.collect {
|
||||
case f: FactionAffinity if f.Faction != foundFaction && foundFaction != PlanetSideEmpire.NEUTRAL => f
|
||||
}
|
||||
.collect {
|
||||
case o: TurretDeployable
|
||||
if o.Definition.DeployCategory == DeployableCategory.FieldTurrets =>
|
||||
//remove prior turret and construct new one
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
import scala.concurrent.duration._
|
||||
o.Actor ! Deployable.Deconstruct(Some(2.seconds))
|
||||
sessionLogic.general.handleDeployObject(
|
||||
continent,
|
||||
GlobalDefinitions.PortableMannedTurret(foundFaction).Item,
|
||||
o.Position,
|
||||
o.Orientation,
|
||||
o.WhichSide,
|
||||
foundFaction
|
||||
).onComplete {
|
||||
case Success(obj2) => sendResponse(ChatMsg(ChatMessageType.UNK_227, s"${obj2.GUID.guid}"))
|
||||
case _ => ()
|
||||
}
|
||||
true
|
||||
case o: Deployable =>
|
||||
o.Faction = foundFaction
|
||||
continent.AvatarEvents ! AvatarServiceMessage(
|
||||
continent.id,
|
||||
AvatarAction.SetEmpire(Service.defaultPlayerGUID, o.GUID, foundFaction)
|
||||
)
|
||||
true
|
||||
case o: Building =>
|
||||
ops.commandCaptureBaseProcessResults(Some(Seq(o)), Some(foundFaction), Some(1))
|
||||
true
|
||||
case o: PlanetSideServerObject with Hackable =>
|
||||
o.Actor ! CommonMessages.Hack(player, o)
|
||||
true
|
||||
case o: PlanetSideGameObject with FactionAffinity =>
|
||||
o.Faction = foundFaction
|
||||
continent.AvatarEvents ! AvatarServiceMessage(
|
||||
continent.id,
|
||||
AvatarAction.SetEmpire(Service.defaultPlayerGUID, o.GUID, foundFaction)
|
||||
)
|
||||
true
|
||||
}
|
||||
.getOrElse {
|
||||
if (postUsage) {
|
||||
sendResponse(ChatMsg(ChatMessageType.UNK_227, "!setempire guid [faction]"))
|
||||
} else if (entityOpt.nonEmpty) {
|
||||
sendResponse(ChatMsg(ChatMessageType.UNK_227, "set empire entity not supported"))
|
||||
} else {
|
||||
sendResponse(ChatMsg(ChatMessageType.UNK_227, "set empire entity not found"))
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
override def stop(): Unit = {
|
||||
super.stop()
|
||||
seeSpectatorsIn.foreach(_ => customCommandHideSpectators())
|
||||
seeSpectatorsIn = None
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,204 @@
|
|||
// Copyright (c) 2024 PSForever
|
||||
package net.psforever.actors.session.csr
|
||||
|
||||
import net.psforever.actors.session.support.{ChatFunctions, GalaxyHandlerFunctions, GeneralFunctions, LocalHandlerFunctions, ModeLogic, MountHandlerFunctions, PlayerMode, SessionData, SquadHandlerFunctions, TerminalHandlerFunctions, VehicleFunctions, VehicleHandlerFunctions, WeaponAndProjectileFunctions}
|
||||
import net.psforever.objects.{Deployables, PlanetSideGameObject, Player, Session, Vehicle}
|
||||
import net.psforever.objects.avatar.Certification
|
||||
import net.psforever.objects.serverobject.ServerObject
|
||||
import net.psforever.objects.serverobject.mount.Mountable
|
||||
import net.psforever.objects.vital.Vitality
|
||||
import net.psforever.objects.zones.Zone
|
||||
import net.psforever.objects.zones.blockmap.BlockMapEntity
|
||||
import net.psforever.packet.game.{ChatMsg, ObjectCreateDetailedMessage, PlanetsideAttributeMessage}
|
||||
import net.psforever.packet.game.objectcreate.RibbonBars
|
||||
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||
import net.psforever.services.chat.{CustomerServiceChannel, SpectatorChannel}
|
||||
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
|
||||
import net.psforever.types.{ChatMessageType, MeritCommendation, PlanetSideGUID}
|
||||
|
||||
class CustomerServiceRepresentativeMode(data: SessionData) extends ModeLogic {
|
||||
val avatarResponse: AvatarHandlerLogic = AvatarHandlerLogic(data.avatarResponse)
|
||||
val chat: ChatFunctions = ChatLogic(data.chat)
|
||||
val galaxy: GalaxyHandlerFunctions = net.psforever.actors.session.normal.GalaxyHandlerLogic(data.galaxyResponseHandlers)
|
||||
val general: GeneralFunctions = GeneralLogic(data.general)
|
||||
val local: LocalHandlerFunctions = net.psforever.actors.session.normal.LocalHandlerLogic(data.localResponse)
|
||||
val mountResponse: MountHandlerFunctions = MountHandlerLogic(data.mountResponse)
|
||||
val shooting: WeaponAndProjectileFunctions = WeaponAndProjectileLogic(data.shooting)
|
||||
val squad: SquadHandlerFunctions = SquadHandlerLogic(data.squad)
|
||||
val terminals: TerminalHandlerFunctions = TerminalHandlerLogic(data.terminals)
|
||||
val vehicles: VehicleFunctions = VehicleLogic(data.vehicles)
|
||||
val vehicleResponse: VehicleHandlerFunctions = net.psforever.actors.session.normal.VehicleHandlerLogic(data.vehicleResponseOperations)
|
||||
|
||||
private var oldRibbons: RibbonBars = RibbonBars()
|
||||
private var oldCertifications : Set[Certification] = Set()
|
||||
|
||||
override def switchTo(session: Session): Unit = {
|
||||
val player = session.player
|
||||
val avatar = session.avatar
|
||||
val continent = session.zone
|
||||
//
|
||||
data.zoning.displayZoningMessageWhenCancelled = false
|
||||
if (oldCertifications.isEmpty) {
|
||||
oldCertifications = avatar.certifications
|
||||
oldRibbons = avatar.decoration.ribbonBars
|
||||
val newAvatar = avatar.copy(
|
||||
certifications = Certification.values.toSet,
|
||||
decoration = avatar.decoration.copy(ribbonBars = RibbonBars(
|
||||
MeritCommendation.CSAppreciation,
|
||||
MeritCommendation.Loser,
|
||||
MeritCommendation.Loser,
|
||||
MeritCommendation.CSAppreciation
|
||||
))
|
||||
)
|
||||
player.avatar = newAvatar
|
||||
data.session = session.copy(avatar = newAvatar, player = player)
|
||||
Deployables.InitializeDeployableQuantities(newAvatar)
|
||||
}
|
||||
requireDismount(continent, player)
|
||||
data.keepAlivePersistenceFunc = keepAlivePersistanceCSR
|
||||
//
|
||||
CustomerServiceRepresentativeMode.renderPlayer(data, continent, player)
|
||||
player.allowInteraction = false
|
||||
if (player.silenced) {
|
||||
data.chat.commandIncomingSilence(session, ChatMsg(ChatMessageType.CMT_SILENCE, "player 0"))
|
||||
}
|
||||
data.chat.JoinChannel(SpectatorChannel)
|
||||
data.chat.JoinChannel(CustomerServiceChannel)
|
||||
data.sendResponse(ChatMsg(ChatMessageType.UNK_225, "CSR MODE ON"))
|
||||
}
|
||||
|
||||
override def switchFrom(session: Session): Unit = {
|
||||
val player = session.player
|
||||
val avatar = session.avatar
|
||||
val continent = session.zone
|
||||
//
|
||||
data.zoning.displayZoningMessageWhenCancelled = true
|
||||
val newAvatar = avatar.copy(
|
||||
certifications = oldCertifications,
|
||||
decoration = avatar.decoration.copy(ribbonBars = oldRibbons)
|
||||
)
|
||||
oldCertifications = Set()
|
||||
oldRibbons = RibbonBars()
|
||||
player.avatar = newAvatar
|
||||
data.session = session.copy(avatar = newAvatar, player = player)
|
||||
Deployables.InitializeDeployableQuantities(newAvatar)
|
||||
//
|
||||
requireDismount(continent, player)
|
||||
data.keepAlivePersistenceFunc = data.keepAlivePersistence
|
||||
//
|
||||
CustomerServiceRepresentativeMode.renderPlayer(data, continent, player)
|
||||
player.allowInteraction = true
|
||||
data.chat.LeaveChannel(SpectatorChannel)
|
||||
data.chat.LeaveChannel(CustomerServiceChannel)
|
||||
data.sendResponse(ChatMsg(ChatMessageType.UNK_225, "CSR MODE OFF"))
|
||||
}
|
||||
|
||||
private def requireDismount(zone: Zone, player: Player): Unit = {
|
||||
data.vehicles.GetMountableAndSeat(None, player, zone) match {
|
||||
case (Some(obj: Vehicle), Some(seatNum)) if seatNum == 0 =>
|
||||
data.vehicles.ServerVehicleOverrideStop(obj)
|
||||
obj.Actor ! ServerObject.AttributeMsg(10, 3) //faction-accessible driver seat
|
||||
obj.Actor ! Mountable.TryDismount(player, seatNum)
|
||||
player.VehicleSeated = None
|
||||
case (Some(obj), Some(seatNum)) =>
|
||||
obj.Actor ! Mountable.TryDismount(player, seatNum)
|
||||
player.VehicleSeated = None
|
||||
case _ =>
|
||||
player.VehicleSeated = None
|
||||
}
|
||||
}
|
||||
|
||||
private def keepAlivePersistanceCSR(): Unit = {
|
||||
val player = data.player
|
||||
data.keepAlivePersistence()
|
||||
topOffHealthOfPlayer(player)
|
||||
player.allowInteraction = false
|
||||
topOffHealthOfPlayer(player)
|
||||
data.continent.GUID(data.player.VehicleSeated)
|
||||
.collect {
|
||||
case obj: PlanetSideGameObject with Vitality with BlockMapEntity =>
|
||||
topOffHealth(obj)
|
||||
data.updateBlockMap(obj, obj.Position)
|
||||
obj
|
||||
}
|
||||
.getOrElse {
|
||||
data.updateBlockMap(player, player.Position)
|
||||
}
|
||||
}
|
||||
|
||||
private def topOffHealth(obj: PlanetSideGameObject with Vitality): Unit = {
|
||||
obj match {
|
||||
case p: Player => topOffHealthOfPlayer(p)
|
||||
case v: Vehicle => topOffHealthOfVehicle(v)
|
||||
case o: PlanetSideGameObject with Vitality => topOffHealthOfGeneric(o)
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
|
||||
private def topOffHealthOfPlayer(player: Player): Unit = {
|
||||
//driver below half health, full heal
|
||||
val maxHealthOfPlayer = player.MaxHealth.toLong
|
||||
if (player.Health < maxHealthOfPlayer * 0.5f) {
|
||||
player.Health = maxHealthOfPlayer.toInt
|
||||
player.LogActivity(player.ClearHistory().head)
|
||||
data.sendResponse(PlanetsideAttributeMessage(player.GUID, 0, maxHealthOfPlayer))
|
||||
data.continent.AvatarEvents ! AvatarServiceMessage(data.zoning.zoneChannel, AvatarAction.PlanetsideAttribute(player.GUID, 0, maxHealthOfPlayer))
|
||||
}
|
||||
}
|
||||
|
||||
private def topOffHealthOfVehicle(vehicle: Vehicle): Unit = {
|
||||
topOffHealthOfGeneric(vehicle)
|
||||
//vehicle shields below half, full shields
|
||||
val maxShieldsOfVehicle = vehicle.MaxShields.toLong
|
||||
val shieldsUi = vehicle.Definition.shieldUiAttribute
|
||||
if (vehicle.Shields < maxShieldsOfVehicle) {
|
||||
val guid = vehicle.GUID
|
||||
vehicle.Shields = maxShieldsOfVehicle.toInt
|
||||
data.sendResponse(PlanetsideAttributeMessage(guid, shieldsUi, maxShieldsOfVehicle))
|
||||
data.continent.VehicleEvents ! VehicleServiceMessage(
|
||||
data.continent.id,
|
||||
VehicleAction.PlanetsideAttribute(PlanetSideGUID(0), guid, shieldsUi, maxShieldsOfVehicle)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private def topOffHealthOfGeneric(obj: PlanetSideGameObject with Vitality): Unit = {
|
||||
//below half health, full heal
|
||||
val guid = obj.GUID
|
||||
val maxHealthOf = obj.MaxHealth.toLong
|
||||
if (obj.Health < maxHealthOf) {
|
||||
obj.Health = maxHealthOf.toInt
|
||||
data.sendResponse(PlanetsideAttributeMessage(guid, 0, maxHealthOf))
|
||||
data.continent.VehicleEvents ! VehicleServiceMessage(
|
||||
data.continent.id,
|
||||
VehicleAction.PlanetsideAttribute(PlanetSideGUID(0), guid, 0, maxHealthOf)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case object CustomerServiceRepresentativeMode extends PlayerMode {
|
||||
def setup(data: SessionData): ModeLogic = {
|
||||
new CustomerServiceRepresentativeMode(data)
|
||||
}
|
||||
|
||||
private[csr] def renderPlayer(data: SessionData, zone: Zone, player: Player): Unit = {
|
||||
val pguid = player.GUID
|
||||
val definition = player.Definition
|
||||
val objectClass = definition.ObjectId
|
||||
val packet = definition.Packet
|
||||
data.sendResponse(ObjectCreateDetailedMessage(
|
||||
objectClass,
|
||||
pguid,
|
||||
packet.DetailedConstructorData(player).get
|
||||
))
|
||||
data.zoning.spawn.HandleSetCurrentAvatar(player)
|
||||
zone.AvatarEvents ! AvatarServiceMessage(zone.id, AvatarAction.LoadPlayer(
|
||||
pguid,
|
||||
objectClass,
|
||||
pguid,
|
||||
packet.ConstructorData(player).get,
|
||||
None
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,781 @@
|
|||
// Copyright (c) 2024 PSForever
|
||||
package net.psforever.actors.session.csr
|
||||
|
||||
import akka.actor.{ActorContext, ActorRef, typed}
|
||||
import net.psforever.actors.session.AvatarActor
|
||||
import net.psforever.actors.session.support.{GeneralFunctions, GeneralOperations, SessionData}
|
||||
import net.psforever.objects.{Account, BoomerDeployable, BoomerTrigger, ConstructionItem, GlobalDefinitions, LivePlayerList, Player, SensorDeployable, ShieldGeneratorDeployable, SpecialEmp, TelepadDeployable, Tool, TrapDeployable, TurretDeployable, Vehicle}
|
||||
import net.psforever.objects.avatar.{Avatar, PlayerControl}
|
||||
import net.psforever.objects.ballistics.Projectile
|
||||
import net.psforever.objects.ce.Deployable
|
||||
import net.psforever.objects.definition.{BasicDefinition, KitDefinition, SpecialExoSuitDefinition}
|
||||
import net.psforever.objects.entity.WorldEntity
|
||||
import net.psforever.objects.equipment.Equipment
|
||||
import net.psforever.objects.inventory.Container
|
||||
import net.psforever.objects.serverobject.{CommonMessages, ServerObject}
|
||||
import net.psforever.objects.serverobject.containable.Containable
|
||||
import net.psforever.objects.serverobject.doors.Door
|
||||
import net.psforever.objects.serverobject.generator.Generator
|
||||
import net.psforever.objects.serverobject.llu.CaptureFlag
|
||||
import net.psforever.objects.serverobject.locks.IFFLock
|
||||
import net.psforever.objects.serverobject.mblocker.Locker
|
||||
import net.psforever.objects.serverobject.resourcesilo.ResourceSilo
|
||||
import net.psforever.objects.serverobject.structures.WarpGate
|
||||
import net.psforever.objects.serverobject.terminals.capture.CaptureTerminal
|
||||
import net.psforever.objects.serverobject.terminals.{ProximityUnit, Terminal}
|
||||
import net.psforever.objects.serverobject.terminals.implant.ImplantTerminalMech
|
||||
import net.psforever.objects.serverobject.tube.SpawnTube
|
||||
import net.psforever.objects.serverobject.turret.FacilityTurret
|
||||
import net.psforever.objects.vehicles.Utility
|
||||
import net.psforever.objects.vital.Vitality
|
||||
import net.psforever.objects.zones.{ZoneProjectile, Zoning}
|
||||
import net.psforever.packet.PlanetSideGamePacket
|
||||
import net.psforever.packet.game.{ActionCancelMessage, AvatarFirstTimeEventMessage, AvatarImplantMessage, AvatarJumpMessage, BattleplanMessage, BindPlayerMessage, BugReportMessage, ChangeFireModeMessage, ChangeShortcutBankMessage, CharacterCreateRequestMessage, CharacterRequestMessage, ChatMsg, CollisionIs, ConnectToWorldRequestMessage, CreateShortcutMessage, DeadState, DeployObjectMessage, DisplayedAwardMessage, DropItemMessage, EmoteMsg, FacilityBenefitShieldChargeRequestMessage, FriendsRequest, GenericAction, GenericActionMessage, GenericCollisionMsg, GenericObjectActionAtPositionMessage, GenericObjectActionMessage, GenericObjectStateMsg, HitHint, InvalidTerrainMessage, LootItemMessage, MoveItemMessage, ObjectDetectedMessage, ObjectHeldMessage, PickupItemMessage, PlanetsideAttributeMessage, PlayerStateMessageUpstream, RequestDestroyMessage, TargetingImplantRequest, TerrainCondition, TradeMessage, UnuseItemMessage, UseItemMessage, VoiceHostInfo, VoiceHostRequest, ZipLineMessage}
|
||||
import net.psforever.services.RemoverActor
|
||||
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||
import net.psforever.types.{CapacitorStateType, ChatMessageType, Cosmetic, ExoSuitType, PlanetSideEmpire, PlanetSideGUID, Vector3}
|
||||
|
||||
import scala.util.Success
|
||||
|
||||
object GeneralLogic {
|
||||
def apply(ops: GeneralOperations): GeneralLogic = {
|
||||
new GeneralLogic(ops, ops.context)
|
||||
}
|
||||
}
|
||||
|
||||
class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContext) extends GeneralFunctions {
|
||||
def sessionLogic: SessionData = ops.sessionLogic
|
||||
|
||||
private val avatarActor: typed.ActorRef[AvatarActor.Command] = ops.avatarActor
|
||||
|
||||
def handleConnectToWorldRequest(pkt: ConnectToWorldRequestMessage): Unit = { /* intentionally blank */ }
|
||||
|
||||
def handleCharacterCreateRequest(pkt: CharacterCreateRequestMessage): Unit = { /* intentionally blank */ }
|
||||
|
||||
def handleCharacterRequest(pkt: CharacterRequestMessage): Unit = { /* intentionally blank */ }
|
||||
|
||||
def handlePlayerStateUpstream(pkt: PlayerStateMessageUpstream): Unit = {
|
||||
val PlayerStateMessageUpstream(
|
||||
avatarGuid,
|
||||
pos,
|
||||
vel,
|
||||
yaw,
|
||||
pitch,
|
||||
yawUpper,
|
||||
seqTime,
|
||||
_,
|
||||
isCrouching,
|
||||
isJumping,
|
||||
jumpThrust,
|
||||
isCloaking,
|
||||
_,
|
||||
_
|
||||
) = pkt
|
||||
sessionLogic.persist()
|
||||
sessionLogic.turnCounterFunc(avatarGuid)
|
||||
sessionLogic.updateBlockMap(player, pos)
|
||||
//below half health, full heal
|
||||
val maxHealth = player.MaxHealth.toLong
|
||||
if (player.Health < maxHealth) {
|
||||
player.Health = maxHealth.toInt
|
||||
player.LogActivity(player.ClearHistory().head)
|
||||
sendResponse(PlanetsideAttributeMessage(avatarGuid, 0, maxHealth))
|
||||
continent.AvatarEvents ! AvatarServiceMessage(continent.id, AvatarAction.PlanetsideAttribute(avatarGuid, 0, maxHealth))
|
||||
}
|
||||
//below half stamina, full stamina
|
||||
val avatar = player.avatar
|
||||
val maxStamina = avatar.maxStamina
|
||||
if (avatar.stamina < maxStamina) {
|
||||
avatarActor ! AvatarActor.RestoreStamina(maxStamina)
|
||||
sendResponse(PlanetsideAttributeMessage(player.GUID, 2, maxStamina.toLong))
|
||||
}
|
||||
//below half armor, full armor
|
||||
val maxArmor = player.MaxArmor.toLong
|
||||
if (player.Armor < maxArmor) {
|
||||
player.Armor = maxArmor.toInt
|
||||
sendResponse(PlanetsideAttributeMessage(avatarGuid, 4, maxArmor))
|
||||
continent.AvatarEvents ! AvatarServiceMessage(continent.id, AvatarAction.PlanetsideAttribute(avatarGuid, 4, maxArmor))
|
||||
}
|
||||
//expected
|
||||
val isMoving = WorldEntity.isMoving(vel)
|
||||
val isMovingPlus = isMoving || isJumping || jumpThrust
|
||||
if (isMovingPlus) {
|
||||
if (sessionLogic.zoning.zoningStatus == Zoning.Status.Deconstructing) {
|
||||
sessionLogic.zoning.spawn.stopDeconstructing()
|
||||
} else if (sessionLogic.zoning.zoningStatus != Zoning.Status.None) {
|
||||
sessionLogic.zoning.CancelZoningProcess()
|
||||
}
|
||||
}
|
||||
ops.fallHeightTracker(pos.z)
|
||||
// if (isCrouching && !player.Crouching) {
|
||||
// //dev stuff goes here
|
||||
// }
|
||||
player.Position = pos
|
||||
player.Velocity = vel
|
||||
player.Orientation = Vector3(player.Orientation.x, pitch, yaw)
|
||||
player.FacingYawUpper = yawUpper
|
||||
player.Crouching = isCrouching
|
||||
player.Jumping = isJumping
|
||||
if (isCloaking && !player.Cloaked) {
|
||||
sessionLogic.zoning.CancelZoningProcess()
|
||||
}
|
||||
player.Cloaked = player.ExoSuit == ExoSuitType.Infiltration && isCloaking
|
||||
maxCapacitorTick(jumpThrust)
|
||||
if (isMovingPlus && sessionLogic.terminals.usingMedicalTerminal.isDefined) {
|
||||
continent.GUID(sessionLogic.terminals.usingMedicalTerminal) match {
|
||||
case Some(term: Terminal with ProximityUnit) =>
|
||||
sessionLogic.terminals.StopUsingProximityUnit(term)
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
ops.accessedContainer match {
|
||||
// Ensure we don't unload the contents of the vehicle trunk for players seated in the vehicle.
|
||||
// This can happen if PSUM arrives during the mounting process
|
||||
case Some(container)
|
||||
if !container.HasGUID && (player.VehicleSeated.isEmpty || player.VehicleSeated.get != container.GUID) => //just in case
|
||||
// Ensure we don't close the container if the player is seated in it
|
||||
val guid = player.GUID
|
||||
// If the container is a corpse and gets removed just as this runs it can cause a client disconnect, so we'll check the container has a GUID first.
|
||||
sendResponse(UnuseItemMessage(guid, guid))
|
||||
ops.unaccessContainer(container)
|
||||
case _ => ()
|
||||
}
|
||||
val channel = if (!player.spectator) {
|
||||
sessionLogic.updateBlockMap(player, pos)
|
||||
continent.id
|
||||
} else {
|
||||
"spectator"
|
||||
}
|
||||
val eagleEye: Boolean = ops.canSeeReallyFar
|
||||
val isNotVisible: Boolean = sessionLogic.zoning.zoningStatus == Zoning.Status.Deconstructing ||
|
||||
(player.isAlive && sessionLogic.zoning.spawn.deadState == DeadState.RespawnTime)
|
||||
continent.AvatarEvents ! AvatarServiceMessage(
|
||||
channel,
|
||||
AvatarAction.PlayerState(
|
||||
avatarGuid,
|
||||
player.Position,
|
||||
player.Velocity,
|
||||
yaw,
|
||||
pitch,
|
||||
yawUpper,
|
||||
seqTime,
|
||||
isCrouching,
|
||||
isJumping,
|
||||
jumpThrust,
|
||||
isCloaking,
|
||||
isNotVisible,
|
||||
eagleEye
|
||||
)
|
||||
)
|
||||
sessionLogic.squad.updateSquad()
|
||||
player.allowInteraction = false
|
||||
}
|
||||
|
||||
def handleVoiceHostRequest(pkt: VoiceHostRequest): Unit = {
|
||||
ops.noVoicedChat(pkt)
|
||||
}
|
||||
|
||||
def handleVoiceHostInfo(pkt: VoiceHostInfo): Unit = {
|
||||
ops.noVoicedChat(pkt)
|
||||
}
|
||||
|
||||
def handleEmote(pkt: EmoteMsg): Unit = {
|
||||
val EmoteMsg(avatarGuid, emote) = pkt
|
||||
sendResponse(EmoteMsg(avatarGuid, emote))
|
||||
}
|
||||
|
||||
def handleDropItem(pkt: DropItemMessage): Unit = {
|
||||
ops.handleDropItem(pkt) match {
|
||||
case GeneralOperations.ItemDropState.Dropped =>
|
||||
sessionLogic.zoning.CancelZoningProcess()
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
|
||||
def handlePickupItem(pkt: PickupItemMessage): Unit = {
|
||||
ops.handlePickupItem(pkt) match {
|
||||
case GeneralOperations.ItemPickupState.PickedUp =>
|
||||
sessionLogic.zoning.CancelZoningProcess()
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
|
||||
def handleObjectHeld(pkt: ObjectHeldMessage): Unit = {
|
||||
val ObjectHeldMessage(_, heldHolsters, _) = pkt
|
||||
player.Actor ! PlayerControl.ObjectHeld(heldHolsters)
|
||||
}
|
||||
|
||||
def handleAvatarJump(pkt: AvatarJumpMessage): Unit = { /* no stamina loss */ }
|
||||
|
||||
def handleZipLine(pkt: ZipLineMessage): Unit = {
|
||||
ops.handleZipLine(pkt) match {
|
||||
case GeneralOperations.ZiplineBehavior.Teleporter | GeneralOperations.ZiplineBehavior.Zipline =>
|
||||
sessionLogic.zoning.CancelZoningProcess()
|
||||
case _ =>
|
||||
()
|
||||
}
|
||||
}
|
||||
|
||||
def handleRequestDestroy(pkt: RequestDestroyMessage): Unit = {
|
||||
val RequestDestroyMessage(objectGuid) = pkt
|
||||
//make sure this is the correct response for all cases
|
||||
sessionLogic.validObject(objectGuid, decorator = "RequestDestroy") match {
|
||||
case Some(vehicle: Vehicle) =>
|
||||
vehicle.Actor ! Vehicle.Deconstruct()
|
||||
|
||||
case Some(obj: Projectile) =>
|
||||
if (!obj.isResolved) {
|
||||
obj.Miss()
|
||||
}
|
||||
continent.Projectile ! ZoneProjectile.Remove(objectGuid)
|
||||
|
||||
case Some(obj: BoomerTrigger) =>
|
||||
if (ops.findEquipmentToDelete(objectGuid, obj)) {
|
||||
continent.GUID(obj.Companion) match {
|
||||
case Some(boomer: BoomerDeployable) =>
|
||||
boomer.Trigger = None
|
||||
boomer.Actor ! Deployable.Deconstruct()
|
||||
case Some(thing) =>
|
||||
log.warn(s"RequestDestroy: BoomerTrigger object connected to wrong object - $thing")
|
||||
case None => ()
|
||||
}
|
||||
}
|
||||
|
||||
case Some(obj: Deployable) =>
|
||||
obj.Actor ! Deployable.Deconstruct()
|
||||
|
||||
case Some(obj: Equipment) =>
|
||||
ops.findEquipmentToDelete(objectGuid, obj)
|
||||
|
||||
case Some(obj: Player) if obj.isBackpack =>
|
||||
obj.Position = Vector3.Zero
|
||||
continent.AvatarEvents ! AvatarServiceMessage.Corpse(RemoverActor.ClearSpecific(List(obj), continent))
|
||||
|
||||
case Some(obj: Player) =>
|
||||
sessionLogic.general.suicide(obj)
|
||||
|
||||
case Some(_) => ()
|
||||
|
||||
case None => ()
|
||||
}
|
||||
}
|
||||
|
||||
def handleMoveItem(pkt: MoveItemMessage): Unit = {
|
||||
ops.handleMoveItem(pkt)
|
||||
}
|
||||
|
||||
def handleLootItem(pkt: LootItemMessage): Unit = {
|
||||
ops.handleLootItem(pkt)
|
||||
}
|
||||
|
||||
def handleAvatarImplant(pkt: AvatarImplantMessage): Unit = {
|
||||
ops.handleAvatarImplant(pkt) match {
|
||||
case GeneralOperations.ImplantActivationBehavior.Activate | GeneralOperations.ImplantActivationBehavior.Deactivate =>
|
||||
sessionLogic.zoning.CancelZoningProcess()
|
||||
case GeneralOperations.ImplantActivationBehavior.NotFound =>
|
||||
log.error(s"AvatarImplantMessage: ${player.Name} has an unknown implant in ${pkt.implantSlot}")
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
|
||||
def handleUseItem(pkt: UseItemMessage): Unit = {
|
||||
val equipment = ops.findContainedEquipment(pkt.item_used_guid) match {
|
||||
case (o @ Some(_), a) if a.exists(_.isInstanceOf[Tool]) =>
|
||||
sessionLogic.shooting.FindEnabledWeaponsToHandleWeaponFireAccountability(o, a.collect { case w: Tool => w })._2.headOption
|
||||
case (Some(_), a) =>
|
||||
a.headOption
|
||||
case _ =>
|
||||
None
|
||||
}
|
||||
sessionLogic.validObject(pkt.object_guid, decorator = "UseItem") match {
|
||||
case Some(door: Door) =>
|
||||
handleUseDoor(door, equipment)
|
||||
case Some(resourceSilo: ResourceSilo) =>
|
||||
ops.handleUseResourceSilo(resourceSilo, equipment)
|
||||
case Some(panel: IFFLock) =>
|
||||
ops.handleUseGeneralEntity(panel, equipment)
|
||||
case Some(obj: Player) =>
|
||||
ops.handleUsePlayer(obj, equipment, pkt)
|
||||
case Some(locker: Locker) =>
|
||||
ops.handleUseLocker(locker, equipment, pkt)
|
||||
case Some(gen: Generator) =>
|
||||
ops.handleUseGeneralEntity(gen, equipment)
|
||||
case Some(mech: ImplantTerminalMech) =>
|
||||
ops.handleUseGeneralEntity(mech, equipment)
|
||||
case Some(captureTerminal: CaptureTerminal) =>
|
||||
ops.handleUseCaptureTerminal(captureTerminal, equipment)
|
||||
case Some(obj: FacilityTurret) =>
|
||||
ops.handleUseFacilityTurret(obj, equipment, pkt)
|
||||
case Some(obj: Vehicle) =>
|
||||
ops.handleUseVehicle(obj, equipment, pkt)
|
||||
case Some(terminal: Terminal) =>
|
||||
ops.handleUseTerminal(terminal, equipment, pkt)
|
||||
case Some(obj: SpawnTube) =>
|
||||
ops.handleUseSpawnTube(obj, equipment)
|
||||
case Some(obj: SensorDeployable) =>
|
||||
ops.handleUseGeneralEntity(obj, equipment)
|
||||
case Some(obj: TurretDeployable) =>
|
||||
ops.handleUseGeneralEntity(obj, equipment)
|
||||
case Some(obj: TrapDeployable) =>
|
||||
ops.handleUseGeneralEntity(obj, equipment)
|
||||
case Some(obj: ShieldGeneratorDeployable) =>
|
||||
ops.handleUseGeneralEntity(obj, equipment)
|
||||
case Some(obj: TelepadDeployable) if player.spectator =>
|
||||
ops.handleUseTelepadDeployable(obj, equipment, pkt, ops.useRouterTelepadSystemSecretly)
|
||||
case Some(obj: Utility.InternalTelepad) if player.spectator =>
|
||||
ops.handleUseInternalTelepad(obj, pkt, ops.useRouterTelepadSystemSecretly)
|
||||
case Some(obj: TelepadDeployable) =>
|
||||
ops.handleUseTelepadDeployable(obj, equipment, pkt, ops.useRouterTelepadSystem)
|
||||
case Some(obj: Utility.InternalTelepad) =>
|
||||
ops.handleUseInternalTelepad(obj, pkt, ops.useRouterTelepadSystem)
|
||||
case Some(obj: CaptureFlag) =>
|
||||
ops.handleUseCaptureFlag(obj)
|
||||
case Some(_: WarpGate) =>
|
||||
ops.handleUseWarpGate(equipment)
|
||||
case Some(obj) =>
|
||||
ops.handleUseDefaultEntity(obj, equipment)
|
||||
case None => ()
|
||||
}
|
||||
}
|
||||
|
||||
def handleUnuseItem(pkt: UnuseItemMessage): Unit = {
|
||||
val UnuseItemMessage(_, objectGuid) = pkt
|
||||
sessionLogic.validObject(objectGuid, decorator = "UnuseItem") match {
|
||||
case Some(obj: Player) =>
|
||||
ops.unaccessContainer(obj)
|
||||
sessionLogic.zoning.spawn.TryDisposeOfLootedCorpse(obj)
|
||||
case Some(obj: Container) =>
|
||||
// Make sure we don't unload the contents of the vehicle the player is seated in
|
||||
// An example scenario of this would be closing the trunk contents when rearming at a landing pad
|
||||
if (player.VehicleSeated.isEmpty || player.VehicleSeated.get != obj.GUID) {
|
||||
ops.unaccessContainer(obj)
|
||||
}
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
|
||||
def handleDeployObject(pkt: DeployObjectMessage): Unit = {
|
||||
if (!player.spectator) {
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
val DeployObjectMessage(guid, _, pos, orient, _) = pkt
|
||||
player.Holsters().find(slot => slot.Equipment.nonEmpty && slot.Equipment.get.GUID == guid).flatMap { slot => slot.Equipment } match {
|
||||
case Some(obj: ConstructionItem) =>
|
||||
sessionLogic.zoning.CancelZoningProcess()
|
||||
val result = ops.handleDeployObject(continent, obj.AmmoType, pos, orient, player.WhichSide, PlanetSideEmpire.NEUTRAL)
|
||||
result.onComplete {
|
||||
case Success(obj) => sendResponse(ChatMsg(ChatMessageType.UNK_227, s"${obj.GUID.guid}"))
|
||||
case _ => ()
|
||||
}
|
||||
case Some(obj) =>
|
||||
log.warn(s"DeployObject: what is $obj, ${player.Name}? It's not a construction tool!")
|
||||
case None =>
|
||||
log.error(s"DeployObject: nothing, ${player.Name}? It's not a construction tool!")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def handlePlanetsideAttribute(pkt: PlanetsideAttributeMessage): Unit = {
|
||||
val PlanetsideAttributeMessage(objectGuid, attributeType, attributeValue) = pkt
|
||||
sessionLogic.validObject(objectGuid, decorator = "PlanetsideAttribute") match {
|
||||
case Some(vehicle: Vehicle) =>
|
||||
vehicle.Actor ! ServerObject.AttributeMsg(attributeType, attributeValue)
|
||||
// Cosmetics options
|
||||
case Some(_: Player) if attributeType == 106 =>
|
||||
avatarActor ! AvatarActor.SetCosmetics(Cosmetic.valuesFromAttributeValue(attributeValue))
|
||||
case Some(obj) =>
|
||||
log.trace(s"PlanetsideAttribute: ${player.Name} does not know how to apply unknown attributes behavior $attributeType to ${obj.Definition.Name}")
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
|
||||
def handleGenericObjectAction(pkt: GenericObjectActionMessage): Unit = {
|
||||
val GenericObjectActionMessage(objectGuid, code) = pkt
|
||||
sessionLogic.validObject(objectGuid, decorator = "GenericObjectAction") match {
|
||||
case Some(vehicle: Vehicle)
|
||||
if vehicle.OwnerName.contains(player.Name) =>
|
||||
vehicle.Actor ! ServerObject.GenericObjectAction(objectGuid, code, Some(player.GUID))
|
||||
|
||||
case Some(tool: Tool) =>
|
||||
if (code == 35 &&
|
||||
(tool.Definition == GlobalDefinitions.maelstrom || tool.Definition.Name.startsWith("aphelion_laser"))
|
||||
) {
|
||||
//maelstrom primary fire mode discharge (no target)
|
||||
//aphelion_laser discharge (no target)
|
||||
sessionLogic.shooting.handleWeaponFireAccountability(objectGuid, PlanetSideGUID(Projectile.baseUID))
|
||||
} else {
|
||||
sessionLogic.validObject(player.VehicleSeated, decorator = "GenericObjectAction/Vehicle") collect {
|
||||
case vehicle: Vehicle
|
||||
if vehicle.OwnerName.contains(player.Name) =>
|
||||
vehicle.Actor ! ServerObject.GenericObjectAction(objectGuid, code, Some(tool))
|
||||
}
|
||||
}
|
||||
case _ =>
|
||||
log.info(s"${player.Name} - $pkt")
|
||||
}
|
||||
}
|
||||
|
||||
def handleGenericObjectActionAtPosition(pkt: GenericObjectActionAtPositionMessage): Unit = {
|
||||
val GenericObjectActionAtPositionMessage(objectGuid, _, _) = pkt
|
||||
sessionLogic.validObject(objectGuid, decorator = "GenericObjectActionAtPosition") match {
|
||||
case Some(tool: Tool) if GlobalDefinitions.isBattleFrameNTUSiphon(tool.Definition) =>
|
||||
sessionLogic.shooting.FindContainedWeapon match {
|
||||
case (Some(vehicle: Vehicle), weps) if weps.exists(_.GUID == objectGuid) =>
|
||||
vehicle.Actor ! SpecialEmp.Burst()
|
||||
case _ => ()
|
||||
}
|
||||
case _ =>
|
||||
log.info(s"${player.Name} - $pkt")
|
||||
}
|
||||
}
|
||||
|
||||
def handleGenericObjectState(pkt: GenericObjectStateMsg): Unit = {
|
||||
val GenericObjectStateMsg(_, _) = pkt
|
||||
log.info(s"${player.Name} - $pkt")
|
||||
}
|
||||
|
||||
def handleGenericAction(pkt: GenericActionMessage): Unit = {
|
||||
val GenericActionMessage(action) = pkt
|
||||
if (player != null) {
|
||||
val (toolOpt, definition) = player.Slot(0).Equipment match {
|
||||
case Some(tool: Tool) =>
|
||||
(Some(tool), tool.Definition)
|
||||
case _ =>
|
||||
(None, GlobalDefinitions.bullet_9mm)
|
||||
}
|
||||
action match {
|
||||
case GenericAction.DropSpecialItem =>
|
||||
ops.dropSpecialSlotItem()
|
||||
case GenericAction.MaxAnchorsExtend_RCV =>
|
||||
player.UsingSpecial = SpecialExoSuitDefinition.Mode.Anchored
|
||||
continent.AvatarEvents ! AvatarServiceMessage(
|
||||
continent.id,
|
||||
AvatarAction.PlanetsideAttribute(player.GUID, 19, 1)
|
||||
)
|
||||
definition match {
|
||||
case GlobalDefinitions.trhev_dualcycler | GlobalDefinitions.trhev_burster =>
|
||||
val tool = toolOpt.get
|
||||
tool.ToFireMode = 1
|
||||
sendResponse(ChangeFireModeMessage(tool.GUID, 1))
|
||||
case GlobalDefinitions.trhev_pounder =>
|
||||
val tool = toolOpt.get
|
||||
val convertFireModeIndex = if (tool.FireModeIndex == 0) { 1 }
|
||||
else { 4 }
|
||||
tool.ToFireMode = convertFireModeIndex
|
||||
sendResponse(ChangeFireModeMessage(tool.GUID, convertFireModeIndex))
|
||||
case _ =>
|
||||
log.warn(s"GenericObject: ${player.Name} is a MAX with an unexpected attachment - ${definition.Name}")
|
||||
}
|
||||
case GenericAction.MaxAnchorsRelease_RCV =>
|
||||
player.UsingSpecial = SpecialExoSuitDefinition.Mode.Normal
|
||||
continent.AvatarEvents ! AvatarServiceMessage(
|
||||
continent.id,
|
||||
AvatarAction.PlanetsideAttribute(player.GUID, 19, 0)
|
||||
)
|
||||
definition match {
|
||||
case GlobalDefinitions.trhev_dualcycler | GlobalDefinitions.trhev_burster =>
|
||||
val tool = toolOpt.get
|
||||
tool.ToFireMode = 0
|
||||
sendResponse(ChangeFireModeMessage(tool.GUID, 0))
|
||||
case GlobalDefinitions.trhev_pounder =>
|
||||
val tool = toolOpt.get
|
||||
val convertFireModeIndex = if (tool.FireModeIndex == 1) { 0 } else { 3 }
|
||||
tool.ToFireMode = convertFireModeIndex
|
||||
sendResponse(ChangeFireModeMessage(tool.GUID, convertFireModeIndex))
|
||||
case _ =>
|
||||
log.warn(s"GenericObject: $player is MAX with an unexpected attachment - ${definition.Name}")
|
||||
}
|
||||
case GenericAction.MaxSpecialEffect_RCV =>
|
||||
if (player.ExoSuit == ExoSuitType.MAX) {
|
||||
ops.toggleMaxSpecialState(enable = true)
|
||||
} else {
|
||||
log.warn(s"GenericActionMessage: ${player.Name} can't handle MAX special effect")
|
||||
}
|
||||
case GenericAction.StopMaxSpecialEffect_RCV =>
|
||||
if (player.ExoSuit == ExoSuitType.MAX) {
|
||||
player.Faction match {
|
||||
case PlanetSideEmpire.NC =>
|
||||
ops.toggleMaxSpecialState(enable = false)
|
||||
case _ =>
|
||||
log.warn(s"GenericActionMessage: ${player.Name} tried to cancel an uncancellable MAX special ability")
|
||||
}
|
||||
} else {
|
||||
log.warn(s"GenericActionMessage: ${player.Name} can't stop MAX special effect")
|
||||
}
|
||||
case GenericAction.AwayFromKeyboard_RCV =>
|
||||
AvatarActor.savePlayerLocation(player)
|
||||
player.AwayFromKeyboard = true
|
||||
case GenericAction.BackInGame_RCV =>
|
||||
player.AwayFromKeyboard = false
|
||||
case GenericAction.LookingForSquad_RCV => //Looking For Squad ON
|
||||
if (!avatar.lookingForSquad) {
|
||||
avatarActor ! AvatarActor.SetLookingForSquad(false)
|
||||
}
|
||||
case GenericAction.NotLookingForSquad_RCV => //Looking For Squad OFF
|
||||
if (avatar.lookingForSquad) {
|
||||
avatarActor ! AvatarActor.SetLookingForSquad(false)
|
||||
}
|
||||
case _ =>
|
||||
log.warn(s"GenericActionMessage: ${player.Name} can't handle $action")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
def handleGenericCollision(pkt: GenericCollisionMsg): Unit = {
|
||||
player.BailProtection = false
|
||||
val GenericCollisionMsg(ctype, p, _, _, pv, _, _, _, _, _, _, _) = pkt
|
||||
if (pv.z * pv.z >= (pv.x * pv.x + pv.y * pv.y) * 0.5f) {
|
||||
if (ops.heightTrend) {
|
||||
ops.heightHistory = ops.heightLast
|
||||
}
|
||||
else {
|
||||
ops.heightLast = ops.heightHistory
|
||||
}
|
||||
}
|
||||
(ctype, sessionLogic.validObject(p, decorator = "GenericCollision/Primary")) match {
|
||||
case (CollisionIs.OfInfantry, Some(user: Player))
|
||||
if user == player => ()
|
||||
case (CollisionIs.OfGroundVehicle, Some(v: Vehicle))
|
||||
if v.Seats(0).occupant.contains(player) =>
|
||||
v.BailProtection = false
|
||||
case (CollisionIs.OfAircraft, Some(v: Vehicle))
|
||||
if v.Definition.CanFly && v.Seats(0).occupant.contains(player) => ()
|
||||
case (CollisionIs.BetweenThings, _) =>
|
||||
log.warn("GenericCollision: CollisionIs.BetweenThings detected - no handling case")
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
|
||||
def handleAvatarFirstTimeEvent(pkt: AvatarFirstTimeEventMessage): Unit = {
|
||||
val AvatarFirstTimeEventMessage(_, _, _, eventName) = pkt
|
||||
avatarActor ! AvatarActor.AddFirstTimeEvent(eventName)
|
||||
}
|
||||
|
||||
def handleBugReport(pkt: PlanetSideGamePacket): Unit = {
|
||||
val BugReportMessage(
|
||||
_/*versionMajor*/,
|
||||
_/*versionMinor*/,
|
||||
_/*versionDate*/,
|
||||
_/*bugType*/,
|
||||
_/*repeatable*/,
|
||||
_/*location*/,
|
||||
_/*zone*/,
|
||||
_/*pos*/,
|
||||
_/*summary*/,
|
||||
_/*desc*/
|
||||
) = pkt
|
||||
log.warn(s"${player.Name} filed a bug report - it might be something important")
|
||||
log.debug(s"$pkt")
|
||||
}
|
||||
|
||||
def handleFacilityBenefitShieldChargeRequest(pkt: FacilityBenefitShieldChargeRequestMessage): Unit = {
|
||||
val FacilityBenefitShieldChargeRequestMessage(_) = pkt
|
||||
val vehicleGuid = player.VehicleSeated
|
||||
continent
|
||||
.GUID(vehicleGuid)
|
||||
.foreach {
|
||||
case obj: Vitality if obj.Destroyed => () //some entities will try to charge even if destroyed
|
||||
case obj: Vehicle if obj.MountedIn.nonEmpty => () //cargo vehicles need to be excluded
|
||||
case obj: Vehicle => ops.commonFacilityShieldCharging(obj)
|
||||
case obj: TurretDeployable => ops.commonFacilityShieldCharging(obj)
|
||||
case _ if vehicleGuid.nonEmpty => ()
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
|
||||
def handleBattleplan(pkt: BattleplanMessage): Unit = {
|
||||
/* can not draw battleplan */
|
||||
//todo csr exclusive battleplan channel
|
||||
}
|
||||
|
||||
def handleBindPlayer(pkt: BindPlayerMessage): Unit = {
|
||||
val BindPlayerMessage(_, _, _, _, _, _, _, _) = pkt
|
||||
}
|
||||
|
||||
def handleCreateShortcut(pkt: CreateShortcutMessage): Unit = {
|
||||
val CreateShortcutMessage(_, slot, shortcutOpt) = pkt
|
||||
shortcutOpt match {
|
||||
case Some(shortcut) =>
|
||||
avatarActor ! AvatarActor.AddShortcut(slot - 1, shortcut)
|
||||
case None =>
|
||||
avatarActor ! AvatarActor.RemoveShortcut(slot - 1)
|
||||
}
|
||||
}
|
||||
|
||||
def handleChangeShortcutBank(pkt: ChangeShortcutBankMessage): Unit = {
|
||||
val ChangeShortcutBankMessage(_, _) = pkt
|
||||
}
|
||||
|
||||
def handleFriendRequest(pkt: FriendsRequest): Unit = {
|
||||
val FriendsRequest(action, name) = pkt
|
||||
avatarActor ! AvatarActor.MemberListRequest(action, name)
|
||||
}
|
||||
|
||||
def handleInvalidTerrain(pkt: InvalidTerrainMessage): Unit = {
|
||||
val InvalidTerrainMessage(_, vehicleGuid, alert, _) = pkt
|
||||
(continent.GUID(vehicleGuid), continent.GUID(player.VehicleSeated)) match {
|
||||
case (Some(packetVehicle: Vehicle), Some(playerVehicle: Vehicle)) if packetVehicle eq playerVehicle =>
|
||||
if (alert == TerrainCondition.Unsafe) {
|
||||
log.info(s"${player.Name}'s ${packetVehicle.Definition.Name} is approaching terrain unsuitable for idling")
|
||||
}
|
||||
case (Some(packetVehicle: Vehicle), Some(_: Vehicle)) =>
|
||||
if (alert == TerrainCondition.Unsafe) {
|
||||
log.info(s"${packetVehicle.Definition.Name}@${packetVehicle.GUID} is approaching terrain unsuitable for idling, but is not ${player.Name}'s vehicle")
|
||||
}
|
||||
case (Some(_: Vehicle), _) =>
|
||||
log.warn(s"InvalidTerrain: ${player.Name} is not seated in a(ny) vehicle near unsuitable terrain")
|
||||
case (Some(packetThing), _) =>
|
||||
log.warn(s"InvalidTerrain: ${player.Name} thinks that ${packetThing.Definition.Name}@${packetThing.GUID} is near unsuitable terrain")
|
||||
case _ =>
|
||||
log.error(s"InvalidTerrain: ${player.Name} is complaining about a thing@$vehicleGuid that can not be found")
|
||||
}
|
||||
}
|
||||
|
||||
def handleActionCancel(pkt: ActionCancelMessage): Unit = {
|
||||
val ActionCancelMessage(_, _, _) = pkt
|
||||
ops.progressBarUpdate.cancel()
|
||||
ops.progressBarValue = None
|
||||
}
|
||||
|
||||
def handleTrade(pkt: TradeMessage): Unit = {
|
||||
val TradeMessage(trade) = pkt
|
||||
log.trace(s"${player.Name} wants to trade for some reason - $trade")
|
||||
}
|
||||
|
||||
def handleDisplayedAward(pkt: DisplayedAwardMessage): Unit = { /* intentionally blank */ }
|
||||
|
||||
def handleObjectDetected(pkt: ObjectDetectedMessage): Unit = {
|
||||
ops.handleObjectDetected(pkt)
|
||||
}
|
||||
|
||||
def handleTargetingImplantRequest(pkt: TargetingImplantRequest): Unit = {
|
||||
ops.handleTargetingImplantRequest(pkt)
|
||||
}
|
||||
|
||||
def handleHitHint(pkt: HitHint): Unit = {
|
||||
val HitHint(_, _) = pkt
|
||||
}
|
||||
|
||||
/* messages */
|
||||
|
||||
def handleRenewCharSavedTimer(): Unit = { /* */ }
|
||||
|
||||
def handleRenewCharSavedTimerMsg(): Unit = { /* */ }
|
||||
|
||||
def handleSetAvatar(avatar: Avatar): Unit = {
|
||||
session = session.copy(avatar = avatar)
|
||||
if (session.player != null) {
|
||||
session.player.avatar = avatar
|
||||
}
|
||||
LivePlayerList.Update(avatar.id, avatar)
|
||||
}
|
||||
|
||||
def handleReceiveAccountData(account: Account): Unit = { /* no need */ }
|
||||
|
||||
def handleUseCooldownRenew: BasicDefinition => Unit = {
|
||||
case _: KitDefinition => ops.kitToBeUsed = None
|
||||
case _ => ()
|
||||
}
|
||||
|
||||
def handleAvatarResponse(avatar: Avatar): Unit = { /* no need */ }
|
||||
|
||||
def handleSetSpeed(speed: Float): Unit = {
|
||||
session = session.copy(speed = speed)
|
||||
}
|
||||
|
||||
def handleSetFlying(flying: Boolean): Unit = {
|
||||
session = session.copy(flying = flying)
|
||||
}
|
||||
|
||||
def handleSetSpectator(spectator: Boolean): Unit = {
|
||||
session.player.spectator = spectator
|
||||
}
|
||||
|
||||
def handleKick(player: Player, time: Option[Long]): Unit = {
|
||||
ops.administrativeKick(player, time)
|
||||
}
|
||||
|
||||
def handleSilenced(isSilenced: Boolean): Unit = { /* can not be silenced */ }
|
||||
|
||||
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 */
|
||||
|
||||
def handleUseDoor(door: Door, equipment: Option[Equipment]): Unit = {
|
||||
if (!player.spectator) {
|
||||
//opens for everyone
|
||||
equipment match {
|
||||
case Some(tool: Tool) if tool.Definition == GlobalDefinitions.medicalapplicator =>
|
||||
door.Actor ! CommonMessages.Use(player, Some(Float.MaxValue))
|
||||
case _ =>
|
||||
door.Actor ! CommonMessages.Use(player)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def maxCapacitorTick(jumpThrust: Boolean): Unit = {
|
||||
if (player.ExoSuit == ExoSuitType.MAX) {
|
||||
val activate = (jumpThrust || player.isOverdrived || player.isShielded) && player.Capacitor > 0
|
||||
player.CapacitorState match {
|
||||
case CapacitorStateType.Discharging => maxCapacitorTickDischarging(activate)
|
||||
case CapacitorStateType.Charging => maxCapacitorTickCharging(activate)
|
||||
case _ => maxCapacitorTickIdle(activate)
|
||||
}
|
||||
} else if (player.CapacitorState != CapacitorStateType.Idle) {
|
||||
player.CapacitorState = CapacitorStateType.Idle
|
||||
}
|
||||
}
|
||||
|
||||
private def maxCapacitorTickIdle(activate: Boolean): Unit = {
|
||||
if (activate) {
|
||||
player.CapacitorState = CapacitorStateType.Discharging
|
||||
//maxCapacitorTickDischarging(activate)
|
||||
} else if (player.Capacitor < player.ExoSuitDef.MaxCapacitor) {
|
||||
player.CapacitorState = CapacitorStateType.Charging
|
||||
}
|
||||
}
|
||||
|
||||
private def maxCapacitorTickDischarging(activate: Boolean): Unit = {
|
||||
if (activate) {
|
||||
val timeDiff = (System.currentTimeMillis() - player.CapacitorLastUsedMillis).toFloat / 1000
|
||||
val drainAmount = player.ExoSuitDef.CapacitorDrainPerSecond.toFloat * timeDiff
|
||||
player.Capacitor -= drainAmount
|
||||
sendResponse(PlanetsideAttributeMessage(player.GUID, 7, player.Capacitor.toInt))
|
||||
} else if (player.Capacitor == 0) {
|
||||
if (player.Faction == PlanetSideEmpire.TR) {
|
||||
ops.toggleMaxSpecialState(enable = false)
|
||||
}
|
||||
player.Capacitor = player.ExoSuitDef.MaxCapacitor.toFloat
|
||||
sendResponse(PlanetsideAttributeMessage(player.GUID, 7, player.Capacitor.toInt))
|
||||
player.CapacitorState = CapacitorStateType.Idle
|
||||
} else if (player.Capacitor < player.ExoSuitDef.MaxCapacitor) {
|
||||
player.CapacitorState = CapacitorStateType.Charging
|
||||
} else {
|
||||
player.CapacitorState = CapacitorStateType.Idle
|
||||
}
|
||||
}
|
||||
|
||||
private def maxCapacitorTickCharging(activate: Boolean): Unit = {
|
||||
val maxCapacitor = player.ExoSuitDef.MaxCapacitor
|
||||
if (activate) {
|
||||
player.CapacitorState = CapacitorStateType.Discharging
|
||||
//maxCapacitorTickDischarging(activate)
|
||||
} else if (player.Capacitor < player.ExoSuitDef.MaxCapacitor) {
|
||||
player.Capacitor = maxCapacitor.toFloat
|
||||
sendResponse(PlanetsideAttributeMessage(player.GUID, 7, maxCapacitor))
|
||||
} else {
|
||||
player.CapacitorState = CapacitorStateType.Idle
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,330 @@
|
|||
// Copyright (c) 2024 PSForever
|
||||
package net.psforever.actors.session.csr
|
||||
|
||||
import akka.actor.ActorContext
|
||||
import net.psforever.actors.session.support.{MountHandlerFunctions, SessionData, SessionMountHandlers}
|
||||
import net.psforever.actors.zone.ZoneActor
|
||||
import net.psforever.objects.{GlobalDefinitions, PlanetSideGameObject, Player, Vehicle, Vehicles}
|
||||
import net.psforever.objects.serverobject.affinity.FactionAffinity
|
||||
import net.psforever.objects.serverobject.environment.interaction.ResetAllEnvironmentInteractions
|
||||
import net.psforever.objects.serverobject.hackable.GenericHackables
|
||||
import net.psforever.objects.serverobject.mount.Mountable
|
||||
import net.psforever.objects.serverobject.structures.WarpGate
|
||||
import net.psforever.objects.serverobject.terminals.implant.ImplantTerminalMech
|
||||
import net.psforever.objects.serverobject.turret.{FacilityTurret, WeaponTurret}
|
||||
import net.psforever.objects.vehicles.AccessPermissionGroup
|
||||
import net.psforever.objects.vital.InGameHistory
|
||||
import net.psforever.packet.game.{ChatMsg, DelayedPathMountMsg, DismountVehicleCargoMsg, DismountVehicleMsg, GenericObjectActionMessage, MountVehicleCargoMsg, MountVehicleMsg, ObjectDetachMessage, PlanetsideAttributeMessage, PlayerStasisMessage, PlayerStateShiftMessage, ShiftState}
|
||||
import net.psforever.services.Service
|
||||
import net.psforever.services.local.{LocalAction, LocalServiceMessage}
|
||||
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
|
||||
import net.psforever.types.{BailType, ChatMessageType, DriveState, PlanetSideGUID, Vector3}
|
||||
|
||||
object MountHandlerLogic {
|
||||
def apply(ops: SessionMountHandlers): MountHandlerLogic = {
|
||||
new MountHandlerLogic(ops, ops.context)
|
||||
}
|
||||
}
|
||||
|
||||
class MountHandlerLogic(val ops: SessionMountHandlers, implicit val context: ActorContext) extends MountHandlerFunctions {
|
||||
def sessionLogic: SessionData = ops.sessionLogic
|
||||
|
||||
/* packets */
|
||||
|
||||
def handleMountVehicle(pkt: MountVehicleMsg): Unit = {
|
||||
//can only mount vehicle when not in csr spectator mode
|
||||
if (!player.spectator) {
|
||||
ops.handleMountVehicle(pkt)
|
||||
}
|
||||
}
|
||||
|
||||
def handleDismountVehicle(pkt: DismountVehicleMsg): Unit = {
|
||||
//can't do this if we're not in vehicle, so also not csr spectator
|
||||
ops.handleDismountVehicle(pkt.copy(bailType = BailType.Bailed))
|
||||
}
|
||||
|
||||
def handleMountVehicleCargo(pkt: MountVehicleCargoMsg): Unit = {
|
||||
//can't do this if we're not in vehicle, so also not csr spectator
|
||||
ops.handleMountVehicleCargo(pkt)
|
||||
}
|
||||
|
||||
def handleDismountVehicleCargo(pkt: DismountVehicleCargoMsg): Unit = {
|
||||
//can't do this if we're not in vehicle, so also not csr spectator
|
||||
ops.handleDismountVehicleCargo(pkt.copy(bailed = true))
|
||||
}
|
||||
|
||||
/* response handlers */
|
||||
|
||||
/**
|
||||
* na
|
||||
*
|
||||
* @param tplayer na
|
||||
* @param reply na
|
||||
*/
|
||||
def handle(tplayer: Player, reply: Mountable.Exchange): Unit = {
|
||||
reply match {
|
||||
case Mountable.CanMount(obj: ImplantTerminalMech, seatNumber, _) =>
|
||||
sessionLogic.zoning.CancelZoningProcess()
|
||||
sessionLogic.terminals.CancelAllProximityUnits()
|
||||
ops.MountingAction(tplayer, obj, seatNumber)
|
||||
sessionLogic.keepAliveFunc = sessionLogic.keepAlivePersistenceFunc
|
||||
|
||||
case Mountable.CanMount(obj: Vehicle, seatNumber, _)
|
||||
if obj.Definition == GlobalDefinitions.orbital_shuttle =>
|
||||
sessionLogic.zoning.CancelZoningProcess()
|
||||
sessionLogic.terminals.CancelAllProximityUnits()
|
||||
ops.MountingAction(tplayer, obj, seatNumber)
|
||||
sessionLogic.keepAliveFunc = sessionLogic.keepAlivePersistenceFunc
|
||||
|
||||
case Mountable.CanMount(obj: Vehicle, seatNumber, _)
|
||||
if obj.Definition == GlobalDefinitions.ant =>
|
||||
sessionLogic.zoning.CancelZoningProcess()
|
||||
val obj_guid: PlanetSideGUID = obj.GUID
|
||||
sessionLogic.terminals.CancelAllProximityUnits()
|
||||
sendResponse(PlanetsideAttributeMessage(obj_guid, attribute_type=0, obj.Health))
|
||||
sendResponse(PlanetsideAttributeMessage(obj_guid, obj.Definition.shieldUiAttribute, obj.Shields))
|
||||
sendResponse(PlanetsideAttributeMessage(obj_guid, attribute_type=45, obj.NtuCapacitorScaled))
|
||||
sendResponse(GenericObjectActionMessage(obj_guid, code=11))
|
||||
sessionLogic.general.accessContainer(obj)
|
||||
tplayer.Actor ! ResetAllEnvironmentInteractions
|
||||
ops.MountingAction(tplayer, obj, seatNumber)
|
||||
|
||||
case Mountable.CanMount(obj: Vehicle, seatNumber, _)
|
||||
if obj.Definition == GlobalDefinitions.quadstealth =>
|
||||
sessionLogic.zoning.CancelZoningProcess()
|
||||
val obj_guid: PlanetSideGUID = obj.GUID
|
||||
sessionLogic.terminals.CancelAllProximityUnits()
|
||||
sendResponse(PlanetsideAttributeMessage(obj_guid, attribute_type=0, obj.Health))
|
||||
sendResponse(PlanetsideAttributeMessage(obj_guid, obj.Definition.shieldUiAttribute, obj.Shields))
|
||||
//exclusive to the wraith, cloak state matches the cloak state of the driver
|
||||
//phantasm doesn't uncloak if the driver is uncloaked and no other vehicle cloaks
|
||||
obj.Cloaked = tplayer.Cloaked
|
||||
sendResponse(GenericObjectActionMessage(obj_guid, code=11))
|
||||
sessionLogic.general.accessContainer(obj)
|
||||
tplayer.Actor ! ResetAllEnvironmentInteractions
|
||||
ops.MountingAction(tplayer, obj, seatNumber)
|
||||
|
||||
case Mountable.CanMount(obj: Vehicle, seatNumber, _)
|
||||
if seatNumber == 0 && obj.Definition.MaxCapacitor > 0 =>
|
||||
sessionLogic.zoning.CancelZoningProcess()
|
||||
val obj_guid: PlanetSideGUID = obj.GUID
|
||||
sessionLogic.terminals.CancelAllProximityUnits()
|
||||
sendResponse(PlanetsideAttributeMessage(obj_guid, attribute_type=0, obj.Health))
|
||||
sendResponse(PlanetsideAttributeMessage(obj_guid, obj.Definition.shieldUiAttribute, obj.Shields))
|
||||
sendResponse(PlanetsideAttributeMessage(obj_guid, attribute_type=113, obj.Capacitor))
|
||||
sendResponse(GenericObjectActionMessage(obj_guid, code=11))
|
||||
sessionLogic.general.accessContainer(obj)
|
||||
ops.updateWeaponAtSeatPosition(obj, seatNumber)
|
||||
tplayer.Actor ! ResetAllEnvironmentInteractions
|
||||
ops.MountingAction(tplayer, obj, seatNumber)
|
||||
|
||||
case Mountable.CanMount(obj: Vehicle, seatNumber, _)
|
||||
if seatNumber == 0 =>
|
||||
sessionLogic.zoning.CancelZoningProcess()
|
||||
val obj_guid: PlanetSideGUID = obj.GUID
|
||||
sessionLogic.terminals.CancelAllProximityUnits()
|
||||
sendResponse(PlanetsideAttributeMessage(obj_guid, attribute_type=0, obj.Health))
|
||||
sendResponse(PlanetsideAttributeMessage(obj_guid, obj.Definition.shieldUiAttribute, obj.Shields))
|
||||
sendResponse(GenericObjectActionMessage(obj_guid, code=11))
|
||||
sessionLogic.general.accessContainer(obj)
|
||||
ops.updateWeaponAtSeatPosition(obj, seatNumber)
|
||||
tplayer.Actor ! ResetAllEnvironmentInteractions
|
||||
ops.MountingAction(tplayer, obj, seatNumber)
|
||||
|
||||
case Mountable.CanMount(obj: Vehicle, seatNumber, _)
|
||||
if obj.Definition.MaxCapacitor > 0 =>
|
||||
sessionLogic.zoning.CancelZoningProcess()
|
||||
val obj_guid: PlanetSideGUID = obj.GUID
|
||||
sessionLogic.terminals.CancelAllProximityUnits()
|
||||
sendResponse(PlanetsideAttributeMessage(obj_guid, attribute_type=0, obj.Health))
|
||||
sendResponse(PlanetsideAttributeMessage(obj_guid, obj.Definition.shieldUiAttribute, obj.Shields))
|
||||
sendResponse(PlanetsideAttributeMessage(obj_guid, attribute_type=113, obj.Capacitor))
|
||||
sessionLogic.general.accessContainer(obj)
|
||||
ops.updateWeaponAtSeatPosition(obj, seatNumber)
|
||||
sessionLogic.keepAliveFunc = sessionLogic.keepAlivePersistenceFunc
|
||||
tplayer.Actor ! ResetAllEnvironmentInteractions
|
||||
ops.MountingAction(tplayer, obj, seatNumber)
|
||||
|
||||
case Mountable.CanMount(obj: Vehicle, seatNumber, _) =>
|
||||
sessionLogic.zoning.CancelZoningProcess()
|
||||
val obj_guid: PlanetSideGUID = obj.GUID
|
||||
sessionLogic.terminals.CancelAllProximityUnits()
|
||||
sendResponse(PlanetsideAttributeMessage(obj_guid, attribute_type=0, obj.Health))
|
||||
sendResponse(PlanetsideAttributeMessage(obj_guid, obj.Definition.shieldUiAttribute, obj.Shields))
|
||||
sessionLogic.general.accessContainer(obj)
|
||||
ops.updateWeaponAtSeatPosition(obj, seatNumber)
|
||||
sessionLogic.keepAliveFunc = sessionLogic.keepAlivePersistenceFunc
|
||||
tplayer.Actor ! ResetAllEnvironmentInteractions
|
||||
ops.MountingAction(tplayer, obj, seatNumber)
|
||||
|
||||
case Mountable.CanMount(obj: FacilityTurret, seatNumber, _)
|
||||
if obj.Definition == GlobalDefinitions.vanu_sentry_turret =>
|
||||
sessionLogic.zoning.CancelZoningProcess()
|
||||
obj.Zone.LocalEvents ! LocalServiceMessage(obj.Zone.id, LocalAction.SetEmpire(obj.GUID, player.Faction))
|
||||
sendResponse(PlanetsideAttributeMessage(obj.GUID, attribute_type=0, obj.Health))
|
||||
ops.updateWeaponAtSeatPosition(obj, seatNumber)
|
||||
ops.MountingAction(tplayer, obj, seatNumber)
|
||||
|
||||
case Mountable.CanMount(obj: FacilityTurret, seatNumber, _)
|
||||
if !obj.isUpgrading || System.currentTimeMillis() - GenericHackables.getTurretUpgradeTime >= 1500L =>
|
||||
obj.setMiddleOfUpgrade(false)
|
||||
sessionLogic.zoning.CancelZoningProcess()
|
||||
sendResponse(PlanetsideAttributeMessage(obj.GUID, attribute_type=0, obj.Health))
|
||||
ops.updateWeaponAtSeatPosition(obj, seatNumber)
|
||||
ops.MountingAction(tplayer, obj, seatNumber)
|
||||
|
||||
case Mountable.CanMount(obj: FacilityTurret, _, _) =>
|
||||
sessionLogic.zoning.CancelZoningProcess()
|
||||
log.warn(
|
||||
s"MountVehicleMsg: ${tplayer.Name} wants to mount turret ${obj.GUID.guid}, but needs to wait until it finishes updating"
|
||||
)
|
||||
|
||||
case Mountable.CanMount(obj: PlanetSideGameObject with FactionAffinity with WeaponTurret with InGameHistory, seatNumber, _) =>
|
||||
sessionLogic.zoning.CancelZoningProcess()
|
||||
sendResponse(PlanetsideAttributeMessage(obj.GUID, attribute_type=0, obj.Health))
|
||||
ops.updateWeaponAtSeatPosition(obj, seatNumber)
|
||||
ops.MountingAction(tplayer, obj, seatNumber)
|
||||
|
||||
case Mountable.CanMount(obj: Mountable, _, _) =>
|
||||
log.warn(s"MountVehicleMsg: $obj is some kind of mountable object but nothing will happen for ${player.Name}")
|
||||
|
||||
case Mountable.CanDismount(obj: ImplantTerminalMech, seatNum, _) =>
|
||||
ops.DismountAction(tplayer, obj, seatNum)
|
||||
|
||||
case Mountable.CanDismount(obj: Vehicle, _, mountPoint)
|
||||
if obj.Definition == GlobalDefinitions.orbital_shuttle && obj.MountedIn.nonEmpty =>
|
||||
//dismount to hart lobby
|
||||
val pguid = player.GUID
|
||||
val sguid = obj.GUID
|
||||
val (pos, zang) = Vehicles.dismountShuttle(obj, mountPoint)
|
||||
tplayer.Position = pos
|
||||
sendResponse(DelayedPathMountMsg(pguid, sguid, u1=60, u2=true))
|
||||
continent.LocalEvents ! LocalServiceMessage(
|
||||
continent.id,
|
||||
LocalAction.SendResponse(ObjectDetachMessage(sguid, pguid, pos, roll=0, pitch=0, zang))
|
||||
)
|
||||
sessionLogic.keepAliveFunc = sessionLogic.zoning.NormalKeepAlive
|
||||
|
||||
case Mountable.CanDismount(obj: Vehicle, seatNum, _)
|
||||
if obj.Definition == GlobalDefinitions.orbital_shuttle =>
|
||||
//get ready for orbital drop
|
||||
val pguid = player.GUID
|
||||
val events = continent.VehicleEvents
|
||||
ops.DismountAction(tplayer, obj, seatNum)
|
||||
continent.actor ! ZoneActor.RemoveFromBlockMap(player) //character doesn't need it
|
||||
//DismountAction(...) uses vehicle service, so use that service to coordinate the remainder of the messages
|
||||
events ! VehicleServiceMessage(
|
||||
player.Name,
|
||||
VehicleAction.SendResponse(Service.defaultPlayerGUID, PlayerStasisMessage(pguid)) //the stasis message
|
||||
)
|
||||
//when the player dismounts, they will be positioned where the shuttle was when it disappeared in the sky
|
||||
//the player will fall to the ground and is perfectly vulnerable in this state
|
||||
//additionally, our player must exist in the current zone
|
||||
//having no in-game avatar target will throw us out of the map screen when deploying and cause softlock
|
||||
events ! VehicleServiceMessage(
|
||||
player.Name,
|
||||
VehicleAction.SendResponse(
|
||||
Service.defaultPlayerGUID,
|
||||
PlayerStateShiftMessage(ShiftState(unk=0, obj.Position, obj.Orientation.z, vel=None)) //cower in the shuttle bay
|
||||
)
|
||||
)
|
||||
events ! VehicleServiceMessage(
|
||||
continent.id,
|
||||
VehicleAction.SendResponse(pguid, GenericObjectActionMessage(pguid, code=9)) //conceal the player
|
||||
)
|
||||
sessionLogic.keepAliveFunc = sessionLogic.zoning.NormalKeepAlive
|
||||
|
||||
case Mountable.CanDismount(obj: Vehicle, seatNum, _)
|
||||
if obj.Definition == GlobalDefinitions.droppod =>
|
||||
sessionLogic.general.unaccessContainer(obj)
|
||||
ops.DismountAction(tplayer, obj, seatNum)
|
||||
obj.Actor ! Vehicle.Deconstruct()
|
||||
|
||||
case Mountable.CanDismount(obj: Vehicle, seatNum, _)
|
||||
if tplayer.GUID == player.GUID &&
|
||||
obj.isFlying &&
|
||||
obj.SeatPermissionGroup(seatNum).contains(AccessPermissionGroup.Driver) =>
|
||||
// Deconstruct the vehicle if the driver has bailed out and the vehicle is capable of flight
|
||||
//todo: implement auto landing procedure if the pilot bails but passengers are still present instead of deconstructing the vehicle
|
||||
//todo: continue flight path until aircraft crashes if no passengers present (or no passenger seats), then deconstruct.
|
||||
//todo: kick cargo passengers out. To be added after PR #216 is merged
|
||||
ops.DismountVehicleAction(tplayer, obj, seatNum)
|
||||
obj.Actor ! Vehicle.Deconstruct(None) //immediate deconstruction
|
||||
|
||||
case Mountable.CanDismount(obj: Vehicle, seatNum, _)
|
||||
if tplayer.GUID == player.GUID =>
|
||||
ops.DismountVehicleAction(tplayer, obj, seatNum)
|
||||
|
||||
case Mountable.CanDismount(obj: Vehicle, seat_num, _) =>
|
||||
continent.VehicleEvents ! VehicleServiceMessage(
|
||||
continent.id,
|
||||
VehicleAction.KickPassenger(tplayer.GUID, seat_num, unk2=true, obj.GUID)
|
||||
)
|
||||
|
||||
case Mountable.CanDismount(obj: PlanetSideGameObject with PlanetSideGameObject with Mountable with FactionAffinity with InGameHistory, seatNum, _) =>
|
||||
ops.DismountAction(tplayer, obj, seatNum)
|
||||
|
||||
case Mountable.CanDismount(obj: Mountable, _, _) =>
|
||||
log.warn(s"DismountVehicleMsg: $obj is some dismountable object but nothing will happen for ${player.Name}")
|
||||
|
||||
case Mountable.CanNotMount(obj: Vehicle, seatNumber) =>
|
||||
log.warn(s"MountVehicleMsg: ${tplayer.Name} attempted to mount $obj's seat $seatNumber, but was not allowed")
|
||||
obj.GetSeatFromMountPoint(seatNumber).collect {
|
||||
case seatNum if obj.SeatPermissionGroup(seatNum).contains(AccessPermissionGroup.Driver) =>
|
||||
sendResponse(
|
||||
ChatMsg(ChatMessageType.CMT_OPEN, wideContents=false, recipient="", "You are not the driver of this vehicle.", note=None)
|
||||
)
|
||||
}
|
||||
|
||||
case Mountable.CanNotMount(obj: Mountable, seatNumber) =>
|
||||
log.warn(s"MountVehicleMsg: ${tplayer.Name} attempted to mount $obj's seat $seatNumber, but was not allowed")
|
||||
|
||||
case Mountable.CanNotDismount(obj: Vehicle, _, BailType.Normal)
|
||||
if obj.DeploymentState == DriveState.AutoPilot =>
|
||||
if (!player.spectator) {
|
||||
sendResponse(ChatMsg(ChatMessageType.UNK_224, "@SA_CannotDismountAtThisTime"))
|
||||
}
|
||||
|
||||
case Mountable.CanNotDismount(obj: Vehicle, _, BailType.Bailed)
|
||||
if obj.Definition == GlobalDefinitions.droppod =>
|
||||
if (!player.spectator) {
|
||||
sendResponse(ChatMsg(ChatMessageType.UNK_224, "@CannotBailFromDroppod"))
|
||||
}
|
||||
|
||||
case Mountable.CanNotDismount(obj: Vehicle, _, BailType.Bailed)
|
||||
if obj.DeploymentState == DriveState.AutoPilot =>
|
||||
if (!player.spectator) {
|
||||
sendResponse(ChatMsg(ChatMessageType.UNK_224, "@SA_CannotBailAtThisTime"))
|
||||
}
|
||||
|
||||
case Mountable.CanNotDismount(obj: Vehicle, _, BailType.Bailed)
|
||||
if {
|
||||
continent
|
||||
.blockMap
|
||||
.sector(obj)
|
||||
.buildingList
|
||||
.exists {
|
||||
case wg: WarpGate =>
|
||||
Vector3.DistanceSquared(obj.Position, wg.Position) < math.pow(wg.Definition.SOIRadius, 2)
|
||||
case _ =>
|
||||
false
|
||||
}
|
||||
} =>
|
||||
if (!player.spectator) {
|
||||
sendResponse(ChatMsg(ChatMessageType.UNK_227, "@Vehicle_CannotBailInWarpgateEnvelope"))
|
||||
}
|
||||
|
||||
case Mountable.CanNotDismount(obj: Vehicle, _, _)
|
||||
if obj.isMoving(test = 1f) =>
|
||||
ops.handleDismountVehicle(DismountVehicleMsg(player.GUID, BailType.Bailed, wasKickedByDriver=true))
|
||||
if (!player.spectator) {
|
||||
sendResponse(ChatMsg(ChatMessageType.UNK_224, "@TooFastToDismount"))
|
||||
}
|
||||
|
||||
case Mountable.CanNotDismount(obj, seatNum, _) =>
|
||||
log.warn(s"DismountVehicleMsg: ${tplayer.Name} attempted to dismount $obj's mount $seatNum, but was not allowed")
|
||||
}
|
||||
}
|
||||
|
||||
/* support functions */
|
||||
}
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
// Copyright (c) 2024 PSForever
|
||||
package net.psforever.actors.session.csr
|
||||
|
||||
import net.psforever.actors.session.support.{AvatarHandlerFunctions, ChatFunctions, GalaxyHandlerFunctions, GeneralFunctions, LocalHandlerFunctions, MountHandlerFunctions, SquadHandlerFunctions, TerminalHandlerFunctions, VehicleFunctions, VehicleHandlerFunctions, WeaponAndProjectileFunctions}
|
||||
import net.psforever.objects.serverobject.ServerObject
|
||||
import net.psforever.objects.serverobject.mount.Mountable
|
||||
import net.psforever.objects.{Player, Session, Vehicle}
|
||||
import net.psforever.objects.zones.Zone
|
||||
import net.psforever.packet.PlanetSidePacket
|
||||
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||
import net.psforever.services.chat.SpectatorChannel
|
||||
import net.psforever.services.teamwork.{SquadAction, SquadServiceMessage}
|
||||
import net.psforever.types.{ChatMessageType, SquadRequestType}
|
||||
//
|
||||
import net.psforever.actors.session.support.{ModeLogic, PlayerMode, SessionData}
|
||||
import net.psforever.packet.game.ChatMsg
|
||||
|
||||
class SpectatorCSRModeLogic(data: SessionData) extends ModeLogic {
|
||||
val avatarResponse: AvatarHandlerFunctions = AvatarHandlerLogic(data.avatarResponse)
|
||||
val chat: ChatFunctions = ChatLogic(data.chat)
|
||||
val galaxy: GalaxyHandlerFunctions = net.psforever.actors.session.normal.GalaxyHandlerLogic(data.galaxyResponseHandlers)
|
||||
val general: GeneralFunctions = GeneralLogic(data.general)
|
||||
val local: LocalHandlerFunctions = net.psforever.actors.session.normal.LocalHandlerLogic(data.localResponse)
|
||||
val mountResponse: MountHandlerFunctions = MountHandlerLogic(data.mountResponse)
|
||||
val shooting: WeaponAndProjectileFunctions = WeaponAndProjectileLogic(data.shooting)
|
||||
val squad: SquadHandlerFunctions = SquadHandlerLogic(data.squad)
|
||||
val terminals: TerminalHandlerFunctions = TerminalHandlerLogic(data.terminals)
|
||||
val vehicles: VehicleFunctions = VehicleLogic(data.vehicles)
|
||||
val vehicleResponse: VehicleHandlerFunctions = net.psforever.actors.session.normal.VehicleHandlerLogic(data.vehicleResponseOperations)
|
||||
|
||||
override def switchTo(session: Session): Unit = {
|
||||
val player = session.player
|
||||
val continent = session.zone
|
||||
val pguid = player.GUID
|
||||
val sendResponse: PlanetSidePacket => Unit = data.sendResponse
|
||||
//
|
||||
data.squadService ! SquadServiceMessage(
|
||||
player,
|
||||
continent,
|
||||
SquadAction.Membership(SquadRequestType.Leave, player.CharId, Some(player.CharId), player.Name, None)
|
||||
)
|
||||
if (requireDismount(data, continent, player)) {
|
||||
CustomerServiceRepresentativeMode.renderPlayer(data, continent, player)
|
||||
}
|
||||
//
|
||||
player.spectator = true
|
||||
data.chat.JoinChannel(SpectatorChannel)
|
||||
continent.AvatarEvents ! AvatarServiceMessage(continent.id, AvatarAction.ObjectDelete(pguid, pguid))
|
||||
sendResponse(ChatMsg(ChatMessageType.CMT_TOGGLESPECTATORMODE, "on"))
|
||||
sendResponse(ChatMsg(ChatMessageType.UNK_225, "CSR SPECTATOR MODE ON"))
|
||||
}
|
||||
|
||||
override def switchFrom(session: Session): Unit = {
|
||||
val player = data.player
|
||||
val pguid = player.GUID
|
||||
val continent = data.continent
|
||||
val avatarId = player.Definition.ObjectId
|
||||
val sendResponse: PlanetSidePacket => Unit = data.sendResponse
|
||||
//
|
||||
player.spectator = false
|
||||
data.chat.LeaveChannel(SpectatorChannel)
|
||||
continent.AvatarEvents ! AvatarServiceMessage(
|
||||
continent.id,
|
||||
AvatarAction.LoadPlayer(pguid, avatarId, pguid, player.Definition.Packet.ConstructorData(player).get, None)
|
||||
)
|
||||
sendResponse(ChatMsg(ChatMessageType.CMT_TOGGLESPECTATORMODE, "off"))
|
||||
}
|
||||
|
||||
private def requireDismount(data: SessionData, zone: Zone, player: Player): Boolean = {
|
||||
data.vehicles.GetMountableAndSeat(None, player, zone) match {
|
||||
case (Some(obj: Vehicle), Some(seatNum)) if seatNum == 0 =>
|
||||
data.vehicles.ServerVehicleOverrideStop(obj)
|
||||
obj.Actor ! ServerObject.AttributeMsg(10, 3) //faction-accessible driver seat
|
||||
obj.Actor ! Mountable.TryDismount(player, seatNum)
|
||||
player.VehicleSeated = None
|
||||
true
|
||||
case (Some(obj), Some(seatNum)) =>
|
||||
obj.Actor ! Mountable.TryDismount(player, seatNum)
|
||||
player.VehicleSeated = None
|
||||
true
|
||||
case _ =>
|
||||
player.VehicleSeated = None
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case object SpectateAsCustomerServiceRepresentativeMode extends PlayerMode {
|
||||
def setup(data: SessionData): ModeLogic = {
|
||||
new SpectatorCSRModeLogic(data)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,362 @@
|
|||
// Copyright (c) 2024 PSForever
|
||||
package net.psforever.actors.session.csr
|
||||
|
||||
import akka.actor.{ActorContext, ActorRef, typed}
|
||||
import net.psforever.actors.session.support.SessionSquadHandlers.SquadUIElement
|
||||
import net.psforever.actors.session.AvatarActor
|
||||
import net.psforever.actors.session.support.{SessionData, SessionSquadHandlers, SquadHandlerFunctions}
|
||||
import net.psforever.objects.{Default, LivePlayerList}
|
||||
import net.psforever.objects.avatar.Avatar
|
||||
import net.psforever.packet.game.{CharacterKnowledgeInfo, CharacterKnowledgeMessage, ChatMsg, MemberEvent, PlanetsideAttributeMessage, ReplicationStreamMessage, SquadAction, SquadDefinitionActionMessage, SquadDetailDefinitionUpdateMessage, SquadListing, SquadMemberEvent, SquadMembershipRequest, SquadMembershipResponse, SquadState, SquadStateInfo, SquadWaypointEvent, SquadWaypointRequest, WaypointEvent, WaypointEventAction}
|
||||
import net.psforever.services.chat.SquadChannel
|
||||
import net.psforever.services.teamwork.{SquadResponse, SquadServiceMessage, SquadAction => SquadServiceAction}
|
||||
import net.psforever.types.{ChatMessageType, PlanetSideGUID, SquadListDecoration, SquadResponseType, WaypointSubtype}
|
||||
|
||||
object SquadHandlerLogic {
|
||||
def apply(ops: SessionSquadHandlers): SquadHandlerLogic = {
|
||||
new SquadHandlerLogic(ops, ops.context)
|
||||
}
|
||||
}
|
||||
|
||||
class SquadHandlerLogic(val ops: SessionSquadHandlers, implicit val context: ActorContext) extends SquadHandlerFunctions {
|
||||
def sessionLogic: SessionData = ops.sessionLogic
|
||||
|
||||
private val avatarActor: typed.ActorRef[AvatarActor.Command] = ops.avatarActor
|
||||
|
||||
private val squadService: ActorRef = ops.squadService
|
||||
|
||||
/* packet */
|
||||
|
||||
def handleSquadDefinitionAction(pkt: SquadDefinitionActionMessage): Unit = {
|
||||
if (!player.spectator) {
|
||||
val SquadDefinitionActionMessage(u1, u2, action) = pkt
|
||||
squadService ! SquadServiceMessage(player, continent, SquadServiceAction.Definition(u1, u2, action))
|
||||
}
|
||||
}
|
||||
|
||||
def handleSquadMemberRequest(pkt: SquadMembershipRequest): Unit = {
|
||||
if (!player.spectator) {
|
||||
val SquadMembershipRequest(request_type, char_id, unk3, player_name, unk5) = pkt
|
||||
squadService ! SquadServiceMessage(
|
||||
player,
|
||||
continent,
|
||||
SquadServiceAction.Membership(request_type, char_id, unk3, player_name, unk5)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
def handleSquadWaypointRequest(pkt: SquadWaypointRequest): Unit = {
|
||||
if (!player.spectator) {
|
||||
val SquadWaypointRequest(request, _, wtype, unk, info) = pkt
|
||||
val time = System.currentTimeMillis()
|
||||
val subtype = wtype.subtype
|
||||
if (subtype == WaypointSubtype.Squad) {
|
||||
squadService ! SquadServiceMessage(player, continent, SquadServiceAction.Waypoint(request, wtype, unk, info))
|
||||
} else if (subtype == WaypointSubtype.Laze && time - waypointCooldown > 1000) {
|
||||
//guarding against duplicating laze waypoints
|
||||
waypointCooldown = time
|
||||
squadService ! SquadServiceMessage(player, continent, SquadServiceAction.Waypoint(request, wtype, unk, info))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* response handlers */
|
||||
|
||||
def handle(response: SquadResponse.Response, excluded: Iterable[Long]): Unit = {
|
||||
if (!excluded.exists(_ == avatar.id)) {
|
||||
response match {
|
||||
case SquadResponse.ListSquadFavorite(line, task) =>
|
||||
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), line, SquadAction.ListSquadFavorite(task)))
|
||||
|
||||
case SquadResponse.InitList(infos) =>
|
||||
sendResponse(ReplicationStreamMessage(infos))
|
||||
|
||||
case SquadResponse.UpdateList(infos) if infos.nonEmpty =>
|
||||
sendResponse(
|
||||
ReplicationStreamMessage(
|
||||
6,
|
||||
None,
|
||||
infos.map {
|
||||
case (index, squadInfo) =>
|
||||
SquadListing(index, squadInfo)
|
||||
}.toVector
|
||||
)
|
||||
)
|
||||
|
||||
case SquadResponse.RemoveFromList(infos) if infos.nonEmpty =>
|
||||
sendResponse(
|
||||
ReplicationStreamMessage(
|
||||
1,
|
||||
None,
|
||||
infos.map { index =>
|
||||
SquadListing(index, None)
|
||||
}.toVector
|
||||
)
|
||||
)
|
||||
|
||||
case SquadResponse.SquadDecoration(guid, squad) =>
|
||||
val decoration = if (
|
||||
ops.squadUI.nonEmpty ||
|
||||
squad.Size == squad.Capacity ||
|
||||
{
|
||||
val offer = avatar.certifications
|
||||
!squad.Membership.exists { _.isAvailable(offer) }
|
||||
}
|
||||
) {
|
||||
SquadListDecoration.NotAvailable
|
||||
} else {
|
||||
SquadListDecoration.Available
|
||||
}
|
||||
sendResponse(SquadDefinitionActionMessage(guid, 0, SquadAction.SquadListDecorator(decoration)))
|
||||
|
||||
case SquadResponse.Detail(guid, detail) =>
|
||||
sendResponse(SquadDetailDefinitionUpdateMessage(guid, detail))
|
||||
|
||||
case SquadResponse.IdentifyAsSquadLeader(squad_guid) =>
|
||||
sendResponse(SquadDefinitionActionMessage(squad_guid, 0, SquadAction.IdentifyAsSquadLeader()))
|
||||
|
||||
case SquadResponse.SetListSquad(squad_guid) =>
|
||||
sendResponse(SquadDefinitionActionMessage(squad_guid, 0, SquadAction.SetListSquad()))
|
||||
|
||||
case SquadResponse.Membership(request_type, unk1, unk2, charId, opt_char_id, player_name, unk5, unk6) =>
|
||||
val name = request_type match {
|
||||
case SquadResponseType.Invite if unk5 =>
|
||||
//the name of the player indicated by unk3 is needed
|
||||
LivePlayerList.WorldPopulation({ case (_, a: Avatar) => charId == a.id }).headOption match {
|
||||
case Some(player) =>
|
||||
player.name
|
||||
case None =>
|
||||
player_name
|
||||
}
|
||||
case _ =>
|
||||
player_name
|
||||
}
|
||||
sendResponse(SquadMembershipResponse(request_type, unk1, unk2, charId, opt_char_id, name, unk5, unk6))
|
||||
|
||||
case SquadResponse.WantsSquadPosition(_, name) =>
|
||||
sendResponse(
|
||||
ChatMsg(
|
||||
ChatMessageType.CMT_SQUAD,
|
||||
wideContents=true,
|
||||
name,
|
||||
s"\\#6 would like to join your squad. (respond with \\#3/accept\\#6 or \\#3/reject\\#6)",
|
||||
None
|
||||
)
|
||||
)
|
||||
|
||||
case SquadResponse.Join(squad, positionsToUpdate, _, ref) =>
|
||||
val avatarId = avatar.id
|
||||
val membershipPositions = (positionsToUpdate map squad.Membership.zipWithIndex)
|
||||
.filter { case (mem, index) =>
|
||||
mem.CharId > 0 && positionsToUpdate.contains(index)
|
||||
}
|
||||
membershipPositions.find { case (mem, _) => mem.CharId == avatarId } match {
|
||||
case Some((ourMember, ourIndex)) =>
|
||||
//we are joining the squad
|
||||
//load each member's entry (our own too)
|
||||
ops.squad_supplement_id = squad.GUID.guid + 1
|
||||
membershipPositions.foreach {
|
||||
case (member, index) =>
|
||||
sendResponse(
|
||||
SquadMemberEvent.Add(
|
||||
ops.squad_supplement_id,
|
||||
member.CharId,
|
||||
index,
|
||||
member.Name,
|
||||
member.ZoneId,
|
||||
outfit_id = 0
|
||||
)
|
||||
)
|
||||
ops.squadUI(member.CharId) =
|
||||
SquadUIElement(member.Name, outfit=0L, index, member.ZoneId, member.Health, member.Armor, member.Position)
|
||||
}
|
||||
//repeat our entry
|
||||
sendResponse(
|
||||
SquadMemberEvent.Add(
|
||||
ops.squad_supplement_id,
|
||||
ourMember.CharId,
|
||||
ourIndex,
|
||||
ourMember.Name,
|
||||
ourMember.ZoneId,
|
||||
outfit_id = 0
|
||||
)
|
||||
)
|
||||
//turn lfs off
|
||||
if (avatar.lookingForSquad) {
|
||||
avatarActor ! AvatarActor.SetLookingForSquad(false)
|
||||
}
|
||||
val playerGuid = player.GUID
|
||||
val factionChannel = s"${player.Faction}"
|
||||
//squad colors
|
||||
ops.GiveSquadColorsToMembers()
|
||||
ops.GiveSquadColorsForOthers(playerGuid, factionChannel, ops.squad_supplement_id)
|
||||
//associate with member position in squad
|
||||
sendResponse(PlanetsideAttributeMessage(playerGuid, 32, ourIndex))
|
||||
//a finalization? what does this do?
|
||||
sendResponse(SquadDefinitionActionMessage(squad.GUID, 0, SquadAction.Unknown(18)))
|
||||
squadService ! SquadServiceMessage(player, continent, SquadServiceAction.ReloadDecoration())
|
||||
ops.updateSquadRef = ref
|
||||
ops.updateSquad = ops.PeriodicUpdatesWhenEnrolledInSquad
|
||||
sessionLogic.chat.JoinChannel(SquadChannel(squad.GUID))
|
||||
case _ =>
|
||||
//other player is joining our squad
|
||||
//load each member's entry
|
||||
ops.GiveSquadColorsToMembers(
|
||||
membershipPositions.map {
|
||||
case (member, index) =>
|
||||
val charId = member.CharId
|
||||
sendResponse(
|
||||
SquadMemberEvent.Add(ops.squad_supplement_id, charId, index, member.Name, member.ZoneId, outfit_id = 0)
|
||||
)
|
||||
ops.squadUI(charId) =
|
||||
SquadUIElement(member.Name, outfit=0L, index, member.ZoneId, member.Health, member.Armor, member.Position)
|
||||
charId
|
||||
}
|
||||
)
|
||||
}
|
||||
//send an initial dummy update for map icon(s)
|
||||
sendResponse(
|
||||
SquadState(
|
||||
PlanetSideGUID(ops.squad_supplement_id),
|
||||
membershipPositions.map { case (member, _) =>
|
||||
SquadStateInfo(member.CharId, member.Health, member.Armor, member.Position)
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
case SquadResponse.Leave(squad, positionsToUpdate) =>
|
||||
positionsToUpdate.find({ case (member, _) => member == avatar.id }) match {
|
||||
case Some((ourMember, ourIndex)) =>
|
||||
//we are leaving the squad
|
||||
//remove each member's entry (our own too)
|
||||
ops.updateSquadRef = Default.Actor
|
||||
positionsToUpdate.foreach {
|
||||
case (member, index) =>
|
||||
sendResponse(SquadMemberEvent.Remove(ops.squad_supplement_id, member, index))
|
||||
ops.squadUI.remove(member)
|
||||
}
|
||||
//uninitialize
|
||||
val playerGuid = player.GUID
|
||||
sendResponse(SquadMemberEvent.Remove(ops.squad_supplement_id, ourMember, ourIndex)) //repeat of our entry
|
||||
ops.GiveSquadColorsToSelf(value = 0)
|
||||
sendResponse(PlanetsideAttributeMessage(playerGuid, 32, 0)) //disassociate with member position in squad?
|
||||
sendResponse(PlanetsideAttributeMessage(playerGuid, 34, 4294967295L)) //unknown, perhaps unrelated?
|
||||
avatarActor ! AvatarActor.SetLookingForSquad(false)
|
||||
//a finalization? what does this do?
|
||||
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SquadAction.Unknown(18)))
|
||||
ops.squad_supplement_id = 0
|
||||
ops.squadUpdateCounter = 0
|
||||
ops.updateSquad = ops.NoSquadUpdates
|
||||
sessionLogic.chat.LeaveChannel(SquadChannel(squad.GUID))
|
||||
case _ =>
|
||||
//remove each member's entry
|
||||
ops.GiveSquadColorsToMembers(
|
||||
positionsToUpdate.map {
|
||||
case (member, index) =>
|
||||
sendResponse(SquadMemberEvent.Remove(ops.squad_supplement_id, member, index))
|
||||
ops.squadUI.remove(member)
|
||||
member
|
||||
},
|
||||
value = 0
|
||||
)
|
||||
}
|
||||
|
||||
case SquadResponse.AssignMember(squad, from_index, to_index) =>
|
||||
//we've already swapped position internally; now we swap the cards
|
||||
ops.SwapSquadUIElements(squad, from_index, to_index)
|
||||
|
||||
case SquadResponse.PromoteMember(squad, promotedPlayer, from_index) =>
|
||||
if (promotedPlayer != player.CharId) {
|
||||
//demoted from leader; no longer lfsm
|
||||
if (player.avatar.lookingForSquad) {
|
||||
avatarActor ! AvatarActor.SetLookingForSquad(false)
|
||||
}
|
||||
}
|
||||
sendResponse(SquadMemberEvent(MemberEvent.Promote, squad.GUID.guid, promotedPlayer, position = 0))
|
||||
//the players have already been swapped in the backend object
|
||||
ops.PromoteSquadUIElements(squad, from_index)
|
||||
|
||||
case SquadResponse.UpdateMembers(_, positions) =>
|
||||
val pairedEntries = positions.collect {
|
||||
case entry if ops.squadUI.contains(entry.char_id) =>
|
||||
(entry, ops.squadUI(entry.char_id))
|
||||
}
|
||||
//prune entries
|
||||
val updatedEntries = pairedEntries
|
||||
.collect({
|
||||
case (entry, element) if entry.zone_number != element.zone =>
|
||||
//zone gets updated for these entries
|
||||
sendResponse(
|
||||
SquadMemberEvent.UpdateZone(ops.squad_supplement_id, entry.char_id, element.index, entry.zone_number)
|
||||
)
|
||||
ops.squadUI(entry.char_id) =
|
||||
SquadUIElement(element.name, element.outfit, element.index, entry.zone_number, entry.health, entry.armor, entry.pos)
|
||||
entry
|
||||
case (entry, element)
|
||||
if entry.health != element.health || entry.armor != element.armor || entry.pos != element.position =>
|
||||
//other elements that need to be updated
|
||||
ops.squadUI(entry.char_id) =
|
||||
SquadUIElement(element.name, element.outfit, element.index, entry.zone_number, entry.health, entry.armor, entry.pos)
|
||||
entry
|
||||
})
|
||||
.filterNot(_.char_id == avatar.id) //we want to update our backend, but not our frontend
|
||||
if (updatedEntries.nonEmpty) {
|
||||
sendResponse(
|
||||
SquadState(
|
||||
PlanetSideGUID(ops.squad_supplement_id),
|
||||
updatedEntries.map { entry =>
|
||||
SquadStateInfo(entry.char_id, entry.health, entry.armor, entry.pos)
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
case SquadResponse.CharacterKnowledge(charId, name, certs, u1, u2, zone) =>
|
||||
sendResponse(CharacterKnowledgeMessage(charId, Some(CharacterKnowledgeInfo(name, certs, u1, u2, zone))))
|
||||
|
||||
case SquadResponse.SquadSearchResults(_/*results*/) =>
|
||||
//TODO positive squad search results message?
|
||||
// if(results.nonEmpty) {
|
||||
// results.foreach { guid =>
|
||||
// sendResponse(SquadDefinitionActionMessage(
|
||||
// guid,
|
||||
// 0,
|
||||
// SquadAction.SquadListDecorator(SquadListDecoration.SearchResult))
|
||||
// )
|
||||
// }
|
||||
// } else {
|
||||
// sendResponse(SquadDefinitionActionMessage(player.GUID, 0, SquadAction.NoSquadSearchResults()))
|
||||
// }
|
||||
// sendResponse(SquadDefinitionActionMessage(player.GUID, 0, SquadAction.CancelSquadSearch()))
|
||||
|
||||
case SquadResponse.InitWaypoints(char_id, waypoints) =>
|
||||
waypoints.foreach {
|
||||
case (waypoint_type, info, unk) =>
|
||||
sendResponse(
|
||||
SquadWaypointEvent.Add(
|
||||
ops.squad_supplement_id,
|
||||
char_id,
|
||||
waypoint_type,
|
||||
WaypointEvent(info.zone_number, info.pos, unk)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
case SquadResponse.WaypointEvent(WaypointEventAction.Add, char_id, waypoint_type, _, Some(info), unk) =>
|
||||
sendResponse(
|
||||
SquadWaypointEvent.Add(
|
||||
ops.squad_supplement_id,
|
||||
char_id,
|
||||
waypoint_type,
|
||||
WaypointEvent(info.zone_number, info.pos, unk)
|
||||
)
|
||||
)
|
||||
|
||||
case SquadResponse.WaypointEvent(WaypointEventAction.Remove, char_id, waypoint_type, _, _, _) =>
|
||||
sendResponse(SquadWaypointEvent.Remove(ops.squad_supplement_id, char_id, waypoint_type))
|
||||
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
// Copyright (c) 2024 PSForever
|
||||
package net.psforever.actors.session.csr
|
||||
|
||||
import akka.actor.{ActorContext, typed}
|
||||
import net.psforever.actors.session.AvatarActor
|
||||
import net.psforever.actors.session.support.{SessionData, SessionTerminalHandlers, TerminalHandlerFunctions}
|
||||
import net.psforever.login.WorldSession.{BuyNewEquipmentPutInInventory, SellEquipmentFromInventory}
|
||||
import net.psforever.objects.{Player, Vehicle}
|
||||
import net.psforever.objects.guid.TaskWorkflow
|
||||
import net.psforever.objects.serverobject.terminals.{OrderTerminalDefinition, Terminal}
|
||||
import net.psforever.packet.game.{FavoritesRequest, ItemTransactionMessage, ItemTransactionResultMessage, ProximityTerminalUseMessage}
|
||||
|
||||
object TerminalHandlerLogic {
|
||||
def apply(ops: SessionTerminalHandlers): TerminalHandlerLogic = {
|
||||
new TerminalHandlerLogic(ops, ops.context)
|
||||
}
|
||||
}
|
||||
|
||||
class TerminalHandlerLogic(val ops: SessionTerminalHandlers, implicit val context: ActorContext) extends TerminalHandlerFunctions {
|
||||
def sessionLogic: SessionData = ops.sessionLogic
|
||||
|
||||
private val avatarActor: typed.ActorRef[AvatarActor.Command] = ops.avatarActor
|
||||
|
||||
def handleItemTransaction(pkt: ItemTransactionMessage): Unit = {
|
||||
if (player.spectator) {
|
||||
val ItemTransactionMessage(terminal_guid, _, _, _, _, _) = pkt
|
||||
sessionLogic.zoning.CancelZoningProcess()
|
||||
continent
|
||||
.GUID(terminal_guid)
|
||||
.collect { case t: Terminal => t.Definition }
|
||||
.collect { case t: OrderTerminalDefinition => t }
|
||||
.map(t => t.Request(player, pkt))
|
||||
.collect {
|
||||
case order: Terminal.BuyVehicle =>
|
||||
//do not handle transaction
|
||||
order
|
||||
}
|
||||
.orElse {
|
||||
ops.handleItemTransaction(pkt)
|
||||
None
|
||||
}
|
||||
} else {
|
||||
ops.handleItemTransaction(pkt)
|
||||
}
|
||||
}
|
||||
|
||||
def handleProximityTerminalUse(pkt: ProximityTerminalUseMessage): Unit = {
|
||||
ops.handleProximityTerminalUse(pkt)
|
||||
}
|
||||
|
||||
def handleFavoritesRequest(pkt: FavoritesRequest): Unit = {
|
||||
sessionLogic.zoning.CancelZoningProcess()
|
||||
ops.handleFavoritesRequest(pkt)
|
||||
}
|
||||
|
||||
/**
|
||||
* na
|
||||
* @param tplayer na
|
||||
* @param msg na
|
||||
* @param order na
|
||||
*/
|
||||
def handle(tplayer: Player, msg: ItemTransactionMessage, order: Terminal.Exchange): Unit = {
|
||||
order match {
|
||||
case Terminal.BuyEquipment(item) =>
|
||||
TaskWorkflow.execute(BuyNewEquipmentPutInInventory(
|
||||
continent.GUID(tplayer.VehicleSeated) match {
|
||||
case Some(v: Vehicle) => v
|
||||
case _ => player
|
||||
},
|
||||
tplayer,
|
||||
msg.terminal_guid
|
||||
)(item))
|
||||
|
||||
case Terminal.SellEquipment() =>
|
||||
SellEquipmentFromInventory(tplayer, tplayer, msg.terminal_guid)(Player.FreeHandSlot)
|
||||
|
||||
case Terminal.LearnCertification(cert) =>
|
||||
avatarActor ! AvatarActor.LearnCertification(msg.terminal_guid, cert)
|
||||
ops.lastTerminalOrderFulfillment = true
|
||||
|
||||
case Terminal.SellCertification(cert) =>
|
||||
avatarActor ! AvatarActor.SellCertification(msg.terminal_guid, cert)
|
||||
ops.lastTerminalOrderFulfillment = true
|
||||
|
||||
case Terminal.LearnImplant(implant) =>
|
||||
avatarActor ! AvatarActor.LearnImplant(msg.terminal_guid, implant)
|
||||
ops.lastTerminalOrderFulfillment = true
|
||||
|
||||
case Terminal.SellImplant(implant) =>
|
||||
avatarActor ! AvatarActor.SellImplant(msg.terminal_guid, implant)
|
||||
ops.lastTerminalOrderFulfillment = true
|
||||
|
||||
case Terminal.BuyVehicle(vehicle, weapons, trunk) =>
|
||||
ops.buyVehicle(msg.terminal_guid, msg.transaction_type, vehicle, weapons, trunk)
|
||||
ops.lastTerminalOrderFulfillment = true
|
||||
|
||||
case Terminal.NoDeal() if msg != null =>
|
||||
val transaction = msg.transaction_type
|
||||
log.warn(s"NoDeal: ${tplayer.Name} made a request but the terminal rejected the ${transaction.toString} order")
|
||||
sendResponse(ItemTransactionResultMessage(msg.terminal_guid, transaction, success = false))
|
||||
ops.lastTerminalOrderFulfillment = true
|
||||
|
||||
case _ =>
|
||||
val terminal = msg.terminal_guid.guid
|
||||
continent.GUID(terminal) match {
|
||||
case Some(term: Terminal) =>
|
||||
log.warn(s"NoDeal?: ${tplayer.Name} made a request but the ${term.Definition.Name}#$terminal rejected the missing order")
|
||||
case Some(_) =>
|
||||
log.warn(s"NoDeal?: ${tplayer.Name} made a request to a non-terminal entity#$terminal")
|
||||
case None =>
|
||||
log.warn(s"NoDeal?: ${tplayer.Name} made a request to a missing entity#$terminal")
|
||||
}
|
||||
ops.lastTerminalOrderFulfillment = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,397 @@
|
|||
// Copyright (c) 2024 PSForever
|
||||
package net.psforever.actors.session.csr
|
||||
|
||||
import akka.actor.{ActorContext, typed}
|
||||
import net.psforever.actors.session.AvatarActor
|
||||
import net.psforever.actors.session.support.{SessionData, VehicleFunctions, VehicleOperations}
|
||||
import net.psforever.objects.serverobject.PlanetSideServerObject
|
||||
import net.psforever.objects.{PlanetSideGameObject, Player, Vehicle, Vehicles}
|
||||
import net.psforever.objects.serverobject.deploy.Deployment
|
||||
import net.psforever.objects.serverobject.mount.Mountable
|
||||
import net.psforever.objects.vehicles.control.BfrFlight
|
||||
import net.psforever.objects.vital.Vitality
|
||||
import net.psforever.objects.zones.Zone
|
||||
import net.psforever.packet.game.{ChildObjectStateMessage, DeployRequestMessage, FrameVehicleStateMessage, PlanetsideAttributeMessage, VehicleStateMessage, VehicleSubStateMessage}
|
||||
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
|
||||
import net.psforever.types.{DriveState, PlanetSideGUID, Vector3}
|
||||
|
||||
object VehicleLogic {
|
||||
def apply(ops: VehicleOperations): VehicleLogic = {
|
||||
new VehicleLogic(ops, ops.context)
|
||||
}
|
||||
}
|
||||
|
||||
class VehicleLogic(val ops: VehicleOperations, implicit val context: ActorContext) extends VehicleFunctions {
|
||||
def sessionLogic: SessionData = ops.sessionLogic
|
||||
|
||||
private val avatarActor: typed.ActorRef[AvatarActor.Command] = ops.avatarActor
|
||||
|
||||
/* packets */
|
||||
|
||||
def handleVehicleState(pkt: VehicleStateMessage): Unit = {
|
||||
val VehicleStateMessage(
|
||||
vehicle_guid,
|
||||
unk1,
|
||||
pos,
|
||||
ang,
|
||||
vel,
|
||||
is_flying,
|
||||
unk6,
|
||||
unk7,
|
||||
wheels,
|
||||
is_decelerating,
|
||||
is_cloaked
|
||||
) = pkt
|
||||
ops.GetVehicleAndSeat() match {
|
||||
case (Some(obj), Some(0)) =>
|
||||
//we're driving the vehicle
|
||||
sessionLogic.persist()
|
||||
sessionLogic.turnCounterFunc(player.GUID)
|
||||
sessionLogic.general.fallHeightTracker(pos.z)
|
||||
if (obj.MountedIn.isEmpty) {
|
||||
sessionLogic.updateBlockMap(obj, pos)
|
||||
}
|
||||
topOffHealthOfPlayer()
|
||||
topOffHealth(obj)
|
||||
player.Position = pos //convenient
|
||||
if (obj.WeaponControlledFromSeat(0).isEmpty) {
|
||||
player.Orientation = Vector3.z(ang.z) //convenient
|
||||
}
|
||||
obj.Position = pos
|
||||
obj.Orientation = ang
|
||||
if (obj.MountedIn.isEmpty) {
|
||||
if (obj.DeploymentState != DriveState.Deployed) {
|
||||
obj.Velocity = vel
|
||||
} else {
|
||||
obj.Velocity = Some(Vector3.Zero)
|
||||
}
|
||||
if (obj.Definition.CanFly) {
|
||||
obj.Flying = is_flying //usually Some(7)
|
||||
}
|
||||
obj.Cloaked = obj.Definition.CanCloak && is_cloaked
|
||||
} else {
|
||||
obj.Velocity = None
|
||||
obj.Flying = None
|
||||
}
|
||||
continent.VehicleEvents ! VehicleServiceMessage(
|
||||
continent.id,
|
||||
VehicleAction.VehicleState(
|
||||
player.GUID,
|
||||
vehicle_guid,
|
||||
unk1,
|
||||
obj.Position,
|
||||
ang,
|
||||
obj.Velocity,
|
||||
if (obj.isFlying) {
|
||||
is_flying
|
||||
} else {
|
||||
None
|
||||
},
|
||||
unk6,
|
||||
unk7,
|
||||
wheels,
|
||||
is_decelerating,
|
||||
obj.Cloaked
|
||||
)
|
||||
)
|
||||
sessionLogic.squad.updateSquad()
|
||||
player.allowInteraction = false
|
||||
obj.zoneInteractions()
|
||||
case (None, _) =>
|
||||
//log.error(s"VehicleState: no vehicle $vehicle_guid found in zone")
|
||||
//TODO placing a "not driving" warning here may trigger as we are disembarking the vehicle
|
||||
case (_, Some(index)) =>
|
||||
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)"
|
||||
)
|
||||
case _ => ()
|
||||
}
|
||||
if (player.death_by == -1) {
|
||||
sessionLogic.kickedByAdministration()
|
||||
}
|
||||
}
|
||||
|
||||
def handleFrameVehicleState(pkt: FrameVehicleStateMessage): Unit = {
|
||||
val FrameVehicleStateMessage(
|
||||
vehicle_guid,
|
||||
unk1,
|
||||
pos,
|
||||
ang,
|
||||
vel,
|
||||
unk2,
|
||||
unk3,
|
||||
unk4,
|
||||
is_crouched,
|
||||
is_airborne,
|
||||
ascending_flight,
|
||||
flight_time,
|
||||
unk9,
|
||||
unkA
|
||||
) = pkt
|
||||
ops.GetVehicleAndSeat() match {
|
||||
case (Some(obj), Some(0)) =>
|
||||
//we're driving the vehicle
|
||||
sessionLogic.persist()
|
||||
sessionLogic.turnCounterFunc(player.GUID)
|
||||
topOffHealthOfPlayer()
|
||||
topOffHealth(obj)
|
||||
val (position, angle, velocity, notMountedState) = continent.GUID(obj.MountedIn) match {
|
||||
case Some(v: Vehicle) =>
|
||||
sessionLogic.updateBlockMap(obj, pos)
|
||||
(pos, v.Orientation - Vector3.z(value = 90f) * Vehicles.CargoOrientation(obj).toFloat, v.Velocity, false)
|
||||
case _ =>
|
||||
(pos, ang, vel, true)
|
||||
}
|
||||
player.Position = position //convenient
|
||||
if (obj.WeaponControlledFromSeat(seatNumber = 0).isEmpty) {
|
||||
player.Orientation = Vector3.z(ang.z) //convenient
|
||||
}
|
||||
obj.Position = position
|
||||
obj.Orientation = angle
|
||||
obj.Velocity = velocity
|
||||
// if (is_crouched && obj.DeploymentState != DriveState.Kneeling) {
|
||||
// //dev stuff goes here
|
||||
// }
|
||||
// else
|
||||
// if (!is_crouched && obj.DeploymentState == DriveState.Kneeling) {
|
||||
// //dev stuff goes here
|
||||
// }
|
||||
obj.DeploymentState = if (is_crouched || !notMountedState) DriveState.Kneeling else DriveState.Mobile
|
||||
if (notMountedState) {
|
||||
if (obj.DeploymentState != DriveState.Kneeling) {
|
||||
if (is_airborne) {
|
||||
val flight = if (ascending_flight) flight_time else -flight_time
|
||||
obj.Flying = Some(flight)
|
||||
obj.Actor ! BfrFlight.Soaring(flight)
|
||||
} else if (obj.Flying.nonEmpty) {
|
||||
obj.Flying = None
|
||||
obj.Actor ! BfrFlight.Landed
|
||||
}
|
||||
} else {
|
||||
obj.Velocity = None
|
||||
obj.Flying = None
|
||||
}
|
||||
player.allowInteraction = false
|
||||
obj.zoneInteractions()
|
||||
} else {
|
||||
obj.Velocity = None
|
||||
obj.Flying = None
|
||||
}
|
||||
continent.VehicleEvents ! VehicleServiceMessage(
|
||||
continent.id,
|
||||
VehicleAction.FrameVehicleState(
|
||||
player.GUID,
|
||||
vehicle_guid,
|
||||
unk1,
|
||||
position,
|
||||
angle,
|
||||
velocity,
|
||||
unk2,
|
||||
unk3,
|
||||
unk4,
|
||||
is_crouched,
|
||||
is_airborne,
|
||||
ascending_flight,
|
||||
flight_time,
|
||||
unk9,
|
||||
unkA
|
||||
)
|
||||
)
|
||||
sessionLogic.squad.updateSquad()
|
||||
case (None, _) =>
|
||||
//log.error(s"VehicleState: no vehicle $vehicle_guid found in zone")
|
||||
//TODO placing a "not driving" warning here may trigger as we are disembarking the vehicle
|
||||
case (_, Some(index)) =>
|
||||
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)"
|
||||
)
|
||||
case _ => ()
|
||||
}
|
||||
if (player.death_by == -1) {
|
||||
sessionLogic.kickedByAdministration()
|
||||
}
|
||||
}
|
||||
|
||||
def handleChildObjectState(pkt: ChildObjectStateMessage): Unit = {
|
||||
val ChildObjectStateMessage(object_guid, pitch, yaw) = pkt
|
||||
val (o, tools) = sessionLogic.shooting.FindContainedWeapon
|
||||
//is COSM our primary upstream packet?
|
||||
(o match {
|
||||
case Some(mount: Mountable) => (o, mount.PassengerInSeat(player))
|
||||
case _ => (None, None)
|
||||
}) match {
|
||||
case (None, None) | (_, None) | (Some(_: Vehicle), Some(0)) =>
|
||||
()
|
||||
case (Some(obj: PlanetSideGameObject with Vitality), _) =>
|
||||
sessionLogic.persist()
|
||||
sessionLogic.turnCounterFunc(player.GUID)
|
||||
topOffHealthOfPlayer()
|
||||
topOffHealth(obj)
|
||||
case _ =>
|
||||
sessionLogic.persist()
|
||||
sessionLogic.turnCounterFunc(player.GUID)
|
||||
}
|
||||
//the majority of the following check retrieves information to determine if we are in control of the child
|
||||
tools.find { _.GUID == object_guid } match {
|
||||
case None =>
|
||||
//todo: old warning; this state is problematic, but can trigger in otherwise valid instances
|
||||
//log.warn(
|
||||
// s"ChildObjectState: ${player.Name} is using a different controllable agent than entity ${object_guid.guid}"
|
||||
//)
|
||||
case Some(_) =>
|
||||
//TODO set tool orientation?
|
||||
player.Orientation = Vector3(0f, pitch, yaw)
|
||||
continent.VehicleEvents ! VehicleServiceMessage(
|
||||
continent.id,
|
||||
VehicleAction.ChildObjectState(player.GUID, object_guid, pitch, yaw)
|
||||
)
|
||||
}
|
||||
//TODO status condition of "playing getting out of vehicle to allow for late packets without warning
|
||||
if (player.death_by == -1) {
|
||||
sessionLogic.kickedByAdministration()
|
||||
}
|
||||
}
|
||||
|
||||
def handleVehicleSubState(pkt: VehicleSubStateMessage): Unit = {
|
||||
val VehicleSubStateMessage(vehicle_guid, _, pos, ang, vel, unk1, _) = pkt
|
||||
sessionLogic.validObject(vehicle_guid, decorator = "VehicleSubState")
|
||||
.collect {
|
||||
case obj: Vehicle =>
|
||||
import net.psforever.login.WorldSession.boolToInt
|
||||
obj.Position = pos
|
||||
obj.Orientation = ang
|
||||
obj.Velocity = vel
|
||||
sessionLogic.updateBlockMap(obj, pos)
|
||||
obj.zoneInteractions()
|
||||
continent.VehicleEvents ! VehicleServiceMessage(
|
||||
continent.id,
|
||||
VehicleAction.VehicleState(
|
||||
player.GUID,
|
||||
vehicle_guid,
|
||||
unk1,
|
||||
pos,
|
||||
ang,
|
||||
obj.Velocity,
|
||||
obj.Flying,
|
||||
0,
|
||||
0,
|
||||
15,
|
||||
unk5 = false,
|
||||
obj.Cloaked
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
def handleDeployRequest(pkt: DeployRequestMessage): Unit = {
|
||||
val DeployRequestMessage(_, vehicle_guid, deploy_state, _, _, _) = pkt
|
||||
continent.GUID(vehicle_guid)
|
||||
.collect {
|
||||
case obj: Vehicle =>
|
||||
continent.Transport ! Zone.Vehicle.TryDeploymentChange(obj, deploy_state)
|
||||
obj
|
||||
case obj =>
|
||||
log.error(s"DeployRequest: ${player.Name} expected a vehicle, but found a ${obj.Definition.Name} instead")
|
||||
obj
|
||||
}
|
||||
.orElse {
|
||||
log.error(s"DeployRequest: ${player.Name} can not find entity $vehicle_guid")
|
||||
avatarActor ! AvatarActor.SetVehicle(None) //todo is this safe
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/* messages */
|
||||
|
||||
def handleCanDeploy(obj: Deployment.DeploymentObject, state: DriveState.Value): Unit = {
|
||||
if (!Deployment.CheckForDeployState(state)) {
|
||||
CanNotChangeDeployment(obj, state, "incorrect deploy state")
|
||||
}
|
||||
}
|
||||
|
||||
def handleCanUndeploy(obj: Deployment.DeploymentObject, state: DriveState.Value): Unit = {
|
||||
if (!Deployment.CheckForUndeployState(state)) {
|
||||
CanNotChangeDeployment(obj, state, "incorrect undeploy state")
|
||||
}
|
||||
}
|
||||
|
||||
def handleCanNotChangeDeployment(obj: Deployment.DeploymentObject, state: DriveState.Value, reason: String): Unit = {
|
||||
CanNotChangeDeployment(obj, state, reason)
|
||||
}
|
||||
|
||||
/* support functions */
|
||||
|
||||
/**
|
||||
* Common reporting behavior when a `Deployment` object fails to properly transition between states.
|
||||
* @param obj the game object that could not
|
||||
* @param state the `DriveState` that could not be promoted
|
||||
* @param reason a string explaining why the state can not or will not change
|
||||
*/
|
||||
private def CanNotChangeDeployment(
|
||||
obj: PlanetSideServerObject with Deployment,
|
||||
state: DriveState.Value,
|
||||
reason: String
|
||||
): Unit = {
|
||||
if (obj.DeploymentState != DriveState.Mobile) {
|
||||
obj.DeploymentState = DriveState.Mobile
|
||||
sendResponse(DeployRequestMessage(player.GUID, obj.GUID, DriveState.Mobile, 0, unk3=false, Vector3.Zero))
|
||||
continent.VehicleEvents ! VehicleServiceMessage(
|
||||
continent.id,
|
||||
VehicleAction.DeployRequest(player.GUID, obj.GUID, DriveState.Mobile, 0, unk2=false, Vector3.Zero)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private def topOffHealth(obj: PlanetSideGameObject with Vitality): Unit = {
|
||||
obj match {
|
||||
case _: Player => topOffHealthOfPlayer()
|
||||
case v: Vehicle => topOffHealthOfVehicle(v)
|
||||
case o: PlanetSideGameObject with Vitality => topOffHealthOfGeneric(o)
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
|
||||
private def topOffHealthOfPlayer(): Unit = {
|
||||
//driver below half health, full heal
|
||||
val maxHealthOfPlayer = player.MaxHealth.toLong
|
||||
if (player.Health < maxHealthOfPlayer * 0.5f) {
|
||||
player.Health = maxHealthOfPlayer.toInt
|
||||
player.LogActivity(player.ClearHistory().head)
|
||||
sendResponse(PlanetsideAttributeMessage(player.GUID, 0, maxHealthOfPlayer))
|
||||
continent.AvatarEvents ! AvatarServiceMessage(sessionLogic.zoning.zoneChannel, AvatarAction.PlanetsideAttribute(player.GUID, 0, maxHealthOfPlayer))
|
||||
}
|
||||
}
|
||||
|
||||
private def topOffHealthOfVehicle(vehicle: Vehicle): Unit = {
|
||||
topOffHealthOfPlayer()
|
||||
topOffHealthOfGeneric(vehicle)
|
||||
//vehicle shields below half, full shields
|
||||
val maxShieldsOfVehicle = vehicle.MaxShields.toLong
|
||||
val shieldsUi = vehicle.Definition.shieldUiAttribute
|
||||
if (vehicle.Shields < maxShieldsOfVehicle) {
|
||||
val guid = vehicle.GUID
|
||||
vehicle.Shields = maxShieldsOfVehicle.toInt
|
||||
sendResponse(PlanetsideAttributeMessage(guid, shieldsUi, maxShieldsOfVehicle))
|
||||
continent.VehicleEvents ! VehicleServiceMessage(
|
||||
continent.id,
|
||||
VehicleAction.PlanetsideAttribute(PlanetSideGUID(0), guid, shieldsUi, maxShieldsOfVehicle)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private def topOffHealthOfGeneric(obj: PlanetSideGameObject with Vitality): Unit = {
|
||||
topOffHealthOfPlayer()
|
||||
//vehicle below half health, full heal
|
||||
val guid = obj.GUID
|
||||
val maxHealthOf = obj.MaxHealth.toLong
|
||||
if (obj.Health < maxHealthOf) {
|
||||
obj.Health = maxHealthOf.toInt
|
||||
sendResponse(PlanetsideAttributeMessage(guid, 0, maxHealthOf))
|
||||
continent.VehicleEvents ! VehicleServiceMessage(
|
||||
continent.id,
|
||||
VehicleAction.PlanetsideAttribute(PlanetSideGUID(0), guid, 0, maxHealthOf)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,208 @@
|
|||
// Copyright (c) 2024 PSForever
|
||||
package net.psforever.actors.session.csr
|
||||
|
||||
import akka.actor.ActorContext
|
||||
import net.psforever.actors.session.support.{SessionData, WeaponAndProjectileFunctions, WeaponAndProjectileOperations}
|
||||
import net.psforever.objects.inventory.Container
|
||||
import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject}
|
||||
import net.psforever.objects.{BoomerDeployable, BoomerTrigger, Player, SpecialEmp, Tool, Vehicle}
|
||||
import net.psforever.objects.vital.base.{DamageResolution, DamageType}
|
||||
import net.psforever.objects.zones.{Zone, ZoneProjectile}
|
||||
import net.psforever.packet.game.{AIDamage, AvatarGrenadeStateMessage, ChangeAmmoMessage, ChangeFireModeMessage, ChangeFireStateMessage_Start, ChangeFireStateMessage_Stop, HitMessage, LashMessage, LongRangeProjectileInfoMessage, ProjectileStateMessage, ReloadMessage, SplashHitMessage, UplinkRequest, WeaponDelayFireMessage, WeaponDryFireMessage, WeaponFireMessage, WeaponLazeTargetPositionMessage}
|
||||
import net.psforever.types.Vector3
|
||||
|
||||
object WeaponAndProjectileLogic {
|
||||
def apply(ops: WeaponAndProjectileOperations): WeaponAndProjectileLogic = {
|
||||
new WeaponAndProjectileLogic(ops, ops.context)
|
||||
}
|
||||
}
|
||||
|
||||
class WeaponAndProjectileLogic(val ops: WeaponAndProjectileOperations, implicit val context: ActorContext) extends WeaponAndProjectileFunctions {
|
||||
def sessionLogic: SessionData = ops.sessionLogic
|
||||
|
||||
//private val avatarActor: typed.ActorRef[AvatarActor.Command] = ops.avatarActor
|
||||
|
||||
/* packets */
|
||||
|
||||
def handleWeaponFire(pkt: WeaponFireMessage): Unit = {
|
||||
ops.handleWeaponFireOperations(pkt)
|
||||
}
|
||||
|
||||
def handleWeaponDelayFire(pkt: WeaponDelayFireMessage): Unit = {
|
||||
val WeaponDelayFireMessage(_, _) = pkt
|
||||
}
|
||||
|
||||
def handleWeaponDryFire(pkt: WeaponDryFireMessage): Unit = {
|
||||
ops.handleWeaponDryFire(pkt)
|
||||
}
|
||||
|
||||
def handleWeaponLazeTargetPosition(pkt: WeaponLazeTargetPositionMessage): Unit = { /* laze is handled elsewhere */ }
|
||||
|
||||
def handleUplinkRequest(packet: UplinkRequest): Unit = { /* CUD not implemented yet */ }
|
||||
|
||||
def handleAvatarGrenadeState(pkt: AvatarGrenadeStateMessage): Unit = { /* grenades are handled elsewhere */ }
|
||||
|
||||
def handleChangeFireStateStart(pkt: ChangeFireStateMessage_Start): Unit = {
|
||||
val ChangeFireStateMessage_Start(item_guid) = pkt
|
||||
if (ops.shooting.isEmpty) {
|
||||
sessionLogic.findEquipment(item_guid) match {
|
||||
case Some(tool: Tool) if player.VehicleSeated.isEmpty =>
|
||||
ops.fireStateStartWhenPlayer(tool, item_guid)
|
||||
case Some(tool: Tool) =>
|
||||
ops.fireStateStartWhenMounted(tool, item_guid)
|
||||
case Some(_) if player.VehicleSeated.isEmpty =>
|
||||
ops.fireStateStartSetup(item_guid)
|
||||
ops.fireStateStartPlayerMessages(item_guid)
|
||||
case Some(_) =>
|
||||
ops.fireStateStartSetup(item_guid)
|
||||
ops.fireStateStartMountedMessages(item_guid)
|
||||
case None =>
|
||||
log.warn(s"ChangeFireState_Start: can not find $item_guid")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def handleChangeFireStateStop(pkt: ChangeFireStateMessage_Stop): Unit = {
|
||||
val ChangeFireStateMessage_Stop(item_guid) = pkt
|
||||
val now = System.currentTimeMillis()
|
||||
ops.prefire -= item_guid
|
||||
ops.shootingStop += item_guid -> now
|
||||
ops.shooting -= item_guid
|
||||
sessionLogic.findEquipment(item_guid) match {
|
||||
case Some(tool: Tool) if player.VehicleSeated.isEmpty =>
|
||||
ops.fireStateStopWhenPlayer(tool, item_guid)
|
||||
case Some(tool: Tool) =>
|
||||
ops.fireStateStopWhenMounted(tool, item_guid)
|
||||
case Some(trigger: BoomerTrigger) =>
|
||||
ops.fireStateStopPlayerMessages(item_guid)
|
||||
continent.GUID(trigger.Companion).collect {
|
||||
case boomer: BoomerDeployable =>
|
||||
boomer.Actor ! CommonMessages.Use(player, Some(trigger))
|
||||
}
|
||||
case Some(_) if player.VehicleSeated.isEmpty =>
|
||||
ops.fireStateStopPlayerMessages(item_guid)
|
||||
case Some(_) =>
|
||||
ops.fireStateStopMountedMessages(item_guid)
|
||||
case _ =>
|
||||
log.warn(s"ChangeFireState_Stop: can not find $item_guid")
|
||||
}
|
||||
sessionLogic.general.progressBarUpdate.cancel()
|
||||
sessionLogic.general.progressBarValue = None
|
||||
}
|
||||
|
||||
def handleReload(pkt: ReloadMessage): Unit = {
|
||||
val ReloadMessage(item_guid, _, unk1) = pkt
|
||||
ops.FindContainedWeapon match {
|
||||
case (Some(obj: Player), tools) =>
|
||||
ops.handleReloadWhenPlayer(item_guid, obj, tools, unk1)
|
||||
case (Some(obj: PlanetSideServerObject with Container), tools) =>
|
||||
ops.handleReloadWhenMountable(item_guid, obj, tools, unk1)
|
||||
case (_, _) =>
|
||||
log.warn(s"ReloadMessage: either can not find $item_guid or the object found was not a Tool")
|
||||
}
|
||||
}
|
||||
|
||||
def handleChangeAmmo(pkt: ChangeAmmoMessage): Unit = {
|
||||
ops.handleChangeAmmo(pkt)
|
||||
}
|
||||
|
||||
def handleChangeFireMode(pkt: ChangeFireModeMessage): Unit = {
|
||||
ops.handleChangeFireMode(pkt)
|
||||
}
|
||||
|
||||
def handleProjectileState(pkt: ProjectileStateMessage): Unit = {
|
||||
ops.handleProjectileState(pkt)
|
||||
}
|
||||
|
||||
def handleLongRangeProjectileState(pkt: LongRangeProjectileInfoMessage): Unit = {
|
||||
val LongRangeProjectileInfoMessage(guid, _, _) = pkt
|
||||
ops.FindContainedWeapon match {
|
||||
case (Some(_: Vehicle), weapons)
|
||||
if weapons.exists(_.GUID == guid) => () //now what?
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
|
||||
def handleDirectHit(pkt: HitMessage): Unit = {
|
||||
val list = ops.composeDirectDamageInformation(pkt)
|
||||
if (!player.spectator) {
|
||||
list.foreach {
|
||||
case (target, projectile, _, _) =>
|
||||
ops.resolveProjectileInteraction(target, projectile, DamageResolution.Hit, target.Position)
|
||||
}
|
||||
//...
|
||||
if (list.isEmpty) {
|
||||
ops.handleProxyDamage(pkt.projectile_guid, pkt.hit_info.map(_.hit_pos).getOrElse(Vector3.Zero))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def handleSplashHit(pkt: SplashHitMessage): Unit = {
|
||||
val list = ops.composeSplashDamageInformation(pkt)
|
||||
if (!player.spectator) {
|
||||
if (list.nonEmpty) {
|
||||
val projectile = list.head._2
|
||||
val explosionPosition = projectile.Position
|
||||
val projectileGuid = projectile.GUID
|
||||
val profile = projectile.profile
|
||||
val (resolution1, resolution2) = profile.Aggravated match {
|
||||
case Some(_) if profile.ProjectileDamageTypes.contains(DamageType.Aggravated) =>
|
||||
(DamageResolution.AggravatedDirect, DamageResolution.AggravatedSplash)
|
||||
case _ =>
|
||||
(DamageResolution.Splash, DamageResolution.Splash)
|
||||
}
|
||||
//...
|
||||
val (direct, others) = list.partition { case (_, _, hitPos, targetPos) => hitPos == targetPos }
|
||||
direct.foreach {
|
||||
case (target, _, _, _) =>
|
||||
ops.resolveProjectileInteraction(target, projectile, resolution1, target.Position)
|
||||
}
|
||||
others.foreach {
|
||||
case (target, _, _, _) =>
|
||||
ops.resolveProjectileInteraction(target, projectile, resolution2, target.Position)
|
||||
}
|
||||
//...
|
||||
if (
|
||||
profile.HasJammedEffectDuration ||
|
||||
profile.JammerProjectile ||
|
||||
profile.SympatheticExplosion
|
||||
) {
|
||||
//can also substitute 'profile' for 'SpecialEmp.emp'
|
||||
Zone.serverSideDamage(
|
||||
continent,
|
||||
player,
|
||||
SpecialEmp.emp,
|
||||
SpecialEmp.createEmpInteraction(SpecialEmp.emp, explosionPosition),
|
||||
SpecialEmp.prepareDistanceCheck(player, explosionPosition, player.Faction),
|
||||
SpecialEmp.findAllBoomers(profile.DamageRadius)
|
||||
)
|
||||
}
|
||||
if (profile.ExistsOnRemoteClients && projectile.HasGUID) {
|
||||
continent.Projectile ! ZoneProjectile.Remove(projectileGuid)
|
||||
}
|
||||
}
|
||||
//...
|
||||
ops.handleProxyDamage(pkt.projectile_uid, pkt.projectile_pos)
|
||||
}
|
||||
}
|
||||
|
||||
def handleLashHit(pkt: LashMessage): Unit = {
|
||||
val list = ops.composeLashDamageInformation(pkt)
|
||||
if (!player.spectator) {
|
||||
list.foreach {
|
||||
case (target, projectile, _, targetPosition) =>
|
||||
ops.resolveProjectileInteraction(target, projectile, DamageResolution.Lash, targetPosition)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def handleAIDamage(pkt: AIDamage): Unit = {
|
||||
val list = ops.composeAIDamageInformation(pkt)
|
||||
if (!player.spectator && ops.confirmAIDamageTarget(pkt, list.map(_._1))) {
|
||||
list.foreach {
|
||||
case (target, projectile, _, targetPosition) =>
|
||||
ops.resolveProjectileInteraction(target, projectile, DamageResolution.Hit, targetPosition)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,8 +3,15 @@ package net.psforever.actors.session.normal
|
|||
|
||||
import akka.actor.{ActorContext, typed}
|
||||
import net.psforever.actors.session.support.AvatarHandlerFunctions
|
||||
import net.psforever.actors.zone.ZoneActor
|
||||
import net.psforever.objects.inventory.Container
|
||||
import net.psforever.objects.{Default, PlanetSideGameObject}
|
||||
import net.psforever.objects.serverobject.containable.ContainableBehavior
|
||||
import net.psforever.objects.serverobject.mount.Mountable
|
||||
import net.psforever.objects.sourcing.PlayerSource
|
||||
import net.psforever.objects.vital.interaction.Adversarial
|
||||
import net.psforever.packet.game.{AvatarImplantMessage, CreateShortcutMessage, ImplantAction}
|
||||
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||
import net.psforever.types.ImplantType
|
||||
|
||||
import scala.concurrent.duration._
|
||||
|
|
@ -21,7 +28,7 @@ import net.psforever.objects.vital.etc.ExplodingEntityReason
|
|||
import net.psforever.objects.zones.Zoning
|
||||
import net.psforever.packet.game.objectcreate.ObjectCreateMessageParent
|
||||
import net.psforever.packet.game.{ArmorChangedMessage, AvatarDeadStateMessage, ChangeAmmoMessage, ChangeFireModeMessage, ChangeFireStateMessage_Start, ChangeFireStateMessage_Stop, ChatMsg, DeadState, DestroyMessage, DrowningTarget, GenericActionMessage, GenericObjectActionMessage, HitHint, ItemTransactionResultMessage, ObjectCreateDetailedMessage, ObjectCreateMessage, ObjectDeleteMessage, ObjectHeldMessage, OxygenStateMessage, PlanetsideAttributeMessage, PlayerStateMessage, ProjectileStateMessage, ReloadMessage, SetEmpireMessage, UseItemMessage, WeaponDryFireMessage}
|
||||
import net.psforever.services.avatar.{AvatarAction, AvatarResponse, AvatarServiceMessage}
|
||||
import net.psforever.services.avatar.AvatarResponse
|
||||
import net.psforever.services.Service
|
||||
import net.psforever.types.{ChatMessageType, PlanetSideGUID, TransactionType, Vector3}
|
||||
import net.psforever.util.Config
|
||||
|
|
@ -311,10 +318,14 @@ class AvatarHandlerLogic(val ops: SessionAvatarHandlers, implicit val context: A
|
|||
//redraw
|
||||
if (maxhand) {
|
||||
sendResponse(PlanetsideAttributeMessage(target, attribute_type=7, player.Capacitor.toLong))
|
||||
TaskWorkflow.execute(HoldNewEquipmentUp(player)(
|
||||
Tool(GlobalDefinitions.MAXArms(subtype, player.Faction)),
|
||||
0
|
||||
))
|
||||
val maxArmDefinition = GlobalDefinitions.MAXArms(subtype, player.Faction)
|
||||
TaskWorkflow.execute(HoldNewEquipmentUp(player)(Tool(maxArmDefinition), slot = 0))
|
||||
player.avatar.purchaseCooldown(maxArmDefinition)
|
||||
.collect(a => a)
|
||||
.getOrElse {
|
||||
avatarActor ! AvatarActor.UpdatePurchaseTime(maxArmDefinition)
|
||||
None
|
||||
}
|
||||
}
|
||||
//draw free hand
|
||||
player.FreeHand.Equipment.foreach { obj =>
|
||||
|
|
@ -342,6 +353,8 @@ class AvatarHandlerLogic(val ops: SessionAvatarHandlers, implicit val context: A
|
|||
)
|
||||
}
|
||||
DropLeftovers(player)(drop)
|
||||
//deactivate non-passive implants
|
||||
avatarActor ! AvatarActor.DeactivateActiveImplants
|
||||
|
||||
case AvatarResponse.ChangeExosuit(target, armor, exosuit, subtype, slot, _, oldHolsters, holsters, _, _, drop, delete) =>
|
||||
sendResponse(ArmorChangedMessage(target, exosuit, subtype))
|
||||
|
|
@ -394,14 +407,19 @@ class AvatarHandlerLogic(val ops: SessionAvatarHandlers, implicit val context: A
|
|||
drops.foreach(item => sendResponse(ObjectDeleteMessage(item.obj.GUID, unk1=0)))
|
||||
//redraw
|
||||
if (maxhand) {
|
||||
val maxArmWeapon = GlobalDefinitions.MAXArms(subtype, player.Faction)
|
||||
sendResponse(PlanetsideAttributeMessage(target, attribute_type=7, player.Capacitor.toLong))
|
||||
TaskWorkflow.execute(HoldNewEquipmentUp(player)(
|
||||
Tool(GlobalDefinitions.MAXArms(subtype, player.Faction)),
|
||||
slot = 0
|
||||
))
|
||||
TaskWorkflow.execute(HoldNewEquipmentUp(player)(Tool(maxArmWeapon), slot = 0))
|
||||
player.avatar.purchaseCooldown(maxArmWeapon)
|
||||
if (!oldHolsters.exists { case (e, _) => e.Definition == maxArmWeapon } &&
|
||||
player.avatar.purchaseCooldown(maxArmWeapon).isEmpty) {
|
||||
avatarActor ! AvatarActor.UpdatePurchaseTime(maxArmWeapon) //switching for first time causes cooldown
|
||||
}
|
||||
}
|
||||
sessionLogic.general.applyPurchaseTimersBeforePackingLoadout(player, player, holsters ++ inventory)
|
||||
DropLeftovers(player)(drops)
|
||||
//deactivate non-passive implants
|
||||
avatarActor ! AvatarActor.DeactivateActiveImplants
|
||||
|
||||
case AvatarResponse.ChangeLoadout(target, armor, exosuit, subtype, slot, _, oldHolsters, _, _, _, _) =>
|
||||
//redraw handled by callbacks
|
||||
|
|
@ -466,9 +484,33 @@ class AvatarHandlerLogic(val ops: SessionAvatarHandlers, implicit val context: A
|
|||
if isNotSameTarget && ops.lastSeenStreamMessage.get(guid.guid).exists { _.visible } =>
|
||||
sendResponse(ReloadMessage(itemGuid, ammo_clip=1, unk1=0))
|
||||
|
||||
case AvatarResponse.Killed(mount) =>
|
||||
case AvatarResponse.Killed(cause, mount) =>
|
||||
//log and chat messages
|
||||
val cause = player.LastDamage.flatMap { damage =>
|
||||
//destroy display
|
||||
val zoneChannel = continent.id
|
||||
val events = continent.AvatarEvents
|
||||
val pentry = PlayerSource(player)
|
||||
cause
|
||||
.adversarial
|
||||
.collect { case out @ Adversarial(attacker, _, _) if attacker != PlayerSource.Nobody => out }
|
||||
.orElse {
|
||||
player.LastDamage.collect {
|
||||
case attack if System.currentTimeMillis() - attack.interaction.hitTime < (10 seconds).toMillis =>
|
||||
attack
|
||||
.adversarial
|
||||
.collect { case out @ Adversarial(attacker, _, _) if attacker != PlayerSource.Nobody => out }
|
||||
}.flatten
|
||||
} match {
|
||||
case Some(adversarial) =>
|
||||
events ! AvatarServiceMessage(
|
||||
zoneChannel,
|
||||
AvatarAction.DestroyDisplay(adversarial.attacker, pentry, adversarial.implement)
|
||||
)
|
||||
case _ =>
|
||||
events ! AvatarServiceMessage(zoneChannel, AvatarAction.DestroyDisplay(pentry, pentry, 0))
|
||||
}
|
||||
//events chat and log
|
||||
val excuse = player.LastDamage.flatMap { damage =>
|
||||
val interaction = damage.interaction
|
||||
val reason = interaction.cause
|
||||
val adversarial = interaction.adversarial.map { _.attacker }
|
||||
|
|
@ -480,15 +522,17 @@ class AvatarHandlerLogic(val ops: SessionAvatarHandlers, implicit val context: A
|
|||
}
|
||||
adversarial.map {_.Name }.orElse { Some(s"a ${reason.getClass.getSimpleName}") }
|
||||
}.getOrElse { s"an unfortunate circumstance (probably ${player.Sex.pronounObject} own fault)" }
|
||||
log.info(s"${player.Name} has died, killed by $cause")
|
||||
log.info(s"${player.Name} has died, killed by $excuse")
|
||||
if (sessionLogic.shooting.shotsWhileDead > 0) {
|
||||
log.warn(
|
||||
s"SHOTS_WHILE_DEAD: client of ${avatar.name} fired ${sessionLogic.shooting.shotsWhileDead} rounds while character was dead on server"
|
||||
)
|
||||
sessionLogic.shooting.shotsWhileDead = 0
|
||||
}
|
||||
//TODO other methods of death?
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason(msg = "cancel")
|
||||
sessionLogic.general.renewCharSavedTimer(fixedLen = 1800L, varLen = 0L)
|
||||
continent.actor ! ZoneActor.RewardThisDeath(player)
|
||||
|
||||
//player state changes
|
||||
AvatarActor.updateToolDischargeFor(avatar)
|
||||
|
|
@ -500,9 +544,18 @@ class AvatarHandlerLogic(val ops: SessionAvatarHandlers, implicit val context: A
|
|||
sessionLogic.keepAliveFunc = sessionLogic.zoning.NormalKeepAlive
|
||||
sessionLogic.zoning.zoningStatus = Zoning.Status.None
|
||||
sessionLogic.zoning.spawn.deadState = DeadState.Dead
|
||||
continent.GUID(mount).collect { case obj: Vehicle =>
|
||||
sessionLogic.vehicles.ConditionalDriverVehicleControl(obj)
|
||||
sessionLogic.general.unaccessContainer(obj)
|
||||
continent.GUID(mount).collect {
|
||||
case obj: Vehicle =>
|
||||
killedWhileMounted(obj, resolvedPlayerGuid)
|
||||
sessionLogic.vehicles.ConditionalDriverVehicleControl(obj)
|
||||
sessionLogic.general.unaccessContainer(obj)
|
||||
|
||||
case obj: PlanetSideGameObject with Mountable with Container =>
|
||||
killedWhileMounted(obj, resolvedPlayerGuid)
|
||||
sessionLogic.general.unaccessContainer(obj)
|
||||
|
||||
case obj: PlanetSideGameObject with Mountable =>
|
||||
killedWhileMounted(obj, resolvedPlayerGuid)
|
||||
}
|
||||
sessionLogic.actionsToCancel()
|
||||
sessionLogic.terminals.CancelAllProximityUnits()
|
||||
|
|
@ -510,7 +563,10 @@ class AvatarHandlerLogic(val ops: SessionAvatarHandlers, implicit val context: A
|
|||
sessionLogic.zoning.spawn.shiftPosition = Some(player.Position)
|
||||
|
||||
//respawn
|
||||
val respawnTimer = 300000 //milliseconds
|
||||
sendResponse(AvatarDeadStateMessage(DeadState.Dead, respawnTimer, respawnTimer, player.Position, player.Faction, unk5=true))
|
||||
sessionLogic.zoning.spawn.reviveTimer.cancel()
|
||||
sessionLogic.zoning.spawn.reviveTimer = Default.Cancellable
|
||||
if (player.death_by == 0) {
|
||||
sessionLogic.zoning.spawn.randomRespawn(300.seconds)
|
||||
} else {
|
||||
|
|
@ -523,16 +579,7 @@ class AvatarHandlerLogic(val ops: SessionAvatarHandlers, implicit val context: A
|
|||
case AvatarResponse.Revive(revivalTargetGuid)
|
||||
if resolvedPlayerGuid == revivalTargetGuid =>
|
||||
log.info(s"No time for rest, ${player.Name}. Back on your feet!")
|
||||
sessionLogic.zoning.spawn.reviveTimer.cancel()
|
||||
sessionLogic.zoning.spawn.deadState = DeadState.Alive
|
||||
player.Revive
|
||||
val health = player.Health
|
||||
sendResponse(PlanetsideAttributeMessage(revivalTargetGuid, attribute_type=0, health))
|
||||
sendResponse(AvatarDeadStateMessage(DeadState.Alive, timer_max=0, timer=0, player.Position, player.Faction, unk5=true))
|
||||
continent.AvatarEvents ! AvatarServiceMessage(
|
||||
continent.id,
|
||||
AvatarAction.PlanetsideAttributeToAll(revivalTargetGuid, attribute_type=0, health)
|
||||
)
|
||||
ops.revive(revivalTargetGuid)
|
||||
|
||||
/* uncommon messages (utility, or once in a while) */
|
||||
case AvatarResponse.ChangeAmmo(weapon_guid, weapon_slot, previous_guid, ammo_id, ammo_guid, ammo_data)
|
||||
|
|
@ -622,4 +669,13 @@ class AvatarHandlerLogic(val ops: SessionAvatarHandlers, implicit val context: A
|
|||
case _ => ()
|
||||
}
|
||||
}
|
||||
|
||||
def killedWhileMounted(obj: PlanetSideGameObject with Mountable, playerGuid: PlanetSideGUID): Unit = {
|
||||
val events = continent.AvatarEvents
|
||||
ops.killedWhileMounted(obj, playerGuid)
|
||||
//make player invisible on client
|
||||
events ! AvatarServiceMessage(player.Name, AvatarAction.PlanetsideAttributeToAll(playerGuid, 29, 1))
|
||||
//only the dead player should "see" their own body, so that the death camera has something to focus on
|
||||
events ! AvatarServiceMessage(continent.id, AvatarAction.ObjectDelete(playerGuid, playerGuid))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,12 +2,14 @@
|
|||
package net.psforever.actors.session.normal
|
||||
|
||||
import akka.actor.ActorContext
|
||||
import net.psforever.actors.session.SessionActor
|
||||
import net.psforever.actors.session.spectator.SpectatorMode
|
||||
import net.psforever.actors.session.support.{ChatFunctions, ChatOperations, SessionData}
|
||||
import net.psforever.objects.Session
|
||||
import net.psforever.objects.avatar.ModePermissions
|
||||
import net.psforever.packet.game.{ChatMsg, SetChatFilterMessage}
|
||||
import net.psforever.packet.game.{ChatMsg, ServerType, SetChatFilterMessage}
|
||||
import net.psforever.services.chat.DefaultChannel
|
||||
import net.psforever.types.ChatMessageType
|
||||
import net.psforever.types.ChatMessageType.{CMT_TOGGLESPECTATORMODE, CMT_TOGGLE_GM}
|
||||
import net.psforever.util.Config
|
||||
|
||||
object ChatLogic {
|
||||
|
|
@ -19,34 +21,27 @@ object ChatLogic {
|
|||
class ChatLogic(val ops: ChatOperations, implicit val context: ActorContext) extends ChatFunctions {
|
||||
def sessionLogic: SessionData = ops.sessionLogic
|
||||
|
||||
ops.CurrentSpectatorMode = SpectatorMode
|
||||
ops.transitoryCommandEntered = None
|
||||
|
||||
def handleChatMsg(message: ChatMsg): Unit = {
|
||||
import net.psforever.types.ChatMessageType._
|
||||
val isAlive = if (player != null) player.isAlive else false
|
||||
val perms = if (avatar != null) avatar.permissions else ModePermissions()
|
||||
val gmCommandAllowed = (session.account.gm && perms.canGM) ||
|
||||
Config.app.development.unprivilegedGmCommands.contains(message.messageType)
|
||||
lazy val isAlive = avatar != null && player != null && player.isAlive
|
||||
(message.messageType, message.recipient.trim, message.contents.trim) match {
|
||||
/** Messages starting with ! are custom chat commands */
|
||||
case (_, _, contents) if contents.startsWith("!") &&
|
||||
customCommandMessages(message, session) => ()
|
||||
|
||||
case (CMT_FLY, recipient, contents) if gmCommandAllowed =>
|
||||
ops.commandFly(contents, recipient)
|
||||
case (CMT_ANONYMOUS, _, _) => ()
|
||||
|
||||
case (CMT_ANONYMOUS, _, _) =>
|
||||
// ?
|
||||
|
||||
case (CMT_TOGGLE_GM, _, _) =>
|
||||
// ?
|
||||
case (CMT_TOGGLE_GM, _, contents) if isAlive =>
|
||||
customCommandModerator(contents)
|
||||
|
||||
case (CMT_CULLWATERMARK, _, contents) =>
|
||||
ops.commandWatermark(contents)
|
||||
|
||||
case (CMT_SPEED, _, contents) if gmCommandAllowed =>
|
||||
ops.commandSpeed(message, contents)
|
||||
|
||||
case (CMT_TOGGLESPECTATORMODE, _, contents) if isAlive && (gmCommandAllowed || perms.canSpectate) =>
|
||||
ops.commandToggleSpectatorMode(session, contents)
|
||||
case (CMT_TOGGLESPECTATORMODE, _, contents) if isAlive =>
|
||||
commandToggleSpectatorMode(contents)
|
||||
|
||||
case (CMT_RECALL, _, _) =>
|
||||
ops.commandRecall(session)
|
||||
|
|
@ -63,28 +58,6 @@ class ChatLogic(val ops: ChatOperations, implicit val context: ActorContext) ext
|
|||
case (CMT_DESTROY, _, contents) if contents.matches("\\d+") =>
|
||||
ops.commandDestroy(session, message, contents)
|
||||
|
||||
case (CMT_SETBASERESOURCES, _, contents) if gmCommandAllowed =>
|
||||
ops.commandSetBaseResources(session, contents)
|
||||
|
||||
case (CMT_ZONELOCK, _, contents) if gmCommandAllowed =>
|
||||
ops.commandZoneLock(contents)
|
||||
|
||||
case (U_CMT_ZONEROTATE, _, _) if gmCommandAllowed =>
|
||||
ops.commandZoneRotate()
|
||||
|
||||
case (CMT_CAPTUREBASE, _, contents) if gmCommandAllowed =>
|
||||
ops.commandCaptureBase(session, message, contents)
|
||||
|
||||
case (CMT_GMBROADCAST | CMT_GMBROADCAST_NC | CMT_GMBROADCAST_VS | CMT_GMBROADCAST_TR, _, _)
|
||||
if gmCommandAllowed =>
|
||||
ops.commandSendToRecipient(session, message, DefaultChannel)
|
||||
|
||||
case (CMT_GMTELL, _, _) if gmCommandAllowed =>
|
||||
ops.commandSend(session, message, DefaultChannel)
|
||||
|
||||
case (CMT_GMBROADCASTPOPUP, _, _) if gmCommandAllowed =>
|
||||
ops.commandSendToRecipient(session, message, DefaultChannel)
|
||||
|
||||
case (CMT_OPEN, _, _) if !player.silenced =>
|
||||
ops.commandSendToRecipient(session, message, DefaultChannel)
|
||||
|
||||
|
|
@ -100,54 +73,21 @@ class ChatLogic(val ops: ChatOperations, implicit val context: ActorContext) ext
|
|||
case (CMT_PLATOON, _, _) if !player.silenced =>
|
||||
ops.commandSendToRecipient(session, message, DefaultChannel)
|
||||
|
||||
case (CMT_COMMAND, _, _) if gmCommandAllowed =>
|
||||
ops.commandSendToRecipient(session, message, DefaultChannel)
|
||||
|
||||
case (CMT_NOTE, _, _) =>
|
||||
ops.commandSend(session, message, DefaultChannel)
|
||||
|
||||
case (CMT_SILENCE, _, _) if gmCommandAllowed =>
|
||||
ops.commandSend(session, message, DefaultChannel)
|
||||
|
||||
case (CMT_SQUAD, _, _) =>
|
||||
ops.commandSquad(session, message, DefaultChannel) //todo SquadChannel, but what is the guid
|
||||
|
||||
case (CMT_WHO | CMT_WHO_CSR | CMT_WHO_CR | CMT_WHO_PLATOONLEADERS | CMT_WHO_SQUADLEADERS | CMT_WHO_TEAMS, _, _) =>
|
||||
ops.commandWho(session)
|
||||
|
||||
case (CMT_ZONE, _, contents) if gmCommandAllowed =>
|
||||
ops.commandZone(message, contents)
|
||||
|
||||
case (CMT_WARP, _, contents) if gmCommandAllowed =>
|
||||
ops.commandWarp(session, message, contents)
|
||||
|
||||
case (CMT_SETBATTLERANK, _, contents) if gmCommandAllowed =>
|
||||
ops.commandSetBattleRank(session, message, contents)
|
||||
|
||||
case (CMT_SETCOMMANDRANK, _, contents) if gmCommandAllowed =>
|
||||
ops.commandSetCommandRank(session, message, contents)
|
||||
|
||||
case (CMT_ADDBATTLEEXPERIENCE, _, contents) if gmCommandAllowed =>
|
||||
ops.commandAddBattleExperience(message, contents)
|
||||
|
||||
case (CMT_ADDCOMMANDEXPERIENCE, _, contents) if gmCommandAllowed =>
|
||||
ops.commandAddCommandExperience(message, contents)
|
||||
|
||||
case (CMT_TOGGLE_HAT, _, contents) =>
|
||||
ops.commandToggleHat(session, message, contents)
|
||||
|
||||
case (CMT_HIDE_HELMET | CMT_TOGGLE_SHADES | CMT_TOGGLE_EARPIECE, _, contents) =>
|
||||
ops.commandToggleCosmetics(session, message, contents)
|
||||
|
||||
case (CMT_ADDCERTIFICATION, _, contents) if gmCommandAllowed =>
|
||||
ops.commandAddCertification(session, message, contents)
|
||||
|
||||
case (CMT_KICK, _, contents) if gmCommandAllowed =>
|
||||
ops.commandKick(session, message, contents)
|
||||
|
||||
case (CMT_REPORTUSER, _, contents) =>
|
||||
ops.commandReportUser(session, message, contents)
|
||||
|
||||
case _ =>
|
||||
sendResponse(ChatMsg(ChatMessageType.UNK_227, "@no_permission"))
|
||||
}
|
||||
|
|
@ -192,51 +132,76 @@ class ChatLogic(val ops: ChatOperations, implicit val context: ActorContext) ext
|
|||
case a :: b => (a, b)
|
||||
case _ => ("", Seq(""))
|
||||
}
|
||||
val perms = if (avatar != null) avatar.permissions else ModePermissions()
|
||||
val gmBangCommandAllowed = (session.account.gm && perms.canGM) ||
|
||||
Config.app.development.unprivilegedGmBangCommands.contains(command)
|
||||
//try gm commands
|
||||
val tryGmCommandResult = if (gmBangCommandAllowed) {
|
||||
command match {
|
||||
case "whitetext" => Some(ops.customCommandWhitetext(session, params))
|
||||
case "list" => Some(ops.customCommandList(session, params, message))
|
||||
case "ntu" => Some(ops.customCommandNtu(session, params))
|
||||
case "zonerotate" => Some(ops.customCommandZonerotate(params))
|
||||
case "nearby" => Some(ops.customCommandNearby(session))
|
||||
case _ => None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
//try commands for all players if not caught as a gm command
|
||||
val result = tryGmCommandResult match {
|
||||
case None =>
|
||||
command match {
|
||||
case "loc" => ops.customCommandLoc(session, message)
|
||||
case "suicide" => ops.customCommandSuicide(session)
|
||||
case "grenade" => ops.customCommandGrenade(session, log)
|
||||
case "macro" => ops.customCommandMacro(session, params)
|
||||
case "progress" => ops.customCommandProgress(session, params)
|
||||
case _ => false
|
||||
}
|
||||
case Some(out) =>
|
||||
out
|
||||
}
|
||||
if (!result) {
|
||||
// command was not handled
|
||||
sendResponse(
|
||||
ChatMsg(
|
||||
ChatMessageType.CMT_GMOPEN, // CMT_GMTELL
|
||||
message.wideContents,
|
||||
"Server",
|
||||
s"Unknown command !$command",
|
||||
message.note
|
||||
command match {
|
||||
case "loc" => ops.customCommandLoc(session, message)
|
||||
case "suicide" => ops.customCommandSuicide(session)
|
||||
case "grenade" => ops.customCommandGrenade(session, log)
|
||||
case "macro" => ops.customCommandMacro(session, params)
|
||||
case "progress" => ops.customCommandProgress(session, params)
|
||||
case _ =>
|
||||
// command was not handled
|
||||
sendResponse(
|
||||
ChatMsg(
|
||||
ChatMessageType.CMT_GMOPEN, // CMT_GMTELL
|
||||
message.wideContents,
|
||||
"Server",
|
||||
s"Unknown command !$command",
|
||||
message.note
|
||||
)
|
||||
)
|
||||
)
|
||||
true
|
||||
}
|
||||
true // do not accidentally send mistyped commands to chat
|
||||
} else {
|
||||
false // not a handled command
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
def commandToggleSpectatorMode(contents: String): Boolean = {
|
||||
ops.transitoryCommandEntered
|
||||
.collect {
|
||||
case CMT_TOGGLESPECTATORMODE => true
|
||||
case CMT_TOGGLE_GM => false
|
||||
}
|
||||
.getOrElse {
|
||||
val currentSpectatorActivation =
|
||||
avatar.permissions.canSpectate ||
|
||||
avatar.permissions.canGM ||
|
||||
Config.app.world.serverType == ServerType.Development
|
||||
contents.toLowerCase() match {
|
||||
case "on" | "o" | "" if currentSpectatorActivation && !player.spectator =>
|
||||
ops.transitoryCommandEntered = Some(CMT_TOGGLESPECTATORMODE)
|
||||
context.self ! SessionActor.SetMode(ops.CurrentSpectatorMode)
|
||||
true
|
||||
case _ =>
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def customCommandModerator(contents: String): Boolean = {
|
||||
if (sessionLogic.zoning.maintainInitialGmState) {
|
||||
sessionLogic.zoning.maintainInitialGmState = false
|
||||
true
|
||||
} else {
|
||||
ops.transitoryCommandEntered
|
||||
.collect {
|
||||
case CMT_TOGGLE_GM => true
|
||||
case CMT_TOGGLESPECTATORMODE => false
|
||||
}
|
||||
.getOrElse {
|
||||
val currentCsrActivation =
|
||||
avatar.permissions.canGM ||
|
||||
Config.app.world.serverType == ServerType.Development
|
||||
contents.toLowerCase() match {
|
||||
case "on" | "o" | "" if currentCsrActivation =>
|
||||
import net.psforever.actors.session.csr.CustomerServiceRepresentativeMode
|
||||
ops.transitoryCommandEntered = Some(CMT_TOGGLE_GM)
|
||||
context.self ! SessionActor.SetMode(CustomerServiceRepresentativeMode)
|
||||
true
|
||||
case _ =>
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,17 +5,15 @@ import akka.actor.typed.scaladsl.adapter._
|
|||
import akka.actor.{ActorContext, ActorRef, typed}
|
||||
import net.psforever.actors.session.{AvatarActor, SessionActor}
|
||||
import net.psforever.actors.session.support.{GeneralFunctions, GeneralOperations, SessionData}
|
||||
import net.psforever.login.WorldSession.{CallBackForTask, ContainableMoveItem, DropEquipmentFromInventory, PickUpEquipmentFromGround, RemoveOldEquipmentFromInventory}
|
||||
import net.psforever.objects.{Account, BoomerDeployable, BoomerTrigger, ConstructionItem, Deployables, GlobalDefinitions, Kit, LivePlayerList, PlanetSideGameObject, Player, SensorDeployable, ShieldGeneratorDeployable, SpecialEmp, TelepadDeployable, Tool, TrapDeployable, TurretDeployable, Vehicle}
|
||||
import net.psforever.objects.{Account, BoomerDeployable, BoomerTrigger, ConstructionItem, GlobalDefinitions, LivePlayerList, Player, SensorDeployable, ShieldGeneratorDeployable, SpecialEmp, TelepadDeployable, Tool, TrapDeployable, TurretDeployable, Vehicle}
|
||||
import net.psforever.objects.avatar.{Avatar, PlayerControl, SpecialCarry}
|
||||
import net.psforever.objects.ballistics.Projectile
|
||||
import net.psforever.objects.ce.{Deployable, DeployedItem, TelepadLike}
|
||||
import net.psforever.objects.ce.{Deployable, DeployedItem}
|
||||
import net.psforever.objects.definition.{BasicDefinition, KitDefinition, SpecialExoSuitDefinition}
|
||||
import net.psforever.objects.entity.WorldEntity
|
||||
import net.psforever.objects.equipment.Equipment
|
||||
import net.psforever.objects.guid.{GUIDTask, TaskBundle, TaskWorkflow}
|
||||
import net.psforever.objects.inventory.Container
|
||||
import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject, ServerObject}
|
||||
import net.psforever.objects.serverobject.{PlanetSideServerObject, ServerObject}
|
||||
import net.psforever.objects.serverobject.affinity.FactionAffinity
|
||||
import net.psforever.objects.serverobject.containable.Containable
|
||||
import net.psforever.objects.serverobject.doors.Door
|
||||
|
|
@ -24,30 +22,25 @@ import net.psforever.objects.serverobject.llu.CaptureFlag
|
|||
import net.psforever.objects.serverobject.locks.IFFLock
|
||||
import net.psforever.objects.serverobject.mblocker.Locker
|
||||
import net.psforever.objects.serverobject.resourcesilo.ResourceSilo
|
||||
import net.psforever.objects.serverobject.structures.{Building, WarpGate}
|
||||
import net.psforever.objects.serverobject.structures.WarpGate
|
||||
import net.psforever.objects.serverobject.terminals.capture.CaptureTerminal
|
||||
import net.psforever.objects.serverobject.terminals.{MatrixTerminalDefinition, ProximityUnit, Terminal}
|
||||
import net.psforever.objects.serverobject.terminals.{ProximityUnit, Terminal}
|
||||
import net.psforever.objects.serverobject.terminals.implant.ImplantTerminalMech
|
||||
import net.psforever.objects.serverobject.tube.SpawnTube
|
||||
import net.psforever.objects.serverobject.turret.FacilityTurret
|
||||
import net.psforever.objects.sourcing.{PlayerSource, SourceEntry, VehicleSource}
|
||||
import net.psforever.objects.vehicles.{AccessPermissionGroup, Utility, UtilityType, VehicleLockState}
|
||||
import net.psforever.objects.vehicles.Utility.InternalTelepad
|
||||
import net.psforever.objects.vital.{VehicleDismountActivity, VehicleMountActivity, Vitality}
|
||||
import net.psforever.objects.sourcing.SourceEntry
|
||||
import net.psforever.objects.vehicles.Utility
|
||||
import net.psforever.objects.vital.Vitality
|
||||
import net.psforever.objects.vital.collision.{CollisionReason, CollisionWithReason}
|
||||
import net.psforever.objects.vital.etc.SuicideReason
|
||||
import net.psforever.objects.vital.interaction.DamageInteraction
|
||||
import net.psforever.objects.zones.blockmap.BlockMapEntity
|
||||
import net.psforever.objects.zones.{Zone, ZoneProjectile, Zoning}
|
||||
import net.psforever.objects.zones.{ZoneProjectile, Zoning}
|
||||
import net.psforever.packet.PlanetSideGamePacket
|
||||
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, VoiceHostRequest, ZipLineMessage}
|
||||
import net.psforever.services.RemoverActor
|
||||
import net.psforever.packet.game.{ActionCancelMessage, ActionResultMessage, AvatarFirstTimeEventMessage, AvatarImplantMessage, AvatarJumpMessage, BattleplanMessage, BindPlayerMessage, BugReportMessage, ChangeFireModeMessage, ChangeShortcutBankMessage, CharacterCreateRequestMessage, CharacterRequestAction, CharacterRequestMessage, ChatMsg, CollisionIs, ConnectToWorldRequestMessage, CreateShortcutMessage, DeadState, DeployObjectMessage, DisplayedAwardMessage, DropItemMessage, EmoteMsg, FacilityBenefitShieldChargeRequestMessage, FriendsRequest, GenericAction, GenericActionMessage, GenericCollisionMsg, GenericObjectActionAtPositionMessage, GenericObjectActionMessage, GenericObjectStateMsg, HitHint, InvalidTerrainMessage, LootItemMessage, MoveItemMessage, ObjectDetectedMessage, ObjectHeldMessage, PickupItemMessage, PlanetsideAttributeMessage, PlayerStateMessageUpstream, RequestDestroyMessage, TargetingImplantRequest, TerrainCondition, TradeMessage, UnuseItemMessage, UseItemMessage, VoiceHostInfo, VoiceHostRequest, ZipLineMessage}
|
||||
import net.psforever.services.account.{AccountPersistenceService, RetrieveAccountData}
|
||||
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||
import net.psforever.services.local.{LocalAction, LocalServiceMessage}
|
||||
import net.psforever.services.local.support.CaptureFlagManager
|
||||
import net.psforever.types.{CapacitorStateType, ChatMessageType, Cosmetic, DriveState, ExoSuitType, ImplantType, PlanetSideEmpire, PlanetSideGUID, SpawnGroup, TransactionType, Vector3}
|
||||
import net.psforever.types.{CapacitorStateType, ChatMessageType, Cosmetic, ExoSuitType, ImplantType, PlanetSideEmpire, PlanetSideGUID, Vector3}
|
||||
import net.psforever.util.Config
|
||||
|
||||
import scala.concurrent.duration._
|
||||
|
|
@ -209,34 +202,25 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
|
|||
}
|
||||
|
||||
def handleDropItem(pkt: DropItemMessage): Unit = {
|
||||
val DropItemMessage(itemGuid) = pkt
|
||||
(sessionLogic.validObject(itemGuid, decorator = "DropItem"), player.FreeHand.Equipment) match {
|
||||
case (Some(anItem: Equipment), Some(heldItem))
|
||||
if (anItem eq heldItem) && continent.GUID(player.VehicleSeated).nonEmpty =>
|
||||
ops.handleDropItem(pkt) match {
|
||||
case GeneralOperations.ItemDropState.Dropped =>
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
|
||||
RemoveOldEquipmentFromInventory(player)(heldItem)
|
||||
case (Some(anItem: Equipment), Some(heldItem))
|
||||
if anItem eq heldItem =>
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
|
||||
DropEquipmentFromInventory(player)(heldItem)
|
||||
case (Some(anItem: Equipment), _)
|
||||
case GeneralOperations.ItemDropState.NotDropped
|
||||
if continent.GUID(player.VehicleSeated).isEmpty =>
|
||||
//suppress the warning message if in a vehicle
|
||||
log.warn(s"DropItem: ${player.Name} wanted to drop a ${anItem.Definition.Name}, but it wasn't at hand")
|
||||
case (Some(obj), _) =>
|
||||
log.warn(s"DropItem: ${player.Name} wanted to drop a ${obj.Definition.Name}, but it was not equipment")
|
||||
log.warn(s"DropItem: ${player.Name} wanted to drop an item, but it wasn't at hand")
|
||||
case GeneralOperations.ItemDropState.NotDropped =>
|
||||
log.warn(s"DropItem: ${player.Name} wanted to drop an item, but it was not equipment")
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
|
||||
def handlePickupItem(pkt: PickupItemMessage): Unit = {
|
||||
val PickupItemMessage(itemGuid, _, _, _) = pkt
|
||||
sessionLogic.validObject(itemGuid, decorator = "PickupItem").collect {
|
||||
case item: Equipment if player.Fit(item).nonEmpty =>
|
||||
ops.handlePickupItem(pkt) match {
|
||||
case GeneralOperations.ItemPickupState.PickedUp =>
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
|
||||
PickUpEquipmentFromGround(player)(item)
|
||||
case _: Equipment =>
|
||||
case GeneralOperations.ItemPickupState.Dropped =>
|
||||
sendResponse(ActionResultMessage.Fail(16)) //error code?
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -252,33 +236,17 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
|
|||
}
|
||||
|
||||
def handleZipLine(pkt: ZipLineMessage): Unit = {
|
||||
val ZipLineMessage(playerGuid, forwards, action, pathId, pos) = pkt
|
||||
continent.zipLinePaths.find(x => x.PathId == pathId) match {
|
||||
case Some(path) if path.IsTeleporter =>
|
||||
ops.handleZipLine(pkt) match {
|
||||
case GeneralOperations.ZiplineBehavior.Teleporter =>
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel")
|
||||
val endPoint = path.ZipLinePoints.last
|
||||
sendResponse(ZipLineMessage(PlanetSideGUID(0), forwards, 0, pathId, pos))
|
||||
//todo: send to zone to show teleport animation to all clients
|
||||
sendResponse(PlayerStateShiftMessage(ShiftState(0, endPoint, (player.Orientation.z + player.FacingYawUpper) % 360f, None)))
|
||||
case Some(_) =>
|
||||
case GeneralOperations.ZiplineBehavior.Zipline =>
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_motion")
|
||||
action match {
|
||||
case 0 =>
|
||||
//travel along the zipline in the direction specified
|
||||
sendResponse(ZipLineMessage(playerGuid, forwards, action, pathId, pos))
|
||||
case 1 =>
|
||||
//disembark from zipline at destination
|
||||
sendResponse(ZipLineMessage(playerGuid, forwards, action, 0, pos))
|
||||
case 2 =>
|
||||
//get off by force
|
||||
sendResponse(ZipLineMessage(playerGuid, forwards, action, 0, pos))
|
||||
case _ =>
|
||||
log.warn(
|
||||
s"${player.Name} tried to do something with a zipline but can't handle it. forwards: $forwards action: $action pathId: $pathId zone: ${continent.Number} / ${continent.id}"
|
||||
)
|
||||
}
|
||||
case _ =>
|
||||
log.warn(s"${player.Name} couldn't find a zipline path $pathId in zone ${continent.id}")
|
||||
case GeneralOperations.ZiplineBehavior.Unsupported =>
|
||||
log.warn(
|
||||
s"${player.Name} tried to do something with a zipline but can't handle it. action: ${pkt.action}, pathId: ${pkt.path_id}, zone: ${continent.id}"
|
||||
)
|
||||
case GeneralOperations.ZiplineBehavior.NotFound =>
|
||||
log.warn(s"${player.Name} couldn't find a zipline path ${pkt.path_id} in zone ${continent.id}")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -287,13 +255,11 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
|
|||
//make sure this is the correct response for all cases
|
||||
sessionLogic.validObject(objectGuid, decorator = "RequestDestroy") match {
|
||||
case Some(vehicle: Vehicle) =>
|
||||
/* line 1a: player is admin (and overrules other access requirements) */
|
||||
/* line 1b: vehicle and player (as the owner) acknowledge each other */
|
||||
/* line 1c: vehicle is the same faction as player, is ownable, and either the owner is absent or the vehicle is destroyed */
|
||||
/* line 1a: vehicle and player (as the owner) acknowledge each other */
|
||||
/* line 1b: vehicle is the same faction as player, is ownable, and either the owner is absent or the vehicle is destroyed */
|
||||
/* line 2: vehicle is not mounted in anything or, if it is, its seats are empty */
|
||||
if (
|
||||
(session.account.gm ||
|
||||
(player.avatar.vehicle.contains(objectGuid) && vehicle.OwnerGuid.contains(player.GUID)) ||
|
||||
((avatar.vehicle.contains(objectGuid) && vehicle.OwnerGuid.contains(player.GUID)) ||
|
||||
(player.Faction == vehicle.Faction &&
|
||||
(vehicle.Definition.CanBeOwned.nonEmpty &&
|
||||
(vehicle.OwnerGuid.isEmpty || continent.GUID(vehicle.OwnerGuid.get).isEmpty) || vehicle.Destroyed))) &&
|
||||
|
|
@ -312,7 +278,7 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
|
|||
continent.Projectile ! ZoneProjectile.Remove(objectGuid)
|
||||
|
||||
case Some(obj: BoomerTrigger) =>
|
||||
if (findEquipmentToDelete(objectGuid, obj)) {
|
||||
if (ops.findEquipmentToDelete(objectGuid, obj)) {
|
||||
continent.GUID(obj.Companion) match {
|
||||
case Some(boomer: BoomerDeployable) =>
|
||||
boomer.Trigger = None
|
||||
|
|
@ -324,14 +290,14 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
|
|||
}
|
||||
|
||||
case Some(obj: Deployable) =>
|
||||
if (session.account.gm || obj.OwnerGuid.isEmpty || obj.OwnerGuid.contains(player.GUID) || obj.Destroyed) {
|
||||
if (obj.OwnerGuid.isEmpty || obj.OwnerGuid.contains(player.GUID) || obj.Destroyed) {
|
||||
obj.Actor ! Deployable.Deconstruct()
|
||||
} else {
|
||||
log.warn(s"RequestDestroy: ${player.Name} must own the deployable in order to deconstruct it")
|
||||
}
|
||||
|
||||
case Some(obj: Equipment) =>
|
||||
findEquipmentToDelete(objectGuid, obj)
|
||||
ops.findEquipmentToDelete(objectGuid, obj)
|
||||
|
||||
case Some(thing) =>
|
||||
log.warn(s"RequestDestroy: not allowed to delete this ${thing.Definition.Name}")
|
||||
|
|
@ -341,99 +307,20 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
|
|||
}
|
||||
|
||||
def handleMoveItem(pkt: MoveItemMessage): Unit = {
|
||||
val MoveItemMessage(itemGuid, sourceGuid, destinationGuid, dest, _) = pkt
|
||||
(
|
||||
continent.GUID(sourceGuid),
|
||||
continent.GUID(destinationGuid),
|
||||
sessionLogic.validObject(itemGuid, decorator = "MoveItem")
|
||||
) match {
|
||||
case (
|
||||
Some(source: PlanetSideServerObject with Container),
|
||||
Some(destination: PlanetSideServerObject with Container),
|
||||
Some(item: Equipment)
|
||||
) =>
|
||||
ContainableMoveItem(player.Name, source, destination, item, destination.SlotMapResolution(dest))
|
||||
case (None, _, _) =>
|
||||
log.error(
|
||||
s"MoveItem: ${player.Name} wanted to move $itemGuid from $sourceGuid, but could not find source object"
|
||||
)
|
||||
case (_, None, _) =>
|
||||
log.error(
|
||||
s"MoveItem: ${player.Name} wanted to move $itemGuid to $destinationGuid, but could not find destination object"
|
||||
)
|
||||
case (_, _, None) => ()
|
||||
case _ =>
|
||||
log.error(
|
||||
s"MoveItem: ${player.Name} wanted to move $itemGuid from $sourceGuid to $destinationGuid, but multiple problems were encountered"
|
||||
)
|
||||
}
|
||||
ops.handleMoveItem(pkt)
|
||||
}
|
||||
|
||||
def handleLootItem(pkt: LootItemMessage): Unit = {
|
||||
val LootItemMessage(itemGuid, targetGuid) = pkt
|
||||
(sessionLogic.validObject(itemGuid, decorator = "LootItem"), continent.GUID(targetGuid)) match {
|
||||
case (Some(item: Equipment), Some(destination: PlanetSideServerObject with Container)) =>
|
||||
//figure out the source
|
||||
(
|
||||
{
|
||||
val findFunc: PlanetSideServerObject with Container => Option[
|
||||
(PlanetSideServerObject with Container, Option[Int])
|
||||
] = ops.findInLocalContainer(itemGuid)
|
||||
findFunc(player.avatar.locker)
|
||||
.orElse(findFunc(player))
|
||||
.orElse(ops.accessedContainer match {
|
||||
case Some(parent: PlanetSideServerObject) =>
|
||||
findFunc(parent)
|
||||
case _ =>
|
||||
None
|
||||
})
|
||||
},
|
||||
destination.Fit(item)
|
||||
) match {
|
||||
case (Some((source, Some(_))), Some(dest)) =>
|
||||
ContainableMoveItem(player.Name, source, destination, item, dest)
|
||||
case (None, _) =>
|
||||
log.error(s"LootItem: ${player.Name} can not find where $item is put currently")
|
||||
case (_, None) =>
|
||||
log.error(s"LootItem: ${player.Name} can not find anywhere to put $item in $destination")
|
||||
case _ =>
|
||||
log.error(
|
||||
s"LootItem: ${player.Name}wanted to move $itemGuid to $targetGuid, but multiple problems were encountered"
|
||||
)
|
||||
}
|
||||
case (Some(obj), _) =>
|
||||
log.error(s"LootItem: item $obj is (probably) not lootable to ${player.Name}")
|
||||
case (None, _) => ()
|
||||
case (_, None) =>
|
||||
log.error(s"LootItem: ${player.Name} can not find where to put $itemGuid")
|
||||
}
|
||||
ops.handleLootItem(pkt)
|
||||
}
|
||||
|
||||
def handleAvatarImplant(pkt: AvatarImplantMessage): Unit = {
|
||||
val AvatarImplantMessage(_, action, slot, status) = pkt
|
||||
if (action == ImplantAction.Activation) {
|
||||
if (sessionLogic.zoning.zoningStatus == Zoning.Status.Deconstructing) {
|
||||
//do not activate; play deactivation sound instead
|
||||
sessionLogic.zoning.spawn.stopDeconstructing()
|
||||
avatar.implants(slot).collect {
|
||||
case implant if implant.active =>
|
||||
avatarActor ! AvatarActor.DeactivateImplant(implant.definition.implantType)
|
||||
case implant =>
|
||||
sendResponse(PlanetsideAttributeMessage(player.GUID, 28, implant.definition.implantType.value * 2))
|
||||
}
|
||||
} else {
|
||||
ops.handleAvatarImplant(pkt) match {
|
||||
case GeneralOperations.ImplantActivationBehavior.Activate | GeneralOperations.ImplantActivationBehavior.Deactivate =>
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_implant")
|
||||
avatar.implants(slot) match {
|
||||
case Some(implant) =>
|
||||
if (status == 1) {
|
||||
avatarActor ! AvatarActor.ActivateImplant(implant.definition.implantType)
|
||||
} else {
|
||||
avatarActor ! AvatarActor.DeactivateImplant(implant.definition.implantType)
|
||||
}
|
||||
case _ =>
|
||||
log.error(s"AvatarImplantMessage: ${player.Name} has an unknown implant in $slot")
|
||||
}
|
||||
}
|
||||
case GeneralOperations.ImplantActivationBehavior.NotFound =>
|
||||
log.error(s"AvatarImplantMessage: ${player.Name} has an unknown implant in ${pkt.implantSlot}")
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -448,47 +335,47 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
|
|||
}
|
||||
sessionLogic.validObject(pkt.object_guid, decorator = "UseItem") match {
|
||||
case Some(door: Door) =>
|
||||
handleUseDoor(door, equipment)
|
||||
ops.handleUseDoor(door, equipment)
|
||||
case Some(resourceSilo: ResourceSilo) =>
|
||||
handleUseResourceSilo(resourceSilo, equipment)
|
||||
ops.handleUseResourceSilo(resourceSilo, equipment)
|
||||
case Some(panel: IFFLock) =>
|
||||
handleUseGeneralEntity(panel, equipment)
|
||||
ops.handleUseGeneralEntity(panel, equipment)
|
||||
case Some(obj: Player) =>
|
||||
handleUsePlayer(obj, equipment, pkt)
|
||||
ops.handleUsePlayer(obj, equipment, pkt)
|
||||
case Some(locker: Locker) =>
|
||||
handleUseLocker(locker, equipment, pkt)
|
||||
ops.handleUseLocker(locker, equipment, pkt)
|
||||
case Some(gen: Generator) =>
|
||||
handleUseGeneralEntity(gen, equipment)
|
||||
ops.handleUseGeneralEntity(gen, equipment)
|
||||
case Some(mech: ImplantTerminalMech) =>
|
||||
handleUseGeneralEntity(mech, equipment)
|
||||
ops.handleUseGeneralEntity(mech, equipment)
|
||||
case Some(captureTerminal: CaptureTerminal) =>
|
||||
handleUseCaptureTerminal(captureTerminal, equipment)
|
||||
ops.handleUseCaptureTerminal(captureTerminal, equipment)
|
||||
case Some(obj: FacilityTurret) =>
|
||||
handleUseFacilityTurret(obj, equipment, pkt)
|
||||
ops.handleUseFacilityTurret(obj, equipment, pkt)
|
||||
case Some(obj: Vehicle) =>
|
||||
handleUseVehicle(obj, equipment, pkt)
|
||||
ops.handleUseVehicle(obj, equipment, pkt)
|
||||
case Some(terminal: Terminal) =>
|
||||
handleUseTerminal(terminal, equipment, pkt)
|
||||
ops.handleUseTerminal(terminal, equipment, pkt)
|
||||
case Some(obj: SpawnTube) =>
|
||||
handleUseSpawnTube(obj, equipment)
|
||||
ops.handleUseSpawnTube(obj, equipment)
|
||||
case Some(obj: SensorDeployable) =>
|
||||
handleUseGeneralEntity(obj, equipment)
|
||||
ops.handleUseGeneralEntity(obj, equipment)
|
||||
case Some(obj: TurretDeployable) =>
|
||||
handleUseGeneralEntity(obj, equipment)
|
||||
ops.handleUseGeneralEntity(obj, equipment)
|
||||
case Some(obj: TrapDeployable) =>
|
||||
handleUseGeneralEntity(obj, equipment)
|
||||
ops.handleUseGeneralEntity(obj, equipment)
|
||||
case Some(obj: ShieldGeneratorDeployable) =>
|
||||
handleUseGeneralEntity(obj, equipment)
|
||||
ops.handleUseGeneralEntity(obj, equipment)
|
||||
case Some(obj: TelepadDeployable) =>
|
||||
handleUseTelepadDeployable(obj, equipment, pkt)
|
||||
ops.handleUseTelepadDeployable(obj, equipment, pkt, ops.useRouterTelepadSystem)
|
||||
case Some(obj: Utility.InternalTelepad) =>
|
||||
handleUseInternalTelepad(obj, pkt)
|
||||
ops.handleUseInternalTelepad(obj, pkt, ops.useRouterTelepadSystem)
|
||||
case Some(obj: CaptureFlag) =>
|
||||
handleUseCaptureFlag(obj)
|
||||
ops.handleUseCaptureFlag(obj)
|
||||
case Some(_: WarpGate) =>
|
||||
handleUseWarpGate(equipment)
|
||||
ops.handleUseWarpGate(equipment)
|
||||
case Some(obj) =>
|
||||
handleUseDefaultEntity(obj, equipment)
|
||||
ops.handleUseDefaultEntity(obj, equipment)
|
||||
case None => ()
|
||||
}
|
||||
}
|
||||
|
|
@ -519,19 +406,7 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
|
|||
}
|
||||
log.info(s"${player.Name} is constructing a $ammoType deployable")
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
|
||||
val dObj: Deployable = Deployables.Make(ammoType)()
|
||||
dObj.Position = pos
|
||||
dObj.Orientation = orient
|
||||
dObj.WhichSide = player.WhichSide
|
||||
dObj.Faction = player.Faction
|
||||
dObj.AssignOwnership(player)
|
||||
val tasking: TaskBundle = dObj match {
|
||||
case turret: TurretDeployable =>
|
||||
GUIDTask.registerDeployableTurret(continent.GUID, turret)
|
||||
case _ =>
|
||||
GUIDTask.registerObject(continent.GUID, dObj)
|
||||
}
|
||||
TaskWorkflow.execute(CallBackForTask(tasking, continent.Deployables, Zone.Deployable.BuildByOwner(dObj, player, obj), context.self))
|
||||
ops.handleDeployObject(continent, ammoType, pos, orient, player.WhichSide, player.Faction, player, obj)
|
||||
case Some(obj) =>
|
||||
log.warn(s"DeployObject: what is $obj, ${player.Name}? It's not a construction tool!")
|
||||
case None =>
|
||||
|
|
@ -568,7 +443,7 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
|
|||
) {
|
||||
//maelstrom primary fire mode discharge (no target)
|
||||
//aphelion_laser discharge (no target)
|
||||
sessionLogic.shooting.HandleWeaponFireAccountability(objectGuid, PlanetSideGUID(Projectile.baseUID))
|
||||
sessionLogic.shooting.handleWeaponFireAccountability(objectGuid, PlanetSideGUID(Projectile.baseUID))
|
||||
} else {
|
||||
sessionLogic.validObject(player.VehicleSeated, decorator = "GenericObjectAction/Vehicle") collect {
|
||||
case vehicle: Vehicle
|
||||
|
|
@ -748,7 +623,8 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
|
|||
}
|
||||
val curr = System.currentTimeMillis()
|
||||
(target1, t, target2) match {
|
||||
case (None, _, _) => ()
|
||||
case (None, _, _) =>
|
||||
()
|
||||
|
||||
case (Some(us: PlanetSideServerObject with Vitality with FactionAffinity), PlanetSideGUID(0), _) =>
|
||||
if (updateCollisionHistoryForTarget(us, curr)) {
|
||||
|
|
@ -820,10 +696,8 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
|
|||
.foreach {
|
||||
case obj: Vitality if obj.Destroyed => () //some entities will try to charge even if destroyed
|
||||
case obj: Vehicle if obj.MountedIn.nonEmpty => () //cargo vehicles need to be excluded
|
||||
case obj: Vehicle =>
|
||||
commonFacilityShieldCharging(obj)
|
||||
case obj: TurretDeployable =>
|
||||
commonFacilityShieldCharging(obj)
|
||||
case obj: Vehicle => ops.commonFacilityShieldCharging(obj)
|
||||
case obj: TurretDeployable => ops.commonFacilityShieldCharging(obj)
|
||||
case _ if vehicleGuid.nonEmpty =>
|
||||
log.warn(
|
||||
s"FacilityBenefitShieldChargeRequest: ${player.Name} can not find chargeable entity ${vehicleGuid.get.guid} in ${continent.id}"
|
||||
|
|
@ -845,13 +719,7 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
|
|||
}
|
||||
|
||||
def handleCreateShortcut(pkt: CreateShortcutMessage): Unit = {
|
||||
val CreateShortcutMessage(_, slot, shortcutOpt) = pkt
|
||||
shortcutOpt match {
|
||||
case Some(shortcut) =>
|
||||
avatarActor ! AvatarActor.AddShortcut(slot - 1, shortcut)
|
||||
case None =>
|
||||
avatarActor ! AvatarActor.RemoveShortcut(slot - 1)
|
||||
}
|
||||
ops.handleCreateShortcut(pkt)
|
||||
}
|
||||
|
||||
def handleChangeShortcutBank(pkt: ChangeShortcutBankMessage): Unit = {
|
||||
|
|
@ -901,39 +769,11 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
|
|||
}
|
||||
|
||||
def handleObjectDetected(pkt: ObjectDetectedMessage): Unit = {
|
||||
val ObjectDetectedMessage(_, _, _, targets) = pkt
|
||||
sessionLogic.shooting.FindWeapon.foreach {
|
||||
case weapon if weapon.Projectile.AutoLock =>
|
||||
//projectile with auto-lock instigates a warning on the target
|
||||
val detectedTargets = sessionLogic.shooting.FindDetectedProjectileTargets(targets)
|
||||
val mode = 7 + (if (weapon.Projectile == GlobalDefinitions.wasp_rocket_projectile) 1 else 0)
|
||||
detectedTargets.foreach { target =>
|
||||
continent.AvatarEvents ! AvatarServiceMessage(target, AvatarAction.ProjectileAutoLockAwareness(mode))
|
||||
}
|
||||
case _ => ()
|
||||
}
|
||||
ops.handleObjectDetected(pkt)
|
||||
}
|
||||
|
||||
def handleTargetingImplantRequest(pkt: TargetingImplantRequest): Unit = {
|
||||
val TargetingImplantRequest(list) = pkt
|
||||
val targetInfo: List[TargetInfo] = list.flatMap { x =>
|
||||
continent.GUID(x.target_guid) match {
|
||||
case Some(player: Player) =>
|
||||
val health = player.Health.toFloat / player.MaxHealth
|
||||
val armor = if (player.MaxArmor > 0) {
|
||||
player.Armor.toFloat / player.MaxArmor
|
||||
} else {
|
||||
0
|
||||
}
|
||||
Some(TargetInfo(player.GUID, health, armor))
|
||||
case _ =>
|
||||
log.warn(
|
||||
s"TargetingImplantRequest: the info that ${player.Name} requested for target ${x.target_guid} is not for a player"
|
||||
)
|
||||
None
|
||||
}
|
||||
}
|
||||
sendResponse(TargetingInfoMessage(targetInfo))
|
||||
ops.handleTargetingImplantRequest(pkt)
|
||||
}
|
||||
|
||||
def handleHitHint(pkt: HitHint): Unit = {
|
||||
|
|
@ -988,13 +828,10 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
|
|||
session = session.copy(flying = flying)
|
||||
}
|
||||
|
||||
def handleSetSpectator(spectator: Boolean): Unit = {
|
||||
session.player.spectator = spectator
|
||||
}
|
||||
def handleSetSpectator(spectator: Boolean): Unit = { /* normal players can not flag spectate */ }
|
||||
|
||||
def handleKick(player: Player, time: Option[Long]): Unit = {
|
||||
ops.administrativeKick(player)
|
||||
sessionLogic.accountPersistence ! AccountPersistenceService.Kick(player.Name, time)
|
||||
ops.administrativeKick(player, time)
|
||||
}
|
||||
|
||||
def handleSilenced(isSilenced: Boolean): Unit = {
|
||||
|
|
@ -1015,413 +852,6 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
|
|||
|
||||
/* supporting functions */
|
||||
|
||||
private def handleUseDoor(door: Door, equipment: Option[Equipment]): Unit = {
|
||||
equipment match {
|
||||
case Some(tool: Tool) if tool.Definition == GlobalDefinitions.medicalapplicator =>
|
||||
val distance: Float = math.max(
|
||||
Config.app.game.doorsCanBeOpenedByMedAppFromThisDistance,
|
||||
door.Definition.initialOpeningDistance
|
||||
)
|
||||
door.Actor ! CommonMessages.Use(player, Some(distance))
|
||||
case _ =>
|
||||
door.Actor ! CommonMessages.Use(player)
|
||||
}
|
||||
}
|
||||
|
||||
private def handleUseResourceSilo(resourceSilo: ResourceSilo, equipment: Option[Equipment]): Unit = {
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
|
||||
val vehicleOpt = continent.GUID(player.avatar.vehicle)
|
||||
(vehicleOpt, equipment) match {
|
||||
case (Some(vehicle: Vehicle), Some(item))
|
||||
if GlobalDefinitions.isBattleFrameVehicle(vehicle.Definition) &&
|
||||
GlobalDefinitions.isBattleFrameNTUSiphon(item.Definition) =>
|
||||
resourceSilo.Actor ! CommonMessages.Use(player, Some(vehicle))
|
||||
case (Some(vehicle: Vehicle), _)
|
||||
if vehicle.Definition == GlobalDefinitions.ant &&
|
||||
vehicle.DeploymentState == DriveState.Deployed &&
|
||||
Vector3.DistanceSquared(resourceSilo.Position.xy, vehicle.Position.xy) < math.pow(resourceSilo.Definition.UseRadius, 2) =>
|
||||
resourceSilo.Actor ! CommonMessages.Use(player, Some(vehicle))
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
|
||||
private def handleUsePlayer(obj: Player, equipment: Option[Equipment], msg: UseItemMessage): Unit = {
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
|
||||
if (obj.isBackpack) {
|
||||
if (equipment.isEmpty) {
|
||||
log.info(s"${player.Name} is looting the corpse of ${obj.Name}")
|
||||
sendResponse(msg)
|
||||
ops.accessContainer(obj)
|
||||
}
|
||||
} else if (!msg.unk3 && player.isAlive) { //potential kit use
|
||||
(continent.GUID(msg.item_used_guid), ops.kitToBeUsed) match {
|
||||
case (Some(kit: Kit), None) =>
|
||||
ops.kitToBeUsed = Some(msg.item_used_guid)
|
||||
player.Actor ! CommonMessages.Use(player, Some(kit))
|
||||
case (Some(_: Kit), Some(_)) | (None, Some(_)) =>
|
||||
//a kit is already queued to be used; ignore this request
|
||||
sendResponse(ChatMsg(ChatMessageType.UNK_225, wideContents=false, "", "Please wait ...", None))
|
||||
case (Some(item), _) =>
|
||||
log.error(s"UseItem: ${player.Name} looking for Kit to use, but found $item instead")
|
||||
case (None, None) =>
|
||||
log.warn(s"UseItem: anticipated a Kit ${msg.item_used_guid} for ${player.Name}, but can't find it") }
|
||||
} else if (msg.object_id == ObjectClass.avatar && msg.unk3) {
|
||||
equipment match {
|
||||
case Some(tool: Tool) if tool.Definition == GlobalDefinitions.bank =>
|
||||
obj.Actor ! CommonMessages.Use(player, equipment)
|
||||
|
||||
case Some(tool: Tool) if tool.Definition == GlobalDefinitions.medicalapplicator =>
|
||||
obj.Actor ! CommonMessages.Use(player, equipment)
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def handleUseLocker(locker: Locker, equipment: Option[Equipment], msg: UseItemMessage): Unit = {
|
||||
equipment match {
|
||||
case Some(item) =>
|
||||
sendUseGeneralEntityMessage(locker, item)
|
||||
case None if locker.Faction == player.Faction || locker.HackedBy.nonEmpty =>
|
||||
log.info(s"${player.Name} is accessing a locker")
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
|
||||
val playerLocker = player.avatar.locker
|
||||
sendResponse(msg.copy(object_guid = playerLocker.GUID, object_id = 456))
|
||||
ops.accessContainer(playerLocker)
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
|
||||
private def handleUseCaptureTerminal(captureTerminal: CaptureTerminal, equipment: Option[Equipment]): Unit = {
|
||||
equipment match {
|
||||
case Some(item) =>
|
||||
sendUseGeneralEntityMessage(captureTerminal, item)
|
||||
case _ if ops.specialItemSlotGuid.nonEmpty =>
|
||||
continent.GUID(ops.specialItemSlotGuid) match {
|
||||
case Some(llu: CaptureFlag) =>
|
||||
if (llu.Target.GUID == captureTerminal.Owner.GUID) {
|
||||
continent.LocalEvents ! LocalServiceMessage(continent.id, LocalAction.LluCaptured(llu))
|
||||
} else {
|
||||
log.info(
|
||||
s"LLU target is not this base. Target GUID: ${llu.Target.GUID} This base: ${captureTerminal.Owner.GUID}"
|
||||
)
|
||||
}
|
||||
case _ => log.warn("Item in specialItemSlotGuid is not registered with continent or is not a LLU")
|
||||
}
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
|
||||
private def handleUseFacilityTurret(obj: FacilityTurret, equipment: Option[Equipment], msg: UseItemMessage): Unit = {
|
||||
equipment.foreach { item =>
|
||||
sendUseGeneralEntityMessage(obj, item)
|
||||
obj.Actor ! CommonMessages.Use(player, Some((item, msg.unk2.toInt))) //try upgrade path
|
||||
}
|
||||
}
|
||||
|
||||
private def handleUseVehicle(obj: Vehicle, equipment: Option[Equipment], msg: UseItemMessage): Unit = {
|
||||
equipment match {
|
||||
case Some(item) =>
|
||||
sendUseGeneralEntityMessage(obj, item)
|
||||
case None if player.Faction == obj.Faction =>
|
||||
//access to trunk
|
||||
if (
|
||||
obj.AccessingTrunk.isEmpty &&
|
||||
(!obj.PermissionGroup(AccessPermissionGroup.Trunk.id).contains(VehicleLockState.Locked) || obj.OwnerGuid
|
||||
.contains(player.GUID))
|
||||
) {
|
||||
log.info(s"${player.Name} is looking in the ${obj.Definition.Name}'s trunk")
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
|
||||
obj.AccessingTrunk = player.GUID
|
||||
ops.accessContainer(obj)
|
||||
sendResponse(msg)
|
||||
}
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
|
||||
private def handleUseTerminal(terminal: Terminal, equipment: Option[Equipment], msg: UseItemMessage): Unit = {
|
||||
equipment match {
|
||||
case Some(item) =>
|
||||
sendUseGeneralEntityMessage(terminal, item)
|
||||
case None
|
||||
if terminal.Owner == Building.NoBuilding || terminal.Faction == player.Faction ||
|
||||
terminal.HackedBy.nonEmpty || terminal.Faction == PlanetSideEmpire.NEUTRAL =>
|
||||
val tdef = terminal.Definition
|
||||
if (tdef.isInstanceOf[MatrixTerminalDefinition]) {
|
||||
//TODO matrix spawn point; for now, just blindly bind to show work (and hope nothing breaks)
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
|
||||
sendResponse(
|
||||
BindPlayerMessage(BindStatus.Bind, "", display_icon=true, logging=true, SpawnGroup.Sanctuary, 0, 0, terminal.Position)
|
||||
)
|
||||
} else if (
|
||||
tdef == GlobalDefinitions.multivehicle_rearm_terminal || tdef == GlobalDefinitions.bfr_rearm_terminal ||
|
||||
tdef == GlobalDefinitions.air_rearm_terminal || tdef == GlobalDefinitions.ground_rearm_terminal
|
||||
) {
|
||||
findLocalVehicle match {
|
||||
case Some(vehicle) =>
|
||||
log.info(
|
||||
s"${player.Name} is accessing a ${terminal.Definition.Name} for ${player.Sex.possessive} ${vehicle.Definition.Name}"
|
||||
)
|
||||
sendResponse(msg)
|
||||
sendResponse(msg.copy(object_guid = vehicle.GUID, object_id = vehicle.Definition.ObjectId))
|
||||
case None =>
|
||||
log.error(s"UseItem: Expecting a seated vehicle, ${player.Name} found none")
|
||||
}
|
||||
} else if (tdef == GlobalDefinitions.teleportpad_terminal) {
|
||||
//explicit request
|
||||
log.info(s"${player.Name} is purchasing a router telepad")
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
|
||||
terminal.Actor ! Terminal.Request(
|
||||
player,
|
||||
ItemTransactionMessage(msg.object_guid, TransactionType.Buy, 0, "router_telepad", 0, PlanetSideGUID(0))
|
||||
)
|
||||
} else if (tdef == GlobalDefinitions.targeting_laser_dispenser) {
|
||||
//explicit request
|
||||
log.info(s"${player.Name} is purchasing a targeting laser")
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
|
||||
terminal.Actor ! Terminal.Request(
|
||||
player,
|
||||
ItemTransactionMessage(msg.object_guid, TransactionType.Buy, 0, "flail_targeting_laser", 0, PlanetSideGUID(0))
|
||||
)
|
||||
} else {
|
||||
log.info(s"${player.Name} is accessing a ${terminal.Definition.Name}")
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
|
||||
sendResponse(msg)
|
||||
}
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
|
||||
private def handleUseSpawnTube(obj: SpawnTube, equipment: Option[Equipment]): Unit = {
|
||||
equipment match {
|
||||
case Some(item) =>
|
||||
sendUseGeneralEntityMessage(obj, item)
|
||||
case None if player.Faction == obj.Faction =>
|
||||
//deconstruction
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
|
||||
sessionLogic.actionsToCancel()
|
||||
sessionLogic.terminals.CancelAllProximityUnits()
|
||||
sessionLogic.zoning.spawn.startDeconstructing(obj)
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
|
||||
private def handleUseTelepadDeployable(obj: TelepadDeployable, equipment: Option[Equipment], msg: UseItemMessage): Unit = {
|
||||
if (equipment.isEmpty) {
|
||||
(continent.GUID(obj.Router) match {
|
||||
case Some(vehicle: Vehicle) => Some((vehicle, vehicle.Utility(UtilityType.internal_router_telepad_deployable)))
|
||||
case Some(vehicle) => Some(vehicle, None)
|
||||
case None => None
|
||||
}) match {
|
||||
case Some((vehicle: Vehicle, Some(util: Utility.InternalTelepad))) =>
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel")
|
||||
player.WhichSide = vehicle.WhichSide
|
||||
useRouterTelepadSystem(
|
||||
router = vehicle,
|
||||
internalTelepad = util,
|
||||
remoteTelepad = obj,
|
||||
src = obj,
|
||||
dest = util
|
||||
)
|
||||
case Some((vehicle: Vehicle, None)) =>
|
||||
log.error(
|
||||
s"telepad@${msg.object_guid.guid} is not linked to a router - ${vehicle.Definition.Name}"
|
||||
)
|
||||
case Some((o, _)) =>
|
||||
log.error(
|
||||
s"telepad@${msg.object_guid.guid} is linked to wrong kind of object - ${o.Definition.Name}, ${obj.Router}"
|
||||
)
|
||||
obj.Actor ! Deployable.Deconstruct()
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def handleUseInternalTelepad(obj: InternalTelepad, msg: UseItemMessage): Unit = {
|
||||
continent.GUID(obj.Telepad) match {
|
||||
case Some(pad: TelepadDeployable) =>
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel")
|
||||
player.WhichSide = pad.WhichSide
|
||||
useRouterTelepadSystem(
|
||||
router = obj.Owner.asInstanceOf[Vehicle],
|
||||
internalTelepad = obj,
|
||||
remoteTelepad = pad,
|
||||
src = obj,
|
||||
dest = pad
|
||||
)
|
||||
case Some(o) =>
|
||||
log.error(
|
||||
s"internal telepad@${msg.object_guid.guid} is not linked to a remote telepad - ${o.Definition.Name}@${o.GUID.guid}"
|
||||
)
|
||||
case None => ()
|
||||
}
|
||||
}
|
||||
|
||||
private def handleUseCaptureFlag(obj: CaptureFlag): Unit = {
|
||||
// LLU can normally only be picked up the faction that owns it
|
||||
ops.specialItemSlotGuid match {
|
||||
case None if obj.Faction == player.Faction =>
|
||||
ops.specialItemSlotGuid = Some(obj.GUID)
|
||||
player.Carrying = SpecialCarry.CaptureFlag
|
||||
continent.LocalEvents ! CaptureFlagManager.PickupFlag(obj, player)
|
||||
case None =>
|
||||
log.warn(s"${player.Faction} player ${player.toString} tried to pick up a ${obj.Faction} LLU - ${obj.GUID}")
|
||||
case Some(guid) if guid != obj.GUID =>
|
||||
// Ignore duplicate pickup requests
|
||||
log.warn(
|
||||
s"${player.Faction} player ${player.toString} tried to pick up a ${obj.Faction} LLU, but their special slot already contains $guid"
|
||||
)
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
|
||||
private def handleUseWarpGate(equipment: Option[Equipment]): Unit = {
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
|
||||
(continent.GUID(player.VehicleSeated), equipment) match {
|
||||
case (Some(vehicle: Vehicle), Some(item))
|
||||
if GlobalDefinitions.isBattleFrameVehicle(vehicle.Definition) &&
|
||||
GlobalDefinitions.isBattleFrameNTUSiphon(item.Definition) =>
|
||||
vehicle.Actor ! CommonMessages.Use(player, equipment)
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
|
||||
private def handleUseGeneralEntity(obj: PlanetSideServerObject, equipment: Option[Equipment]): Unit = {
|
||||
equipment.foreach { item =>
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
|
||||
obj.Actor ! CommonMessages.Use(player, Some(item))
|
||||
}
|
||||
}
|
||||
|
||||
private def sendUseGeneralEntityMessage(obj: PlanetSideServerObject, equipment: Equipment): Unit = {
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
|
||||
obj.Actor ! CommonMessages.Use(player, Some(equipment))
|
||||
}
|
||||
|
||||
private def handleUseDefaultEntity(obj: PlanetSideGameObject, equipment: Option[Equipment]): Unit = {
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
|
||||
equipment match {
|
||||
case Some(item)
|
||||
if GlobalDefinitions.isBattleFrameArmorSiphon(item.Definition) ||
|
||||
GlobalDefinitions.isBattleFrameNTUSiphon(item.Definition) => ()
|
||||
case _ =>
|
||||
log.warn(s"UseItem: ${player.Name} does not know how to handle $obj")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current `Vehicle` object that the player is riding/driving.
|
||||
* The vehicle must be found solely through use of `player.VehicleSeated`.
|
||||
* @return the vehicle
|
||||
*/
|
||||
private def findLocalVehicle: Option[Vehicle] = {
|
||||
continent.GUID(player.VehicleSeated) match {
|
||||
case Some(obj: Vehicle) => Some(obj)
|
||||
case _ => None
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple object searching algorithm that is limited to containers currently known and accessible by the player.
|
||||
* If all relatively local containers are checked and the object is not found,
|
||||
* the player's locker inventory will be checked, and then
|
||||
* the game environment (items on the ground) will be checked too.
|
||||
* If the target object is discovered, it is removed from its current location and is completely destroyed.
|
||||
* @see `RequestDestroyMessage`
|
||||
* @see `Zone.ItemIs.Where`
|
||||
* @param objectGuid the target object's globally unique identifier;
|
||||
* it is not expected that the object will be unregistered, but it is also not gauranteed
|
||||
* @param obj the target object
|
||||
* @return `true`, if the target object was discovered and removed;
|
||||
* `false`, otherwise
|
||||
*/
|
||||
private def findEquipmentToDelete(objectGuid: PlanetSideGUID, obj: Equipment): Boolean = {
|
||||
val findFunc
|
||||
: PlanetSideServerObject with Container => Option[(PlanetSideServerObject with Container, Option[Int])] =
|
||||
ops.findInLocalContainer(objectGuid)
|
||||
|
||||
findFunc(player)
|
||||
.orElse(ops.accessedContainer match {
|
||||
case Some(parent: PlanetSideServerObject) =>
|
||||
findFunc(parent)
|
||||
case _ =>
|
||||
None
|
||||
})
|
||||
.orElse(findLocalVehicle match {
|
||||
case Some(parent: PlanetSideServerObject) =>
|
||||
findFunc(parent)
|
||||
case _ =>
|
||||
None
|
||||
}) match {
|
||||
case Some((parent, Some(_))) =>
|
||||
obj.Position = Vector3.Zero
|
||||
RemoveOldEquipmentFromInventory(parent)(obj)
|
||||
true
|
||||
case _ if player.avatar.locker.Inventory.Remove(objectGuid) =>
|
||||
sendResponse(ObjectDeleteMessage(objectGuid, 0))
|
||||
true
|
||||
case _ if continent.EquipmentOnGround.contains(obj) =>
|
||||
obj.Position = Vector3.Zero
|
||||
continent.Ground ! Zone.Ground.RemoveItem(objectGuid)
|
||||
continent.AvatarEvents ! AvatarServiceMessage.Ground(RemoverActor.ClearSpecific(List(obj), continent))
|
||||
true
|
||||
case _ =>
|
||||
Zone.EquipmentIs.Where(obj, objectGuid, continent) match {
|
||||
case None =>
|
||||
true
|
||||
case Some(Zone.EquipmentIs.Orphaned()) if obj.HasGUID =>
|
||||
TaskWorkflow.execute(GUIDTask.unregisterEquipment(continent.GUID, obj))
|
||||
true
|
||||
case Some(Zone.EquipmentIs.Orphaned()) =>
|
||||
true
|
||||
case _ =>
|
||||
log.warn(s"RequestDestroy: equipment $obj exists, but ${player.Name} can not reach it to dispose of it")
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A player uses a fully-linked Router teleportation system.
|
||||
* @param router the Router vehicle
|
||||
* @param internalTelepad the internal telepad within the Router vehicle
|
||||
* @param remoteTelepad the remote telepad that is currently associated with this Router
|
||||
* @param src the origin of the teleportation (where the player starts)
|
||||
* @param dest the destination of the teleportation (where the player is going)
|
||||
*/
|
||||
private def useRouterTelepadSystem(
|
||||
router: Vehicle,
|
||||
internalTelepad: InternalTelepad,
|
||||
remoteTelepad: TelepadDeployable,
|
||||
src: PlanetSideGameObject with TelepadLike,
|
||||
dest: PlanetSideGameObject with TelepadLike
|
||||
): Unit = {
|
||||
val time = System.currentTimeMillis()
|
||||
if (
|
||||
time - ops.recentTeleportAttempt > 2000L && router.DeploymentState == DriveState.Deployed &&
|
||||
internalTelepad.Active &&
|
||||
remoteTelepad.Active
|
||||
) {
|
||||
val pguid = player.GUID
|
||||
val sguid = src.GUID
|
||||
val dguid = dest.GUID
|
||||
sendResponse(PlayerStateShiftMessage(ShiftState(0, dest.Position, player.Orientation.z)))
|
||||
ops.useRouterTelepadEffect(pguid, sguid, dguid)
|
||||
continent.LocalEvents ! LocalServiceMessage(
|
||||
continent.id,
|
||||
LocalAction.RouterTelepadTransport(pguid, pguid, sguid, dguid)
|
||||
)
|
||||
val vSource = VehicleSource(router)
|
||||
val zoneNumber = continent.Number
|
||||
player.LogActivity(VehicleMountActivity(vSource, PlayerSource(player), zoneNumber))
|
||||
player.Position = dest.Position
|
||||
player.LogActivity(VehicleDismountActivity(vSource, PlayerSource(player), zoneNumber))
|
||||
} else {
|
||||
log.warn(s"UseRouterTelepadSystem: ${player.Name} can not teleport")
|
||||
}
|
||||
ops.recentTeleportAttempt = time
|
||||
}
|
||||
|
||||
private def maxCapacitorTick(jumpThrust: Boolean): Unit = {
|
||||
if (player.ExoSuit == ExoSuitType.MAX) {
|
||||
val activate = (jumpThrust || player.isOverdrived || player.isShielded) && player.Capacitor > 0
|
||||
|
|
@ -1541,11 +971,4 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
|
|||
)
|
||||
)
|
||||
}
|
||||
|
||||
private def commonFacilityShieldCharging(obj: PlanetSideServerObject with BlockMapEntity): Unit = {
|
||||
obj.Actor ! CommonMessages.ChargeShields(
|
||||
15,
|
||||
Some(continent.blockMap.sector(obj).buildingList.maxBy(_.Definition.SOIRadius))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import net.psforever.objects.{BoomerDeployable, ExplosiveDeployable, TelepadDepl
|
|||
import net.psforever.packet.game.{ChatMsg, DeployableObjectsInfoMessage, GenericActionMessage, GenericObjectActionMessage, GenericObjectStateMsg, HackMessage, HackState, HackState1, InventoryStateMessage, ObjectAttachMessage, ObjectCreateMessage, ObjectDeleteMessage, ObjectDetachMessage, OrbitalShuttleTimeMsg, PadAndShuttlePair, PlanetsideAttributeMessage, ProximityTerminalUseMessage, SetEmpireMessage, TriggerEffectMessage, TriggerSoundMessage, TriggeredSound, VehicleStateMessage}
|
||||
import net.psforever.services.Service
|
||||
import net.psforever.services.local.LocalResponse
|
||||
import net.psforever.types.{ChatMessageType, PlanetSideGUID, Vector3}
|
||||
import net.psforever.types.{ChatMessageType, PlanetSideGUID}
|
||||
|
||||
object LocalHandlerLogic {
|
||||
def apply(ops: SessionLocalHandlers): LocalHandlerLogic = {
|
||||
|
|
@ -88,7 +88,7 @@ class LocalHandlerLogic(val ops: SessionLocalHandlers, implicit val context: Act
|
|||
|
||||
case LocalResponse.EliminateDeployable(obj: TurretDeployable, dguid, pos, _) =>
|
||||
obj.Destroyed = true
|
||||
DeconstructDeployable(
|
||||
ops.DeconstructDeployable(
|
||||
obj,
|
||||
dguid,
|
||||
pos,
|
||||
|
|
@ -102,7 +102,7 @@ class LocalHandlerLogic(val ops: SessionLocalHandlers, implicit val context: Act
|
|||
|
||||
case LocalResponse.EliminateDeployable(obj: ExplosiveDeployable, dguid, pos, effect) =>
|
||||
obj.Destroyed = true
|
||||
DeconstructDeployable(obj, dguid, pos, obj.Orientation, effect)
|
||||
ops.DeconstructDeployable(obj, dguid, pos, obj.Orientation, effect)
|
||||
|
||||
case LocalResponse.EliminateDeployable(obj: TelepadDeployable, dguid, _, _) if obj.Active && obj.Destroyed =>
|
||||
//if active, deactivate
|
||||
|
|
@ -117,7 +117,7 @@ class LocalHandlerLogic(val ops: SessionLocalHandlers, implicit val context: Act
|
|||
ops.deactivateTelpadDeployableMessages(dguid)
|
||||
//standard deployable elimination behavior
|
||||
obj.Destroyed = true
|
||||
DeconstructDeployable(obj, dguid, pos, obj.Orientation, deletionType=2)
|
||||
ops.DeconstructDeployable(obj, dguid, pos, obj.Orientation, deletionType=2)
|
||||
|
||||
case LocalResponse.EliminateDeployable(obj: TelepadDeployable, dguid, _, _) if obj.Destroyed =>
|
||||
//standard deployable elimination behavior
|
||||
|
|
@ -126,14 +126,14 @@ class LocalHandlerLogic(val ops: SessionLocalHandlers, implicit val context: Act
|
|||
case LocalResponse.EliminateDeployable(obj: TelepadDeployable, dguid, pos, _) =>
|
||||
//standard deployable elimination behavior
|
||||
obj.Destroyed = true
|
||||
DeconstructDeployable(obj, dguid, pos, obj.Orientation, deletionType=2)
|
||||
ops.DeconstructDeployable(obj, dguid, pos, obj.Orientation, deletionType=2)
|
||||
|
||||
case LocalResponse.EliminateDeployable(obj, dguid, _, _) if obj.Destroyed =>
|
||||
sendResponse(ObjectDeleteMessage(dguid, unk1=0))
|
||||
|
||||
case LocalResponse.EliminateDeployable(obj, dguid, pos, effect) =>
|
||||
obj.Destroyed = true
|
||||
DeconstructDeployable(obj, dguid, pos, obj.Orientation, effect)
|
||||
ops.DeconstructDeployable(obj, dguid, pos, obj.Orientation, effect)
|
||||
|
||||
case LocalResponse.SendHackMessageHackCleared(targetGuid, unk1, unk2) =>
|
||||
sendResponse(HackMessage(HackState1.Unk0, targetGuid, guid, progress=0, unk1.toFloat, HackState.HackCleared, unk2))
|
||||
|
|
@ -245,24 +245,4 @@ class LocalHandlerLogic(val ops: SessionLocalHandlers, implicit val context: Act
|
|||
}
|
||||
|
||||
/* support functions */
|
||||
|
||||
/**
|
||||
* Common behavior for deconstructing deployables in the game environment.
|
||||
* @param obj the deployable
|
||||
* @param guid the globally unique identifier for the deployable
|
||||
* @param pos the previous position of the deployable
|
||||
* @param orient the previous orientation of the deployable
|
||||
* @param deletionType the value passed to `ObjectDeleteMessage` concerning the deconstruction animation
|
||||
*/
|
||||
def DeconstructDeployable(
|
||||
obj: Deployable,
|
||||
guid: PlanetSideGUID,
|
||||
pos: Vector3,
|
||||
orient: Vector3,
|
||||
deletionType: Int
|
||||
): Unit = {
|
||||
sendResponse(TriggerEffectMessage("spawn_object_failed_effect", pos, orient))
|
||||
sendResponse(PlanetsideAttributeMessage(guid, 29, 1)) //make deployable vanish
|
||||
sendResponse(ObjectDeleteMessage(guid, deletionType))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
// Copyright (c) 2024 PSForever
|
||||
package net.psforever.actors.session.normal
|
||||
|
||||
import akka.actor.{ActorContext, typed}
|
||||
import net.psforever.actors.session.AvatarActor
|
||||
import akka.actor.ActorContext
|
||||
import net.psforever.actors.session.support.{MountHandlerFunctions, SessionData, SessionMountHandlers}
|
||||
import net.psforever.actors.zone.ZoneActor
|
||||
import net.psforever.objects.{GlobalDefinitions, PlanetSideGameObject, Player, Vehicle, Vehicles}
|
||||
|
|
@ -14,16 +13,14 @@ import net.psforever.objects.serverobject.mount.Mountable
|
|||
import net.psforever.objects.serverobject.structures.WarpGate
|
||||
import net.psforever.objects.serverobject.terminals.implant.ImplantTerminalMech
|
||||
import net.psforever.objects.serverobject.turret.{FacilityTurret, WeaponTurret}
|
||||
import net.psforever.objects.vehicles.{AccessPermissionGroup, CargoBehavior}
|
||||
import net.psforever.objects.vehicles.AccessPermissionGroup
|
||||
import net.psforever.objects.vital.InGameHistory
|
||||
import net.psforever.packet.game.{ChatMsg, DelayedPathMountMsg, DismountVehicleCargoMsg, DismountVehicleMsg, GenericObjectActionMessage, MountVehicleCargoMsg, MountVehicleMsg, ObjectAttachMessage, ObjectDetachMessage, PlanetsideAttributeMessage, PlayerStasisMessage, PlayerStateShiftMessage, ShiftState}
|
||||
import net.psforever.packet.game.{ChatMsg, DelayedPathMountMsg, DismountVehicleCargoMsg, DismountVehicleMsg, GenericObjectActionMessage, MountVehicleCargoMsg, MountVehicleMsg, ObjectDetachMessage, PlanetsideAttributeMessage, PlayerStasisMessage, PlayerStateShiftMessage, ShiftState}
|
||||
import net.psforever.services.Service
|
||||
import net.psforever.services.local.{LocalAction, LocalServiceMessage}
|
||||
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
|
||||
import net.psforever.types.{BailType, ChatMessageType, DriveState, PlanetSideGUID, Vector3}
|
||||
|
||||
import scala.concurrent.duration._
|
||||
|
||||
object MountHandlerLogic {
|
||||
def apply(ops: SessionMountHandlers): MountHandlerLogic = {
|
||||
new MountHandlerLogic(ops, ops.context)
|
||||
|
|
@ -33,116 +30,22 @@ object MountHandlerLogic {
|
|||
class MountHandlerLogic(val ops: SessionMountHandlers, implicit val context: ActorContext) extends MountHandlerFunctions {
|
||||
def sessionLogic: SessionData = ops.sessionLogic
|
||||
|
||||
private val avatarActor: typed.ActorRef[AvatarActor.Command] = ops.avatarActor
|
||||
|
||||
/* packets */
|
||||
|
||||
def handleMountVehicle(pkt: MountVehicleMsg): Unit = {
|
||||
val MountVehicleMsg(_, mountable_guid, entry_point) = pkt
|
||||
sessionLogic.validObject(mountable_guid, decorator = "MountVehicle").collect {
|
||||
case obj: Mountable =>
|
||||
obj.Actor ! Mountable.TryMount(player, entry_point)
|
||||
case _ =>
|
||||
log.error(s"MountVehicleMsg: object ${mountable_guid.guid} not a mountable thing, ${player.Name}")
|
||||
}
|
||||
ops.handleMountVehicle(pkt)
|
||||
}
|
||||
|
||||
def handleDismountVehicle(pkt: DismountVehicleMsg): Unit = {
|
||||
val DismountVehicleMsg(player_guid, bailType, wasKickedByDriver) = pkt
|
||||
val dError: (String, Player)=> Unit = dismountError(bailType, wasKickedByDriver)
|
||||
//TODO optimize this later
|
||||
//common warning for this section
|
||||
if (player.GUID == player_guid) {
|
||||
//normally disembarking from a mount
|
||||
(sessionLogic.zoning.interstellarFerry.orElse(continent.GUID(player.VehicleSeated)) match {
|
||||
case out @ Some(obj: Vehicle) =>
|
||||
continent.GUID(obj.MountedIn) match {
|
||||
case Some(_: Vehicle) => None //cargo vehicle
|
||||
case _ => out //arrangement "may" be permissible
|
||||
}
|
||||
case out @ Some(_: Mountable) =>
|
||||
out
|
||||
case _ =>
|
||||
dError(s"DismountVehicleMsg: player ${player.Name} not considered seated in a mountable entity", player)
|
||||
None
|
||||
}) match {
|
||||
case Some(obj: Mountable) =>
|
||||
obj.PassengerInSeat(player) match {
|
||||
case Some(seat_num) =>
|
||||
obj.Actor ! Mountable.TryDismount(player, seat_num, bailType)
|
||||
//short-circuit the temporary channel for transferring between zones, the player is no longer doing that
|
||||
sessionLogic.zoning.interstellarFerry = None
|
||||
|
||||
case None =>
|
||||
dError(s"DismountVehicleMsg: can not find where player ${player.Name}_guid is seated in mountable ${player.VehicleSeated}", player)
|
||||
}
|
||||
case _ =>
|
||||
dError(s"DismountVehicleMsg: can not find mountable entity ${player.VehicleSeated}", player)
|
||||
}
|
||||
} else {
|
||||
//kicking someone else out of a mount; need to own that mount/mountable
|
||||
val dWarn: (String, Player)=> Unit = dismountWarning(bailType, wasKickedByDriver)
|
||||
player.avatar.vehicle match {
|
||||
case Some(obj_guid) =>
|
||||
(
|
||||
(
|
||||
sessionLogic.validObject(obj_guid, decorator = "DismountVehicle/Vehicle"),
|
||||
sessionLogic.validObject(player_guid, decorator = "DismountVehicle/Player")
|
||||
) match {
|
||||
case (vehicle @ Some(obj: Vehicle), tplayer) =>
|
||||
if (obj.MountedIn.isEmpty) (vehicle, tplayer) else (None, None)
|
||||
case (mount @ Some(_: Mountable), tplayer) =>
|
||||
(mount, tplayer)
|
||||
case _ =>
|
||||
(None, None)
|
||||
}) match {
|
||||
case (Some(obj: Mountable), Some(tplayer: Player)) =>
|
||||
obj.PassengerInSeat(tplayer) match {
|
||||
case Some(seat_num) =>
|
||||
obj.Actor ! Mountable.TryDismount(tplayer, seat_num, bailType)
|
||||
case None =>
|
||||
dError(s"DismountVehicleMsg: can not find where other player ${tplayer.Name} is seated in mountable $obj_guid", tplayer)
|
||||
}
|
||||
case (None, _) =>
|
||||
dWarn(s"DismountVehicleMsg: ${player.Name} can not find his vehicle", player)
|
||||
case (_, None) =>
|
||||
dWarn(s"DismountVehicleMsg: player $player_guid could not be found to kick, ${player.Name}", player)
|
||||
case _ =>
|
||||
dWarn(s"DismountVehicleMsg: object is either not a Mountable or not a Player", player)
|
||||
}
|
||||
case None =>
|
||||
dWarn(s"DismountVehicleMsg: ${player.Name} does not own a vehicle", player)
|
||||
}
|
||||
}
|
||||
ops.handleDismountVehicle(pkt)
|
||||
}
|
||||
|
||||
def handleMountVehicleCargo(pkt: MountVehicleCargoMsg): Unit = {
|
||||
val MountVehicleCargoMsg(_, cargo_guid, carrier_guid, _) = pkt
|
||||
(continent.GUID(cargo_guid), continent.GUID(carrier_guid)) match {
|
||||
case (Some(cargo: Vehicle), Some(carrier: Vehicle)) =>
|
||||
carrier.CargoHolds.find({ case (_, hold) => !hold.isOccupied }) match {
|
||||
case Some((mountPoint, _)) =>
|
||||
cargo.Actor ! CargoBehavior.StartCargoMounting(carrier_guid, mountPoint)
|
||||
case _ =>
|
||||
log.warn(
|
||||
s"MountVehicleCargoMsg: ${player.Name} trying to load cargo into a ${carrier.Definition.Name} which oes not have a cargo hold"
|
||||
)
|
||||
}
|
||||
case (None, _) | (Some(_), None) =>
|
||||
log.warn(
|
||||
s"MountVehicleCargoMsg: ${player.Name} lost a vehicle while working with cargo - either $carrier_guid or $cargo_guid"
|
||||
)
|
||||
case _ => ()
|
||||
}
|
||||
ops.handleMountVehicleCargo(pkt)
|
||||
}
|
||||
|
||||
def handleDismountVehicleCargo(pkt: DismountVehicleCargoMsg): Unit = {
|
||||
val DismountVehicleCargoMsg(_, cargo_guid, bailed, _, kicked) = pkt
|
||||
continent.GUID(cargo_guid) match {
|
||||
case Some(cargo: Vehicle) =>
|
||||
cargo.Actor ! CargoBehavior.StartCargoDismounting(bailed || kicked)
|
||||
case _ => ()
|
||||
}
|
||||
ops.handleDismountVehicleCargo(pkt)
|
||||
}
|
||||
|
||||
/* response handlers */
|
||||
|
|
@ -156,24 +59,24 @@ class MountHandlerLogic(val ops: SessionMountHandlers, implicit val context: Act
|
|||
def handle(tplayer: Player, reply: Mountable.Exchange): Unit = {
|
||||
reply match {
|
||||
case Mountable.CanMount(obj: ImplantTerminalMech, seatNumber, _) =>
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
|
||||
log.info(s"${player.Name} mounts an implant terminal")
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
|
||||
sessionLogic.terminals.CancelAllProximityUnits()
|
||||
MountingAction(tplayer, obj, seatNumber)
|
||||
sessionLogic.keepAliveFunc = sessionLogic.keepAlivePersistence
|
||||
ops.MountingAction(tplayer, obj, seatNumber)
|
||||
sessionLogic.keepAliveFunc = sessionLogic.keepAlivePersistenceFunc
|
||||
|
||||
case Mountable.CanMount(obj: Vehicle, seatNumber, _)
|
||||
if obj.Definition == GlobalDefinitions.orbital_shuttle =>
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_mount")
|
||||
log.info(s"${player.Name} mounts the orbital shuttle")
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_mount")
|
||||
sessionLogic.terminals.CancelAllProximityUnits()
|
||||
MountingAction(tplayer, obj, seatNumber)
|
||||
sessionLogic.keepAliveFunc = sessionLogic.keepAlivePersistence
|
||||
ops.MountingAction(tplayer, obj, seatNumber)
|
||||
sessionLogic.keepAliveFunc = sessionLogic.keepAlivePersistenceFunc
|
||||
|
||||
case Mountable.CanMount(obj: Vehicle, seatNumber, _)
|
||||
if obj.Definition == GlobalDefinitions.ant =>
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_mount")
|
||||
log.info(s"${player.Name} mounts the driver seat of the ${obj.Definition.Name}")
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_mount")
|
||||
val obj_guid: PlanetSideGUID = obj.GUID
|
||||
sessionLogic.terminals.CancelAllProximityUnits()
|
||||
sendResponse(PlanetsideAttributeMessage(obj_guid, attribute_type=0, obj.Health))
|
||||
|
|
@ -182,12 +85,12 @@ class MountHandlerLogic(val ops: SessionMountHandlers, implicit val context: Act
|
|||
sendResponse(GenericObjectActionMessage(obj_guid, code=11))
|
||||
sessionLogic.general.accessContainer(obj)
|
||||
tplayer.Actor ! ResetAllEnvironmentInteractions
|
||||
MountingAction(tplayer, obj, seatNumber)
|
||||
ops.MountingAction(tplayer, obj, seatNumber)
|
||||
|
||||
case Mountable.CanMount(obj: Vehicle, seatNumber, _)
|
||||
if obj.Definition == GlobalDefinitions.quadstealth =>
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_mount")
|
||||
log.info(s"${player.Name} mounts the driver seat of the ${obj.Definition.Name}")
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_mount")
|
||||
val obj_guid: PlanetSideGUID = obj.GUID
|
||||
sessionLogic.terminals.CancelAllProximityUnits()
|
||||
sendResponse(PlanetsideAttributeMessage(obj_guid, attribute_type=0, obj.Health))
|
||||
|
|
@ -198,12 +101,12 @@ class MountHandlerLogic(val ops: SessionMountHandlers, implicit val context: Act
|
|||
sendResponse(GenericObjectActionMessage(obj_guid, code=11))
|
||||
sessionLogic.general.accessContainer(obj)
|
||||
tplayer.Actor ! ResetAllEnvironmentInteractions
|
||||
MountingAction(tplayer, obj, seatNumber)
|
||||
ops.MountingAction(tplayer, obj, seatNumber)
|
||||
|
||||
case Mountable.CanMount(obj: Vehicle, seatNumber, _)
|
||||
if seatNumber == 0 && obj.Definition.MaxCapacitor > 0 =>
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_mount")
|
||||
log.info(s"${player.Name} mounts the driver seat of the ${obj.Definition.Name}")
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_mount")
|
||||
val obj_guid: PlanetSideGUID = obj.GUID
|
||||
sessionLogic.terminals.CancelAllProximityUnits()
|
||||
sendResponse(PlanetsideAttributeMessage(obj_guid, attribute_type=0, obj.Health))
|
||||
|
|
@ -213,12 +116,12 @@ class MountHandlerLogic(val ops: SessionMountHandlers, implicit val context: Act
|
|||
sessionLogic.general.accessContainer(obj)
|
||||
ops.updateWeaponAtSeatPosition(obj, seatNumber)
|
||||
tplayer.Actor ! ResetAllEnvironmentInteractions
|
||||
MountingAction(tplayer, obj, seatNumber)
|
||||
ops.MountingAction(tplayer, obj, seatNumber)
|
||||
|
||||
case Mountable.CanMount(obj: Vehicle, seatNumber, _)
|
||||
if seatNumber == 0 =>
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_mount")
|
||||
log.info(s"${player.Name} mounts the driver seat of the ${obj.Definition.Name}")
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_mount")
|
||||
val obj_guid: PlanetSideGUID = obj.GUID
|
||||
sessionLogic.terminals.CancelAllProximityUnits()
|
||||
sendResponse(PlanetsideAttributeMessage(obj_guid, attribute_type=0, obj.Health))
|
||||
|
|
@ -227,17 +130,17 @@ class MountHandlerLogic(val ops: SessionMountHandlers, implicit val context: Act
|
|||
sessionLogic.general.accessContainer(obj)
|
||||
ops.updateWeaponAtSeatPosition(obj, seatNumber)
|
||||
tplayer.Actor ! ResetAllEnvironmentInteractions
|
||||
MountingAction(tplayer, obj, seatNumber)
|
||||
ops.MountingAction(tplayer, obj, seatNumber)
|
||||
|
||||
case Mountable.CanMount(obj: Vehicle, seatNumber, _)
|
||||
if obj.Definition.MaxCapacitor > 0 =>
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_mount")
|
||||
log.info(s"${player.Name} mounts ${
|
||||
obj.SeatPermissionGroup(seatNumber) match {
|
||||
case Some(seatType) => s"a $seatType seat (#$seatNumber)"
|
||||
case None => "a seat"
|
||||
}
|
||||
} of the ${obj.Definition.Name}")
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_mount")
|
||||
val obj_guid: PlanetSideGUID = obj.GUID
|
||||
sessionLogic.terminals.CancelAllProximityUnits()
|
||||
sendResponse(PlanetsideAttributeMessage(obj_guid, attribute_type=0, obj.Health))
|
||||
|
|
@ -245,71 +148,71 @@ class MountHandlerLogic(val ops: SessionMountHandlers, implicit val context: Act
|
|||
sendResponse(PlanetsideAttributeMessage(obj_guid, attribute_type=113, obj.Capacitor))
|
||||
sessionLogic.general.accessContainer(obj)
|
||||
ops.updateWeaponAtSeatPosition(obj, seatNumber)
|
||||
sessionLogic.keepAliveFunc = sessionLogic.keepAlivePersistence
|
||||
sessionLogic.keepAliveFunc = sessionLogic.keepAlivePersistenceFunc
|
||||
tplayer.Actor ! ResetAllEnvironmentInteractions
|
||||
MountingAction(tplayer, obj, seatNumber)
|
||||
ops.MountingAction(tplayer, obj, seatNumber)
|
||||
|
||||
case Mountable.CanMount(obj: Vehicle, seatNumber, _) =>
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_mount")
|
||||
log.info(s"${player.Name} mounts the ${
|
||||
obj.SeatPermissionGroup(seatNumber) match {
|
||||
case Some(seatType) => s"a $seatType seat (#$seatNumber)"
|
||||
case None => "a seat"
|
||||
}
|
||||
} of the ${obj.Definition.Name}")
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_mount")
|
||||
val obj_guid: PlanetSideGUID = obj.GUID
|
||||
sessionLogic.terminals.CancelAllProximityUnits()
|
||||
sendResponse(PlanetsideAttributeMessage(obj_guid, attribute_type=0, obj.Health))
|
||||
sendResponse(PlanetsideAttributeMessage(obj_guid, obj.Definition.shieldUiAttribute, obj.Shields))
|
||||
sessionLogic.general.accessContainer(obj)
|
||||
ops.updateWeaponAtSeatPosition(obj, seatNumber)
|
||||
sessionLogic.keepAliveFunc = sessionLogic.keepAlivePersistence
|
||||
sessionLogic.keepAliveFunc = sessionLogic.keepAlivePersistenceFunc
|
||||
tplayer.Actor ! ResetAllEnvironmentInteractions
|
||||
MountingAction(tplayer, obj, seatNumber)
|
||||
ops.MountingAction(tplayer, obj, seatNumber)
|
||||
|
||||
case Mountable.CanMount(obj: FacilityTurret, seatNumber, _)
|
||||
if obj.Definition == GlobalDefinitions.vanu_sentry_turret =>
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_mount")
|
||||
log.info(s"${player.Name} mounts the ${obj.Definition.Name}")
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_mount")
|
||||
obj.Zone.LocalEvents ! LocalServiceMessage(obj.Zone.id, LocalAction.SetEmpire(obj.GUID, player.Faction))
|
||||
sendResponse(PlanetsideAttributeMessage(obj.GUID, attribute_type=0, obj.Health))
|
||||
ops.updateWeaponAtSeatPosition(obj, seatNumber)
|
||||
MountingAction(tplayer, obj, seatNumber)
|
||||
ops.MountingAction(tplayer, obj, seatNumber)
|
||||
|
||||
case Mountable.CanMount(obj: FacilityTurret, seatNumber, _)
|
||||
if !obj.isUpgrading || System.currentTimeMillis() - GenericHackables.getTurretUpgradeTime >= 1500L =>
|
||||
log.info(s"${player.Name} mounts the ${obj.Definition.Name}")
|
||||
obj.setMiddleOfUpgrade(false)
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_mount")
|
||||
log.info(s"${player.Name} mounts the ${obj.Definition.Name}")
|
||||
sendResponse(PlanetsideAttributeMessage(obj.GUID, attribute_type=0, obj.Health))
|
||||
ops.updateWeaponAtSeatPosition(obj, seatNumber)
|
||||
MountingAction(tplayer, obj, seatNumber)
|
||||
ops.MountingAction(tplayer, obj, seatNumber)
|
||||
|
||||
case Mountable.CanMount(obj: FacilityTurret, _, _) =>
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_mount")
|
||||
log.warn(
|
||||
s"MountVehicleMsg: ${tplayer.Name} wants to mount turret ${obj.GUID.guid}, but needs to wait until it finishes updating"
|
||||
)
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_mount")
|
||||
|
||||
case Mountable.CanMount(obj: PlanetSideGameObject with FactionAffinity with WeaponTurret with InGameHistory, seatNumber, _) =>
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_mount")
|
||||
log.info(s"${player.Name} mounts the ${obj.Definition.asInstanceOf[BasicDefinition].Name}")
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_mount")
|
||||
sendResponse(PlanetsideAttributeMessage(obj.GUID, attribute_type=0, obj.Health))
|
||||
ops.updateWeaponAtSeatPosition(obj, seatNumber)
|
||||
MountingAction(tplayer, obj, seatNumber)
|
||||
ops.MountingAction(tplayer, obj, seatNumber)
|
||||
|
||||
case Mountable.CanMount(obj: Mountable, _, _) =>
|
||||
log.warn(s"MountVehicleMsg: $obj is some kind of mountable object but nothing will happen for ${player.Name}")
|
||||
|
||||
case Mountable.CanDismount(obj: ImplantTerminalMech, seatNum, _) =>
|
||||
log.info(s"${tplayer.Name} dismounts the implant terminal")
|
||||
DismountAction(tplayer, obj, seatNum)
|
||||
ops.DismountAction(tplayer, obj, seatNum)
|
||||
|
||||
case Mountable.CanDismount(obj: Vehicle, _, mountPoint)
|
||||
if obj.Definition == GlobalDefinitions.orbital_shuttle && obj.MountedIn.nonEmpty =>
|
||||
log.info(s"${tplayer.Name} dismounts the orbital shuttle into the lobby")
|
||||
//dismount to hart lobby
|
||||
val pguid = player.GUID
|
||||
log.info(s"${tplayer.Name} dismounts the orbital shuttle into the lobby")
|
||||
val sguid = obj.GUID
|
||||
val (pos, zang) = Vehicles.dismountShuttle(obj, mountPoint)
|
||||
tplayer.Position = pos
|
||||
|
|
@ -322,11 +225,11 @@ class MountHandlerLogic(val ops: SessionMountHandlers, implicit val context: Act
|
|||
|
||||
case Mountable.CanDismount(obj: Vehicle, seatNum, _)
|
||||
if obj.Definition == GlobalDefinitions.orbital_shuttle =>
|
||||
log.info(s"${player.Name} is prepped for dropping")
|
||||
//get ready for orbital drop
|
||||
val pguid = player.GUID
|
||||
val events = continent.VehicleEvents
|
||||
log.info(s"${player.Name} is prepped for dropping")
|
||||
DismountAction(tplayer, obj, seatNum)
|
||||
ops.DismountAction(tplayer, obj, seatNum)
|
||||
continent.actor ! ZoneActor.RemoveFromBlockMap(player) //character doesn't need it
|
||||
//DismountAction(...) uses vehicle service, so use that service to coordinate the remainder of the messages
|
||||
events ! VehicleServiceMessage(
|
||||
|
|
@ -354,7 +257,7 @@ class MountHandlerLogic(val ops: SessionMountHandlers, implicit val context: Act
|
|||
if obj.Definition == GlobalDefinitions.droppod =>
|
||||
log.info(s"${tplayer.Name} has landed on ${continent.id}")
|
||||
sessionLogic.general.unaccessContainer(obj)
|
||||
DismountAction(tplayer, obj, seatNum)
|
||||
ops.DismountAction(tplayer, obj, seatNum)
|
||||
obj.Actor ! Vehicle.Deconstruct()
|
||||
|
||||
case Mountable.CanDismount(obj: Vehicle, seatNum, _)
|
||||
|
|
@ -365,12 +268,12 @@ class MountHandlerLogic(val ops: SessionMountHandlers, implicit val context: Act
|
|||
//todo: implement auto landing procedure if the pilot bails but passengers are still present instead of deconstructing the vehicle
|
||||
//todo: continue flight path until aircraft crashes if no passengers present (or no passenger seats), then deconstruct.
|
||||
//todo: kick cargo passengers out. To be added after PR #216 is merged
|
||||
DismountVehicleAction(tplayer, obj, seatNum)
|
||||
ops.DismountVehicleAction(tplayer, obj, seatNum)
|
||||
obj.Actor ! Vehicle.Deconstruct(None) //immediate deconstruction
|
||||
|
||||
case Mountable.CanDismount(obj: Vehicle, seatNum, _)
|
||||
if tplayer.GUID == player.GUID =>
|
||||
DismountVehicleAction(tplayer, obj, seatNum)
|
||||
ops.DismountVehicleAction(tplayer, obj, seatNum)
|
||||
|
||||
case Mountable.CanDismount(obj: Vehicle, seat_num, _) =>
|
||||
continent.VehicleEvents ! VehicleServiceMessage(
|
||||
|
|
@ -380,7 +283,7 @@ class MountHandlerLogic(val ops: SessionMountHandlers, implicit val context: Act
|
|||
|
||||
case Mountable.CanDismount(obj: PlanetSideGameObject with Mountable with FactionAffinity with InGameHistory, seatNum, _) =>
|
||||
log.info(s"${tplayer.Name} dismounts a ${obj.Definition.asInstanceOf[ObjectDefinition].Name}")
|
||||
DismountAction(tplayer, obj, seatNum)
|
||||
ops.DismountAction(tplayer, obj, seatNum)
|
||||
|
||||
case Mountable.CanDismount(obj: Mountable, _, _) =>
|
||||
log.warn(s"DismountVehicleMsg: $obj is some dismountable object but nothing will happen for ${player.Name}")
|
||||
|
|
@ -434,118 +337,4 @@ class MountHandlerLogic(val ops: SessionMountHandlers, implicit val context: Act
|
|||
}
|
||||
|
||||
/* support functions */
|
||||
|
||||
private def dismountWarning(
|
||||
bailAs: BailType.Value,
|
||||
kickedByDriver: Boolean
|
||||
)
|
||||
(
|
||||
note: String,
|
||||
player: Player
|
||||
): Unit = {
|
||||
log.warn(note)
|
||||
player.VehicleSeated = None
|
||||
sendResponse(DismountVehicleMsg(player.GUID, bailAs, kickedByDriver))
|
||||
}
|
||||
|
||||
private def dismountError(
|
||||
bailAs: BailType.Value,
|
||||
kickedByDriver: Boolean
|
||||
)
|
||||
(
|
||||
note: String,
|
||||
player: Player
|
||||
): Unit = {
|
||||
log.error(s"$note; some vehicle might not know that ${player.Name} is no longer sitting in it")
|
||||
player.VehicleSeated = None
|
||||
sendResponse(DismountVehicleMsg(player.GUID, bailAs, kickedByDriver))
|
||||
}
|
||||
|
||||
/**
|
||||
* Common activities/procedure when a player mounts a valid object.
|
||||
* @param tplayer the player
|
||||
* @param obj the mountable object
|
||||
* @param seatNum the mount into which the player is mounting
|
||||
*/
|
||||
private def MountingAction(tplayer: Player, obj: PlanetSideGameObject with FactionAffinity with InGameHistory, seatNum: Int): Unit = {
|
||||
val playerGuid: PlanetSideGUID = tplayer.GUID
|
||||
val objGuid: PlanetSideGUID = obj.GUID
|
||||
sessionLogic.actionsToCancel()
|
||||
avatarActor ! AvatarActor.DeactivateActiveImplants
|
||||
avatarActor ! AvatarActor.SuspendStaminaRegeneration(3.seconds)
|
||||
sendResponse(ObjectAttachMessage(objGuid, playerGuid, seatNum))
|
||||
continent.VehicleEvents ! VehicleServiceMessage(
|
||||
continent.id,
|
||||
VehicleAction.MountVehicle(playerGuid, objGuid, seatNum)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Common activities/procedure when a player dismounts a valid mountable object.
|
||||
* @param tplayer the player
|
||||
* @param obj the mountable object
|
||||
* @param seatNum the mount out of which which the player is disembarking
|
||||
*/
|
||||
private def DismountVehicleAction(tplayer: Player, obj: Vehicle, seatNum: Int): Unit = {
|
||||
//disembarking self
|
||||
log.info(s"${player.Name} dismounts the ${obj.Definition.Name}'s ${
|
||||
obj.SeatPermissionGroup(seatNum) match {
|
||||
case Some(AccessPermissionGroup.Driver) => "driver seat"
|
||||
case Some(seatType) => s"$seatType seat (#$seatNum)"
|
||||
case None => "seat"
|
||||
}
|
||||
}")
|
||||
sessionLogic.vehicles.ConditionalDriverVehicleControl(obj)
|
||||
sessionLogic.general.unaccessContainer(obj)
|
||||
DismountAction(tplayer, obj, seatNum)
|
||||
//until vehicles maintain synchronized momentum without a driver
|
||||
obj match {
|
||||
case v: Vehicle
|
||||
if seatNum == 0 && Vector3.MagnitudeSquared(v.Velocity.getOrElse(Vector3.Zero)) > 0f =>
|
||||
sessionLogic.vehicles.serverVehicleControlVelocity.collect { _ =>
|
||||
sessionLogic.vehicles.ServerVehicleOverrideStop(v)
|
||||
}
|
||||
v.Velocity = Vector3.Zero
|
||||
continent.VehicleEvents ! VehicleServiceMessage(
|
||||
continent.id,
|
||||
VehicleAction.VehicleState(
|
||||
tplayer.GUID,
|
||||
v.GUID,
|
||||
unk1 = 0,
|
||||
v.Position,
|
||||
v.Orientation,
|
||||
vel = None,
|
||||
v.Flying,
|
||||
unk3 = 0,
|
||||
unk4 = 0,
|
||||
wheel_direction = 15,
|
||||
unk5 = false,
|
||||
unk6 = v.Cloaked
|
||||
)
|
||||
)
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Common activities/procedure when a player dismounts a valid mountable object.
|
||||
* @param tplayer the player
|
||||
* @param obj the mountable object
|
||||
* @param seatNum the mount out of which which the player is disembarking
|
||||
*/
|
||||
private def DismountAction(tplayer: Player, obj: PlanetSideGameObject with FactionAffinity with InGameHistory, seatNum: Int): Unit = {
|
||||
val playerGuid: PlanetSideGUID = tplayer.GUID
|
||||
tplayer.ContributionFrom(obj)
|
||||
sessionLogic.keepAliveFunc = sessionLogic.zoning.NormalKeepAlive
|
||||
val bailType = if (tplayer.BailProtection) {
|
||||
BailType.Bailed
|
||||
} else {
|
||||
BailType.Normal
|
||||
}
|
||||
sendResponse(DismountVehicleMsg(playerGuid, bailType, wasKickedByDriver = false))
|
||||
continent.VehicleEvents ! VehicleServiceMessage(
|
||||
continent.id,
|
||||
VehicleAction.DismountVehicle(playerGuid, bailType, unk2 = false)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
// Copyright (c) 2024 PSForever
|
||||
package net.psforever.actors.session.normal
|
||||
|
||||
import net.psforever.actors.session.support.{ChatFunctions, GeneralFunctions, LocalHandlerFunctions, MountHandlerFunctions, SquadHandlerFunctions, TerminalHandlerFunctions, VehicleFunctions, VehicleHandlerFunctions, WeaponAndProjectileFunctions}
|
||||
import net.psforever.actors.session.support.{ModeLogic, PlayerMode, SessionData}
|
||||
import net.psforever.actors.session.support.{ChatFunctions, GalaxyHandlerFunctions, GeneralFunctions, LocalHandlerFunctions, ModeLogic, MountHandlerFunctions, PlayerMode, SessionData, SquadHandlerFunctions, TerminalHandlerFunctions, VehicleFunctions, VehicleHandlerFunctions, WeaponAndProjectileFunctions}
|
||||
|
||||
class NormalModeLogic(data: SessionData) extends ModeLogic {
|
||||
val avatarResponse: AvatarHandlerLogic = AvatarHandlerLogic(data.avatarResponse)
|
||||
val chat: ChatFunctions = ChatLogic(data.chat)
|
||||
val galaxy: GalaxyHandlerLogic = GalaxyHandlerLogic(data.galaxyResponseHandlers)
|
||||
val galaxy: GalaxyHandlerFunctions = GalaxyHandlerLogic(data.galaxyResponseHandlers)
|
||||
val general: GeneralFunctions = GeneralLogic(data.general)
|
||||
val local: LocalHandlerFunctions = LocalHandlerLogic(data.localResponse)
|
||||
val mountResponse: MountHandlerFunctions = MountHandlerLogic(data.mountResponse)
|
||||
|
|
|
|||
|
|
@ -25,8 +25,6 @@ class SquadHandlerLogic(val ops: SessionSquadHandlers, implicit val context: Act
|
|||
|
||||
private val squadService: ActorRef = ops.squadService
|
||||
|
||||
private var waypointCooldown: Long = 0L
|
||||
|
||||
/* packet */
|
||||
|
||||
def handleSquadDefinitionAction(pkt: SquadDefinitionActionMessage): Unit = {
|
||||
|
|
|
|||
|
|
@ -5,14 +5,11 @@ import akka.actor.{ActorContext, typed}
|
|||
import net.psforever.actors.session.AvatarActor
|
||||
import net.psforever.actors.session.support.{SessionData, SessionTerminalHandlers, TerminalHandlerFunctions}
|
||||
import net.psforever.login.WorldSession.{BuyNewEquipmentPutInInventory, SellEquipmentFromInventory}
|
||||
import net.psforever.objects.{GlobalDefinitions, Player, Vehicle}
|
||||
import net.psforever.objects.{Player, Vehicle}
|
||||
import net.psforever.objects.guid.TaskWorkflow
|
||||
import net.psforever.objects.serverobject.pad.VehicleSpawnPad
|
||||
import net.psforever.objects.serverobject.terminals.{ProximityUnit, Terminal}
|
||||
import net.psforever.objects.sourcing.AmenitySource
|
||||
import net.psforever.objects.vital.TerminalUsedActivity
|
||||
import net.psforever.packet.game.{FavoritesAction, FavoritesRequest, ItemTransactionMessage, ItemTransactionResultMessage, ProximityTerminalUseMessage, UnuseItemMessage}
|
||||
import net.psforever.types.{TransactionType, Vector3}
|
||||
import net.psforever.objects.serverobject.terminals.Terminal
|
||||
import net.psforever.packet.game.{FavoritesRequest, ItemTransactionMessage, ItemTransactionResultMessage, ProximityTerminalUseMessage}
|
||||
import net.psforever.types.TransactionType
|
||||
|
||||
object TerminalHandlerLogic {
|
||||
def apply(ops: SessionTerminalHandlers): TerminalHandlerLogic = {
|
||||
|
|
@ -26,46 +23,17 @@ class TerminalHandlerLogic(val ops: SessionTerminalHandlers, implicit val contex
|
|||
private val avatarActor: typed.ActorRef[AvatarActor.Command] = ops.avatarActor
|
||||
|
||||
def handleItemTransaction(pkt: ItemTransactionMessage): Unit = {
|
||||
val ItemTransactionMessage(terminalGuid, transactionType, _, itemName, _, _) = pkt
|
||||
continent.GUID(terminalGuid) match {
|
||||
case Some(term: Terminal) if ops.lastTerminalOrderFulfillment =>
|
||||
val msg: String = if (itemName.nonEmpty) s" of $itemName" else ""
|
||||
log.info(s"${player.Name} is submitting an order - a $transactionType from a ${term.Definition.Name}$msg")
|
||||
ops.lastTerminalOrderFulfillment = false
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
|
||||
term.Actor ! Terminal.Request(player, pkt)
|
||||
case Some(_: Terminal) =>
|
||||
log.warn(s"Please Wait until your previous order has been fulfilled, ${player.Name}")
|
||||
case Some(obj) =>
|
||||
log.error(s"ItemTransaction: ${obj.Definition.Name} is not a terminal, ${player.Name}")
|
||||
case _ =>
|
||||
log.error(s"ItemTransaction: entity with guid=${terminalGuid.guid} does not exist, ${player.Name}")
|
||||
}
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
|
||||
ops.handleItemTransaction(pkt)
|
||||
}
|
||||
|
||||
def handleProximityTerminalUse(pkt: ProximityTerminalUseMessage): Unit = {
|
||||
val ProximityTerminalUseMessage(_, objectGuid, _) = pkt
|
||||
continent.GUID(objectGuid) match {
|
||||
case Some(obj: Terminal with ProximityUnit) =>
|
||||
ops.HandleProximityTerminalUse(obj)
|
||||
case Some(obj) =>
|
||||
log.warn(s"ProximityTerminalUse: ${obj.Definition.Name} guid=${objectGuid.guid} is not ready to implement proximity effects")
|
||||
case None =>
|
||||
log.error(s"ProximityTerminalUse: ${player.Name} can not find an object with guid ${objectGuid.guid}")
|
||||
}
|
||||
ops.handleProximityTerminalUse(pkt)
|
||||
}
|
||||
|
||||
def handleFavoritesRequest(pkt: FavoritesRequest): Unit = {
|
||||
val FavoritesRequest(_, loadoutType, action, line, label) = pkt
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
|
||||
action match {
|
||||
case FavoritesAction.Save =>
|
||||
avatarActor ! AvatarActor.SaveLoadout(player, loadoutType, label, line)
|
||||
case FavoritesAction.Delete =>
|
||||
avatarActor ! AvatarActor.DeleteLoadout(player, loadoutType, line)
|
||||
case FavoritesAction.Unknown =>
|
||||
log.warn(s"FavoritesRequest: ${player.Name} requested an unknown favorites action")
|
||||
}
|
||||
ops.handleFavoritesRequest(pkt)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -117,48 +85,10 @@ class TerminalHandlerLogic(val ops: SessionTerminalHandlers, implicit val contex
|
|||
ops.lastTerminalOrderFulfillment = true
|
||||
|
||||
case Terminal.BuyVehicle(vehicle, weapons, trunk) =>
|
||||
continent.map.terminalToSpawnPad
|
||||
.find { case (termid, _) => termid == msg.terminal_guid.guid }
|
||||
.map { case (a: Int, b: Int) => (continent.GUID(a), continent.GUID(b)) }
|
||||
.collect { case (Some(term: Terminal), Some(pad: VehicleSpawnPad)) =>
|
||||
avatarActor ! AvatarActor.UpdatePurchaseTime(vehicle.Definition)
|
||||
vehicle.Faction = tplayer.Faction
|
||||
vehicle.Position = pad.Position
|
||||
vehicle.Orientation = pad.Orientation + Vector3.z(pad.Definition.VehicleCreationZOrientOffset)
|
||||
//default loadout, weapons
|
||||
val vWeapons = vehicle.Weapons
|
||||
weapons.foreach { entry =>
|
||||
vWeapons.get(entry.start) match {
|
||||
case Some(slot) =>
|
||||
entry.obj.Faction = tplayer.Faction
|
||||
slot.Equipment = None
|
||||
slot.Equipment = entry.obj
|
||||
case None =>
|
||||
log.warn(
|
||||
s"BuyVehicle: ${player.Name} tries to apply default loadout to $vehicle on spawn, but can not find a mounted weapon for ${entry.start}"
|
||||
)
|
||||
}
|
||||
}
|
||||
//default loadout, trunk
|
||||
val vTrunk = vehicle.Trunk
|
||||
vTrunk.Clear()
|
||||
trunk.foreach { entry =>
|
||||
entry.obj.Faction = tplayer.Faction
|
||||
vTrunk.InsertQuickly(entry.start, entry.obj)
|
||||
}
|
||||
TaskWorkflow.execute(ops.registerVehicleFromSpawnPad(vehicle, pad, term))
|
||||
sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, success = true))
|
||||
if (GlobalDefinitions.isBattleFrameVehicle(vehicle.Definition)) {
|
||||
sendResponse(UnuseItemMessage(player.GUID, msg.terminal_guid))
|
||||
}
|
||||
player.LogActivity(TerminalUsedActivity(AmenitySource(term), msg.transaction_type))
|
||||
}
|
||||
.orElse {
|
||||
log.error(
|
||||
s"${tplayer.Name} wanted to spawn a vehicle, but there was no spawn pad associated with terminal ${msg.terminal_guid} to accept it"
|
||||
)
|
||||
sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, success = false))
|
||||
None
|
||||
ops.buyVehicle(msg.terminal_guid, msg.transaction_type, vehicle, weapons, trunk)
|
||||
.collect {
|
||||
case _: Vehicle =>
|
||||
avatarActor ! AvatarActor.UpdatePurchaseTime(vehicle.Definition)
|
||||
}
|
||||
ops.lastTerminalOrderFulfillment = true
|
||||
|
||||
|
|
|
|||
|
|
@ -214,7 +214,8 @@ class VehicleLogic(val ops: VehicleOperations, implicit val context: ActorContex
|
|||
case Some(mount: Mountable) => (o, mount.PassengerInSeat(player))
|
||||
case _ => (None, None)
|
||||
}) match {
|
||||
case (None, None) | (_, None) | (Some(_: Vehicle), Some(0)) => ()
|
||||
case (None, None) | (_, None) | (Some(_: Vehicle), Some(0)) =>
|
||||
()
|
||||
case _ =>
|
||||
sessionLogic.persist()
|
||||
sessionLogic.turnCounterFunc(player.GUID)
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -221,7 +221,7 @@ class AvatarHandlerLogic(val ops: SessionAvatarHandlers, implicit val context: A
|
|||
|
||||
case AvatarResponse.HitHint(sourceGuid) if player.isAlive =>
|
||||
sendResponse(HitHint(sourceGuid, guid))
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_dmg")
|
||||
sessionLogic.zoning.CancelZoningProcess()
|
||||
|
||||
case AvatarResponse.Destroy(victim, killer, weapon, pos) =>
|
||||
// guid = victim // killer = killer
|
||||
|
|
@ -235,10 +235,6 @@ class AvatarHandlerLogic(val ops: SessionAvatarHandlers, implicit val context: A
|
|||
sendResponse(ItemTransactionResultMessage(terminalGuid, action, result))
|
||||
sessionLogic.terminals.lastTerminalOrderFulfillment = true
|
||||
AvatarActor.savePlayerData(player)
|
||||
sessionLogic.general.renewCharSavedTimer(
|
||||
Config.app.game.savedMsg.interruptedByAction.fixed,
|
||||
Config.app.game.savedMsg.interruptedByAction.variable
|
||||
)
|
||||
|
||||
case AvatarResponse.TerminalOrderResult(terminalGuid, action, result) =>
|
||||
sendResponse(ItemTransactionResultMessage(terminalGuid, action, result))
|
||||
|
|
@ -430,7 +426,7 @@ class AvatarHandlerLogic(val ops: SessionAvatarHandlers, implicit val context: A
|
|||
if isNotSameTarget && ops.lastSeenStreamMessage.get(guid.guid).exists { _.visible } =>
|
||||
sendResponse(ReloadMessage(itemGuid, ammo_clip=1, unk1=0))
|
||||
|
||||
case AvatarResponse.Killed(mount) =>
|
||||
case AvatarResponse.Killed(_, mount) =>
|
||||
//log and chat messages
|
||||
val cause = player.LastDamage.flatMap { damage =>
|
||||
val interaction = damage.interaction
|
||||
|
|
@ -451,8 +447,7 @@ class AvatarHandlerLogic(val ops: SessionAvatarHandlers, implicit val context: A
|
|||
)
|
||||
sessionLogic.shooting.shotsWhileDead = 0
|
||||
}
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason(msg = "cancel")
|
||||
sessionLogic.general.renewCharSavedTimer(fixedLen = 1800L, varLen = 0L)
|
||||
sessionLogic.zoning.CancelZoningProcess()
|
||||
|
||||
//player state changes
|
||||
AvatarActor.updateToolDischargeFor(avatar)
|
||||
|
|
@ -515,7 +510,7 @@ class AvatarHandlerLogic(val ops: SessionAvatarHandlers, implicit val context: A
|
|||
|
||||
case AvatarResponse.EnvironmentalDamage(_, _, _) =>
|
||||
//TODO damage marker?
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_dmg")
|
||||
sessionLogic.zoning.CancelZoningProcess()
|
||||
|
||||
case AvatarResponse.DropItem(pkt) if isNotSameTarget =>
|
||||
sendResponse(pkt)
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ import net.psforever.objects.Session
|
|||
import net.psforever.packet.game.{ChatMsg, SetChatFilterMessage}
|
||||
import net.psforever.services.chat.SpectatorChannel
|
||||
import net.psforever.types.ChatMessageType
|
||||
import net.psforever.types.ChatMessageType.{CMT_TOGGLESPECTATORMODE, CMT_TOGGLE_GM}
|
||||
import net.psforever.zones.Zones
|
||||
|
||||
import scala.collection.Seq
|
||||
|
||||
|
|
@ -19,6 +21,8 @@ object ChatLogic {
|
|||
}
|
||||
|
||||
class ChatLogic(val ops: ChatOperations, implicit val context: ActorContext) extends ChatFunctions {
|
||||
ops.transitoryCommandEntered = None
|
||||
|
||||
def sessionLogic: SessionData = ops.sessionLogic
|
||||
|
||||
def handleChatMsg(message: ChatMsg): Unit = {
|
||||
|
|
@ -31,11 +35,10 @@ class ChatLogic(val ops: ChatOperations, implicit val context: ActorContext) ext
|
|||
case (CMT_FLY, recipient, contents) =>
|
||||
ops.commandFly(contents, recipient)
|
||||
|
||||
case (CMT_ANONYMOUS, _, _) =>
|
||||
// ?
|
||||
case (CMT_ANONYMOUS, _, _) => ()
|
||||
|
||||
case (CMT_TOGGLE_GM, _, _) =>
|
||||
// ?
|
||||
case (CMT_TOGGLE_GM, _, _) => ()
|
||||
sessionLogic.zoning.maintainInitialGmState = false
|
||||
|
||||
case (CMT_CULLWATERMARK, _, contents) =>
|
||||
ops.commandWatermark(contents)
|
||||
|
|
@ -56,19 +59,19 @@ class ChatLogic(val ops: ChatOperations, implicit val context: ActorContext) ext
|
|||
commandToggleSpectatorMode(contents = "off")
|
||||
|
||||
case (CMT_OPEN, _, _) =>
|
||||
ops.commandSendToRecipient(session, message, SpectatorChannel)
|
||||
ops.commandSendToRecipient(session, spectatorColoredMessage(message), SpectatorChannel)
|
||||
|
||||
case (CMT_VOICE, _, contents) =>
|
||||
ops.commandVoice(session, message, contents, SpectatorChannel)
|
||||
|
||||
case (CMT_TELL, _, _) =>
|
||||
ops.commandTellOrIgnore(session, message, SpectatorChannel)
|
||||
ops.commandTellOrIgnore(session, spectatorColoredMessage(message), SpectatorChannel)
|
||||
|
||||
case (CMT_BROADCAST, _, _) =>
|
||||
ops.commandSendToRecipient(session, message, SpectatorChannel)
|
||||
ops.commandSendToRecipient(session, spectatorColoredMessage(message), SpectatorChannel)
|
||||
|
||||
case (CMT_PLATOON, _, _) =>
|
||||
ops.commandSendToRecipient(session, message, SpectatorChannel)
|
||||
ops.commandSendToRecipient(session, spectatorColoredMessage(message), SpectatorChannel)
|
||||
|
||||
case (CMT_GMTELL, _, _) =>
|
||||
ops.commandSend(session, message, SpectatorChannel)
|
||||
|
|
@ -79,8 +82,9 @@ class ChatLogic(val ops: ChatOperations, implicit val context: ActorContext) ext
|
|||
case (CMT_WHO | CMT_WHO_CSR | CMT_WHO_CR | CMT_WHO_PLATOONLEADERS | CMT_WHO_SQUADLEADERS | CMT_WHO_TEAMS, _, _) =>
|
||||
ops.commandWho(session)
|
||||
|
||||
case (CMT_ZONE, _, contents) =>
|
||||
ops.commandZone(message, contents)
|
||||
case (CMT_ZONE, _, _) =>
|
||||
commandToggleSpectatorMode(contents = "off")
|
||||
ops.commandZone(message, Zones.sanctuaryZoneId(player.Faction))
|
||||
|
||||
case (CMT_WARP, _, contents) =>
|
||||
ops.commandWarp(session, message, contents)
|
||||
|
|
@ -115,6 +119,16 @@ class ChatLogic(val ops: ChatOperations, implicit val context: ActorContext) ext
|
|||
}
|
||||
}
|
||||
|
||||
private def spectatorColoredMessage(message: ChatMsg): ChatMsg = {
|
||||
if (message.contents.nonEmpty) {
|
||||
val colorlessText = message.contents.replaceAll("//#\\d", "").trim
|
||||
val colorCodedText = s"/#5$colorlessText/#0"
|
||||
message.copy(recipient = s"<spectator:${message.recipient}>", contents = colorCodedText)
|
||||
} else {
|
||||
message
|
||||
}
|
||||
}
|
||||
|
||||
private def customCommandMessages(
|
||||
message: ChatMsg,
|
||||
session: Session
|
||||
|
|
@ -136,11 +150,21 @@ class ChatLogic(val ops: ChatOperations, implicit val context: ActorContext) ext
|
|||
}
|
||||
}
|
||||
|
||||
private def commandToggleSpectatorMode(contents: String): Unit = {
|
||||
contents.toLowerCase() match {
|
||||
case "off" | "of" =>
|
||||
context.self ! SessionActor.SetMode(NormalMode)
|
||||
case _ => ()
|
||||
}
|
||||
private def commandToggleSpectatorMode(contents: String): Boolean = {
|
||||
ops.transitoryCommandEntered
|
||||
.collect {
|
||||
case CMT_TOGGLESPECTATORMODE => true
|
||||
case CMT_TOGGLE_GM => false
|
||||
}
|
||||
.getOrElse {
|
||||
contents.toLowerCase() match {
|
||||
case "off" | "of" =>
|
||||
ops.transitoryCommandEntered = Some(CMT_TOGGLESPECTATORMODE)
|
||||
context.self ! SessionActor.SetMode(NormalMode)
|
||||
true
|
||||
case _ =>
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,85 +0,0 @@
|
|||
// Copyright (c) 2024 PSForever
|
||||
package net.psforever.actors.session.spectator
|
||||
|
||||
import akka.actor.{ActorContext, ActorRef, typed}
|
||||
import net.psforever.actors.session.AvatarActor
|
||||
import net.psforever.actors.session.support.{GalaxyHandlerFunctions, SessionGalaxyHandlers, SessionData}
|
||||
import net.psforever.packet.game.{BroadcastWarpgateUpdateMessage, FriendsResponse, HotSpotUpdateMessage, ZoneInfoMessage, ZonePopulationUpdateMessage, HotSpotInfo => PacketHotSpotInfo}
|
||||
import net.psforever.services.galaxy.{GalaxyAction, GalaxyResponse, GalaxyServiceMessage}
|
||||
import net.psforever.types.{MemberAction, PlanetSideEmpire}
|
||||
|
||||
object GalaxyHandlerLogic {
|
||||
def apply(ops: SessionGalaxyHandlers): GalaxyHandlerLogic = {
|
||||
new GalaxyHandlerLogic(ops, ops.context)
|
||||
}
|
||||
}
|
||||
|
||||
class GalaxyHandlerLogic(val ops: SessionGalaxyHandlers, implicit val context: ActorContext) extends GalaxyHandlerFunctions {
|
||||
def sessionLogic: SessionData = ops.sessionLogic
|
||||
|
||||
private val avatarActor: typed.ActorRef[AvatarActor.Command] = ops.avatarActor
|
||||
|
||||
private val galaxyService: ActorRef = ops.galaxyService
|
||||
|
||||
/* packets */
|
||||
|
||||
def handleUpdateIgnoredPlayers(pkt: FriendsResponse): Unit = {
|
||||
sendResponse(pkt)
|
||||
pkt.friends.foreach { f =>
|
||||
galaxyService ! GalaxyServiceMessage(GalaxyAction.LogStatusChange(f.name))
|
||||
}
|
||||
}
|
||||
|
||||
/* response handlers */
|
||||
|
||||
def handle(reply: GalaxyResponse.Response): Unit = {
|
||||
reply match {
|
||||
case GalaxyResponse.HotSpotUpdate(zone_index, priority, hot_spot_info) =>
|
||||
sendResponse(
|
||||
HotSpotUpdateMessage(
|
||||
zone_index,
|
||||
priority,
|
||||
hot_spot_info.map { spot => PacketHotSpotInfo(spot.DisplayLocation.x, spot.DisplayLocation.y, 40) }
|
||||
)
|
||||
)
|
||||
|
||||
case GalaxyResponse.MapUpdate(msg) =>
|
||||
sendResponse(msg)
|
||||
|
||||
case GalaxyResponse.UpdateBroadcastPrivileges(zoneId, gateMapId, fromFactions, toFactions) =>
|
||||
val faction = player.Faction
|
||||
val from = fromFactions.contains(faction)
|
||||
val to = toFactions.contains(faction)
|
||||
if (from && !to) {
|
||||
sendResponse(BroadcastWarpgateUpdateMessage(zoneId, gateMapId, PlanetSideEmpire.NEUTRAL))
|
||||
} else if (!from && to) {
|
||||
sendResponse(BroadcastWarpgateUpdateMessage(zoneId, gateMapId, faction))
|
||||
}
|
||||
|
||||
case GalaxyResponse.FlagMapUpdate(msg) =>
|
||||
sendResponse(msg)
|
||||
|
||||
case GalaxyResponse.TransferPassenger(temp_channel, vehicle, _, manifest) =>
|
||||
sessionLogic.zoning.handleTransferPassenger(temp_channel, vehicle, manifest)
|
||||
|
||||
case GalaxyResponse.LockedZoneUpdate(zone, time) =>
|
||||
sendResponse(ZoneInfoMessage(zone.Number, empire_status=false, lock_time=time))
|
||||
|
||||
case GalaxyResponse.UnlockedZoneUpdate(zone) =>
|
||||
sendResponse(ZoneInfoMessage(zone.Number, empire_status=true, lock_time=0L))
|
||||
val popBO = 0
|
||||
val popTR = zone.Players.count(_.faction == PlanetSideEmpire.TR)
|
||||
val popNC = zone.Players.count(_.faction == PlanetSideEmpire.NC)
|
||||
val popVS = zone.Players.count(_.faction == PlanetSideEmpire.VS)
|
||||
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)) =>
|
||||
avatarActor ! AvatarActor.MemberListRequest(MemberAction.UpdateFriend, name)
|
||||
|
||||
case GalaxyResponse.SendResponse(msg) =>
|
||||
sendResponse(msg)
|
||||
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,24 +4,19 @@ package net.psforever.actors.session.spectator
|
|||
import akka.actor.{ActorContext, ActorRef, typed}
|
||||
import net.psforever.actors.session.AvatarActor
|
||||
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, Player, TelepadDeployable, Tool, Vehicle}
|
||||
import net.psforever.objects.avatar.{Avatar, Implant}
|
||||
import net.psforever.objects.ballistics.Projectile
|
||||
import net.psforever.objects.ce.{Deployable, TelepadLike}
|
||||
import net.psforever.objects.definition.{BasicDefinition, KitDefinition, SpecialExoSuitDefinition}
|
||||
import net.psforever.objects.equipment.Equipment
|
||||
import net.psforever.objects.serverobject.CommonMessages
|
||||
import net.psforever.objects.serverobject.containable.Containable
|
||||
import net.psforever.objects.serverobject.doors.Door
|
||||
import net.psforever.objects.vehicles.{Utility, UtilityType}
|
||||
import net.psforever.objects.vehicles.Utility.InternalTelepad
|
||||
import net.psforever.objects.vehicles.Utility
|
||||
import net.psforever.objects.zones.ZoneProjectile
|
||||
import net.psforever.packet.PlanetSideGamePacket
|
||||
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.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, RequestDestroyMessage, TargetingImplantRequest, TradeMessage, UnuseItemMessage, UseItemMessage, VoiceHostInfo, VoiceHostRequest, ZipLineMessage}
|
||||
import net.psforever.services.account.AccountPersistenceService
|
||||
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||
import net.psforever.types.{DriveState, ExoSuitType, PlanetSideGUID, Vector3}
|
||||
import net.psforever.util.Config
|
||||
import net.psforever.types.{ExoSuitType, Vector3}
|
||||
|
||||
object GeneralLogic {
|
||||
def apply(ops: GeneralOperations): GeneralLogic = {
|
||||
|
|
@ -52,7 +47,7 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
|
|||
yaw,
|
||||
pitch,
|
||||
yawUpper,
|
||||
_/*seqTime*/,
|
||||
seqTime,
|
||||
_,
|
||||
isCrouching,
|
||||
isJumping,
|
||||
|
|
@ -63,6 +58,7 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
|
|||
)= pkt
|
||||
sessionLogic.persist()
|
||||
sessionLogic.turnCounterFunc(avatarGuid)
|
||||
sessionLogic.updateBlockMap(player, pos)
|
||||
ops.fallHeightTracker(pos.z)
|
||||
// if (isCrouching && !player.Crouching) {
|
||||
// //dev stuff goes here
|
||||
|
|
@ -74,6 +70,24 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
|
|||
player.Crouching = isCrouching
|
||||
player.Jumping = isJumping
|
||||
player.Cloaked = player.ExoSuit == ExoSuitType.Infiltration && isCloaking
|
||||
continent.AvatarEvents ! AvatarServiceMessage(
|
||||
"spectator",
|
||||
AvatarAction.PlayerState(
|
||||
avatarGuid,
|
||||
player.Position,
|
||||
player.Velocity,
|
||||
yaw,
|
||||
pitch,
|
||||
yawUpper,
|
||||
seqTime,
|
||||
isCrouching,
|
||||
isJumping,
|
||||
jump_thrust = false,
|
||||
is_cloaked = isCloaking,
|
||||
spectator = false,
|
||||
weaponInHand = false
|
||||
)
|
||||
)
|
||||
if (player.death_by == -1) {
|
||||
sessionLogic.kickedByAdministration()
|
||||
}
|
||||
|
|
@ -106,31 +120,15 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
|
|||
def handleAvatarJump(pkt: AvatarJumpMessage): Unit = { /* intentionally blank */ }
|
||||
|
||||
def handleZipLine(pkt: ZipLineMessage): Unit = {
|
||||
val ZipLineMessage(playerGuid, forwards, action, pathId, pos) = pkt
|
||||
continent.zipLinePaths.find(x => x.PathId == pathId) match {
|
||||
case Some(path) if path.IsTeleporter =>
|
||||
val endPoint = path.ZipLinePoints.last
|
||||
sendResponse(ZipLineMessage(PlanetSideGUID(0), forwards, 0, pathId, pos))
|
||||
//todo: send to zone to show teleport animation to all clients
|
||||
sendResponse(PlayerStateShiftMessage(ShiftState(0, endPoint, (player.Orientation.z + player.FacingYawUpper) % 360f, None)))
|
||||
case Some(_) =>
|
||||
action match {
|
||||
case 0 =>
|
||||
//travel along the zipline in the direction specified
|
||||
sendResponse(ZipLineMessage(playerGuid, forwards, action, pathId, pos))
|
||||
case 1 =>
|
||||
//disembark from zipline at destination!
|
||||
sendResponse(ZipLineMessage(playerGuid, forwards, action, 0, pos))
|
||||
case 2 =>
|
||||
//get off by force
|
||||
sendResponse(ZipLineMessage(playerGuid, forwards, action, 0, pos))
|
||||
case _ =>
|
||||
log.warn(
|
||||
s"${player.Name} tried to do something with a zipline but can't handle it. forwards: $forwards action: $action pathId: $pathId zone: ${continent.Number} / ${continent.id}"
|
||||
)
|
||||
}
|
||||
case _ =>
|
||||
log.warn(s"${player.Name} couldn't find a zipline path $pathId in zone ${continent.id}")
|
||||
ops.handleZipLine(pkt) match {
|
||||
case GeneralOperations.ZiplineBehavior.Teleporter | GeneralOperations.ZiplineBehavior.Zipline =>
|
||||
sessionLogic.zoning.CancelZoningProcess()
|
||||
case GeneralOperations.ZiplineBehavior.Unsupported =>
|
||||
log.warn(
|
||||
s"${player.Name} tried to do something with a zipline but can't handle it. action: ${pkt.action}, pathId: ${pkt.path_id}, zone: ${continent.id}"
|
||||
)
|
||||
case GeneralOperations.ZiplineBehavior.NotFound =>
|
||||
log.warn(s"${player.Name} couldn't find a zipline path ${pkt.path_id} in zone ${continent.id}")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -168,11 +166,11 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
|
|||
def handleUseItem(pkt: UseItemMessage): Unit = {
|
||||
sessionLogic.validObject(pkt.object_guid, decorator = "UseItem") match {
|
||||
case Some(door: Door) =>
|
||||
handleUseDoor(door, None)
|
||||
ops.handleUseDoor(door, None)
|
||||
case Some(obj: TelepadDeployable) =>
|
||||
handleUseTelepadDeployable(obj, None, pkt)
|
||||
ops.handleUseTelepadDeployable(obj, None, pkt, ops.useRouterTelepadSystemSecretly)
|
||||
case Some(obj: Utility.InternalTelepad) =>
|
||||
handleUseInternalTelepad(obj, pkt)
|
||||
ops.handleUseInternalTelepad(obj, pkt, ops.useRouterTelepadSystemSecretly)
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
|
|
@ -267,15 +265,10 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
|
|||
case GenericAction.AwayFromKeyboard_RCV =>
|
||||
log.info(s"${player.Name} is AFK")
|
||||
AvatarActor.savePlayerLocation(player)
|
||||
ops.displayCharSavedMsgThenRenewTimer(fixedLen=1800L, varLen=0L) //~30min
|
||||
player.AwayFromKeyboard = true
|
||||
case GenericAction.BackInGame_RCV =>
|
||||
log.info(s"${player.Name} is back")
|
||||
player.AwayFromKeyboard = false
|
||||
ops.renewCharSavedTimer(
|
||||
Config.app.game.savedMsg.renewal.fixed,
|
||||
Config.app.game.savedMsg.renewal.variable
|
||||
)
|
||||
case GenericAction.LookingForSquad_RCV => //Looking For Squad ON
|
||||
if (!avatar.lookingForSquad && (sessionLogic.squad.squadUI.isEmpty || sessionLogic.squad.squadUI(player.CharId).index == 0)) {
|
||||
avatarActor ! AvatarActor.SetLookingForSquad(true)
|
||||
|
|
@ -369,34 +362,14 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
|
|||
|
||||
def handleTrade(pkt: TradeMessage): Unit = { /* intentionally blank */ }
|
||||
|
||||
def handleDisplayedAward(pkt: DisplayedAwardMessage): Unit = {
|
||||
val DisplayedAwardMessage(_, ribbon, bar) = pkt
|
||||
log.trace(s"${player.Name} changed the $bar displayed award ribbon to $ribbon")
|
||||
avatarActor ! AvatarActor.SetRibbon(ribbon, bar)
|
||||
def handleDisplayedAward(pkt: DisplayedAwardMessage): Unit = { /* intentionally blank */ }
|
||||
|
||||
def handleObjectDetected(pkt: ObjectDetectedMessage): Unit = {
|
||||
ops.handleObjectDetected(pkt)
|
||||
}
|
||||
|
||||
def handleObjectDetected(pkt: ObjectDetectedMessage): Unit = { /* intentionally blank */ }
|
||||
|
||||
def handleTargetingImplantRequest(pkt: TargetingImplantRequest): Unit = {
|
||||
val TargetingImplantRequest(list) = pkt
|
||||
val targetInfo: List[TargetInfo] = list.flatMap { x =>
|
||||
continent.GUID(x.target_guid) match {
|
||||
case Some(player: Player) =>
|
||||
val health = player.Health.toFloat / player.MaxHealth
|
||||
val armor = if (player.MaxArmor > 0) {
|
||||
player.Armor.toFloat / player.MaxArmor
|
||||
} else {
|
||||
0
|
||||
}
|
||||
Some(TargetInfo(player.GUID, health, armor))
|
||||
case _ =>
|
||||
log.warn(
|
||||
s"TargetingImplantRequest: the info that ${player.Name} requested for target ${x.target_guid} is not for a player"
|
||||
)
|
||||
None
|
||||
}
|
||||
}
|
||||
sendResponse(TargetingInfoMessage(targetInfo))
|
||||
ops.handleTargetingImplantRequest(pkt)
|
||||
}
|
||||
|
||||
def handleHitHint(pkt: HitHint): Unit = { /* intentionally blank */ }
|
||||
|
|
@ -444,8 +417,7 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
|
|||
}
|
||||
|
||||
def handleKick(player: Player, time: Option[Long]): Unit = {
|
||||
administrativeKick(player)
|
||||
sessionLogic.accountPersistence ! AccountPersistenceService.Kick(player.Name, time)
|
||||
ops.administrativeKick(player, None)
|
||||
}
|
||||
|
||||
def handleSilenced(isSilenced: Boolean): Unit = {
|
||||
|
|
@ -460,107 +432,6 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
|
|||
|
||||
/* supporting functions */
|
||||
|
||||
private def handleUseDoor(door: Door, equipment: Option[Equipment]): Unit = {
|
||||
equipment match {
|
||||
case Some(tool: Tool) if tool.Definition == GlobalDefinitions.medicalapplicator =>
|
||||
val distance: Float = math.max(
|
||||
Config.app.game.doorsCanBeOpenedByMedAppFromThisDistance,
|
||||
door.Definition.initialOpeningDistance
|
||||
)
|
||||
door.Actor ! CommonMessages.Use(player, Some(distance))
|
||||
case _ =>
|
||||
door.Actor ! CommonMessages.Use(player)
|
||||
}
|
||||
}
|
||||
|
||||
private def handleUseTelepadDeployable(obj: TelepadDeployable, equipment: Option[Equipment], msg: UseItemMessage): Unit = {
|
||||
if (equipment.isEmpty) {
|
||||
(continent.GUID(obj.Router) match {
|
||||
case Some(vehicle: Vehicle) => Some((vehicle, vehicle.Utility(UtilityType.internal_router_telepad_deployable)))
|
||||
case Some(vehicle) => Some(vehicle, None)
|
||||
case None => None
|
||||
}) match {
|
||||
case Some((vehicle: Vehicle, Some(util: Utility.InternalTelepad))) =>
|
||||
player.WhichSide = vehicle.WhichSide
|
||||
useRouterTelepadSystem(
|
||||
router = vehicle,
|
||||
internalTelepad = util,
|
||||
remoteTelepad = obj,
|
||||
src = obj,
|
||||
dest = util
|
||||
)
|
||||
case Some((vehicle: Vehicle, None)) =>
|
||||
log.error(
|
||||
s"telepad@${msg.object_guid.guid} is not linked to a router - ${vehicle.Definition.Name}"
|
||||
)
|
||||
case Some((o, _)) =>
|
||||
log.error(
|
||||
s"telepad@${msg.object_guid.guid} is linked to wrong kind of object - ${o.Definition.Name}, ${obj.Router}"
|
||||
)
|
||||
obj.Actor ! Deployable.Deconstruct()
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def handleUseInternalTelepad(obj: InternalTelepad, msg: UseItemMessage): Unit = {
|
||||
continent.GUID(obj.Telepad) match {
|
||||
case Some(pad: TelepadDeployable) =>
|
||||
player.WhichSide = pad.WhichSide
|
||||
useRouterTelepadSystem(
|
||||
router = obj.Owner.asInstanceOf[Vehicle],
|
||||
internalTelepad = obj,
|
||||
remoteTelepad = pad,
|
||||
src = obj,
|
||||
dest = pad
|
||||
)
|
||||
case Some(o) =>
|
||||
log.error(
|
||||
s"internal telepad@${msg.object_guid.guid} is not linked to a remote telepad - ${o.Definition.Name}@${o.GUID.guid}"
|
||||
)
|
||||
case None => ()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A player uses a fully-linked Router teleportation system.
|
||||
* @param router the Router vehicle
|
||||
* @param internalTelepad the internal telepad within the Router vehicle
|
||||
* @param remoteTelepad the remote telepad that is currently associated with this Router
|
||||
* @param src the origin of the teleportation (where the player starts)
|
||||
* @param dest the destination of the teleportation (where the player is going)
|
||||
*/
|
||||
private def useRouterTelepadSystem(
|
||||
router: Vehicle,
|
||||
internalTelepad: InternalTelepad,
|
||||
remoteTelepad: TelepadDeployable,
|
||||
src: PlanetSideGameObject with TelepadLike,
|
||||
dest: PlanetSideGameObject with TelepadLike
|
||||
): Unit = {
|
||||
val time = System.currentTimeMillis()
|
||||
if (
|
||||
time - ops.recentTeleportAttempt > 2000L && router.DeploymentState == DriveState.Deployed &&
|
||||
internalTelepad.Active &&
|
||||
remoteTelepad.Active
|
||||
) {
|
||||
val pguid = player.GUID
|
||||
val sguid = src.GUID
|
||||
val dguid = dest.GUID
|
||||
sendResponse(PlayerStateShiftMessage(ShiftState(0, dest.Position, player.Orientation.z)))
|
||||
ops.useRouterTelepadEffect(pguid, sguid, dguid)
|
||||
player.Position = dest.Position
|
||||
} else {
|
||||
log.warn(s"UseRouterTelepadSystem: ${player.Name} can not teleport")
|
||||
}
|
||||
ops.recentTeleportAttempt = time
|
||||
}
|
||||
|
||||
private def administrativeKick(tplayer: Player): Unit = {
|
||||
log.warn(s"${tplayer.Name} has been kicked by ${player.Name}")
|
||||
tplayer.death_by = -1
|
||||
sessionLogic.accountPersistence ! AccountPersistenceService.Kick(tplayer.Name)
|
||||
}
|
||||
|
||||
private def customImplantOff(slot: Int, implant: Implant): Unit = {
|
||||
customImplants = customImplants.updated(slot, implant.copy(active = false))
|
||||
sendResponse(AvatarImplantMessage(player.GUID, ImplantAction.Activation, slot, 0))
|
||||
|
|
|
|||
|
|
@ -1,257 +0,0 @@
|
|||
// Copyright (c) 2024 PSForever
|
||||
package net.psforever.actors.session.spectator
|
||||
|
||||
import akka.actor.ActorContext
|
||||
import net.psforever.actors.session.support.{LocalHandlerFunctions, SessionData, SessionLocalHandlers}
|
||||
import net.psforever.objects.ce.Deployable
|
||||
import net.psforever.objects.guid.{GUIDTask, TaskWorkflow}
|
||||
import net.psforever.objects.vehicles.MountableWeapons
|
||||
import net.psforever.objects.{BoomerDeployable, ExplosiveDeployable, TelepadDeployable, Tool, TurretDeployable}
|
||||
import net.psforever.packet.game.{ChatMsg, DeployableObjectsInfoMessage, GenericActionMessage, GenericObjectActionMessage, GenericObjectStateMsg, HackMessage, HackState, HackState1, InventoryStateMessage, ObjectAttachMessage, ObjectCreateMessage, ObjectDeleteMessage, ObjectDetachMessage, OrbitalShuttleTimeMsg, PadAndShuttlePair, PlanetsideAttributeMessage, ProximityTerminalUseMessage, SetEmpireMessage, TriggerEffectMessage, TriggerSoundMessage, TriggeredSound, VehicleStateMessage}
|
||||
import net.psforever.services.Service
|
||||
import net.psforever.services.local.LocalResponse
|
||||
import net.psforever.types.{ChatMessageType, PlanetSideGUID, Vector3}
|
||||
|
||||
object LocalHandlerLogic {
|
||||
def apply(ops: SessionLocalHandlers): LocalHandlerLogic = {
|
||||
new LocalHandlerLogic(ops, ops.context)
|
||||
}
|
||||
}
|
||||
|
||||
class LocalHandlerLogic(val ops: SessionLocalHandlers, implicit val context: ActorContext) extends LocalHandlerFunctions {
|
||||
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 */
|
||||
|
||||
/**
|
||||
* na
|
||||
* @param toChannel na
|
||||
* @param guid na
|
||||
* @param reply na
|
||||
*/
|
||||
def handle(toChannel: String, guid: PlanetSideGUID, reply: LocalResponse.Response): Unit = {
|
||||
val resolvedPlayerGuid = if (player.HasGUID) {
|
||||
player.GUID
|
||||
} else {
|
||||
Service.defaultPlayerGUID
|
||||
}
|
||||
val isNotSameTarget = resolvedPlayerGuid != guid
|
||||
reply match {
|
||||
case LocalResponse.DeployableMapIcon(behavior, deployInfo) if isNotSameTarget =>
|
||||
sendResponse(DeployableObjectsInfoMessage(behavior, deployInfo))
|
||||
|
||||
case LocalResponse.DeployableUIFor(item) =>
|
||||
sessionLogic.general.updateDeployableUIElements(avatar.deployables.UpdateUIElement(item))
|
||||
|
||||
case LocalResponse.Detonate(dguid, _: BoomerDeployable) =>
|
||||
sendResponse(TriggerEffectMessage(dguid, "detonate_boomer"))
|
||||
sendResponse(PlanetsideAttributeMessage(dguid, attribute_type=29, attribute_value=1))
|
||||
sendResponse(ObjectDeleteMessage(dguid, unk1=0))
|
||||
|
||||
case LocalResponse.Detonate(dguid, _: ExplosiveDeployable) =>
|
||||
sendResponse(GenericObjectActionMessage(dguid, code=19))
|
||||
sendResponse(PlanetsideAttributeMessage(dguid, attribute_type=29, attribute_value=1))
|
||||
sendResponse(ObjectDeleteMessage(dguid, unk1=0))
|
||||
|
||||
case LocalResponse.Detonate(_, obj) =>
|
||||
log.warn(s"LocalResponse.Detonate: ${obj.Definition.Name} not configured to explode correctly")
|
||||
|
||||
case LocalResponse.DoorOpens(doorGuid) if isNotSameTarget =>
|
||||
sendResponse(GenericObjectStateMsg(doorGuid, state=16))
|
||||
|
||||
case LocalResponse.DoorCloses(doorGuid) => //door closes for everyone
|
||||
sendResponse(GenericObjectStateMsg(doorGuid, state=17))
|
||||
|
||||
case LocalResponse.EliminateDeployable(obj: TurretDeployable, dguid, _, _) if obj.Destroyed =>
|
||||
sendResponse(ObjectDeleteMessage(dguid, unk1=0))
|
||||
|
||||
case LocalResponse.EliminateDeployable(obj: TurretDeployable, dguid, pos, _) =>
|
||||
obj.Destroyed = true
|
||||
DeconstructDeployable(
|
||||
obj,
|
||||
dguid,
|
||||
pos,
|
||||
obj.Orientation,
|
||||
deletionType= if (obj.MountPoints.isEmpty) { 2 } else { 1 }
|
||||
)
|
||||
|
||||
case LocalResponse.EliminateDeployable(obj: ExplosiveDeployable, dguid, _, _)
|
||||
if obj.Destroyed || obj.Jammed || obj.Health == 0 =>
|
||||
sendResponse(ObjectDeleteMessage(dguid, unk1=0))
|
||||
|
||||
case LocalResponse.EliminateDeployable(obj: ExplosiveDeployable, dguid, pos, effect) =>
|
||||
obj.Destroyed = true
|
||||
DeconstructDeployable(obj, dguid, pos, obj.Orientation, effect)
|
||||
|
||||
case LocalResponse.EliminateDeployable(obj: TelepadDeployable, dguid, _, _) if obj.Active && obj.Destroyed =>
|
||||
//if active, deactivate
|
||||
obj.Active = false
|
||||
ops.deactivateTelpadDeployableMessages(dguid)
|
||||
//standard deployable elimination behavior
|
||||
sendResponse(ObjectDeleteMessage(dguid, unk1=0))
|
||||
|
||||
case LocalResponse.EliminateDeployable(obj: TelepadDeployable, dguid, pos, _) if obj.Active =>
|
||||
//if active, deactivate
|
||||
obj.Active = false
|
||||
ops.deactivateTelpadDeployableMessages(dguid)
|
||||
//standard deployable elimination behavior
|
||||
obj.Destroyed = true
|
||||
DeconstructDeployable(obj, dguid, pos, obj.Orientation, deletionType=2)
|
||||
|
||||
case LocalResponse.EliminateDeployable(obj: TelepadDeployable, dguid, _, _) if obj.Destroyed =>
|
||||
//standard deployable elimination behavior
|
||||
sendResponse(ObjectDeleteMessage(dguid, unk1=0))
|
||||
|
||||
case LocalResponse.EliminateDeployable(obj: TelepadDeployable, dguid, pos, _) =>
|
||||
//standard deployable elimination behavior
|
||||
obj.Destroyed = true
|
||||
DeconstructDeployable(obj, dguid, pos, obj.Orientation, deletionType=2)
|
||||
|
||||
case LocalResponse.EliminateDeployable(obj, dguid, _, _) if obj.Destroyed =>
|
||||
sendResponse(ObjectDeleteMessage(dguid, unk1=0))
|
||||
|
||||
case LocalResponse.EliminateDeployable(obj, dguid, pos, effect) =>
|
||||
obj.Destroyed = true
|
||||
DeconstructDeployable(obj, dguid, pos, obj.Orientation, effect)
|
||||
|
||||
case LocalResponse.SendHackMessageHackCleared(targetGuid, unk1, unk2) =>
|
||||
sendResponse(HackMessage(HackState1.Unk0, targetGuid, guid, progress=0, unk1.toFloat, HackState.HackCleared, unk2))
|
||||
|
||||
case LocalResponse.HackObject(targetGuid, unk1, unk2) =>
|
||||
sessionLogic.general.hackObject(targetGuid, unk1, unk2)
|
||||
|
||||
case LocalResponse.PlanetsideAttribute(targetGuid, attributeType, attributeValue) =>
|
||||
sessionLogic.general.sendPlanetsideAttributeMessage(targetGuid, attributeType, attributeValue)
|
||||
|
||||
case LocalResponse.GenericObjectAction(targetGuid, actionNumber) =>
|
||||
sendResponse(GenericObjectActionMessage(targetGuid, actionNumber))
|
||||
|
||||
case LocalResponse.GenericActionMessage(actionNumber) =>
|
||||
sendResponse(GenericActionMessage(actionNumber))
|
||||
|
||||
case LocalResponse.ChatMessage(msg) =>
|
||||
sendResponse(msg)
|
||||
|
||||
case LocalResponse.SendPacket(packet) =>
|
||||
sendResponse(packet)
|
||||
|
||||
case LocalResponse.LluSpawned(llu) =>
|
||||
// Create LLU on client
|
||||
sendResponse(ObjectCreateMessage(
|
||||
llu.Definition.ObjectId,
|
||||
llu.GUID,
|
||||
llu.Definition.Packet.ConstructorData(llu).get
|
||||
))
|
||||
sendResponse(TriggerSoundMessage(TriggeredSound.LLUMaterialize, llu.Position, unk=20, volume=0.8000001f))
|
||||
|
||||
case LocalResponse.LluDespawned(lluGuid, position) =>
|
||||
sendResponse(TriggerSoundMessage(TriggeredSound.LLUDeconstruct, position, unk=20, volume=0.8000001f))
|
||||
sendResponse(ObjectDeleteMessage(lluGuid, unk1=0))
|
||||
// If the player was holding the LLU, remove it from their tracked special item slot
|
||||
sessionLogic.general.specialItemSlotGuid.collect { case guid if guid == lluGuid =>
|
||||
sessionLogic.general.specialItemSlotGuid = None
|
||||
player.Carrying = None
|
||||
}
|
||||
|
||||
case LocalResponse.ObjectDelete(objectGuid, unk) if isNotSameTarget =>
|
||||
sendResponse(ObjectDeleteMessage(objectGuid, unk))
|
||||
|
||||
case LocalResponse.ProximityTerminalEffect(object_guid, true) =>
|
||||
sendResponse(ProximityTerminalUseMessage(Service.defaultPlayerGUID, object_guid, unk=true))
|
||||
|
||||
case LocalResponse.ProximityTerminalEffect(objectGuid, false) =>
|
||||
sendResponse(ProximityTerminalUseMessage(Service.defaultPlayerGUID, objectGuid, unk=false))
|
||||
sessionLogic.terminals.ForgetAllProximityTerminals(objectGuid)
|
||||
|
||||
case LocalResponse.RouterTelepadMessage(msg) =>
|
||||
sendResponse(ChatMsg(ChatMessageType.UNK_229, wideContents=false, recipient="", msg, note=None))
|
||||
|
||||
case LocalResponse.RouterTelepadTransport(passengerGuid, srcGuid, destGuid) =>
|
||||
sessionLogic.general.useRouterTelepadEffect(passengerGuid, srcGuid, destGuid)
|
||||
|
||||
case LocalResponse.SendResponse(msg) =>
|
||||
sendResponse(msg)
|
||||
|
||||
case LocalResponse.SetEmpire(objectGuid, empire) =>
|
||||
sendResponse(SetEmpireMessage(objectGuid, empire))
|
||||
|
||||
case LocalResponse.ShuttleEvent(ev) =>
|
||||
val msg = OrbitalShuttleTimeMsg(
|
||||
ev.u1,
|
||||
ev.u2,
|
||||
ev.t1,
|
||||
ev.t2,
|
||||
ev.t3,
|
||||
pairs=ev.pairs.map { case ((a, b), c) => PadAndShuttlePair(a, b, c) }
|
||||
)
|
||||
sendResponse(msg)
|
||||
|
||||
case LocalResponse.ShuttleDock(pguid, sguid, slot) =>
|
||||
sendResponse(ObjectAttachMessage(pguid, sguid, slot))
|
||||
|
||||
case LocalResponse.ShuttleUndock(pguid, sguid, pos, orient) =>
|
||||
sendResponse(ObjectDetachMessage(pguid, sguid, pos, orient))
|
||||
|
||||
case LocalResponse.ShuttleState(sguid, pos, orient, state) =>
|
||||
sendResponse(VehicleStateMessage(sguid, unk1=0, pos, orient, vel=None, Some(state), unk3=0, unk4=0, wheel_direction=15, is_decelerating=false, is_cloaked=false))
|
||||
|
||||
case LocalResponse.ToggleTeleportSystem(router, systemPlan) =>
|
||||
sessionLogic.general.toggleTeleportSystem(router, systemPlan)
|
||||
|
||||
case LocalResponse.TriggerEffect(targetGuid, effect, effectInfo, triggerLocation) =>
|
||||
sendResponse(TriggerEffectMessage(targetGuid, effect, effectInfo, triggerLocation))
|
||||
|
||||
case LocalResponse.TriggerSound(sound, pos, unk, volume) =>
|
||||
sendResponse(TriggerSoundMessage(sound, pos, unk, volume))
|
||||
|
||||
case LocalResponse.UpdateForceDomeStatus(buildingGuid, true) =>
|
||||
sendResponse(GenericObjectActionMessage(buildingGuid, 11))
|
||||
|
||||
case LocalResponse.UpdateForceDomeStatus(buildingGuid, false) =>
|
||||
sendResponse(GenericObjectActionMessage(buildingGuid, 12))
|
||||
|
||||
case LocalResponse.RechargeVehicleWeapon(vehicleGuid, weaponGuid) if resolvedPlayerGuid == guid =>
|
||||
continent.GUID(vehicleGuid)
|
||||
.collect { case vehicle: MountableWeapons => (vehicle, vehicle.PassengerInSeat(player)) }
|
||||
.collect { case (vehicle, Some(seat_num)) => vehicle.WeaponControlledFromSeat(seat_num) }
|
||||
.getOrElse(Set.empty)
|
||||
.collect { case weapon: Tool if weapon.GUID == weaponGuid =>
|
||||
sendResponse(InventoryStateMessage(weapon.AmmoSlot.Box.GUID, weapon.GUID, weapon.Magazine))
|
||||
}
|
||||
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
|
||||
/* support functions */
|
||||
|
||||
/**
|
||||
* Common behavior for deconstructing deployables in the game environment.
|
||||
* @param obj the deployable
|
||||
* @param guid the globally unique identifier for the deployable
|
||||
* @param pos the previous position of the deployable
|
||||
* @param orient the previous orientation of the deployable
|
||||
* @param deletionType the value passed to `ObjectDeleteMessage` concerning the deconstruction animation
|
||||
*/
|
||||
def DeconstructDeployable(
|
||||
obj: Deployable,
|
||||
guid: PlanetSideGUID,
|
||||
pos: Vector3,
|
||||
orient: Vector3,
|
||||
deletionType: Int
|
||||
): Unit = {
|
||||
sendResponse(TriggerEffectMessage("spawn_object_failed_effect", pos, orient))
|
||||
sendResponse(PlanetsideAttributeMessage(guid, 29, 1)) //make deployable vanish
|
||||
sendResponse(ObjectDeleteMessage(guid, deletionType))
|
||||
}
|
||||
}
|
||||
|
|
@ -10,13 +10,11 @@ import net.psforever.objects.{GlobalDefinitions, PlanetSideGameObject, Player, V
|
|||
import net.psforever.objects.serverobject.affinity.FactionAffinity
|
||||
import net.psforever.objects.serverobject.mount.Mountable
|
||||
import net.psforever.objects.serverobject.terminals.implant.ImplantTerminalMech
|
||||
import net.psforever.objects.vehicles.{AccessPermissionGroup, CargoBehavior}
|
||||
import net.psforever.objects.vital.InGameHistory
|
||||
import net.psforever.packet.game.{DelayedPathMountMsg, DismountVehicleCargoMsg, DismountVehicleMsg, GenericObjectActionMessage, MountVehicleCargoMsg, MountVehicleMsg, ObjectDetachMessage, PlayerStasisMessage, PlayerStateShiftMessage, ShiftState}
|
||||
import net.psforever.services.Service
|
||||
import net.psforever.services.local.{LocalAction, LocalServiceMessage}
|
||||
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
|
||||
import net.psforever.types.{BailType, PlanetSideGUID, Vector3}
|
||||
|
||||
object MountHandlerLogic {
|
||||
def apply(ops: SessionMountHandlers): MountHandlerLogic = {
|
||||
|
|
@ -29,98 +27,16 @@ class MountHandlerLogic(val ops: SessionMountHandlers, implicit val context: Act
|
|||
|
||||
/* packets */
|
||||
|
||||
def handleMountVehicle(pkt: MountVehicleMsg): Unit = { /* intentionally blank */ }
|
||||
def handleMountVehicle(pkt: MountVehicleMsg): Unit = { /* can not mount as spectator */ }
|
||||
|
||||
def handleDismountVehicle(pkt: DismountVehicleMsg): Unit = {
|
||||
val DismountVehicleMsg(player_guid, bailType, wasKickedByDriver) = pkt
|
||||
val dError: (String, Player)=> Unit = dismountError(bailType, wasKickedByDriver)
|
||||
//TODO optimize this later
|
||||
//common warning for this section
|
||||
if (player.GUID == player_guid) {
|
||||
//normally disembarking from a mount
|
||||
(sessionLogic.zoning.interstellarFerry.orElse(continent.GUID(player.VehicleSeated)) match {
|
||||
case out @ Some(obj: Vehicle) =>
|
||||
continent.GUID(obj.MountedIn) match {
|
||||
case Some(_: Vehicle) => None //cargo vehicle
|
||||
case _ => out //arrangement "may" be permissible
|
||||
}
|
||||
case out @ Some(_: Mountable) =>
|
||||
out
|
||||
case _ =>
|
||||
dError(s"DismountVehicleMsg: player ${player.Name} not considered seated in a mountable entity", player)
|
||||
None
|
||||
}) match {
|
||||
case Some(obj: Mountable) =>
|
||||
obj.PassengerInSeat(player) match {
|
||||
case Some(seat_num) =>
|
||||
obj.Actor ! Mountable.TryDismount(player, seat_num, bailType)
|
||||
//short-circuit the temporary channel for transferring between zones, the player is no longer doing that
|
||||
sessionLogic.zoning.interstellarFerry = None
|
||||
// Deconstruct the vehicle if the driver has bailed out and the vehicle is capable of flight
|
||||
//todo: implement auto landing procedure if the pilot bails but passengers are still present instead of deconstructing the vehicle
|
||||
//todo: continue flight path until aircraft crashes if no passengers present (or no passenger seats), then deconstruct.
|
||||
//todo: kick cargo passengers out. To be added after PR #216 is merged
|
||||
obj match {
|
||||
case v: Vehicle
|
||||
if bailType == BailType.Bailed &&
|
||||
v.SeatPermissionGroup(seat_num).contains(AccessPermissionGroup.Driver) &&
|
||||
v.isFlying =>
|
||||
v.Actor ! Vehicle.Deconstruct(None) //immediate deconstruction
|
||||
case _ => ()
|
||||
}
|
||||
|
||||
case None =>
|
||||
dError(s"DismountVehicleMsg: can not find where player ${player.Name}_guid is seated in mountable ${player.VehicleSeated}", player)
|
||||
}
|
||||
case _ =>
|
||||
dError(s"DismountVehicleMsg: can not find mountable entity ${player.VehicleSeated}", player)
|
||||
}
|
||||
} else {
|
||||
//kicking someone else out of a mount; need to own that mount/mountable
|
||||
val dWarn: (String, Player)=> Unit = dismountWarning(bailType, wasKickedByDriver)
|
||||
player.avatar.vehicle match {
|
||||
case Some(obj_guid) =>
|
||||
(
|
||||
(
|
||||
sessionLogic.validObject(obj_guid, decorator = "DismountVehicle/Vehicle"),
|
||||
sessionLogic.validObject(player_guid, decorator = "DismountVehicle/Player")
|
||||
) match {
|
||||
case (vehicle @ Some(obj: Vehicle), tplayer) =>
|
||||
if (obj.MountedIn.isEmpty) (vehicle, tplayer) else (None, None)
|
||||
case (mount @ Some(_: Mountable), tplayer) =>
|
||||
(mount, tplayer)
|
||||
case _ =>
|
||||
(None, None)
|
||||
}) match {
|
||||
case (Some(obj: Mountable), Some(tplayer: Player)) =>
|
||||
obj.PassengerInSeat(tplayer) match {
|
||||
case Some(seat_num) =>
|
||||
obj.Actor ! Mountable.TryDismount(tplayer, seat_num, bailType)
|
||||
case None =>
|
||||
dError(s"DismountVehicleMsg: can not find where other player ${tplayer.Name} is seated in mountable $obj_guid", tplayer)
|
||||
}
|
||||
case (None, _) =>
|
||||
dWarn(s"DismountVehicleMsg: ${player.Name} can not find his vehicle", player)
|
||||
case (_, None) =>
|
||||
dWarn(s"DismountVehicleMsg: player $player_guid could not be found to kick, ${player.Name}", player)
|
||||
case _ =>
|
||||
dWarn(s"DismountVehicleMsg: object is either not a Mountable or not a Player", player)
|
||||
}
|
||||
case None =>
|
||||
dWarn(s"DismountVehicleMsg: ${player.Name} does not own a vehicle", player)
|
||||
}
|
||||
}
|
||||
ops.handleDismountVehicle(pkt)
|
||||
}
|
||||
|
||||
def handleMountVehicleCargo(pkt: MountVehicleCargoMsg): Unit = { /* intentionally blank */ }
|
||||
def handleMountVehicleCargo(pkt: MountVehicleCargoMsg): Unit = { /* can not mount as spectator */ }
|
||||
|
||||
def handleDismountVehicleCargo(pkt: DismountVehicleCargoMsg): Unit = {
|
||||
val DismountVehicleCargoMsg(_, cargo_guid, bailed, _, kicked) = pkt
|
||||
continent.GUID(cargo_guid) match {
|
||||
case Some(cargo: Vehicle) =>
|
||||
cargo.Actor ! CargoBehavior.StartCargoDismounting(bailed || kicked)
|
||||
case _ => ()
|
||||
}
|
||||
ops.handleDismountVehicleCargo(pkt)
|
||||
}
|
||||
|
||||
/* response handlers */
|
||||
|
|
@ -134,7 +50,7 @@ class MountHandlerLogic(val ops: SessionMountHandlers, implicit val context: Act
|
|||
def handle(tplayer: Player, reply: Mountable.Exchange): Unit = {
|
||||
reply match {
|
||||
case Mountable.CanDismount(obj: ImplantTerminalMech, seatNum, _) =>
|
||||
DismountAction(tplayer, obj, seatNum)
|
||||
ops.DismountAction(tplayer, obj, seatNum)
|
||||
obj.Zone.actor ! ZoneActor.RemoveFromBlockMap(player)
|
||||
|
||||
case Mountable.CanDismount(obj: Vehicle, _, mountPoint)
|
||||
|
|
@ -157,7 +73,7 @@ class MountHandlerLogic(val ops: SessionMountHandlers, implicit val context: Act
|
|||
//get ready for orbital drop
|
||||
val pguid = player.GUID
|
||||
val events = continent.VehicleEvents
|
||||
DismountAction(tplayer, obj, seatNum)
|
||||
ops.DismountAction(tplayer, obj, seatNum)
|
||||
continent.actor ! ZoneActor.RemoveFromBlockMap(player) //character doesn't need it
|
||||
//DismountAction(...) uses vehicle service, so use that service to coordinate the remainder of the messages
|
||||
events ! VehicleServiceMessage(
|
||||
|
|
@ -185,14 +101,14 @@ class MountHandlerLogic(val ops: SessionMountHandlers, implicit val context: Act
|
|||
case Mountable.CanDismount(obj: Vehicle, seatNum, _)
|
||||
if obj.Definition == GlobalDefinitions.droppod =>
|
||||
sessionLogic.general.unaccessContainer(obj)
|
||||
DismountAction(tplayer, obj, seatNum)
|
||||
ops.DismountAction(tplayer, obj, seatNum)
|
||||
obj.Actor ! Vehicle.Deconstruct()
|
||||
|
||||
case Mountable.CanDismount(obj: Vehicle, seatNum, _)
|
||||
if tplayer.GUID == player.GUID =>
|
||||
sessionLogic.vehicles.ConditionalDriverVehicleControl(obj)
|
||||
sessionLogic.general.unaccessContainer(obj)
|
||||
DismountVehicleAction(tplayer, obj, seatNum)
|
||||
ops.DismountVehicleAction(tplayer, obj, seatNum)
|
||||
|
||||
case Mountable.CanDismount(obj: Vehicle, seat_num, _) =>
|
||||
continent.VehicleEvents ! VehicleServiceMessage(
|
||||
|
|
@ -201,7 +117,7 @@ class MountHandlerLogic(val ops: SessionMountHandlers, implicit val context: Act
|
|||
)
|
||||
|
||||
case Mountable.CanDismount(obj: PlanetSideGameObject with PlanetSideGameObject with Mountable with FactionAffinity with InGameHistory, seatNum, _) =>
|
||||
DismountAction(tplayer, obj, seatNum)
|
||||
ops.DismountAction(tplayer, obj, seatNum)
|
||||
|
||||
case Mountable.CanDismount(_: Mountable, _, _) => ()
|
||||
|
||||
|
|
@ -213,90 +129,4 @@ class MountHandlerLogic(val ops: SessionMountHandlers, implicit val context: Act
|
|||
}
|
||||
|
||||
/* support functions */
|
||||
|
||||
private def dismountWarning(
|
||||
bailAs: BailType.Value,
|
||||
kickedByDriver: Boolean
|
||||
)
|
||||
(
|
||||
note: String,
|
||||
player: Player
|
||||
): Unit = {
|
||||
log.warn(note)
|
||||
player.VehicleSeated = None
|
||||
sendResponse(DismountVehicleMsg(player.GUID, bailAs, kickedByDriver))
|
||||
}
|
||||
|
||||
private def dismountError(
|
||||
bailAs: BailType.Value,
|
||||
kickedByDriver: Boolean
|
||||
)
|
||||
(
|
||||
note: String,
|
||||
player: Player
|
||||
): Unit = {
|
||||
log.error(s"$note; some vehicle might not know that ${player.Name} is no longer sitting in it")
|
||||
player.VehicleSeated = None
|
||||
sendResponse(DismountVehicleMsg(player.GUID, bailAs, kickedByDriver))
|
||||
}
|
||||
|
||||
/**
|
||||
* Common activities/procedure when a player dismounts a valid mountable object.
|
||||
* @param tplayer the player
|
||||
* @param obj the mountable object
|
||||
* @param seatNum the mount out of which which the player is disembarking
|
||||
*/
|
||||
private def DismountVehicleAction(tplayer: Player, obj: PlanetSideGameObject with FactionAffinity with InGameHistory, seatNum: Int): Unit = {
|
||||
DismountAction(tplayer, obj, seatNum)
|
||||
//until vehicles maintain synchronized momentum without a driver
|
||||
obj match {
|
||||
case v: Vehicle
|
||||
if seatNum == 0 && Vector3.MagnitudeSquared(v.Velocity.getOrElse(Vector3.Zero)) > 0f =>
|
||||
sessionLogic.vehicles.serverVehicleControlVelocity.collect { _ =>
|
||||
sessionLogic.vehicles.ServerVehicleOverrideStop(v)
|
||||
}
|
||||
v.Velocity = Vector3.Zero
|
||||
continent.VehicleEvents ! VehicleServiceMessage(
|
||||
continent.id,
|
||||
VehicleAction.VehicleState(
|
||||
tplayer.GUID,
|
||||
v.GUID,
|
||||
unk1 = 0,
|
||||
v.Position,
|
||||
v.Orientation,
|
||||
vel = None,
|
||||
v.Flying,
|
||||
unk3 = 0,
|
||||
unk4 = 0,
|
||||
wheel_direction = 15,
|
||||
unk5 = false,
|
||||
unk6 = v.Cloaked
|
||||
)
|
||||
)
|
||||
v.Zone.actor ! ZoneActor.RemoveFromBlockMap(player)
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Common activities/procedure when a player dismounts a valid mountable object.
|
||||
* @param tplayer the player
|
||||
* @param obj the mountable object
|
||||
* @param seatNum the mount out of which which the player is disembarking
|
||||
*/
|
||||
private def DismountAction(tplayer: Player, obj: PlanetSideGameObject with FactionAffinity with InGameHistory, seatNum: Int): Unit = {
|
||||
val playerGuid: PlanetSideGUID = tplayer.GUID
|
||||
tplayer.ContributionFrom(obj)
|
||||
sessionLogic.keepAliveFunc = sessionLogic.zoning.NormalKeepAlive
|
||||
val bailType = if (tplayer.BailProtection) {
|
||||
BailType.Bailed
|
||||
} else {
|
||||
BailType.Normal
|
||||
}
|
||||
sendResponse(DismountVehicleMsg(playerGuid, bailType, wasKickedByDriver = false))
|
||||
continent.VehicleEvents ! VehicleServiceMessage(
|
||||
continent.id,
|
||||
VehicleAction.DismountVehicle(playerGuid, bailType, unk2 = false)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,9 +24,9 @@ import net.psforever.packet.game.{ChatMsg, CreateShortcutMessage, UnuseItemMessa
|
|||
class SpectatorModeLogic(data: SessionData) extends ModeLogic {
|
||||
val avatarResponse: AvatarHandlerFunctions = AvatarHandlerLogic(data.avatarResponse)
|
||||
val chat: ChatFunctions = ChatLogic(data.chat)
|
||||
val galaxy: GalaxyHandlerFunctions = GalaxyHandlerLogic(data.galaxyResponseHandlers)
|
||||
val galaxy: GalaxyHandlerFunctions = net.psforever.actors.session.normal.GalaxyHandlerLogic(data.galaxyResponseHandlers)
|
||||
val general: GeneralFunctions = GeneralLogic(data.general)
|
||||
val local: LocalHandlerFunctions = LocalHandlerLogic(data.localResponse)
|
||||
val local: LocalHandlerFunctions = net.psforever.actors.session.normal.LocalHandlerLogic(data.localResponse)
|
||||
val mountResponse: MountHandlerFunctions = MountHandlerLogic(data.mountResponse)
|
||||
val shooting: WeaponAndProjectileFunctions = WeaponAndProjectileLogic(data.shooting)
|
||||
val squad: SquadHandlerFunctions = SquadHandlerLogic(data.squad)
|
||||
|
|
@ -119,6 +119,7 @@ class SpectatorModeLogic(data: SessionData) extends ModeLogic {
|
|||
}
|
||||
//
|
||||
player.spectator = true
|
||||
player.allowInteraction = false
|
||||
data.chat.JoinChannel(SpectatorChannel)
|
||||
val newPlayer = SpectatorModeLogic.spectatorCharacter(player)
|
||||
newPlayer.LogActivity(player.History.headOption)
|
||||
|
|
@ -152,6 +153,8 @@ class SpectatorModeLogic(data: SessionData) extends ModeLogic {
|
|||
val pguid = player.GUID
|
||||
val sendResponse: PlanetSidePacket => Unit = data.sendResponse
|
||||
//
|
||||
player.spectator = false
|
||||
player.allowInteraction = true
|
||||
data.general.stop()
|
||||
player.avatar.shortcuts.slice(1, 4)
|
||||
.zipWithIndex
|
||||
|
|
@ -159,12 +162,11 @@ class SpectatorModeLogic(data: SessionData) extends ModeLogic {
|
|||
.map(CreateShortcutMessage(pguid, _, None))
|
||||
.foreach(sendResponse)
|
||||
data.chat.LeaveChannel(SpectatorChannel)
|
||||
player.spectator = false
|
||||
sendResponse(ObjectDeleteMessage(player.avatar.locker.GUID, 0)) //free up the slot
|
||||
sendResponse(ChatMsg(ChatMessageType.CMT_TOGGLESPECTATORMODE, "off"))
|
||||
sendResponse(ChatMsg(ChatMessageType.UNK_227, "@SpectatorDisabled"))
|
||||
zoning.zoneReload = true
|
||||
zoning.spawn.randomRespawn(0.seconds) //to sanctuary
|
||||
zoning.spawn.randomRespawn(10.milliseconds) //to sanctuary
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -178,7 +180,7 @@ object SpectatorModeLogic {
|
|||
private def spectatorCharacter(player: Player): Player = {
|
||||
val avatar = player.avatar
|
||||
val newAvatar = avatar.copy(
|
||||
basic = avatar.basic.copy(name = "spectator"),
|
||||
basic = avatar.basic,
|
||||
bep = BattleRank.BR18.experience,
|
||||
cep = CommandRank.CR5.experience,
|
||||
certifications = Set(),
|
||||
|
|
@ -212,6 +214,7 @@ object SpectatorModeLogic {
|
|||
newPlayer.Position = player.Position
|
||||
newPlayer.Orientation = player.Orientation
|
||||
newPlayer.spectator = true
|
||||
newPlayer.bops = true
|
||||
newPlayer.Spawn()
|
||||
newPlayer
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,15 @@
|
|||
// Copyright (c) 2024 PSForever
|
||||
package net.psforever.actors.session.spectator
|
||||
|
||||
import akka.actor.{ActorContext, ActorRef, typed}
|
||||
import net.psforever.actors.session.AvatarActor
|
||||
import akka.actor.ActorContext
|
||||
import net.psforever.actors.session.support.{SessionData, SessionVehicleHandlers, VehicleHandlerFunctions}
|
||||
import net.psforever.objects.{GlobalDefinitions, Player, Tool, Vehicle, Vehicles}
|
||||
import net.psforever.objects.equipment.{Equipment, JammableMountedWeapons, JammableUnit}
|
||||
import net.psforever.objects.guid.{GUIDTask, TaskWorkflow}
|
||||
import net.psforever.objects.serverobject.mount.Mountable
|
||||
import net.psforever.objects.{Tool, Vehicle, Vehicles}
|
||||
import net.psforever.objects.equipment.Equipment
|
||||
import net.psforever.objects.serverobject.pad.VehicleSpawnPad
|
||||
import net.psforever.packet.game.objectcreate.ObjectCreateMessageParent
|
||||
import net.psforever.packet.game.{ChangeAmmoMessage, ChangeFireStateMessage_Start, ChangeFireStateMessage_Stop, ChatMsg, ChildObjectStateMessage, DeadState, DeployRequestMessage, DismountVehicleMsg, FrameVehicleStateMessage, GenericObjectActionMessage, HitHint, InventoryStateMessage, ObjectAttachMessage, ObjectCreateDetailedMessage, ObjectCreateMessage, ObjectDeleteMessage, ObjectDetachMessage, PlanetsideAttributeMessage, ReloadMessage, ServerVehicleOverrideMsg, VehicleStateMessage, WeaponDryFireMessage}
|
||||
import net.psforever.services.Service
|
||||
import net.psforever.packet.game.{ChangeAmmoMessage, ChangeFireStateMessage_Start, ChangeFireStateMessage_Stop, ChildObjectStateMessage, DeadState, DeployRequestMessage, DismountVehicleMsg, FrameVehicleStateMessage, GenericObjectActionMessage, HitHint, InventoryStateMessage, ObjectAttachMessage, ObjectCreateDetailedMessage, ObjectCreateMessage, ObjectDeleteMessage, ObjectDetachMessage, PlanetsideAttributeMessage, ReloadMessage, ServerVehicleOverrideMsg, VehicleStateMessage, WeaponDryFireMessage}
|
||||
import net.psforever.services.vehicle.{VehicleResponse, VehicleServiceResponse}
|
||||
import net.psforever.types.{BailType, ChatMessageType, PlanetSideGUID, Vector3}
|
||||
import net.psforever.types.{BailType, PlanetSideGUID, Vector3}
|
||||
|
||||
object VehicleHandlerLogic {
|
||||
def apply(ops: SessionVehicleHandlers): VehicleHandlerLogic = {
|
||||
|
|
@ -24,9 +20,9 @@ object VehicleHandlerLogic {
|
|||
class VehicleHandlerLogic(val ops: SessionVehicleHandlers, implicit val context: ActorContext) extends VehicleHandlerFunctions {
|
||||
def sessionLogic: SessionData = ops.sessionLogic
|
||||
|
||||
private val avatarActor: typed.ActorRef[AvatarActor.Command] = ops.avatarActor
|
||||
//private val avatarActor: typed.ActorRef[AvatarActor.Command] = ops.avatarActor
|
||||
|
||||
private val galaxyService: ActorRef = ops.galaxyService
|
||||
//private val galaxyService: ActorRef = ops.galaxyService
|
||||
|
||||
/**
|
||||
* na
|
||||
|
|
@ -151,9 +147,6 @@ class VehicleHandlerLogic(val ops: SessionVehicleHandlers, implicit val context:
|
|||
case VehicleResponse.GenericObjectAction(objectGuid, action) if isNotSameTarget =>
|
||||
sendResponse(GenericObjectActionMessage(objectGuid, action))
|
||||
|
||||
case VehicleResponse.HitHint(sourceGuid) if player.isAlive =>
|
||||
sendResponse(HitHint(sourceGuid, player.GUID))
|
||||
|
||||
case VehicleResponse.InventoryState(obj, parentGuid, start, conData) if isNotSameTarget =>
|
||||
//TODO prefer ObjectDetachMessage, but how to force ammo pools to update properly?
|
||||
val objGuid = obj.GUID
|
||||
|
|
@ -169,14 +162,11 @@ class VehicleHandlerLogic(val ops: SessionVehicleHandlers, implicit val context:
|
|||
//seat number (first field) seems to be correct if passenger is kicked manually by driver
|
||||
//but always seems to return 4 if user is kicked by mount permissions changing
|
||||
sendResponse(DismountVehicleMsg(guid, BailType.Kicked, wasKickedByDriver))
|
||||
val typeOfRide = continent.GUID(vehicleGuid) match {
|
||||
continent.GUID(vehicleGuid) match {
|
||||
case Some(obj: Vehicle) =>
|
||||
sessionLogic.general.unaccessContainer(obj)
|
||||
s"the ${obj.Definition.Name}'s seat by ${obj.OwnerName.getOrElse("the pilot")}"
|
||||
case _ =>
|
||||
s"${player.Sex.possessive} ride"
|
||||
case _ => ()
|
||||
}
|
||||
log.info(s"${player.Name} has been kicked from $typeOfRide!")
|
||||
|
||||
case VehicleResponse.KickPassenger(_, wasKickedByDriver, _) =>
|
||||
//seat number (first field) seems to be correct if passenger is kicked manually by driver
|
||||
|
|
@ -194,11 +184,6 @@ class VehicleHandlerLogic(val ops: SessionVehicleHandlers, implicit val context:
|
|||
case VehicleResponse.ObjectDelete(itemGuid) if isNotSameTarget =>
|
||||
sendResponse(ObjectDeleteMessage(itemGuid, unk1=0))
|
||||
|
||||
case VehicleResponse.Ownership(vehicleGuid) if resolvedPlayerGuid == guid =>
|
||||
//Only the player that owns this vehicle needs the ownership packet
|
||||
avatarActor ! AvatarActor.SetVehicle(Some(vehicleGuid))
|
||||
sendResponse(PlanetsideAttributeMessage(resolvedPlayerGuid, attribute_type=21, vehicleGuid))
|
||||
|
||||
case VehicleResponse.PlanetsideAttribute(vehicleGuid, attributeType, attributeValue) if isNotSameTarget =>
|
||||
sendResponse(PlanetsideAttributeMessage(vehicleGuid, attributeType, attributeValue))
|
||||
|
||||
|
|
@ -211,10 +196,6 @@ class VehicleHandlerLogic(val ops: SessionVehicleHandlers, implicit val context:
|
|||
case VehicleResponse.SeatPermissions(vehicleGuid, seatGroup, permission) if isNotSameTarget =>
|
||||
sendResponse(PlanetsideAttributeMessage(vehicleGuid, seatGroup, permission))
|
||||
|
||||
case VehicleResponse.StowEquipment(vehicleGuid, slot, itemType, itemGuid, itemData) if isNotSameTarget =>
|
||||
//TODO prefer ObjectAttachMessage, but how to force ammo pools to update properly?
|
||||
sendResponse(ObjectCreateDetailedMessage(itemType, itemGuid, ObjectCreateMessageParent(vehicleGuid, slot), itemData))
|
||||
|
||||
case VehicleResponse.UnloadVehicle(_, vehicleGuid) =>
|
||||
sendResponse(ObjectDeleteMessage(vehicleGuid, unk1=0))
|
||||
|
||||
|
|
@ -226,13 +207,6 @@ class VehicleHandlerLogic(val ops: SessionVehicleHandlers, implicit val context:
|
|||
sessionLogic.zoning.spawn.amsSpawnPoints = list.filter(tube => tube.Faction == player.Faction)
|
||||
sessionLogic.zoning.spawn.DrawCurrentAmsSpawnPoint()
|
||||
|
||||
case VehicleResponse.TransferPassengerChannel(oldChannel, tempChannel, vehicle, vehicleToDelete) if isNotSameTarget =>
|
||||
sessionLogic.zoning.interstellarFerry = Some(vehicle)
|
||||
sessionLogic.zoning.interstellarFerryTopLevelGUID = Some(vehicleToDelete)
|
||||
continent.VehicleEvents ! Service.Leave(Some(oldChannel)) //old vehicle-specific channel (was s"${vehicle.Actor}")
|
||||
galaxyService ! Service.Join(tempChannel) //temporary vehicle-specific channel
|
||||
log.debug(s"TransferPassengerChannel: ${player.Name} now subscribed to $tempChannel for vehicle gating")
|
||||
|
||||
case VehicleResponse.KickCargo(vehicle, speed, delay)
|
||||
if player.VehicleSeated.nonEmpty && sessionLogic.zoning.spawn.deadState == DeadState.Alive && speed > 0 =>
|
||||
val strafe = 1 + Vehicles.CargoOrientation(vehicle)
|
||||
|
|
@ -263,100 +237,9 @@ class VehicleHandlerLogic(val ops: SessionVehicleHandlers, implicit val context:
|
|||
if player.VehicleSeated.nonEmpty && sessionLogic.zoning.spawn.deadState == DeadState.Alive =>
|
||||
sessionLogic.vehicles.TotalDriverVehicleControl(cargo)
|
||||
|
||||
case VehicleResponse.StartPlayerSeatedInVehicle(vehicle, _)
|
||||
if player.VisibleSlots.contains(player.DrawnSlot) =>
|
||||
player.DrawnSlot = Player.HandsDownSlot
|
||||
startPlayerSeatedInVehicle(vehicle)
|
||||
|
||||
case VehicleResponse.StartPlayerSeatedInVehicle(vehicle, _) =>
|
||||
startPlayerSeatedInVehicle(vehicle)
|
||||
|
||||
case VehicleResponse.PlayerSeatedInVehicle(vehicle, _) =>
|
||||
Vehicles.ReloadAccessPermissions(vehicle, player.Name)
|
||||
sessionLogic.vehicles.ServerVehicleOverrideWithPacket(
|
||||
vehicle,
|
||||
ServerVehicleOverrideMsg(
|
||||
lock_accelerator=true,
|
||||
lock_wheel=true,
|
||||
reverse=true,
|
||||
unk4=false,
|
||||
lock_vthrust=1,
|
||||
lock_strafe=0,
|
||||
movement_speed=0,
|
||||
unk8=Some(0)
|
||||
)
|
||||
)
|
||||
sessionLogic.vehicles.serverVehicleControlVelocity = Some(0)
|
||||
|
||||
case VehicleResponse.ServerVehicleOverrideStart(vehicle, _) =>
|
||||
val vdef = vehicle.Definition
|
||||
sessionLogic.vehicles.ServerVehicleOverrideWithPacket(
|
||||
vehicle,
|
||||
ServerVehicleOverrideMsg(
|
||||
lock_accelerator=true,
|
||||
lock_wheel=true,
|
||||
reverse=false,
|
||||
unk4=false,
|
||||
lock_vthrust=if (GlobalDefinitions.isFlightVehicle(vdef)) { 1 } else { 0 },
|
||||
lock_strafe=0,
|
||||
movement_speed=vdef.AutoPilotSpeed1,
|
||||
unk8=Some(0)
|
||||
)
|
||||
)
|
||||
|
||||
case VehicleResponse.ServerVehicleOverrideEnd(vehicle, _) =>
|
||||
sessionLogic.vehicles.ServerVehicleOverrideStop(vehicle)
|
||||
|
||||
case VehicleResponse.PeriodicReminder(VehicleSpawnPad.Reminders.Blocked, data) =>
|
||||
sendResponse(ChatMsg(
|
||||
ChatMessageType.CMT_OPEN,
|
||||
wideContents=true,
|
||||
recipient="",
|
||||
s"The vehicle spawn where you placed your order is blocked. ${data.getOrElse("")}",
|
||||
note=None
|
||||
))
|
||||
|
||||
case VehicleResponse.PeriodicReminder(_, data) =>
|
||||
val (isType, flag, msg): (ChatMessageType, Boolean, String) = data match {
|
||||
case Some(msg: String) if msg.startsWith("@") => (ChatMessageType.UNK_227, false, msg)
|
||||
case Some(msg: String) => (ChatMessageType.CMT_OPEN, true, msg)
|
||||
case _ => (ChatMessageType.CMT_OPEN, true, "Your vehicle order has been cancelled.")
|
||||
}
|
||||
sendResponse(ChatMsg(isType, flag, recipient="", msg, None))
|
||||
|
||||
case VehicleResponse.ChangeLoadout(target, oldWeapons, addedWeapons, oldInventory, newInventory)
|
||||
if player.avatar.vehicle.contains(target) =>
|
||||
//TODO when vehicle weapons can be changed without visual glitches, rewrite this
|
||||
continent.GUID(target).collect { case vehicle: Vehicle =>
|
||||
import net.psforever.login.WorldSession.boolToInt
|
||||
//owner: must unregister old equipment, and register and install new equipment
|
||||
(oldWeapons ++ oldInventory).foreach {
|
||||
case (obj, eguid) =>
|
||||
sendResponse(ObjectDeleteMessage(eguid, unk1=0))
|
||||
TaskWorkflow.execute(GUIDTask.unregisterEquipment(continent.GUID, obj))
|
||||
}
|
||||
sessionLogic.general.applyPurchaseTimersBeforePackingLoadout(player, vehicle, addedWeapons ++ newInventory)
|
||||
//jammer or unjamm new weapons based on vehicle status
|
||||
val vehicleJammered = vehicle.Jammed
|
||||
addedWeapons
|
||||
.map { _.obj }
|
||||
.collect {
|
||||
case jamItem: JammableUnit if jamItem.Jammed != vehicleJammered =>
|
||||
jamItem.Jammed = vehicleJammered
|
||||
JammableMountedWeapons.JammedWeaponStatus(vehicle.Zone, jamItem, vehicleJammered)
|
||||
}
|
||||
changeLoadoutDeleteOldEquipment(vehicle, oldWeapons, oldInventory)
|
||||
}
|
||||
|
||||
case VehicleResponse.ChangeLoadout(target, oldWeapons, _, oldInventory, _)
|
||||
if sessionLogic.general.accessedContainer.map(_.GUID).contains(target) =>
|
||||
//TODO when vehicle weapons can be changed without visual glitches, rewrite this
|
||||
continent.GUID(target).collect { case vehicle: Vehicle =>
|
||||
//external participant: observe changes to equipment
|
||||
(oldWeapons ++ oldInventory).foreach { case (_, eguid) => sendResponse(ObjectDeleteMessage(eguid, unk1=0)) }
|
||||
changeLoadoutDeleteOldEquipment(vehicle, oldWeapons, oldInventory)
|
||||
}
|
||||
|
||||
case VehicleResponse.ChangeLoadout(target, oldWeapons, _, oldInventory, _) =>
|
||||
//TODO when vehicle weapons can be changed without visual glitches, rewrite this
|
||||
continent.GUID(target).collect { case vehicle: Vehicle =>
|
||||
|
|
@ -384,16 +267,4 @@ class VehicleHandlerLogic(val ops: SessionVehicleHandlers, implicit val context:
|
|||
oldWeapons.foreach { case (_, eguid) => sendResponse(ObjectDeleteMessage(eguid, unk1=0)) }
|
||||
}
|
||||
}
|
||||
|
||||
private def startPlayerSeatedInVehicle(vehicle: Vehicle): Unit = {
|
||||
val vehicle_guid = vehicle.GUID
|
||||
sessionLogic.actionsToCancel()
|
||||
sessionLogic.terminals.CancelAllProximityUnits()
|
||||
sessionLogic.vehicles.serverVehicleControlVelocity = Some(0)
|
||||
sendResponse(PlanetsideAttributeMessage(vehicle_guid, attribute_type=22, attribute_value=1L)) //mount points off
|
||||
sendResponse(PlanetsideAttributeMessage(player.GUID, attribute_type=21, vehicle_guid)) //ownership
|
||||
vehicle.MountPoints.find { case (_, mp) => mp.seatIndex == 0 }.collect {
|
||||
case (mountPoint, _) => vehicle.Actor ! Mountable.TryMount(player, mountPoint)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,11 @@
|
|||
// Copyright (c) 2024 PSForever
|
||||
package net.psforever.actors.session.spectator
|
||||
|
||||
import akka.actor.{ActorContext, typed}
|
||||
import net.psforever.actors.session.AvatarActor
|
||||
import akka.actor.ActorContext
|
||||
import net.psforever.actors.session.support.{SessionData, VehicleFunctions, VehicleOperations}
|
||||
import net.psforever.objects.serverobject.PlanetSideServerObject
|
||||
import net.psforever.objects.{Vehicle, Vehicles}
|
||||
import net.psforever.objects.Vehicle
|
||||
import net.psforever.objects.serverobject.deploy.Deployment
|
||||
import net.psforever.objects.serverobject.mount.Mountable
|
||||
import net.psforever.objects.vehicles.control.BfrFlight
|
||||
import net.psforever.packet.game.{ChildObjectStateMessage, DeployRequestMessage, FrameVehicleStateMessage, VehicleStateMessage, VehicleSubStateMessage}
|
||||
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
|
||||
import net.psforever.types.{DriveState, Vector3}
|
||||
|
|
@ -22,210 +19,15 @@ object VehicleLogic {
|
|||
class VehicleLogic(val ops: VehicleOperations, implicit val context: ActorContext) extends VehicleFunctions {
|
||||
def sessionLogic: SessionData = ops.sessionLogic
|
||||
|
||||
private val avatarActor: typed.ActorRef[AvatarActor.Command] = ops.avatarActor
|
||||
//private val avatarActor: typed.ActorRef[AvatarActor.Command] = ops.avatarActor
|
||||
|
||||
/* packets */
|
||||
|
||||
def handleVehicleState(pkt: VehicleStateMessage): Unit = {
|
||||
val VehicleStateMessage(
|
||||
vehicle_guid,
|
||||
unk1,
|
||||
pos,
|
||||
ang,
|
||||
vel,
|
||||
is_flying,
|
||||
unk6,
|
||||
unk7,
|
||||
wheels,
|
||||
is_decelerating,
|
||||
is_cloaked
|
||||
) = pkt
|
||||
ops.GetVehicleAndSeat() match {
|
||||
case (Some(obj), Some(0)) =>
|
||||
//we're driving the vehicle
|
||||
sessionLogic.persist()
|
||||
sessionLogic.turnCounterFunc(player.GUID)
|
||||
sessionLogic.general.fallHeightTracker(pos.z)
|
||||
if (obj.MountedIn.isEmpty) {
|
||||
sessionLogic.updateBlockMap(obj, pos)
|
||||
}
|
||||
player.Position = pos //convenient
|
||||
if (obj.WeaponControlledFromSeat(0).isEmpty) {
|
||||
player.Orientation = Vector3.z(ang.z) //convenient
|
||||
}
|
||||
obj.Position = pos
|
||||
obj.Orientation = ang
|
||||
if (obj.MountedIn.isEmpty) {
|
||||
if (obj.DeploymentState != DriveState.Deployed) {
|
||||
obj.Velocity = vel
|
||||
} else {
|
||||
obj.Velocity = Some(Vector3.Zero)
|
||||
}
|
||||
if (obj.Definition.CanFly) {
|
||||
obj.Flying = is_flying //usually Some(7)
|
||||
}
|
||||
obj.Cloaked = obj.Definition.CanCloak && is_cloaked
|
||||
} else {
|
||||
obj.Velocity = None
|
||||
obj.Flying = None
|
||||
}
|
||||
continent.VehicleEvents ! VehicleServiceMessage(
|
||||
continent.id,
|
||||
VehicleAction.VehicleState(
|
||||
player.GUID,
|
||||
vehicle_guid,
|
||||
unk1,
|
||||
obj.Position,
|
||||
ang,
|
||||
obj.Velocity,
|
||||
if (obj.isFlying) {
|
||||
is_flying
|
||||
} else {
|
||||
None
|
||||
},
|
||||
unk6,
|
||||
unk7,
|
||||
wheels,
|
||||
is_decelerating,
|
||||
obj.Cloaked
|
||||
)
|
||||
)
|
||||
sessionLogic.squad.updateSquad()
|
||||
obj.zoneInteractions()
|
||||
case (None, _) =>
|
||||
//log.error(s"VehicleState: no vehicle $vehicle_guid found in zone")
|
||||
//TODO placing a "not driving" warning here may trigger as we are disembarking the vehicle
|
||||
case (_, Some(index)) =>
|
||||
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)"
|
||||
)
|
||||
case _ => ()
|
||||
}
|
||||
if (player.death_by == -1) {
|
||||
sessionLogic.kickedByAdministration()
|
||||
}
|
||||
}
|
||||
def handleVehicleState(pkt: VehicleStateMessage): Unit = { /* can not drive vehicle as spectator */ }
|
||||
|
||||
def handleFrameVehicleState(pkt: FrameVehicleStateMessage): Unit = {
|
||||
val FrameVehicleStateMessage(
|
||||
vehicle_guid,
|
||||
unk1,
|
||||
pos,
|
||||
ang,
|
||||
vel,
|
||||
unk2,
|
||||
unk3,
|
||||
unk4,
|
||||
is_crouched,
|
||||
is_airborne,
|
||||
ascending_flight,
|
||||
flight_time,
|
||||
unk9,
|
||||
unkA
|
||||
) = pkt
|
||||
ops.GetVehicleAndSeat() match {
|
||||
case (Some(obj), Some(0)) =>
|
||||
//we're driving the vehicle
|
||||
sessionLogic.persist()
|
||||
sessionLogic.turnCounterFunc(player.GUID)
|
||||
val (position, angle, velocity, notMountedState) = continent.GUID(obj.MountedIn) match {
|
||||
case Some(v: Vehicle) =>
|
||||
sessionLogic.updateBlockMap(obj, pos)
|
||||
(pos, v.Orientation - Vector3.z(value = 90f) * Vehicles.CargoOrientation(obj).toFloat, v.Velocity, false)
|
||||
case _ =>
|
||||
(pos, ang, vel, true)
|
||||
}
|
||||
player.Position = position //convenient
|
||||
if (obj.WeaponControlledFromSeat(seatNumber = 0).isEmpty) {
|
||||
player.Orientation = Vector3.z(ang.z) //convenient
|
||||
}
|
||||
obj.Position = position
|
||||
obj.Orientation = angle
|
||||
obj.Velocity = velocity
|
||||
// if (is_crouched && obj.DeploymentState != DriveState.Kneeling) {
|
||||
// //dev stuff goes here
|
||||
// }
|
||||
// else
|
||||
// if (!is_crouched && obj.DeploymentState == DriveState.Kneeling) {
|
||||
// //dev stuff goes here
|
||||
// }
|
||||
obj.DeploymentState = if (is_crouched || !notMountedState) DriveState.Kneeling else DriveState.Mobile
|
||||
if (notMountedState) {
|
||||
if (obj.DeploymentState != DriveState.Kneeling) {
|
||||
if (is_airborne) {
|
||||
val flight = if (ascending_flight) flight_time else -flight_time
|
||||
obj.Flying = Some(flight)
|
||||
obj.Actor ! BfrFlight.Soaring(flight)
|
||||
} else if (obj.Flying.nonEmpty) {
|
||||
obj.Flying = None
|
||||
obj.Actor ! BfrFlight.Landed
|
||||
}
|
||||
} else {
|
||||
obj.Velocity = None
|
||||
obj.Flying = None
|
||||
}
|
||||
obj.zoneInteractions()
|
||||
} else {
|
||||
obj.Velocity = None
|
||||
obj.Flying = None
|
||||
}
|
||||
continent.VehicleEvents ! VehicleServiceMessage(
|
||||
continent.id,
|
||||
VehicleAction.FrameVehicleState(
|
||||
player.GUID,
|
||||
vehicle_guid,
|
||||
unk1,
|
||||
position,
|
||||
angle,
|
||||
velocity,
|
||||
unk2,
|
||||
unk3,
|
||||
unk4,
|
||||
is_crouched,
|
||||
is_airborne,
|
||||
ascending_flight,
|
||||
flight_time,
|
||||
unk9,
|
||||
unkA
|
||||
)
|
||||
)
|
||||
sessionLogic.squad.updateSquad()
|
||||
case (None, _) =>
|
||||
//log.error(s"VehicleState: no vehicle $vehicle_guid found in zone")
|
||||
//TODO placing a "not driving" warning here may trigger as we are disembarking the vehicle
|
||||
case (_, Some(index)) =>
|
||||
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)"
|
||||
)
|
||||
case _ => ()
|
||||
}
|
||||
if (player.death_by == -1) {
|
||||
sessionLogic.kickedByAdministration()
|
||||
}
|
||||
}
|
||||
def handleFrameVehicleState(pkt: FrameVehicleStateMessage): Unit = { /* can not drive vehicle as spectator */ }
|
||||
|
||||
def handleChildObjectState(pkt: ChildObjectStateMessage): Unit = {
|
||||
val ChildObjectStateMessage(object_guid, pitch, yaw) = pkt
|
||||
val (o, tools) = sessionLogic.shooting.FindContainedWeapon
|
||||
//is COSM our primary upstream packet?
|
||||
(o match {
|
||||
case Some(mount: Mountable) => (o, mount.PassengerInSeat(player))
|
||||
case _ => (None, None)
|
||||
}) match {
|
||||
case (None, None) | (_, None) | (Some(_: Vehicle), Some(0)) => ()
|
||||
case _ =>
|
||||
sessionLogic.persist()
|
||||
sessionLogic.turnCounterFunc(player.GUID)
|
||||
}
|
||||
//the majority of the following check retrieves information to determine if we are in control of the child
|
||||
tools.find { _.GUID == object_guid } match {
|
||||
case None => ()
|
||||
case Some(_) => player.Orientation = Vector3(0f, pitch, yaw)
|
||||
}
|
||||
if (player.death_by == -1) {
|
||||
sessionLogic.kickedByAdministration()
|
||||
}
|
||||
}
|
||||
def handleChildObjectState(pkt: ChildObjectStateMessage): Unit = { /* can not drive vehicle as spectator */ }
|
||||
|
||||
def handleVehicleSubState(pkt: VehicleSubStateMessage): Unit = {
|
||||
val VehicleSubStateMessage(vehicle_guid, _, pos, ang, vel, unk1, _) = pkt
|
||||
|
|
@ -258,22 +60,7 @@ class VehicleLogic(val ops: VehicleOperations, implicit val context: ActorContex
|
|||
}
|
||||
}
|
||||
|
||||
def handleDeployRequest(pkt: DeployRequestMessage): Unit = {
|
||||
val DeployRequestMessage(_, vehicle_guid, deploy_state, _, _, _) = pkt
|
||||
val vehicle = player.avatar.vehicle
|
||||
if (vehicle.contains(vehicle_guid)) {
|
||||
if (vehicle == player.VehicleSeated) {
|
||||
continent.GUID(vehicle_guid) match {
|
||||
case Some(obj: Vehicle) =>
|
||||
if (obj.DeploymentState == DriveState.Deployed) {
|
||||
obj.Actor ! Deployment.TryDeploymentChange(deploy_state)
|
||||
}
|
||||
case _ => ()
|
||||
avatarActor ! AvatarActor.SetVehicle(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
def handleDeployRequest(pkt: DeployRequestMessage): Unit = { /* can not drive vehicle as spectator */ }
|
||||
|
||||
/* messages */
|
||||
|
||||
|
|
|
|||
|
|
@ -3,15 +3,9 @@ package net.psforever.actors.session.spectator
|
|||
|
||||
import akka.actor.ActorContext
|
||||
import net.psforever.actors.session.support.{SessionData, WeaponAndProjectileFunctions, WeaponAndProjectileOperations}
|
||||
import net.psforever.login.WorldSession.{CountGrenades, FindEquipmentStock, FindToolThatUses, RemoveOldEquipmentFromInventory}
|
||||
import net.psforever.objects.ballistics.Projectile
|
||||
import net.psforever.objects.equipment.ChargeFireModeDefinition
|
||||
import net.psforever.objects.inventory.Container
|
||||
import net.psforever.objects.serverobject.CommonMessages
|
||||
import net.psforever.objects.{AmmoBox, BoomerDeployable, BoomerTrigger, GlobalDefinitions, PlanetSideGameObject, Tool}
|
||||
import net.psforever.packet.game.{AIDamage, AvatarGrenadeStateMessage, ChangeAmmoMessage, ChangeFireModeMessage, ChangeFireStateMessage_Start, ChangeFireStateMessage_Stop, HitMessage, InventoryStateMessage, LashMessage, LongRangeProjectileInfoMessage, ProjectileStateMessage, QuantityUpdateMessage, ReloadMessage, SplashHitMessage, UplinkRequest, UplinkRequestType, UplinkResponse, WeaponDelayFireMessage, WeaponDryFireMessage, WeaponFireMessage, WeaponLazeTargetPositionMessage}
|
||||
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||
import net.psforever.types.PlanetSideGUID
|
||||
import net.psforever.objects.{BoomerDeployable, BoomerTrigger, Tool}
|
||||
import net.psforever.packet.game.{AIDamage, AvatarGrenadeStateMessage, ChangeAmmoMessage, ChangeFireModeMessage, ChangeFireStateMessage_Start, ChangeFireStateMessage_Stop, HitMessage, LashMessage, LongRangeProjectileInfoMessage, ProjectileStateMessage, ReloadMessage, SplashHitMessage, UplinkRequest, UplinkRequestType, UplinkResponse, WeaponDelayFireMessage, WeaponDryFireMessage, WeaponFireMessage, WeaponLazeTargetPositionMessage}
|
||||
|
||||
object WeaponAndProjectileLogic {
|
||||
def apply(ops: WeaponAndProjectileOperations): WeaponAndProjectileLogic = {
|
||||
|
|
@ -59,9 +53,9 @@ class WeaponAndProjectileLogic(val ops: WeaponAndProjectileOperations, implicit
|
|||
ops.shooting -= item_guid
|
||||
sessionLogic.findEquipment(item_guid) match {
|
||||
case Some(tool: Tool) if player.VehicleSeated.isEmpty =>
|
||||
fireStateStopWhenPlayer(tool, item_guid)
|
||||
ops.fireStateStopWhenPlayer(tool, item_guid)
|
||||
case Some(tool: Tool) =>
|
||||
fireStateStopWhenMounted(tool, item_guid)
|
||||
ops.fireStateStopWhenMounted(tool, item_guid)
|
||||
case Some(trigger: BoomerTrigger) =>
|
||||
ops.fireStateStopPlayerMessages(item_guid)
|
||||
continent.GUID(trigger.Companion).collect {
|
||||
|
|
@ -85,32 +79,7 @@ class WeaponAndProjectileLogic(val ops: WeaponAndProjectileOperations, implicit
|
|||
def handleChangeFireMode(pkt: ChangeFireModeMessage): Unit = { /* intentionally blank */ }
|
||||
|
||||
def handleProjectileState(pkt: ProjectileStateMessage): Unit = {
|
||||
val ProjectileStateMessage(projectile_guid, shot_pos, shot_vel, shot_orient, seq, end, target_guid) = pkt
|
||||
val index = projectile_guid.guid - Projectile.baseUID
|
||||
ops.projectiles(index) match {
|
||||
case Some(projectile) if projectile.HasGUID =>
|
||||
val projectileGlobalUID = projectile.GUID
|
||||
projectile.Position = shot_pos
|
||||
projectile.Orientation = shot_orient
|
||||
projectile.Velocity = shot_vel
|
||||
continent.AvatarEvents ! AvatarServiceMessage(
|
||||
continent.id,
|
||||
AvatarAction.ProjectileState(
|
||||
player.GUID,
|
||||
projectileGlobalUID,
|
||||
shot_pos,
|
||||
shot_vel,
|
||||
shot_orient,
|
||||
seq,
|
||||
end,
|
||||
target_guid
|
||||
)
|
||||
)
|
||||
case _ if seq == 0 =>
|
||||
/* missing the first packet in the sequence is permissible */
|
||||
case _ =>
|
||||
log.warn(s"ProjectileState: constructed projectile ${projectile_guid.guid} can not be found")
|
||||
}
|
||||
ops.handleProjectileState(pkt)
|
||||
}
|
||||
|
||||
def handleLongRangeProjectileState(pkt: LongRangeProjectileInfoMessage): Unit = { /* intentionally blank */ }
|
||||
|
|
@ -122,97 +91,4 @@ class WeaponAndProjectileLogic(val ops: WeaponAndProjectileOperations, implicit
|
|||
def handleLashHit(pkt: LashMessage): Unit = { /* intentionally blank */ }
|
||||
|
||||
def handleAIDamage(pkt: AIDamage): Unit = { /* intentionally blank */ }
|
||||
|
||||
/* support code */
|
||||
|
||||
/**
|
||||
* After a weapon has finished shooting, determine if it needs to be sorted in a special way.
|
||||
* @param tool a weapon
|
||||
*/
|
||||
private def FireCycleCleanup(tool: Tool): Unit = {
|
||||
//TODO replaced by more appropriate functionality in the future
|
||||
val tdef = tool.Definition
|
||||
if (GlobalDefinitions.isGrenade(tdef)) {
|
||||
val ammoType = tool.AmmoType
|
||||
FindEquipmentStock(player, FindToolThatUses(ammoType), 3, CountGrenades).reverse match { //do not search sidearm holsters
|
||||
case Nil =>
|
||||
log.info(s"${player.Name} has no more $ammoType grenades to throw")
|
||||
RemoveOldEquipmentFromInventory(player)(tool)
|
||||
|
||||
case x :: xs => //this is similar to ReloadMessage
|
||||
val box = x.obj.asInstanceOf[Tool]
|
||||
val tailReloadValue: Int = if (xs.isEmpty) { 0 }
|
||||
else { xs.map(_.obj.asInstanceOf[Tool].Magazine).sum }
|
||||
val sumReloadValue: Int = box.Magazine + tailReloadValue
|
||||
val actualReloadValue = if (sumReloadValue <= 3) {
|
||||
RemoveOldEquipmentFromInventory(player)(x.obj)
|
||||
sumReloadValue
|
||||
} else {
|
||||
ModifyAmmunition(player)(box.AmmoSlot.Box, 3 - tailReloadValue)
|
||||
3
|
||||
}
|
||||
log.info(s"${player.Name} found $actualReloadValue more $ammoType grenades to throw")
|
||||
ModifyAmmunition(player)(
|
||||
tool.AmmoSlot.Box,
|
||||
-actualReloadValue
|
||||
) //grenade item already in holster (negative because empty)
|
||||
xs.foreach(item => { RemoveOldEquipmentFromInventory(player)(item.obj) })
|
||||
}
|
||||
} else if (tdef == GlobalDefinitions.phoenix) {
|
||||
RemoveOldEquipmentFromInventory(player)(tool)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an object that contains a box of amunition in its `Inventory` at a certain location,
|
||||
* change the amount of ammunition within that box.
|
||||
* @param obj the `Container`
|
||||
* @param box an `AmmoBox` to modify
|
||||
* @param reloadValue the value to modify the `AmmoBox`;
|
||||
* subtracted from the current `Capacity` of `Box`
|
||||
*/
|
||||
private def ModifyAmmunition(obj: PlanetSideGameObject with Container)(box: AmmoBox, reloadValue: Int): Unit = {
|
||||
val capacity = box.Capacity - reloadValue
|
||||
box.Capacity = capacity
|
||||
sendResponse(InventoryStateMessage(box.GUID, obj.GUID, capacity))
|
||||
}
|
||||
|
||||
private def fireStateStartPlayerMessages(itemGuid: PlanetSideGUID): Unit = {
|
||||
continent.AvatarEvents ! AvatarServiceMessage(
|
||||
continent.id,
|
||||
AvatarAction.ChangeFireState_Start(player.GUID, itemGuid)
|
||||
)
|
||||
}
|
||||
|
||||
/*
|
||||
used by ChangeFireStateMessage_Stop handling
|
||||
*/
|
||||
private def fireStateStopUpdateChargeAndCleanup(tool: Tool): Unit = {
|
||||
tool.FireMode match {
|
||||
case _: ChargeFireModeDefinition =>
|
||||
sendResponse(QuantityUpdateMessage(tool.AmmoSlot.Box.GUID, tool.Magazine))
|
||||
case _ => ()
|
||||
}
|
||||
if (tool.Magazine == 0) {
|
||||
FireCycleCleanup(tool)
|
||||
}
|
||||
}
|
||||
|
||||
private def fireStateStopWhenPlayer(tool: Tool, itemGuid: PlanetSideGUID): Unit = {
|
||||
//the decimator does not send a ChangeFireState_Start on the last shot; heaven knows why
|
||||
//suppress the decimator's alternate fire mode, however
|
||||
if (
|
||||
tool.Definition == GlobalDefinitions.phoenix &&
|
||||
tool.Projectile != GlobalDefinitions.phoenix_missile_guided_projectile
|
||||
) {
|
||||
fireStateStartPlayerMessages(itemGuid)
|
||||
}
|
||||
fireStateStopUpdateChargeAndCleanup(tool)
|
||||
ops.fireStateStopPlayerMessages(itemGuid)
|
||||
}
|
||||
|
||||
private def fireStateStopWhenMounted(tool: Tool, itemGuid: PlanetSideGUID): Unit = {
|
||||
fireStateStopUpdateChargeAndCleanup(tool)
|
||||
ops.fireStateStopMountedMessages(itemGuid)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,9 +4,8 @@ package net.psforever.actors.session.support
|
|||
import akka.actor.Cancellable
|
||||
import akka.actor.typed.ActorRef
|
||||
import akka.actor.{ActorContext, typed}
|
||||
import net.psforever.actors.session.spectator.SpectatorMode
|
||||
import net.psforever.actors.session.{AvatarActor, SessionActor}
|
||||
import net.psforever.actors.session.normal.{NormalMode => SessionNormalMode}
|
||||
import net.psforever.actors.session.spectator.{SpectatorMode => SessionSpectatorMode}
|
||||
import net.psforever.actors.zone.ZoneActor
|
||||
import net.psforever.objects.sourcing.PlayerSource
|
||||
import net.psforever.objects.zones.ZoneInfo
|
||||
|
|
@ -59,6 +58,7 @@ class ChatOperations(
|
|||
) extends CommonSessionInterfacingFunctionality {
|
||||
private var channels: List[ChatChannel] = List()
|
||||
private var silenceTimer: Cancellable = Default.Cancellable
|
||||
private[session] var transitoryCommandEntered: Option[ChatMessageType] = None
|
||||
/**
|
||||
* when another player is listed as one of our ignored players,
|
||||
* and that other player sends an emote,
|
||||
|
|
@ -67,6 +67,8 @@ class ChatOperations(
|
|||
*/
|
||||
private val ignoredEmoteCooldown: mutable.LongMap[Long] = mutable.LongMap[Long]()
|
||||
|
||||
private[session] var CurrentSpectatorMode: PlayerMode = SpectatorMode
|
||||
|
||||
import akka.actor.typed.scaladsl.adapter._
|
||||
private val chatServiceAdapter: ActorRef[ChatService.MessageResponse] = context.self.toTyped[ChatService.MessageResponse]
|
||||
|
||||
|
|
@ -112,17 +114,6 @@ class ChatOperations(
|
|||
sendResponse(message.copy(contents = f"$speed%.3f"))
|
||||
}
|
||||
|
||||
def commandToggleSpectatorMode(session: Session, contents: String): Unit = {
|
||||
val currentSpectatorActivation = session.player.spectator
|
||||
contents.toLowerCase() match {
|
||||
case "on" | "o" | "" if !currentSpectatorActivation =>
|
||||
context.self ! SessionActor.SetMode(SessionSpectatorMode)
|
||||
case "off" | "of" if currentSpectatorActivation =>
|
||||
context.self ! SessionActor.SetMode(SessionNormalMode)
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
|
||||
def commandRecall(session: Session): Unit = {
|
||||
val player = session.player
|
||||
val errorMessage = session.zoningType match {
|
||||
|
|
@ -337,6 +328,28 @@ class ChatOperations(
|
|||
}
|
||||
}
|
||||
//evaluate results
|
||||
if (!commandCaptureBaseProcessResults(resolvedFacilities, resolvedFaction, resolvedTimer)) {
|
||||
if (usageMessage) {
|
||||
sendResponse(
|
||||
message.copy(messageType = UNK_229, contents = "@CMT_CAPTUREBASE_usage")
|
||||
)
|
||||
} else {
|
||||
val msg = if (facilityError == 1) { "can not contextually determine building target" }
|
||||
else if (facilityError == 2) { s"\'${foundFacilitiesTag.get}\' is not a valid building name" }
|
||||
else if (factionError) { s"\'${foundFactionTag.get}\' is not a valid faction designation" }
|
||||
else if (timerError) { s"\'${foundTimerTag.get}\' is not a valid timer value" }
|
||||
else { "malformed params; check usage" }
|
||||
sendResponse(ChatMsg(UNK_229, wideContents=true, "", s"\\#FF4040ERROR - $msg", None))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def commandCaptureBaseProcessResults(
|
||||
resolvedFacilities: Option[Seq[Building]],
|
||||
resolvedFaction: Option[PlanetSideEmpire.Value],
|
||||
resolvedTimer: Option[Int]
|
||||
): Boolean = {
|
||||
//evaluate results
|
||||
(resolvedFacilities, resolvedFaction, resolvedTimer) match {
|
||||
case (Some(buildings), Some(faction), Some(_)) =>
|
||||
buildings.foreach { building =>
|
||||
|
|
@ -360,19 +373,9 @@ class ChatOperations(
|
|||
//push for map updates again
|
||||
zoneActor ! ZoneActor.ZoneMapUpdate()
|
||||
}
|
||||
true
|
||||
case _ =>
|
||||
if (usageMessage) {
|
||||
sendResponse(
|
||||
message.copy(messageType = UNK_229, contents = "@CMT_CAPTUREBASE_usage")
|
||||
)
|
||||
} else {
|
||||
val msg = if (facilityError == 1) { "can not contextually determine building target" }
|
||||
else if (facilityError == 2) { s"\'${foundFacilitiesTag.get}\' is not a valid building name" }
|
||||
else if (factionError) { s"\'${foundFactionTag.get}\' is not a valid faction designation" }
|
||||
else if (timerError) { s"\'${foundTimerTag.get}\' is not a valid timer value" }
|
||||
else { "malformed params; check usage" }
|
||||
sendResponse(ChatMsg(UNK_229, wideContents=true, "", s"\\#FF4040ERROR - $msg", None))
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -998,10 +1001,17 @@ class ChatOperations(
|
|||
private def captureBaseCurrSoi(
|
||||
session: Session
|
||||
): Iterable[Building] = {
|
||||
val charId = session.player.CharId
|
||||
session.zone.Buildings.values.filter { building =>
|
||||
building.PlayersInSOI.exists(_.CharId == charId)
|
||||
}
|
||||
val player = session.player
|
||||
val positionxy = player.Position.xy
|
||||
session
|
||||
.zone
|
||||
.blockMap
|
||||
.sector(player)
|
||||
.buildingList
|
||||
.filter { building =>
|
||||
val radius = building.Definition.SOIRadius
|
||||
Vector3.DistanceSquared(building.Position.xy, positionxy) < radius * radius
|
||||
}
|
||||
}
|
||||
|
||||
private def captureBaseParamFaction(
|
||||
|
|
@ -1227,7 +1237,7 @@ class ChatOperations(
|
|||
params: Seq[String]
|
||||
): Boolean = {
|
||||
val ourRank = BattleRank.withExperience(session.avatar.bep).value
|
||||
if (!session.account.gm &&
|
||||
if (!avatar.permissions.canGM &&
|
||||
(ourRank <= Config.app.game.promotion.broadcastBattleRank ||
|
||||
ourRank > Config.app.game.promotion.resetBattleRank && ourRank < Config.app.game.promotion.maxBattleRank + 1)) {
|
||||
setBattleRank(session, params, AvatarActor.Progress)
|
||||
|
|
|
|||
|
|
@ -3,11 +3,26 @@ package net.psforever.actors.session.support
|
|||
|
||||
import akka.actor.{ActorContext, ActorRef, Cancellable, typed}
|
||||
import net.psforever.objects.serverobject.containable.Containable
|
||||
import net.psforever.objects.sourcing.PlayerSource
|
||||
import net.psforever.objects.serverobject.doors.Door
|
||||
import net.psforever.objects.serverobject.interior.Sidedness
|
||||
import net.psforever.objects.serverobject.mblocker.Locker
|
||||
import net.psforever.objects.serverobject.resourcesilo.ResourceSilo
|
||||
import net.psforever.objects.serverobject.structures.Building
|
||||
import net.psforever.objects.serverobject.terminals.capture.CaptureTerminal
|
||||
import net.psforever.objects.serverobject.terminals.{MatrixTerminalDefinition, Terminal}
|
||||
import net.psforever.objects.serverobject.tube.SpawnTube
|
||||
import net.psforever.objects.serverobject.turret.FacilityTurret
|
||||
import net.psforever.objects.sourcing.{DeployableSource, PlayerSource, VehicleSource}
|
||||
import net.psforever.objects.vehicles.Utility.InternalTelepad
|
||||
import net.psforever.objects.zones.blockmap.BlockMapEntity
|
||||
import net.psforever.services.RemoverActor
|
||||
import net.psforever.services.local.{LocalAction, LocalServiceMessage}
|
||||
|
||||
import scala.collection.mutable
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
import scala.concurrent.{Future, Promise}
|
||||
import scala.concurrent.duration._
|
||||
import scala.util.Success
|
||||
//
|
||||
import net.psforever.actors.session.{AvatarActor, SessionActor}
|
||||
import net.psforever.login.WorldSession._
|
||||
|
|
@ -170,6 +185,217 @@ class GeneralOperations(
|
|||
private[session] var progressBarUpdate: Cancellable = Default.Cancellable
|
||||
private var charSavedTimer: Cancellable = Default.Cancellable
|
||||
|
||||
def handleDropItem(pkt: DropItemMessage): GeneralOperations.ItemDropState.Behavior = {
|
||||
val DropItemMessage(itemGuid) = pkt
|
||||
(sessionLogic.validObject(itemGuid, decorator = "DropItem"), player.FreeHand.Equipment) match {
|
||||
case (Some(anItem: Equipment), Some(heldItem))
|
||||
if (anItem eq heldItem) && continent.GUID(player.VehicleSeated).nonEmpty =>
|
||||
RemoveOldEquipmentFromInventory(player)(heldItem)
|
||||
GeneralOperations.ItemDropState.Dropped
|
||||
case (Some(anItem: Equipment), Some(heldItem))
|
||||
if anItem eq heldItem =>
|
||||
DropEquipmentFromInventory(player)(heldItem)
|
||||
GeneralOperations.ItemDropState.Dropped
|
||||
case (Some(_), _) =>
|
||||
GeneralOperations.ItemDropState.NotDropped
|
||||
case _ =>
|
||||
GeneralOperations.ItemDropState.NotFound
|
||||
}
|
||||
}
|
||||
|
||||
def handlePickupItem(pkt: PickupItemMessage): GeneralOperations.ItemPickupState.Behavior = {
|
||||
val PickupItemMessage(itemGuid, _, _, _) = pkt
|
||||
sessionLogic.validObject(itemGuid, decorator = "PickupItem") match {
|
||||
case Some(item: Equipment)
|
||||
if player.Fit(item).nonEmpty =>
|
||||
PickUpEquipmentFromGround(player)(item)
|
||||
GeneralOperations.ItemPickupState.PickedUp
|
||||
case Some(_: Equipment) =>
|
||||
GeneralOperations.ItemPickupState.Dropped
|
||||
case _ =>
|
||||
GeneralOperations.ItemPickupState.NotFound
|
||||
}
|
||||
}
|
||||
|
||||
def handleZipLine(pkt: ZipLineMessage): GeneralOperations.ZiplineBehavior.Behavior = {
|
||||
val ZipLineMessage(playerGuid, forwards, action, pathId, pos) = pkt
|
||||
continent.zipLinePaths.find(x => x.PathId == pathId) match {
|
||||
case Some(path) if path.IsTeleporter =>
|
||||
val endPoint = path.ZipLinePoints.last
|
||||
sendResponse(ZipLineMessage(PlanetSideGUID(0), forwards, 0, pathId, pos))
|
||||
//todo: send to zone to show teleport animation to all clients
|
||||
sendResponse(PlayerStateShiftMessage(ShiftState(0, endPoint, (player.Orientation.z + player.FacingYawUpper) % 360f, None)))
|
||||
GeneralOperations.ZiplineBehavior.Teleporter
|
||||
case Some(_) =>
|
||||
//todo: send to zone to show zipline animation to all clients
|
||||
action match {
|
||||
case 0 =>
|
||||
//travel along the zipline in the direction specified
|
||||
sendResponse(ZipLineMessage(playerGuid, forwards, action, pathId, pos))
|
||||
GeneralOperations.ZiplineBehavior.Zipline
|
||||
case 1 =>
|
||||
//disembark from zipline at destination
|
||||
sendResponse(ZipLineMessage(playerGuid, forwards, action, 0, pos))
|
||||
GeneralOperations.ZiplineBehavior.Zipline
|
||||
case 2 =>
|
||||
//get off by force
|
||||
sendResponse(ZipLineMessage(playerGuid, forwards, action, 0, pos))
|
||||
GeneralOperations.ZiplineBehavior.Zipline
|
||||
case _ =>
|
||||
GeneralOperations.ZiplineBehavior.Unsupported
|
||||
}
|
||||
case _ =>
|
||||
GeneralOperations.ZiplineBehavior.NotFound
|
||||
}
|
||||
}
|
||||
|
||||
def handleMoveItem(pkt: MoveItemMessage): Unit = {
|
||||
val MoveItemMessage(itemGuid, sourceGuid, destinationGuid, dest, _) = pkt
|
||||
(
|
||||
continent.GUID(sourceGuid),
|
||||
continent.GUID(destinationGuid),
|
||||
sessionLogic.validObject(itemGuid, decorator = "MoveItem")
|
||||
) match {
|
||||
case (
|
||||
Some(source: PlanetSideServerObject with Container),
|
||||
Some(destination: PlanetSideServerObject with Container),
|
||||
Some(item: Equipment)
|
||||
) =>
|
||||
ContainableMoveItem(player.Name, source, destination, item, destination.SlotMapResolution(dest))
|
||||
case (None, _, _) =>
|
||||
log.error(
|
||||
s"MoveItem: ${player.Name} wanted to move $itemGuid from $sourceGuid, but could not find source object"
|
||||
)
|
||||
case (_, None, _) =>
|
||||
log.error(
|
||||
s"MoveItem: ${player.Name} wanted to move $itemGuid to $destinationGuid, but could not find destination object"
|
||||
)
|
||||
case (_, _, None) => ()
|
||||
case _ =>
|
||||
log.error(
|
||||
s"MoveItem: ${player.Name} wanted to move $itemGuid from $sourceGuid to $destinationGuid, but multiple problems were encountered"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
def handleLootItem(pkt: LootItemMessage): Unit = {
|
||||
val LootItemMessage(itemGuid, targetGuid) = pkt
|
||||
(sessionLogic.validObject(itemGuid, decorator = "LootItem"), continent.GUID(targetGuid)) match {
|
||||
case (Some(item: Equipment), Some(destination: PlanetSideServerObject with Container)) =>
|
||||
//figure out the source
|
||||
(
|
||||
{
|
||||
val findFunc: PlanetSideServerObject with Container => Option[
|
||||
(PlanetSideServerObject with Container, Option[Int])
|
||||
] = findInLocalContainer(itemGuid)
|
||||
findFunc(player.avatar.locker)
|
||||
.orElse(findFunc(player))
|
||||
.orElse(accessedContainer match {
|
||||
case Some(parent: PlanetSideServerObject) =>
|
||||
findFunc(parent)
|
||||
case _ =>
|
||||
None
|
||||
})
|
||||
},
|
||||
destination.Fit(item)
|
||||
) match {
|
||||
case (Some((source, Some(_))), Some(dest)) =>
|
||||
ContainableMoveItem(player.Name, source, destination, item, dest)
|
||||
case (None, _) =>
|
||||
log.error(s"LootItem: ${player.Name} can not find where $item is put currently")
|
||||
case (_, None) =>
|
||||
log.error(s"LootItem: ${player.Name} can not find anywhere to put $item in $destination")
|
||||
case _ =>
|
||||
log.error(
|
||||
s"LootItem: ${player.Name}wanted to move $itemGuid to $targetGuid, but multiple problems were encountered"
|
||||
)
|
||||
}
|
||||
case (Some(obj), _) =>
|
||||
log.error(s"LootItem: item $obj is (probably) not lootable to ${player.Name}")
|
||||
case (None, _) => ()
|
||||
case (_, None) =>
|
||||
log.error(s"LootItem: ${player.Name} can not find where to put $itemGuid")
|
||||
}
|
||||
}
|
||||
|
||||
def handleAvatarImplant(pkt: AvatarImplantMessage): GeneralOperations.ImplantActivationBehavior.Behavior = {
|
||||
val AvatarImplantMessage(_, action, slot, status) = pkt
|
||||
if (action == ImplantAction.Activation) {
|
||||
if (sessionLogic.zoning.zoningStatus == Zoning.Status.Deconstructing) {
|
||||
//do not activate; play deactivation sound instead
|
||||
sessionLogic.zoning.spawn.stopDeconstructing()
|
||||
avatar.implants(slot).collect {
|
||||
case implant if implant.active =>
|
||||
avatarActor ! AvatarActor.DeactivateImplant(implant.definition.implantType)
|
||||
case implant =>
|
||||
sendResponse(PlanetsideAttributeMessage(player.GUID, 28, implant.definition.implantType.value * 2))
|
||||
}
|
||||
GeneralOperations.ImplantActivationBehavior.Failed
|
||||
} else {
|
||||
avatar.implants(slot) match {
|
||||
case Some(implant) =>
|
||||
if (status == 1) {
|
||||
avatarActor ! AvatarActor.ActivateImplant(implant.definition.implantType)
|
||||
GeneralOperations.ImplantActivationBehavior.Activate
|
||||
} else {
|
||||
avatarActor ! AvatarActor.DeactivateImplant(implant.definition.implantType)
|
||||
GeneralOperations.ImplantActivationBehavior.Deactivate
|
||||
}
|
||||
case _ =>
|
||||
GeneralOperations.ImplantActivationBehavior.NotFound
|
||||
}
|
||||
}
|
||||
} else {
|
||||
GeneralOperations.ImplantActivationBehavior.Failed
|
||||
}
|
||||
}
|
||||
|
||||
def handleCreateShortcut(pkt: CreateShortcutMessage): Unit = {
|
||||
val CreateShortcutMessage(_, slot, shortcutOpt) = pkt
|
||||
shortcutOpt match {
|
||||
case Some(shortcut) =>
|
||||
avatarActor ! AvatarActor.AddShortcut(slot - 1, shortcut)
|
||||
case None =>
|
||||
avatarActor ! AvatarActor.RemoveShortcut(slot - 1)
|
||||
}
|
||||
}
|
||||
|
||||
def handleObjectDetected(pkt: ObjectDetectedMessage): Unit = {
|
||||
val ObjectDetectedMessage(_, _, _, targets) = pkt
|
||||
sessionLogic.shooting.FindWeapon.foreach {
|
||||
case weapon if weapon.Projectile.AutoLock =>
|
||||
//projectile with auto-lock instigates a warning on the target
|
||||
val detectedTargets = sessionLogic.shooting.FindDetectedProjectileTargets(targets)
|
||||
val mode = 7 + (if (weapon.Projectile == GlobalDefinitions.wasp_rocket_projectile) 1 else 0)
|
||||
detectedTargets.foreach { target =>
|
||||
continent.AvatarEvents ! AvatarServiceMessage(target, AvatarAction.ProjectileAutoLockAwareness(mode))
|
||||
}
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
|
||||
def handleTargetingImplantRequest(pkt: TargetingImplantRequest): Unit = {
|
||||
val TargetingImplantRequest(list) = pkt
|
||||
val targetInfo: List[TargetInfo] = list.flatMap { x =>
|
||||
continent.GUID(x.target_guid) match {
|
||||
case Some(player: Player) =>
|
||||
val health = player.Health.toFloat / player.MaxHealth
|
||||
val armor = if (player.MaxArmor > 0) {
|
||||
player.Armor.toFloat / player.MaxArmor
|
||||
} else {
|
||||
0
|
||||
}
|
||||
Some(TargetInfo(player.GUID, health, armor))
|
||||
case _ =>
|
||||
log.warn(
|
||||
s"TargetingImplantRequest: the info that ${player.Name} requested for target ${x.target_guid} is not for a player"
|
||||
)
|
||||
None
|
||||
}
|
||||
}
|
||||
sendResponse(TargetingInfoMessage(targetInfo))
|
||||
}
|
||||
|
||||
/**
|
||||
* Enforce constraints on bulk purchases as determined by a given player's previous purchase times and hard acquisition delays.
|
||||
* Intended to assist in sanitizing loadout information from the perspective of the player, or target owner.
|
||||
|
|
@ -560,6 +786,66 @@ class GeneralOperations(
|
|||
parent.Find(objectGuid).flatMap { slot => Some((parent, Some(slot))) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple object searching algorithm that is limited to containers currently known and accessible by the player.
|
||||
* If all relatively local containers are checked and the object is not found,
|
||||
* the player's locker inventory will be checked, and then
|
||||
* the game environment (items on the ground) will be checked too.
|
||||
* If the target object is discovered, it is removed from its current location and is completely destroyed.
|
||||
* @see `RequestDestroyMessage`
|
||||
* @see `Zone.ItemIs.Where`
|
||||
* @param objectGuid the target object's globally unique identifier;
|
||||
* it is not expected that the object will be unregistered, but it is also not gauranteed
|
||||
* @param obj the target object
|
||||
* @return `true`, if the target object was discovered and removed;
|
||||
* `false`, otherwise
|
||||
*/
|
||||
def findEquipmentToDelete(objectGuid: PlanetSideGUID, obj: Equipment): Boolean = {
|
||||
val findFunc
|
||||
: PlanetSideServerObject with Container => Option[(PlanetSideServerObject with Container, Option[Int])] =
|
||||
findInLocalContainer(objectGuid)
|
||||
|
||||
findFunc(player)
|
||||
.orElse(accessedContainer match {
|
||||
case Some(parent: PlanetSideServerObject) =>
|
||||
findFunc(parent)
|
||||
case _ =>
|
||||
None
|
||||
})
|
||||
.orElse(sessionLogic.vehicles.findLocalVehicle match {
|
||||
case Some(parent: PlanetSideServerObject) =>
|
||||
findFunc(parent)
|
||||
case _ =>
|
||||
None
|
||||
}) match {
|
||||
case Some((parent, Some(_))) =>
|
||||
obj.Position = Vector3.Zero
|
||||
RemoveOldEquipmentFromInventory(parent)(obj)
|
||||
true
|
||||
case _ if player.avatar.locker.Inventory.Remove(objectGuid) =>
|
||||
sendResponse(ObjectDeleteMessage(objectGuid, 0))
|
||||
true
|
||||
case _ if continent.EquipmentOnGround.contains(obj) =>
|
||||
obj.Position = Vector3.Zero
|
||||
continent.Ground ! Zone.Ground.RemoveItem(objectGuid)
|
||||
continent.AvatarEvents ! AvatarServiceMessage.Ground(RemoverActor.ClearSpecific(List(obj), continent))
|
||||
true
|
||||
case _ =>
|
||||
Zone.EquipmentIs.Where(obj, objectGuid, continent) match {
|
||||
case None =>
|
||||
true
|
||||
case Some(Zone.EquipmentIs.Orphaned()) if obj.HasGUID =>
|
||||
TaskWorkflow.execute(GUIDTask.unregisterEquipment(continent.GUID, obj))
|
||||
true
|
||||
case Some(Zone.EquipmentIs.Orphaned()) =>
|
||||
true
|
||||
case _ =>
|
||||
log.warn(s"RequestDestroy: equipment $obj exists, but ${player.Name} can not reach it to dispose of it")
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* na
|
||||
* @param targetGuid na
|
||||
|
|
@ -692,10 +978,10 @@ class GeneralOperations(
|
|||
)
|
||||
}
|
||||
|
||||
def administrativeKick(tplayer: Player): Unit = {
|
||||
def administrativeKick(tplayer: Player, time: Option[Long]): Unit = {
|
||||
log.warn(s"${tplayer.Name} has been kicked by ${player.Name}")
|
||||
tplayer.death_by = -1
|
||||
sessionLogic.accountPersistence ! AccountPersistenceService.Kick(tplayer.Name)
|
||||
sessionLogic.accountPersistence ! AccountPersistenceService.Kick(tplayer.Name, time)
|
||||
//get out of that vehicle
|
||||
sessionLogic.vehicles.GetMountableAndSeat(None, tplayer, continent) match {
|
||||
case (Some(obj), Some(seatNum)) =>
|
||||
|
|
@ -767,6 +1053,460 @@ class GeneralOperations(
|
|||
)
|
||||
}
|
||||
|
||||
def handleDeployObject(
|
||||
zone: Zone,
|
||||
deployableType: DeployedItem.Value,
|
||||
position: Vector3,
|
||||
orientation: Vector3,
|
||||
side: Sidedness,
|
||||
faction: PlanetSideEmpire.Value,
|
||||
owner: Player,
|
||||
builtWith: ConstructionItem
|
||||
): Future[Deployable] = {
|
||||
val (deployableEntity, tasking) = commonHandleDeployObjectSetup(zone, deployableType, position, orientation, side, faction)
|
||||
deployableEntity.AssignOwnership(owner)
|
||||
val promisedDeployable: Promise[Deployable] = Promise()
|
||||
//execute
|
||||
val result = TaskWorkflow.execute(CallBackForTask(
|
||||
tasking,
|
||||
zone.Deployables,
|
||||
Zone.Deployable.BuildByOwner(deployableEntity, owner, builtWith),
|
||||
context.self
|
||||
))
|
||||
result.onComplete {
|
||||
case Success(_) => promisedDeployable.success(deployableEntity)
|
||||
case _ => ()
|
||||
}
|
||||
promisedDeployable.future
|
||||
}
|
||||
|
||||
def handleDeployObject(
|
||||
zone: Zone,
|
||||
deployableType: DeployedItem.Value,
|
||||
position: Vector3,
|
||||
orientation: Vector3,
|
||||
side: Sidedness,
|
||||
faction: PlanetSideEmpire.Value
|
||||
): Future[Deployable] = {
|
||||
val (deployableEntity, tasking) = commonHandleDeployObjectSetup(zone, deployableType, position, orientation, side, faction)
|
||||
val promisedDeployable: Promise[Deployable] = Promise()
|
||||
//execute
|
||||
val result = TaskWorkflow.execute(CallBackForTask(
|
||||
tasking,
|
||||
zone.Deployables,
|
||||
Zone.Deployable.Build(deployableEntity),
|
||||
context.self
|
||||
))
|
||||
result.onComplete {
|
||||
case Success(_) =>
|
||||
Players.buildCooldownReset(zone, player.Name, deployableEntity.GUID)
|
||||
deployableEntity.Actor ! Deployable.Deconstruct(Some(20.minutes))
|
||||
if (deployableType == DeployedItem.boomer) {
|
||||
val trigger = new BoomerTrigger
|
||||
trigger.Companion = deployableEntity.GUID
|
||||
deployableEntity.asInstanceOf[BoomerDeployable].Trigger = trigger
|
||||
TaskWorkflow.execute(CallBackForTask(
|
||||
GUIDTask.registerEquipment(zone.GUID, trigger),
|
||||
zone.Ground,
|
||||
Zone.Ground.DropItem(trigger, position + Vector3.z(value = 0.5f), Vector3.z(orientation.z))
|
||||
))
|
||||
}
|
||||
promisedDeployable.success(deployableEntity)
|
||||
case _ => ()
|
||||
}
|
||||
promisedDeployable.future
|
||||
}
|
||||
|
||||
def handleUseDoor(door: Door, equipment: Option[Equipment]): Unit = {
|
||||
equipment match {
|
||||
case Some(tool: Tool) if tool.Definition == GlobalDefinitions.medicalapplicator =>
|
||||
val distance: Float = math.max(
|
||||
Config.app.game.doorsCanBeOpenedByMedAppFromThisDistance,
|
||||
door.Definition.initialOpeningDistance
|
||||
)
|
||||
door.Actor ! CommonMessages.Use(player, Some(distance))
|
||||
case _ =>
|
||||
door.Actor ! CommonMessages.Use(player)
|
||||
}
|
||||
}
|
||||
|
||||
def handleUseResourceSilo(resourceSilo: ResourceSilo, equipment: Option[Equipment]): Unit = {
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
|
||||
val vehicleOpt = continent.GUID(player.avatar.vehicle)
|
||||
(vehicleOpt, equipment) match {
|
||||
case (Some(vehicle: Vehicle), Some(item))
|
||||
if GlobalDefinitions.isBattleFrameVehicle(vehicle.Definition) &&
|
||||
GlobalDefinitions.isBattleFrameNTUSiphon(item.Definition) =>
|
||||
resourceSilo.Actor ! CommonMessages.Use(player, Some(vehicle))
|
||||
case (Some(vehicle: Vehicle), _)
|
||||
if vehicle.Definition == GlobalDefinitions.ant &&
|
||||
vehicle.DeploymentState == DriveState.Deployed &&
|
||||
Vector3.DistanceSquared(resourceSilo.Position.xy, vehicle.Position.xy) < math.pow(resourceSilo.Definition.UseRadius, 2) =>
|
||||
resourceSilo.Actor ! CommonMessages.Use(player, Some(vehicle))
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
|
||||
def handleUsePlayer(obj: Player, equipment: Option[Equipment], msg: UseItemMessage): Unit = {
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
|
||||
if (obj.isBackpack) {
|
||||
if (equipment.isEmpty) {
|
||||
log.info(s"${player.Name} is looting the corpse of ${obj.Name}")
|
||||
sendResponse(msg)
|
||||
accessContainer(obj)
|
||||
}
|
||||
} else if (!msg.unk3 && player.isAlive) { //potential kit use
|
||||
(continent.GUID(msg.item_used_guid), kitToBeUsed) match {
|
||||
case (Some(kit: Kit), None) =>
|
||||
kitToBeUsed = Some(msg.item_used_guid)
|
||||
player.Actor ! CommonMessages.Use(player, Some(kit))
|
||||
case (Some(_: Kit), Some(_)) | (None, Some(_)) =>
|
||||
//a kit is already queued to be used; ignore this request
|
||||
sendResponse(ChatMsg(ChatMessageType.UNK_225, wideContents=false, "", "Please wait ...", None))
|
||||
case (Some(item), _) =>
|
||||
log.error(s"UseItem: ${player.Name} looking for Kit to use, but found $item instead")
|
||||
case (None, None) =>
|
||||
log.warn(s"UseItem: anticipated a Kit ${msg.item_used_guid} for ${player.Name}, but can't find it") }
|
||||
} else if (msg.object_id == ObjectClass.avatar && msg.unk3) {
|
||||
equipment match {
|
||||
case Some(tool: Tool) if tool.Definition == GlobalDefinitions.bank =>
|
||||
obj.Actor ! CommonMessages.Use(player, equipment)
|
||||
|
||||
case Some(tool: Tool) if tool.Definition == GlobalDefinitions.medicalapplicator =>
|
||||
obj.Actor ! CommonMessages.Use(player, equipment)
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def handleUseLocker(locker: Locker, equipment: Option[Equipment], msg: UseItemMessage): Unit = {
|
||||
equipment match {
|
||||
case Some(item) =>
|
||||
sendUseGeneralEntityMessage(locker, item)
|
||||
case None if locker.Faction == player.Faction || locker.HackedBy.nonEmpty =>
|
||||
log.info(s"${player.Name} is accessing a locker")
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
|
||||
val playerLocker = player.avatar.locker
|
||||
sendResponse(msg.copy(object_guid = playerLocker.GUID, object_id = 456))
|
||||
accessContainer(playerLocker)
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
|
||||
def handleUseCaptureTerminal(captureTerminal: CaptureTerminal, equipment: Option[Equipment]): Unit = {
|
||||
equipment match {
|
||||
case Some(item) =>
|
||||
sendUseGeneralEntityMessage(captureTerminal, item)
|
||||
case _ if specialItemSlotGuid.nonEmpty =>
|
||||
continent.GUID(specialItemSlotGuid) match {
|
||||
case Some(llu: CaptureFlag) =>
|
||||
if (llu.Target.GUID == captureTerminal.Owner.GUID) {
|
||||
continent.LocalEvents ! LocalServiceMessage(continent.id, LocalAction.LluCaptured(llu))
|
||||
} else {
|
||||
log.info(
|
||||
s"LLU target is not this base. Target GUID: ${llu.Target.GUID} This base: ${captureTerminal.Owner.GUID}"
|
||||
)
|
||||
}
|
||||
case _ => log.warn("Item in specialItemSlotGuid is not registered with continent or is not a LLU")
|
||||
}
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
|
||||
def handleUseFacilityTurret(obj: FacilityTurret, equipment: Option[Equipment], msg: UseItemMessage): Unit = {
|
||||
equipment.foreach { item =>
|
||||
sendUseGeneralEntityMessage(obj, item)
|
||||
obj.Actor ! CommonMessages.Use(player, Some((item, msg.unk2.toInt))) //try upgrade path
|
||||
}
|
||||
}
|
||||
|
||||
def handleUseVehicle(obj: Vehicle, equipment: Option[Equipment], msg: UseItemMessage): Unit = {
|
||||
equipment match {
|
||||
case Some(item) =>
|
||||
sendUseGeneralEntityMessage(obj, item)
|
||||
case None if player.Faction == obj.Faction =>
|
||||
//access to trunk
|
||||
if (
|
||||
obj.AccessingTrunk.isEmpty &&
|
||||
(!obj.PermissionGroup(AccessPermissionGroup.Trunk.id).contains(VehicleLockState.Locked) || obj.OwnerGuid
|
||||
.contains(player.GUID))
|
||||
) {
|
||||
log.info(s"${player.Name} is looking in the ${obj.Definition.Name}'s trunk")
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
|
||||
obj.AccessingTrunk = player.GUID
|
||||
accessContainer(obj)
|
||||
sendResponse(msg)
|
||||
}
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
|
||||
def handleUseTerminal(terminal: Terminal, equipment: Option[Equipment], msg: UseItemMessage): Unit = {
|
||||
equipment match {
|
||||
case Some(item) =>
|
||||
sendUseGeneralEntityMessage(terminal, item)
|
||||
case None
|
||||
if terminal.Owner == Building.NoBuilding || terminal.Faction == player.Faction ||
|
||||
terminal.HackedBy.nonEmpty || terminal.Faction == PlanetSideEmpire.NEUTRAL =>
|
||||
val tdef = terminal.Definition
|
||||
if (tdef.isInstanceOf[MatrixTerminalDefinition]) {
|
||||
//TODO matrix spawn point; for now, just blindly bind to show work (and hope nothing breaks)
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
|
||||
sendResponse(
|
||||
BindPlayerMessage(BindStatus.Bind, "", display_icon=true, logging=true, SpawnGroup.Sanctuary, 0, 0, terminal.Position)
|
||||
)
|
||||
} else if (
|
||||
tdef == GlobalDefinitions.multivehicle_rearm_terminal || tdef == GlobalDefinitions.bfr_rearm_terminal ||
|
||||
tdef == GlobalDefinitions.air_rearm_terminal || tdef == GlobalDefinitions.ground_rearm_terminal
|
||||
) {
|
||||
sessionLogic.vehicles.findLocalVehicle match {
|
||||
case Some(vehicle) =>
|
||||
log.info(
|
||||
s"${player.Name} is accessing a ${terminal.Definition.Name} for ${player.Sex.possessive} ${vehicle.Definition.Name}"
|
||||
)
|
||||
sendResponse(msg)
|
||||
sendResponse(msg.copy(object_guid = vehicle.GUID, object_id = vehicle.Definition.ObjectId))
|
||||
case None =>
|
||||
log.error(s"UseItem: Expecting a seated vehicle, ${player.Name} found none")
|
||||
}
|
||||
} else if (tdef == GlobalDefinitions.teleportpad_terminal) {
|
||||
//explicit request
|
||||
log.info(s"${player.Name} is purchasing a router telepad")
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
|
||||
terminal.Actor ! Terminal.Request(
|
||||
player,
|
||||
ItemTransactionMessage(msg.object_guid, TransactionType.Buy, 0, "router_telepad", 0, PlanetSideGUID(0))
|
||||
)
|
||||
} else if (tdef == GlobalDefinitions.targeting_laser_dispenser) {
|
||||
//explicit request
|
||||
log.info(s"${player.Name} is purchasing a targeting laser")
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
|
||||
terminal.Actor ! Terminal.Request(
|
||||
player,
|
||||
ItemTransactionMessage(msg.object_guid, TransactionType.Buy, 0, "flail_targeting_laser", 0, PlanetSideGUID(0))
|
||||
)
|
||||
} else {
|
||||
log.info(s"${player.Name} is accessing a ${terminal.Definition.Name}")
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
|
||||
sendResponse(msg)
|
||||
}
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
|
||||
def handleUseSpawnTube(obj: SpawnTube, equipment: Option[Equipment]): Unit = {
|
||||
equipment match {
|
||||
case Some(item) =>
|
||||
sendUseGeneralEntityMessage(obj, item)
|
||||
case None if player.Faction == obj.Faction =>
|
||||
//deconstruction
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
|
||||
sessionLogic.actionsToCancel()
|
||||
sessionLogic.terminals.CancelAllProximityUnits()
|
||||
sessionLogic.zoning.spawn.startDeconstructing(obj)
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
|
||||
def handleUseTelepadDeployable(
|
||||
obj: TelepadDeployable,
|
||||
equipment: Option[Equipment],
|
||||
msg: UseItemMessage,
|
||||
useTelepadFunc: (Vehicle, InternalTelepad, TelepadDeployable, PlanetSideGameObject with TelepadLike, PlanetSideGameObject with TelepadLike) => Unit
|
||||
): Unit = {
|
||||
if (equipment.isEmpty) {
|
||||
(continent.GUID(obj.Router) match {
|
||||
case Some(vehicle: Vehicle) => Some((vehicle, vehicle.Utility(UtilityType.internal_router_telepad_deployable)))
|
||||
case Some(vehicle) => Some(vehicle, None)
|
||||
case None => None
|
||||
}) match {
|
||||
case Some((vehicle: Vehicle, Some(util: Utility.InternalTelepad))) =>
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel")
|
||||
player.WhichSide = vehicle.WhichSide
|
||||
useTelepadFunc(vehicle, util, obj, obj, util)
|
||||
case Some((vehicle: Vehicle, None)) =>
|
||||
log.error(
|
||||
s"telepad@${msg.object_guid.guid} is not linked to a router - ${vehicle.Definition.Name}"
|
||||
)
|
||||
case Some((o, _)) =>
|
||||
log.error(
|
||||
s"telepad@${msg.object_guid.guid} is linked to wrong kind of object - ${o.Definition.Name}, ${obj.Router}"
|
||||
)
|
||||
obj.Actor ! Deployable.Deconstruct()
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def handleUseInternalTelepad(
|
||||
obj: InternalTelepad,
|
||||
msg: UseItemMessage,
|
||||
useTelepadFunc: (Vehicle, InternalTelepad, TelepadDeployable, PlanetSideGameObject with TelepadLike, PlanetSideGameObject with TelepadLike) => Unit
|
||||
): Unit = {
|
||||
continent.GUID(obj.Telepad) match {
|
||||
case Some(pad: TelepadDeployable) =>
|
||||
player.WhichSide = pad.WhichSide
|
||||
useTelepadFunc(obj.Owner.asInstanceOf[Vehicle], obj, pad, obj, pad)
|
||||
case Some(o) =>
|
||||
log.error(
|
||||
s"internal telepad@${msg.object_guid.guid} is not linked to a remote telepad - ${o.Definition.Name}@${o.GUID.guid}"
|
||||
)
|
||||
case None => ()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A player uses a fully-linked Router teleportation system.
|
||||
* @param router the Router vehicle
|
||||
* @param internalTelepad the internal telepad within the Router vehicle
|
||||
* @param remoteTelepad the remote telepad that is currently associated with this Router
|
||||
* @param src the origin of the teleportation (where the player starts)
|
||||
* @param dest the destination of the teleportation (where the player is going)
|
||||
*/
|
||||
def useRouterTelepadSystem(
|
||||
router: Vehicle,
|
||||
internalTelepad: InternalTelepad,
|
||||
remoteTelepad: TelepadDeployable,
|
||||
src: PlanetSideGameObject with TelepadLike,
|
||||
dest: PlanetSideGameObject with TelepadLike
|
||||
): Unit = {
|
||||
val time = System.currentTimeMillis()
|
||||
if (
|
||||
time - recentTeleportAttempt > 2000L && router.DeploymentState == DriveState.Deployed &&
|
||||
internalTelepad.Active &&
|
||||
remoteTelepad.Active
|
||||
) {
|
||||
val pguid = player.GUID
|
||||
val sguid = src.GUID
|
||||
val dguid = dest.GUID
|
||||
sendResponse(PlayerStateShiftMessage(ShiftState(0, dest.Position, player.Orientation.z)))
|
||||
useRouterTelepadEffect(pguid, sguid, dguid)
|
||||
continent.LocalEvents ! LocalServiceMessage(
|
||||
continent.id,
|
||||
LocalAction.RouterTelepadTransport(pguid, pguid, sguid, dguid)
|
||||
)
|
||||
player.Position = dest.Position
|
||||
player.LogActivity(TelepadUseActivity(VehicleSource(router), DeployableSource(remoteTelepad), PlayerSource(player)))
|
||||
} else {
|
||||
log.warn(s"UseRouterTelepadSystem: ${player.Name} can not teleport")
|
||||
}
|
||||
recentTeleportAttempt = time
|
||||
}
|
||||
|
||||
/**
|
||||
* A player uses a fully-linked Router teleportation system.
|
||||
* @param router the Router vehicle
|
||||
* @param internalTelepad the internal telepad within the Router vehicle
|
||||
* @param remoteTelepad the remote telepad that is currently associated with this Router
|
||||
* @param src the origin of the teleportation (where the player starts)
|
||||
* @param dest the destination of the teleportation (where the player is going)
|
||||
*/
|
||||
def useRouterTelepadSystemSecretly(
|
||||
router: Vehicle,
|
||||
internalTelepad: InternalTelepad,
|
||||
remoteTelepad: TelepadDeployable,
|
||||
src: PlanetSideGameObject with TelepadLike,
|
||||
dest: PlanetSideGameObject with TelepadLike
|
||||
): Unit = {
|
||||
val time = System.currentTimeMillis()
|
||||
if (
|
||||
time - recentTeleportAttempt > 2000L && router.DeploymentState == DriveState.Deployed &&
|
||||
internalTelepad.Active &&
|
||||
remoteTelepad.Active
|
||||
) {
|
||||
val pguid = player.GUID
|
||||
val sguid = src.GUID
|
||||
val dguid = dest.GUID
|
||||
sendResponse(PlayerStateShiftMessage(ShiftState(0, dest.Position, player.Orientation.z)))
|
||||
useRouterTelepadEffect(pguid, sguid, dguid)
|
||||
player.Position = dest.Position
|
||||
} else {
|
||||
log.warn(s"UseRouterTelepadSystem: ${player.Name} can not teleport")
|
||||
}
|
||||
recentTeleportAttempt = time
|
||||
}
|
||||
|
||||
def handleUseCaptureFlag(obj: CaptureFlag): Unit = {
|
||||
// LLU can normally only be picked up the faction that owns it
|
||||
specialItemSlotGuid match {
|
||||
case None if obj.Faction == player.Faction =>
|
||||
specialItemSlotGuid = Some(obj.GUID)
|
||||
player.Carrying = SpecialCarry.CaptureFlag
|
||||
continent.LocalEvents ! CaptureFlagManager.PickupFlag(obj, player)
|
||||
case None =>
|
||||
log.warn(s"${player.Faction} player ${player.toString} tried to pick up a ${obj.Faction} LLU - ${obj.GUID}")
|
||||
case Some(guid) if guid != obj.GUID =>
|
||||
// Ignore duplicate pickup requests
|
||||
log.warn(
|
||||
s"${player.Faction} player ${player.toString} tried to pick up a ${obj.Faction} LLU, but their special slot already contains $guid"
|
||||
)
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
|
||||
def handleUseWarpGate(equipment: Option[Equipment]): Unit = {
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
|
||||
(continent.GUID(player.VehicleSeated), equipment) match {
|
||||
case (Some(vehicle: Vehicle), Some(item))
|
||||
if GlobalDefinitions.isBattleFrameVehicle(vehicle.Definition) &&
|
||||
GlobalDefinitions.isBattleFrameNTUSiphon(item.Definition) =>
|
||||
vehicle.Actor ! CommonMessages.Use(player, equipment)
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
|
||||
def handleUseGeneralEntity(obj: PlanetSideServerObject, equipment: Option[Equipment]): Unit = {
|
||||
equipment.foreach { item =>
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
|
||||
obj.Actor ! CommonMessages.Use(player, Some(item))
|
||||
}
|
||||
}
|
||||
|
||||
def sendUseGeneralEntityMessage(obj: PlanetSideServerObject, equipment: Equipment): Unit = {
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
|
||||
obj.Actor ! CommonMessages.Use(player, Some(equipment))
|
||||
}
|
||||
|
||||
def handleUseDefaultEntity(obj: PlanetSideGameObject, equipment: Option[Equipment]): Unit = {
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
|
||||
equipment match {
|
||||
case Some(item)
|
||||
if GlobalDefinitions.isBattleFrameArmorSiphon(item.Definition) ||
|
||||
GlobalDefinitions.isBattleFrameNTUSiphon(item.Definition) => ()
|
||||
case _ =>
|
||||
log.warn(s"UseItem: ${player.Name} does not know how to handle $obj")
|
||||
}
|
||||
}
|
||||
|
||||
def commonFacilityShieldCharging(obj: PlanetSideServerObject with BlockMapEntity): Unit = {
|
||||
obj.Actor ! CommonMessages.ChargeShields(
|
||||
15,
|
||||
Some(continent.blockMap.sector(obj).buildingList.maxBy(_.Definition.SOIRadius))
|
||||
)
|
||||
}
|
||||
|
||||
private def commonHandleDeployObjectSetup(
|
||||
zone: Zone,
|
||||
deployableType: DeployedItem.Value,
|
||||
position: Vector3,
|
||||
orientation: Vector3,
|
||||
side: Sidedness,
|
||||
faction: PlanetSideEmpire.Value
|
||||
): (Deployable, TaskBundle) = {
|
||||
val deployableEntity: Deployable = Deployables.Make(deployableType)()
|
||||
deployableEntity.Position = position
|
||||
deployableEntity.Orientation = orientation
|
||||
deployableEntity.WhichSide = side
|
||||
deployableEntity.Faction = faction
|
||||
val tasking: TaskBundle = deployableEntity match {
|
||||
case turret: TurretDeployable =>
|
||||
GUIDTask.registerDeployableTurret(zone.GUID, turret)
|
||||
case _ =>
|
||||
GUIDTask.registerObject(zone.GUID, deployableEntity)
|
||||
}
|
||||
(deployableEntity, tasking)
|
||||
}
|
||||
|
||||
override protected[session] def actionsToCancel(): Unit = {
|
||||
progressBarValue = None
|
||||
kitToBeUsed = None
|
||||
|
|
@ -786,11 +1526,12 @@ class GeneralOperations(
|
|||
}
|
||||
}
|
||||
|
||||
case Some(o) if player.isAlive =>
|
||||
unaccessContainer(o)
|
||||
sendResponse(UnuseItemMessage(player.GUID, o.GUID))
|
||||
|
||||
case Some(o) =>
|
||||
unaccessContainer(o)
|
||||
if (player.isAlive) {
|
||||
sendResponse(UnuseItemMessage(player.GUID, o.GUID))
|
||||
}
|
||||
|
||||
case None => ()
|
||||
}
|
||||
|
|
@ -801,3 +1542,42 @@ class GeneralOperations(
|
|||
charSavedTimer.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
object GeneralOperations {
|
||||
object UseItem {
|
||||
sealed trait Behavior
|
||||
case object Handled extends Behavior
|
||||
case object HandledPassive extends Behavior
|
||||
case object Unhandled extends Behavior
|
||||
}
|
||||
|
||||
object ItemDropState {
|
||||
sealed trait Behavior
|
||||
case object Dropped extends Behavior
|
||||
case object NotDropped extends Behavior
|
||||
case object NotFound extends Behavior
|
||||
}
|
||||
|
||||
object ItemPickupState {
|
||||
sealed trait Behavior
|
||||
case object PickedUp extends Behavior
|
||||
case object Dropped extends Behavior
|
||||
case object NotFound extends Behavior
|
||||
}
|
||||
|
||||
object ZiplineBehavior {
|
||||
sealed trait Behavior
|
||||
case object Teleporter extends Behavior
|
||||
case object Zipline extends Behavior
|
||||
case object Unsupported extends Behavior
|
||||
case object NotFound extends Behavior
|
||||
}
|
||||
|
||||
object ImplantActivationBehavior {
|
||||
sealed trait Behavior
|
||||
case object Activate extends Behavior
|
||||
case object Deactivate extends Behavior
|
||||
case object Failed extends Behavior
|
||||
case object NotFound extends Behavior
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,9 +2,13 @@
|
|||
package net.psforever.actors.session.support
|
||||
|
||||
import akka.actor.{ActorContext, typed}
|
||||
import net.psforever.objects.serverobject.mount.Mountable
|
||||
import net.psforever.objects.{Default, PlanetSideGameObject}
|
||||
import net.psforever.objects.sourcing.{PlayerSource, SourceEntry}
|
||||
import net.psforever.packet.game.objectcreate.ConstructorData
|
||||
import net.psforever.objects.zones.exp
|
||||
import net.psforever.services.Service
|
||||
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage, AvatarServiceResponse}
|
||||
|
||||
import scala.collection.mutable
|
||||
//
|
||||
|
|
@ -154,6 +158,45 @@ class SessionAvatarHandlers(
|
|||
victimSeated
|
||||
)
|
||||
}
|
||||
|
||||
def revive(revivalTargetGuid: PlanetSideGUID): Unit = {
|
||||
val spawn = sessionLogic.zoning.spawn
|
||||
spawn.reviveTimer.cancel()
|
||||
spawn.reviveTimer = Default.Cancellable
|
||||
spawn.respawnTimer.cancel()
|
||||
spawn.respawnTimer = Default.Cancellable
|
||||
player.Revive
|
||||
val health = player.Health
|
||||
sendResponse(PlanetsideAttributeMessage(revivalTargetGuid, attribute_type=0, health))
|
||||
sendResponse(AvatarDeadStateMessage(DeadState.Alive, timer_max=0, timer=0, player.Position, player.Faction, unk5=true))
|
||||
continent.AvatarEvents ! AvatarServiceMessage(
|
||||
continent.id,
|
||||
AvatarAction.PlanetsideAttributeToAll(revivalTargetGuid, attribute_type=0, health)
|
||||
)
|
||||
}
|
||||
|
||||
def killedWhileMounted(obj: PlanetSideGameObject with Mountable, playerGuid: PlanetSideGUID): Unit = {
|
||||
val playerName = player.Name
|
||||
//boot cadaver from mount on client
|
||||
context.self ! AvatarServiceResponse(
|
||||
playerName,
|
||||
Service.defaultPlayerGUID,
|
||||
AvatarResponse.SendResponse(
|
||||
ObjectDetachMessage(obj.GUID, playerGuid, player.Position, Vector3.Zero)
|
||||
)
|
||||
)
|
||||
//player no longer seated
|
||||
obj.PassengerInSeat(player).foreach { seatNumber =>
|
||||
//boot cadaver from mount internally (vehicle perspective)
|
||||
obj.Seats(seatNumber).unmount(player)
|
||||
//inform client-specific logic
|
||||
context.self ! Mountable.MountMessages(
|
||||
player,
|
||||
Mountable.CanDismount(obj, seatNumber, 0)
|
||||
)
|
||||
}
|
||||
player.VehicleSeated = None
|
||||
}
|
||||
}
|
||||
|
||||
object SessionAvatarHandlers {
|
||||
|
|
|
|||
|
|
@ -89,6 +89,7 @@ class SessionData(
|
|||
private[session] var persistFunc: () => Unit = noPersistence
|
||||
private[session] var persist: () => Unit = updatePersistenceOnly
|
||||
private[session] var keepAliveFunc: () => Unit = keepAlivePersistenceInitial
|
||||
private[session] var keepAlivePersistenceFunc: () => Unit = keepAlivePersistence
|
||||
private[session] var turnCounterFunc: PlanetSideGUID => Unit = SessionData.NoTurnCounterYet
|
||||
private[session] val oldRefsMap: mutable.HashMap[PlanetSideGUID, String] = new mutable.HashMap[PlanetSideGUID, String]()
|
||||
private var contextSafeEntity: PlanetSideGUID = PlanetSideGUID(0)
|
||||
|
|
@ -471,7 +472,7 @@ class SessionData(
|
|||
def keepAlivePersistenceInitial(): Unit = {
|
||||
persist()
|
||||
if (player != null && player.HasGUID) {
|
||||
keepAliveFunc = keepAlivePersistence
|
||||
keepAliveFunc = keepAlivePersistenceFunc
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,9 +6,9 @@ import net.psforever.objects.{Players, TurretDeployable}
|
|||
import net.psforever.objects.ce.Deployable
|
||||
import net.psforever.objects.guid.{GUIDTask, TaskWorkflow}
|
||||
import net.psforever.objects.serverobject.interior.Sidedness
|
||||
import net.psforever.packet.game.GenericObjectActionMessage
|
||||
import net.psforever.packet.game.{GenericObjectActionMessage, ObjectDeleteMessage, PlanetsideAttributeMessage, TriggerEffectMessage}
|
||||
import net.psforever.services.local.LocalResponse
|
||||
import net.psforever.types.PlanetSideGUID
|
||||
import net.psforever.types.{PlanetSideGUID, Vector3}
|
||||
|
||||
trait LocalHandlerFunctions extends CommonSessionInterfacingFunctionality {
|
||||
def ops: SessionLocalHandlers
|
||||
|
|
@ -30,12 +30,12 @@ class SessionLocalHandlers(
|
|||
}
|
||||
|
||||
def handleTurretDeployableIsDismissed(obj: TurretDeployable): Unit = {
|
||||
Players.buildCooldownReset(continent, player.Name, obj)
|
||||
Players.buildCooldownReset(continent, player.Name, obj.GUID)
|
||||
TaskWorkflow.execute(GUIDTask.unregisterDeployableTurret(continent.GUID, obj))
|
||||
}
|
||||
|
||||
def handleDeployableIsDismissed(obj: Deployable): Unit = {
|
||||
Players.buildCooldownReset(continent, player.Name, obj)
|
||||
Players.buildCooldownReset(continent, player.Name, obj.GUID)
|
||||
TaskWorkflow.execute(GUIDTask.unregisterObject(continent.GUID, obj))
|
||||
}
|
||||
|
||||
|
|
@ -47,4 +47,26 @@ class SessionLocalHandlers(
|
|||
else
|
||||
400f
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Common behavior for deconstructing deployables in the game environment.
|
||||
* @param obj the deployable
|
||||
* @param guid the globally unique identifier for the deployable
|
||||
* @param pos the previous position of the deployable
|
||||
* @param orient the previous orientation of the deployable
|
||||
* @param deletionType the value passed to `ObjectDeleteMessage` concerning the deconstruction animation
|
||||
*/
|
||||
def DeconstructDeployable(
|
||||
obj: Deployable,
|
||||
guid: PlanetSideGUID,
|
||||
pos: Vector3,
|
||||
orient: Vector3,
|
||||
deletionType: Int
|
||||
): Unit = {
|
||||
sendResponse(TriggerEffectMessage("spawn_object_failed_effect", pos, orient))
|
||||
sendResponse(PlanetsideAttributeMessage(guid, 29, 1)) //make deployable vanish
|
||||
sendResponse(ObjectDeleteMessage(guid, deletionType))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,15 +2,21 @@
|
|||
package net.psforever.actors.session.support
|
||||
|
||||
import akka.actor.{ActorContext, typed}
|
||||
import net.psforever.objects.Tool
|
||||
import net.psforever.objects.vehicles.MountableWeapons
|
||||
import net.psforever.packet.game.{DismountVehicleCargoMsg, InventoryStateMessage, MountVehicleCargoMsg, MountVehicleMsg}
|
||||
import net.psforever.objects.serverobject.affinity.FactionAffinity
|
||||
import net.psforever.objects.{PlanetSideGameObject, Tool, Vehicle}
|
||||
import net.psforever.objects.vehicles.{CargoBehavior, MountableWeapons}
|
||||
import net.psforever.objects.vital.InGameHistory
|
||||
import net.psforever.packet.game.{DismountVehicleCargoMsg, InventoryStateMessage, MountVehicleCargoMsg, MountVehicleMsg, ObjectAttachMessage}
|
||||
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
|
||||
import net.psforever.types.{BailType, PlanetSideGUID, Vector3}
|
||||
//
|
||||
import net.psforever.actors.session.AvatarActor
|
||||
import net.psforever.objects.Player
|
||||
import net.psforever.objects.serverobject.mount.Mountable
|
||||
import net.psforever.packet.game.DismountVehicleMsg
|
||||
|
||||
import scala.concurrent.duration._
|
||||
|
||||
trait MountHandlerFunctions extends CommonSessionInterfacingFunctionality {
|
||||
val ops: SessionMountHandlers
|
||||
|
||||
|
|
@ -30,6 +36,218 @@ class SessionMountHandlers(
|
|||
val avatarActor: typed.ActorRef[AvatarActor.Command],
|
||||
implicit val context: ActorContext
|
||||
) extends CommonSessionInterfacingFunctionality {
|
||||
def handleMountVehicle(pkt: MountVehicleMsg): Unit = {
|
||||
val MountVehicleMsg(_, mountable_guid, entry_point) = pkt
|
||||
sessionLogic.validObject(mountable_guid, decorator = "MountVehicle").collect {
|
||||
case obj: Mountable =>
|
||||
obj.Actor ! Mountable.TryMount(player, entry_point)
|
||||
case _ =>
|
||||
log.error(s"MountVehicleMsg: object ${mountable_guid.guid} not a mountable thing, ${player.Name}")
|
||||
}
|
||||
}
|
||||
|
||||
def handleDismountVehicle(pkt: DismountVehicleMsg): Unit = {
|
||||
val DismountVehicleMsg(player_guid, bailType, wasKickedByDriver) = pkt
|
||||
val dError: (String, Player)=> Unit = dismountError(bailType, wasKickedByDriver)
|
||||
//TODO optimize this later
|
||||
//common warning for this section
|
||||
if (player.GUID == player_guid) {
|
||||
//normally disembarking from a mount
|
||||
(sessionLogic.zoning.interstellarFerry.orElse(continent.GUID(player.VehicleSeated)) match {
|
||||
case out @ Some(obj: Vehicle) =>
|
||||
continent.GUID(obj.MountedIn) match {
|
||||
case Some(_: Vehicle) => None //cargo vehicle
|
||||
case _ => out //arrangement "may" be permissible
|
||||
}
|
||||
case out @ Some(_: Mountable) =>
|
||||
out
|
||||
case _ =>
|
||||
dError(s"DismountVehicleMsg: player ${player.Name} not considered seated in a mountable entity", player)
|
||||
None
|
||||
}) match {
|
||||
case Some(obj: Mountable) =>
|
||||
obj.PassengerInSeat(player) match {
|
||||
case Some(seat_num) =>
|
||||
obj.Actor ! Mountable.TryDismount(player, seat_num, bailType)
|
||||
//short-circuit the temporary channel for transferring between zones, the player is no longer doing that
|
||||
sessionLogic.zoning.interstellarFerry = None
|
||||
|
||||
case None =>
|
||||
dError(s"DismountVehicleMsg: can not find where player ${player.Name}_guid is seated in mountable ${player.VehicleSeated}", player)
|
||||
}
|
||||
case _ =>
|
||||
dError(s"DismountVehicleMsg: can not find mountable entity ${player.VehicleSeated}", player)
|
||||
}
|
||||
} else {
|
||||
//kicking someone else out of a mount; need to own that mount/mountable
|
||||
val dWarn: (String, Player)=> Unit = dismountWarning(bailType, wasKickedByDriver)
|
||||
player.avatar.vehicle match {
|
||||
case Some(obj_guid) =>
|
||||
(
|
||||
(
|
||||
sessionLogic.validObject(obj_guid, decorator = "DismountVehicle/Vehicle"),
|
||||
sessionLogic.validObject(player_guid, decorator = "DismountVehicle/Player")
|
||||
) match {
|
||||
case (vehicle @ Some(obj: Vehicle), tplayer) =>
|
||||
if (obj.MountedIn.isEmpty) (vehicle, tplayer) else (None, None)
|
||||
case (mount @ Some(_: Mountable), tplayer) =>
|
||||
(mount, tplayer)
|
||||
case _ =>
|
||||
(None, None)
|
||||
}) match {
|
||||
case (Some(obj: Mountable), Some(tplayer: Player)) =>
|
||||
obj.PassengerInSeat(tplayer) match {
|
||||
case Some(seat_num) =>
|
||||
obj.Actor ! Mountable.TryDismount(tplayer, seat_num, bailType)
|
||||
case None =>
|
||||
dError(s"DismountVehicleMsg: can not find where other player ${tplayer.Name} is seated in mountable $obj_guid", tplayer)
|
||||
}
|
||||
case (None, _) =>
|
||||
dWarn(s"DismountVehicleMsg: ${player.Name} can not find his vehicle", player)
|
||||
case (_, None) =>
|
||||
dWarn(s"DismountVehicleMsg: player $player_guid could not be found to kick, ${player.Name}", player)
|
||||
case _ =>
|
||||
dWarn(s"DismountVehicleMsg: object is either not a Mountable or not a Player", player)
|
||||
}
|
||||
case None =>
|
||||
dWarn(s"DismountVehicleMsg: ${player.Name} does not own a vehicle", player)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def handleMountVehicleCargo(pkt: MountVehicleCargoMsg): Unit = {
|
||||
val MountVehicleCargoMsg(_, cargo_guid, carrier_guid, _) = pkt
|
||||
(continent.GUID(cargo_guid), continent.GUID(carrier_guid)) match {
|
||||
case (Some(cargo: Vehicle), Some(carrier: Vehicle)) =>
|
||||
carrier.CargoHolds.find({ case (_, hold) => !hold.isOccupied }) match {
|
||||
case Some((mountPoint, _)) =>
|
||||
cargo.Actor ! CargoBehavior.StartCargoMounting(carrier_guid, mountPoint)
|
||||
case _ =>
|
||||
log.warn(
|
||||
s"MountVehicleCargoMsg: ${player.Name} trying to load cargo into a ${carrier.Definition.Name} which oes not have a cargo hold"
|
||||
)
|
||||
}
|
||||
case (None, _) | (Some(_), None) =>
|
||||
log.warn(
|
||||
s"MountVehicleCargoMsg: ${player.Name} lost a vehicle while working with cargo - either $carrier_guid or $cargo_guid"
|
||||
)
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
|
||||
def handleDismountVehicleCargo(pkt: DismountVehicleCargoMsg): Unit = {
|
||||
val DismountVehicleCargoMsg(_, cargo_guid, bailed, _, kicked) = pkt
|
||||
continent.GUID(cargo_guid) match {
|
||||
case Some(cargo: Vehicle) =>
|
||||
cargo.Actor ! CargoBehavior.StartCargoDismounting(bailed || kicked)
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
|
||||
private def dismountWarning(
|
||||
bailAs: BailType.Value,
|
||||
kickedByDriver: Boolean
|
||||
)
|
||||
(
|
||||
note: String,
|
||||
player: Player
|
||||
): Unit = {
|
||||
log.warn(note)
|
||||
player.VehicleSeated = None
|
||||
sendResponse(DismountVehicleMsg(player.GUID, bailAs, kickedByDriver))
|
||||
}
|
||||
|
||||
private def dismountError(
|
||||
bailAs: BailType.Value,
|
||||
kickedByDriver: Boolean
|
||||
)
|
||||
(
|
||||
note: String,
|
||||
player: Player
|
||||
): Unit = {
|
||||
log.error(s"$note; some vehicle might not know that ${player.Name} is no longer sitting in it")
|
||||
player.VehicleSeated = None
|
||||
sendResponse(DismountVehicleMsg(player.GUID, bailAs, kickedByDriver))
|
||||
}
|
||||
|
||||
/**
|
||||
* Common activities/procedure when a player mounts a valid object.
|
||||
* @param tplayer the player
|
||||
* @param obj the mountable object
|
||||
* @param seatNum the mount into which the player is mounting
|
||||
*/
|
||||
def MountingAction(tplayer: Player, obj: PlanetSideGameObject with FactionAffinity with InGameHistory, seatNum: Int): Unit = {
|
||||
val playerGuid: PlanetSideGUID = tplayer.GUID
|
||||
val objGuid: PlanetSideGUID = obj.GUID
|
||||
sessionLogic.actionsToCancel()
|
||||
avatarActor ! AvatarActor.DeactivateActiveImplants
|
||||
avatarActor ! AvatarActor.SuspendStaminaRegeneration(3.seconds)
|
||||
sendResponse(ObjectAttachMessage(objGuid, playerGuid, seatNum))
|
||||
continent.VehicleEvents ! VehicleServiceMessage(
|
||||
continent.id,
|
||||
VehicleAction.MountVehicle(playerGuid, objGuid, seatNum)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Common activities/procedure when a player dismounts a valid mountable object.
|
||||
* @param tplayer the player
|
||||
* @param obj the mountable object
|
||||
* @param seatNum the mount out of which which the player is disembarking
|
||||
*/
|
||||
def DismountVehicleAction(tplayer: Player, obj: PlanetSideGameObject with FactionAffinity with InGameHistory, seatNum: Int): Unit = {
|
||||
DismountAction(tplayer, obj, seatNum)
|
||||
//until vehicles maintain synchronized momentum without a driver
|
||||
obj match {
|
||||
case v: Vehicle
|
||||
if seatNum == 0 && Vector3.MagnitudeSquared(v.Velocity.getOrElse(Vector3.Zero)) > 0f =>
|
||||
sessionLogic.vehicles.serverVehicleControlVelocity.collect { _ =>
|
||||
sessionLogic.vehicles.ServerVehicleOverrideStop(v)
|
||||
}
|
||||
v.Velocity = Vector3.Zero
|
||||
continent.VehicleEvents ! VehicleServiceMessage(
|
||||
continent.id,
|
||||
VehicleAction.VehicleState(
|
||||
tplayer.GUID,
|
||||
v.GUID,
|
||||
unk1 = 0,
|
||||
v.Position,
|
||||
v.Orientation,
|
||||
vel = None,
|
||||
v.Flying,
|
||||
unk3 = 0,
|
||||
unk4 = 0,
|
||||
wheel_direction = 15,
|
||||
unk5 = false,
|
||||
unk6 = v.Cloaked
|
||||
)
|
||||
)
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Common activities/procedure when a player dismounts a valid mountable object.
|
||||
* @param tplayer the player
|
||||
* @param obj the mountable object
|
||||
* @param seatNum the mount out of which which the player is disembarking
|
||||
*/
|
||||
def DismountAction(tplayer: Player, obj: PlanetSideGameObject with FactionAffinity with InGameHistory, seatNum: Int): Unit = {
|
||||
val playerGuid: PlanetSideGUID = tplayer.GUID
|
||||
tplayer.ContributionFrom(obj)
|
||||
sessionLogic.keepAliveFunc = sessionLogic.zoning.NormalKeepAlive
|
||||
val bailType = if (tplayer.BailProtection) {
|
||||
BailType.Bailed
|
||||
} else {
|
||||
BailType.Normal
|
||||
}
|
||||
sendResponse(DismountVehicleMsg(playerGuid, bailType, wasKickedByDriver = false))
|
||||
continent.VehicleEvents ! VehicleServiceMessage(
|
||||
continent.id,
|
||||
VehicleAction.DismountVehicle(playerGuid, bailType, unk2 = false)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* From a mount, find the weapon controlled from it, and update the ammunition counts for that weapon's magazines.
|
||||
* @param objWithSeat the object that owns seats (and weaponry)
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID, Vector3}
|
|||
trait SquadHandlerFunctions extends CommonSessionInterfacingFunctionality {
|
||||
val ops: SessionSquadHandlers
|
||||
|
||||
protected var waypointCooldown: Long = 0L
|
||||
|
||||
def handleSquadDefinitionAction(pkt: SquadDefinitionActionMessage): Unit
|
||||
|
||||
def handleSquadMemberRequest(pkt: SquadMembershipRequest): Unit
|
||||
|
|
|
|||
|
|
@ -2,8 +2,12 @@
|
|||
package net.psforever.actors.session.support
|
||||
|
||||
import akka.actor.{ActorContext, typed}
|
||||
import net.psforever.objects.guid.GUIDTask
|
||||
import net.psforever.packet.game.FavoritesRequest
|
||||
import net.psforever.objects.guid.{GUIDTask, TaskWorkflow}
|
||||
import net.psforever.objects.inventory.InventoryItem
|
||||
import net.psforever.objects.sourcing.AmenitySource
|
||||
import net.psforever.objects.vital.TerminalUsedActivity
|
||||
import net.psforever.packet.game.{FavoritesAction, FavoritesRequest, ItemTransactionResultMessage, UnuseItemMessage}
|
||||
import net.psforever.types.{TransactionType, Vector3}
|
||||
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
import scala.concurrent.Future
|
||||
|
|
@ -39,6 +43,99 @@ class SessionTerminalHandlers(
|
|||
private[session] var lastTerminalOrderFulfillment: Boolean = true
|
||||
private[session] var usingMedicalTerminal: Option[PlanetSideGUID] = None
|
||||
|
||||
def handleItemTransaction(pkt: ItemTransactionMessage): Unit = {
|
||||
val ItemTransactionMessage(terminalGuid, transactionType, _, itemName, _, _) = pkt
|
||||
continent.GUID(terminalGuid) match {
|
||||
case Some(term: Terminal) if lastTerminalOrderFulfillment =>
|
||||
val msg: String = if (itemName.nonEmpty) s" of $itemName" else ""
|
||||
log.info(s"${player.Name} is submitting an order - a $transactionType from a ${term.Definition.Name}$msg")
|
||||
lastTerminalOrderFulfillment = false
|
||||
term.Actor ! Terminal.Request(player, pkt)
|
||||
case Some(_: Terminal) =>
|
||||
log.warn(s"Please Wait until your previous order has been fulfilled, ${player.Name}")
|
||||
case Some(obj) =>
|
||||
log.error(s"ItemTransaction: ${obj.Definition.Name} is not a terminal, ${player.Name}")
|
||||
case _ =>
|
||||
log.error(s"ItemTransaction: entity with guid=${terminalGuid.guid} does not exist, ${player.Name}")
|
||||
}
|
||||
}
|
||||
|
||||
def handleProximityTerminalUse(pkt: ProximityTerminalUseMessage): Unit = {
|
||||
val ProximityTerminalUseMessage(_, objectGuid, _) = pkt
|
||||
continent.GUID(objectGuid) match {
|
||||
case Some(obj: Terminal with ProximityUnit) =>
|
||||
performProximityTerminalUse(obj)
|
||||
case Some(obj) =>
|
||||
log.warn(s"ProximityTerminalUse: ${obj.Definition.Name} guid=${objectGuid.guid} is not ready to implement proximity effects")
|
||||
case None =>
|
||||
log.error(s"ProximityTerminalUse: ${player.Name} can not find an object with guid ${objectGuid.guid}")
|
||||
}
|
||||
}
|
||||
|
||||
def handleFavoritesRequest(pkt: FavoritesRequest): Unit = {
|
||||
val FavoritesRequest(_, loadoutType, action, line, label) = pkt
|
||||
action match {
|
||||
case FavoritesAction.Save =>
|
||||
avatarActor ! AvatarActor.SaveLoadout(player, loadoutType, label, line)
|
||||
case FavoritesAction.Delete =>
|
||||
avatarActor ! AvatarActor.DeleteLoadout(player, loadoutType, line)
|
||||
case FavoritesAction.Unknown =>
|
||||
log.warn(s"FavoritesRequest: ${player.Name} requested an unknown favorites action")
|
||||
}
|
||||
}
|
||||
|
||||
def buyVehicle(
|
||||
terminalGuid: PlanetSideGUID,
|
||||
transactionType: TransactionType.Value,
|
||||
vehicle: Vehicle,
|
||||
weapons: List[InventoryItem],
|
||||
trunk: List[InventoryItem]
|
||||
): Option[Vehicle] = {
|
||||
continent.map.terminalToSpawnPad
|
||||
.find { case (termid, _) => termid == terminalGuid.guid }
|
||||
.map { case (a: Int, b: Int) => (continent.GUID(a), continent.GUID(b)) }
|
||||
.collect { case (Some(term: Terminal), Some(pad: VehicleSpawnPad)) =>
|
||||
vehicle.Faction = player.Faction
|
||||
vehicle.Position = pad.Position
|
||||
vehicle.Orientation = pad.Orientation + Vector3.z(pad.Definition.VehicleCreationZOrientOffset)
|
||||
//default loadout, weapons
|
||||
val vWeapons = vehicle.Weapons
|
||||
weapons.foreach { entry =>
|
||||
vWeapons.get(entry.start) match {
|
||||
case Some(slot) =>
|
||||
entry.obj.Faction = player.Faction
|
||||
slot.Equipment = None
|
||||
slot.Equipment = entry.obj
|
||||
case None =>
|
||||
log.warn(
|
||||
s"BuyVehicle: ${player.Name} tries to apply default loadout to $vehicle on spawn, but can not find a mounted weapon for ${entry.start}"
|
||||
)
|
||||
}
|
||||
}
|
||||
//default loadout, trunk
|
||||
val vTrunk = vehicle.Trunk
|
||||
vTrunk.Clear()
|
||||
trunk.foreach { entry =>
|
||||
entry.obj.Faction = player.Faction
|
||||
vTrunk.InsertQuickly(entry.start, entry.obj)
|
||||
}
|
||||
TaskWorkflow.execute(registerVehicleFromSpawnPad(vehicle, pad, term))
|
||||
sendResponse(ItemTransactionResultMessage(terminalGuid, TransactionType.Buy, success = true))
|
||||
if (GlobalDefinitions.isBattleFrameVehicle(vehicle.Definition)) {
|
||||
sendResponse(UnuseItemMessage(player.GUID, terminalGuid))
|
||||
}
|
||||
player.LogActivity(TerminalUsedActivity(AmenitySource(term), transactionType))
|
||||
vehicle
|
||||
}
|
||||
.orElse {
|
||||
log.error(
|
||||
s"${player.Name} wanted to spawn a vehicle, but there was no spawn pad associated with terminal ${terminalGuid.guid} to accept it"
|
||||
)
|
||||
sendResponse(ItemTransactionResultMessage(terminalGuid, TransactionType.Buy, success = false))
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct tasking that adds a completed and registered vehicle into the scene.
|
||||
* The major difference between `RegisterVehicle` and `RegisterVehicleFromSpawnPad` is the assumption that this vehicle lacks an internal `Actor`.
|
||||
|
|
@ -92,7 +189,7 @@ class SessionTerminalHandlers(
|
|||
* na
|
||||
* @param terminal na
|
||||
*/
|
||||
def HandleProximityTerminalUse(terminal: Terminal with ProximityUnit): Unit = {
|
||||
def performProximityTerminalUse(terminal: Terminal with ProximityUnit): Unit = {
|
||||
val term_guid = terminal.GUID
|
||||
val targets = FindProximityUnitTargetsInScope(terminal)
|
||||
val currentTargets = terminal.Targets
|
||||
|
|
|
|||
|
|
@ -39,6 +39,18 @@ class VehicleOperations(
|
|||
) extends CommonSessionInterfacingFunctionality {
|
||||
private[session] var serverVehicleControlVelocity: Option[Int] = None
|
||||
|
||||
/**
|
||||
* Get the current `Vehicle` object that the player is riding/driving.
|
||||
* The vehicle must be found solely through use of `player.VehicleSeated`.
|
||||
* @return the vehicle
|
||||
*/
|
||||
def findLocalVehicle: Option[Vehicle] = {
|
||||
continent.GUID(player.VehicleSeated) match {
|
||||
case Some(obj: Vehicle) => Some(obj)
|
||||
case _ => None
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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`.
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -6,7 +6,6 @@ import akka.actor.typed.scaladsl.adapter._
|
|||
import akka.actor.{ActorContext, ActorRef, Cancellable, typed}
|
||||
import akka.pattern.ask
|
||||
import akka.util.Timeout
|
||||
import net.psforever.actors.session.spectator.SpectatorMode
|
||||
import net.psforever.login.WorldSession
|
||||
import net.psforever.objects.avatar.{BattleRank, DeployableToolbox}
|
||||
import net.psforever.objects.avatar.scoring.{CampaignStatistics, ScoreCard, SessionStatistics}
|
||||
|
|
@ -18,6 +17,7 @@ import net.psforever.objects.serverobject.tube.SpawnTube
|
|||
import net.psforever.objects.serverobject.turret.auto.AutomatedTurret
|
||||
import net.psforever.objects.sourcing.{PlayerSource, SourceEntry, VehicleSource}
|
||||
import net.psforever.objects.vital.{InGameHistory, IncarnationActivity, ReconstructionActivity, SpawningActivity}
|
||||
import net.psforever.objects.zones.blockmap.BlockMapEntity
|
||||
import net.psforever.packet.game.{CampaignStatistic, ChangeFireStateMessage_Start, HackState7, MailMessage, ObjectDetectedMessage, SessionStatistic, TriggeredSound}
|
||||
import net.psforever.services.chat.DefaultChannel
|
||||
|
||||
|
|
@ -159,6 +159,30 @@ object ZoningOperations {
|
|||
events ! LocalServiceMessage(target, soundMessage)
|
||||
}
|
||||
}
|
||||
|
||||
def findBuildingsBySoiOccupancy(zone: Zone, obj: PlanetSideGameObject with BlockMapEntity): List[Building] = {
|
||||
val positionxy = obj.Position.xy
|
||||
zone
|
||||
.blockMap
|
||||
.sector(obj)
|
||||
.buildingList
|
||||
.filter { building =>
|
||||
val radius = building.Definition.SOIRadius
|
||||
Vector3.DistanceSquared(building.Position.xy, positionxy) < radius * radius
|
||||
}
|
||||
}
|
||||
|
||||
def findBuildingsBySoiOccupancy(zone: Zone, position: Vector3): List[Building] = {
|
||||
val positionxy = position.xy
|
||||
zone
|
||||
.blockMap
|
||||
.sector(positionxy, range=5)
|
||||
.buildingList
|
||||
.filter { building =>
|
||||
val radius = building.Definition.SOIRadius
|
||||
Vector3.DistanceSquared(building.Position.xy, positionxy) < radius * radius
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ZoningOperations(
|
||||
|
|
@ -193,6 +217,9 @@ class ZoningOperations(
|
|||
/** a flag that forces the current zone to reload itself during a zoning operation */
|
||||
private[session] var zoneReload: Boolean = false
|
||||
private[session] val spawn: SpawnOperations = new SpawnOperations()
|
||||
private[session] var maintainInitialGmState: Boolean = false
|
||||
|
||||
private[session] var zoneChannel: String = Zone.Nowhere.id
|
||||
|
||||
private var loadConfZone: Boolean = false
|
||||
private var instantActionFallbackDestination: Option[Zoning.InstantAction.Located] = None
|
||||
|
|
@ -200,6 +227,7 @@ class ZoningOperations(
|
|||
private var zoningChatMessageType: ChatMessageType = ChatMessageType.CMT_QUIT
|
||||
private var zoningCounter: Int = 0
|
||||
private var zoningTimer: Cancellable = Default.Cancellable
|
||||
var displayZoningMessageWhenCancelled: Boolean = true
|
||||
|
||||
/* packets */
|
||||
|
||||
|
|
@ -609,6 +637,7 @@ class ZoningOperations(
|
|||
def handleZoneResponse(foundZone: Zone): Unit = {
|
||||
log.trace(s"ZoneResponse: zone ${foundZone.id} will now load for ${player.Name}")
|
||||
loadConfZone = true
|
||||
maintainInitialGmState = true
|
||||
val oldZone = session.zone
|
||||
session = session.copy(zone = foundZone)
|
||||
sessionLogic.persist()
|
||||
|
|
@ -864,12 +893,7 @@ class ZoningOperations(
|
|||
val location = if (Zones.sanctuaryZoneNumber(player.Faction) == continent.Number) {
|
||||
Zoning.Time.Sanctuary
|
||||
} else {
|
||||
val playerPosition = player.Position.xy
|
||||
continent.Buildings.values
|
||||
.filter { building =>
|
||||
val radius = building.Definition.SOIRadius
|
||||
Vector3.DistanceSquared(building.Position.xy, playerPosition) < radius * radius
|
||||
} match {
|
||||
ZoningOperations.findBuildingsBySoiOccupancy(continent, player.Position) match {
|
||||
case Nil =>
|
||||
Zoning.Time.None
|
||||
case List(building: FactionAffinity) =>
|
||||
|
|
@ -903,7 +927,7 @@ class ZoningOperations(
|
|||
* defaults to `None`
|
||||
*/
|
||||
def CancelZoningProcessWithReason(msg: String, msgType: Option[ChatMessageType] = None): Unit = {
|
||||
if (zoningStatus != Zoning.Status.None) {
|
||||
if (displayZoningMessageWhenCancelled && zoningStatus != Zoning.Status.None) {
|
||||
sendResponse(ChatMsg(msgType.getOrElse(zoningChatMessageType), wideContents=false, "", msg, None))
|
||||
}
|
||||
CancelZoningProcess()
|
||||
|
|
@ -2128,6 +2152,7 @@ class ZoningOperations(
|
|||
val map = zone.map
|
||||
val mapName = map.name
|
||||
log.info(s"${tplayer.Name} has spawned into $id")
|
||||
sessionLogic.zoning.zoneChannel = Players.ZoneChannelIfSpectating(tplayer, zone.id)
|
||||
sessionLogic.oldRefsMap.clear()
|
||||
sessionLogic.persist = UpdatePersistenceAndRefs
|
||||
tplayer.avatar = avatar
|
||||
|
|
@ -2137,6 +2162,7 @@ class ZoningOperations(
|
|||
sendResponse(LoadMapMessage(mapName, id, 40100, 25, weaponsEnabled, map.checksum))
|
||||
if (isAcceptableNextSpawnPoint) {
|
||||
//important! the LoadMapMessage must be processed by the client before the avatar is created
|
||||
player.allowInteraction = true
|
||||
setupAvatarFunc()
|
||||
//interimUngunnedVehicle should have been setup by setupAvatarFunc, if it is applicable
|
||||
sessionLogic.turnCounterFunc = interimUngunnedVehicle match {
|
||||
|
|
@ -2174,6 +2200,7 @@ class ZoningOperations(
|
|||
session = session.copy(player = tplayer)
|
||||
if (isAcceptableNextSpawnPoint) {
|
||||
//try this spawn point
|
||||
player.allowInteraction = true
|
||||
setupAvatarFunc()
|
||||
//interimUngunnedVehicle should have been setup by setupAvatarFunc, if it is applicable
|
||||
sessionLogic.turnCounterFunc = interimUngunnedVehicle match {
|
||||
|
|
@ -2294,7 +2321,7 @@ class ZoningOperations(
|
|||
* @param zone na
|
||||
*/
|
||||
def HandleReleaseAvatar(tplayer: Player, zone: Zone): Unit = {
|
||||
sessionLogic.keepAliveFunc = sessionLogic.keepAlivePersistence
|
||||
sessionLogic.keepAliveFunc = sessionLogic.keepAlivePersistenceFunc
|
||||
tplayer.Release
|
||||
tplayer.VehicleSeated match {
|
||||
case None =>
|
||||
|
|
@ -2439,6 +2466,16 @@ class ZoningOperations(
|
|||
log.debug(s"AvatarCreate (vehicle): ${player.Name}'s ${vdef.Name}")
|
||||
AvatarCreateInVehicle(player, vehicle, seat)
|
||||
|
||||
case _ if player.spectator =>
|
||||
player.VehicleSeated = None
|
||||
val definition = player.avatar.definition
|
||||
val guid = player.GUID
|
||||
sendResponse(OCM.detailed(player))
|
||||
continent.AvatarEvents ! AvatarServiceMessage(
|
||||
s"spectator",
|
||||
AvatarAction.LoadPlayer(guid, definition.ObjectId, guid, definition.Packet.ConstructorData(player).get, None)
|
||||
)
|
||||
|
||||
case _ =>
|
||||
player.VehicleSeated = None
|
||||
val definition = player.avatar.definition
|
||||
|
|
@ -2587,8 +2624,6 @@ class ZoningOperations(
|
|||
zones.exp.ToDatabase.reportRespawns(tplayer.CharId, ScoreCard.reviveCount(player.avatar.scorecard.CurrentLife))
|
||||
val obj = Player.Respawn(tplayer)
|
||||
DefinitionUtil.applyDefaultLoadout(obj)
|
||||
obj.death_by = tplayer.death_by
|
||||
obj.silenced = tplayer.silenced
|
||||
obj
|
||||
}
|
||||
|
||||
|
|
@ -2866,7 +2901,6 @@ class ZoningOperations(
|
|||
)
|
||||
)
|
||||
nextSpawnPoint = physSpawnPoint
|
||||
prevSpawnPoint = physSpawnPoint
|
||||
shiftPosition = Some(pos)
|
||||
shiftOrientation = Some(ori)
|
||||
val toZoneNumber = if (continent.id.equals(zoneId)) {
|
||||
|
|
@ -3012,10 +3046,11 @@ class ZoningOperations(
|
|||
sessionLogic.keepAliveFunc = sessionLogic.vehicles.GetMountableAndSeat(None, player, continent) match {
|
||||
case (Some(v: Vehicle), Some(seatNumber))
|
||||
if seatNumber > 0 && v.WeaponControlledFromSeat(seatNumber).isEmpty =>
|
||||
sessionLogic.keepAlivePersistence
|
||||
sessionLogic.keepAlivePersistenceFunc
|
||||
case _ =>
|
||||
NormalKeepAlive
|
||||
}
|
||||
prevSpawnPoint = nextSpawnPoint
|
||||
nextSpawnPoint = None
|
||||
}
|
||||
//if not the condition above, player has started playing normally
|
||||
|
|
@ -3214,10 +3249,10 @@ class ZoningOperations(
|
|||
upstreamMessageCount = 0
|
||||
if (tplayer.spectator) {
|
||||
if (!setAvatar) {
|
||||
context.self ! SessionActor.SetMode(SpectatorMode) //should reload spectator status
|
||||
context.self ! SessionActor.SetMode(sessionLogic.chat.CurrentSpectatorMode) //should reload spectator status
|
||||
}
|
||||
} else if (
|
||||
!account.gm && /* gm's are excluded */
|
||||
!avatar.permissions.canGM && /* gm's are excluded */
|
||||
Config.app.game.promotion.active && /* play versus progress system must be active */
|
||||
BattleRank.withExperience(tplayer.avatar.bep).value <= Config.app.game.promotion.broadcastBattleRank && /* must be below a certain battle rank */
|
||||
tavatar.scorecard.Lives.isEmpty && /* first life after login */
|
||||
|
|
@ -3464,7 +3499,7 @@ class ZoningOperations(
|
|||
//sit down
|
||||
sendResponse(ObjectAttachMessage(vguid, pguid, seat))
|
||||
sessionLogic.general.accessContainer(vehicle)
|
||||
sessionLogic.keepAliveFunc = sessionLogic.keepAlivePersistence
|
||||
sessionLogic.keepAliveFunc = sessionLogic.keepAlivePersistenceFunc
|
||||
case _ => ()
|
||||
//we can't find a vehicle? and we're still here? that's bad
|
||||
player.VehicleSeated = None
|
||||
|
|
|
|||
|
|
@ -1067,6 +1067,13 @@ object WorldSession {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a task and, upon completion, dispatch a message.
|
||||
* @param task task to be completed first
|
||||
* @param sendTo where to send the message
|
||||
* @param pass message
|
||||
* @return a `TaskBundle` object
|
||||
*/
|
||||
def CallBackForTask(task: TaskBundle, sendTo: ActorRef, pass: Any): TaskBundle = {
|
||||
TaskBundle(
|
||||
new StraightforwardTask() {
|
||||
|
|
@ -1085,6 +1092,14 @@ object WorldSession {
|
|||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a task and, upon completion, dispatch a message whose origin is specific and different from normal.
|
||||
* @param task task to be completed first
|
||||
* @param sendTo where to send the message
|
||||
* @param pass message
|
||||
* @param replyTo whom to attribute the message being passed (e.g., `tell`)
|
||||
* @return a `TaskBundle` object
|
||||
*/
|
||||
def CallBackForTask(task: TaskBundle, sendTo: ActorRef, pass: Any, replyTo: ActorRef): TaskBundle = {
|
||||
TaskBundle(
|
||||
new StraightforwardTask() {
|
||||
|
|
|
|||
|
|
@ -81,6 +81,7 @@ class Player(var avatar: Avatar)
|
|||
Continent = "home2" //the zone id
|
||||
|
||||
var spectator: Boolean = false
|
||||
var bops: Boolean = false
|
||||
var silenced: Boolean = false
|
||||
var death_by: Int = 0
|
||||
var lastShotSeq_time: Int = -1
|
||||
|
|
@ -634,13 +635,16 @@ object Player {
|
|||
player.Inventory.Resize(eSuit.InventoryScale.Width, eSuit.InventoryScale.Height)
|
||||
player.Inventory.Offset = eSuit.InventoryOffset
|
||||
//holsters
|
||||
(0 until 5).foreach(index => { player.Slot(index).Size = eSuit.Holster(index) })
|
||||
(0 until 5).foreach { index => player.Slot(index).Size = eSuit.Holster(index) }
|
||||
}
|
||||
|
||||
def Respawn(player: Player): Player = {
|
||||
if (player.Release) {
|
||||
val obj = new Player(player.avatar)
|
||||
obj.Continent = player.Continent
|
||||
obj.death_by = player.death_by
|
||||
obj.silenced = player.silenced
|
||||
obj.allowInteraction = player.allowInteraction
|
||||
obj.avatar.scorecard.respawn()
|
||||
obj
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import net.psforever.objects.vital.projectile.ProjectileReason
|
|||
import net.psforever.objects.vital.{InGameActivity, InGameHistory, RevivingActivity}
|
||||
import net.psforever.objects.zones.Zone
|
||||
import net.psforever.packet.game._
|
||||
import net.psforever.types.{ChatMessageType, ExoSuitType, Vector3}
|
||||
import net.psforever.types.{ChatMessageType, ExoSuitType, PlanetSideGUID, Vector3}
|
||||
import net.psforever.services.Service
|
||||
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||
import net.psforever.services.local.{LocalAction, LocalServiceMessage}
|
||||
|
|
@ -327,7 +327,7 @@ object Players {
|
|||
*/
|
||||
def successfulBuildActivity(zone: Zone, channel: String, obj: Deployable): Unit = {
|
||||
//sent to avatar event bus to preempt additional tool management
|
||||
buildCooldownReset(zone, channel, obj)
|
||||
buildCooldownReset(zone, channel, obj.GUID)
|
||||
//sent to local event bus to cooperate with deployable management
|
||||
zone.LocalEvents ! LocalServiceMessage(
|
||||
channel,
|
||||
|
|
@ -339,13 +339,13 @@ object Players {
|
|||
* Common actions related to constructing a new `Deployable` object in the game environment.
|
||||
* @param zone in which zone these messages apply
|
||||
* @param channel to whom to send the messages
|
||||
* @param obj the `Deployable` object
|
||||
* @param guid `Deployable` object
|
||||
*/
|
||||
def buildCooldownReset(zone: Zone, channel: String, obj: Deployable): Unit = {
|
||||
def buildCooldownReset(zone: Zone, channel: String, guid: PlanetSideGUID): Unit = {
|
||||
//sent to avatar event bus to preempt additional tool management
|
||||
zone.AvatarEvents ! AvatarServiceMessage(
|
||||
channel,
|
||||
AvatarAction.SendResponse(Service.defaultPlayerGUID, GenericObjectActionMessage(obj.GUID, 21))
|
||||
AvatarAction.SendResponse(Service.defaultPlayerGUID, GenericObjectActionMessage(guid, 21))
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -488,4 +488,35 @@ object Players {
|
|||
}
|
||||
player.HistoryAndContributions()
|
||||
}
|
||||
|
||||
/**
|
||||
* Select the player's zone channel.
|
||||
* If the player is spectating, then that is their channel instead.
|
||||
* The resulting channel name should never be used for subscribing - only for publishing.
|
||||
* @param player player in a zone
|
||||
* @return channel name
|
||||
*/
|
||||
def ZoneChannelIfSpectating(player: Player): String = {
|
||||
if (player.spectator) {
|
||||
"spectator"
|
||||
} else {
|
||||
player.Zone.id
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Select the player's zone channel.
|
||||
* If the player is spectating, then that is their channel instead.
|
||||
* The resulting channel name should never be used for subscribing - only for publishing.
|
||||
* @param player player in a zone
|
||||
* @param zoneid custom zone name to be used as the channel name
|
||||
* @return channel name
|
||||
*/
|
||||
def ZoneChannelIfSpectating(player: Player, zoneid: String): String = {
|
||||
if (player.spectator) {
|
||||
"spectator"
|
||||
} else {
|
||||
zoneid
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package net.psforever.objects.avatar
|
|||
|
||||
import akka.actor.{Actor, ActorRef, Props, typed}
|
||||
import net.psforever.actors.session.AvatarActor
|
||||
import net.psforever.actors.zone.ZoneActor
|
||||
import net.psforever.login.WorldSession.{DropEquipmentFromInventory, HoldNewEquipmentUp, PutNewEquipmentInInventoryOrDrop, RemoveOldEquipmentFromInventory}
|
||||
import net.psforever.objects._
|
||||
import net.psforever.objects.ce.Deployable
|
||||
|
|
@ -18,7 +17,6 @@ import net.psforever.objects.serverobject.containable.{Containable, ContainableB
|
|||
import net.psforever.objects.serverobject.damage.Damageable.Target
|
||||
import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject}
|
||||
import net.psforever.objects.serverobject.damage.{AggravatedBehavior, Damageable, DamageableEntity}
|
||||
import net.psforever.objects.serverobject.mount.Mountable
|
||||
import net.psforever.objects.serverobject.terminals.Terminal
|
||||
import net.psforever.objects.vital._
|
||||
import net.psforever.objects.vital.resolution.ResolutionCalculations.Output
|
||||
|
|
@ -35,7 +33,7 @@ import net.psforever.objects.serverobject.repair.Repairable
|
|||
import net.psforever.objects.sourcing.{AmenitySource, PlayerSource}
|
||||
import net.psforever.objects.vital.collision.CollisionReason
|
||||
import net.psforever.objects.vital.etc.{PainboxReason, SuicideReason}
|
||||
import net.psforever.objects.vital.interaction.{Adversarial, DamageInteraction, DamageResult}
|
||||
import net.psforever.objects.vital.interaction.{DamageInteraction, DamageResult}
|
||||
import net.psforever.packet.PlanetSideGamePacket
|
||||
|
||||
import scala.concurrent.duration._
|
||||
|
|
@ -320,7 +318,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
|
|||
} else if ((!resistance && before != slot && (player.DrawnSlot = slot) != before) && ItemSwapSlot != before) {
|
||||
val mySlot = if (updateMyHolsterArm) slot else -1 //use as a short-circuit
|
||||
events ! AvatarServiceMessage(
|
||||
player.Continent,
|
||||
Players.ZoneChannelIfSpectating(player),
|
||||
AvatarAction.ObjectHeld(player.GUID, mySlot, player.LastDrawnSlot)
|
||||
)
|
||||
val isHolsters = player.VisibleSlots.contains(slot)
|
||||
|
|
@ -332,11 +330,11 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
|
|||
if (unholsteredItem.Definition == GlobalDefinitions.remote_electronics_kit) {
|
||||
//rek beam/icon colour must match the player's correct hack level
|
||||
events ! AvatarServiceMessage(
|
||||
player.Continent,
|
||||
Players.ZoneChannelIfSpectating(player),
|
||||
AvatarAction.PlanetsideAttribute(unholsteredItem.GUID, 116, player.avatar.hackingSkillLevel())
|
||||
)
|
||||
}
|
||||
case None => ;
|
||||
case None => ()
|
||||
}
|
||||
} else {
|
||||
equipment match {
|
||||
|
|
@ -385,15 +383,13 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
|
|||
}
|
||||
if (Players.CertificationToUseExoSuit(player, exosuit, subtype)) {
|
||||
if (exosuit == ExoSuitType.MAX) {
|
||||
val weapon = GlobalDefinitions.MAXArms(subtype, player.Faction)
|
||||
val cooldown = player.avatar.purchaseCooldown(weapon)
|
||||
val cooldown = player.avatar.purchaseCooldown(GlobalDefinitions.MAXArms(subtype, player.Faction))
|
||||
if (originalSubtype == subtype) {
|
||||
(exosuit, subtype) //same MAX subtype is free
|
||||
(exosuit, subtype) //same MAX subtype
|
||||
} else if (cooldown.nonEmpty) {
|
||||
fallbackSuit //different MAX subtype can not have cooldown
|
||||
fallbackSuit //different MAX subtype
|
||||
} else {
|
||||
avatarActor ! AvatarActor.UpdatePurchaseTime(weapon)
|
||||
(exosuit, subtype) //switching for first time causes cooldown
|
||||
(exosuit, subtype) //switching
|
||||
}
|
||||
} else {
|
||||
(exosuit, subtype)
|
||||
|
|
@ -475,11 +471,9 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
|
|||
case InventoryItem(citem: ConstructionItem, _) =>
|
||||
Deployables.initializeConstructionItem(player.avatar.certifications, citem)
|
||||
}
|
||||
//deactivate non-passive implants
|
||||
avatarActor ! AvatarActor.DeactivateActiveImplants
|
||||
val zone = player.Zone
|
||||
zone.AvatarEvents ! AvatarServiceMessage(
|
||||
zone.id,
|
||||
Players.ZoneChannelIfSpectating(player),
|
||||
AvatarAction.ChangeLoadout(
|
||||
player.GUID,
|
||||
toArmor,
|
||||
|
|
@ -556,7 +550,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
|
|||
//don't know where boomer trigger "should" go
|
||||
TaskWorkflow.execute(PutNewEquipmentInInventoryOrDrop(player)(trigger))
|
||||
}
|
||||
Players.buildCooldownReset(zone, player.Name, obj)
|
||||
Players.buildCooldownReset(zone, player.Name, obj.GUID)
|
||||
case _ => ()
|
||||
}
|
||||
deployablePair = None
|
||||
|
|
@ -573,7 +567,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
|
|||
player.Actor ! Player.LoseDeployable(obj)
|
||||
TelepadControl.TelepadError(zone, player.Name, msg = "@Telepad_NoDeploy_RouterLost")
|
||||
}
|
||||
Players.buildCooldownReset(zone, player.Name, obj)
|
||||
Players.buildCooldownReset(zone, player.Name, obj.GUID)
|
||||
case _ => ()
|
||||
}
|
||||
deployablePair = None
|
||||
|
|
@ -581,7 +575,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
|
|||
case Zone.Deployable.IsBuilt(obj) =>
|
||||
deployablePair match {
|
||||
case Some((deployable, tool)) if deployable eq obj =>
|
||||
Players.buildCooldownReset(player.Zone, player.Name, obj)
|
||||
Players.buildCooldownReset(player.Zone, player.Name, obj.GUID)
|
||||
player.Find(tool) match {
|
||||
case Some(index) =>
|
||||
Players.commonDestroyConstructionItem(player, tool, index)
|
||||
|
|
@ -613,10 +607,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
|
|||
val weapon = GlobalDefinitions.MAXArms(subtype, player.Faction)
|
||||
player.avatar.purchaseCooldown(weapon)
|
||||
.collect(_ => false)
|
||||
.getOrElse {
|
||||
avatarActor ! AvatarActor.UpdatePurchaseTime(weapon)
|
||||
true
|
||||
}
|
||||
.getOrElse(true)
|
||||
} else {
|
||||
true
|
||||
})
|
||||
|
|
@ -671,10 +662,8 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
|
|||
//insert
|
||||
afterHolsters.foreach(elem => player.Slot(elem.start).Equipment = elem.obj)
|
||||
afterInventory.foreach(elem => player.Inventory.InsertQuickly(elem.start, elem.obj))
|
||||
//deactivate non-passive implants
|
||||
avatarActor ! AvatarActor.DeactivateActiveImplants
|
||||
player.Zone.AvatarEvents ! AvatarServiceMessage(
|
||||
player.Zone.id,
|
||||
Players.ZoneChannelIfSpectating(player),
|
||||
AvatarAction.ChangeExosuit(
|
||||
player.GUID,
|
||||
toArmor,
|
||||
|
|
@ -744,7 +733,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
|
|||
else {
|
||||
log.warn(s"cannot build a ${obj.Definition.Name}")
|
||||
DropEquipmentFromInventory(player)(tool, Some(obj.Position))
|
||||
Players.buildCooldownReset(zone, player.Name, obj)
|
||||
Players.buildCooldownReset(zone, player.Name, obj.GUID)
|
||||
obj.Position = Vector3.Zero
|
||||
obj.AssignOwnership(None)
|
||||
zone.Deployables ! Zone.Deployable.Dismiss(obj)
|
||||
|
|
@ -755,7 +744,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
|
|||
obj.AssignOwnership(None)
|
||||
val zone = player.Zone
|
||||
zone.Deployables ! Zone.Deployable.Dismiss(obj)
|
||||
Players.buildCooldownReset(zone, player.Name, obj)
|
||||
Players.buildCooldownReset(zone, player.Name, obj.GUID)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -954,7 +943,6 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
|
|||
def DestructionAwareness(target: Player, cause: DamageResult): Unit = {
|
||||
val player_guid = target.GUID
|
||||
val pos = target.Position
|
||||
val respawnTimer = 300000 //milliseconds
|
||||
val zone = target.Zone
|
||||
val events = zone.AvatarEvents
|
||||
val nameChannel = target.Name
|
||||
|
|
@ -980,36 +968,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
|
|||
damageLog.info(s"${player.Name} killed ${player.Sex.pronounObject}self")
|
||||
}
|
||||
|
||||
// This would normally happen async as part of AvatarAction.Killed, but if it doesn't happen before deleting calling AvatarAction.ObjectDelete on the player the LLU will end up invisible to others if carried
|
||||
// Therefore, queue it up to happen first.
|
||||
events ! AvatarServiceMessage(nameChannel, AvatarAction.DropSpecialItem())
|
||||
|
||||
events ! AvatarServiceMessage(
|
||||
nameChannel,
|
||||
AvatarAction.Killed(player_guid, target.VehicleSeated)
|
||||
) //align client interface fields with state
|
||||
zone.GUID(target.VehicleSeated) match {
|
||||
case Some(obj: Mountable) =>
|
||||
//boot cadaver from mount internally (vehicle perspective)
|
||||
obj.PassengerInSeat(target) match {
|
||||
case Some(index) =>
|
||||
obj.Seats(index).unmount(target)
|
||||
case _ => ;
|
||||
}
|
||||
//boot cadaver from mount on client
|
||||
events ! AvatarServiceMessage(
|
||||
nameChannel,
|
||||
AvatarAction.SendResponse(
|
||||
Service.defaultPlayerGUID,
|
||||
ObjectDetachMessage(obj.GUID, player_guid, target.Position, Vector3.Zero)
|
||||
)
|
||||
)
|
||||
//make player invisible on client
|
||||
events ! AvatarServiceMessage(nameChannel, AvatarAction.PlanetsideAttributeToAll(player_guid, 29, 1))
|
||||
//only the dead player should "see" their own body, so that the death camera has something to focus on
|
||||
events ! AvatarServiceMessage(zoneChannel, AvatarAction.ObjectDelete(player_guid, player_guid))
|
||||
case _ => ;
|
||||
}
|
||||
events ! AvatarServiceMessage(nameChannel, AvatarAction.Killed(player_guid, cause, target.VehicleSeated)) //align client interface fields with state
|
||||
events ! AvatarServiceMessage(zoneChannel, AvatarAction.PlanetsideAttributeToAll(player_guid, 0, 0)) //health
|
||||
if (target.Capacitor > 0) {
|
||||
target.Capacitor = 0
|
||||
|
|
@ -1023,35 +982,6 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
|
|||
DestroyMessage(player_guid, attribute, Service.defaultPlayerGUID, pos)
|
||||
) //how many players get this message?
|
||||
)
|
||||
events ! AvatarServiceMessage(
|
||||
nameChannel,
|
||||
AvatarAction.SendResponse(
|
||||
Service.defaultPlayerGUID,
|
||||
AvatarDeadStateMessage(DeadState.Dead, respawnTimer, respawnTimer, pos, target.Faction, unk5=true)
|
||||
)
|
||||
)
|
||||
//TODO other methods of death?
|
||||
val pentry = PlayerSource(target)
|
||||
cause
|
||||
.adversarial
|
||||
.collect { case out @ Adversarial(attacker, _, _) if attacker != PlayerSource.Nobody => out }
|
||||
.orElse {
|
||||
target.LastDamage.collect {
|
||||
case attack if System.currentTimeMillis() - attack.interaction.hitTime < (10 seconds).toMillis =>
|
||||
attack
|
||||
.adversarial
|
||||
.collect { case out @ Adversarial(attacker, _, _) if attacker != PlayerSource.Nobody => out }
|
||||
}.flatten
|
||||
} match {
|
||||
case Some(adversarial) =>
|
||||
events ! AvatarServiceMessage(
|
||||
zoneChannel,
|
||||
AvatarAction.DestroyDisplay(adversarial.attacker, pentry, adversarial.implement)
|
||||
)
|
||||
case _ =>
|
||||
events ! AvatarServiceMessage(zoneChannel, AvatarAction.DestroyDisplay(pentry, pentry, 0))
|
||||
}
|
||||
zone.actor ! ZoneActor.RewardThisDeath(player)
|
||||
}
|
||||
|
||||
def suicide() : Unit = {
|
||||
|
|
|
|||
|
|
@ -81,24 +81,7 @@ final case class Projectile(
|
|||
* @return a new `Projectile` entity
|
||||
*/
|
||||
def quality(value: ProjectileQuality): Projectile = {
|
||||
val projectile = new Projectile(
|
||||
profile,
|
||||
tool_def,
|
||||
fire_mode,
|
||||
mounted_in,
|
||||
owner,
|
||||
attribute_to,
|
||||
shot_origin,
|
||||
shot_angle,
|
||||
shot_velocity,
|
||||
value,
|
||||
id,
|
||||
fire_time
|
||||
)
|
||||
if(isMiss) projectile.Miss()
|
||||
else if(isResolved) projectile.Resolve()
|
||||
projectile.WhichSide = this.WhichSide
|
||||
projectile
|
||||
Projectile.copy(original=this, copy(quality = value))
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -186,4 +169,15 @@ object Projectile {
|
|||
): Projectile = {
|
||||
Projectile(profile, tool_def, fire_mode, None, owner, attribute_to, shot_origin, shot_angle, None)
|
||||
}
|
||||
|
||||
def copy(original: Projectile, dirtyCopy: Projectile): Projectile = {
|
||||
val properCopy = dirtyCopy.copy(fire_time = original.fire_time, id = original.id)
|
||||
properCopy.GUID = original.GUID
|
||||
properCopy.WhichSide = original.WhichSide
|
||||
if (original.isMiss)
|
||||
properCopy.Miss()
|
||||
else if (original.isResolved)
|
||||
properCopy.Resolve()
|
||||
properCopy
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,11 +65,17 @@ object AvatarConverter {
|
|||
*/
|
||||
def MakeAppearanceData(obj: Player): Int => CharacterAppearanceData = {
|
||||
val alt_model_flag: Boolean = obj.isBackpack
|
||||
val avatar = obj.avatar
|
||||
val tempAvatarInfo = if (obj.spectator) {
|
||||
avatar.basic.copy(name = s"<spectator:${avatar.basic.name}>")
|
||||
} else {
|
||||
avatar.basic
|
||||
}
|
||||
val aa: Int => CharacterAppearanceA = CharacterAppearanceA(
|
||||
obj.avatar.basic,
|
||||
tempAvatarInfo,
|
||||
CommonFieldData(
|
||||
obj.Faction,
|
||||
bops = obj.spectator,
|
||||
bops = obj.bops,
|
||||
alt_model_flag,
|
||||
v1 = false,
|
||||
None,
|
||||
|
|
@ -106,7 +112,7 @@ object AvatarConverter {
|
|||
unk7 = false,
|
||||
on_zipline = None
|
||||
)
|
||||
CharacterAppearanceData(aa, ab, obj.avatar.decoration.ribbonBars)
|
||||
CharacterAppearanceData(aa, ab, avatar.decoration.ribbonBars)
|
||||
}
|
||||
|
||||
def MakeCharacterData(obj: Player): (Boolean, Boolean) => CharacterData = {
|
||||
|
|
|
|||
|
|
@ -289,7 +289,7 @@ object GlobalDefinitionsProjectile {
|
|||
chainblade_projectile.Damage1 = 0
|
||||
chainblade_projectile.ProjectileDamageType = DamageType.Direct
|
||||
chainblade_projectile.InitialVelocity = 100
|
||||
chainblade_projectile.Lifespan = .02f
|
||||
chainblade_projectile.Lifespan = .03f //.02f
|
||||
ProjectileDefinition.CalculateDerivedFields(chainblade_projectile)
|
||||
chainblade_projectile.Modifiers = List(MeleeBoosted, MaxDistanceCutoff)
|
||||
|
||||
|
|
@ -601,7 +601,7 @@ object GlobalDefinitionsProjectile {
|
|||
forceblade_projectile.Damage1 = 0
|
||||
forceblade_projectile.ProjectileDamageType = DamageType.Direct
|
||||
forceblade_projectile.InitialVelocity = 100
|
||||
forceblade_projectile.Lifespan = .02f
|
||||
forceblade_projectile.Lifespan = .03f //.02f
|
||||
ProjectileDefinition.CalculateDerivedFields(forceblade_projectile)
|
||||
forceblade_projectile.Modifiers = List(MeleeBoosted, MaxDistanceCutoff)
|
||||
|
||||
|
|
@ -942,7 +942,7 @@ object GlobalDefinitionsProjectile {
|
|||
katana_projectile.Damage1 = 0
|
||||
katana_projectile.ProjectileDamageType = DamageType.Direct
|
||||
katana_projectile.InitialVelocity = 100
|
||||
katana_projectile.Lifespan = .03f
|
||||
katana_projectile.Lifespan = .04f //.03f
|
||||
ProjectileDefinition.CalculateDerivedFields(katana_projectile)
|
||||
|
||||
katana_projectileb.Name = "katana_projectileb"
|
||||
|
|
@ -1088,7 +1088,7 @@ object GlobalDefinitionsProjectile {
|
|||
magcutter_projectile.Damage1 = 0
|
||||
magcutter_projectile.ProjectileDamageType = DamageType.Direct
|
||||
magcutter_projectile.InitialVelocity = 100
|
||||
magcutter_projectile.Lifespan = .02f
|
||||
magcutter_projectile.Lifespan = .03f //.02f
|
||||
ProjectileDefinition.CalculateDerivedFields(magcutter_projectile)
|
||||
magcutter_projectile.Modifiers = List(MeleeBoosted, MaxDistanceCutoff)
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ package net.psforever.objects.vital
|
|||
import net.psforever.objects.PlanetSideGameObject
|
||||
import net.psforever.objects.definition.{EquipmentDefinition, KitDefinition, ToolDefinition}
|
||||
import net.psforever.objects.serverobject.affinity.FactionAffinity
|
||||
import net.psforever.objects.sourcing.{AmenitySource, PlayerSource, SourceEntry, SourceUniqueness, SourceWithHealthEntry, VehicleSource}
|
||||
import net.psforever.objects.sourcing.{AmenitySource, DeployableSource, PlayerSource, SourceEntry, SourceUniqueness, SourceWithHealthEntry, VehicleSource}
|
||||
import net.psforever.objects.vital.environment.EnvironmentReason
|
||||
import net.psforever.objects.vital.etc.{ExplodingEntityReason, PainboxReason, SuicideReason}
|
||||
import net.psforever.objects.vital.interaction.{DamageInteraction, DamageResult}
|
||||
|
|
@ -82,6 +82,9 @@ final case class ShieldCharge(amount: Int, cause: Option[SourceEntry])
|
|||
final case class TerminalUsedActivity(terminal: AmenitySource, transaction: TransactionType.Value)
|
||||
extends GeneralActivity
|
||||
|
||||
final case class TelepadUseActivity(router: VehicleSource, telepad: DeployableSource, player: PlayerSource)
|
||||
extends GeneralActivity
|
||||
|
||||
sealed trait VehicleMountChange extends GeneralActivity {
|
||||
def vehicle: VehicleSource
|
||||
def zoneNumber: Int
|
||||
|
|
@ -248,7 +251,7 @@ trait InGameHistory {
|
|||
*/
|
||||
def LogActivity(action: Option[InGameActivity]): List[InGameActivity] = {
|
||||
action match {
|
||||
case Some(act: VehicleDismountActivity) =>
|
||||
case Some(act: VehicleDismountActivity) if act.pairedEvent.isEmpty =>
|
||||
history
|
||||
.findLast(_.isInstanceOf[VehicleMountActivity])
|
||||
.collect {
|
||||
|
|
@ -259,6 +262,8 @@ trait InGameHistory {
|
|||
history = history :+ act
|
||||
None
|
||||
}
|
||||
case Some(act: VehicleDismountActivity) =>
|
||||
history = history :+ act
|
||||
case Some(act: VehicleCargoDismountActivity) =>
|
||||
history
|
||||
.findLast(_.isInstanceOf[VehicleCargoMountActivity])
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ package net.psforever.objects.vital.damage
|
|||
|
||||
import net.psforever.objects.vital.interaction.DamageInteraction
|
||||
|
||||
import scala.annotation.unused
|
||||
|
||||
/**
|
||||
* A series of methods for extraction of the base damage against a given target type
|
||||
* as well as incorporating damage modifiers from the other aspects of the interaction.
|
||||
|
|
@ -11,7 +13,7 @@ object DamageCalculations {
|
|||
type Selector = DamageProfile => Int
|
||||
|
||||
//raw damage selectors
|
||||
def AgainstNothing(profile : DamageProfile) : Int = 0
|
||||
def AgainstNothing(@unused profile : DamageProfile) : Int = 0
|
||||
|
||||
def AgainstExoSuit(profile : DamageProfile) : Int = profile.Damage0
|
||||
|
||||
|
|
|
|||
|
|
@ -313,7 +313,7 @@ object SectorGroup {
|
|||
new SectorGroup(
|
||||
rangeX,
|
||||
rangeY,
|
||||
sector.livePlayerList,
|
||||
sector.livePlayerList.filterNot(p => p.spectator || !p.allowInteraction),
|
||||
sector.corpseList,
|
||||
sector.vehicleList,
|
||||
sector.equipmentOnGroundList,
|
||||
|
|
@ -368,7 +368,7 @@ object SectorGroup {
|
|||
new SectorGroup(
|
||||
rangeX,
|
||||
rangeY,
|
||||
sector.livePlayerList,
|
||||
sector.livePlayerList.filterNot(p => p.spectator || !p.allowInteraction),
|
||||
sector.corpseList,
|
||||
sector.vehicleList,
|
||||
sector.equipmentOnGroundList,
|
||||
|
|
@ -382,7 +382,7 @@ object SectorGroup {
|
|||
new SectorGroup(
|
||||
rangeX,
|
||||
rangeY,
|
||||
sectors.flatMap { _.livePlayerList }.toList.distinct,
|
||||
sectors.flatMap { _.livePlayerList }.toList.distinct.filterNot(p => p.spectator || !p.allowInteraction),
|
||||
sectors.flatMap { _.corpseList }.toList.distinct,
|
||||
sectors.flatMap { _.vehicleList }.toList.distinct,
|
||||
sectors.flatMap { _.equipmentOnGroundList }.toList.distinct,
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import akka.actor.ActorRef
|
|||
import net.psforever.objects.GlobalDefinitions
|
||||
import net.psforever.objects.avatar.scoring.{Kill, SupportActivity}
|
||||
import net.psforever.objects.sourcing.{BuildingSource, PlayerSource, SourceEntry, SourceUniqueness, TurretSource, VehicleSource}
|
||||
import net.psforever.objects.vital.{Contribution, HealFromTerminal, InGameActivity, RepairFromTerminal, RevivingActivity, TerminalUsedActivity, VehicleCargoDismountActivity, VehicleCargoMountActivity, VehicleDismountActivity, VehicleMountActivity}
|
||||
import net.psforever.objects.vital.{Contribution, HealFromTerminal, InGameActivity, RepairFromTerminal, RevivingActivity, TelepadUseActivity, TerminalUsedActivity, VehicleCargoDismountActivity, VehicleCargoMountActivity, VehicleDismountActivity, VehicleMountActivity}
|
||||
import net.psforever.objects.vital.projectile.ProjectileReason
|
||||
import net.psforever.objects.zones.exp.rec.{CombinedHealthAndArmorContributionProcess, MachineRecoveryExperienceContributionProcess}
|
||||
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||
|
|
@ -55,6 +55,11 @@ object KillContributions {
|
|||
/** cached for empty collection returns; please do not add anything to it */
|
||||
private val emptyMap: mutable.LongMap[ContributionStats] = mutable.LongMap.empty[ContributionStats]
|
||||
|
||||
/** cached for use with telepad deployable activities, from the perspective of the router */
|
||||
private val routerKillAssist = RouterKillAssist(GlobalDefinitions.router.ObjectId)
|
||||
/** cached for use with telepad deployable activities */
|
||||
private val routerTelepadKillAssist = RouterKillAssist(GlobalDefinitions.router_telepad_deployable.ObjectId)
|
||||
|
||||
/**
|
||||
* Primary landing point for calculating the rewards given for helping one player kill another player.
|
||||
* Rewards in the form of "support experience points" are given
|
||||
|
|
@ -263,6 +268,7 @@ object KillContributions {
|
|||
): mutable.LongMap[ContributionStats] = {
|
||||
contributeWithRevivalActivity(history, existingParticipants)
|
||||
contributeWithTerminalActivity(history, faction, contributions, excludedTargets, existingParticipants)
|
||||
contributeWithRouterTelepadActivity(kill, history, faction, contributions, excludedTargets, existingParticipants)
|
||||
contributeWithVehicleTransportActivity(kill, history, faction, contributions, excludedTargets, existingParticipants)
|
||||
contributeWithVehicleCargoTransportActivity(kill, history, faction, contributions, excludedTargets, existingParticipants)
|
||||
contributeWithKillWhileMountedActivity(kill, faction, contributions, excludedTargets, existingParticipants)
|
||||
|
|
@ -371,10 +377,17 @@ object KillContributions {
|
|||
/*
|
||||
collect the dismount activity of all vehicles from which this player is not the owner
|
||||
make certain all dismount activity can be paired with a mounting activity
|
||||
certain other qualifications of the prior mounting must be met before the support bonus applies
|
||||
other qualifications of the prior mounting must be met before the support bonus applies
|
||||
*/
|
||||
val killerOpt = kill.info.adversarial
|
||||
.map(_.attacker)
|
||||
.collect { case p: PlayerSource => p }
|
||||
val dismountActivity = history
|
||||
.collect {
|
||||
/*
|
||||
the player should not get credit from being the vehicle owner in matters of transportation
|
||||
there are considerations of time and distance traveled before the kill as well
|
||||
*/
|
||||
case out: VehicleDismountActivity
|
||||
if !out.vehicle.owner.contains(out.player.unique) && out.pairedEvent.nonEmpty => (out.pairedEvent.get, out)
|
||||
}
|
||||
|
|
@ -382,29 +395,30 @@ object KillContributions {
|
|||
case (in: VehicleMountActivity, out: VehicleDismountActivity)
|
||||
if in.vehicle.unique == out.vehicle.unique &&
|
||||
out.vehicle.Faction == out.player.Faction &&
|
||||
(in.vehicle.Definition == GlobalDefinitions.router || {
|
||||
val inTime = in.time
|
||||
val outTime = out.time
|
||||
out.player.progress.kills.exists { death =>
|
||||
val deathTime = death.info.interaction.hitTime
|
||||
inTime < deathTime && deathTime <= outTime
|
||||
}
|
||||
} || {
|
||||
val sameZone = in.zoneNumber == out.zoneNumber
|
||||
val distanceTransported = Vector3.DistanceSquared(in.vehicle.Position.xy, out.vehicle.Position.xy)
|
||||
val distanceMoved = {
|
||||
val killLocation = kill.info.adversarial
|
||||
.collect { adversarial => adversarial.attacker.Position.xy }
|
||||
.getOrElse(Vector3.Zero)
|
||||
Vector3.DistanceSquared(killLocation, out.player.Position.xy)
|
||||
}
|
||||
val timeSpent = out.time - in.time
|
||||
distanceMoved < 5625f /* 75m */ &&
|
||||
(timeSpent >= 210000L /* 3:30 */ ||
|
||||
(sameZone && (distanceTransported > 160000f /* 400m */ ||
|
||||
distanceTransported > 10000f /* 100m */ && timeSpent >= 60000L /* 1:00m */)) ||
|
||||
(!sameZone && (distanceTransported > 10000f /* 100m */ || timeSpent >= 120000L /* 2:00 */ )))
|
||||
}) =>
|
||||
/*
|
||||
considerations of time and distance transported before the kill
|
||||
*/
|
||||
({
|
||||
val inTime = in.time
|
||||
val outTime = out.time
|
||||
out.player.progress.kills.exists { death =>
|
||||
val deathTime = death.info.interaction.hitTime
|
||||
inTime < deathTime && deathTime <= outTime
|
||||
}
|
||||
} || {
|
||||
val sameZone = in.zoneNumber == out.zoneNumber
|
||||
val distanceTransported = Vector3.DistanceSquared(in.vehicle.Position.xy, out.vehicle.Position.xy)
|
||||
val distanceMoved = {
|
||||
val killLocation = killerOpt.map(_.Position.xy).getOrElse(Vector3.Zero)
|
||||
Vector3.DistanceSquared(killLocation, out.player.Position.xy)
|
||||
}
|
||||
val timeSpent = out.time - in.time
|
||||
distanceMoved < 5625f /* 75m */ &&
|
||||
(timeSpent >= 210000L /* 3:30 */ ||
|
||||
(sameZone && (distanceTransported > 160000f /* 400m */ ||
|
||||
distanceTransported > 10000f /* 100m */ && timeSpent >= 60000L /* 1:00m */)) ||
|
||||
(!sameZone && (distanceTransported > 10000f /* 100m */ || timeSpent >= 120000L /* 2:00 */ )))
|
||||
}) =>
|
||||
out
|
||||
}
|
||||
//apply
|
||||
|
|
@ -412,25 +426,20 @@ object KillContributions {
|
|||
.groupBy { _.vehicle }
|
||||
.collect { case (mount, dismountsFromVehicle) if mount.owner.nonEmpty =>
|
||||
val promotedOwner = PlayerSource(mount.owner.get, mount.Position)
|
||||
val (equipmentUseContext, equipmentUseEvent) = mount.Definition match {
|
||||
case v @ GlobalDefinitions.router =>
|
||||
(RouterKillAssist(v.ObjectId), "router")
|
||||
case v =>
|
||||
(HotDropKillAssist(v.ObjectId, 0), "hotdrop")
|
||||
}
|
||||
val size = dismountsFromVehicle.size
|
||||
val time = dismountsFromVehicle.maxBy(_.time).time
|
||||
val weaponStat = Support.calculateSupportExperience(
|
||||
equipmentUseEvent,
|
||||
WeaponStats(equipmentUseContext, size, size, time, 1f)
|
||||
)
|
||||
combineStatsInto(
|
||||
out,
|
||||
(
|
||||
promotedOwner.CharId,
|
||||
ContributionStats(promotedOwner, Seq(weaponStat), size, size, size, time)
|
||||
)
|
||||
)
|
||||
List((HotDropKillAssist(mount.Definition.ObjectId, 0), "hotdrop", promotedOwner))
|
||||
.foreach {
|
||||
case (equipmentUseContext, equipmentUseEvent, eventOwner) =>
|
||||
val weaponStat = Support.calculateSupportExperience(
|
||||
equipmentUseEvent,
|
||||
WeaponStats(equipmentUseContext, size, size, time, 1f)
|
||||
)
|
||||
combineStatsInto(
|
||||
out,
|
||||
(eventOwner.CharId, ContributionStats(eventOwner, Seq(weaponStat), size, size, size, time))
|
||||
)
|
||||
}
|
||||
contributions.get(mount.unique).collect {
|
||||
case list =>
|
||||
val mountHistory = dismountsFromVehicle
|
||||
|
|
@ -554,6 +563,86 @@ object KillContributions {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gather and reward use of a telepad deployable in performing a kill.
|
||||
* There are two ways to account for telepad deployable use,
|
||||
* i.e., traveling through s telepad deployable or using the internal telepad system of a Router:
|
||||
* the user that places the telepad deployable unit,
|
||||
* and the user that owns the Router.<br>
|
||||
* na
|
||||
* @param kill the in-game event that maintains information about the other player's death
|
||||
* @param faction empire to target
|
||||
* @param contributions mapping between external entities
|
||||
* the target has interacted with in the form of in-game activity
|
||||
* and history related to the time period in which the interaction ocurred
|
||||
* @param excludedTargets if a potential target is listed here already, skip processing it
|
||||
* @param out quantitative record of activity in relation to the other players and their equipment
|
||||
* @see `combineStatsInto`
|
||||
* @see `extractContributionsForMachineByTarget`
|
||||
*/
|
||||
private def contributeWithRouterTelepadActivity(
|
||||
kill: Kill,
|
||||
history: List[InGameActivity],
|
||||
faction: PlanetSideEmpire.Value,
|
||||
contributions: Map[SourceUniqueness, List[InGameActivity]],
|
||||
excludedTargets: mutable.ListBuffer[SourceUniqueness],
|
||||
out: mutable.LongMap[ContributionStats]
|
||||
): Unit = {
|
||||
/*
|
||||
collect the use of all router telepads from which this player is not the owner (deployer) of the telepad
|
||||
*/
|
||||
val killer = kill.info.adversarial
|
||||
.map(_.attacker)
|
||||
.collect { case p: PlayerSource => p }
|
||||
.getOrElse(PlayerSource.Nobody)
|
||||
history
|
||||
.collect {
|
||||
case event: TelepadUseActivity if !event.player.Name.equals(event.telepad.OwnerName) =>
|
||||
event
|
||||
}
|
||||
.groupBy(_.telepad.unique)
|
||||
.flatMap {
|
||||
case (_, telepadEvents) =>
|
||||
val size = telepadEvents.size
|
||||
val time = telepadEvents.maxBy(_.time).time
|
||||
val firstEvent = telepadEvents.head
|
||||
val telepadOwner = firstEvent.telepad.owner.asInstanceOf[PlayerSource]
|
||||
val mount = firstEvent.router
|
||||
contributions.get(mount.unique).collect {
|
||||
case list =>
|
||||
val mountHistory = telepadEvents
|
||||
.flatMap { event =>
|
||||
val eventTime = event.time
|
||||
val startTime = eventTime - Config.app.game.experience.longContributionTime
|
||||
limitHistoryToThisLife(list, eventTime, startTime)
|
||||
}
|
||||
.distinctBy(_.time)
|
||||
combineStatsInto(
|
||||
out,
|
||||
extractContributionsForMachineByTarget(mount, faction, mountHistory, contributions, excludedTargets, eventOutputType="support-repair")
|
||||
)
|
||||
}
|
||||
telepadEvents
|
||||
.flatMap(_.router.owner)
|
||||
.distinct
|
||||
.filterNot(owner => owner == killer.unique || owner == telepadOwner.unique)
|
||||
.map(p => (WeaponStats(routerKillAssist, size, size, time, 1f), "router-driver", PlayerSource(p, Vector3.Zero))) :+
|
||||
(WeaponStats(routerTelepadKillAssist, size, size, time, 1f), "telepad-use", telepadOwner)
|
||||
}
|
||||
.foreach {
|
||||
case (equipmentUseContext, equipmentUseEventId, eventOwner) =>
|
||||
val size = equipmentUseContext.amount
|
||||
val weaponStat = Support.calculateSupportExperience(equipmentUseEventId, equipmentUseContext)
|
||||
combineStatsInto(
|
||||
out,
|
||||
(
|
||||
eventOwner.CharId,
|
||||
ContributionStats(eventOwner, Seq(weaponStat), size, size, size, equipmentUseContext.time)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gather and reward specific in-game equipment use activity.<br>
|
||||
* na
|
||||
|
|
|
|||
|
|
@ -676,12 +676,14 @@ object Support {
|
|||
val shots = weaponStat.shots
|
||||
val shotsMax = event.shotsMax
|
||||
val shotsMultiplier = event.shotsMultiplier
|
||||
if (shotsMultiplier > 0f && shots < event.shotsCutoff) {
|
||||
val modifiedShotsReward: Float =
|
||||
shotsMultiplier * math.log(math.min(shotsMax, shots).toDouble + 2d).toFloat
|
||||
val modifiedAmountReward: Float =
|
||||
event.amountMultiplier * weaponStat.amount.toFloat
|
||||
event.base.toFloat + modifiedShotsReward + modifiedAmountReward
|
||||
if (shots < event.shotsCutoff) {
|
||||
if (shotsMultiplier > 0f) {
|
||||
val modifiedShotsReward: Float = shotsMultiplier * math.log(math.min(shotsMax, shots).toDouble + 2d).toFloat
|
||||
val modifiedAmountReward: Float = event.amountMultiplier * weaponStat.amount.toFloat
|
||||
event.base.toFloat + modifiedShotsReward + modifiedAmountReward
|
||||
} else {
|
||||
event.base.toFloat
|
||||
}
|
||||
} else {
|
||||
0f
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,11 +2,34 @@
|
|||
package net.psforever.packet.game
|
||||
|
||||
import net.psforever.newcodecs._
|
||||
import net.psforever.packet.GamePacketOpcode.Type
|
||||
import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
|
||||
import net.psforever.types.ChatMessageType
|
||||
import scodec.Codec
|
||||
import scodec.bits.BitVector
|
||||
import scodec.{Attempt, Codec}
|
||||
import scodec.codecs._
|
||||
|
||||
/*
|
||||
For colors, type '/#n' before text, where `n` is one of the following hexadecimal numbers:
|
||||
0 white
|
||||
1 black
|
||||
2 cyan
|
||||
3 yellow
|
||||
4 green
|
||||
5 light blue
|
||||
6 brown
|
||||
7 violet
|
||||
8 magneta
|
||||
9 purple
|
||||
a purple
|
||||
b yellow green
|
||||
c blue
|
||||
d light pink
|
||||
e light green
|
||||
f beige
|
||||
All other options result in white text.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Instructs client to display and/or process a chat message/command when sent server to client.
|
||||
* Instructs server to route and/or process a chat message/command when sent client to server.
|
||||
|
|
@ -35,8 +58,8 @@ final case class ChatMsg(
|
|||
assert(note.isEmpty, "Note contents found, but message type isnt Note")
|
||||
|
||||
type Packet = ChatMsg
|
||||
def opcode = GamePacketOpcode.ChatMsg
|
||||
def encode = ChatMsg.encode(this)
|
||||
def opcode: Type = GamePacketOpcode.ChatMsg
|
||||
def encode: Attempt[BitVector] = ChatMsg.encode(this)
|
||||
}
|
||||
|
||||
object ChatMsg extends Marshallable[ChatMsg] {
|
||||
|
|
|
|||
|
|
@ -204,6 +204,7 @@ final case class DetailedCharacterB(
|
|||
*/
|
||||
final case class DetailedCharacterData(a: DetailedCharacterA, b: DetailedCharacterB)(pad_length: Option[Int])
|
||||
extends ConstructorData {
|
||||
val padLength: Option[Int] = pad_length
|
||||
|
||||
override def bitsize: Long = a.bitsize + b.bitsize
|
||||
}
|
||||
|
|
|
|||
|
|
@ -138,9 +138,9 @@ class AvatarService(zone: Zone) extends Actor {
|
|||
AvatarEvents.publish(
|
||||
AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.HitHint(source_guid))
|
||||
)
|
||||
case AvatarAction.Killed(player_guid, mount_guid) =>
|
||||
case AvatarAction.Killed(player_guid, cause, mount_guid) =>
|
||||
AvatarEvents.publish(
|
||||
AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.Killed(mount_guid))
|
||||
AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.Killed(cause, mount_guid))
|
||||
)
|
||||
case AvatarAction.LoadPlayer(player_guid, object_id, target_guid, cdata, pdata) =>
|
||||
val pkt = pdata match {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import net.psforever.objects.equipment.Equipment
|
|||
import net.psforever.objects.inventory.InventoryItem
|
||||
import net.psforever.objects.serverobject.environment.interaction.common.Watery.OxygenStateTarget
|
||||
import net.psforever.objects.sourcing.SourceEntry
|
||||
import net.psforever.objects.vital.interaction.DamageResult
|
||||
import net.psforever.objects.zones.Zone
|
||||
import net.psforever.packet.PlanetSideGamePacket
|
||||
import net.psforever.packet.game.ImplantAction
|
||||
|
|
@ -54,7 +55,7 @@ object AvatarAction {
|
|||
final case class GenericObjectAction(player_guid: PlanetSideGUID, object_guid: PlanetSideGUID, action_code: Int)
|
||||
extends Action
|
||||
final case class HitHint(source_guid: PlanetSideGUID, player_guid: PlanetSideGUID) extends Action
|
||||
final case class Killed(player_guid: PlanetSideGUID, mount_guid: Option[PlanetSideGUID]) extends Action
|
||||
final case class Killed(player_guid: PlanetSideGUID, cause: DamageResult, mount_guid: Option[PlanetSideGUID]) extends Action
|
||||
final case class LoadPlayer(
|
||||
player_guid: PlanetSideGUID,
|
||||
object_id: Int,
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import net.psforever.objects.equipment.Equipment
|
|||
import net.psforever.objects.inventory.InventoryItem
|
||||
import net.psforever.objects.serverobject.environment.interaction.common.Watery.OxygenStateTarget
|
||||
import net.psforever.objects.sourcing.SourceEntry
|
||||
import net.psforever.objects.vital.interaction.DamageResult
|
||||
import net.psforever.packet.PlanetSideGamePacket
|
||||
import net.psforever.packet.game.objectcreate.ConstructorData
|
||||
import net.psforever.packet.game.{ImplantAction, ObjectCreateMessage}
|
||||
|
|
@ -46,7 +47,7 @@ object AvatarResponse {
|
|||
final case class EquipmentInHand(pkt: ObjectCreateMessage) extends Response
|
||||
final case class GenericObjectAction(object_guid: PlanetSideGUID, action_code: Int) extends Response
|
||||
final case class HitHint(source_guid: PlanetSideGUID) extends Response
|
||||
final case class Killed(mount_guid: Option[PlanetSideGUID]) extends Response
|
||||
final case class Killed(cause: DamageResult, mount_guid: Option[PlanetSideGUID]) extends Response
|
||||
final case class LoadPlayer(pkt: ObjectCreateMessage) extends Response
|
||||
final case class LoadProjectile(pkt: ObjectCreateMessage) extends Response
|
||||
final case class ObjectDelete(item_guid: PlanetSideGUID, unk: Int) extends Response
|
||||
|
|
|
|||
|
|
@ -10,3 +10,5 @@ case object DefaultChannel extends ChatChannel
|
|||
final case class SquadChannel(guid: PlanetSideGUID) extends ChatChannel
|
||||
|
||||
case object SpectatorChannel extends ChatChannel
|
||||
|
||||
case object CustomerServiceChannel extends ChatChannel
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ import net.psforever.objects.zones.{Zone, ZoneMap}
|
|||
import net.psforever.objects._
|
||||
import net.psforever.objects.definition.ProjectileDefinition
|
||||
import net.psforever.objects.serverobject.CommonMessages
|
||||
import net.psforever.objects.serverobject.environment.interaction.common.Watery.OxygenStateTarget
|
||||
import net.psforever.objects.serverobject.environment.{DeepSquare, EnvironmentAttribute, Pool}
|
||||
import net.psforever.objects.sourcing.{PlayerSource, SourceEntry}
|
||||
import net.psforever.objects.vital.base.DamageResolution
|
||||
|
|
@ -533,7 +532,7 @@ class PlayerControlDeathStandingTest extends ActorTest {
|
|||
assert(player2.isAlive)
|
||||
|
||||
player2.Actor ! Vitality.Damage(applyDamageTo)
|
||||
val msg_avatar = avatarProbe.receiveN(8, 500 milliseconds)
|
||||
val msg_avatar = avatarProbe.receiveN(5, 500 milliseconds)
|
||||
val msg_stamina = probe.receiveOne(500 milliseconds)
|
||||
activityProbe.expectNoMessage(200 milliseconds)
|
||||
assert(
|
||||
|
|
@ -550,31 +549,25 @@ class PlayerControlDeathStandingTest extends ActorTest {
|
|||
)
|
||||
assert(
|
||||
msg_avatar(1) match {
|
||||
case AvatarServiceMessage("TestCharacter2", AvatarAction.DropSpecialItem()) => true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
assert(
|
||||
msg_avatar(2) match {
|
||||
case AvatarServiceMessage("TestCharacter2", AvatarAction.Killed(PlanetSideGUID(2), None)) => true
|
||||
case AvatarServiceMessage("TestCharacter2", AvatarAction.Killed(PlanetSideGUID(2), _, None)) => true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
assert(
|
||||
msg_avatar(3) match {
|
||||
msg_avatar(2) match {
|
||||
case AvatarServiceMessage("test", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 0, _)) => true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
assert(
|
||||
msg_avatar(4) match {
|
||||
msg_avatar(3) match {
|
||||
case AvatarServiceMessage("TestCharacter2", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 7, _)) =>
|
||||
true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
assert(
|
||||
msg_avatar(5) match {
|
||||
msg_avatar(4) match {
|
||||
case AvatarServiceMessage(
|
||||
"TestCharacter2",
|
||||
AvatarAction.SendResponse(_, DestroyMessage(PlanetSideGUID(2), PlanetSideGUID(1), _, _))
|
||||
|
|
@ -583,27 +576,6 @@ class PlayerControlDeathStandingTest extends ActorTest {
|
|||
case _ => false
|
||||
}
|
||||
)
|
||||
assert(
|
||||
msg_avatar(6) match {
|
||||
case AvatarServiceMessage(
|
||||
"TestCharacter2",
|
||||
AvatarAction.SendResponse(
|
||||
_,
|
||||
AvatarDeadStateMessage(DeadState.Dead, 300000, 300000, Vector3.Zero, PlanetSideEmpire.NC, true)
|
||||
)
|
||||
) =>
|
||||
true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
assert(
|
||||
msg_avatar(7) match {
|
||||
case AvatarServiceMessage("test", AvatarAction.DestroyDisplay(killer, victim, _, _))
|
||||
if killer.Name.equals(player1.Name) && victim.Name.equals(player2.Name) =>
|
||||
true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
assert(player2.Health <= player2.Definition.DamageDestroysAt)
|
||||
assert(player2.Armor == 0)
|
||||
assert(!player2.isAlive)
|
||||
|
|
@ -680,7 +652,7 @@ class PlayerControlDeathStandingTest extends ActorTest {
|
|||
// assert(player2.isAlive)
|
||||
//
|
||||
// player2.Actor ! Vitality.Damage(applyDamageTo)
|
||||
// val msg_avatar = avatarProbe.receiveN(9, 1500 milliseconds)
|
||||
// val msg_avatar = avatarProbe.receiveN(3, 1500 milliseconds)
|
||||
// val msg_stamina = probe.receiveOne(500 milliseconds)
|
||||
// activityProbe.expectNoMessage(200 milliseconds)
|
||||
// assert(
|
||||
|
|
@ -691,83 +663,30 @@ class PlayerControlDeathStandingTest extends ActorTest {
|
|||
// )
|
||||
// assert(
|
||||
// msg_avatar.head match {
|
||||
// case AvatarServiceMessage("TestCharacter2", AvatarAction.DropSpecialItem()) => true
|
||||
// case _ => false
|
||||
// case AvatarServiceMessage(
|
||||
// "TestCharacter2",
|
||||
// AvatarAction.Killed(PlanetSideGUID(2), _, Some(PlanetSideGUID(7)))
|
||||
// ) =>
|
||||
// true
|
||||
// case _ => false
|
||||
// }
|
||||
// )
|
||||
// assert(
|
||||
// msg_avatar(1) match {
|
||||
// case AvatarServiceMessage(
|
||||
// "TestCharacter2",
|
||||
// AvatarAction.Killed(PlanetSideGUID(2), Some(PlanetSideGUID(7)))
|
||||
// ) =>
|
||||
// true
|
||||
// case _ => false
|
||||
// case AvatarServiceMessage("test", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 0, _)) => true
|
||||
// case _ => false
|
||||
// }
|
||||
// )
|
||||
// assert(
|
||||
// msg_avatar(2) match {
|
||||
// case AvatarServiceMessage(
|
||||
// "TestCharacter2",
|
||||
// AvatarAction.SendResponse(_, ObjectDetachMessage(PlanetSideGUID(7), PlanetSideGUID(2), _, _, _, _))
|
||||
// ) =>
|
||||
// true
|
||||
// case _ => false
|
||||
// }
|
||||
// )
|
||||
// assert(
|
||||
// msg_avatar(3) match {
|
||||
// case AvatarServiceMessage(
|
||||
// "TestCharacter2",
|
||||
// AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 29, 1)
|
||||
// ) =>
|
||||
// true
|
||||
// case _ => false
|
||||
// }
|
||||
// )
|
||||
// assert(
|
||||
// msg_avatar(4) match {
|
||||
// case AvatarServiceMessage("test", AvatarAction.ObjectDelete(PlanetSideGUID(2), PlanetSideGUID(2), _)) => true
|
||||
// case _ => false
|
||||
// }
|
||||
// )
|
||||
// assert(
|
||||
// msg_avatar(5) match {
|
||||
// case AvatarServiceMessage("test", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 0, _)) => true
|
||||
// case _ => false
|
||||
// }
|
||||
// )
|
||||
// assert(
|
||||
// msg_avatar(6) match {
|
||||
// case AvatarServiceMessage(
|
||||
// "TestCharacter2",
|
||||
// AvatarAction.SendResponse(_, DestroyMessage(PlanetSideGUID(2), PlanetSideGUID(1), _, _))
|
||||
// ) =>
|
||||
// true
|
||||
// case _ => false
|
||||
// }
|
||||
// )
|
||||
// assert(
|
||||
// msg_avatar(7) match {
|
||||
// case AvatarServiceMessage(
|
||||
// "TestCharacter2",
|
||||
// AvatarAction.SendResponse(
|
||||
// _,
|
||||
// AvatarDeadStateMessage(DeadState.Dead, 300000, 300000, Vector3.Zero, PlanetSideEmpire.NC, true)
|
||||
// )
|
||||
// ) =>
|
||||
// true
|
||||
// case _ => false
|
||||
// }
|
||||
// )
|
||||
// assert(
|
||||
// msg_avatar(8) match {
|
||||
// case AvatarServiceMessage("test", AvatarAction.DestroyDisplay(killer, victim, _, _))
|
||||
// if killer.Name.equals(player1.Name) && victim.Name.equals(player2.Name) =>
|
||||
// true
|
||||
// case _ => false
|
||||
// }
|
||||
// )
|
||||
// assert(player2.Health <= player2.Definition.DamageDestroysAt)
|
||||
// assert(!player2.isAlive)
|
||||
// }
|
||||
|
|
|
|||
Loading…
Reference in a new issue