Skip to content

Commit

Permalink
Bug Fixes 20240712 (#1212)
Browse files Browse the repository at this point in the history
* correctly clear the hack state of terminals when unpowered; turrets will no longer act like they have AI control if jammed when mounted; restore passive implants

* reload opened and closed doors upon zoning changes; rework ntu silo and ant interaction start; clear hack on proximity terminals; facility turrets stop indicating towards jamming cause when mounted

* radiator is turned off due to potential for server crashes; cerebus -> cerberus; turret kills name owner only when they are in the same zone; fewer chances for turrets to fire when they should not

* incorporating structural changes to hacking for future expansion

* an attempt at fixing tests was made
  • Loading branch information
Fate-JH authored Jul 29, 2024
1 parent 5990f24 commit d1dbbcb
Show file tree
Hide file tree
Showing 37 changed files with 569 additions and 409 deletions.
1 change: 1 addition & 0 deletions server/src/main/resources/overrides/game_objects0.adb.lst
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ add_property pulsar equiptime 600
add_property pulsar holstertime 600
add_property punisher equiptime 600
add_property punisher holstertime 600
add_property radiator allowed false
add_property r_shotgun equiptime 750
add_property r_shotgun holstertime 750
add_property remote_electronics_kit equiptime 500
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ import net.psforever.objects.zones.blockmap.BlockMapEntity
import net.psforever.objects.zones.{Zone, 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, Shortcut, TargetInfo, TargetingImplantRequest, TargetingInfoMessage, TerrainCondition, TradeMessage, UnuseItemMessage, UseItemMessage, VoiceHostInfo, VoiceHostRequest, ZipLineMessage}
import net.psforever.packet.game.{ActionCancelMessage, ActionResultMessage, AvatarFirstTimeEventMessage, AvatarImplantMessage, AvatarJumpMessage, BattleplanMessage, BindPlayerMessage, BindStatus, BugReportMessage, ChangeFireModeMessage, ChangeShortcutBankMessage, CharacterCreateRequestMessage, CharacterRequestAction, CharacterRequestMessage, ChatMsg, CollisionIs, ConnectToWorldRequestMessage, CreateShortcutMessage, DeadState, DeployObjectMessage, DisplayedAwardMessage, DropItemMessage, EmoteMsg, FacilityBenefitShieldChargeRequestMessage, FriendsRequest, GenericAction, GenericActionMessage, GenericCollisionMsg, GenericObjectActionAtPositionMessage, GenericObjectActionMessage, GenericObjectStateMsg, HitHint, ImplantAction, InvalidTerrainMessage, ItemTransactionMessage, LootItemMessage, MoveItemMessage, ObjectDeleteMessage, ObjectDetectedMessage, ObjectHeldMessage, PickupItemMessage, PlanetsideAttributeMessage, PlayerStateMessageUpstream, PlayerStateShiftMessage, RequestDestroyMessage, ShiftState, TargetInfo, TargetingImplantRequest, TargetingInfoMessage, TerrainCondition, TradeMessage, UnuseItemMessage, UseItemMessage, VoiceHostInfo, VoiceHostRequest, ZipLineMessage}
import net.psforever.services.RemoverActor
import net.psforever.services.account.{AccountPersistenceService, RetrieveAccountData}
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
Expand Down Expand Up @@ -1025,13 +1025,18 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex

private def handleUseResourceSilo(resourceSilo: ResourceSilo, equipment: Option[Equipment]): Unit = {
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
(continent.GUID(player.VehicleSeated), equipment) match {
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, equipment)
case _ =>
resourceSilo.Actor ! CommonMessages.Use(player)
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 _ => ()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package net.psforever.actors.session.normal
import akka.actor.ActorContext
import net.psforever.actors.session.support.{LocalHandlerFunctions, SessionData, SessionLocalHandlers}
import net.psforever.objects.ce.Deployable
import net.psforever.objects.serverobject.doors.Door
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, InventoryStateMessage, ObjectAttachMessage, ObjectCreateMessage, ObjectDeleteMessage, ObjectDetachMessage, OrbitalShuttleTimeMsg, PadAndShuttlePair, PlanetsideAttributeMessage, ProximityTerminalUseMessage, SetEmpireMessage, TriggerEffectMessage, TriggerSoundMessage, TriggeredSound, VehicleStateMessage}
Expand Down Expand Up @@ -66,7 +67,18 @@ class LocalHandlerLogic(val ops: SessionLocalHandlers, implicit val context: Act
log.warn(s"LocalResponse.Detonate: ${obj.Definition.Name} not configured to explode correctly")

case LocalResponse.DoorOpens(doorGuid) if isNotSameTarget =>
sendResponse(GenericObjectStateMsg(doorGuid, state=16))
val pos = player.Position.xy
val range = ops.doorLoadRange()
val foundDoor = continent
.blockMap
.sector(pos, range)
.amenityList
.collect { case door: Door => door }
.find(_.GUID == doorGuid)
val doorExistsInRange: Boolean = foundDoor.nonEmpty
if (doorExistsInRange) {
sendResponse(GenericObjectStateMsg(doorGuid, state=16))
}

case LocalResponse.DoorCloses(doorGuid) => //door closes for everyone
sendResponse(GenericObjectStateMsg(doorGuid, state=17))
Expand Down Expand Up @@ -95,16 +107,14 @@ class LocalHandlerLogic(val ops: SessionLocalHandlers, implicit val context: Act
case LocalResponse.EliminateDeployable(obj: TelepadDeployable, dguid, _, _) if obj.Active && obj.Destroyed =>
//if active, deactivate
obj.Active = false
sendResponse(GenericObjectActionMessage(dguid, code=29))
sendResponse(GenericObjectActionMessage(dguid, code=30))
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
sendResponse(GenericObjectActionMessage(dguid, code=29))
sendResponse(GenericObjectActionMessage(dguid, code=30))
ops.deactivateTelpadDeployableMessages(dguid)
//standard deployable elimination behavior
obj.Destroyed = true
DeconstructDeployable(obj, dguid, pos, obj.Orientation, deletionType=2)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -545,9 +545,9 @@ class WeaponAndProjectileLogic(val ops: WeaponAndProjectileOperations, implicit
turret.Actor ! AutomatedTurretBehavior.ConfirmShot(target)
Some(target)

case turret: AutomatedTurret =>
case turret: AutomatedTurret with OwnableByPlayer =>
turret.Actor ! AutomatedTurretBehavior.ConfirmShot(target)
HandleAIDamage(target, CompileAutomatedTurretDamageData(turret, turret.TurretOwner, projectileTypeId))
HandleAIDamage(target, CompileAutomatedTurretDamageData(turret, projectileTypeId))
Some(target)
}
}
Expand All @@ -558,9 +558,9 @@ class WeaponAndProjectileLogic(val ops: WeaponAndProjectileOperations, implicit
case target: PlanetSideServerObject with FactionAffinity with Vitality =>
sessionLogic.validObject(attackerGuid, decorator = "AIDamage/Attacker")
.collect {
case turret: AutomatedTurret if turret.Target.nonEmpty =>
case turret: AutomatedTurret with OwnableByPlayer if turret.Target.nonEmpty =>
//the turret must be shooting at something (else) first
HandleAIDamage(target, CompileAutomatedTurretDamageData(turret, turret.TurretOwner, projectileTypeId))
HandleAIDamage(target, CompileAutomatedTurretDamageData(turret, projectileTypeId))
}
Some(target)
}
Expand Down Expand Up @@ -1268,14 +1268,17 @@ class WeaponAndProjectileLogic(val ops: WeaponAndProjectileOperations, implicit
}

private def CompileAutomatedTurretDamageData(
turret: AutomatedTurret,
owner: SourceEntry,
turret: AutomatedTurret with OwnableByPlayer,
projectileTypeId: Long
): Option[(AutomatedTurret, Tool, SourceEntry, ProjectileDefinition)] = {
turret.Weapons
.values
.flatMap { _.Equipment }
.collect { case weapon: Tool => (turret, weapon, owner, weapon.Projectile) }
.collect {
case weapon: Tool =>
val source = Deployables.AssignBlameTo(continent, turret.OwnerName, SourceEntry(turret))
(turret, weapon, source, weapon.Projectile)
}
.find { case (_, _, _, p) => p.ObjectId == projectileTypeId }
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,16 +96,14 @@ class LocalHandlerLogic(val ops: SessionLocalHandlers, implicit val context: Act
case LocalResponse.EliminateDeployable(obj: TelepadDeployable, dguid, _, _) if obj.Active && obj.Destroyed =>
//if active, deactivate
obj.Active = false
sendResponse(GenericObjectActionMessage(dguid, code=29))
sendResponse(GenericObjectActionMessage(dguid, code=30))
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
sendResponse(GenericObjectActionMessage(dguid, code=29))
sendResponse(GenericObjectActionMessage(dguid, code=30))
ops.deactivateTelpadDeployableMessages(dguid)
//standard deployable elimination behavior
obj.Destroyed = true
DeconstructDeployable(obj, dguid, pos, obj.Orientation, deletionType=2)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import akka.actor.ActorContext
import net.psforever.objects.{Players, TurretDeployable}
import net.psforever.objects.ce.Deployable
import net.psforever.objects.guid.{GUIDTask, TaskWorkflow}
import net.psforever.objects.serverobject.interior.Sidedness
import net.psforever.packet.game.GenericObjectActionMessage
import net.psforever.services.local.LocalResponse
import net.psforever.types.PlanetSideGUID

Expand All @@ -22,7 +24,10 @@ class SessionLocalHandlers(
val sessionLogic: SessionData,
implicit val context: ActorContext
) extends CommonSessionInterfacingFunctionality {

def deactivateTelpadDeployableMessages(guid: PlanetSideGUID): Unit = {
sendResponse(GenericObjectActionMessage(guid, code = 29))
sendResponse(GenericObjectActionMessage(guid, code = 30))
}

def handleTurretDeployableIsDismissed(obj: TurretDeployable): Unit = {
Players.buildCooldownReset(continent, player.Name, obj)
Expand All @@ -33,4 +38,13 @@ class SessionLocalHandlers(
Players.buildCooldownReset(continent, player.Name, obj)
TaskWorkflow.execute(GUIDTask.unregisterObject(continent.GUID, obj))
}

def doorLoadRange(): Float = {
if (Sidedness.equals(player.WhichSide, Sidedness.InsideOf))
100f
else if (sessionLogic.general.canSeeReallyFar)
800f
else
400f
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2227,15 +2227,28 @@ class ZoningOperations(
sessionLogic.actionsToCancel()
continent.GUID(player.VehicleSeated) match {
case Some(vehicle: Vehicle) if vehicle.MountedIn.isEmpty =>
vehicle.PassengerInSeat(player) match {
case Some(0) =>
deadState = DeadState.Release // cancel movement updates
vehicle.Position = position
LoadZonePhysicalSpawnPoint(continent.id, position, Vector3.z(vehicle.Orientation.z), 0 seconds, None)
case _ => // not seated as the driver, in which case we can't move
vehicle
.PassengerInSeat(player)
.collect {
case 0 => //driver of the vehicle carries the vehicle and its passengers
deadState = DeadState.Release //cancel movement updates
vehicle.Position = position
doorsThatShouldBeClosedOrBeOpenedByRange(
player.Position,
sessionLogic.localResponse.doorLoadRange(),
position,
openRange = 100f
)
LoadZonePhysicalSpawnPoint(continent.id, position, Vector3.z(vehicle.Orientation.z), 0 seconds, None)
}
case None =>
deadState = DeadState.Release // cancel movement updates
doorsThatShouldBeClosedOrBeOpenedByRange(
player.Position,
sessionLogic.localResponse.doorLoadRange(),
position,
openRange = 100f
)
player.Position = position
sendResponse(PlayerStateShiftMessage(ShiftState(0, position, player.Orientation.z, None)))
deadState = DeadState.Alive // must be set here
Expand Down Expand Up @@ -3105,6 +3118,7 @@ class ZoningOperations(
}
})
}
doorsThatShouldBeOpenInRange(pos, range = 100f)
setAvatar = true
player.allowInteraction = true
upstreamMessageCount = 0
Expand Down Expand Up @@ -3622,6 +3636,32 @@ class ZoningOperations(
}
}

def doorsThatShouldBeClosedOrBeOpenedByRange(
closedPosition: Vector3,
closedRange: Float,
openPosition: Vector3,
openRange: Float
): Unit = {
continent
.blockMap
.sector(closedPosition, closedRange)
.amenityList
.collect { case door: Door if door.isOpen =>
sendResponse(GenericObjectStateMsg(door.GUID, state=17))
}
doorsThatShouldBeOpenInRange(openPosition, openRange)
}

def doorsThatShouldBeOpenInRange(position: Vector3, range: Float): Unit = {
continent
.blockMap
.sector(position.xy, range)
.amenityList
.collect { case door: Door if door.isOpen =>
sendResponse(GenericObjectStateMsg(door.GUID, state=16))
}
}

override protected[session] def stop(): Unit = {
zoningTimer.cancel()
spawn.respawnTimer.cancel()
Expand Down
45 changes: 45 additions & 0 deletions src/main/scala/net/psforever/objects/Deployables.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import net.psforever.objects.avatar.{Avatar, Certification}

import scala.concurrent.duration._
import net.psforever.objects.ce.{Deployable, DeployedItem}
import net.psforever.objects.sourcing.{PlayerSource, SourceEntry}
import net.psforever.objects.zones.Zone
import net.psforever.packet.game._
import net.psforever.types.PlanetSideGUID
Expand Down Expand Up @@ -261,4 +262,48 @@ object Deployables {
}
(sample intersect testDiff equals testDiff) && (sampleIntersect intersect testIntersect equals testIntersect)
}

/**
* Find a player with a given name in this zone.
* The assumption is the player is the owner of a given deployable entity.
* If the player can not be found, the deployable entity can stand in as it's own owner.
* @param zone continent in which the player should be found;
* should be the same zone as the deployable, but not required
* @param nameOpt optional player's name
* @param deployableSource deployable entity
* @return discovered player as a reference
*/
def AssignBlameTo(zone: Zone, nameOpt: Option[String], deployableSource: SourceEntry): SourceEntry = {
zone
.Players
.find(a => nameOpt.contains(a.name))
.collect { a =>
val name = a.name
Deployables.AssignBlameToFrom(name, zone.LivePlayers)
.orElse(Deployables.AssignBlameToFrom(name, zone.Corpses))
.getOrElse {
val player = PlayerSource(name, deployableSource.Faction, deployableSource.Position) //might report minor inconsistencies, e.g., exo-suit type
player.copy(unique = player.unique.copy(charId = a.id), progress = a.scorecard.CurrentLife)
}
}
.getOrElse(deployableSource)
}

/**
* Find a player with a given name from this list of possible players.
* If the player is seated, attach a shallow copy of the mounting information.
* @param name player name
* @param blameList possible players in which to find the player name
* @return discovered player as a reference, or `None` if not found
*/
private def AssignBlameToFrom(name: String, blameList: List[Player]): Option[SourceEntry] = {
blameList
.find(_.Name.equals(name))
.map { player =>
PlayerSource
.mountableAndSeat(player)
.map { case (mount, seat) => PlayerSource.inSeat(player, mount, seat) }
.getOrElse { PlayerSource(player) }
}
}
}
38 changes: 3 additions & 35 deletions src/main/scala/net/psforever/objects/MineDeployableControl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import akka.actor.{ActorContext, ActorRef, Props}
import net.psforever.objects.ce.{Deployable, DeployedItem}
import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.sourcing.{DeployableSource, PlayerSource, SourceEntry}
import net.psforever.objects.sourcing.{DeployableSource, SourceEntry}
import net.psforever.objects.vital.Vitality
import net.psforever.objects.vital.etc.TrippedMineReason
import net.psforever.objects.vital.interaction.DamageInteraction
Expand Down Expand Up @@ -98,40 +98,8 @@ object MineDeployableControl {
private case class Triggered()

def trippedMineReason(mine: ExplosiveDeployable): TrippedMineReason = {
lazy val deployableSource = DeployableSource(mine)
val zone = mine.Zone
val ownerName = mine.OwnerName
val blame = zone
.Players
.find(a => ownerName.contains(a.name))
.collect { a =>
val name = a.name
assignBlameToFrom(name, zone.LivePlayers)
.orElse(assignBlameToFrom(name, zone.Corpses))
.getOrElse {
val player = PlayerSource(name, mine.Faction, mine.Position) //might report minor inconsistencies, e.g., exo-suit type
player.copy(unique = player.unique.copy(charId = a.id), progress = a.scorecard.CurrentLife)
}
}
.getOrElse(deployableSource)
val deployableSource = DeployableSource(mine)
val blame = Deployables.AssignBlameTo(mine.Zone, mine.OwnerName, deployableSource)
TrippedMineReason(deployableSource, blame)
}

/**
* Find a player with a given name from this list of possible players.
* If the player is seated, attach a shallow copy of the mounting information.
* @param name player name
* @param blameList possible players in which to find the player name
* @return discovered player as a reference, or `None` if not found
*/
private def assignBlameToFrom(name: String, blameList: List[Player]): Option[SourceEntry] = {
blameList
.find(_.Name.equals(name))
.map { player =>
PlayerSource
.mountableAndSeat(player)
.map { case (mount, seat) => PlayerSource.inSeat(player, mount, seat) }
.getOrElse { PlayerSource(player) }
}
}
}
Loading

0 comments on commit d1dbbcb

Please sign in to comment.