Bug Fixes 20240712 (#1212)

* 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
This commit is contained in:
Fate-JH 2024-07-29 02:17:42 -04:00 committed by GitHub
parent 5990f247c9
commit d1dbbcb08f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
37 changed files with 569 additions and 409 deletions

View file

@ -76,6 +76,7 @@ add_property pulsar equiptime 600
add_property pulsar holstertime 600 add_property pulsar holstertime 600
add_property punisher equiptime 600 add_property punisher equiptime 600
add_property punisher holstertime 600 add_property punisher holstertime 600
add_property radiator allowed false
add_property r_shotgun equiptime 750 add_property r_shotgun equiptime 750
add_property r_shotgun holstertime 750 add_property r_shotgun holstertime 750
add_property remote_electronics_kit equiptime 500 add_property remote_electronics_kit equiptime 500

View file

@ -41,7 +41,7 @@ import net.psforever.objects.zones.blockmap.BlockMapEntity
import net.psforever.objects.zones.{Zone, ZoneProjectile, Zoning} import net.psforever.objects.zones.{Zone, ZoneProjectile, Zoning}
import net.psforever.packet.PlanetSideGamePacket import net.psforever.packet.PlanetSideGamePacket
import net.psforever.packet.game.objectcreate.ObjectClass 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.RemoverActor
import net.psforever.services.account.{AccountPersistenceService, RetrieveAccountData} import net.psforever.services.account.{AccountPersistenceService, RetrieveAccountData}
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage} import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
@ -1025,13 +1025,18 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
private def handleUseResourceSilo(resourceSilo: ResourceSilo, equipment: Option[Equipment]): Unit = { private def handleUseResourceSilo(resourceSilo: ResourceSilo, equipment: Option[Equipment]): Unit = {
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use") 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)) case (Some(vehicle: Vehicle), Some(item))
if GlobalDefinitions.isBattleFrameVehicle(vehicle.Definition) && if GlobalDefinitions.isBattleFrameVehicle(vehicle.Definition) &&
GlobalDefinitions.isBattleFrameNTUSiphon(item.Definition) => GlobalDefinitions.isBattleFrameNTUSiphon(item.Definition) =>
resourceSilo.Actor ! CommonMessages.Use(player, equipment) resourceSilo.Actor ! CommonMessages.Use(player, Some(vehicle))
case _ => case (Some(vehicle: Vehicle), _)
resourceSilo.Actor ! CommonMessages.Use(player) 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 _ => ()
} }
} }

View file

@ -4,6 +4,7 @@ package net.psforever.actors.session.normal
import akka.actor.ActorContext import akka.actor.ActorContext
import net.psforever.actors.session.support.{LocalHandlerFunctions, SessionData, SessionLocalHandlers} import net.psforever.actors.session.support.{LocalHandlerFunctions, SessionData, SessionLocalHandlers}
import net.psforever.objects.ce.Deployable import net.psforever.objects.ce.Deployable
import net.psforever.objects.serverobject.doors.Door
import net.psforever.objects.vehicles.MountableWeapons import net.psforever.objects.vehicles.MountableWeapons
import net.psforever.objects.{BoomerDeployable, ExplosiveDeployable, TelepadDeployable, Tool, TurretDeployable} 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} 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}
@ -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") log.warn(s"LocalResponse.Detonate: ${obj.Definition.Name} not configured to explode correctly")
case LocalResponse.DoorOpens(doorGuid) if isNotSameTarget => 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 case LocalResponse.DoorCloses(doorGuid) => //door closes for everyone
sendResponse(GenericObjectStateMsg(doorGuid, state=17)) sendResponse(GenericObjectStateMsg(doorGuid, state=17))
@ -95,16 +107,14 @@ class LocalHandlerLogic(val ops: SessionLocalHandlers, implicit val context: Act
case LocalResponse.EliminateDeployable(obj: TelepadDeployable, dguid, _, _) if obj.Active && obj.Destroyed => case LocalResponse.EliminateDeployable(obj: TelepadDeployable, dguid, _, _) if obj.Active && obj.Destroyed =>
//if active, deactivate //if active, deactivate
obj.Active = false obj.Active = false
sendResponse(GenericObjectActionMessage(dguid, code=29)) ops.deactivateTelpadDeployableMessages(dguid)
sendResponse(GenericObjectActionMessage(dguid, code=30))
//standard deployable elimination behavior //standard deployable elimination behavior
sendResponse(ObjectDeleteMessage(dguid, unk1=0)) sendResponse(ObjectDeleteMessage(dguid, unk1=0))
case LocalResponse.EliminateDeployable(obj: TelepadDeployable, dguid, pos, _) if obj.Active => case LocalResponse.EliminateDeployable(obj: TelepadDeployable, dguid, pos, _) if obj.Active =>
//if active, deactivate //if active, deactivate
obj.Active = false obj.Active = false
sendResponse(GenericObjectActionMessage(dguid, code=29)) ops.deactivateTelpadDeployableMessages(dguid)
sendResponse(GenericObjectActionMessage(dguid, code=30))
//standard deployable elimination behavior //standard deployable elimination behavior
obj.Destroyed = true obj.Destroyed = true
DeconstructDeployable(obj, dguid, pos, obj.Orientation, deletionType=2) DeconstructDeployable(obj, dguid, pos, obj.Orientation, deletionType=2)

View file

@ -545,9 +545,9 @@ class WeaponAndProjectileLogic(val ops: WeaponAndProjectileOperations, implicit
turret.Actor ! AutomatedTurretBehavior.ConfirmShot(target) turret.Actor ! AutomatedTurretBehavior.ConfirmShot(target)
Some(target) Some(target)
case turret: AutomatedTurret => case turret: AutomatedTurret with OwnableByPlayer =>
turret.Actor ! AutomatedTurretBehavior.ConfirmShot(target) turret.Actor ! AutomatedTurretBehavior.ConfirmShot(target)
HandleAIDamage(target, CompileAutomatedTurretDamageData(turret, turret.TurretOwner, projectileTypeId)) HandleAIDamage(target, CompileAutomatedTurretDamageData(turret, projectileTypeId))
Some(target) Some(target)
} }
} }
@ -558,9 +558,9 @@ class WeaponAndProjectileLogic(val ops: WeaponAndProjectileOperations, implicit
case target: PlanetSideServerObject with FactionAffinity with Vitality => case target: PlanetSideServerObject with FactionAffinity with Vitality =>
sessionLogic.validObject(attackerGuid, decorator = "AIDamage/Attacker") sessionLogic.validObject(attackerGuid, decorator = "AIDamage/Attacker")
.collect { .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 //the turret must be shooting at something (else) first
HandleAIDamage(target, CompileAutomatedTurretDamageData(turret, turret.TurretOwner, projectileTypeId)) HandleAIDamage(target, CompileAutomatedTurretDamageData(turret, projectileTypeId))
} }
Some(target) Some(target)
} }
@ -1268,14 +1268,17 @@ class WeaponAndProjectileLogic(val ops: WeaponAndProjectileOperations, implicit
} }
private def CompileAutomatedTurretDamageData( private def CompileAutomatedTurretDamageData(
turret: AutomatedTurret, turret: AutomatedTurret with OwnableByPlayer,
owner: SourceEntry,
projectileTypeId: Long projectileTypeId: Long
): Option[(AutomatedTurret, Tool, SourceEntry, ProjectileDefinition)] = { ): Option[(AutomatedTurret, Tool, SourceEntry, ProjectileDefinition)] = {
turret.Weapons turret.Weapons
.values .values
.flatMap { _.Equipment } .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 } .find { case (_, _, _, p) => p.ObjectId == projectileTypeId }
} }

View file

