From 72572ad1259a8f22611dca00d48dc46f529e232e Mon Sep 17 00:00:00 2001 From: Fate-JH Date: Wed, 26 Apr 2023 12:25:08 -0400 Subject: [PATCH] Deconstruct in Privacy (#1069) * when deconstructing at a spawn tube, eliminate damage by hiding the player character from rendering through psm manipulation; adjusts the psm load balancing algorithm; add conditions for checking for cancelling the deconstruction flag when certain actions are taken * condition for avoiding server-size (all) damage during deconstruction period * mutually assured discetion --- .../support/SessionAvatarHandlers.scala | 91 +++++++++++-------- .../actors/session/support/SessionData.scala | 70 +++++++++++--- .../session/support/ZoningOperations.scala | 7 ++ .../scala/net/psforever/objects/Player.scala | 4 + .../net/psforever/objects/zones/Zoning.scala | 1 + 5 files changed, 121 insertions(+), 52 deletions(-) diff --git a/src/main/scala/net/psforever/actors/session/support/SessionAvatarHandlers.scala b/src/main/scala/net/psforever/actors/session/support/SessionAvatarHandlers.scala index bba71963..06d7e44f 100644 --- a/src/main/scala/net/psforever/actors/session/support/SessionAvatarHandlers.scala +++ b/src/main/scala/net/psforever/actors/session/support/SessionAvatarHandlers.scala @@ -69,27 +69,27 @@ class SessionAvatarHandlers( isJumping, jumpThrust, isCloaking, - spectating, + isNotRendered, canSeeReallyFar ) if isNotSameTarget => val pstateToSave = pstate.copy(timestamp = 0) - val (lastMsg, lastTime, lastPosition, wasSpectating, wasVisible) = lastSeenStreamMessage(guid.guid) match { - case SessionAvatarHandlers.LastUpstream(Some(msg), visible, time) => (Some(msg), time, msg.pos, msg.spectator, visible) - case _ => (None, 0L, Vector3.Zero, false, false) + val (lastMsg, lastTime, lastPosition, wasVisible) = lastSeenStreamMessage(guid.guid) match { + case SessionAvatarHandlers.LastUpstream(Some(msg), visible, time) => (Some(msg), time, msg.pos, visible) + case _ => (None, 0L, Vector3.Zero, false) } - val drawConfig = Config.app.game.playerDraw + val drawConfig = Config.app.game.playerDraw //m val maxRange = drawConfig.rangeMax * drawConfig.rangeMax //sq.m - val ourPosition = player.Position + val ourPosition = player.Position //xyz val currentDistance = Vector3.DistanceSquared(ourPosition, pos) //sq.m - val inVisibleRange = currentDistance <= maxRange - val wasInVisibleRange = Vector3.DistanceSquared(lastPosition, pos) <= maxRange - val comingIntoVisibleRange = inVisibleRange && !wasInVisibleRange + val inDrawableRange = currentDistance <= maxRange val now = System.currentTimeMillis() //ms - val durationSince = now - lastTime //ms - val released = player.isReleased - if (!released && - !spectating && - (comingIntoVisibleRange || (inVisibleRange && !lastMsg.contains(pstateToSave)))) { + if ( + sessionData.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, @@ -102,12 +102,17 @@ class SessionAvatarHandlers( case index => drawConfig.delays(index) } } //ms - if (comingIntoVisibleRange || - canSeeReallyFar || - currentDistance < drawConfig.rangeMin * drawConfig.rangeMin || - durationSince > drawConfig.delayMax || - sessionData.canSeeReallyFar || - durationSince > targetDelay) { + if (!wasVisible || + !previouslyInDrawableRange || + (!lastMsg.contains(pstateToSave) && + (canSeeReallyFar || + currentDistance < drawConfig.rangeMin * drawConfig.rangeMin || + durationSince > drawConfig.delayMax || + sessionData.canSeeReallyFar || + durationSince > targetDelay + ) + ) + ) { //must draw sendResponse( PlayerStateMessage( @@ -126,28 +131,31 @@ class SessionAvatarHandlers( ) lastSeenStreamMessage(guid.guid) = SessionAvatarHandlers.LastUpstream(Some(pstateToSave), visible=true, now) } else { + //is visible, but skip reinforcement + lastSeenStreamMessage(guid.guid) = SessionAvatarHandlers.LastUpstream(Some(pstateToSave), visible=true, 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 + 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 + ) + ) + lastSeenStreamMessage(guid.guid) = SessionAvatarHandlers.LastUpstream(Some(pstateToSave), visible=false, now) + } else { + //skip drawing altogether lastSeenStreamMessage(guid.guid) = SessionAvatarHandlers.LastUpstream(Some(pstateToSave), visible=false, lastTime) } - } else if ( - (!spectating && wasInVisibleRange && !inVisibleRange) || - (inVisibleRange && !wasSpectating && spectating) || - (wasVisible && released) - ) { - //must hide - val lat = (1 + 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 - ) - ) - lastSeenStreamMessage(guid.guid) = SessionAvatarHandlers.LastUpstream(Some(pstateToSave), visible=false, now) } case AvatarResponse.ObjectHeld(slot, _) @@ -157,6 +165,9 @@ class SessionAvatarHandlers( continent.GUID(sessionData.terminals.usingMedicalTerminal).collect { case term: Terminal with ProximityUnit => sessionData.terminals.StopUsingProximityUnit(term) } + if (sessionData.zoning.zoningStatus == Zoning.Status.Deconstructing) { + sessionData.stopDeconstructing() + } case AvatarResponse.ObjectHeld(slot, _) if isSameTarget && slot > -1 => diff --git a/src/main/scala/net/psforever/actors/session/support/SessionData.scala b/src/main/scala/net/psforever/actors/session/support/SessionData.scala index 0ad74b3d..74243d91 100644 --- a/src/main/scala/net/psforever/actors/session/support/SessionData.scala +++ b/src/main/scala/net/psforever/actors/session/support/SessionData.scala @@ -210,7 +210,11 @@ class SessionData( val isMoving = WorldEntity.isMoving(vel) val isMovingPlus = isMoving || isJumping || jumpThrust if (isMovingPlus) { - zoning.CancelZoningProcessWithDescriptiveReason("cancel_motion") + if (zoning.zoningStatus == Zoning.Status.Deconstructing) { + stopDeconstructing() + } else { + zoning.CancelZoningProcessWithDescriptiveReason("cancel_motion") + } } fallHeightTracker(pos.z) // if (isCrouching && !player.Crouching) { @@ -258,6 +262,9 @@ class SessionData( case None => () } val eagleEye: Boolean = canSeeReallyFar + val isNotVisible: Boolean = player.spectator || + zoning.zoningStatus == Zoning.Status.Deconstructing || + (player.isAlive && zoning.spawn.deadState == DeadState.RespawnTime) continent.AvatarEvents ! AvatarServiceMessage( continent.id, AvatarAction.PlayerState( @@ -272,7 +279,7 @@ class SessionData( isJumping, jumpThrust, isCloaking, - player.spectator, + isNotVisible, eagleEye ) ) @@ -516,15 +523,27 @@ class SessionData( def handleAvatarImplant(pkt: AvatarImplantMessage): Unit = { val AvatarImplantMessage(_, action, slot, status) = pkt if (action == ImplantAction.Activation) { - zoning.CancelZoningProcessWithDescriptiveReason("cancel_implant") - avatar.implants(slot) match { - case Some(implant) => - if (status == 1) { - avatarActor ! AvatarActor.ActivateImplant(implant.definition.implantType) - } else { + if (zoning.zoningStatus == Zoning.Status.Deconstructing) { + //do not activate; play deactivation sound instead + stopDeconstructing() + avatar.implants(slot).collect { + case implant if implant.active => avatarActor ! AvatarActor.DeactivateImplant(implant.definition.implantType) - } - case _ => log.error(s"AvatarImplantMessage: ${player.Name} has an unknown implant in $slot") + case implant => + sendResponse(PlanetsideAttributeMessage(player.GUID, 28, implant.definition.implantType.value * 2)) + } + } else { + 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") + } } } } @@ -1422,11 +1441,10 @@ class SessionData( sendUseGeneralEntityMessage(obj, item) case None if player.Faction == obj.Faction => //deconstruction - log.info(s"${player.Name} is deconstructing at the ${obj.Owner.Definition.Name}'s spawns") zoning.CancelZoningProcessWithDescriptiveReason("cancel_use") playerActionsToCancel() terminals.CancelAllProximityUnits() - zoning.spawn.GoToDeploymentMap() + startDeconstructing(obj) case _ => () } } @@ -2751,6 +2769,34 @@ class SessionData( sendResponse(ChatMsg(ChatMessageType.UNK_227, wideContents=false, "", "@charsaved", None)) } + def startDeconstructing(obj: SpawnTube): Unit = { + log.info(s"${player.Name} is deconstructing at the ${obj.Owner.Definition.Name}'s spawns") + avatar.implants.collect { + case Some(implant) if implant.active && !implant.definition.Passive => + avatarActor ! AvatarActor.DeactivateImplant(implant.definition.implantType) + } + if (player.ExoSuit != ExoSuitType.MAX) { + player.Actor ! PlayerControl.ObjectHeld(Player.HandsDownSlot, updateMyHolsterArm = true) + } + zoning.spawn.nextSpawnPoint = Some(obj) //set fallback + zoning.zoningStatus = Zoning.Status.Deconstructing + player.allowInteraction = false + if (player.death_by == 0) { + player.death_by = 1 + } + zoning.spawn.GoToDeploymentMap() + } + + def stopDeconstructing(): Unit = { + zoning.zoningStatus = Zoning.Status.None + player.death_by = math.min(player.death_by, 0) + player.allowInteraction = true + zoning.spawn.nextSpawnPoint.foreach { tube => + sendResponse(PlayerStateShiftMessage(ShiftState(0, tube.Position, tube.Orientation.z))) + zoning.spawn.nextSpawnPoint = None + } + } + def failWithError(error: String): Unit = { log.error(error) middlewareActor ! MiddlewareActor.Teardown() diff --git a/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala b/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala index fa576f63..41264f42 100644 --- a/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala +++ b/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala @@ -1920,6 +1920,9 @@ class ZoningOperations( TurnCounterDuringInterim } sessionData.keepAliveFunc = NormalKeepAlive + if (zoningStatus == Zoning.Status.Deconstructing) { + sessionData.stopDeconstructing() + } upstreamMessageCount = 0 setAvatar = false sessionData.persist() @@ -1950,6 +1953,9 @@ class ZoningOperations( TurnCounterDuringInterim } sessionData.keepAliveFunc = NormalKeepAlive + if (zoningStatus == Zoning.Status.Deconstructing) { + sessionData.stopDeconstructing() + } upstreamMessageCount = 0 setAvatar = false sessionData.persist() @@ -2080,6 +2086,7 @@ class ZoningOperations( player.Health = health player.Armor = armor } + player.death_by = math.min(player.death_by, 0) sessionData.vehicles.GetKnownVehicleAndSeat() match { case (Some(vehicle: Vehicle), Some(seat: Int)) => //if the vehicle is the cargo of another vehicle in this zone diff --git a/src/main/scala/net/psforever/objects/Player.scala b/src/main/scala/net/psforever/objects/Player.scala index 79725b2b..343092da 100644 --- a/src/main/scala/net/psforever/objects/Player.scala +++ b/src/main/scala/net/psforever/objects/Player.scala @@ -547,6 +547,10 @@ class Player(var avatar: Avatar) ZoningRequest } + override def CanDamage: Boolean = { + death_by < 1 && super.CanDamage + } + def DamageModel: DamageResistanceModel = exosuit.asInstanceOf[DamageResistanceModel] def canEqual(other: Any): Boolean = other.isInstanceOf[Player] diff --git a/src/main/scala/net/psforever/objects/zones/Zoning.scala b/src/main/scala/net/psforever/objects/zones/Zoning.scala index 6d9bcb7f..54b25660 100644 --- a/src/main/scala/net/psforever/objects/zones/Zoning.scala +++ b/src/main/scala/net/psforever/objects/zones/Zoning.scala @@ -25,6 +25,7 @@ object Zoning { val values: IndexedSeq[Status] = findValues case object None extends Status(value = "None") + case object Deconstructing extends Status(value = "Deconstructing") case object Request extends Status(value = "Request") case object Countdown extends Status(value = "Countdown") }