mirror of
https://github.com/psforever/PSF-LoginServer.git
synced 2026-01-19 18:44:45 +00:00
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
This commit is contained in:
parent
a4b7e77d9d
commit
4cbcf3a5e4
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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?<br>
|
||||
* 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}")
|
||||
|
|
|
|||
Loading…
Reference in a new issue