From 4b7d41db2f07a1b9303e07e3390fc42ccf37fc56 Mon Sep 17 00:00:00 2001 From: Fate-JH Date: Mon, 2 Sep 2024 14:30:56 -0400 Subject: [PATCH] removed unnecessary handling from spectator mode; safety rails added to ValidObject, so oit doesn't get too silly; turrets will no longer end the world for somebody (#1232) --- .../normal/WeaponAndProjectileLogic.scala | 2 +- .../spectator/WeaponAndProjectileLogic.scala | 483 +----------------- .../actors/session/support/SessionData.scala | 6 +- 3 files changed, 15 insertions(+), 476 deletions(-) diff --git a/src/main/scala/net/psforever/actors/session/normal/WeaponAndProjectileLogic.scala b/src/main/scala/net/psforever/actors/session/normal/WeaponAndProjectileLogic.scala index 43144a110..6d202dc8b 100644 --- a/src/main/scala/net/psforever/actors/session/normal/WeaponAndProjectileLogic.scala +++ b/src/main/scala/net/psforever/actors/session/normal/WeaponAndProjectileLogic.scala @@ -554,7 +554,7 @@ class WeaponAndProjectileLogic(val ops: WeaponAndProjectileOperations, implicit } .orElse { //occasionally, something that is not technically a turret's natural target may be attacked - sessionLogic.validObject(targetGuid, decorator = "AIDamage/Target") + continent.GUID(targetGuid) //AIDamage/Attacker .collect { case target: PlanetSideServerObject with FactionAffinity with Vitality => sessionLogic.validObject(attackerGuid, decorator = "AIDamage/Attacker") diff --git a/src/main/scala/net/psforever/actors/session/spectator/WeaponAndProjectileLogic.scala b/src/main/scala/net/psforever/actors/session/spectator/WeaponAndProjectileLogic.scala index 7f63dc73d..1ebcc1fdd 100644 --- a/src/main/scala/net/psforever/actors/session/spectator/WeaponAndProjectileLogic.scala +++ b/src/main/scala/net/psforever/actors/session/spectator/WeaponAndProjectileLogic.scala @@ -1,138 +1,28 @@ // 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, WeaponAndProjectileFunctions, WeaponAndProjectileOperations} import net.psforever.login.WorldSession.{CountGrenades, FindEquipmentStock, FindToolThatUses, RemoveOldEquipmentFromInventory} -import net.psforever.objects.ballistics.{Projectile, ProjectileQuality} -import net.psforever.objects.definition.ProjectileDefinition -import net.psforever.objects.equipment.{ChargeFireModeDefinition, EquipmentSize} +import net.psforever.objects.ballistics.Projectile +import net.psforever.objects.equipment.ChargeFireModeDefinition import net.psforever.objects.inventory.Container -import net.psforever.objects.serverobject.affinity.FactionAffinity -import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject} -import net.psforever.objects.serverobject.doors.InteriorDoorPassage -import net.psforever.objects.{AmmoBox, BoomerDeployable, BoomerTrigger, DummyExplodingEntity, GlobalDefinitions, OwnableByPlayer, PlanetSideGameObject, SpecialEmp, Tool} -import net.psforever.objects.serverobject.interior.Sidedness -import net.psforever.objects.serverobject.mount.Mountable -import net.psforever.objects.serverobject.turret.auto.{AutomatedTurret, AutomatedTurretBehavior} -import net.psforever.objects.sourcing.SourceEntry -import net.psforever.objects.vital.Vitality -import net.psforever.objects.vital.base.{DamageResolution, DamageType} -import net.psforever.objects.vital.etc.OicwLilBuddyReason -import net.psforever.objects.vital.interaction.DamageInteraction -import net.psforever.objects.vital.projectile.ProjectileReason -import net.psforever.objects.zones.{Zone, ZoneProjectile} -import net.psforever.packet.game.{AIDamage, AvatarGrenadeStateMessage, ChainLashMessage, ChangeAmmoMessage, ChangeFireModeMessage, ChangeFireStateMessage_Start, ChangeFireStateMessage_Stop, HitMessage, InventoryStateMessage, LashMessage, LongRangeProjectileInfoMessage, ProjectileStateMessage, QuantityUpdateMessage, ReloadMessage, SplashHitMessage, UplinkRequest, UplinkRequestType, UplinkResponse, WeaponDelayFireMessage, WeaponDryFireMessage, WeaponFireMessage, WeaponLazeTargetPositionMessage} +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, Vector3} -import net.psforever.util.Config - -import scala.concurrent.ExecutionContext.Implicits.global -import scala.concurrent.duration._ +import net.psforever.types.PlanetSideGUID object WeaponAndProjectileLogic { def apply(ops: WeaponAndProjectileOperations): WeaponAndProjectileLogic = { new WeaponAndProjectileLogic(ops, ops.context) } - - /** - * Does a line segment line intersect with a sphere?
- * This most likely belongs in `Geometry` or `GeometryForm` or somehow in association with the `\objects\geometry\` package. - * @param start first point of the line segment - * @param end second point of the line segment - * @param center center of the sphere - * @param radius radius of the sphere - * @return list of all points of intersection, if any - * @see `Vector3.DistanceSquared` - * @see `Vector3.MagnitudeSquared` - */ - private def quickLineSphereIntersectionPoints( - start: Vector3, - end: Vector3, - center: Vector3, - radius: Float - ): Iterable[Vector3] = { - /* - Algorithm adapted from code found on https://paulbourke.net/geometry/circlesphere/index.html#linesphere, - because I kept messing up proper substitution of the line formula and the circle formula into the quadratic equation. - */ - val Vector3(cx, cy, cz) = center - val Vector3(sx, sy, sz) = start - val vector = end - start - //speed our way through a quadratic equation - val (a, b) = { - val Vector3(dx, dy, dz) = vector - ( - dx * dx + dy * dy + dz * dz, - 2f * (dx * (sx - cx) + dy * (sy - cy) + dz * (sz - cz)) - ) - } - val c = Vector3.MagnitudeSquared(center) + Vector3.MagnitudeSquared(start) - 2f * (cx * sx + cy * sy + cz * sz) - radius * radius - val result = b * b - 4 * a * c - if (result < 0f) { - //negative, no intersection - Seq() - } else if (result < 0.00001f) { - //zero-ish, one intersection point - Seq(start - vector * (b / (2f * a))) - } else { - //positive, two intersection points - val sqrt = math.sqrt(result).toFloat - val endStart = vector / (2f * a) - Seq(start + endStart * (sqrt - b), start + endStart * (b + sqrt) * -1f) - }.filter(p => Vector3.DistanceSquared(start, p) <= a) - } - /** - * Preparation for explosion damage that utilizes the Scorpion's little buddy sub-projectiles. - * The main difference from "normal" server-side explosion - * is that the owner of the projectile must be clarified explicitly. - * @see `Zone::serverSideDamage` - * @param zone where the explosion is taking place - * (`source` contains the coordinate location) - * @param source a game object that represents the source of the explosion - * @param owner who or what to accredit damage from the explosion to; - * clarifies a normal `SourceEntry(source)` accreditation - */ - private def detonateLittleBuddy( - zone: Zone, - source: PlanetSideGameObject with FactionAffinity with Vitality, - proxy: Projectile, - owner: SourceEntry - )(): Unit = { - Zone.serverSideDamage(zone, source, littleBuddyExplosionDamage(owner, proxy.id, source.Position)) - } - - /** - * Preparation for explosion damage that utilizes the Scorpion's little buddy sub-projectiles. - * The main difference from "normal" server-side explosion - * is that the owner of the projectile must be clarified explicitly. - * The sub-projectiles will be the product of a normal projectile rather than a standard game object - * so a custom `source` entity must wrap around it and fulfill the requirements of the field. - * @see `Zone::explosionDamage` - * @param owner who or what to accredit damage from the explosion to - * @param explosionPosition where the explosion will be positioned in the game world - * @param source a game object that represents the source of the explosion - * @param target a game object that is affected by the explosion - * @return a `DamageInteraction` object - */ - private def littleBuddyExplosionDamage( - owner: SourceEntry, - projectileId: Long, - explosionPosition: Vector3 - ) - ( - source: PlanetSideGameObject with FactionAffinity with Vitality, - target: PlanetSideGameObject with FactionAffinity with Vitality - ): DamageInteraction = { - DamageInteraction(SourceEntry(target), OicwLilBuddyReason(owner, projectileId, target.DamageModel), explosionPosition) - } } 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 + //private val avatarActor: typed.ActorRef[AvatarActor.Command] = ops.avatarActor /* packets */ @@ -227,126 +117,11 @@ class WeaponAndProjectileLogic(val ops: WeaponAndProjectileOperations, implicit def handleDirectHit(pkt: HitMessage): Unit = { /* intentionally blank */ } - def handleSplashHit(pkt: SplashHitMessage): Unit = { - val SplashHitMessage( - _, - projectile_guid, - explosion_pos, - direct_victim_uid, - _, - projectile_vel, - _, - targets - ) = pkt - ops.FindProjectileEntry(projectile_guid) match { - case Some(projectile) => - val profile = projectile.profile - projectile.Velocity = projectile_vel - val (resolution1, resolution2) = profile.Aggravated match { - case Some(_) if profile.ProjectileDamageTypes.contains(DamageType.Aggravated) => - (DamageResolution.AggravatedDirect, DamageResolution.AggravatedSplash) - case _ => - (DamageResolution.Splash, DamageResolution.Splash) - } - //direct_victim_uid - sessionLogic.validObject(direct_victim_uid, decorator = "SplashHit/direct_victim") match { - case Some(target: PlanetSideGameObject with FactionAffinity with Vitality) => - CheckForHitPositionDiscrepancy(projectile_guid, explosion_pos, target) - ResolveProjectileInteraction(projectile, resolution1, target, target.Position).collect { resprojectile => - addShotsLanded(resprojectile.cause.attribution, shots = 1) - sessionLogic.handleDealingDamage(target, resprojectile) - } - case _ => () - } - //other victims - targets.foreach(elem => { - sessionLogic.validObject(elem.uid, decorator = "SplashHit/other_victims") match { - case Some(target: PlanetSideGameObject with FactionAffinity with Vitality) => - CheckForHitPositionDiscrepancy(projectile_guid, explosion_pos, target) - ResolveProjectileInteraction(projectile, resolution2, target, explosion_pos).collect { resprojectile => - addShotsLanded(resprojectile.cause.attribution, shots = 1) - sessionLogic.handleDealingDamage(target, resprojectile) - } - case _ => () - } - }) - //... - HandleDamageProxy(projectile, projectile_guid, explosion_pos) - if ( - projectile.profile.HasJammedEffectDuration || - projectile.profile.JammerProjectile || - projectile.profile.SympatheticExplosion - ) { - //can also substitute 'projectile.profile' for 'SpecialEmp.emp' - Zone.serverSideDamage( - continent, - player, - SpecialEmp.emp, - SpecialEmp.createEmpInteraction(SpecialEmp.emp, explosion_pos), - SpecialEmp.prepareDistanceCheck(player, explosion_pos, player.Faction), - SpecialEmp.findAllBoomers(profile.DamageRadius) - ) - } - if (profile.ExistsOnRemoteClients && projectile.HasGUID) { - //cleanup - if (projectile.HasGUID) { - continent.Projectile ! ZoneProjectile.Remove(projectile.GUID) - } - } - case None => () - } - } + def handleSplashHit(pkt: SplashHitMessage): Unit = { /* intentionally blank */ } def handleLashHit(pkt: LashMessage): Unit = { /* intentionally blank */ } - def handleAIDamage(pkt: AIDamage): Unit = { - val AIDamage(targetGuid, attackerGuid, projectileTypeId, _, _) = pkt - (continent.GUID(player.VehicleSeated) match { - case Some(tobj: PlanetSideServerObject with FactionAffinity with Vitality with OwnableByPlayer) - if tobj.GUID == targetGuid && - tobj.OwnerGuid.contains(player.GUID) => - //deployable turrets - Some(tobj) - case Some(tobj: PlanetSideServerObject with FactionAffinity with Vitality with Mountable) - if tobj.GUID == targetGuid && - tobj.Seats.values.flatMap(_.occupants.map(_.GUID)).toSeq.contains(player.GUID) => - //facility turrets, etc. - Some(tobj) - case _ - if player.GUID == targetGuid => - //player avatars - Some(player) - case _ => - None - }).collect { - case target: AutomatedTurret.Target => - sessionLogic.validObject(attackerGuid, decorator = "AIDamage/AutomatedTurret") - .collect { - case turret: AutomatedTurret if turret.Target.isEmpty => - turret.Actor ! AutomatedTurretBehavior.ConfirmShot(target) - Some(target) - - case turret: AutomatedTurret => - turret.Actor ! AutomatedTurretBehavior.ConfirmShot(target) - HandleAIDamage(target, CompileAutomatedTurretDamageData(turret, turret.TurretOwner, projectileTypeId)) - Some(target) - } - } - .orElse { - //occasionally, something that is not technically a turret's natural target may be attacked - sessionLogic.validObject(targetGuid, decorator = "AIDamage/Target") - .collect { - case target: PlanetSideServerObject with FactionAffinity with Vitality => - sessionLogic.validObject(attackerGuid, decorator = "AIDamage/Attacker") - .collect { - case turret: AutomatedTurret if turret.Target.nonEmpty => - //the turret must be shooting at something (else) first - HandleAIDamage(target, CompileAutomatedTurretDamageData(turret, turret.TurretOwner, projectileTypeId)) - } - Some(target) - } - } - } + def handleAIDamage(pkt: AIDamage): Unit = { /* intentionally blank */ } /* support code */ @@ -402,167 +177,6 @@ class WeaponAndProjectileLogic(val ops: WeaponAndProjectileOperations, implicit sendResponse(InventoryStateMessage(box.GUID, obj.GUID, capacity)) } - private def CheckForHitPositionDiscrepancy( - projectile_guid: PlanetSideGUID, - hitPos: Vector3, - target: PlanetSideGameObject with FactionAffinity with Vitality - ): Unit = { - val hitPositionDiscrepancy = Vector3.DistanceSquared(hitPos, target.Position) - if (hitPositionDiscrepancy > Config.app.antiCheat.hitPositionDiscrepancyThreshold) { - // If the target position on the server does not match the position where the projectile landed within reason there may be foul play - log.warn( - s"${player.Name}'s shot #${projectile_guid.guid} has hit discrepancy with target. Target: ${target.Position}, Reported: $hitPos, Distance: $hitPositionDiscrepancy / ${math.sqrt(hitPositionDiscrepancy).toFloat}; suspect" - ) - } - } - - /** - * na - * @param projectile the projectile object - * @param resolution the resolution status to promote the projectile - * @return a copy of the projectile - */ - private def ResolveProjectileInteraction( - projectile: Projectile, - resolution: DamageResolution.Value, - target: PlanetSideGameObject with FactionAffinity with Vitality, - pos: Vector3 - ): Option[DamageInteraction] = { - if (projectile.isMiss) { - log.warn("expected projectile was already counted as a missed shot; can not resolve any further") - None - } else { - val outProjectile = ProjectileQuality.modifiers(projectile, resolution, target, pos, Some(player)) - if (projectile.tool_def.Size == EquipmentSize.Melee && outProjectile.quality == ProjectileQuality.Modified(25)) { - avatarActor ! AvatarActor.ConsumeStamina(10) - } - Some(DamageInteraction(SourceEntry(target), ProjectileReason(resolution, outProjectile, target.DamageModel), pos)) - } - } - - /** - * Take a projectile that was introduced into the game world and - * determine if it generates a secondary damage projectile or - * an method of damage causation that requires additional management. - * @param projectile the projectile - * @param pguid the client-local projectile identifier - * @param hitPos the game world position where the projectile is being recorded - * @return a for all affected targets, a combination of projectiles, projectile location, and the target's location; - * nothing if no targets were affected - */ - private def HandleDamageProxy( - projectile: Projectile, - pguid: PlanetSideGUID, - hitPos: Vector3 - ): List[(PlanetSideGameObject with FactionAffinity with Vitality, Projectile, Vector3, Vector3)] = { - GlobalDefinitions.getDamageProxy(projectile, hitPos) match { - case Nil => - Nil - case list if list.isEmpty => - Nil - case list => - HandleDamageProxySetupLittleBuddy(list, hitPos) - UpdateProjectileSidednessAfterHit(projectile, hitPos) - val projectileSide = projectile.WhichSide - list.flatMap { proxy => - if (proxy.profile.ExistsOnRemoteClients) { - proxy.Position = hitPos - proxy.WhichSide = projectileSide - continent.Projectile ! ZoneProjectile.Add(player.GUID, proxy) - Nil - } else if (proxy.tool_def == GlobalDefinitions.maelstrom) { - //server-side maelstrom grenade target selection - val radius = proxy.profile.LashRadius * proxy.profile.LashRadius - val targets = Zone.findAllTargets(continent, hitPos, proxy.profile.LashRadius, { _.livePlayerList }) - .filter { target => - Vector3.DistanceSquared(target.Position, hitPos) <= radius - } - //chainlash is separated from the actual damage application for convenience - continent.AvatarEvents ! AvatarServiceMessage( - continent.id, - AvatarAction.SendResponse( - PlanetSideGUID(0), - ChainLashMessage( - hitPos, - projectile.profile.ObjectId, - targets.map { _.GUID } - ) - ) - ) - targets.map { target => - CheckForHitPositionDiscrepancy(pguid, hitPos, target) - (target, proxy, hitPos, target.Position) - } - } else { - Nil - } - } - } - } - - private def HandleDamageProxySetupLittleBuddy(listOfProjectiles: List[Projectile], detonationPosition: Vector3): Boolean = { - val listOfLittleBuddies: List[Projectile] = listOfProjectiles.filter { _.tool_def == GlobalDefinitions.oicw } - val size: Int = listOfLittleBuddies.size - if (size > 0) { - val desiredDownwardsProjectiles: Int = 2 - val firstHalf: Int = math.min(size, desiredDownwardsProjectiles) //number that fly straight down - val secondHalf: Int = math.max(size - firstHalf, 0) //number that are flared out - val z: Float = player.Orientation.z //player's standing direction - val north: Vector3 = Vector3(0,1,0) //map North - val speed: Float = 144f //speed (packet discovered) - val dist: Float = 25 //distance (client defined) - val downwardsAngle: Float = -85f - val flaredAngle: Float = -70f - //angle of separation for downwards, degrees from vertical for flared out - val (smallStep, smallAngle): (Float, Float) = if (firstHalf > 1) { - (360f / firstHalf, downwardsAngle) - } else { - (0f, 0f) - } - val (largeStep, largeAngle): (Float, Float) = if (secondHalf > 1) { - (360f / secondHalf, flaredAngle) - } else { - (0f, 0f) - } - val smallRotOffset: Float = z + 90f - val largeRotOffset: Float = z + math.random().toFloat * 45f - val verticalCorrection = Vector3.z(dist - dist * math.sin(math.toRadians(90 - smallAngle + largeAngle)).toFloat) - //downwards projectiles - var i: Int = 0 - listOfLittleBuddies.take(firstHalf).foreach { proxy => - val facing = (smallRotOffset + smallStep * i.toFloat) % 360 - val dir = north.Rx(smallAngle).Rz(facing) - proxy.Position = detonationPosition + dir.xy + verticalCorrection - proxy.Velocity = dir * speed - proxy.Orientation = Vector3(0, (360f + smallAngle) % 360, facing) - HandleDamageProxyLittleBuddyExplosion(proxy, dir, dist) - i += 1 - } - //flared out projectiles - i = 0 - listOfLittleBuddies.drop(firstHalf).foreach { proxy => - val facing = (largeRotOffset + largeStep * i.toFloat) % 360 - val dir = north.Rx(largeAngle).Rz(facing) - proxy.Position = detonationPosition + dir - proxy.Velocity = dir * speed - proxy.Orientation = Vector3(0, (360f + largeAngle) % 360, facing) - HandleDamageProxyLittleBuddyExplosion(proxy, dir, dist) - i += 1 - } - true - } else { - false - } - } - - private def HandleDamageProxyLittleBuddyExplosion(proxy: Projectile, orientation: Vector3, distance: Float): Unit = { - //explosion - val obj = new DummyExplodingEntity(proxy, proxy.owner.Faction) - obj.Position = obj.Position + orientation * distance - val explosionFunc: ()=>Unit = WeaponAndProjectileLogic.detonateLittleBuddy(continent, obj, proxy, proxy.owner) - context.system.scheduler.scheduleOnce(500.milliseconds) { explosionFunc() } - } - private def fireStateStartPlayerMessages(itemGuid: PlanetSideGUID): Unit = { continent.AvatarEvents ! AvatarServiceMessage( continent.id, @@ -601,81 +215,4 @@ class WeaponAndProjectileLogic(val ops: WeaponAndProjectileOperations, implicit fireStateStopUpdateChargeAndCleanup(tool) ops.fireStateStopMountedMessages(itemGuid) } - - //noinspection SameParameterValue - private def addShotsLanded(weaponId: Int, shots: Int): Unit = { - ops.addShotsToMap(ops.shotsLanded, weaponId, shots) - } - - private def CompileAutomatedTurretDamageData( - turret: AutomatedTurret, - owner: SourceEntry, - projectileTypeId: Long - ): Option[(AutomatedTurret, Tool, SourceEntry, ProjectileDefinition)] = { - turret.Weapons - .values - .flatMap { _.Equipment } - .collect { case weapon: Tool => (turret, weapon, owner, weapon.Projectile) } - .find { case (_, _, _, p) => p.ObjectId == projectileTypeId } - } - - private def HandleAIDamage( - target: PlanetSideServerObject with FactionAffinity with Vitality, - results: Option[(AutomatedTurret, Tool, SourceEntry, ProjectileDefinition)] - ): Unit = { - results.collect { - case (obj, tool, owner, projectileInfo) => - val angle = Vector3.Unit(target.Position - obj.Position) - val proj = new Projectile( - projectileInfo, - tool.Definition, - tool.FireMode, - None, - owner, - obj.Definition.ObjectId, - obj.Position + Vector3.z(value = 1f), - angle, - Some(angle * projectileInfo.FinalVelocity) - ) - val hitPos = target.Position + Vector3.z(value = 1f) - ResolveProjectileInteraction(proj, DamageResolution.Hit, target, hitPos).collect { resprojectile => - addShotsLanded(resprojectile.cause.attribution, shots = 1) - sessionLogic.handleDealingDamage(target, resprojectile) - } - } - } - - private def UpdateProjectileSidednessAfterHit(projectile: Projectile, hitPosition: Vector3): Unit = { - val origin = projectile.Position - val distance = Vector3.Magnitude(hitPosition - origin) - continent.blockMap - .sector(hitPosition, distance) - .environmentList - .collect { case o: InteriorDoorPassage => - val door = o.door - val intersectTest = WeaponAndProjectileLogic.quickLineSphereIntersectionPoints( - origin, - hitPosition, - door.Position, - door.Definition.UseRadius + 0.1f - ) - (door, intersectTest) - } - .collect { case (door, intersectionTest) if intersectionTest.nonEmpty => - (door, Vector3.Magnitude(hitPosition - door.Position), intersectionTest) - } - .minByOption { case (_, dist, _) => dist } - .foreach { case (door, _, intersects) => - val strictly = if (Vector3.DotProduct(Vector3.Unit(hitPosition - door.Position), door.Outwards) > 0f) { - Sidedness.OutsideOf - } else { - Sidedness.InsideOf - } - projectile.WhichSide = if (intersects.size == 1) { - Sidedness.InBetweenSides(door, strictly) - } else { - strictly - } - } - } } 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 5c40651fb..8821f6cd9 100644 --- a/src/main/scala/net/psforever/actors/session/support/SessionData.scala +++ b/src/main/scala/net/psforever/actors/session/support/SessionData.scala @@ -238,8 +238,9 @@ class SessionData( case Some(_: LocalLockerItem) => player.avatar.locker.Inventory.hasItem(guid) match { - case out @ Some(_) => + case out @ Some(thing) => contextSafeEntity = guid + oldRefsMap.put(guid, thing.Definition.Name) out case None if contextSafeEntity == guid => //safeguard @@ -263,9 +264,10 @@ class SessionData( None case out @ Some(obj) if obj.HasGUID => + oldRefsMap.put(guid, obj.Definition.Name) out - case None if !id.contains(PlanetSideGUID(0)) => + case None if guid != PlanetSideGUID(0) && guid != player.GUID && !player.VehicleSeated.contains(guid) => //delete stale entity reference from client //deleting guid=0 will cause BAD things to happen log.error(s"$elevatedDecorator: ${player.Name} has an invalid reference to $hint with GUID $guid in zone ${continent.id}")