@ -96,16 +96,14 @@ class LocalHandlerLogic(val ops: SessionLocalHandlers, implicit val context: Act
case LocalResponse.EliminateDeployable(obj: TelepadDeployable, dguid, _, _) if obj.Active && obj.Destroyed => case LocalResponse.EliminateDeployable(obj: TelepadDeployable, dguid, _, _) if obj.Active && obj.Destroyed =>
//if active, deactivate //if active, deactivate
obj.Active = false obj.Active = false
sendResponse(GenericObjectActionMessage(dguid, code=29)) ops.deactivateTelpadDeployableMessages(dguid)
sendResponse(GenericObjectActionMessage(dguid, code=30))
//standard deployable elimination behavior //standard deployable elimination behavior
sendResponse(ObjectDeleteMessage(dguid, unk1=0)) sendResponse(ObjectDeleteMessage(dguid, unk1=0))
case LocalResponse.EliminateDeployable(obj: TelepadDeployable, dguid, pos, _) if obj.Active => case LocalResponse.EliminateDeployable(obj: TelepadDeployable, dguid, pos, _) if obj.Active =>
//if active, deactivate //if active, deactivate
obj.Active = false obj.Active = false
sendResponse(GenericObjectActionMessage(dguid, code=29)) ops.deactivateTelpadDeployableMessages(dguid)
sendResponse(GenericObjectActionMessage(dguid, code=30))
//standard deployable elimination behavior //standard deployable elimination behavior
obj.Destroyed = true obj.Destroyed = true
DeconstructDeployable(obj, dguid, pos, obj.Orientation, deletionType=2) DeconstructDeployable(obj, dguid, pos, obj.Orientation, deletionType=2)

View file

@ -5,6 +5,8 @@ import akka.actor.ActorContext
import net.psforever.objects.{Players, TurretDeployable} import net.psforever.objects.{Players, TurretDeployable}
import net.psforever.objects.ce.Deployable import net.psforever.objects.ce.Deployable
import net.psforever.objects.guid.{GUIDTask, TaskWorkflow} 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.services.local.LocalResponse
import net.psforever.types.PlanetSideGUID import net.psforever.types.PlanetSideGUID
@ -22,7 +24,10 @@ class SessionLocalHandlers(
val sessionLogic: SessionData, val sessionLogic: SessionData,
implicit val context: ActorContext implicit val context: ActorContext
) extends CommonSessionInterfacingFunctionality { ) extends CommonSessionInterfacingFunctionality {
def deactivateTelpadDeployableMessages(guid: PlanetSideGUID): Unit = {
sendResponse(GenericObjectActionMessage(guid, code = 29))
sendResponse(GenericObjectActionMessage(guid, code = 30))
}
def handleTurretDeployableIsDismissed(obj: TurretDeployable): Unit = { def handleTurretDeployableIsDismissed(obj: TurretDeployable): Unit = {
Players.buildCooldownReset(continent, player.Name, obj) Players.buildCooldownReset(continent, player.Name, obj)
@ -33,4 +38,13 @@ class SessionLocalHandlers(
Players.buildCooldownReset(continent, player.Name, obj) Players.buildCooldownReset(continent, player.Name, obj)
TaskWorkflow.execute(GUIDTask.unregisterObject(continent.GUID, 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
}
} }

View file

@ -2227,15 +2227,28 @@ class ZoningOperations(
sessionLogic.actionsToCancel() sessionLogic.actionsToCancel()
continent.GUID(player.VehicleSeated) match { continent.GUID(player.VehicleSeated) match {
case Some(vehicle: Vehicle) if vehicle.MountedIn.isEmpty => case Some(vehicle: Vehicle) if vehicle.MountedIn.isEmpty =>
vehicle.PassengerInSeat(player) match { vehicle
case Some(0) => .PassengerInSeat(player)
deadState = DeadState.Release // cancel movement updates .collect {
vehicle.Position = position case 0 => //driver of the vehicle carries the vehicle and its passengers
LoadZonePhysicalSpawnPoint(continent.id, position, Vector3.z(vehicle.Orientation.z), 0 seconds, None) deadState = DeadState.Release //cancel movement updates
case _ => // not seated as the driver, in which case we can't move 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 => case None =>
deadState = DeadState.Release // cancel movement updates deadState = DeadState.Release // cancel movement updates
doorsThatShouldBeClosedOrBeOpenedByRange(
player.Position,
sessionLogic.localResponse.doorLoadRange(),
position,
openRange = 100f
)
player.Position = position player.Position = position
sendResponse(PlayerStateShiftMessage(ShiftState(0, position, player.Orientation.z, None))) sendResponse(PlayerStateShiftMessage(ShiftState(0, position, player.Orientation.z, None)))
deadState = DeadState.Alive // must be set here deadState = DeadState.Alive // must be set here
@ -3105,6 +3118,7 @@ class ZoningOperations(
} }
}) })
} }
doorsThatShouldBeOpenInRange(pos, range = 100f)
setAvatar = true setAvatar = true
player.allowInteraction = true player.allowInteraction = true
upstreamMessageCount = 0 upstreamMessageCount = 0
@ -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 = { override protected[session] def stop(): Unit = {
zoningTimer.cancel() zoningTimer.cancel()
spawn.respawnTimer.cancel() spawn.respawnTimer.cancel()

View file

@ -6,6 +6,7 @@ import net.psforever.objects.avatar.{Avatar, Certification}
import scala.concurrent.duration._ import scala.concurrent.duration._
import net.psforever.objects.ce.{Deployable, DeployedItem} import net.psforever.objects.ce.{Deployable, DeployedItem}
import net.psforever.objects.sourcing.{PlayerSource, SourceEntry}
import net.psforever.objects.zones.Zone import net.psforever.objects.zones.Zone
import net.psforever.packet.game._ import net.psforever.packet.game._
import net.psforever.types.PlanetSideGUID import net.psforever.types.PlanetSideGUID
@ -261,4 +262,48 @@ object Deployables {
} }
(sample intersect testDiff equals testDiff) && (sampleIntersect intersect testIntersect equals testIntersect) (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) }
}
}
} }

View file

@ -5,7 +5,7 @@ import akka.actor.{ActorContext, ActorRef, Props}
import net.psforever.objects.ce.{Deployable, DeployedItem} import net.psforever.objects.ce.{Deployable, DeployedItem}
import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.affinity.FactionAffinity 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.Vitality
import net.psforever.objects.vital.etc.TrippedMineReason import net.psforever.objects.vital.etc.TrippedMineReason
import net.psforever.objects.vital.interaction.DamageInteraction import net.psforever.objects.vital.interaction.DamageInteraction
@ -98,40 +98,8 @@ object MineDeployableControl {
private case class Triggered() private case class Triggered()
def trippedMineReason(mine: ExplosiveDeployable): TrippedMineReason = { def trippedMineReason(mine: ExplosiveDeployable): TrippedMineReason = {
lazy val deployableSource = DeployableSource(mine) val deployableSource = DeployableSource(mine)
val zone = mine.Zone val blame = Deployables.AssignBlameTo(mine.Zone, mine.OwnerName, deployableSource)
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)
TrippedMineReason(deployableSource, blame) 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) }
}
}
} }

View file

@ -22,9 +22,9 @@ import scala.collection.mutable
* As deployables are added and removed, and tracked certifications are added and removed, * As deployables are added and removed, and tracked certifications are added and removed,
* these structures are updated to reflect proper count. * these structures are updated to reflect proper count.
* For example, the greatest number of spitfire turrets that can be placed is 15 (individual count) * For example, the greatest number of spitfire turrets that can be placed is 15 (individual count)
* and the greatest number of shadow turrets and cerebus turrets that can be placed is 5 each (individual counts) * and the greatest number of shadow turrets and cerberus turrets that can be placed is 5 each (individual counts)
* but the maximum number of small turrets that can be placed overall is only 15 (categorical count). * but the maximum number of small turrets that can be placed overall is only 15 (categorical count).
* Spitfire turrets, shadow turrets, and cerebus turrets are all included in the category of small turrets. * Spitfire turrets, shadow turrets, and cerberus turrets are all included in the category of small turrets.
*/ */
class DeployableToolbox { class DeployableToolbox {

View file

@ -114,7 +114,7 @@ object Deployable {
DeployedItem.jammer_mine.id -> DeployableIcon.DisruptorMine, DeployedItem.jammer_mine.id -> DeployableIcon.DisruptorMine,
DeployedItem.spitfire_turret.id -> DeployableIcon.SpitfireTurret, DeployedItem.spitfire_turret.id -> DeployableIcon.SpitfireTurret,
DeployedItem.spitfire_cloaked.id -> DeployableIcon.ShadowTurret, DeployedItem.spitfire_cloaked.id -> DeployableIcon.ShadowTurret,
DeployedItem.spitfire_aa.id -> DeployableIcon.CerebusTurret, DeployedItem.spitfire_aa.id -> DeployableIcon.cerberusTurret,
DeployedItem.motionalarmsensor.id -> DeployableIcon.MotionAlarmSensor, DeployedItem.motionalarmsensor.id -> DeployableIcon.MotionAlarmSensor,
DeployedItem.sensor_shield.id -> DeployableIcon.SensorDisruptor, DeployedItem.sensor_shield.id -> DeployableIcon.SensorDisruptor,
DeployedItem.tank_traps.id -> DeployableIcon.TRAP, DeployedItem.tank_traps.id -> DeployableIcon.TRAP,

View file

@ -10,7 +10,7 @@ object DeployedItem extends Enumeration {
final val jammer_mine = Value(420) //disruptor mine final val jammer_mine = Value(420) //disruptor mine
final val motionalarmsensor = Value(575) final val motionalarmsensor = Value(575)
final val sensor_shield = Value(752) //sensor disruptor final val sensor_shield = Value(752) //sensor disruptor
final val spitfire_aa = Value(819) //cerebus turret final val spitfire_aa = Value(819) //cerberus turret
final val spitfire_cloaked = Value(825) //shadow turret final val spitfire_cloaked = Value(825) //shadow turret
final val spitfire_turret = Value(826) final val spitfire_turret = Value(826)
final val tank_traps = Value(849) //trap final val tank_traps = Value(849) //trap

View file

@ -18,6 +18,7 @@ object GlobalDefinitionsImplant {
targeting.Name = "targeting" targeting.Name = "targeting"
targeting.InitializationDuration = 60 targeting.InitializationDuration = 60
targeting.Passive = true
audio_amplifier.Name = "audio_amplifier" audio_amplifier.Name = "audio_amplifier"
audio_amplifier.InitializationDuration = 60 audio_amplifier.InitializationDuration = 60
@ -41,9 +42,11 @@ object GlobalDefinitionsImplant {
range_magnifier.Name = "range_magnifier" range_magnifier.Name = "range_magnifier"
range_magnifier.InitializationDuration = 60 range_magnifier.InitializationDuration = 60
range_magnifier.Passive = true
second_wind.Name = "second_wind" second_wind.Name = "second_wind"
second_wind.InitializationDuration = 180 second_wind.InitializationDuration = 180
second_wind.Passive = true
silent_run.Name = "silent_run" silent_run.Name = "silent_run"
silent_run.InitializationDuration = 90 silent_run.InitializationDuration = 90

View file

@ -698,6 +698,7 @@ object GlobalDefinitionsMiscellaneous {
spawn_tube_door_coffin.Damageable = true spawn_tube_door_coffin.Damageable = true
resource_silo.Name = "resource_silo" resource_silo.Name = "resource_silo"
resource_silo.UseRadius = 22 //20
resource_silo.Damageable = false resource_silo.Damageable = false
resource_silo.Repairable = false resource_silo.Repairable = false
resource_silo.MaxNtuCapacitor = 1000 resource_silo.MaxNtuCapacitor = 1000

View file

@ -50,7 +50,7 @@ class UniqueNumberOps(
private val poolActors: Map[String, ActorRef] private val poolActors: Map[String, ActorRef]
) { ) {
/** The timeout used by all number pool `ask` messaging */ /** The timeout used by all number pool `ask` messaging */
private implicit val timeout = UniqueNumberOps.timeout private implicit val timeout: Timeout = UniqueNumberOps.timeout
/** /**
* The entry point for the entity GUID registration process. * The entry point for the entity GUID registration process.
@ -149,25 +149,26 @@ class UniqueNumberOps(
val localPool = pool val localPool = pool
val result = ask(pool, NumberPoolActor.GetAnyNumber())(timeout) val result = ask(pool, NumberPoolActor.GetAnyNumber())(timeout)
result.onComplete { result
case Success(NumberPoolActor.GiveNumber(number)) => .recover {
UniqueNumberOps.processRegisterResult( case ex: AskTimeoutException =>
localPromise, localPromise.failure(new RegisteringException(msg = s"did not register entity $localTarget in time", ex))
localTarget, }
localUns, .onComplete {
localPoolName, case Success(NumberPoolActor.GiveNumber(number)) =>
localPool, UniqueNumberOps.processRegisterResult(
number localPromise,
) localTarget,
case Success(NumberPoolActor.NoNumber(ex)) => localUns,
registrationProcessRetry(localPromise, ex, localTarget, localUns, localPools, localPoolName) localPoolName,
case msg => localPool,
UniqueNumberOps.log.warn(s"unexpected message during $localTarget's registration process - $msg") number
} )
result.recover { case Success(NumberPoolActor.NoNumber(ex)) =>
case ex: AskTimeoutException => registrationProcessRetry(localPromise, ex, localTarget, localUns, localPools, localPoolName)
localPromise.failure(new RegisteringException(msg = s"did not register entity $localTarget in time", ex)) case msg =>
} UniqueNumberOps.log.warn(s"unexpected message during $localTarget's registration process - $msg")
}
case None => case None =>
//do not log //do not log
@ -197,7 +198,7 @@ class UniqueNumberOps(
if (poolName.equals("generic")) { if (poolName.equals("generic")) {
promise.failure(new RegisteringException(msg = s"did not register entity $obj", exception)) promise.failure(new RegisteringException(msg = s"did not register entity $obj", exception))
} else { } else {
org.log4s.getLogger("UniqueNumberOps").warn(s"${exception.getLocalizedMessage()} - $poolName") UniqueNumberOps.log.warn(s"${exception.getLocalizedMessage()} - $poolName")
promise.completeWith(registrationProcess(obj, guid, pools, poolName = "generic")) promise.completeWith(registrationProcess(obj, guid, pools, poolName = "generic"))
} }
} }
@ -302,7 +303,7 @@ class UniqueNumberOps(
object UniqueNumberOps { object UniqueNumberOps {
private val log = org.log4s.getLogger private val log = org.log4s.getLogger
private implicit val timeout = Timeout(2.seconds) private implicit val timeout: Timeout = Timeout(4.seconds)
/** /**
* Final step of the object registration process. * Final step of the object registration process.
@ -431,7 +432,7 @@ class UniqueNumberSetup(
) extends Actor { ) extends Actor {
init() init()
final def receive: Receive = { case _ => ; } final def receive: Receive = { case _ => () }
def init(): UniqueNumberOps = { def init(): UniqueNumberOps = {
new UniqueNumberOps(hub, poolActorConversionFunc(context, hub)) new UniqueNumberOps(hub, poolActorConversionFunc(context, hub))

View file

@ -10,7 +10,8 @@ object CommonMessages {
final case class Unuse(player: Player, data: Option[Any] = None) final case class Unuse(player: Player, data: Option[Any] = None)
final case class Hack(player: Player, obj: PlanetSideServerObject with Hackable, data: Option[Any] = None) final case class Hack(player: Player, obj: PlanetSideServerObject with Hackable, data: Option[Any] = None)
final case class ClearHack() final case class ClearHack()
final case class EntityHackState(obj: PlanetSideGameObject with Hackable, hackState: Boolean)
/** /**
* The message that progresses some form of user-driven activity with a certain eventual outcome * The message that progresses some form of user-driven activity with a certain eventual outcome
* and potential feedback per cycle. * and potential feedback per cycle.

View file

@ -77,7 +77,7 @@ object Interference {
val sector = zone.blockMap.sector(position, Interference.MaxRange) val sector = zone.blockMap.sector(position, Interference.MaxRange)
val targets = (sector.deployableList ++ sector.vehicleList.filter(_.DeploymentState >= DriveState.Deploying)) val targets = (sector.deployableList ++ sector.vehicleList.filter(_.DeploymentState >= DriveState.Deploying))
.collect { case target: PlanetSideGameObject with FactionAffinity .collect { case target: PlanetSideGameObject with FactionAffinity
if target.Faction != faction && if target.Faction == faction &&
(target.Definition.asInstanceOf[ObjectDefinition].interference ne Interference.AllowAll) => (target.Definition.asInstanceOf[ObjectDefinition].interference ne Interference.AllowAll) =>
target target
} }

View file

@ -31,12 +31,12 @@ object GenericHackables {
turretUpgradeTime turretUpgradeTime
} }
/** /**
* na * na
* *
* @param player the player doing the hacking * @param player the player doing the hacking
* @param obj the object being hacked * @param obj the object being hacked
* @return the percentage amount of progress per tick * @return the percentage amount of progress per tick
*/ */
def GetHackSpeed(player: Player, obj: PlanetSideServerObject): Float = { def GetHackSpeed(player: Player, obj: PlanetSideServerObject): Float = {
val playerHackLevel = player.avatar.hackingSkillLevel() val playerHackLevel = player.avatar.hackingSkillLevel()
val timeToHack = obj match { val timeToHack = obj match {
@ -60,23 +60,23 @@ object GenericHackables {
} }
/** /**
* Evaluate the progress of the user applying a tool to modify some server object. * Evaluate the progress of the user applying a tool to modify some server object.
* This action is using the remote electronics kit to convert an enemy unit into an allied unit, primarily. * This action is using the remote electronics kit to convert an enemy unit into an allied unit, primarily.
* The act of transforming allied units of one kind into allied units of another kind (facility turret upgrades) * The act of transforming allied units of one kind into allied units of another kind (facility turret upgrades)
* is also governed by this action per tick of progress. * is also governed by this action per tick of progress.
* @see `HackMessage` * @see `HackMessage`
* @see `HackState` * @see `HackState`
* @param progressType 1 - remote electronics kit hack (various ...); * @param progressType 1 - remote electronics kit hack (various ...);
* 2 - nano dispenser (upgrade canister) turret upgrade * 2 - nano dispenser (upgrade canister) turret upgrade
* @param tplayer the player performing the action * @param tplayer the player performing the action
* @param target the object being affected * @param target the object being affected
* @param tool_guid the tool being used to affest the object * @param tool_guid the tool being used to affest the object
* @param progress the current progress value * @param progress the current progress value
* @return `true`, if the next cycle of progress should occur; * @return `true`, if the next cycle of progress should occur;
* `false`, otherwise * `false`, otherwise
*/ */
def HackingTickAction(progressType: Int, tplayer: Player, target: PlanetSideServerObject, tool_guid: PlanetSideGUID)( def HackingTickAction(progressType: Int, tplayer: Player, target: PlanetSideServerObject, tool_guid: PlanetSideGUID)(
progress: Float progress: Float
): Boolean = { ): Boolean = {
//hack state for progress bar visibility //hack state for progress bar visibility
val vis = if (progress <= 0L) { val vis = if (progress <= 0L) {
@ -108,37 +108,40 @@ object GenericHackables {
} }
/** /**
* The process of hacking an object is completed. * The process of hacking an object is completed.
* Pass the message onto the hackable object and onto the local events system. * Pass the message onto the hackable object and onto the local events system.
* @param target the `Hackable` object that has been hacked * @param target the `Hackable` object that has been hacked
* @param user the player that is performing this hacking task * @param user the player that is performing this hacking task
* @param unk na; * @param unk na;
* used by `HackMessage` as `unk5` * used by `HackMessage` as `unk5`
* @see `HackMessage` * @see `HackMessage`
*/ */
//TODO add params here depending on which params in HackMessage are important //TODO add params here depending on which params in HackMessage are important
def FinishHacking(target: PlanetSideServerObject with Hackable, user: Player, unk: Long)(): Unit = { def FinishHacking(target: PlanetSideServerObject with Hackable, user: Player, unk: Long)(): Unit = {
import akka.pattern.ask import akka.pattern.ask
import scala.concurrent.duration._ import scala.concurrent.duration._
log.info(s"${user.Name} hacked a ${target.Definition.Name}")
// Wait for the target actor to set the HackedBy property, otherwise LocalAction.HackTemporarily will not complete properly // Wait for the target actor to set the HackedBy property, otherwise LocalAction.HackTemporarily will not complete properly
import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.ExecutionContext.Implicits.global
val tplayer = user val tplayer = user
ask(target.Actor, CommonMessages.Hack(tplayer, target))(1 second).mapTo[Boolean].onComplete { ask(target.Actor, CommonMessages.Hack(tplayer, target))(timeout = 2 second)
case Success(_) => .mapTo[CommonMessages.EntityHackState]
val zone = target.Zone .onComplete {
val zoneId = zone.id case Success(_) =>
val pguid = tplayer.GUID val zone = target.Zone
zone.LocalEvents ! LocalServiceMessage( val zoneId = zone.id
zoneId, val pguid = tplayer.GUID
LocalAction.TriggerSound(pguid, target.HackSound, tplayer.Position, 30, 0.49803925f) log.info(s"${user.Name} hacked a ${target.Definition.Name}")
) zone.LocalEvents ! LocalServiceMessage(
zone.LocalEvents ! LocalServiceMessage( zoneId,
zoneId, LocalAction.TriggerSound(pguid, target.HackSound, tplayer.Position, 30, 0.49803925f)
LocalAction )
.HackTemporarily(pguid, zone, target, unk, target.HackEffectDuration(user.avatar.hackingSkillLevel())) zone.LocalEvents ! LocalServiceMessage(
) zoneId,
case Failure(_) => log.warn(s"Hack message failed on target guid: ${target.GUID}") LocalAction
} .HackTemporarily(pguid, zone, target, unk, target.HackEffectDuration(user.avatar.hackingSkillLevel()))
)
case Failure(_) =>
log.warn(s"Hack message failed on target: ${target.Definition.Name}@${target.GUID.guid}")
}
} }
} }

View file

@ -1,29 +1,44 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.hackable package net.psforever.objects.serverobject.hackable
import akka.actor.Actor import akka.actor.{Actor, ActorRef}
import net.psforever.objects.{PlanetSideGameObject, Player}
import net.psforever.objects.serverobject.CommonMessages import net.psforever.objects.serverobject.CommonMessages
import scala.annotation.unused
object HackableBehavior { object HackableBehavior {
/** /**
* The logic governing generic `Hackable` objects that use the `Hack` and `ClearHack` message. * The logic governing generic `Hackable` objects that use the `Hack` and `ClearHack` message.
* This is a mix-in trait for combining with existing `Receive` logic. * This is a mix-in trait for combining with existing `Receive` logic.
* @see `Hackable` * @see `Hackable`
*/ */
trait GenericHackable { trait GenericHackable {
this: Actor => this: Actor =>
def HackableObject: Hackable def HackableObject: PlanetSideGameObject with Hackable
val hackableBehavior: Receive = {
case CommonMessages.Hack(player, _, _) =>
val obj = HackableObject
obj.HackedBy = player
sender() ! true
val clearHackBehavior: Receive = {
case CommonMessages.ClearHack() => case CommonMessages.ClearHack() =>
val obj = HackableObject performClearHack(None, sender())
obj.HackedBy = None }
val hackableBehavior: Receive = clearHackBehavior
.orElse {
case CommonMessages.Hack(player, _, _) =>
performHack(player, None, sender())
}
def performHack(player: Player, @unused data: Option[Any], replyTo: ActorRef): Unit = {
val obj = HackableObject
obj.HackedBy = player
replyTo ! CommonMessages.EntityHackState(obj, hackState = true)
}
def performClearHack(@unused data: Option[Any], replyTo: ActorRef): Unit = {
val obj = HackableObject
obj.HackedBy = None
replyTo ! CommonMessages.EntityHackState(obj, hackState = false)
} }
} }
} }

View file

@ -1,7 +1,7 @@
// Copyright (c) 2019 PSForever // Copyright (c) 2019 PSForever
package net.psforever.objects.serverobject.resourcesilo package net.psforever.objects.serverobject.resourcesilo
import akka.actor.{Actor, ActorRef} import akka.actor.{Actor, ActorRef, Cancellable}
import net.psforever.objects.serverobject.CommonMessages import net.psforever.objects.serverobject.CommonMessages
import net.psforever.actors.zone.BuildingActor import net.psforever.actors.zone.BuildingActor
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior} import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
@ -53,37 +53,31 @@ class ResourceSiloControl(resourceSilo: ResourceSilo)
checkBehavior checkBehavior
.orElse(storageBehavior) .orElse(storageBehavior)
.orElse { .orElse {
case CommonMessages.Use(player, _) => case CommonMessages.Use(_, Some(vehicle: Vehicle))
if GlobalDefinitions.isBattleFrameVehicle(vehicle.Definition) =>
val siloFaction = resourceSilo.Faction val siloFaction = resourceSilo.Faction
val playerFaction = player.Faction val vehicleFaction = vehicle.Faction
resourceSilo.Zone.Vehicles.find(v => v.PassengerInSeat(player).contains(0)) match { val msgOpt = if (siloFaction == vehicleFaction) {
case Some(vehicle) => Some(TransferBehavior.Discharging(Ntu.Nanites))
(if (GlobalDefinitions.isBattleFrameVehicle(vehicle.Definition)) { } else if (resourceSilo.MaxNtuCapacitor * 0.4f < resourceSilo.NtuCapacitor) {
//bfr's discharge into friendly silos and charge from enemy and neutral silos //the bfr never drains below 40%
if (siloFaction == playerFaction) { Some(TransferBehavior.Charging(Ntu.Nanites))
Some(TransferBehavior.Discharging(Ntu.Nanites)) } else {
} else if (resourceSilo.MaxNtuCapacitor * 0.4f < resourceSilo.NtuCapacitor) { None
//the bfr never drains below 40%
Some(TransferBehavior.Charging(Ntu.Nanites))
} else {
None
}
} else if(siloFaction == PlanetSideEmpire.NEUTRAL || siloFaction == playerFaction) {
//ants discharge into neutral and friendly silos
Some(TransferBehavior.Discharging(Ntu.Nanites))
} else {
None
}) match {
case Some(msg) =>
context.system.scheduler.scheduleOnce(
delay = 1000 milliseconds,
vehicle.Actor,
msg
)
case None => ;
}
case _ => ;
} }
msgOpt.collect { SendNtuMessage(vehicle.Actor, _) }
case CommonMessages.Use(_, Some(vehicle: Vehicle))
if vehicle.Definition == GlobalDefinitions.ant =>
val siloFaction = resourceSilo.Faction
val vehicleFaction = vehicle.Faction
val msgOpt = if (siloFaction == PlanetSideEmpire.NEUTRAL || siloFaction == vehicleFaction) {
//ants discharge into neutral and friendly silos
Some(TransferBehavior.Discharging(Ntu.Nanites))
} else {
None
}
msgOpt.collect { SendNtuMessage(vehicle.Actor, _) }
case ResourceSilo.LowNtuWarning(enabled: Boolean) => case ResourceSilo.LowNtuWarning(enabled: Boolean) =>
LowNtuWarning(enabled) LowNtuWarning(enabled)
@ -91,9 +85,13 @@ class ResourceSiloControl(resourceSilo: ResourceSilo)
case ResourceSilo.UpdateChargeLevel(amount: Float) => case ResourceSilo.UpdateChargeLevel(amount: Float) =>
UpdateChargeLevel(amount) UpdateChargeLevel(amount)
case _ => ; case _ => ()
} }
def SendNtuMessage(replyTo: ActorRef, msg: Any): Cancellable = {
context.system.scheduler.scheduleOnce(delay = 1000 milliseconds, replyTo, msg)
}
def LowNtuWarning(enabled: Boolean): Unit = { def LowNtuWarning(enabled: Boolean): Unit = {
resourceSilo.LowNtuWarningOn = enabled resourceSilo.LowNtuWarningOn = enabled
log.trace(s"LowNtuWarning: Silo ${resourceSilo.GUID} low ntu warning set to $enabled") log.trace(s"LowNtuWarning: Silo ${resourceSilo.GUID} low ntu warning set to $enabled")
@ -147,6 +145,7 @@ class ResourceSiloControl(resourceSilo: ResourceSilo)
* The silo will agree to offers until its nanite capacitor is completely full. * The silo will agree to offers until its nanite capacitor is completely full.
*/ */
def HandleNtuOffer(sender: ActorRef, src: NtuContainer): Unit = { def HandleNtuOffer(sender: ActorRef, src: NtuContainer): Unit = {
//todo if I want to test conditions towards continuing ntu transfers, do those here
sender ! (if (resourceSilo.NtuCapacitor < resourceSilo.MaxNtuCapacitor) { sender ! (if (resourceSilo.NtuCapacitor < resourceSilo.MaxNtuCapacitor) {
Ntu.Request(0, resourceSilo.MaxNtuCapacitor - resourceSilo.NtuCapacitor) Ntu.Request(0, resourceSilo.MaxNtuCapacitor - resourceSilo.NtuCapacitor)
} else { } else {

View file

@ -5,6 +5,7 @@ import akka.actor.{ActorRef, Cancellable}
import net.psforever.objects.sourcing.AmenitySource import net.psforever.objects.sourcing.AmenitySource
import org.log4s.Logger import org.log4s.Logger
import scala.annotation.unused
import scala.collection.mutable import scala.collection.mutable
import scala.concurrent.duration._ import scala.concurrent.duration._
// //
@ -121,6 +122,7 @@ class ProximityTerminalControl(term: Terminal with ProximityUnit)
} }
def unpoweredStateLogic : Receive = commonBehavior def unpoweredStateLogic : Receive = commonBehavior
.orElse(clearHackBehavior)
.orElse { .orElse {
case CommonMessages.Use(_, _) => case CommonMessages.Use(_, _) =>
log.warn(s"unexpected format for CommonMessages.Use in this context") log.warn(s"unexpected format for CommonMessages.Use in this context")
@ -145,7 +147,7 @@ class ProximityTerminalControl(term: Terminal with ProximityUnit)
isPowered && super.tryAutoRepair() isPowered && super.tryAutoRepair()
} }
def Use(target: PlanetSideGameObject, zone: String, callback: ActorRef): Unit = { def Use(target: PlanetSideGameObject, @unused zone: String, callback: ActorRef): Unit = {
val hadNoUsers = term.NumberUsers == 0 val hadNoUsers = term.NumberUsers == 0
if (term.AddUser(target)) { if (term.AddUser(target)) {
log.trace(s"ProximityTerminal.Use: unit ${term.Definition.Name}@${term.GUID.guid} will act on $target") log.trace(s"ProximityTerminal.Use: unit ${term.Definition.Name}@${term.GUID.guid} will act on $target")
@ -169,7 +171,7 @@ class ProximityTerminalControl(term: Terminal with ProximityUnit)
} }
} }
def Unuse(target: PlanetSideGameObject, zone: String): Unit = { def Unuse(target: PlanetSideGameObject, @unused zone: String): Unit = {
val whereTarget = term.Targets.indexWhere(_ eq target) val whereTarget = term.Targets.indexWhere(_ eq target)
val previousUsers = term.NumberUsers val previousUsers = term.NumberUsers
val hadUsers = previousUsers > 0 val hadUsers = previousUsers > 0
@ -223,7 +225,7 @@ object ProximityTerminalControl {
* @param target the object being affected by the unit * @param target the object being affected by the unit
*/ */
def selectAndTryProximityUnitBehavior( def selectAndTryProximityUnitBehavior(
callback: ActorRef, @unused callback: ActorRef,
terminal: Terminal with ProximityUnit, terminal: Terminal with ProximityUnit,
target: PlanetSideGameObject target: PlanetSideGameObject
): Boolean = { ): Boolean = {

View file

@ -19,24 +19,24 @@ import net.psforever.services.local.{LocalAction, LocalServiceMessage}
* @param term the `Terminal` object being governed * @param term the `Terminal` object being governed
*/ */
class TerminalControl(term: Terminal) class TerminalControl(term: Terminal)
extends PoweredAmenityControl extends PoweredAmenityControl
with FactionAffinityBehavior.Check with FactionAffinityBehavior.Check
with HackableBehavior.GenericHackable with HackableBehavior.GenericHackable
with DamageableAmenity with DamageableAmenity
with RepairableAmenity with RepairableAmenity
with AmenityAutoRepair { with AmenityAutoRepair {
def FactionObject = term def FactionObject: Terminal = term
def HackableObject = term def HackableObject: Terminal = term
def DamageableObject = term def DamageableObject: Terminal = term
def RepairableObject = term def RepairableObject: Terminal = term
def AutoRepairObject = term def AutoRepairObject: Terminal = term
val commonBehavior: Receive = checkBehavior val commonBehavior: Receive = checkBehavior
.orElse(takesDamage) .orElse(takesDamage)
.orElse(canBeRepairedByNanoDispenser) .orElse(canBeRepairedByNanoDispenser)
.orElse(autoRepairBehavior) .orElse(autoRepairBehavior)
def poweredStateLogic : Receive = def poweredStateLogic: Receive =
commonBehavior commonBehavior
.orElse(hackableBehavior) .orElse(hackableBehavior)
.orElse { .orElse {
@ -53,18 +53,19 @@ class TerminalControl(term: Terminal)
GenericHackables.FinishHacking(term, player, 3212836864L), GenericHackables.FinishHacking(term, player, 3212836864L),
GenericHackables.HackingTickAction(progressType = 1, player, term, item.GUID) GenericHackables.HackingTickAction(progressType = 1, player, term, item.GUID)
) )
case _ => ; case _ => ()
} }
case _ => ; case _ => ()
} }
def unpoweredStateLogic : Receive = commonBehavior def unpoweredStateLogic : Receive = commonBehavior
.orElse(clearHackBehavior)
.orElse { .orElse {
case Terminal.Request(player, msg) => case Terminal.Request(player, msg) =>
sender() ! Terminal.TerminalMessage(player, msg, Terminal.NoDeal()) sender() ! Terminal.TerminalMessage(player, msg, Terminal.NoDeal())
case _ => ; case _ => ()
} }
override protected def DamageAwareness(target: Target, cause: DamageResult, amount: Any) : Unit = { override protected def DamageAwareness(target: Target, cause: DamageResult, amount: Any) : Unit = {

View file

@ -1,6 +1,5 @@
package net.psforever.objects.serverobject.terminals.capture package net.psforever.objects.serverobject.terminals.capture
import akka.util.Timeout
import net.psforever.objects.Player import net.psforever.objects.Player
import net.psforever.objects.serverobject.CommonMessages import net.psforever.objects.serverobject.CommonMessages
import net.psforever.objects.sourcing.PlayerSource import net.psforever.objects.sourcing.PlayerSource
@ -10,17 +9,16 @@ import scala.util.{Failure, Success}
object CaptureTerminals {import scala.concurrent.duration._ object CaptureTerminals {import scala.concurrent.duration._
private val log = org.log4s.getLogger("CaptureTerminals") private val log = org.log4s.getLogger("CaptureTerminals")
private implicit val timeout: Timeout = 1.second
/** /**
* The process of hacking an object is completed. * The process of hacking an object is completed.
* Pass the message onto the hackable object and onto the local events system. * Pass the message onto the hackable object and onto the local events system.
* @param target the `Hackable` object that has been hacked * @param target the `Hackable` object that has been hacked
* @param hackingPlayer The player that hacked the control console * @param hackingPlayer The player that hacked the control console
* @param unk na; * @param unk na;
* used by `HackMessage` as `unk5` * used by `HackMessage` as `unk5`
* @see `HackMessage` * @see `HackMessage`
*/ */
//TODO add params here depending on which params in HackMessage are important //TODO add params here depending on which params in HackMessage are important
def FinishHackingCaptureConsole(target: CaptureTerminal, hackingPlayer: Player, unk: Long)(): Unit = { def FinishHackingCaptureConsole(target: CaptureTerminal, hackingPlayer: Player, unk: Long)(): Unit = {
import akka.pattern.ask import akka.pattern.ask
@ -28,31 +26,33 @@ object CaptureTerminals {import scala.concurrent.duration._
log.info(s"${hackingPlayer.toString} hacked a ${target.Definition.Name}") log.info(s"${hackingPlayer.toString} hacked a ${target.Definition.Name}")
// Wait for the target actor to set the HackedBy property // Wait for the target actor to set the HackedBy property
import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.ExecutionContext.Implicits.global
ask(target.Actor, CommonMessages.Hack(hackingPlayer, target)).mapTo[Boolean].onComplete { ask(target.Actor, CommonMessages.Hack(hackingPlayer, target))(timeout = 2 second)
case Success(_) => .mapTo[CommonMessages.EntityHackState]
val zone = target.Zone .onComplete {
val zoneid = zone.id case Success(_) =>
val events = zone.LocalEvents val zone = target.Zone
val isResecured = hackingPlayer.Faction == target.Faction val zoneid = zone.id
events ! LocalServiceMessage( val events = zone.LocalEvents
zoneid, val isResecured = hackingPlayer.Faction == target.Faction
LocalAction.TriggerSound(hackingPlayer.GUID, target.HackSound, hackingPlayer.Position, 30, 0.49803925f)
)
if (isResecured) {
// Resecure the CC
events ! LocalServiceMessage( events ! LocalServiceMessage(
zoneid, zoneid,
LocalAction.ResecureCaptureTerminal(target, PlayerSource(hackingPlayer)) LocalAction.TriggerSound(hackingPlayer.GUID, target.HackSound, hackingPlayer.Position, 30, 0.49803925f)
) )
} else { if (isResecured) {
// Start the CC hack timer // Resecure the CC
events ! LocalServiceMessage( events ! LocalServiceMessage(
zoneid, zoneid,
LocalAction.StartCaptureTerminalHack(target) LocalAction.ResecureCaptureTerminal(target, PlayerSource(hackingPlayer))
) )
} } else {
case Failure(_) => // Start the CC hack timer
log.warn(s"Hack message failed on target guid: ${target.GUID}") events ! LocalServiceMessage(
} zoneid,
LocalAction.StartCaptureTerminalHack(target)
)
}
case Failure(_) =>
log.warn(s"Hack message failed on target guid: ${target.GUID}")
}
} }
} }

View file

@ -125,7 +125,7 @@ class ImplantTerminalMechControl(mech: ImplantTerminalMech)
seat.unmount(player) seat.unmount(player)
player.VehicleSeated = None player.VehicleSeated = None
if (player.HasGUID) { if (player.HasGUID) {
events ! VehicleServiceMessage(zoneId, VehicleAction.KickPassenger(player.GUID, 4, false, guid)) events ! VehicleServiceMessage(zoneId, VehicleAction.KickPassenger(player.GUID, 4, unk2 = false, guid))
} }
case None => ; case None => ;
} }

View file

@ -104,9 +104,14 @@ class FacilityTurretControl(turret: FacilityTurret)
} }
override protected def tryMount(obj: PlanetSideServerObject with Mountable, seatNumber: Int, player: Player): Boolean = { override protected def tryMount(obj: PlanetSideServerObject with Mountable, seatNumber: Int, player: Player): Boolean = {
val originalAutoState = AutomaticOperation
AutomaticOperation = false //turn off AutomaticOperation = false //turn off
if (AutomaticOperationPossible && JammableObject.Jammed) {
val zone = TurretObject.Zone
AutomatedTurretBehavior.stopTracking(zone, zone.id, TurretObject.GUID) //can not recover lost jamming aggro
}
if (!super.tryMount(obj, seatNumber, player)) { if (!super.tryMount(obj, seatNumber, player)) {
AutomaticOperation = true //revert? AutomaticOperation = originalAutoState //revert
false false
} else { } else {
true true
@ -215,6 +220,15 @@ class FacilityTurretControl(turret: FacilityTurret)
!TurretObject.isUpgrading !TurretObject.isUpgrading
} }
override def AutomaticOperationPossible: Boolean = {
super.AutomaticOperationPossible &&
(turret.Owner match {
case b: Building if b.CaptureTerminal.isEmpty => false
case b: Building => !b.CaptureTerminal.exists(_.Definition == GlobalDefinitions.secondary_capture)
case _ => false
})
}
private def primaryWeaponFireModeOnly(): Unit = { private def primaryWeaponFireModeOnly(): Unit = {
if (testToResetToDefaultFireMode) { if (testToResetToDefaultFireMode) {
val zone = TurretObject.Zone val zone = TurretObject.Zone
@ -289,16 +303,15 @@ class FacilityTurretControl(turret: FacilityTurret)
} }
override def TryJammerEffectActivate(target: Any, cause: DamageResult): Unit = { override def TryJammerEffectActivate(target: Any, cause: DamageResult): Unit = {
val startsUnjammed = !JammableObject.Jammed
super.TryJammerEffectActivate(target, cause) super.TryJammerEffectActivate(target, cause)
if (JammableObject.Jammed && AutomatedTurretObject.Definition.AutoFire.exists(_.retaliatoryDelay > 0)) { if (AutomaticOperationPossible && AutomaticOperation && JammableObject.Jammed) {
if (startsUnjammed) { AutomaticOperation = false
AutomaticOperation = false if (!MountableObject.Seats.values.exists(_.isOccupied) && AutomatedTurretObject.Definition.AutoFire.exists(_.retaliatoryDelay > 0)) {
} //look in direction of cause of jamming
//look in direction of cause of jamming val zone = JammableObject.Zone
val zone = JammableObject.Zone AutomatedTurretBehavior.getAttackVectorFromCause(zone, cause).foreach { attacker =>
AutomatedTurretBehavior.getAttackVectorFromCause(zone, cause).foreach { attacker => AutomatedTurretBehavior.startTracking(zone, zone.id, JammableObject.GUID, List(attacker.GUID))
AutomatedTurretBehavior.startTracking(zone, zone.id, JammableObject.GUID, List(attacker.GUID)) }
} }
} }
} }

View file

@ -80,6 +80,8 @@ trait AutomatedTurretBehavior {
Actor.emptyBehavior Actor.emptyBehavior
} }
def AutomaticOperationPossible: Boolean = autoStats.isDefined
def AutomaticOperation: Boolean = automaticOperation def AutomaticOperation: Boolean = automaticOperation
/** /**
@ -111,7 +113,7 @@ trait AutomatedTurretBehavior {
* @return `true`, if it would be possible for automated behavior to become operational; * @return `true`, if it would be possible for automated behavior to become operational;
* `false`, otherwise * `false`, otherwise
*/ */
protected def AutomaticOperationFunctionalityChecks: Boolean = { autoStats.isDefined } protected def AutomaticOperationFunctionalityChecks: Boolean = AutomaticOperationPossible
/** /**
* The last time weapons fire from the turret was confirmed by this control agency. * The last time weapons fire from the turret was confirmed by this control agency.

View file

@ -288,7 +288,7 @@ class Zone(val id: String, val map: ZoneMap, zoneNumber: Int) {
* @return synchronized reference to the globally unique identifier system * @return synchronized reference to the globally unique identifier system
*/ */
def GUID(hub: NumberPoolHub): Boolean = { def GUID(hub: NumberPoolHub): Boolean = {
if (actor == null && guid.Pools.values.foldLeft(0)(_ + _.Count) == 0) { if (actor == Default.typed.Actor && guid.Pools.values.foldLeft(0)(_ + _.Count) == 0) {
import org.fusesource.jansi.Ansi.Color.RED import org.fusesource.jansi.Ansi.Color.RED
import org.fusesource.jansi.Ansi.ansi import org.fusesource.jansi.Ansi.ansi
println( println(

View file

@ -26,7 +26,7 @@ object DeploymentAction extends Enumeration {
object DeployableIcon extends Enumeration { object DeployableIcon extends Enumeration {
type Type = Value type Type = Value
val Boomer, HEMine, MotionAlarmSensor, SpitfireTurret, RouterTelepad, DisruptorMine, ShadowTurret, CerebusTurret, val Boomer, HEMine, MotionAlarmSensor, SpitfireTurret, RouterTelepad, DisruptorMine, ShadowTurret, cerberusTurret,
TRAP, AegisShieldGenerator, FieldTurret, SensorDisruptor = Value TRAP, AegisShieldGenerator, FieldTurret, SensorDisruptor = Value
implicit val codec: Codec[DeployableIcon.Value] = PacketHelpers.createEnumerationCodec(this, uint4L) implicit val codec: Codec[DeployableIcon.Value] = PacketHelpers.createEnumerationCodec(this, uint4L)

View file

@ -32,7 +32,7 @@ import scodec.codecs._
* `86 - max spitfire turrets`<br> * `86 - max spitfire turrets`<br>
* `87 - max motion sensors`<br> * `87 - max motion sensors`<br>
* `88 - max shadow turrets`<br> * `88 - max shadow turrets`<br>
* `89 - max cerebus turrets`<br> * `89 - max cerberus turrets`<br>
* `90 - max Aegis shield generators`<br> * `90 - max Aegis shield generators`<br>
* `91 - max TRAPs`<br> * `91 - max TRAPs`<br>
* `92 - max OMFTs`<br> * `92 - max OMFTs`<br>
@ -43,7 +43,7 @@ import scodec.codecs._
* `97 - spitfire turrets`<br> * `97 - spitfire turrets`<br>
* `98 - motion sensors`<br> * `98 - motion sensors`<br>
* `99 - shadow turrets`<br> * `99 - shadow turrets`<br>
* `100 - cerebus turrets`<br> * `100 - cerberus turrets`<br>
* `101 - Aegis shield generators`<br> * `101 - Aegis shield generators`<br>
* `102 - TRAPSs`<br> * `102 - TRAPSs`<br>
* `103 - OMFTs`<br> * `103 - OMFTs`<br>

View file

@ -4,6 +4,7 @@ package base
import akka.actor.{Actor, ActorRef, ActorSystem, Props} import akka.actor.{Actor, ActorRef, ActorSystem, Props}
import akka.testkit.{ImplicitSender, TestKit} import akka.testkit.{ImplicitSender, TestKit}
import com.typesafe.config.ConfigFactory import com.typesafe.config.ConfigFactory
import net.psforever.objects.Default
import org.scalatest.BeforeAndAfterAll import org.scalatest.BeforeAndAfterAll
import org.scalatest.matchers.should.Matchers import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpecLike import org.scalatest.wordspec.AnyWordSpecLike
@ -19,6 +20,7 @@ abstract class ActorTest(sys: ActorSystem = ActorSystem("system", ConfigFactory.
with AnyWordSpecLike with AnyWordSpecLike
with Matchers with Matchers
with BeforeAndAfterAll { with BeforeAndAfterAll {
Default(sys)
override def afterAll(): Unit = { override def afterAll(): Unit = {
TestKit.shutdownActorSystem(system) TestKit.shutdownActorSystem(system)
} }

View file

@ -775,7 +775,7 @@ class DamageableWeaponTurretDamageTest extends ActorTest {
zone.AvatarEvents = avatarProbe.ref zone.AvatarEvents = avatarProbe.ref
zone.VehicleEvents = vehicleProbe.ref zone.VehicleEvents = vehicleProbe.ref
val turret = new TurretDeployable(GlobalDefinitions.portable_manned_turret_tr) //2 val turret = new TurretDeployable(GlobalDefinitions.portable_manned_turret_tr) //2
turret.Actor = system.actorOf(Props(classOf[TurretDeployableControl], turret), "turret-control") turret.Actor = system.actorOf(Props(classOf[FieldTurretControl], turret), "turret-control")
turret.Zone = zone turret.Zone = zone
turret.Position = Vector3(1, 0, 0) turret.Position = Vector3(1, 0, 0)
turret.LogActivity(SpawningActivity(SourceEntry(turret), zone.Number, None)) //seed a spawn event turret.LogActivity(SpawningActivity(SourceEntry(turret), zone.Number, None)) //seed a spawn event
@ -873,7 +873,7 @@ class DamageableWeaponTurretJammerTest extends ActorTest {
zone.VehicleEvents = vehicleProbe.ref zone.VehicleEvents = vehicleProbe.ref
val turret = new TurretDeployable(GlobalDefinitions.portable_manned_turret_tr) //2, 5, 6 val turret = new TurretDeployable(GlobalDefinitions.portable_manned_turret_tr) //2, 5, 6
turret.Actor = system.actorOf(Props(classOf[TurretDeployableControl], turret), "turret-control") turret.Actor = system.actorOf(Props(classOf[FieldTurretControl], turret), "turret-control")
turret.Zone = zone turret.Zone = zone
turret.Position = Vector3(1, 0, 0) turret.Position = Vector3(1, 0, 0)
val turretWeapon: Tool = turret.Weapons.values.head.Equipment.get.asInstanceOf[Tool] val turretWeapon: Tool = turret.Weapons.values.head.Equipment.get.asInstanceOf[Tool]

View file

@ -25,18 +25,17 @@ class DefaultActorStartedTest extends ActorTest {
"Default.Actor" should { "Default.Actor" should {
"send messages to deadLetters" in { "send messages to deadLetters" in {
//after being started //after being started
Default(system)
val probe = new TestProbe(system) val probe = new TestProbe(system)
system.eventStream.subscribe(probe.ref, classOf[DeadLetter]) system.eventStream.subscribe(probe.ref, classOf[DeadLetter])
Default.Actor ! "hello world" Default.Actor ! "hello world"
val msg1 = probe.receiveOne(250 milliseconds) val msg1 = probe.receiveOne(5000 milliseconds)
assert(msg1.isInstanceOf[DeadLetter]) assert(msg1.isInstanceOf[DeadLetter])
assert(msg1.asInstanceOf[DeadLetter].message equals "hello world") assert(msg1.asInstanceOf[DeadLetter].message equals "hello world")
//if it was stopped //if it was stopped
system.stop(Default.Actor) system.stop(Default.Actor)
Default.Actor ! "hello world" Default.Actor ! "hello world"
val msg2 = probe.receiveOne(250 milliseconds) val msg2 = probe.receiveOne(5000 milliseconds)
assert(msg2.isInstanceOf[DeadLetter]) assert(msg2.isInstanceOf[DeadLetter])
assert(msg2.asInstanceOf[DeadLetter].message equals "hello world") assert(msg2.asInstanceOf[DeadLetter].message equals "hello world")
} }

View file

@ -138,14 +138,14 @@ class TurretDeployableTest extends Specification {
DeployedItem.portable_manned_turret_nc.id, DeployedItem.portable_manned_turret_nc.id,
DeployedItem.portable_manned_turret_vs.id DeployedItem.portable_manned_turret_vs.id
).foreach(id => { ).foreach(id => {
try { new TurretDeployableDefinition(id) } try { new TurretDeployableDefinition(id) { } }
catch { case _: Exception => ko } catch { case _: Exception => ko }
}) })
ok ok
} }
"define (invalid object)" in { "define (invalid object)" in {
new TurretDeployableDefinition(5) must throwA[NoSuchElementException] //wrong object id altogether new TurretDeployableDefinition(objectId = 5) { } must throwA[NoSuchElementException] //wrong object id altogether
} }
"construct" in { "construct" in {
@ -212,7 +212,7 @@ class DeployableMake extends Specification {
} }
} }
"construct a cerebus turret" in { "construct a cerberus turret" in {
val func = Deployables.Make(DeployedItem.spitfire_aa) val func = Deployables.Make(DeployedItem.spitfire_aa)
func() match { func() match {
case obj: TurretDeployable if obj.Definition == GlobalDefinitions.spitfire_aa => ok case obj: TurretDeployable if obj.Definition == GlobalDefinitions.spitfire_aa => ok
@ -584,7 +584,7 @@ class TurretControlConstructTest extends ActorTest {
"TurretControl" should { "TurretControl" should {
"construct" in { "construct" in {
val obj = new TurretDeployable(GlobalDefinitions.spitfire_turret) val obj = new TurretDeployable(GlobalDefinitions.spitfire_turret)
system.actorOf(Props(classOf[TurretDeployableControl], obj), s"${obj.Definition.Name}_test") system.actorOf(Props(classOf[SmallTurretControl], obj), s"${obj.Definition.Name}_test")
} }
} }
} }
@ -629,7 +629,7 @@ class TurretControlMountTest extends ActorTest {
override def SetupNumberPools() = {} override def SetupNumberPools() = {}
this.actor = new TestProbe(system).ref.toTyped[ZoneActor.Command] this.actor = new TestProbe(system).ref.toTyped[ZoneActor.Command]
} }
obj.Actor = system.actorOf(Props(classOf[TurretDeployableControl], obj), s"${obj.Definition.Name}_test") obj.Actor = system.actorOf(Props(classOf[FieldTurretControl], obj), s"${obj.Definition.Name}_test")
assert(obj.Seats(0).occupant.isEmpty) assert(obj.Seats(0).occupant.isEmpty)
val player1 = Player(Avatar(0, "test1", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)) val player1 = Player(Avatar(0, "test1", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute))
@ -649,7 +649,7 @@ class TurretControlBlockMountTest extends ActorTest {
"block mounting by others if already mounted by someone" in { "block mounting by others if already mounted by someone" in {
val obj = new TurretDeployable(GlobalDefinitions.portable_manned_turret_tr) { GUID = PlanetSideGUID(1) } val obj = new TurretDeployable(GlobalDefinitions.portable_manned_turret_tr) { GUID = PlanetSideGUID(1) }
obj.Faction = PlanetSideEmpire.TR obj.Faction = PlanetSideEmpire.TR
obj.Actor = system.actorOf(Props(classOf[TurretDeployableControl], obj), s"${obj.Definition.Name}_test") obj.Actor = system.actorOf(Props(classOf[FieldTurretControl], obj), s"${obj.Definition.Name}_test")
obj.Zone = new Zone("test", new ZoneMap("test"), 0) { obj.Zone = new Zone("test", new ZoneMap("test"), 0) {
override def SetupNumberPools() = {} override def SetupNumberPools() = {}
this.actor = new TestProbe(system).ref.toTyped[ZoneActor.Command] this.actor = new TestProbe(system).ref.toTyped[ZoneActor.Command]
@ -681,7 +681,7 @@ class TurretControlBlockBetrayalMountTest extends ActorTest {
"block mounting by players of another faction" in { "block mounting by players of another faction" in {
val obj = new TurretDeployable(GlobalDefinitions.portable_manned_turret_tr) { GUID = PlanetSideGUID(1) } val obj = new TurretDeployable(GlobalDefinitions.portable_manned_turret_tr) { GUID = PlanetSideGUID(1) }
obj.Faction = PlanetSideEmpire.TR obj.Faction = PlanetSideEmpire.TR
obj.Actor = system.actorOf(Props(classOf[TurretDeployableControl], obj), s"${obj.Definition.Name}_test") obj.Actor = system.actorOf(Props(classOf[FieldTurretControl], obj), s"${obj.Definition.Name}_test")
assert(obj.Seats(0).occupant.isEmpty) assert(obj.Seats(0).occupant.isEmpty)
val player = Player(Avatar(0, "test", PlanetSideEmpire.VS, CharacterSex.Male, 0, CharacterVoice.Mute)) val player = Player(Avatar(0, "test", PlanetSideEmpire.VS, CharacterSex.Male, 0, CharacterVoice.Mute))
@ -705,7 +705,7 @@ class TurretControlDismountTest extends ActorTest {
override def SetupNumberPools() = {} override def SetupNumberPools() = {}
this.actor = new TestProbe(system).ref.toTyped[ZoneActor.Command] this.actor = new TestProbe(system).ref.toTyped[ZoneActor.Command]
} }
obj.Actor = system.actorOf(Props(classOf[TurretDeployableControl], obj), s"${obj.Definition.Name}_test") obj.Actor = system.actorOf(Props(classOf[FieldTurretControl], obj), s"${obj.Definition.Name}_test")
assert(obj.Seats(0).occupant.isEmpty) assert(obj.Seats(0).occupant.isEmpty)
val player = Player(Avatar(0, "test", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)) val player = Player(Avatar(0, "test", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute))
@ -746,7 +746,7 @@ class TurretControlBetrayalMountTest extends ActorTest {
} }
} }
obj.Faction = PlanetSideEmpire.TR obj.Faction = PlanetSideEmpire.TR
obj.Actor = system.actorOf(Props(classOf[TurretDeployableControl], obj), s"${obj.Definition.Name}_test") obj.Actor = system.actorOf(Props(classOf[FieldTurretControl], obj), s"${obj.Definition.Name}_test")
val probe = new TestProbe(system) val probe = new TestProbe(system)
assert(obj.Seats(0).occupant.isEmpty) assert(obj.Seats(0).occupant.isEmpty)

View file

@ -819,13 +819,13 @@ class DeployableToolboxTest extends Specification {
val obj = new DeployableToolbox val obj = new DeployableToolbox
obj.Initialize(Set(CombatEngineering)) obj.Initialize(Set(CombatEngineering))
val cerebus = new TurretDeployable(GlobalDefinitions.spitfire_aa) //cerebus turret val cerberus = new TurretDeployable(GlobalDefinitions.spitfire_aa) //cerberus turret
obj.Valid(cerebus) mustEqual false obj.Valid(cerberus) mustEqual false
obj.CountDeployable(DeployedItem.spitfire_aa).productIterator.toList mustEqual List(0, 0) obj.CountDeployable(DeployedItem.spitfire_aa).productIterator.toList mustEqual List(0, 0)
obj.UpdateMaxCounts(Set(CombatEngineering, AdvancedEngineering)) obj.UpdateMaxCounts(Set(CombatEngineering, AdvancedEngineering))
obj.Valid(cerebus) mustEqual true obj.Valid(cerberus) mustEqual true
obj.CountDeployable(DeployedItem.spitfire_aa).productIterator.toList mustEqual List(0, 5) obj.CountDeployable(DeployedItem.spitfire_aa).productIterator.toList mustEqual List(0, 5)
} }

View file

@ -774,124 +774,124 @@ class PlayerControlDeathStandingTest extends ActorTest {
// } // }
//} //}
class PlayerControlInteractWithWaterTest extends ActorTest { //class PlayerControlInteractWithWaterTest extends ActorTest {
val player1: Player = // val player1: Player =
Player(Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)) //guid=1 // Player(Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)) //guid=1
val avatarProbe: TestProbe = TestProbe() // val avatarProbe: TestProbe = TestProbe()
val guid = new NumberPoolHub(new MaxNumberSource(15)) // val guid = new NumberPoolHub(new MaxNumberSource(15))
val pool: Pool = Pool(EnvironmentAttribute.Water, DeepSquare(-1, 10, 10, 0, 0)) // val pool: Pool = Pool(EnvironmentAttribute.Water, DeepSquare(-1, 10, 10, 0, 0))
val zone: Zone = new Zone( // val zone: Zone = new Zone(
id = "test", // id = "test",
new ZoneMap(name = "test-map") { // new ZoneMap(name = "test-map") {
environment = List(pool) // environment = List(pool)
}, // },
zoneNumber = 0 // zoneNumber = 0
) { // ) {
override def SetupNumberPools(): Unit = {} // override def SetupNumberPools(): Unit = {}
GUID(guid) // GUID(guid)
override def LivePlayers: List[Player] = List(player1) // override def LivePlayers: List[Player] = List(player1)
override def AvatarEvents: ClassicActorRef = avatarProbe.ref // override def AvatarEvents: ClassicActorRef = avatarProbe.ref
} // }
zone.blockMap.addTo(player1) // zone.blockMap.addTo(player1)
zone.blockMap.addTo(pool) // zone.blockMap.addTo(pool)
//
// player1.Zone = zone
// player1.Spawn()
// guid.register(player1.avatar.locker, 5)
// val (probe, avatarActor) = PlayerControlTest.DummyAvatar(system)
// player1.Actor = system.actorOf(Props(classOf[PlayerControl], player1, avatarActor), "player1-control")
//
// guid.register(player1, 1)
//
// "PlayerControl" should {
// "cause drowning when player steps too deep in water" in {
// assert(player1.Health == 100)
// player1.Position = Vector3(5,5,-3) //right in the pool
// player1.zoneInteractions() //trigger
//
// val msg_drown = avatarProbe.receiveOne(250 milliseconds)
// assert(
// msg_drown match {
// case AvatarServiceMessage(
// "TestCharacter1",
// AvatarAction.OxygenState(OxygenStateTarget(PlanetSideGUID(1), _, OxygenState.Suffocation, 100f), _)
// ) => true
// case _ => false
// }
// )
// //player will die in 60s
// //detailing these death messages is not necessary
// assert(player1.Health == 100)
// probe.receiveOne(65 seconds) //wait until our implants deinitialize
// assert(player1.Health == 0) //ded
// }
// }
//}
player1.Zone = zone //class PlayerControlStopInteractWithWaterTest extends ActorTest {
player1.Spawn() // val player1: Player =
guid.register(player1.avatar.locker, 5) // Player(Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)) //guid=1
val (probe, avatarActor) = PlayerControlTest.DummyAvatar(system) // val avatarProbe: TestProbe = TestProbe()
player1.Actor = system.actorOf(Props(classOf[PlayerControl], player1, avatarActor), "player1-control") // val guid = new NumberPoolHub(new MaxNumberSource(15))
// val pool: Pool = Pool(EnvironmentAttribute.Water, DeepSquare(-1, 10, 10, 0, 0))
guid.register(player1, 1) // val zone: Zone = new Zone(
// id = "test",
"PlayerControl" should { // new ZoneMap(name = "test-map") {
"cause drowning when player steps too deep in water" in { // environment = List(pool)
assert(player1.Health == 100) // },
player1.Position = Vector3(5,5,-3) //right in the pool // zoneNumber = 0
player1.zoneInteractions() //trigger // ) {
// override def SetupNumberPools(): Unit = {}
val msg_drown = avatarProbe.receiveOne(250 milliseconds) // GUID(guid)
assert( // override def LivePlayers: List[Player] = List(player1)
msg_drown match { // override def AvatarEvents: ClassicActorRef = avatarProbe.ref
case AvatarServiceMessage( // }
"TestCharacter1", // zone.blockMap.addTo(player1)
AvatarAction.OxygenState(OxygenStateTarget(PlanetSideGUID(1), _, OxygenState.Suffocation, 100f), _) // zone.blockMap.addTo(pool)
) => true //
case _ => false // player1.Zone = zone
} // player1.Spawn()
) // guid.register(player1.avatar.locker, 5)
//player will die in 60s // val (probe, avatarActor) = PlayerControlTest.DummyAvatar(system)
//detailing these death messages is not necessary // player1.Actor = system.actorOf(Props(classOf[PlayerControl], player1, avatarActor), "player1-control")
assert(player1.Health == 100) //
probe.receiveOne(65 seconds) //wait until our implants deinitialize // guid.register(player1, 1)
assert(player1.Health == 0) //ded //
} // "PlayerControl" should {
} // "stop drowning if player steps out of deep water" in {
} // assert(player1.Health == 100)
// player1.Position = Vector3(5,5,-3) //right in the pool
class PlayerControlStopInteractWithWaterTest extends ActorTest { // player1.zoneInteractions() //trigger
val player1: Player = //
Player(Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)) //guid=1 // val msg_drown = avatarProbe.receiveOne(250 milliseconds)
val avatarProbe: TestProbe = TestProbe() // assert(
val guid = new NumberPoolHub(new MaxNumberSource(15)) // msg_drown match {
val pool: Pool = Pool(EnvironmentAttribute.Water, DeepSquare(-1, 10, 10, 0, 0)) // case AvatarServiceMessage(
val zone: Zone = new Zone( // "TestCharacter1",
id = "test", // AvatarAction.OxygenState(OxygenStateTarget(PlanetSideGUID(1), _, OxygenState.Suffocation, 100f), _)
new ZoneMap(name = "test-map") { // ) => true
environment = List(pool) // case _ => false
}, // }
zoneNumber = 0 // )
) { // //player would normally die in 60s
override def SetupNumberPools(): Unit = {} // player1.Position = Vector3.Zero //pool's closed
GUID(guid) // player1.zoneInteractions() //trigger
override def LivePlayers: List[Player] = List(player1) // val msg_recover = avatarProbe.receiveOne(250 milliseconds)
override def AvatarEvents: ClassicActorRef = avatarProbe.ref // assert(
} // msg_recover match {
zone.blockMap.addTo(player1) // case AvatarServiceMessage(
zone.blockMap.addTo(pool) // "TestCharacter1",
// AvatarAction.OxygenState(OxygenStateTarget(PlanetSideGUID(1), _, OxygenState.Recovery, _), _)
player1.Zone = zone // ) => true
player1.Spawn() // case _ => false
guid.register(player1.avatar.locker, 5) // }
val (probe, avatarActor) = PlayerControlTest.DummyAvatar(system) // )
player1.Actor = system.actorOf(Props(classOf[PlayerControl], player1, avatarActor), "player1-control") // assert(player1.Health == 100) //still alive?
// probe.expectNoMessage(65 seconds)
guid.register(player1, 1) // assert(player1.Health == 100) //yep, still alive
// }
"PlayerControl" should { // }
"stop drowning if player steps out of deep water" in { //}
assert(player1.Health == 100)
player1.Position = Vector3(5,5,-3) //right in the pool
player1.zoneInteractions() //trigger
val msg_drown = avatarProbe.receiveOne(250 milliseconds)
assert(
msg_drown match {
case AvatarServiceMessage(
"TestCharacter1",
AvatarAction.OxygenState(OxygenStateTarget(PlanetSideGUID(1), _, OxygenState.Suffocation, 100f), _)
) => true
case _ => false
}
)
//player would normally die in 60s
player1.Position = Vector3.Zero //pool's closed
player1.zoneInteractions() //trigger
val msg_recover = avatarProbe.receiveOne(250 milliseconds)
assert(
msg_recover match {
case AvatarServiceMessage(
"TestCharacter1",
AvatarAction.OxygenState(OxygenStateTarget(PlanetSideGUID(1), _, OxygenState.Recovery, _), _)
) => true
case _ => false
}
)
assert(player1.Health == 100) //still alive?
probe.expectNoMessage(65 seconds)
assert(player1.Health == 100) //yep, still alive
}
}
}
class PlayerControlInteractWithLavaTest extends ActorTest { class PlayerControlInteractWithLavaTest extends ActorTest {
val player1: Player = val player1: Player =

View file

@ -486,9 +486,13 @@ class ZonePopulationTest extends ActorTest {
class ZoneGroundDropItemTest extends ActorTest { class ZoneGroundDropItemTest extends ActorTest {
val item = AmmoBox(GlobalDefinitions.bullet_9mm) val item = AmmoBox(GlobalDefinitions.bullet_9mm)
val hub = new NumberPoolHub(new MaxNumberSource(20)) val hub = new NumberPoolHub(new MaxNumberSource(20))
val zone = new Zone(
"test",
new ZoneMap("test-map"), 0) {
override def SetupNumberPools() = {}
GUID(hub)
}
hub.register(item, 10) hub.register(item, 10)
val zone = new Zone("test", new ZoneMap("test-map"), 0) { override def SetupNumberPools() = {} }
zone.GUID(hub)
zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName) zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName)
expectNoMessage(200 milliseconds) expectNoMessage(200 milliseconds)
@ -511,9 +515,13 @@ class ZoneGroundDropItemTest extends ActorTest {
class ZoneGroundCanNotDropItem1Test extends ActorTest { class ZoneGroundCanNotDropItem1Test extends ActorTest {
val item = AmmoBox(GlobalDefinitions.bullet_9mm) val item = AmmoBox(GlobalDefinitions.bullet_9mm)
val hub = new NumberPoolHub(new MaxNumberSource(20)) val hub = new NumberPoolHub(new MaxNumberSource(20))
val zone = new Zone(
"test",
new ZoneMap("test-map"), 0) {
override def SetupNumberPools() = {}
GUID(hub)
}
//hub.register(item, 10) //!important //hub.register(item, 10) //!important
val zone = new Zone("test", new ZoneMap("test-map"), 0) { override def SetupNumberPools() = {} }
zone.GUID(hub)
zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName) zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName)
expectNoMessage(200 milliseconds) expectNoMessage(200 milliseconds)
@ -536,9 +544,13 @@ class ZoneGroundCanNotDropItem1Test extends ActorTest {
class ZoneGroundCanNotDropItem2Test extends ActorTest { class ZoneGroundCanNotDropItem2Test extends ActorTest {
val item = AmmoBox(GlobalDefinitions.bullet_9mm) val item = AmmoBox(GlobalDefinitions.bullet_9mm)
val hub = new NumberPoolHub(new MaxNumberSource(20)) val hub = new NumberPoolHub(new MaxNumberSource(20))
hub.register(item, 10) //!important val zone = new Zone(
val zone = new Zone("test", new ZoneMap("test-map"), 0) { override def SetupNumberPools() = {} } "test",
//zone.GUID(hub) //!important new ZoneMap("test-map"), 0) {
override def SetupNumberPools() = {}
//GUID(hub) !important
}
hub.register(item, 10)
zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName) zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName)
expectNoMessage(200 milliseconds) expectNoMessage(200 milliseconds)
@ -561,9 +573,13 @@ class ZoneGroundCanNotDropItem2Test extends ActorTest {
class ZoneGroundCanNotDropItem3Test extends ActorTest { class ZoneGroundCanNotDropItem3Test extends ActorTest {
val item = AmmoBox(GlobalDefinitions.bullet_9mm) val item = AmmoBox(GlobalDefinitions.bullet_9mm)
val hub = new NumberPoolHub(new MaxNumberSource(20)) val hub = new NumberPoolHub(new MaxNumberSource(20))
hub.register(item, 10) //!important val zone = new Zone(
val zone = new Zone("test", new ZoneMap("test-map"), 0) { override def SetupNumberPools() = {} } "test",
zone.GUID(hub) //!important new ZoneMap("test-map"), 0) {
override def SetupNumberPools() = {}
GUID(hub)
}
hub.register(item, 10)
zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName) zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName)
expectNoMessage(200 milliseconds) expectNoMessage(200 milliseconds)
@ -594,9 +610,13 @@ class ZoneGroundCanNotDropItem3Test extends ActorTest {
class ZoneGroundPickupItemTest extends ActorTest { class ZoneGroundPickupItemTest extends ActorTest {
val item = AmmoBox(GlobalDefinitions.bullet_9mm) val item = AmmoBox(GlobalDefinitions.bullet_9mm)
val hub = new NumberPoolHub(new MaxNumberSource(20)) val hub = new NumberPoolHub(new MaxNumberSource(20))
val zone = new Zone(
"test",
new ZoneMap("test-map"), 0) {
override def SetupNumberPools() = {}
GUID(hub)
}
hub.register(item, 10) hub.register(item, 10)
val zone = new Zone("test", new ZoneMap("test-map"), 0) { override def SetupNumberPools() = {} }
zone.GUID(hub)
zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName) zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName)
expectNoMessage(200 milliseconds) expectNoMessage(200 milliseconds)
@ -622,9 +642,13 @@ class ZoneGroundPickupItemTest extends ActorTest {
class ZoneGroundCanNotPickupItemTest extends ActorTest { class ZoneGroundCanNotPickupItemTest extends ActorTest {
val item = AmmoBox(GlobalDefinitions.bullet_9mm) val item = AmmoBox(GlobalDefinitions.bullet_9mm)
val hub = new NumberPoolHub(new MaxNumberSource(20)) val hub = new NumberPoolHub(new MaxNumberSource(20))
val zone = new Zone(
"test",
new ZoneMap("test-map"), 0) {
override def SetupNumberPools() = {}
GUID(hub)
}
hub.register(item, 10) hub.register(item, 10)
val zone = new Zone("test", new ZoneMap("test-map"), 0) { override def SetupNumberPools() = {} }
zone.GUID(hub) //still registered to this zone
zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName) zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName)
expectNoMessage(200 milliseconds) expectNoMessage(200 milliseconds)
@ -646,9 +670,13 @@ class ZoneGroundCanNotPickupItemTest extends ActorTest {
class ZoneGroundRemoveItemTest extends ActorTest { class ZoneGroundRemoveItemTest extends ActorTest {
val item = AmmoBox(GlobalDefinitions.bullet_9mm) val item = AmmoBox(GlobalDefinitions.bullet_9mm)
val hub = new NumberPoolHub(new MaxNumberSource(20)) val hub = new NumberPoolHub(new MaxNumberSource(20))
val zone = new Zone(
"test",
new ZoneMap("test-map"), 0) {
override def SetupNumberPools() = {}
GUID(hub)
}
hub.register(item, 10) hub.register(item, 10)
val zone = new Zone("test", new ZoneMap("test-map"), 0) { override def SetupNumberPools() = {} }
zone.GUID(hub) //still registered to this zone
zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName) zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName)
expectNoMessage(200 milliseconds) expectNoMessage(200 milliseconds)

View file

@ -161,31 +161,37 @@ object ImplantTerminalMechTest {
import akka.actor.typed.scaladsl.adapter._ import akka.actor.typed.scaladsl.adapter._
val guid = new NumberPoolHub(new MaxNumberSource(10)) val guid = new NumberPoolHub(new MaxNumberSource(10))
val map = new ZoneMap("test") val terminal = ImplantTerminalMech(GlobalDefinitions.implant_terminal_mech) //guid=1
val zone = new Zone("test", map, 0) { val interface = Terminal(GlobalDefinitions.implant_terminal_interface) //guid=2
override def SetupNumberPools() = {}
GUID(guid)
this.actor = new TestProbe(system).ref.toTyped[ZoneActor.Command]
}
val building = new Building( val building = new Building(
"Building", "Building",
building_guid = 0, building_guid = 0,
map_id = 0, map_id = 0,
zone, Zone.Nowhere,
StructureType.Building, StructureType.Building,
GlobalDefinitions.building GlobalDefinitions.building
) //guid=3 ) //guid=3
val zone = new Zone(
"test",
new ZoneMap("test") {
},
0) {
override def SetupNumberPools() = {}
GUID(guid)
this.actor = new TestProbe(system).ref.toTyped[ZoneActor.Command]
}
//zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName)
zone.map.linkTerminalToInterface(1, 2)
building.Zone = zone
building.Faction = faction building.Faction = faction
interface.Zone = zone
val interface = Terminal(GlobalDefinitions.implant_terminal_interface) //guid=2 building.Amenities = interface
interface.Owner = building terminal.Zone = zone
val terminal = ImplantTerminalMech(GlobalDefinitions.implant_terminal_mech) //guid=1 building.Amenities = terminal
terminal.Owner = building
guid.register(terminal, 1) guid.register(terminal, 1)
guid.register(interface, 2) guid.register(interface, 2)
guid.register(building, 3) guid.register(building, 3)
map.linkTerminalToInterface(1, 2)
terminal.Actor = system.actorOf(Props(classOf[ImplantTerminalMechControl], terminal), "terminal-control") terminal.Actor = system.actorOf(Props(classOf[ImplantTerminalMechControl], terminal), "terminal-control")
(Player(Avatar(0, "test", faction, CharacterSex.Male, 0, CharacterVoice.Mute)), terminal) (Player(Avatar(0, "test", faction, CharacterSex.Male, 0, CharacterVoice.Mute)), terminal)