mirror of
https://github.com/2revoemag/PSF-BotServer.git
synced 2026-01-20 02:24:45 +00:00
Water and Lava (#649)
* planar classes to describe levels of water and other fluid parallel to the ground * corrected purpose of field in OxygenStateMessage and adjusted the structure of the packet; the environment is now 'regions filled with stuff'; messaging pathways to facilitate drowning and drown recovery in SessionActor, WorldSession, and PlayerControl, as well as the avatar event system * drowning height is now a featur - recommend going through GlobalDefinitions; fixed lava pool collision to work on pool entry rather than drown level; lava now burns; painbox damage now is directed towards players control agency first * drowning timer works correctly for both player and vehicle targets; timing and dive depth information for targets defined, but currently originates from a generic location (ObjectDefinition); packet OSM has been modified for efficiency; classes for environment features previously exclusive to drowning mechanics have been pushed towards generic naming conventions * added sea and pools for z4, z5, z8, and z10 * vehicles now take damage (to the point of destruction) when exposed to lava due the expansion of environmental damage reasons and environmental damage modifiers; modification of the environment exposure lingo; streamlining of vital activity record system * added basic drown params to flying vehicle definitions; object trait and control mixin for environment interaction, code moved from SessionActor and WorldSession * separated environmental classes; handled waterlogged flying vehicles, in properties and code; wrote comments and tests * players mounting vehicles and players subjected to the vehicle transfer process should receive updated drown-state status of the vehicle; drowning should suspend while in the middle of vehicle transfer, in the case the process is long * increased damage performed to vehicles by lava
This commit is contained in:
parent
b07fe77c6e
commit
1a6beba335
|
|
@ -20,7 +20,7 @@ import scala.concurrent.ExecutionContext.Implicits.global
|
|||
import scala.util.Success
|
||||
import net.psforever.login.WorldSession._
|
||||
import net.psforever.objects._
|
||||
import net.psforever.objects.avatar.{Avatar, Certification, Cosmetic, DeployableToolbox}
|
||||
import net.psforever.objects.avatar._
|
||||
import net.psforever.objects.ballistics._
|
||||
import net.psforever.objects.ce._
|
||||
import net.psforever.objects.definition._
|
||||
|
|
@ -1298,8 +1298,8 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
|||
* The user is either already in the current zone and merely transporting himself from one location to another,
|
||||
* also called "dying", or occasionally "deconstructing,"
|
||||
* or is completely switching in between zones.
|
||||
* These correspond to the message NewPlayerLoaded for the case of "dying" or the latter zone switching case,
|
||||
* and PlayerLoaded for "deconstruction."
|
||||
* These correspond to the message `NewPlayerLoaded` for the case of "dying" or the latter zone switching case,
|
||||
* and `PlayerLoaded` for "deconstruction."
|
||||
* In the latter case, the user must wait for the zone to be recognized as loaded for the server
|
||||
* and this is performed through the send LoadMapMessage, receive BeginZoningMessage exchange
|
||||
* The user's player should have already been registered into the new zone
|
||||
|
|
@ -1880,6 +1880,17 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
|||
sendResponse(ObjectHeldMessage(guid, slot, false))
|
||||
}
|
||||
|
||||
case AvatarResponse.OxygenState(player, vehicle) =>
|
||||
sendResponse(
|
||||
OxygenStateMessage(
|
||||
DrowningTarget(player.guid, player.progress, player.state),
|
||||
vehicle match {
|
||||
case Some(vinfo) => Some(DrowningTarget(vinfo.guid, vinfo.progress, vinfo.state))
|
||||
case None => None
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
case AvatarResponse.PlanetsideAttribute(attribute_type, attribute_value) =>
|
||||
if (tplayer_guid != guid) {
|
||||
sendResponse(PlanetsideAttributeMessage(guid, attribute_type, attribute_value))
|
||||
|
|
@ -3035,7 +3046,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
|||
)
|
||||
)
|
||||
case (Some(vehicle), Some(0)) =>
|
||||
//summon any passengers and cargo vehicles left behind on previous continent
|
||||
//driver; summon any passengers and cargo vehicles left behind on previous continent
|
||||
if (vehicle.Jammed) {
|
||||
//TODO something better than just canceling?
|
||||
vehicle.Actor ! JammableUnit.ClearJammeredStatus()
|
||||
|
|
@ -3056,6 +3067,9 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
|||
continent.id,
|
||||
vehicle
|
||||
)
|
||||
case (Some(vehicle), _) =>
|
||||
//passenger
|
||||
vehicle.Actor ! Vehicle.UpdateZoneInteractionProgressUI(player)
|
||||
case _ => ;
|
||||
}
|
||||
interstellarFerryTopLevelGUID = None
|
||||
|
|
@ -3691,6 +3705,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
|||
if (player.death_by == -1) {
|
||||
KickedByAdministration()
|
||||
}
|
||||
player.zoneInteraction()
|
||||
|
||||
case msg @ ChildObjectStateMessage(object_guid, pitch, yaw) =>
|
||||
//log.info(s"$msg")
|
||||
|
|
@ -3792,6 +3807,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
|||
)
|
||||
)
|
||||
updateSquad()
|
||||
obj.zoneInteraction()
|
||||
case (None, _) =>
|
||||
//log.error(s"VehicleState: no vehicle $vehicle_guid found in zone")
|
||||
//TODO placing a "not driving" warning here may trigger as we are disembarking the vehicle
|
||||
|
|
@ -6761,7 +6777,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
|||
* @param tplayer the player to be killed
|
||||
*/
|
||||
def suicide(tplayer: Player): Unit = {
|
||||
tplayer.History(PlayerSuicide(PlayerSource(tplayer)))
|
||||
tplayer.History(PlayerSuicide())
|
||||
tplayer.Actor ! Player.Die()
|
||||
}
|
||||
|
||||
|
|
@ -6881,6 +6897,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
|||
case _ =>
|
||||
vehicle.MountedIn = None
|
||||
}
|
||||
vehicle.allowZoneEnvironmentInteractions = true
|
||||
data
|
||||
} else {
|
||||
//passenger
|
||||
|
|
@ -8501,6 +8518,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
|||
)
|
||||
}
|
||||
//
|
||||
vehicle.allowZoneEnvironmentInteractions = false
|
||||
if (!zoneReload && zoneId == continent.id) {
|
||||
if (vehicle.Definition == GlobalDefinitions.droppod) {
|
||||
//instant action droppod in the same zone
|
||||
|
|
@ -9153,7 +9171,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
|||
if (hitPositionDiscrepancy > Config.app.antiCheat.hitPositionDiscrepancyThreshold) {
|
||||
// If the target position on the server does not match the position where the projectile landed within reason there may be foul play
|
||||
log.warn(
|
||||
s"Shot guid ${projectile_guid} has hit location discrepancy with target location. Target: ${target.Position} Reported: ${hitPos}, Distance: ${hitPositionDiscrepancy} / ${math.sqrt(hitPositionDiscrepancy).toFloat}; suspect"
|
||||
s"Shot guid $projectile_guid has hit location discrepancy with target location. Target: ${target.Position} Reported: $hitPos, Distance: $hitPositionDiscrepancy / ${math.sqrt(hitPositionDiscrepancy).toFloat}; suspect"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import net.psforever.objects.locker.LockerContainer
|
|||
import net.psforever.objects.serverobject.PlanetSideServerObject
|
||||
import net.psforever.objects.serverobject.containable.Containable
|
||||
import net.psforever.objects.zones.Zone
|
||||
import net.psforever.objects.{AmmoBox, GlobalDefinitions, Player, Tool}
|
||||
import net.psforever.objects._
|
||||
import net.psforever.packet.game.ObjectHeldMessage
|
||||
import net.psforever.types.{PlanetSideGUID, TransactionType, Vector3}
|
||||
import net.psforever.services.Service
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import net.psforever.objects.vital.projectile._
|
|||
import net.psforever.objects.vital.prop.DamageWithPosition
|
||||
import net.psforever.objects.vital.{ComplexDeployableResolutions, MaxResolutions, SimpleResolutions}
|
||||
import net.psforever.types.{ExoSuitType, ImplantType, PlanetSideEmpire, Vector3}
|
||||
import net.psforever.types._
|
||||
|
||||
import scala.collection.mutable
|
||||
import scala.concurrent.duration._
|
||||
|
|
@ -37,6 +38,9 @@ object GlobalDefinitions {
|
|||
val avatar = new AvatarDefinition(121)
|
||||
avatar.MaxHealth = 100
|
||||
avatar.Damageable = true
|
||||
avatar.DrownAtMaxDepth = true
|
||||
avatar.MaxDepth = 1.609375f //Male, standing, not MAX
|
||||
avatar.UnderwaterLifespan(suffocation = 60000L, recovery = 10000L)
|
||||
/*
|
||||
exo-suits
|
||||
*/
|
||||
|
|
@ -1623,6 +1627,23 @@ object GlobalDefinitions {
|
|||
}
|
||||
}
|
||||
|
||||
def MaxDepth(obj: PlanetSideGameObject): Float = {
|
||||
obj match {
|
||||
case p: Player =>
|
||||
if (p.Crouching) {
|
||||
1.093750f // same regardless of gender
|
||||
} else if (p.ExoSuit == ExoSuitType.MAX) {
|
||||
1.906250f // VS female MAX
|
||||
} else if (p.Sex == CharacterGender.Male) {
|
||||
obj.Definition.MaxDepth // male
|
||||
} else {
|
||||
1.546875f // female
|
||||
}
|
||||
case _ =>
|
||||
obj.Definition.MaxDepth
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize `KitDefinition` globals.
|
||||
*/
|
||||
|
|
@ -5607,6 +5628,9 @@ object GlobalDefinitions {
|
|||
DamageAtEdge = 0.2f
|
||||
Modifiers = RadialDegrade
|
||||
}
|
||||
fury.DrownAtMaxDepth = true
|
||||
fury.MaxDepth = 1.3f
|
||||
fury.UnderwaterLifespan(suffocation = 5000L, recovery = 2500L)
|
||||
|
||||
quadassault.Name = "quadassault" // Basilisk
|
||||
quadassault.MaxHealth = 650
|
||||
|
|
@ -5635,6 +5659,9 @@ object GlobalDefinitions {
|
|||
DamageAtEdge = 0.2f
|
||||
Modifiers = RadialDegrade
|
||||
}
|
||||
quadassault.DrownAtMaxDepth = true
|
||||
quadassault.MaxDepth = 1.3f
|
||||
quadassault.UnderwaterLifespan(suffocation = 5000L, recovery = 2500L)
|
||||
|
||||
quadstealth.Name = "quadstealth" // Wraith
|
||||
quadstealth.MaxHealth = 650
|
||||
|
|
@ -5663,6 +5690,9 @@ object GlobalDefinitions {
|
|||
DamageAtEdge = 0.2f
|
||||
Modifiers = RadialDegrade
|
||||
}
|
||||
quadstealth.DrownAtMaxDepth = true
|
||||
quadstealth.MaxDepth = 1.25f
|
||||
quadstealth.UnderwaterLifespan(suffocation = 5000L, recovery = 2500L)
|
||||
|
||||
two_man_assault_buggy.Name = "two_man_assault_buggy" // Harasser
|
||||
two_man_assault_buggy.MaxHealth = 1250
|
||||
|
|
@ -5693,6 +5723,9 @@ object GlobalDefinitions {
|
|||
DamageAtEdge = 0.2f
|
||||
Modifiers = RadialDegrade
|
||||
}
|
||||
two_man_assault_buggy.DrownAtMaxDepth = true
|
||||
two_man_assault_buggy.MaxDepth = 1.5f
|
||||
two_man_assault_buggy.UnderwaterLifespan(suffocation = 5000L, recovery = 2500L)
|
||||
|
||||
skyguard.Name = "skyguard"
|
||||
skyguard.MaxHealth = 1000
|
||||
|
|
@ -5715,6 +5748,7 @@ object GlobalDefinitions {
|
|||
skyguard.AutoPilotSpeeds = (22, 8)
|
||||
skyguard.DestroyedModel = Some(DestroyedVehicle.Skyguard)
|
||||
skyguard.JackingDuration = Array(0, 15, 5, 3)
|
||||
|
||||
skyguard.explodes = true
|
||||
skyguard.innateDamage = new DamageWithPosition {
|
||||
CausesDamageType = DamageType.One
|
||||
|
|
@ -5724,6 +5758,9 @@ object GlobalDefinitions {
|
|||
DamageAtEdge = 0.2f
|
||||
Modifiers = RadialDegrade
|
||||
}
|
||||
skyguard.DrownAtMaxDepth = true
|
||||
skyguard.MaxDepth = 1.5f
|
||||
skyguard.UnderwaterLifespan(suffocation = 5000L, recovery = 2500L)
|
||||
|
||||
threemanheavybuggy.Name = "threemanheavybuggy" // Marauder
|
||||
threemanheavybuggy.MaxHealth = 1700
|
||||
|
|
@ -5760,6 +5797,9 @@ object GlobalDefinitions {
|
|||
DamageAtEdge = 0.2f
|
||||
Modifiers = RadialDegrade
|
||||
}
|
||||
threemanheavybuggy.DrownAtMaxDepth = true
|
||||
threemanheavybuggy.MaxDepth = 1.83f
|
||||
threemanheavybuggy.UnderwaterLifespan(suffocation = 5000L, recovery = 2500L)
|
||||
|
||||
twomanheavybuggy.Name = "twomanheavybuggy" // Enforcer
|
||||
twomanheavybuggy.MaxHealth = 1800
|
||||
|
|
@ -5791,6 +5831,9 @@ object GlobalDefinitions {
|
|||
DamageAtEdge = 0.2f
|
||||
Modifiers = RadialDegrade
|
||||
}
|
||||
twomanheavybuggy.DrownAtMaxDepth = true
|
||||
twomanheavybuggy.MaxDepth = 1.95f
|
||||
twomanheavybuggy.UnderwaterLifespan(suffocation = 5000L, recovery = 2500L)
|
||||
|
||||
twomanhoverbuggy.Name = "twomanhoverbuggy" // Thresher
|
||||
twomanhoverbuggy.MaxHealth = 1600
|
||||
|
|
@ -5822,6 +5865,8 @@ object GlobalDefinitions {
|
|||
DamageAtEdge = 0.2f
|
||||
Modifiers = RadialDegrade
|
||||
}
|
||||
twomanhoverbuggy.DrownAtMaxDepth = true
|
||||
twomanhoverbuggy.UnderwaterLifespan(suffocation = 45000L, recovery = 5000L) //but the thresher hovers over water, so ...?
|
||||
|
||||
mediumtransport.Name = "mediumtransport" // Deliverer
|
||||
mediumtransport.MaxHealth = 2500
|
||||
|
|
@ -5860,6 +5905,9 @@ object GlobalDefinitions {
|
|||
DamageAtEdge = 0.2f
|
||||
Modifiers = RadialDegrade
|
||||
}
|
||||
mediumtransport.DrownAtMaxDepth = false
|
||||
mediumtransport.MaxDepth = 1.2f
|
||||
mediumtransport.UnderwaterLifespan(suffocation = -1, recovery = -1)
|
||||
|
||||
battlewagon.Name = "battlewagon" // Raider
|
||||
battlewagon.MaxHealth = 2500
|
||||
|
|
@ -5901,6 +5949,9 @@ object GlobalDefinitions {
|
|||
DamageAtEdge = 0.2f
|
||||
Modifiers = RadialDegrade
|
||||
}
|
||||
battlewagon.DrownAtMaxDepth = true
|
||||
battlewagon.MaxDepth = 1.2f
|
||||
battlewagon.UnderwaterLifespan(suffocation = -1, recovery = -1)
|
||||
|
||||
thunderer.Name = "thunderer"
|
||||
thunderer.MaxHealth = 2500
|
||||
|
|
@ -5939,6 +5990,9 @@ object GlobalDefinitions {
|
|||
DamageAtEdge = 0.2f
|
||||
Modifiers = RadialDegrade
|
||||
}
|
||||
thunderer.DrownAtMaxDepth = true
|
||||
thunderer.MaxDepth = 1.2f
|
||||
thunderer.UnderwaterLifespan(suffocation = -1, recovery = -1)
|
||||
|
||||
aurora.Name = "aurora"
|
||||
aurora.MaxHealth = 2500
|
||||
|
|
@ -5977,6 +6031,9 @@ object GlobalDefinitions {
|
|||
DamageAtEdge = 0.2f
|
||||
Modifiers = RadialDegrade
|
||||
}
|
||||
aurora.DrownAtMaxDepth = true
|
||||
aurora.MaxDepth = 1.2f
|
||||
aurora.UnderwaterLifespan(suffocation = -1, recovery = -1)
|
||||
|
||||
apc_tr.Name = "apc_tr" // Juggernaut
|
||||
apc_tr.MaxHealth = 6000
|
||||
|
|
@ -6038,6 +6095,9 @@ object GlobalDefinitions {
|
|||
DamageAtEdge = 0.2f
|
||||
Modifiers = RadialDegrade
|
||||
}
|
||||
apc_tr.DrownAtMaxDepth = true
|
||||
apc_tr.MaxDepth = 3
|
||||
apc_tr.UnderwaterLifespan(suffocation = 15000L, recovery = 7500L)
|
||||
|
||||
apc_nc.Name = "apc_nc" // Vindicator
|
||||
apc_nc.MaxHealth = 6000
|
||||
|
|
@ -6099,6 +6159,9 @@ object GlobalDefinitions {
|
|||
DamageAtEdge = 0.2f
|
||||
Modifiers = RadialDegrade
|
||||
}
|
||||
apc_nc.DrownAtMaxDepth = true
|
||||
apc_nc.MaxDepth = 3
|
||||
apc_nc.UnderwaterLifespan(suffocation = 15000L, recovery = 7500L)
|
||||
|
||||
apc_vs.Name = "apc_vs" // Leviathan
|
||||
apc_vs.MaxHealth = 6000
|
||||
|
|
@ -6160,6 +6223,9 @@ object GlobalDefinitions {
|
|||
DamageAtEdge = 0.2f
|
||||
Modifiers = RadialDegrade
|
||||
}
|
||||
apc_vs.DrownAtMaxDepth = true
|
||||
apc_vs.MaxDepth = 3
|
||||
apc_vs.UnderwaterLifespan(suffocation = 15000L, recovery = 7500L)
|
||||
|
||||
lightning.Name = "lightning"
|
||||
lightning.MaxHealth = 2000
|
||||
|
|
@ -6189,6 +6255,9 @@ object GlobalDefinitions {
|
|||
DamageAtEdge = 0.2f
|
||||
Modifiers = RadialDegrade
|
||||
}
|
||||
lightning.DrownAtMaxDepth = true
|
||||
lightning.MaxDepth = 1.38f
|
||||
lightning.UnderwaterLifespan(suffocation = 12000L, recovery = 6000L)
|
||||
|
||||
prowler.Name = "prowler"
|
||||
prowler.MaxHealth = 4800
|
||||
|
|
@ -6223,6 +6292,9 @@ object GlobalDefinitions {
|
|||
DamageAtEdge = 0.2f
|
||||
Modifiers = RadialDegrade
|
||||
}
|
||||
prowler.DrownAtMaxDepth = true
|
||||
prowler.MaxDepth = 3
|
||||
prowler.UnderwaterLifespan(suffocation = 12000L, recovery = 6000L)
|
||||
|
||||
vanguard.Name = "vanguard"
|
||||
vanguard.MaxHealth = 5400
|
||||
|
|
@ -6253,6 +6325,9 @@ object GlobalDefinitions {
|
|||
DamageAtEdge = 0.2f
|
||||
Modifiers = RadialDegrade
|
||||
}
|
||||
vanguard.DrownAtMaxDepth = true
|
||||
vanguard.MaxDepth = 2.7f
|
||||
vanguard.UnderwaterLifespan(suffocation = 12000L, recovery = 6000L)
|
||||
|
||||
magrider.Name = "magrider"
|
||||
magrider.MaxHealth = 4200
|
||||
|
|
@ -6285,6 +6360,9 @@ object GlobalDefinitions {
|
|||
DamageAtEdge = 0.2f
|
||||
Modifiers = RadialDegrade
|
||||
}
|
||||
magrider.DrownAtMaxDepth = true
|
||||
magrider.MaxDepth = 2
|
||||
magrider.UnderwaterLifespan(suffocation = 45000L, recovery = 5000L) //but the magrider hovers over water, so ...?
|
||||
|
||||
val utilityConverter = new UtilityVehicleConverter
|
||||
ant.Name = "ant"
|
||||
|
|
@ -6315,6 +6393,9 @@ object GlobalDefinitions {
|
|||
DamageAtEdge = 0.2f
|
||||
Modifiers = RadialDegrade
|
||||
}
|
||||
ant.DrownAtMaxDepth = true
|
||||
ant.MaxDepth = 2
|
||||
ant.UnderwaterLifespan(suffocation = 12000L, recovery = 6000L)
|
||||
|
||||
ams.Name = "ams"
|
||||
ams.MaxHealth = 5000 // Temporary - original value is 3000
|
||||
|
|
@ -6348,6 +6429,9 @@ object GlobalDefinitions {
|
|||
DamageAtEdge = 0.2f
|
||||
Modifiers = RadialDegrade
|
||||
}
|
||||
ams.DrownAtMaxDepth = true
|
||||
ams.MaxDepth = 3
|
||||
ams.UnderwaterLifespan(suffocation = 5000L, recovery = 5000L)
|
||||
|
||||
val variantConverter = new VariantVehicleConverter
|
||||
router.Name = "router"
|
||||
|
|
@ -6381,6 +6465,9 @@ object GlobalDefinitions {
|
|||
DamageAtEdge = 0.2f
|
||||
Modifiers = RadialDegrade
|
||||
}
|
||||
router.DrownAtMaxDepth = true
|
||||
router.MaxDepth = 2
|
||||
router.UnderwaterLifespan(suffocation = 45000L, recovery = 5000L) //but the router hovers over water, so ...?
|
||||
|
||||
switchblade.Name = "switchblade"
|
||||
switchblade.MaxHealth = 1750
|
||||
|
|
@ -6414,6 +6501,9 @@ object GlobalDefinitions {
|
|||
DamageAtEdge = 0.2f
|
||||
Modifiers = RadialDegrade
|
||||
}
|
||||
switchblade.DrownAtMaxDepth = true
|
||||
switchblade.MaxDepth = 2
|
||||
switchblade.UnderwaterLifespan(suffocation = 45000L, recovery = 5000L) //but the switchblade hovers over water, so ...?
|
||||
|
||||
flail.Name = "flail"
|
||||
flail.MaxHealth = 2400
|
||||
|
|
@ -6445,6 +6535,9 @@ object GlobalDefinitions {
|
|||
DamageAtEdge = 0.2f
|
||||
Modifiers = RadialDegrade
|
||||
}
|
||||
flail.DrownAtMaxDepth = true
|
||||
flail.MaxDepth = 2
|
||||
flail.UnderwaterLifespan(suffocation = 45000L, recovery = 5000L) //but the flail hovers over water, so ...?
|
||||
|
||||
mosquito.Name = "mosquito"
|
||||
mosquito.MaxHealth = 665
|
||||
|
|
@ -6476,6 +6569,8 @@ object GlobalDefinitions {
|
|||
DamageAtEdge = 0.2f
|
||||
Modifiers = RadialDegrade
|
||||
}
|
||||
mosquito.DrownAtMaxDepth = true
|
||||
mosquito.MaxDepth = 2 //flying vehicles will automatically disable
|
||||
|
||||
lightgunship.Name = "lightgunship" // Reaver
|
||||
lightgunship.MaxHealth = 1000
|
||||
|
|
@ -6508,6 +6603,8 @@ object GlobalDefinitions {
|
|||
DamageAtEdge = 0.2f
|
||||
Modifiers = RadialDegrade
|
||||
}
|
||||
lightgunship.DrownAtMaxDepth = true
|
||||
lightgunship.MaxDepth = 2 //flying vehicles will automatically disable
|
||||
|
||||
wasp.Name = "wasp"
|
||||
wasp.MaxHealth = 515
|
||||
|
|
@ -6539,6 +6636,8 @@ object GlobalDefinitions {
|
|||
DamageAtEdge = 0.2f
|
||||
Modifiers = RadialDegrade
|
||||
}
|
||||
wasp.DrownAtMaxDepth = true
|
||||
wasp.MaxDepth = 2 //flying vehicles will automatically disable
|
||||
|
||||
liberator.Name = "liberator"
|
||||
liberator.MaxHealth = 2500
|
||||
|
|
@ -6578,6 +6677,8 @@ object GlobalDefinitions {
|
|||
DamageAtEdge = 0.2f
|
||||
Modifiers = RadialDegrade
|
||||
}
|
||||
liberator.DrownAtMaxDepth = true
|
||||
liberator.MaxDepth = 2 //flying vehicles will automatically disable
|
||||
|
||||
vulture.Name = "vulture"
|
||||
vulture.MaxHealth = 2500
|
||||
|
|
@ -6618,6 +6719,8 @@ object GlobalDefinitions {
|
|||
DamageAtEdge = 0.2f
|
||||
Modifiers = RadialDegrade
|
||||
}
|
||||
vulture.DrownAtMaxDepth = true
|
||||
vulture.MaxDepth = 2 //flying vehicles will automatically disable
|
||||
|
||||
dropship.Name = "dropship" // Galaxy
|
||||
dropship.MaxHealth = 5000
|
||||
|
|
@ -6690,6 +6793,8 @@ object GlobalDefinitions {
|
|||
DamageAtEdge = 0.2f
|
||||
Modifiers = RadialDegrade
|
||||
}
|
||||
dropship.DrownAtMaxDepth = true
|
||||
dropship.MaxDepth = 2
|
||||
|
||||
galaxy_gunship.Name = "galaxy_gunship"
|
||||
galaxy_gunship.MaxHealth = 6000
|
||||
|
|
@ -6741,6 +6846,8 @@ object GlobalDefinitions {
|
|||
DamageAtEdge = 0.2f
|
||||
Modifiers = RadialDegrade
|
||||
}
|
||||
galaxy_gunship.DrownAtMaxDepth = true
|
||||
galaxy_gunship.MaxDepth = 2
|
||||
|
||||
lodestar.Name = "lodestar"
|
||||
lodestar.MaxHealth = 5000
|
||||
|
|
@ -6780,6 +6887,8 @@ object GlobalDefinitions {
|
|||
DamageAtEdge = 0.2f
|
||||
Modifiers = RadialDegrade
|
||||
}
|
||||
lodestar.DrownAtMaxDepth = true
|
||||
lodestar.MaxDepth = 2
|
||||
|
||||
phantasm.Name = "phantasm"
|
||||
phantasm.MaxHealth = 2500
|
||||
|
|
@ -6820,6 +6929,8 @@ object GlobalDefinitions {
|
|||
DamageAtEdge = 0.2f
|
||||
Modifiers = RadialDegrade
|
||||
}
|
||||
phantasm.DrownAtMaxDepth = true
|
||||
phantasm.MaxDepth = 2
|
||||
|
||||
droppod.Name = "droppod"
|
||||
droppod.MaxHealth = 20000
|
||||
|
|
@ -6832,6 +6943,7 @@ object GlobalDefinitions {
|
|||
droppod.DeconstructionTime = Some(5 seconds)
|
||||
droppod.DestroyedModel = None //the adb calls out a droppod; the cyclic nature of this confounds me
|
||||
droppod.DamageUsing = DamageCalculations.AgainstAircraft
|
||||
droppod.DrownAtMaxDepth = false
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import net.psforever.objects.inventory.{Container, GridInventory, InventoryItem}
|
|||
import net.psforever.objects.serverobject.PlanetSideServerObject
|
||||
import net.psforever.objects.serverobject.affinity.FactionAffinity
|
||||
import net.psforever.objects.serverobject.aura.AuraContainer
|
||||
import net.psforever.objects.serverobject.environment.InteractsWithZoneEnvironment
|
||||
import net.psforever.objects.vital.resistance.ResistanceProfile
|
||||
import net.psforever.objects.vital.Vitality
|
||||
import net.psforever.objects.vital.interaction.DamageInteraction
|
||||
|
|
@ -20,6 +21,7 @@ import scala.util.{Success, Try}
|
|||
|
||||
class Player(var avatar: Avatar)
|
||||
extends PlanetSideServerObject
|
||||
with InteractsWithZoneEnvironment
|
||||
with FactionAffinity
|
||||
with Vitality
|
||||
with ResistanceProfile
|
||||
|
|
@ -27,8 +29,6 @@ class Player(var avatar: Avatar)
|
|||
with JammableUnit
|
||||
with ZoneAware
|
||||
with AuraContainer {
|
||||
Health = 0 //player health is artificially managed as a part of their lifecycle; start entity as dead
|
||||
Destroyed = true //see isAlive
|
||||
private var backpack: Boolean = false
|
||||
private var armor: Int = 0
|
||||
|
||||
|
|
@ -66,6 +66,9 @@ class Player(var avatar: Avatar)
|
|||
|
||||
val squadLoadouts = new LoadoutManager(10)
|
||||
|
||||
//init
|
||||
Health = 0 //player health is artificially managed as a part of their lifecycle; start entity as dead
|
||||
Destroyed = true //see isAlive
|
||||
Player.SuitSetup(this, exosuit)
|
||||
|
||||
def Definition: AvatarDefinition = avatar.definition
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package net.psforever.objects
|
||||
|
||||
import net.psforever.objects.definition.VehicleDefinition
|
||||
import net.psforever.objects.definition.{SeatDefinition, ToolDefinition, VehicleDefinition}
|
||||
import net.psforever.objects.equipment.{Equipment, EquipmentSize, EquipmentSlot, JammableUnit}
|
||||
import net.psforever.objects.inventory.{Container, GridInventory, InventoryItem, InventoryTile}
|
||||
import net.psforever.objects.serverobject.mount.Mountable
|
||||
|
|
@ -9,6 +9,7 @@ import net.psforever.objects.serverobject.PlanetSideServerObject
|
|||
import net.psforever.objects.serverobject.affinity.FactionAffinity
|
||||
import net.psforever.objects.serverobject.aura.AuraContainer
|
||||
import net.psforever.objects.serverobject.deploy.Deployment
|
||||
import net.psforever.objects.serverobject.environment.InteractsWithZoneEnvironment
|
||||
import net.psforever.objects.serverobject.hackable.Hackable
|
||||
import net.psforever.objects.serverobject.structures.AmenityOwner
|
||||
import net.psforever.objects.vehicles._
|
||||
|
|
@ -71,6 +72,7 @@ import scala.util.{Success, Try}
|
|||
*/
|
||||
class Vehicle(private val vehicleDef: VehicleDefinition)
|
||||
extends AmenityOwner
|
||||
with InteractsWithZoneEnvironment
|
||||
with Hackable
|
||||
with FactionAffinity
|
||||
with Mountable
|
||||
|
|
@ -205,7 +207,7 @@ class Vehicle(private val vehicleDef: VehicleDefinition)
|
|||
|
||||
def NtuCapacitorScaled: Int = {
|
||||
if (Definition.MaxNtuCapacitor > 0) {
|
||||
scala.math.ceil((NtuCapacitor.toFloat / Definition.MaxNtuCapacitor.toFloat) * 10).toInt
|
||||
scala.math.ceil((NtuCapacitor / Definition.MaxNtuCapacitor) * 10).toInt
|
||||
} else {
|
||||
0
|
||||
}
|
||||
|
|
@ -630,6 +632,14 @@ object Vehicle {
|
|||
def apply(player: Player): Ownership = Ownership(Some(player))
|
||||
}
|
||||
|
||||
/**
|
||||
* For vehicles, this pertains mainly to resending information needs to display the the drowning red progress bar
|
||||
* that is a product of the `OxygenStateMessage` packet to vehicle passengers.
|
||||
* It also forces passengers to update their internal understanding of their own drowning state.
|
||||
* @param passenger a player mounted in the vehicle
|
||||
*/
|
||||
final case class UpdateZoneInteractionProgressUI(passenger : Player)
|
||||
|
||||
/**
|
||||
* Overloaded constructor.
|
||||
* @param vehicleDef the vehicle's definition entry
|
||||
|
|
@ -657,30 +667,31 @@ object Vehicle {
|
|||
//general stuff
|
||||
vehicle.Health = vdef.DefaultHealth
|
||||
//create weapons
|
||||
vehicle.weapons = vdef.Weapons
|
||||
.map({
|
||||
case (num, definition) =>
|
||||
val slot = EquipmentSlot(EquipmentSize.VehicleWeapon)
|
||||
slot.Equipment = Tool(definition)
|
||||
num -> slot
|
||||
})
|
||||
.toMap
|
||||
vehicle.weapons = vdef.Weapons.map[Int, EquipmentSlot] {
|
||||
case (num: Int, definition: ToolDefinition) =>
|
||||
val slot = EquipmentSlot(EquipmentSize.VehicleWeapon)
|
||||
slot.Equipment = Tool(definition)
|
||||
num -> slot
|
||||
}.toMap
|
||||
//create seats
|
||||
vehicle.seats = vdef.Seats.map({ case (num, definition) => num -> Seat(definition) }).toMap
|
||||
vehicle.seats = vdef.Seats.map[Int, Seat] {
|
||||
case (num: Int, definition: SeatDefinition) =>
|
||||
num -> Seat(definition)
|
||||
}.toMap
|
||||
// create cargo holds
|
||||
vehicle.cargoHolds = vdef.Cargo.map({ case (num, definition) => num -> Cargo(definition) }).toMap
|
||||
|
||||
vehicle.cargoHolds = vdef.Cargo.map[Int, Cargo] {
|
||||
case (num, definition) =>
|
||||
num -> Cargo(definition)
|
||||
}.toMap
|
||||
//create utilities
|
||||
vehicle.utilities = vdef.Utilities
|
||||
.map({
|
||||
case (num, util) =>
|
||||
val obj = Utility(util, vehicle)
|
||||
val utilObj = obj()
|
||||
vehicle.Amenities = utilObj
|
||||
utilObj.LocationOffset = vdef.UtilityOffset.get(num)
|
||||
num -> obj
|
||||
})
|
||||
.toMap
|
||||
vehicle.utilities = vdef.Utilities.map[Int, Utility] {
|
||||
case (num: Int, util: UtilityType.Value) =>
|
||||
val obj = Utility(util, vehicle)
|
||||
val utilObj = obj()
|
||||
vehicle.Amenities = utilObj
|
||||
utilObj.LocationOffset = vdef.UtilityOffset.get(num)
|
||||
num -> obj
|
||||
}.toMap
|
||||
//trunk
|
||||
vdef.TrunkSize match {
|
||||
case InventoryTile.None => ;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
// Copyright (c) 2020 PSForever
|
||||
package net.psforever.objects.avatar
|
||||
|
||||
import akka.actor.{Actor, ActorRef, Props}
|
||||
import akka.actor.{Actor, ActorRef, Props, typed}
|
||||
import net.psforever.actors.session.AvatarActor
|
||||
import net.psforever.objects.{Player, _}
|
||||
import net.psforever.objects.ballistics.{ObjectSource, PlayerSource}
|
||||
import net.psforever.objects.ballistics.PlayerSource
|
||||
import net.psforever.objects.equipment._
|
||||
import net.psforever.objects.inventory.{GridInventory, InventoryItem}
|
||||
import net.psforever.objects.loadouts.Loadout
|
||||
|
|
@ -18,18 +18,17 @@ import net.psforever.objects.serverobject.repair.Repairable
|
|||
import net.psforever.objects.serverobject.terminals.Terminal
|
||||
import net.psforever.objects.vital._
|
||||
import net.psforever.objects.vital.resolution.ResolutionCalculations.Output
|
||||
import net.psforever.objects.zones.Zone
|
||||
import net.psforever.objects.zones._
|
||||
import net.psforever.packet.game._
|
||||
import net.psforever.packet.game.objectcreate.ObjectCreateMessageParent
|
||||
import net.psforever.types._
|
||||
import net.psforever.services.{RemoverActor, Service}
|
||||
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||
import net.psforever.services.local.{LocalAction, LocalServiceMessage}
|
||||
import akka.actor.typed
|
||||
import net.psforever.objects.locker.LockerContainerControl
|
||||
import net.psforever.objects.serverobject.painbox.Painbox
|
||||
import net.psforever.objects.vital.base.DamageResolution
|
||||
import net.psforever.objects.vital.etc.SuicideReason
|
||||
import net.psforever.objects.serverobject.environment._
|
||||
import net.psforever.objects.vital.environment.EnvironmentReason
|
||||
import net.psforever.objects.vital.etc.{PainboxReason, SuicideReason}
|
||||
import net.psforever.objects.vital.interaction.{DamageInteraction, DamageResult}
|
||||
|
||||
import scala.concurrent.duration._
|
||||
|
|
@ -40,7 +39,8 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
|
|||
with Damageable
|
||||
with ContainableBehavior
|
||||
with AggravatedBehavior
|
||||
with AuraEffectBehavior {
|
||||
with AuraEffectBehavior
|
||||
with RespondsToZoneEnvironment {
|
||||
|
||||
def JammableObject = player
|
||||
|
||||
|
|
@ -49,15 +49,23 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
|
|||
def ContainerObject = player
|
||||
|
||||
def AggravatedObject = player
|
||||
|
||||
def AuraTargetObject = player
|
||||
ApplicableEffect(Aura.Plasma)
|
||||
ApplicableEffect(Aura.Napalm)
|
||||
ApplicableEffect(Aura.Comet)
|
||||
ApplicableEffect(Aura.Fire)
|
||||
|
||||
def AuraTargetObject = player
|
||||
def InteractiveObject = player
|
||||
SetInteraction(EnvironmentAttribute.Water, doInteractingWithWater)
|
||||
SetInteraction(EnvironmentAttribute.Lava, doInteractingWithLava)
|
||||
SetInteraction(EnvironmentAttribute.Death, doInteractingWithDeath)
|
||||
SetInteractionStop(EnvironmentAttribute.Water, stopInteractingWithWater)
|
||||
|
||||
private[this] val log = org.log4s.getLogger(player.Name)
|
||||
private[this] val damageLog = org.log4s.getLogger(Damageable.LogChannel)
|
||||
/** suffocating, or regaining breath? */
|
||||
var submergedCondition: Option[OxygenState] = None
|
||||
|
||||
/** control agency for the player's locker container (dedicated inventory slot #5) */
|
||||
val lockerControlAgent: ActorRef = {
|
||||
|
|
@ -82,33 +90,20 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
|
|||
.orElse(aggravatedBehavior)
|
||||
.orElse(auraBehavior)
|
||||
.orElse(containerBehavior)
|
||||
.orElse(environmentBehavior)
|
||||
.orElse {
|
||||
case Player.Die(Some(reason)) =>
|
||||
if (player.isAlive) {
|
||||
//primary death
|
||||
PerformDamage(player, reason.calculate())
|
||||
if(player.Health > 0 || player.isAlive) {
|
||||
//that wasn't good enough
|
||||
DestructionAwareness(player, None)
|
||||
}
|
||||
suicide()
|
||||
}
|
||||
|
||||
case Player.Die(None) =>
|
||||
if (player.isAlive) {
|
||||
//suicide
|
||||
PerformDamage(
|
||||
player,
|
||||
DamageInteraction(
|
||||
DamageResolution.Resolved,
|
||||
PlayerSource(player),
|
||||
SuicideReason(),
|
||||
player.Position
|
||||
).calculate()
|
||||
)
|
||||
}
|
||||
suicide()
|
||||
|
||||
case CommonMessages.Use(user, Some(item: Tool))
|
||||
if item.Definition == GlobalDefinitions.medicalapplicator && player.isAlive =>
|
||||
if item.Definition == GlobalDefinitions.medicalapplicator && player.isAlive =>
|
||||
//heal
|
||||
val originalHealth = player.Health
|
||||
val definition = player.Definition
|
||||
|
|
@ -564,7 +559,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
|
|||
if (target.Health > 0) {
|
||||
DamageAwareness(target, cause, damageToHealth, damageToArmor, damageToStamina, damageToCapacitor)
|
||||
} else {
|
||||
DestructionAwareness(target, Some(cause))
|
||||
DestructionAwareness(target, cause)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -639,11 +634,6 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
|
|||
)
|
||||
)
|
||||
}
|
||||
case source: ObjectSource if source.obj.isInstanceOf[Painbox] =>
|
||||
zone.AvatarEvents ! AvatarServiceMessage(
|
||||
target.Name,
|
||||
AvatarAction.EnvironmentalDamage(target.GUID, source.obj.GUID, countableDamage)
|
||||
)
|
||||
case source =>
|
||||
zone.AvatarEvents ! AvatarServiceMessage(
|
||||
target.Name,
|
||||
|
|
@ -654,6 +644,18 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
|
|||
)
|
||||
}
|
||||
case None =>
|
||||
cause.interaction.cause match {
|
||||
case o: PainboxReason =>
|
||||
zone.AvatarEvents ! AvatarServiceMessage(
|
||||
target.Name,
|
||||
AvatarAction.EnvironmentalDamage(target.GUID, o.entity.GUID, countableDamage)
|
||||
)
|
||||
case _ =>
|
||||
zone.AvatarEvents ! AvatarServiceMessage(
|
||||
target.Name,
|
||||
AvatarAction.EnvironmentalDamage(target.GUID, ValidPlanetSideGUID(0), countableDamage)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
|
@ -687,7 +689,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
|
|||
* @param target na
|
||||
* @param cause na
|
||||
*/
|
||||
def DestructionAwareness(target: Player, cause: Option[DamageResult]): Unit = {
|
||||
def DestructionAwareness(target: Player, cause: DamageResult): Unit = {
|
||||
val player_guid = target.GUID
|
||||
val pos = target.Position
|
||||
val respawnTimer = 300000 //milliseconds
|
||||
|
|
@ -702,7 +704,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
|
|||
EndAllAggravation()
|
||||
//unjam
|
||||
CancelJammeredSound(target)
|
||||
CancelJammeredStatus(target)
|
||||
super.CancelJammeredStatus(target)
|
||||
//uninitialize implants
|
||||
avatarActor ! AvatarActor.DeinitializeImplants()
|
||||
events ! AvatarServiceMessage(
|
||||
|
|
@ -736,10 +738,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
|
|||
target.Capacitor = 0
|
||||
events ! AvatarServiceMessage(nameChannel, AvatarAction.PlanetsideAttributeToAll(player_guid, 7, 0)) // capacitor
|
||||
}
|
||||
val attribute = cause match {
|
||||
case Some(reason) => DamageableEntity.attributionTo(reason, target.Zone, player_guid)
|
||||
case None => player_guid
|
||||
}
|
||||
val attribute = DamageableEntity.attributionTo(cause, target.Zone, player_guid)
|
||||
events ! AvatarServiceMessage(
|
||||
nameChannel,
|
||||
AvatarAction.SendResponse(
|
||||
|
|
@ -756,17 +755,17 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
|
|||
)
|
||||
//TODO other methods of death?
|
||||
val pentry = PlayerSource(target)
|
||||
(cause match {
|
||||
case Some(result) =>
|
||||
result.adversarial
|
||||
case None =>
|
||||
(cause.adversarial match {
|
||||
case out @ Some(_) =>
|
||||
out
|
||||
case _ =>
|
||||
target.LastDamage match {
|
||||
case Some(attack) if System.currentTimeMillis() - attack.interaction.hitTime < (10 seconds).toMillis =>
|
||||
attack.adversarial
|
||||
case None =>
|
||||
None
|
||||
}
|
||||
}) match {
|
||||
}) match {
|
||||
case Some(adversarial) =>
|
||||
events ! AvatarServiceMessage(
|
||||
zoneChannel,
|
||||
|
|
@ -777,6 +776,19 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
|
|||
}
|
||||
}
|
||||
|
||||
def suicide() : Unit = {
|
||||
if (player.Health > 0 || player.isAlive) {
|
||||
PerformDamage(
|
||||
player,
|
||||
DamageInteraction(
|
||||
PlayerSource(player),
|
||||
SuicideReason(),
|
||||
player.Position
|
||||
).calculate()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the jammered buzzing.
|
||||
* Although, as a rule, the jammering sound effect should last as long as the jammering status,
|
||||
|
|
@ -968,6 +980,109 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
|
|||
val value = target.Aura.foldLeft(0)(_ + PlayerControl.auraEffectToAttributeValue(_))
|
||||
zone.AvatarEvents ! AvatarServiceMessage(zone.id, AvatarAction.PlanetsideAttributeToAll(target.GUID, 54, value))
|
||||
}
|
||||
|
||||
/**
|
||||
* Water causes players to slowly suffocate.
|
||||
* When they (finally) drown, they will die.
|
||||
* @param obj the target
|
||||
* @param body the environment
|
||||
* @param data additional interaction information, if applicable;
|
||||
* for players, this will be data from any mounted vehicles
|
||||
*/
|
||||
def doInteractingWithWater(obj: PlanetSideServerObject, body: PieceOfEnvironment, data: Option[OxygenStateTarget]): Unit = {
|
||||
val (effect: Boolean, time: Long, percentage: Float) =
|
||||
RespondsToZoneEnvironment.drowningInWateryConditions(obj, submergedCondition, interactionTime)
|
||||
if (effect) {
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
interactionTime = System.currentTimeMillis() + time
|
||||
submergedCondition = Some(OxygenState.Suffocation)
|
||||
interactionTimer = context.system.scheduler.scheduleOnce(delay = time milliseconds, self, Player.Die())
|
||||
//inform the player that they are in trouble
|
||||
player.Zone.AvatarEvents ! AvatarServiceMessage(
|
||||
player.Name,
|
||||
AvatarAction.OxygenState(OxygenStateTarget(player.GUID, OxygenState.Suffocation, percentage), data)
|
||||
)
|
||||
} else if (data.isDefined) {
|
||||
//inform the player that their mounted vehicle is in trouble (that they are in trouble)
|
||||
player.Zone.AvatarEvents ! AvatarServiceMessage(
|
||||
player.Name,
|
||||
AvatarAction.OxygenState(OxygenStateTarget(player.GUID, OxygenState.Suffocation, percentage), data)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lava causes players to take (considerable) damage until they inevitably die.
|
||||
* @param obj the target
|
||||
* @param body the environment
|
||||
* @param data additional interaction information, if applicable
|
||||
*/
|
||||
def doInteractingWithLava(obj: PlanetSideServerObject, body: PieceOfEnvironment, data: Option[OxygenStateTarget]): Unit = {
|
||||
if (player.isAlive) {
|
||||
PerformDamage(
|
||||
player,
|
||||
DamageInteraction(
|
||||
PlayerSource(player),
|
||||
EnvironmentReason(body, player),
|
||||
player.Position
|
||||
).calculate()
|
||||
)
|
||||
if (player.Health > 0) {
|
||||
StartAuraEffect(Aura.Fire, duration = 1250L) //burn
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
interactionTimer = context.system.scheduler.scheduleOnce(delay = 250 milliseconds, self, InteractWithEnvironment(player, body, None))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Death causes players to die outright.
|
||||
* It's not even considered as environmental damage anymore.
|
||||
* @param obj the target
|
||||
* @param body the environment
|
||||
* @param data additional interaction information, if applicable
|
||||
*/
|
||||
def doInteractingWithDeath(obj: PlanetSideServerObject, body: PieceOfEnvironment, data: Option[OxygenStateTarget]): Unit = {
|
||||
suicide()
|
||||
}
|
||||
|
||||
/**
|
||||
* When out of water, the player is no longer suffocating.
|
||||
* The player does have to endure a recovery period to get back to normal, though.
|
||||
* @param obj the target
|
||||
* @param body the environment
|
||||
* @param data additional interaction information, if applicable;
|
||||
* for players, this will be data from any mounted vehicles
|
||||
*/
|
||||
def stopInteractingWithWater(obj: PlanetSideServerObject, body: PieceOfEnvironment, data: Option[OxygenStateTarget]): Unit = {
|
||||
val (effect: Boolean, time: Long, percentage: Float) =
|
||||
RespondsToZoneEnvironment.recoveringFromWateryConditions(obj, submergedCondition, interactionTime)
|
||||
if (percentage == 100f) {
|
||||
recoverFromEnvironmentInteracting()
|
||||
}
|
||||
if (effect) {
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
submergedCondition = Some(OxygenState.Recovery)
|
||||
interactionTime = System.currentTimeMillis() + time
|
||||
interactionTimer = context.system.scheduler.scheduleOnce(delay = time milliseconds, self, RecoveredFromEnvironmentInteraction())
|
||||
//inform the player
|
||||
player.Zone.AvatarEvents ! AvatarServiceMessage(
|
||||
player.Name,
|
||||
AvatarAction.OxygenState(OxygenStateTarget(player.GUID, OxygenState.Recovery, percentage), data)
|
||||
)
|
||||
} else if (data.isDefined) {
|
||||
//inform the player
|
||||
player.Zone.AvatarEvents ! AvatarServiceMessage(
|
||||
player.Name,
|
||||
AvatarAction.OxygenState(OxygenStateTarget(player.GUID, OxygenState.Recovery, percentage), data)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override def recoverFromEnvironmentInteracting(): Unit = {
|
||||
super.recoverFromEnvironmentInteracting()
|
||||
submergedCondition = None
|
||||
}
|
||||
}
|
||||
|
||||
object PlayerControl {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package net.psforever.objects.definition
|
|||
|
||||
import net.psforever.objects.PlanetSideGameObject
|
||||
import net.psforever.objects.definition.converter.{ObjectCreateConverter, PacketConverter}
|
||||
import net.psforever.types.OxygenState
|
||||
|
||||
/**
|
||||
* Associate an object's canned in-game representation with its basic game identification unit.
|
||||
|
|
@ -40,5 +41,40 @@ abstract class ObjectDefinition(private val objectId: Int) extends BasicDefiniti
|
|||
Packet
|
||||
}
|
||||
|
||||
private var maxDepth: Float = 0 //water_maxdragdepth
|
||||
private var disableAtMaxDepth: Boolean = false
|
||||
private var drownAtMaxDepth: Boolean = false
|
||||
private var underwaterLifespan: Map[OxygenState, Long] = Map.empty //water_underwaterlifespan and water_underwaterlifespanrecovery
|
||||
|
||||
def MaxDepth: Float = maxDepth
|
||||
|
||||
def MaxDepth_=(height: Float): Float = {
|
||||
maxDepth = height
|
||||
MaxDepth
|
||||
}
|
||||
|
||||
def DisableAtMaxDepth: Boolean = disableAtMaxDepth
|
||||
|
||||
def DisableAtMaxDepth_=(drowns: Boolean): Boolean = {
|
||||
disableAtMaxDepth = drowns
|
||||
DisableAtMaxDepth
|
||||
}
|
||||
|
||||
def DrownAtMaxDepth: Boolean = drownAtMaxDepth
|
||||
|
||||
def DrownAtMaxDepth_=(drowns: Boolean): Boolean = {
|
||||
drownAtMaxDepth = drowns
|
||||
DrownAtMaxDepth
|
||||
}
|
||||
|
||||
def UnderwaterLifespan(): Map[OxygenState, Long] = underwaterLifespan
|
||||
|
||||
def UnderwaterLifespan(key: OxygenState): Long = underwaterLifespan.getOrElse(key, 1L)
|
||||
|
||||
def UnderwaterLifespan(suffocation: Long, recovery: Long): Map[OxygenState, Long] = {
|
||||
underwaterLifespan = Map(OxygenState.Suffocation -> suffocation, OxygenState.Recovery -> recovery)
|
||||
UnderwaterLifespan()
|
||||
}
|
||||
|
||||
def ObjectId: Int = objectId
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ object EffectTarget {
|
|||
def RepairSilo(target: PlanetSideGameObject): Boolean =
|
||||
target match {
|
||||
case v: Vehicle =>
|
||||
!GlobalDefinitions.isFlightVehicle(v.Definition) && v.Health > 0 && v.Health < v.MaxHealth && v.History.find(x => x.isInstanceOf[DamagingActivity] && x.t >= (System.nanoTime - 5000000000L)).isEmpty
|
||||
!GlobalDefinitions.isFlightVehicle(v.Definition) && v.Health > 0 && v.Health < v.MaxHealth && v.History.exists(x => x.isInstanceOf[DamagingActivity] && x.time >= (System.currentTimeMillis() - 5000000000L))
|
||||
case _ =>
|
||||
false
|
||||
}
|
||||
|
|
@ -50,7 +50,7 @@ object EffectTarget {
|
|||
def PadLanding(target: PlanetSideGameObject): Boolean =
|
||||
target match {
|
||||
case v: Vehicle =>
|
||||
GlobalDefinitions.isFlightVehicle(v.Definition) && v.Health > 0 && v.Health < v.MaxHealth && v.History.find(x => x.isInstanceOf[DamagingActivity] && x.t >= (System.nanoTime - 5000000000L)).isEmpty
|
||||
GlobalDefinitions.isFlightVehicle(v.Definition) && v.Health > 0 && v.Health < v.MaxHealth && v.History.exists(x => x.isInstanceOf[DamagingActivity] && x.time >= (System.currentTimeMillis() - 5000000000L))
|
||||
case _ =>
|
||||
false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,6 +33,10 @@ trait AggravatedBehavior {
|
|||
(o.projectile.quality == ProjectileQuality.AggravatesTarget ||
|
||||
damage.targets.exists(validation => validation.test(AggravatedObject))) =>
|
||||
TryAggravationEffectActivate(damage, data.interaction)
|
||||
case (_: DamageReason, Some(damage))
|
||||
if damage.effect_type != Aura.Nothing &&
|
||||
damage.targets.exists(validation => validation.test(AggravatedObject)) =>
|
||||
TryAggravationEffectActivate(damage, data.interaction)
|
||||
case _ =>
|
||||
None
|
||||
}
|
||||
|
|
|
|||
|
|
@ -115,8 +115,10 @@ trait DamageableVehicle
|
|||
}
|
||||
reportDamageToVehicle = false
|
||||
|
||||
//log historical event
|
||||
target.History(cause)
|
||||
if (obj.MountedIn.nonEmpty) {
|
||||
//log historical event
|
||||
target.History(cause)
|
||||
}
|
||||
//damage
|
||||
if (Damageable.CanDamageOrJammer(target, totalDamage, cause.interaction)) {
|
||||
//jammering
|
||||
|
|
|
|||
|
|
@ -0,0 +1,86 @@
|
|||
// Copyright (c) 2020 PSForever
|
||||
package net.psforever.objects.serverobject.environment
|
||||
|
||||
import net.psforever.types.Vector3
|
||||
|
||||
/**
|
||||
* The coordinate representation of a feature of the game world that is not a formal game object,
|
||||
* usually terrain, but can be used to represent any bounded region.
|
||||
* Calling this "geometry" would be accurate yet still generous.
|
||||
*/
|
||||
trait EnvironmentCollision {
|
||||
/** in general, the highest point in this geometry */
|
||||
def altitude: Float
|
||||
|
||||
/**
|
||||
* Is the test point "within" the bounds of the represented environment?
|
||||
* @param pos the test point
|
||||
* @param varDepth how far "into" the environment the point must be
|
||||
* @return `true`, if the point is sufficiently "deep";
|
||||
* `false`, otherwise
|
||||
*/
|
||||
def testInteraction(pos: Vector3, varDepth: Float): Boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* A mathematical plane that is always perpendicular to world-up.
|
||||
* The modifier "deep" indicates that the valid area goes down from the altitude to the bottom of the world.
|
||||
* @param altitude the z-coordinate of the geometry (height)
|
||||
*/
|
||||
final case class DeepPlane(altitude: Float)
|
||||
extends EnvironmentCollision {
|
||||
def testInteraction(pos: Vector3, varDepth: Float): Boolean = {
|
||||
pos.z + varDepth < altitude
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* From above, a rectangular region that is always perpendicular to world-up
|
||||
* and whose sides align with the X-axis and Y-axis, respectively.
|
||||
* The modifier "deep" indicates that the valid area goes down from the altitude to the bottom of the world.
|
||||
* @param altitude the z-coordinate of the geometry (height)
|
||||
* @param north the y-coordinate of the greatest side
|
||||
* @param east the x-coordinate of the other greatest side
|
||||
* @param south the y-coordinate of the least side
|
||||
* @param west the x-coordinate of the other least side
|
||||
*/
|
||||
final case class DeepSquare(altitude: Float, north: Float, east: Float, south: Float, west: Float)
|
||||
extends EnvironmentCollision {
|
||||
def testInteraction(pos: Vector3, varDepth: Float): Boolean = {
|
||||
pos.z + varDepth < altitude && north > pos.y && pos.y >= south && east > pos.x && pos.x >= west
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to `DeepRectangle`,
|
||||
* from above, a rectangular region that is always perpendicular to world-up
|
||||
* and whose sides align with the X-axis and Y-axis, respectively.
|
||||
* The modifier "deep" indicates that the valid area goes down from the altitude to the bottom of the world.
|
||||
* It is never subject to variable intersection depth during testing.
|
||||
* @param altitude the z-coordinate of the geometry (height)
|
||||
* @param north the y-coordinate of the greatest side
|
||||
* @param east the x-coordinate of the other greatest side
|
||||
* @param south the y-coordinate of the least side
|
||||
* @param west the x-coordinate of the other least side
|
||||
*/
|
||||
final case class DeepSurface(altitude: Float, north: Float, east: Float, south: Float, west: Float)
|
||||
extends EnvironmentCollision {
|
||||
def testInteraction(pos: Vector3, varDepth: Float): Boolean = {
|
||||
pos.z < altitude && north > pos.y && pos.y >= south && east > pos.x && pos.x >= west
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* From above, a circular region that is always perpendicular to world-up.
|
||||
* The modifier "deep" indicates that the valid area goes down from the altitude to the bottom of the world.
|
||||
* @param center the center of the geometry (height)
|
||||
* @param radius how large the circle is
|
||||
*/
|
||||
final case class DeepCircularSurface(center: Vector3, radius: Float)
|
||||
extends EnvironmentCollision {
|
||||
def altitude: Float = center.z
|
||||
|
||||
def testInteraction(pos: Vector3, varDepth: Float): Boolean = {
|
||||
pos.z < center.z && Vector3.DistanceSquared(pos.xy, center.xy) < radius * radius
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
// Copyright (c) 2020 PSForever
|
||||
package net.psforever.objects.serverobject.environment
|
||||
|
||||
import net.psforever.objects.serverobject.PlanetSideServerObject
|
||||
import net.psforever.types.{OxygenState, PlanetSideGUID}
|
||||
|
||||
/**
|
||||
* Related to the progress of interacting with a body of water deeper than you are tall or
|
||||
* deeper than your vehicle is off the ground.
|
||||
* @param guid the target
|
||||
* @param state whether they are recovering or suffocating
|
||||
* @param progress the percentage of completion towards the state
|
||||
*/
|
||||
final case class OxygenStateTarget(
|
||||
guid: PlanetSideGUID,
|
||||
state: OxygenState,
|
||||
progress: Float
|
||||
)
|
||||
|
||||
/**
|
||||
* The target has clipped into a critical region of a piece of environment.
|
||||
* @param obj the target
|
||||
* @param environment the terrain clipping region
|
||||
* @param mountedVehicle whether or not the target is mounted
|
||||
* (specifically, if the target is a `Player` who is mounted in a `Vehicle`)
|
||||
*/
|
||||
final case class InteractWithEnvironment(
|
||||
obj: PlanetSideServerObject,
|
||||
environment: PieceOfEnvironment,
|
||||
mountedVehicle: Option[OxygenStateTarget]
|
||||
)
|
||||
|
||||
/**
|
||||
* The target has ceased to clip into a critical region of a piece of environment.
|
||||
* @param obj the target
|
||||
* @param environment the previous terrain clipping region
|
||||
* @param mountedVehicle whether or not the target is mounted
|
||||
* (specifically, if the target is a `Player` who is mounted in a `Vehicle`)
|
||||
*/
|
||||
final case class EscapeFromEnvironment(
|
||||
obj: PlanetSideServerObject,
|
||||
environment: PieceOfEnvironment,
|
||||
mountedVehicle: Option[OxygenStateTarget]
|
||||
)
|
||||
|
||||
/**
|
||||
* Completely reset any internal actions or processes related to environment clipping.
|
||||
*/
|
||||
final case class RecoveredFromEnvironmentInteraction()
|
||||
|
|
@ -0,0 +1,185 @@
|
|||
// Copyright (c) 2020 PSForever
|
||||
package net.psforever.objects.serverobject.environment
|
||||
|
||||
import net.psforever.objects.GlobalDefinitions
|
||||
import net.psforever.objects.serverobject.PlanetSideServerObject
|
||||
import net.psforever.objects.zones.Zone
|
||||
|
||||
/**
|
||||
* This game entity may infrequently test whether it may interact with game world environment.
|
||||
*/
|
||||
trait InteractsWithZoneEnvironment {
|
||||
_: PlanetSideServerObject =>
|
||||
/** interactions for this particular entity is allowed */
|
||||
private var _allowZoneEnvironmentInteractions: Boolean = true
|
||||
|
||||
/**
|
||||
* If the environmental interactive permissions of this entity change.
|
||||
*/
|
||||
def allowZoneEnvironmentInteractions: Boolean = _allowZoneEnvironmentInteractions
|
||||
|
||||
/**
|
||||
* If the environmental interactive permissions of this entity change,
|
||||
* trigger a formal change to the interaction methodology.
|
||||
* @param allow whether or not interaction is permitted
|
||||
* @return whether or not interaction is permitted
|
||||
*/
|
||||
def allowZoneEnvironmentInteractions_=(allow: Boolean): Boolean = {
|
||||
val before = _allowZoneEnvironmentInteractions
|
||||
_allowZoneEnvironmentInteractions = allow
|
||||
if (before != allow) {
|
||||
zoneInteraction()
|
||||
}
|
||||
_allowZoneEnvironmentInteractions
|
||||
}
|
||||
|
||||
private var interactingWithEnvironment: (PlanetSideServerObject, Boolean) => Any =
|
||||
InteractsWithZoneEnvironment.onStableEnvironment()
|
||||
|
||||
/**
|
||||
* The method by which zone interactions are tested or a current interaction maintained.
|
||||
* Utilize a function literal that, when called, returns a function literal of the same type;
|
||||
* the function that is returned will not necessarily be the same as the one that was used
|
||||
* but will represent the existing and ongoing status of interaction with the environment.
|
||||
* Calling one function and exchanging it for another function to be called like this creates a procedure
|
||||
* that controls and limits the interactions with the environment to only what is necessary.
|
||||
* @see `InteractsWithZoneEnvironment.blockedFromInteracting`
|
||||
* @see `InteractsWithZoneEnvironment.onStableEnvironment`
|
||||
* @see `InteractsWithZoneEnvironment.awaitOngoingInteraction`
|
||||
*/
|
||||
def zoneInteraction(): Unit = {
|
||||
//val func: (PlanetSideServerObject, Boolean) => Any = interactingWithEnvironment(this, allowZoneEnvironmentInteractions)
|
||||
interactingWithEnvironment = interactingWithEnvironment(this, allowZoneEnvironmentInteractions)
|
||||
.asInstanceOf[(PlanetSideServerObject, Boolean) => Any]
|
||||
}
|
||||
|
||||
/**
|
||||
* Suspend any current interaction procedures through the proper channels
|
||||
* or deactivate a previously flagged interaction blocking procedure
|
||||
* and reset the system to its neutral state.
|
||||
* The main difference between resetting and flagging the blocking procedure
|
||||
* is that resetting will (probably) restore the previously active procedure on the next `zoneInteraction` call
|
||||
* while blocking will halt all attempts to establish a new active interaction procedure
|
||||
* and unblocking will immediately install whatever is the current active interaction.
|
||||
* @see `InteractsWithZoneEnvironment.onStableEnvironment`
|
||||
*/
|
||||
def resetZoneInteraction() : Unit = {
|
||||
_allowZoneEnvironmentInteractions = true
|
||||
interactingWithEnvironment(this, false)
|
||||
interactingWithEnvironment = InteractsWithZoneEnvironment.onStableEnvironment()
|
||||
}
|
||||
}
|
||||
|
||||
object InteractsWithZoneEnvironment {
|
||||
/**
|
||||
* While on stable non-interactive terrain,
|
||||
* test whether any special terrain component has an affect upon the target entity.
|
||||
* If so, instruct the target that an interaction should occur.
|
||||
* Considered tail recursive, but not treated that way.
|
||||
* @see `blockedFromInteracting`
|
||||
* @see `checkAllEnvironmentInteractions`
|
||||
* @see `awaitOngoingInteraction`
|
||||
* @param obj the target entity
|
||||
* @return the function literal that represents the next iterative call of ongoing interaction testing;
|
||||
* may return itself
|
||||
*/
|
||||
def onStableEnvironment()(obj: PlanetSideServerObject, allow: Boolean): Any = {
|
||||
if(allow) {
|
||||
checkAllEnvironmentInteractions(obj) match {
|
||||
case Some(body) =>
|
||||
obj.Actor ! InteractWithEnvironment(obj, body, None)
|
||||
awaitOngoingInteraction(obj.Zone, body)(_,_)
|
||||
case None =>
|
||||
onStableEnvironment()(_,_)
|
||||
}
|
||||
} else {
|
||||
blockedFromInteracting()(_,_)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* While on unstable, interactive, or special terrain,
|
||||
* test whether that special terrain component has an affect upon the target entity.
|
||||
* If no interaction exists,
|
||||
* treat the target as if it had been previously affected by the given terrain,
|
||||
* and instruct it to cease that assumption.
|
||||
* Transition between the affects of different special terrains is possible.
|
||||
* Considered tail recursive, but not treated that way.
|
||||
* @see `blockedFromInteracting`
|
||||
* @see `checkAllEnvironmentInteractions`
|
||||
* @see `checkSpecificEnvironmentInteraction`
|
||||
* @see `onStableEnvironment`
|
||||
* @param zone the zone in which the terrain is located
|
||||
* @param body the special terrain
|
||||
* @param obj the target entity
|
||||
* @return the function literal that represents the next iterative call of ongoing interaction testing;
|
||||
* may return itself
|
||||
*/
|
||||
def awaitOngoingInteraction(zone: Zone, body: PieceOfEnvironment)(obj: PlanetSideServerObject, allow: Boolean): Any = {
|
||||
if (allow) {
|
||||
checkSpecificEnvironmentInteraction(zone, body)(obj) match {
|
||||
case Some(_) =>
|
||||
awaitOngoingInteraction(obj.Zone, body)(_, _)
|
||||
case None =>
|
||||
checkAllEnvironmentInteractions(obj) match {
|
||||
case Some(newBody) if newBody.attribute == body.attribute =>
|
||||
obj.Actor ! InteractWithEnvironment(obj, newBody, None)
|
||||
awaitOngoingInteraction(obj.Zone, newBody)(_, _)
|
||||
case Some(newBody) =>
|
||||
obj.Actor ! EscapeFromEnvironment(obj, body, None)
|
||||
obj.Actor ! InteractWithEnvironment(obj, newBody, None)
|
||||
awaitOngoingInteraction(obj.Zone, newBody)(_, _)
|
||||
case None =>
|
||||
obj.Actor ! EscapeFromEnvironment(obj, body, None)
|
||||
onStableEnvironment()(_, _)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
obj.Actor ! EscapeFromEnvironment(obj, body, None)
|
||||
blockedFromInteracting()(_,_)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Do not care whether on stable non-interactive terrain or on unstable interactive terrain.
|
||||
* Wait until allowed to test again (external flag).
|
||||
* Considered tail recursive, but not treated that way.
|
||||
* @see `onStableEnvironment`
|
||||
* @param obj the target entity
|
||||
* @return the function literal that represents the next iterative call of ongoing interaction testing;
|
||||
* may return itself
|
||||
*/
|
||||
def blockedFromInteracting()(obj: PlanetSideServerObject, allow: Boolean): Any = {
|
||||
if (allow) {
|
||||
onStableEnvironment()(obj, allow)
|
||||
} else {
|
||||
blockedFromInteracting()(_,_)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether any special terrain component has an affect upon the target entity.
|
||||
* @param obj the target entity
|
||||
* @return any unstable, interactive, or special terrain that is being interacted
|
||||
*/
|
||||
def checkAllEnvironmentInteractions(obj: PlanetSideServerObject): Option[PieceOfEnvironment] = {
|
||||
val position = obj.Position
|
||||
val depth = GlobalDefinitions.MaxDepth(obj)
|
||||
obj.Zone.map.environment.find { body => body.attribute.canInteractWith(obj) && body.testInteraction(position, depth) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether a special terrain component has an affect upon the target entity.
|
||||
* @param zone the zone in which the terrain is located
|
||||
* @param body the special terrain
|
||||
* @param obj the target entity
|
||||
* @return any unstable, interactive, or special terrain that is being interacted
|
||||
*/
|
||||
private def checkSpecificEnvironmentInteraction(zone: Zone, body: PieceOfEnvironment)(obj: PlanetSideServerObject): Option[PieceOfEnvironment] = {
|
||||
if ((obj.Zone eq zone) && body.testInteraction(obj.Position, GlobalDefinitions.MaxDepth(obj))) {
|
||||
Some(body)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,146 @@
|
|||
// Copyright (c) 2020 PSForever
|
||||
package net.psforever.objects.serverobject.environment
|
||||
|
||||
import enumeratum.{Enum, EnumEntry}
|
||||
import net.psforever.objects.PlanetSideGameObject
|
||||
import net.psforever.objects.vital.Vitality
|
||||
import net.psforever.types.Vector3
|
||||
|
||||
/**
|
||||
* The representation of a feature of the game world that is not a formal game object,
|
||||
* usually terrain, but can be used to represent any bounded region.
|
||||
*/
|
||||
trait PieceOfEnvironment {
|
||||
/** a general description of this environment */
|
||||
def attribute: EnvironmentTrait
|
||||
/** a special representation of the region that qualifies as "this environment" */
|
||||
def collision: EnvironmentCollision
|
||||
|
||||
/**
|
||||
* Is the test point "within" the bounds of the represented environment?
|
||||
* @param pos the test point
|
||||
* @param varDepth how far "into" the environment the point must be
|
||||
* @return `true`, if the point is sufficiently "deep";
|
||||
* `false`, otherwise
|
||||
*/
|
||||
def testInteraction(pos: Vector3, varDepth: Float): Boolean = collision.testInteraction(pos, varDepth)
|
||||
|
||||
/**
|
||||
* Did the test point move into or leave the bounds of the represented environment since its previous test?
|
||||
* @param pos the test point
|
||||
* @param previousPos the previous test point which is being compared against
|
||||
* @param varDepth how far "into" the environment the point must be
|
||||
* @return `Some(true)`, if the point has become sufficiently "deep";
|
||||
* `Some(false)`, if the point has left the sufficiently "deep" region;
|
||||
* `None`, otherwise
|
||||
*/
|
||||
def testStepIntoInteraction(pos: Vector3, previousPos: Vector3, varDepth: Float): Option[Boolean] =
|
||||
PieceOfEnvironment.testStepIntoInteraction(body = this, pos, previousPos, varDepth)
|
||||
}
|
||||
|
||||
/**
|
||||
* A general description of environment and its interactive possibilities.
|
||||
*/
|
||||
sealed abstract class EnvironmentTrait extends EnumEntry {
|
||||
def canInteractWith(obj: PlanetSideGameObject): Boolean
|
||||
}
|
||||
|
||||
object EnvironmentAttribute extends Enum[EnvironmentTrait] {
|
||||
/** glue connecting `EnumEntry` to `Enumeration` */
|
||||
val values: IndexedSeq[EnvironmentTrait] = findValues
|
||||
|
||||
case object Water extends EnvironmentTrait {
|
||||
/** water can only interact with objects that are negatively affected by being exposed to water;
|
||||
* it's better this way */
|
||||
def canInteractWith(obj: PlanetSideGameObject): Boolean = {
|
||||
obj.Definition.DrownAtMaxDepth || obj.Definition.DisableAtMaxDepth
|
||||
}
|
||||
}
|
||||
|
||||
case object Lava extends EnvironmentTrait {
|
||||
/** lava can only interact with anything capable of registering damage */
|
||||
def canInteractWith(obj: PlanetSideGameObject): Boolean = {
|
||||
obj match {
|
||||
case o: Vitality => o.Definition.Damageable
|
||||
case _ => false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case object Death extends EnvironmentTrait {
|
||||
/** death can only interact with anything capable of registering damage */
|
||||
def canInteractWith(obj: PlanetSideGameObject): Boolean = {
|
||||
obj match {
|
||||
case o: Vitality => o.Definition.Damageable
|
||||
case _ => false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A planar environment that spans the whole of the game world
|
||||
* and starts at and below a certain altitude.
|
||||
* @param attribute of what the environment is composed
|
||||
* @param altitude how high the environment starts
|
||||
*/
|
||||
final case class SeaLevel(attribute: EnvironmentTrait, altitude: Float)
|
||||
extends PieceOfEnvironment {
|
||||
private val planar = DeepPlane(altitude)
|
||||
|
||||
def collision : EnvironmentCollision = planar
|
||||
}
|
||||
|
||||
object SeaLevel {
|
||||
/**
|
||||
* An overloaded constructor that applies only to water.
|
||||
* @param altitude how high the environment starts
|
||||
* @return a `SeaLevel` `PieceOfEnvironment` object
|
||||
*/
|
||||
def apply(altitude: Float): SeaLevel = SeaLevel(EnvironmentAttribute.Water, altitude)
|
||||
}
|
||||
|
||||
/**
|
||||
* A limited environment that spans no specific region.
|
||||
* @param attribute of what the environment is composed
|
||||
* @param collision a special representation of the region that qualifies as "this environment"
|
||||
*/
|
||||
final case class Pool(attribute: EnvironmentTrait, collision: EnvironmentCollision)
|
||||
extends PieceOfEnvironment
|
||||
|
||||
object Pool {
|
||||
/**
|
||||
* An overloaded constructor that creates environment backed by a `DeepSquare`.
|
||||
* @param attribute of what the environment is composed
|
||||
* @param altitude the z-coordinate of the geometry (height)
|
||||
* @param north the y-coordinate of the greatest side
|
||||
* @param east the x-coordinate of the other greatest side
|
||||
* @param south the y-coordinate of the least side
|
||||
* @param west the x-coordinate of the other least side
|
||||
* @return a `Pool` `PieceOfEnvironment` object
|
||||
*/
|
||||
def apply(attribute: EnvironmentTrait, altitude: Float, north: Float, east: Float, south: Float, west: Float): Pool =
|
||||
Pool(attribute, DeepSquare(altitude, north, east, south, west))
|
||||
}
|
||||
|
||||
object PieceOfEnvironment {
|
||||
/**
|
||||
* Did the test point move into or leave the bounds of the represented environment since its previous test?
|
||||
* @param body the environment
|
||||
* @param pos the test point
|
||||
* @param previousPos the previous test point which is being compared against
|
||||
* @param varDepth how far "into" the environment the point must be
|
||||
* @return `Some(true)`, if the point has become sufficiently "deep";
|
||||
* `Some(false)`, if the point has left the sufficiently "deep" region;
|
||||
* `None`, if the described points only exist outside of or only exists inside of the critical region
|
||||
*/
|
||||
def testStepIntoInteraction(body: PieceOfEnvironment, pos: Vector3, previousPos: Vector3, varDepth: Float): Option[Boolean] = {
|
||||
val isEncroaching = body.collision.testInteraction(pos, varDepth)
|
||||
val wasEncroaching = body.collision.testInteraction(previousPos, varDepth)
|
||||
if (isEncroaching != wasEncroaching) {
|
||||
Some(isEncroaching)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,179 @@
|
|||
// Copyright (c) 2020 PSForever
|
||||
package net.psforever.objects.serverobject.environment
|
||||
|
||||
import akka.actor.{Actor, Cancellable}
|
||||
import net.psforever.objects.Default
|
||||
import net.psforever.objects.serverobject.PlanetSideServerObject
|
||||
import net.psforever.types.OxygenState
|
||||
|
||||
import scala.collection.mutable
|
||||
|
||||
/**
|
||||
* The mixin code for any server object that responds to the game world around it.
|
||||
* Specific types of environmental region is bound by geometry,
|
||||
* designated by attributes,
|
||||
* and gets reacted to when coming into contact with that geometry.
|
||||
* Ideally, the target under control instigates the responses towards the environment
|
||||
* by independently re-evaluating the conditions of its interactions.
|
||||
* Only one kind of environment can elicit a response at a time.
|
||||
* While a reversal of this trigger scheme is possible, it is not ideal.
|
||||
* @see `InteractsWithZoneEnvironment`
|
||||
* @see `PieceOfEnvironment`
|
||||
*/
|
||||
trait RespondsToZoneEnvironment {
|
||||
_: Actor =>
|
||||
/** how long the current interaction has been progressing in the current way */
|
||||
var interactionTime : Long = 0
|
||||
/** the environment that we are currently in interaction with */
|
||||
var interactWith : Option[PieceOfEnvironment] = None
|
||||
/** a gesture of automation added to the interaction */
|
||||
var interactionTimer : Cancellable = Default.Cancellable
|
||||
/** a mapping of responses when specific interactions occur;
|
||||
* select from these options when starting an effect;
|
||||
* key - type of environment, value - reaction function */
|
||||
private var interactWithEnvironmentStart: mutable.HashMap[EnvironmentTrait, RespondsToZoneEnvironment.Interaction] =
|
||||
mutable.HashMap[EnvironmentTrait, RespondsToZoneEnvironment.Interaction]()
|
||||
/** a mapping of responses when specific interactions cease;
|
||||
* select from these options when ending an effect;
|
||||
* key - type of environment, value - reaction function */
|
||||
private var interactWithEnvironmentStop: mutable.HashMap[EnvironmentTrait, RespondsToZoneEnvironment.Interaction] =
|
||||
mutable.HashMap[EnvironmentTrait, RespondsToZoneEnvironment.Interaction]()
|
||||
|
||||
def InteractiveObject: PlanetSideServerObject with InteractsWithZoneEnvironment
|
||||
|
||||
val environmentBehavior: Receive = {
|
||||
case InteractWithEnvironment(target, body, optional) =>
|
||||
doEnvironmentInteracting(target, body, optional)
|
||||
|
||||
case EscapeFromEnvironment(target, body, optional) =>
|
||||
stopEnvironmentInteracting(target, body, optional)
|
||||
|
||||
case RecoveredFromEnvironmentInteraction() =>
|
||||
recoverFromEnvironmentInteracting()
|
||||
}
|
||||
|
||||
def InteractWith: Option[PieceOfEnvironment] = interactWith
|
||||
|
||||
def SetInteraction(attribute: EnvironmentTrait, action: RespondsToZoneEnvironment.Interaction): Unit = {
|
||||
interactWithEnvironmentStart += attribute -> action
|
||||
}
|
||||
|
||||
def SetInteractionStop(attribute: EnvironmentTrait, action: RespondsToZoneEnvironment.Interaction): Unit = {
|
||||
interactWithEnvironmentStop += attribute -> action
|
||||
}
|
||||
|
||||
def doEnvironmentInteracting(obj: PlanetSideServerObject, body: PieceOfEnvironment, data: Option[OxygenStateTarget]): Unit = {
|
||||
val attribute = body.attribute
|
||||
if (interactWith.isEmpty || interactWith.get.attribute == attribute) {
|
||||
interactWith = Some(body)
|
||||
interactionTimer.cancel()
|
||||
interactWithEnvironmentStart.get(attribute) match {
|
||||
case Some(func) => func(obj, body, data)
|
||||
case None => ;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def stopEnvironmentInteracting(obj: PlanetSideServerObject, body: PieceOfEnvironment, data: Option[OxygenStateTarget]): Unit = {
|
||||
val attribute = body.attribute
|
||||
if (interactWith.nonEmpty && interactWith.get.attribute == attribute) {
|
||||
interactWith = None
|
||||
interactionTimer.cancel()
|
||||
interactWithEnvironmentStop.get(attribute) match {
|
||||
case Some(func) => func(obj, body, data)
|
||||
case _ => recoverFromEnvironmentInteracting()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the environment encounter fields and completely stop whatever is the current mechanic.
|
||||
* This does not perform messaging relay either with mounted occupants or with any other service.
|
||||
*/
|
||||
def recoverFromEnvironmentInteracting(): Unit = {
|
||||
interactionTimer.cancel()
|
||||
interactionTime = 0
|
||||
interactWith = None
|
||||
}
|
||||
}
|
||||
|
||||
object RespondsToZoneEnvironment {
|
||||
type Interaction = (PlanetSideServerObject, PieceOfEnvironment, Option[OxygenStateTarget]) => Unit
|
||||
|
||||
/**
|
||||
* Calculate the effect of being exposed to a watery environment beyond its critical region.
|
||||
* @param obj the target
|
||||
* @param condition the current environment progressive event of the target, e.g., already drowning
|
||||
* @param completionTime how long since the current environment progressive event started
|
||||
* @return three values:
|
||||
* whether any change in effect will occur,
|
||||
* for how long this new change if effect will occur after starting,
|
||||
* and what the starting progress value of this new effect looks like
|
||||
*/
|
||||
def drowningInWateryConditions(
|
||||
obj: PlanetSideServerObject,
|
||||
condition: Option[OxygenState],
|
||||
completionTime: Long
|
||||
): (Boolean, Long, Float) = {
|
||||
condition match {
|
||||
case None =>
|
||||
//start suffocation process
|
||||
(true, obj.Definition.UnderwaterLifespan(OxygenState.Suffocation), 100f)
|
||||
case Some(OxygenState.Recovery) =>
|
||||
//switching from recovery to suffocation
|
||||
val oldDuration: Long = obj.Definition.UnderwaterLifespan(OxygenState.Recovery)
|
||||
val newDuration: Long = obj.Definition.UnderwaterLifespan(OxygenState.Suffocation)
|
||||
val oldTimeRemaining: Long = completionTime - System.currentTimeMillis()
|
||||
val oldTimeRatio: Float = 1f - oldTimeRemaining / oldDuration.toFloat
|
||||
val percentage: Float = oldTimeRatio * 100
|
||||
val newDrownTime: Long = (newDuration * oldTimeRatio).toLong
|
||||
(true, newDrownTime, percentage)
|
||||
case Some(OxygenState.Suffocation) =>
|
||||
//interrupted while suffocating, calculate the progress and keep suffocating
|
||||
val oldDuration: Long = obj.Definition.UnderwaterLifespan(OxygenState.Suffocation)
|
||||
val oldTimeRemaining: Long = completionTime - System.currentTimeMillis()
|
||||
val percentage: Float = (oldTimeRemaining / oldDuration.toFloat) * 100f
|
||||
(false, oldTimeRemaining, percentage)
|
||||
case _ =>
|
||||
(false, 0L, 0f)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the effect of being removed from a watery environment beyond its critical region.
|
||||
* @param obj the target
|
||||
* @param condition the current environment progressive event of the target, e.g., already drowning
|
||||
* @param completionTime how long since the current environment progressive event started
|
||||
* @return three values:
|
||||
* whether any change in effect will occur,
|
||||
* for how long this new change if effect will occur after starting,
|
||||
* and what the starting progress value of this new effect looks like
|
||||
*/
|
||||
def recoveringFromWateryConditions(
|
||||
obj: PlanetSideServerObject,
|
||||
condition: Option[OxygenState],
|
||||
completionTime: Long
|
||||
): (Boolean, Long, Float) = {
|
||||
condition match {
|
||||
case Some(OxygenState.Suffocation) =>
|
||||
//switching from suffocation to recovery
|
||||
val oldDuration: Long = obj.Definition.UnderwaterLifespan(OxygenState.Suffocation)
|
||||
val newDuration: Long = obj.Definition.UnderwaterLifespan(OxygenState.Recovery)
|
||||
val oldTimeRemaining: Long = completionTime - System.currentTimeMillis()
|
||||
val oldTimeRatio: Float = oldTimeRemaining / oldDuration.toFloat
|
||||
val percentage: Float = oldTimeRatio * 100
|
||||
val recoveryTime: Long = newDuration - (newDuration * oldTimeRatio).toLong
|
||||
(true, recoveryTime, percentage)
|
||||
case Some(OxygenState.Recovery) =>
|
||||
//interrupted while recovering, calculate the progress and keep recovering
|
||||
val currTime = System.currentTimeMillis()
|
||||
val duration: Long = obj.Definition.UnderwaterLifespan(OxygenState.Recovery)
|
||||
val startTime: Long = completionTime - duration
|
||||
val timeRemaining: Long = completionTime - currTime
|
||||
val percentage: Float = ((currTime - startTime) / duration.toFloat) * 100f
|
||||
(false, timeRemaining, percentage)
|
||||
case _ =>
|
||||
(false, 0L, 100f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -13,6 +13,8 @@ object Painbox {
|
|||
final case class Tick()
|
||||
final case class Stop()
|
||||
|
||||
final case class EnvironmentalDamage(obj: Painbox, amount: Int)
|
||||
|
||||
def apply(tdef: PainboxDefinition): Painbox = {
|
||||
new Painbox(tdef)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,23 +8,26 @@ import net.psforever.objects.ce.TelepadLike
|
|||
import net.psforever.objects.equipment.{Equipment, EquipmentSlot, JammableMountedWeapons}
|
||||
import net.psforever.objects.guid.GUIDTask
|
||||
import net.psforever.objects.inventory.{GridInventory, InventoryItem}
|
||||
import net.psforever.objects.serverobject.CommonMessages
|
||||
import net.psforever.objects.serverobject.mount.{Mountable, MountableBehavior}
|
||||
import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject}
|
||||
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
|
||||
import net.psforever.objects.serverobject.containable.{Containable, ContainableBehavior}
|
||||
import net.psforever.objects.serverobject.damage.{AggravatedBehavior, DamageableVehicle}
|
||||
import net.psforever.objects.serverobject.deploy.Deployment.DeploymentObject
|
||||
import net.psforever.objects.serverobject.deploy.{Deployment, DeploymentBehavior}
|
||||
import net.psforever.objects.serverobject.environment._
|
||||
import net.psforever.objects.serverobject.hackable.GenericHackables
|
||||
import net.psforever.objects.serverobject.transfer.TransferBehavior
|
||||
import net.psforever.objects.serverobject.mount.{Mountable, MountableBehavior}
|
||||
import net.psforever.objects.serverobject.repair.RepairableVehicle
|
||||
import net.psforever.objects.serverobject.terminals.Terminal
|
||||
import net.psforever.objects.vital.interaction.DamageResult
|
||||
import net.psforever.objects.serverobject.transfer.TransferBehavior
|
||||
import net.psforever.objects.vital.interaction.{DamageInteraction, DamageResult}
|
||||
import net.psforever.objects.vital.VehicleShieldCharge
|
||||
import net.psforever.objects.zones.Zone
|
||||
import net.psforever.objects.vital.environment.EnvironmentReason
|
||||
import net.psforever.objects.vital.etc.SuicideReason
|
||||
import net.psforever.objects.zones._
|
||||
import net.psforever.packet.game._
|
||||
import net.psforever.packet.game.objectcreate.ObjectCreateMessageParent
|
||||
import net.psforever.types.{DriveState, ExoSuitType, PlanetSideGUID, Vector3}
|
||||
import net.psforever.types._
|
||||
import net.psforever.services.Service
|
||||
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||
import net.psforever.services.local.{LocalAction, LocalServiceMessage}
|
||||
|
|
@ -53,11 +56,11 @@ class VehicleControl(vehicle: Vehicle)
|
|||
with JammableMountedWeapons
|
||||
with ContainableBehavior
|
||||
with AntTransferBehavior
|
||||
with AggravatedBehavior {
|
||||
|
||||
with AggravatedBehavior
|
||||
with RespondsToZoneEnvironment {
|
||||
//make control actors belonging to utilities when making control actor belonging to vehicle
|
||||
vehicle.Utilities.foreach({ case (_, util) => util.Setup })
|
||||
|
||||
|
||||
def MountableObject = vehicle
|
||||
|
||||
def CargoObject = vehicle
|
||||
|
|
@ -76,20 +79,28 @@ class VehicleControl(vehicle: Vehicle)
|
|||
|
||||
def ChargeTransferObject = vehicle
|
||||
|
||||
if(vehicle.Definition == GlobalDefinitions.ant) {
|
||||
def InteractiveObject = vehicle
|
||||
SetInteraction(EnvironmentAttribute.Water, doInteractingWithWater)
|
||||
SetInteraction(EnvironmentAttribute.Lava, doInteractingWithLava)
|
||||
SetInteraction(EnvironmentAttribute.Death, doInteractingWithDeath)
|
||||
if (!vehicle.Definition.CanFly) { //can not recover from sinking disability
|
||||
SetInteractionStop(EnvironmentAttribute.Water, stopInteractingWithWater)
|
||||
}
|
||||
|
||||
if (vehicle.Definition == GlobalDefinitions.ant) {
|
||||
findChargeTargetFunc = Vehicles.FindANTChargingSource
|
||||
findDischargeTargetFunc = Vehicles.FindANTDischargingTarget
|
||||
}
|
||||
|
||||
/** cheap flag for whether the vehicle is decaying */
|
||||
var decaying: Boolean = false
|
||||
|
||||
var decaying : Boolean = false
|
||||
/** primary vehicle decay timer */
|
||||
var decayTimer: Cancellable = Default.Cancellable
|
||||
var decayTimer : Cancellable = Default.Cancellable
|
||||
/** becoming waterlogged, or drying out? */
|
||||
var submergedCondition : Option[OxygenState] = None
|
||||
|
||||
def receive: Receive = Enabled
|
||||
def receive : Receive = Enabled
|
||||
|
||||
override def postStop(): Unit = {
|
||||
override def postStop() : Unit = {
|
||||
super.postStop()
|
||||
damageableVehiclePostStop()
|
||||
decaying = false
|
||||
|
|
@ -98,9 +109,10 @@ class VehicleControl(vehicle: Vehicle)
|
|||
context.stop(util().Actor)
|
||||
util().Actor = Default.Actor
|
||||
}
|
||||
recoverFromEnvironmentInteracting()
|
||||
}
|
||||
|
||||
def Enabled: Receive =
|
||||
def Enabled : Receive =
|
||||
checkBehavior
|
||||
.orElse(deployBehavior)
|
||||
.orElse(cargoBehavior)
|
||||
|
|
@ -109,6 +121,7 @@ class VehicleControl(vehicle: Vehicle)
|
|||
.orElse(canBeRepairedByNanoDispenser)
|
||||
.orElse(containerBehavior)
|
||||
.orElse(antBehavior)
|
||||
.orElse(environmentBehavior)
|
||||
.orElse {
|
||||
case Vehicle.Ownership(None) =>
|
||||
LoseOwnership()
|
||||
|
|
@ -116,7 +129,7 @@ class VehicleControl(vehicle: Vehicle)
|
|||
case Vehicle.Ownership(Some(player)) =>
|
||||
GainOwnership(player)
|
||||
|
||||
case msg @ Mountable.TryMount(player, seat_num) =>
|
||||
case msg@Mountable.TryMount(player, seat_num) =>
|
||||
tryMountBehavior.apply(msg)
|
||||
val obj = MountableObject
|
||||
//check that the player has actually been sat in the expected seat
|
||||
|
|
@ -125,39 +138,28 @@ class VehicleControl(vehicle: Vehicle)
|
|||
if (seat_num == 0 && !obj.OwnerName.contains(player.Name)) {
|
||||
//whatever vehicle was previously owned
|
||||
vehicle.Zone.GUID(player.avatar.vehicle) match {
|
||||
case Some(v: Vehicle) =>
|
||||
case Some(v : Vehicle) =>
|
||||
v.Actor ! Vehicle.Ownership(None)
|
||||
case _ =>
|
||||
player.avatar.vehicle = None
|
||||
}
|
||||
LoseOwnership() //lose our current ownership
|
||||
LoseOwnership() //lose our current ownership
|
||||
GainOwnership(player) //gain new ownership
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
decaying = false
|
||||
decayTimer.cancel()
|
||||
}
|
||||
//
|
||||
updateZoneInteractionProgressUI(player)
|
||||
}
|
||||
|
||||
case msg: Mountable.TryDismount =>
|
||||
case msg : Mountable.TryDismount =>
|
||||
dismountBehavior.apply(msg)
|
||||
val obj = MountableObject
|
||||
|
||||
// Reset velocity to zero when driver dismounts, to allow jacking/repair if vehicle was moving slightly before dismount
|
||||
if (!obj.Seats(0).isOccupied) {
|
||||
obj.Velocity = Some(Vector3.Zero)
|
||||
}
|
||||
//are we already decaying? are we unowned? is no one seated anywhere?
|
||||
if (!decaying && obj.Owner.isEmpty && obj.Seats.values.forall(!_.isOccupied)) {
|
||||
decaying = true
|
||||
decayTimer = context.system.scheduler.scheduleOnce(
|
||||
MountableObject.Definition.DeconstructionTime.getOrElse(5 minutes),
|
||||
self,
|
||||
VehicleControl.PrepareForDeletion()
|
||||
)
|
||||
}
|
||||
dismountCleanup()
|
||||
|
||||
case Vehicle.ChargeShields(amount) =>
|
||||
val now: Long = System.currentTimeMillis()
|
||||
val now : Long = System.currentTimeMillis()
|
||||
//make certain vehicle doesn't charge shields too quickly
|
||||
if (
|
||||
vehicle.Health > 0 && vehicle.Shields < vehicle.MaxShields &&
|
||||
|
|
@ -171,17 +173,20 @@ class VehicleControl(vehicle: Vehicle)
|
|||
)
|
||||
}
|
||||
|
||||
case Vehicle.UpdateZoneInteractionProgressUI(player) =>
|
||||
updateZoneInteractionProgressUI(player)
|
||||
|
||||
case FactionAffinity.ConvertFactionAffinity(faction) =>
|
||||
val originalAffinity = vehicle.Faction
|
||||
if (originalAffinity != (vehicle.Faction = faction)) {
|
||||
vehicle.Utilities.foreach({
|
||||
case (_: Int, util: Utility) => util().Actor forward FactionAffinity.ConfirmFactionAffinity()
|
||||
case (_ : Int, util : Utility) => util().Actor forward FactionAffinity.ConfirmFactionAffinity()
|
||||
})
|
||||
}
|
||||
sender() ! FactionAffinity.AssertFactionAffinity(vehicle, faction)
|
||||
|
||||
case CommonMessages.Use(player, Some(item: SimpleItem))
|
||||
if item.Definition == GlobalDefinitions.remote_electronics_kit =>
|
||||
case CommonMessages.Use(player, Some(item : SimpleItem))
|
||||
if item.Definition == GlobalDefinitions.remote_electronics_kit =>
|
||||
//TODO setup certifications check
|
||||
if (vehicle.Faction != player.Faction) {
|
||||
sender() ! CommonMessages.Progress(
|
||||
|
|
@ -205,35 +210,39 @@ class VehicleControl(vehicle: Vehicle)
|
|||
//vehicles are the same type
|
||||
//TODO want to completely swap weapons, but holster icon vanishes temporarily after swap
|
||||
//TODO BFR arms must be swapped properly
|
||||
// //remove old weapons
|
||||
// val oldWeapons = vehicle.Weapons.values.collect { case slot if slot.Equipment.nonEmpty =>
|
||||
// val obj = slot.Equipment.get
|
||||
// slot.Equipment = None
|
||||
// (obj, obj.GUID)
|
||||
// }.toList
|
||||
// (oldWeapons, weapons, afterInventory)
|
||||
// //remove old weapons
|
||||
// val oldWeapons = vehicle.Weapons.values.collect { case slot if slot.Equipment.nonEmpty =>
|
||||
// val obj = slot.Equipment.get
|
||||
// slot.Equipment = None
|
||||
// (obj, obj.GUID)
|
||||
// }.toList
|
||||
// (oldWeapons, weapons, afterInventory)
|
||||
//TODO for now, just refill ammo; assume weapons stay the same
|
||||
vehicle.Weapons
|
||||
.collect { case (_, slot: EquipmentSlot) if slot.Equipment.nonEmpty => slot.Equipment.get }
|
||||
.collect { case (_, slot : EquipmentSlot) if slot.Equipment.nonEmpty => slot.Equipment.get }
|
||||
.collect {
|
||||
case weapon: Tool =>
|
||||
case weapon : Tool =>
|
||||
weapon.AmmoSlots.foreach { ammo => ammo.Box.Capacity = ammo.Box.Definition.Capacity }
|
||||
}
|
||||
(Nil, Nil, afterInventory)
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
//vehicle loadout is not for this vehicle
|
||||
//do not transfer over weapon ammo
|
||||
if (
|
||||
vehicle.Definition.TrunkSize == definition.TrunkSize && vehicle.Definition.TrunkOffset == definition.TrunkOffset
|
||||
) {
|
||||
(Nil, Nil, afterInventory) //trunk is the same dimensions, however
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
//accommodate as much of inventory as possible
|
||||
val (stow, _) = GridInventory.recoverInventory(afterInventory, vehicle.Inventory)
|
||||
(Nil, Nil, stow)
|
||||
}
|
||||
}
|
||||
finalInventory.foreach { _.obj.Faction = vehicle.Faction }
|
||||
finalInventory.foreach {
|
||||
_.obj.Faction = vehicle.Faction
|
||||
}
|
||||
player.Zone.VehicleEvents ! VehicleServiceMessage(
|
||||
player.Zone.id,
|
||||
VehicleAction.ChangeLoadout(vehicle.GUID, oldWeapons, newWeapons, oldInventory, finalInventory)
|
||||
|
|
@ -246,6 +255,40 @@ class VehicleControl(vehicle: Vehicle)
|
|||
case _ => ;
|
||||
}
|
||||
|
||||
case VehicleControl.Disable() =>
|
||||
PrepareForDisabled(kickPassengers = false)
|
||||
context.become(Disabled)
|
||||
|
||||
case Vehicle.Deconstruct(time) =>
|
||||
time match {
|
||||
case Some(delay) =>
|
||||
decaying = true
|
||||
decayTimer.cancel()
|
||||
decayTimer = context.system.scheduler.scheduleOnce(delay, self, VehicleControl.PrepareForDeletion())
|
||||
case _ =>
|
||||
PrepareForDisabled(kickPassengers = true)
|
||||
PrepareForDeletion()
|
||||
context.become(ReadyToDelete)
|
||||
}
|
||||
|
||||
case VehicleControl.PrepareForDeletion() =>
|
||||
PrepareForDisabled(kickPassengers = true)
|
||||
PrepareForDeletion()
|
||||
context.become(ReadyToDelete)
|
||||
|
||||
case _ => ;
|
||||
}
|
||||
|
||||
def Disabled : Receive =
|
||||
checkBehavior
|
||||
.orElse {
|
||||
case msg : Deployment.TryUndeploy =>
|
||||
deployBehavior.apply(msg)
|
||||
|
||||
case msg : Mountable.TryDismount =>
|
||||
dismountBehavior.apply(msg)
|
||||
dismountCleanup()
|
||||
|
||||
case Vehicle.Deconstruct(time) =>
|
||||
time match {
|
||||
case Some(delay) =>
|
||||
|
|
@ -254,51 +297,84 @@ class VehicleControl(vehicle: Vehicle)
|
|||
decayTimer = context.system.scheduler.scheduleOnce(delay, self, VehicleControl.PrepareForDeletion())
|
||||
case _ =>
|
||||
PrepareForDeletion()
|
||||
context.become(ReadyToDelete)
|
||||
}
|
||||
|
||||
case VehicleControl.PrepareForDeletion() =>
|
||||
PrepareForDeletion()
|
||||
context.become(ReadyToDelete)
|
||||
|
||||
case _ => ;
|
||||
case _ =>
|
||||
}
|
||||
|
||||
val tryMountBehavior: Receive = {
|
||||
def ReadyToDelete : Receive =
|
||||
checkBehavior
|
||||
.orElse {
|
||||
case msg : Deployment.TryUndeploy =>
|
||||
deployBehavior.apply(msg)
|
||||
|
||||
case VehicleControl.Deletion() =>
|
||||
val zone = vehicle.Zone
|
||||
zone.VehicleEvents ! VehicleServiceMessage(
|
||||
zone.id,
|
||||
VehicleAction.UnloadVehicle(Service.defaultPlayerGUID, vehicle, vehicle.GUID)
|
||||
)
|
||||
zone.Transport ! Zone.Vehicle.Despawn(vehicle)
|
||||
|
||||
case _ =>
|
||||
}
|
||||
|
||||
val tryMountBehavior : Receive = {
|
||||
case msg @ Mountable.TryMount(user, seat_num) =>
|
||||
val exosuit = user.ExoSuit
|
||||
val exosuit = user.ExoSuit
|
||||
val restriction = vehicle.Seats(seat_num).ArmorRestriction
|
||||
val seatGroup = vehicle.SeatPermissionGroup(seat_num).getOrElse(AccessPermissionGroup.Passenger)
|
||||
val permission = vehicle.PermissionGroup(seatGroup.id).getOrElse(VehicleLockState.Empire)
|
||||
val seatGroup = vehicle.SeatPermissionGroup(seat_num).getOrElse(AccessPermissionGroup.Passenger)
|
||||
val permission = vehicle.PermissionGroup(seatGroup.id).getOrElse(VehicleLockState.Empire)
|
||||
if (
|
||||
(if (seatGroup == AccessPermissionGroup.Driver) {
|
||||
vehicle.Owner.contains(user.GUID) || vehicle.Owner.isEmpty || permission != VehicleLockState.Locked
|
||||
} else {
|
||||
permission != VehicleLockState.Locked
|
||||
}) &&
|
||||
vehicle.Owner.contains(user.GUID) || vehicle.Owner.isEmpty || permission != VehicleLockState.Locked
|
||||
}
|
||||
else {
|
||||
permission != VehicleLockState.Locked
|
||||
}) &&
|
||||
(exosuit match {
|
||||
case ExoSuitType.MAX => restriction == SeatArmorRestriction.MaxOnly
|
||||
case ExoSuitType.MAX => restriction == SeatArmorRestriction.MaxOnly
|
||||
case ExoSuitType.Reinforced => restriction == SeatArmorRestriction.NoMax
|
||||
case _ => restriction != SeatArmorRestriction.MaxOnly
|
||||
case _ => restriction != SeatArmorRestriction.MaxOnly
|
||||
})
|
||||
) {
|
||||
mountBehavior.apply(msg)
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
sender() ! Mountable.MountMessages(user, Mountable.CanNotMount(vehicle, seat_num))
|
||||
}
|
||||
}
|
||||
|
||||
def PrepareForDeletion(): Unit = {
|
||||
decaying = false
|
||||
val guid = vehicle.GUID
|
||||
val zone = vehicle.Zone
|
||||
def dismountCleanup(): Unit = {
|
||||
val obj = MountableObject
|
||||
// Reset velocity to zero when driver dismounts, to allow jacking/repair if vehicle was moving slightly before dismount
|
||||
if (!obj.Seats(0).isOccupied) {
|
||||
obj.Velocity = Some(Vector3.Zero)
|
||||
}
|
||||
//are we already decaying? are we unowned? is no one seated anywhere?
|
||||
if (!decaying && obj.Owner.isEmpty && obj.Seats.values.forall(!_.isOccupied)) {
|
||||
decaying = true
|
||||
decayTimer = context.system.scheduler.scheduleOnce(
|
||||
MountableObject.Definition.DeconstructionTime.getOrElse(5 minutes),
|
||||
self,
|
||||
VehicleControl.PrepareForDeletion()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
def PrepareForDisabled(kickPassengers: Boolean) : Unit = {
|
||||
val guid = vehicle.GUID
|
||||
val zone = vehicle.Zone
|
||||
val zoneId = zone.id
|
||||
val events = zone.VehicleEvents
|
||||
//miscellaneous changes
|
||||
recoverFromEnvironmentInteracting()
|
||||
Vehicles.BeforeUnloadVehicle(vehicle, zone)
|
||||
//become disabled
|
||||
context.become(Disabled)
|
||||
//cancel jammed behavior
|
||||
CancelJammeredSound(vehicle)
|
||||
CancelJammeredStatus(vehicle)
|
||||
//escape being someone else's cargo
|
||||
vehicle.MountedIn match {
|
||||
case Some(_) =>
|
||||
|
|
@ -311,33 +387,48 @@ class VehicleControl(vehicle: Vehicle)
|
|||
)
|
||||
case _ => ;
|
||||
}
|
||||
//kick all passengers
|
||||
vehicle.Seats.values.foreach(seat => {
|
||||
seat.Occupant match {
|
||||
case Some(player) =>
|
||||
seat.Occupant = None
|
||||
player.VehicleSeated = None
|
||||
if (player.HasGUID) {
|
||||
events ! VehicleServiceMessage(zoneId, VehicleAction.KickPassenger(player.GUID, 4, false, guid))
|
||||
}
|
||||
case None => ;
|
||||
}
|
||||
//abandon all cargo
|
||||
vehicle.CargoHolds.values
|
||||
.collect {
|
||||
case hold if hold.isOccupied =>
|
||||
val cargo = hold.Occupant.get
|
||||
CargoBehavior.HandleVehicleCargoDismount(
|
||||
cargo.GUID,
|
||||
cargo,
|
||||
guid,
|
||||
vehicle,
|
||||
bailed = false,
|
||||
requestedByPassenger = false,
|
||||
kicked = false
|
||||
)
|
||||
if (!vehicle.Flying || kickPassengers) {
|
||||
//kick all passengers (either not flying, or being explicitly instructed)
|
||||
vehicle.Seats.values.foreach { seat =>
|
||||
seat.Occupant match {
|
||||
case Some(player) =>
|
||||
seat.Occupant = None
|
||||
player.VehicleSeated = None
|
||||
if (player.HasGUID) {
|
||||
events ! VehicleServiceMessage(zoneId, VehicleAction.KickPassenger(player.GUID, 4, false, guid))
|
||||
}
|
||||
case None => ;
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
//abandon all cargo
|
||||
vehicle.CargoHolds.values
|
||||
.collect {
|
||||
case hold if hold.isOccupied =>
|
||||
val cargo = hold.Occupant.get
|
||||
CargoBehavior.HandleVehicleCargoDismount(
|
||||
cargo.GUID,
|
||||
cargo,
|
||||
guid,
|
||||
vehicle,
|
||||
bailed = false,
|
||||
requestedByPassenger = false,
|
||||
kicked = false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
def PrepareForDeletion() : Unit = {
|
||||
decaying = false
|
||||
val guid = vehicle.GUID
|
||||
val zone = vehicle.Zone
|
||||
val zoneId = zone.id
|
||||
val events = zone.VehicleEvents
|
||||
//miscellaneous changes
|
||||
Vehicles.BeforeUnloadVehicle(vehicle, zone)
|
||||
//cancel jammed behavior
|
||||
CancelJammeredSound(vehicle)
|
||||
CancelJammeredStatus(vehicle)
|
||||
//unregister
|
||||
zone.tasks ! GUIDTask.UnregisterVehicle(vehicle)(zone.GUID)
|
||||
//banished to the shadow realm
|
||||
|
|
@ -346,22 +437,6 @@ class VehicleControl(vehicle: Vehicle)
|
|||
decayTimer = context.system.scheduler.scheduleOnce(5 seconds, self, VehicleControl.Deletion())
|
||||
}
|
||||
|
||||
def Disabled: Receive =
|
||||
checkBehavior
|
||||
.orElse {
|
||||
case msg: Deployment.TryUndeploy =>
|
||||
deployBehavior.apply(msg)
|
||||
|
||||
case VehicleControl.Deletion() =>
|
||||
val zone = vehicle.Zone
|
||||
zone.VehicleEvents ! VehicleServiceMessage(
|
||||
zone.id,
|
||||
VehicleAction.UnloadVehicle(Service.defaultPlayerGUID, vehicle, vehicle.GUID)
|
||||
)
|
||||
zone.Transport ! Zone.Vehicle.Despawn(vehicle)
|
||||
case _ =>
|
||||
}
|
||||
|
||||
override def TryJammerEffectActivate(target: Any, cause: DamageResult): Unit = {
|
||||
if (vehicle.MountedIn.isEmpty) {
|
||||
super.TryJammerEffectActivate(target, cause)
|
||||
|
|
@ -582,6 +657,197 @@ class VehicleControl(vehicle: Vehicle)
|
|||
}
|
||||
out
|
||||
}
|
||||
|
||||
/**
|
||||
* Water causes vehicles to become disabled if they dive off too far, too deep.
|
||||
* Flying vehicles do not display progress towards being waterlogged. They just disable outright.
|
||||
* @param obj the target
|
||||
* @param body the environment
|
||||
* @param data additional interaction information, if applicable
|
||||
*/
|
||||
def doInteractingWithWater(obj: PlanetSideServerObject, body: PieceOfEnvironment, data: Option[OxygenStateTarget]): Unit = {
|
||||
val (effect: Boolean, time: Long, percentage: Float) = {
|
||||
val (a, b, c) = RespondsToZoneEnvironment.drowningInWateryConditions(obj, submergedCondition, interactionTime)
|
||||
if (a && vehicle.Definition.CanFly) {
|
||||
(true, 0L, 0f) //no progress bar
|
||||
} else {
|
||||
(a, b, c)
|
||||
}
|
||||
}
|
||||
if (effect) {
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
submergedCondition = Some(OxygenState.Suffocation)
|
||||
interactionTime = System.currentTimeMillis() + time
|
||||
interactionTimer = context.system.scheduler.scheduleOnce(delay = time milliseconds, self, VehicleControl.Disable())
|
||||
doInteractingWithWaterToTargets(
|
||||
percentage,
|
||||
body,
|
||||
vehicle.Seats.values
|
||||
.collect { case seat if seat.isOccupied => seat.Occupant.get }
|
||||
.filter { p => p.isAlive && (p.Zone eq vehicle.Zone) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell the given targets that
|
||||
* water causes vehicles to become disabled if they dive off too far, too deep.
|
||||
* @see `InteractWithEnvironment`
|
||||
* @see `OxygenState`
|
||||
* @see `OxygenStateTarget`
|
||||
* @param percentage the progress bar completion state
|
||||
* @param body the environment
|
||||
* @param targets recipients of the information
|
||||
*/
|
||||
def doInteractingWithWaterToTargets(
|
||||
percentage: Float,
|
||||
body: PieceOfEnvironment,
|
||||
targets: Iterable[PlanetSideServerObject]
|
||||
): Unit = {
|
||||
val vtarget = Some(OxygenStateTarget(vehicle.GUID, OxygenState.Suffocation, percentage))
|
||||
targets.foreach { target =>
|
||||
target.Actor ! InteractWithEnvironment(target, body, vtarget)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lava causes vehicles to take (considerable) damage until they are inevitably destroyed.
|
||||
* @param obj the target
|
||||
* @param body the environment
|
||||
* @param data additional interaction information, if applicable
|
||||
*/
|
||||
def doInteractingWithLava(obj: PlanetSideServerObject, body: PieceOfEnvironment, data: Option[OxygenStateTarget]): Unit = {
|
||||
val vehicle = DamageableObject
|
||||
if (!obj.Destroyed) {
|
||||
PerformDamage(
|
||||
vehicle,
|
||||
DamageInteraction(
|
||||
VehicleSource(vehicle),
|
||||
EnvironmentReason(body, vehicle),
|
||||
vehicle.Position
|
||||
).calculate()
|
||||
)
|
||||
//keep doing damage
|
||||
if (vehicle.Health > 0) {
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
interactionTimer = context.system.scheduler.scheduleOnce(delay = 250 milliseconds, self, InteractWithEnvironment(obj, body, None))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Death causes vehicles to be destroyed outright.
|
||||
* It's not even considered as environmental damage anymore.
|
||||
* @param obj the target
|
||||
* @param body the environment
|
||||
* @param data additional interaction information, if applicable
|
||||
*/
|
||||
def doInteractingWithDeath(obj: PlanetSideServerObject, body: PieceOfEnvironment, data: Option[OxygenStateTarget]): Unit = {
|
||||
if (!obj.Destroyed) {
|
||||
PerformDamage(
|
||||
vehicle,
|
||||
DamageInteraction(
|
||||
VehicleSource(vehicle),
|
||||
SuicideReason(),
|
||||
vehicle.Position
|
||||
).calculate()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When out of water, the vehicle no longer risks becoming disabled.
|
||||
* It does have to endure a recovery period to get back to full dehydration
|
||||
* Flying vehicles are exempt from this process due to the abrupt disability they experience.
|
||||
* @param obj the target
|
||||
* @param body the environment
|
||||
* @param data additional interaction information, if applicable
|
||||
*/
|
||||
def stopInteractingWithWater(obj: PlanetSideServerObject, body: PieceOfEnvironment, data: Option[OxygenStateTarget]): Unit = {
|
||||
val (effect: Boolean, time: Long, percentage: Float) =
|
||||
RespondsToZoneEnvironment.recoveringFromWateryConditions(obj, submergedCondition, interactionTime)
|
||||
if (effect) {
|
||||
recoverFromEnvironmentInteracting()
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
submergedCondition = Some(OxygenState.Recovery)
|
||||
interactionTime = System.currentTimeMillis() + time
|
||||
interactionTimer = context.system.scheduler.scheduleOnce(delay = time milliseconds, self, RecoveredFromEnvironmentInteraction())
|
||||
stopInteractingWithWaterToTargets(
|
||||
percentage,
|
||||
body,
|
||||
vehicle.Seats.values
|
||||
.collect { case seat if seat.isOccupied => seat.Occupant.get }
|
||||
.filter { p => p.isAlive && (p.Zone eq vehicle.Zone) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell the given targets that,
|
||||
* when out of water, the vehicle no longer risks becoming disabled.
|
||||
* @see `EscapeFromEnvironment`
|
||||
* @see `OxygenState`
|
||||
* @see `OxygenStateTarget`
|
||||
* @param percentage the progress bar completion state
|
||||
* @param body the environment
|
||||
* @param targets recipients of the information
|
||||
*/
|
||||
def stopInteractingWithWaterToTargets(
|
||||
percentage: Float,
|
||||
body: PieceOfEnvironment,
|
||||
targets: Iterable[PlanetSideServerObject]
|
||||
): Unit = {
|
||||
val vtarget = Some(OxygenStateTarget(vehicle.GUID, OxygenState.Recovery, percentage))
|
||||
targets.foreach { target =>
|
||||
target.Actor ! EscapeFromEnvironment(target, body, vtarget)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the environment encounter fields and completely stop whatever is the current mechanic.
|
||||
* This does not perform messaging relay either with mounted occupants or with any other service.
|
||||
*/
|
||||
override def recoverFromEnvironmentInteracting(): Unit = {
|
||||
super.recoverFromEnvironmentInteracting()
|
||||
submergedCondition = None
|
||||
}
|
||||
|
||||
/**
|
||||
* Without altering the state or progress of a zone interaction related to water,
|
||||
* update the visual progress element (progress bar) that is visible to the recipient's client.
|
||||
* @param player the recipient of this ui update
|
||||
*/
|
||||
def updateZoneInteractionProgressUI(player : Player) : Unit = {
|
||||
submergedCondition match {
|
||||
case Some(OxygenState.Suffocation) =>
|
||||
interactWith match {
|
||||
case Some(body) =>
|
||||
val percentage: Float = {
|
||||
val (a, _, c) = RespondsToZoneEnvironment.drowningInWateryConditions(vehicle, submergedCondition, interactionTime)
|
||||
if (a && vehicle.Definition.CanFly) {
|
||||
0f //no progress bar
|
||||
} else {
|
||||
c
|
||||
}
|
||||
}
|
||||
doInteractingWithWaterToTargets(percentage, body, List(player))
|
||||
case _ =>
|
||||
recoverFromEnvironmentInteracting()
|
||||
}
|
||||
case Some(OxygenState.Recovery) =>
|
||||
vehicle.Zone.map.environment.find { _.attribute == EnvironmentAttribute.Water } match {
|
||||
case Some(body) => //any body of water will do ...
|
||||
stopInteractingWithWaterToTargets(
|
||||
RespondsToZoneEnvironment.recoveringFromWateryConditions(vehicle, submergedCondition, interactionTime)._3,
|
||||
body,
|
||||
List(player)
|
||||
)
|
||||
case _ =>
|
||||
recoverFromEnvironmentInteracting()
|
||||
}
|
||||
case None => ;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object VehicleControl {
|
||||
|
|
@ -590,6 +856,8 @@ object VehicleControl {
|
|||
|
||||
private case class PrepareForDeletion()
|
||||
|
||||
private case class Disable()
|
||||
|
||||
private case class Deletion()
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,66 +1,80 @@
|
|||
// Copyright (c) 2020 PSForever
|
||||
package net.psforever.objects.vital
|
||||
|
||||
import net.psforever.objects.ballistics._
|
||||
import net.psforever.objects.definition.{EquipmentDefinition, KitDefinition, ObjectDefinition}
|
||||
import net.psforever.objects.ballistics.{PlayerSource, VehicleSource}
|
||||
import net.psforever.objects.definition.{EquipmentDefinition, KitDefinition}
|
||||
import net.psforever.objects.serverobject.terminals.TerminalDefinition
|
||||
import net.psforever.objects.vital.environment.EnvironmentReason
|
||||
import net.psforever.objects.vital.etc.{ExplodingEntityReason, PainboxReason}
|
||||
import net.psforever.objects.vital.interaction.DamageResult
|
||||
import net.psforever.objects.vital.projectile.ProjectileReason
|
||||
import net.psforever.types.{ExoSuitType, ImplantType}
|
||||
|
||||
abstract class VitalsActivity(target: SourceEntry) {
|
||||
def Target: SourceEntry = target
|
||||
val t: Long = System.currentTimeMillis() //???
|
||||
|
||||
def time: Long = t
|
||||
trait VitalsActivity {
|
||||
def time: Long
|
||||
}
|
||||
|
||||
abstract class HealingActivity(target: SourceEntry) extends VitalsActivity(target)
|
||||
trait HealingActivity extends VitalsActivity {
|
||||
def time: Long = System.currentTimeMillis()
|
||||
}
|
||||
|
||||
abstract class DamagingActivity(target: SourceEntry) extends VitalsActivity(target)
|
||||
trait DamagingActivity extends VitalsActivity {
|
||||
def time: Long = data.interaction.hitTime
|
||||
def data: DamageResult
|
||||
}
|
||||
|
||||
final case class HealFromKit(target: PlayerSource, amount: Int, kit_def: KitDefinition) extends HealingActivity(target)
|
||||
final case class HealFromKit(kit_def: KitDefinition, amount: Int)
|
||||
extends HealingActivity
|
||||
|
||||
final case class HealFromEquipment(
|
||||
target: PlayerSource,
|
||||
user: PlayerSource,
|
||||
amount: Int,
|
||||
equipment_def: EquipmentDefinition
|
||||
) extends HealingActivity(target)
|
||||
equipment_def: EquipmentDefinition,
|
||||
amount: Int
|
||||
) extends HealingActivity
|
||||
|
||||
final case class HealFromTerm(target: PlayerSource, health: Int, armor: Int, term_def: TerminalDefinition)
|
||||
extends HealingActivity(target)
|
||||
final case class HealFromTerm(term_def: TerminalDefinition, health: Int, armor: Int)
|
||||
extends HealingActivity
|
||||
|
||||
final case class HealFromImplant(target: PlayerSource, amount: Int, implant: ImplantType)
|
||||
extends HealingActivity(target)
|
||||
final case class HealFromImplant(implant: ImplantType, health: Int)
|
||||
extends HealingActivity
|
||||
|
||||
final case class HealFromExoSuitChange(target: PlayerSource, exosuit: ExoSuitType.Value) extends HealingActivity(target)
|
||||
final case class HealFromExoSuitChange(exosuit: ExoSuitType.Value)
|
||||
extends HealingActivity
|
||||
|
||||
final case class RepairFromKit(target: PlayerSource, amount: Int, kit_def: KitDefinition)
|
||||
extends HealingActivity(target)
|
||||
final case class RepairFromKit(kit_def: KitDefinition, amount: Int)
|
||||
extends HealingActivity()
|
||||
|
||||
final case class RepairFromEquipment(
|
||||
target: PlayerSource,
|
||||
user: PlayerSource,
|
||||
amount: Int,
|
||||
equipment_def: EquipmentDefinition
|
||||
) extends HealingActivity(target)
|
||||
equipment_def: EquipmentDefinition,
|
||||
amount: Int
|
||||
) extends HealingActivity
|
||||
|
||||
final case class RepairFromTerm(target: VehicleSource, amount: Int, term_def: TerminalDefinition)
|
||||
extends HealingActivity(target)
|
||||
final case class RepairFromTerm(term_def: TerminalDefinition, amount: Int)
|
||||
extends HealingActivity
|
||||
|
||||
final case class VehicleShieldCharge(target: VehicleSource, amount: Int) extends HealingActivity(target) //TODO facility
|
||||
final case class VehicleShieldCharge(amount: Int)
|
||||
extends HealingActivity //TODO facility
|
||||
|
||||
final case class DamageFromProjectile(data: DamageResult) extends DamagingActivity(data.targetBefore)
|
||||
final case class DamageFrom(data: DamageResult)
|
||||
extends DamagingActivity
|
||||
|
||||
final case class DamageFromPainbox(data: DamageResult) extends DamagingActivity(data.targetBefore)
|
||||
final case class DamageFromProjectile(data: DamageResult)
|
||||
extends DamagingActivity
|
||||
|
||||
final case class PlayerSuicide(target: PlayerSource) extends DamagingActivity(target)
|
||||
final case class DamageFromPainbox(data: DamageResult)
|
||||
extends DamagingActivity
|
||||
|
||||
final case class DamageFromExplosion(target: PlayerSource, cause: ObjectDefinition) extends DamagingActivity(target)
|
||||
final case class DamageFromEnvironment(data: DamageResult)
|
||||
extends DamagingActivity
|
||||
|
||||
final case class DamageFromExplodingEntity(data: DamageResult) extends DamagingActivity(data.targetBefore)
|
||||
final case class PlayerSuicide()
|
||||
extends DamagingActivity {
|
||||
def data: DamageResult = null //TODO do something
|
||||
}
|
||||
|
||||
final case class DamageFromExplodingEntity(data: DamageResult)
|
||||
extends DamagingActivity
|
||||
|
||||
/**
|
||||
* A vital object can be hurt or damaged or healed or repaired (HDHR).
|
||||
|
|
@ -114,7 +128,13 @@ trait VitalsHistory {
|
|||
lastDamage = Some(result)
|
||||
case _: PainboxReason =>
|
||||
vitalsHistory = DamageFromPainbox(result) +: vitalsHistory
|
||||
case _: EnvironmentReason =>
|
||||
vitalsHistory = DamageFromEnvironment(result) +: vitalsHistory
|
||||
case _ => ;
|
||||
vitalsHistory = DamageFrom(result) +: vitalsHistory
|
||||
if(result.adversarial.nonEmpty) {
|
||||
lastDamage = Some(result)
|
||||
}
|
||||
}
|
||||
vitalsHistory
|
||||
}
|
||||
|
|
@ -140,3 +160,59 @@ trait VitalsHistory {
|
|||
out
|
||||
}
|
||||
}
|
||||
|
||||
//deprecated overrides
|
||||
object HealFromKit {
|
||||
def apply(Target: PlayerSource, amount: Int, kit_def: KitDefinition): HealFromKit =
|
||||
HealFromKit(kit_def, amount)
|
||||
}
|
||||
|
||||
object HealFromEquipment {
|
||||
def apply(
|
||||
Target: PlayerSource,
|
||||
user: PlayerSource,
|
||||
amount: Int,
|
||||
equipment_def: EquipmentDefinition
|
||||
): HealFromEquipment =
|
||||
HealFromEquipment(user, equipment_def, amount)
|
||||
}
|
||||
|
||||
object HealFromTerm {
|
||||
def apply(Target: PlayerSource, health: Int, armor: Int, term_def: TerminalDefinition): HealFromTerm =
|
||||
HealFromTerm(term_def, health, armor)
|
||||
}
|
||||
|
||||
object HealFromImplant {
|
||||
def apply(Target: PlayerSource, amount: Int, implant: ImplantType): HealFromImplant =
|
||||
HealFromImplant(implant, amount)
|
||||
}
|
||||
|
||||
object HealFromExoSuitChange {
|
||||
def apply(Target: PlayerSource, exosuit: ExoSuitType.Value): HealFromExoSuitChange =
|
||||
HealFromExoSuitChange(exosuit)
|
||||
}
|
||||
|
||||
object RepairFromKit {
|
||||
def apply(Target: PlayerSource, amount: Int, kit_def: KitDefinition): RepairFromKit =
|
||||
RepairFromKit(kit_def, amount)
|
||||
}
|
||||
|
||||
object RepairFromEquipment {
|
||||
def apply(
|
||||
Target: PlayerSource,
|
||||
user: PlayerSource,
|
||||
amount: Int,
|
||||
equipment_def: EquipmentDefinition
|
||||
) : RepairFromEquipment =
|
||||
RepairFromEquipment(user, equipment_def, amount)
|
||||
}
|
||||
|
||||
object RepairFromTerm {
|
||||
def apply(Target: VehicleSource, amount: Int, term_def: TerminalDefinition): RepairFromTerm =
|
||||
RepairFromTerm(term_def, amount)
|
||||
}
|
||||
|
||||
object VehicleShieldCharge {
|
||||
def apply(Target: VehicleSource, amount: Int): VehicleShieldCharge =
|
||||
VehicleShieldCharge(amount)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
// Copyright (c) 2020 PSForever
|
||||
package net.psforever.objects.vital.environment
|
||||
|
||||
import net.psforever.objects.ballistics.PlayerSource
|
||||
import net.psforever.objects.vital.interaction.DamageInteraction
|
||||
import net.psforever.objects.serverobject.environment.EnvironmentAttribute
|
||||
|
||||
/**
|
||||
* The deeper you move into lava, the greater the amount of health you burn through.
|
||||
* Vehicles take significant damage.
|
||||
* What do you hope to achieve by wading through molten rock anyway?
|
||||
*/
|
||||
case object LavaDepth extends EnvironmentDamageModifiers.Mod {
|
||||
def calculate(damage: Int, data: DamageInteraction, cause: EnvironmentReason): Int = {
|
||||
if (cause.body.attribute == EnvironmentAttribute.Lava) {
|
||||
val depth: Float = scala.math.max(0, cause.body.collision.altitude - data.target.Position.z)
|
||||
data.target match {
|
||||
case _: PlayerSource =>
|
||||
(damage * (1f + depth)).toInt
|
||||
case t =>
|
||||
damage + (0.1f * depth * t.Definition.MaxHealth).toInt
|
||||
}
|
||||
} else {
|
||||
damage
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object EnvironmentDamageModifierFunctions {
|
||||
//intentionally blank
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package net.psforever.objects.vital.environment
|
||||
|
||||
import net.psforever.objects.vital.base.{DamageModifiers, DamageReason}
|
||||
import net.psforever.objects.vital.interaction.DamageInteraction
|
||||
|
||||
object EnvironmentDamageModifiers {
|
||||
trait Mod extends DamageModifiers.Mod {
|
||||
def calculate(damage : Int, data : DamageInteraction, cause : DamageReason) : Int = {
|
||||
cause match {
|
||||
case o : EnvironmentReason => calculate(damage, data, o)
|
||||
case _ => damage
|
||||
}
|
||||
}
|
||||
|
||||
def calculate(damage : Int, data : DamageInteraction, cause : EnvironmentReason) : Int
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
// Copyright (c) 2020 PSForever
|
||||
package net.psforever.objects.vital.environment
|
||||
|
||||
import net.psforever.objects.ballistics.SourceEntry
|
||||
import net.psforever.objects.serverobject.environment.{EnvironmentAttribute, PieceOfEnvironment}
|
||||
import net.psforever.objects.vital.base.{DamageReason, DamageResolution}
|
||||
import net.psforever.objects.vital.damage.DamageCalculations
|
||||
import net.psforever.objects.vital.prop.DamageProperties
|
||||
import net.psforever.objects.vital.resolution.{DamageAndResistance, DamageResistanceModel}
|
||||
import net.psforever.objects.vital.{NoResistanceSelection, SimpleResolutions, Vitality}
|
||||
|
||||
/**
|
||||
* A wrapper for a "damage source" in damage calculations
|
||||
* that parameterizes information necessary to explain the environment being antagonistic.
|
||||
* @see `DamageCalculations`
|
||||
* @param body a representative of an element of the environment
|
||||
* @param against for the purposes of damage, what kind of target is being acted upon
|
||||
*/
|
||||
final case class EnvironmentReason(body: PieceOfEnvironment, against: DamageCalculations.Selector) extends DamageReason {
|
||||
def resolution: DamageResolution.Value = DamageResolution.Hit
|
||||
|
||||
def source: DamageProperties = EnvironmentReason.selectDamage(body)
|
||||
|
||||
def same(test: DamageReason): Boolean = {
|
||||
test match {
|
||||
case o : EnvironmentReason => body == o.body //TODO eq
|
||||
case _ => false
|
||||
}
|
||||
}
|
||||
|
||||
def adversary: Option[SourceEntry] = None
|
||||
|
||||
def damageModel: DamageAndResistance = EnvironmentReason.drm(against)
|
||||
}
|
||||
|
||||
object EnvironmentReason {
|
||||
/**
|
||||
* Overloaded constructor.
|
||||
* @param body a representative of an element of the environment
|
||||
* @param target the target being involved in this interaction
|
||||
* @return an `EnvironmentReason` object
|
||||
*/
|
||||
def apply(body: PieceOfEnvironment, target: Vitality): EnvironmentReason =
|
||||
EnvironmentReason(body, target.DamageModel.DamageUsing)
|
||||
|
||||
/** variable, no resisting, quick and simple */
|
||||
def drm(against: DamageCalculations.Selector) = new DamageResistanceModel {
|
||||
DamageUsing = against
|
||||
ResistUsing = NoResistanceSelection
|
||||
Model = SimpleResolutions.calculate
|
||||
}
|
||||
|
||||
/** The flags for calculating an absence of environment damage. */
|
||||
private val noDamage = new DamageProperties { }
|
||||
/** The flags for calculating lava-based environment damage. */
|
||||
private val lavaDamage = new DamageProperties {
|
||||
Damage0 = 5 //20 dps per 250ms
|
||||
Damage1 = 37 //150 dps per 250ms
|
||||
Damage2 = 12 //50 dps per 250ms
|
||||
Damage3 = 12 //50 dps per 250ms
|
||||
Damage4 = 37 //150 dps per 250ms
|
||||
DamageToHealthOnly = true
|
||||
DamageToVehicleOnly = true
|
||||
DamageToBattleframeOnly = true
|
||||
Modifiers = LavaDepth
|
||||
//TODO Aggravated?
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an element in the environment,
|
||||
* denote the type of flags and values used in the damage resulting from an interaction.
|
||||
* @param environment the environmental element, with a specific attribute
|
||||
* @return the damage information flags for that attribute
|
||||
*/
|
||||
def selectDamage(environment: PieceOfEnvironment): DamageProperties = {
|
||||
environment.attribute match {
|
||||
case EnvironmentAttribute.Lava => lavaDamage
|
||||
case _ => noDamage
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
// Copyright (c) 2020 PSForever
|
||||
package net.psforever.objects.vital.etc
|
||||
|
||||
import net.psforever.objects.ballistics.SourceEntry
|
||||
import net.psforever.objects.vital.base.{DamageReason, DamageResolution}
|
||||
import net.psforever.objects.vital.prop.DamageProperties
|
||||
import net.psforever.objects.vital.resolution.DamageAndResistance
|
||||
|
||||
final case class EnvironmentReason(body: Any, source: DamageProperties) extends DamageReason {
|
||||
def resolution: DamageResolution.Value = DamageResolution.Unresolved
|
||||
|
||||
def same(test: DamageReason): Boolean = {
|
||||
test match {
|
||||
case o : EnvironmentReason => body == o.body //TODO eq
|
||||
case _ => false
|
||||
}
|
||||
}
|
||||
|
||||
def adversary: Option[SourceEntry] = None
|
||||
|
||||
def damageModel: DamageAndResistance = null
|
||||
}
|
||||
|
|
@ -1,11 +1,14 @@
|
|||
package net.psforever.objects.zones
|
||||
|
||||
import enumeratum.values.{StringEnumEntry, StringEnum}
|
||||
import enumeratum.values.{StringEnum, StringEnumEntry}
|
||||
import net.psforever.objects.serverobject.environment._
|
||||
import net.psforever.types.Vector3
|
||||
|
||||
sealed abstract class MapInfo(
|
||||
val value: String,
|
||||
val checksum: Long,
|
||||
val scale: MapScale
|
||||
val scale: MapScale,
|
||||
val environment: List[PieceOfEnvironment]
|
||||
) extends StringEnumEntry {}
|
||||
|
||||
case object MapInfo extends StringEnum[MapInfo] {
|
||||
|
|
@ -14,184 +17,303 @@ case object MapInfo extends StringEnum[MapInfo] {
|
|||
extends MapInfo(
|
||||
value = "map01",
|
||||
checksum = 2094187456L,
|
||||
scale = MapScale.Dim8192
|
||||
scale = MapScale.Dim8192,
|
||||
environment = List(
|
||||
SeaLevel(EnvironmentAttribute.Water, 35),
|
||||
Pool(EnvironmentAttribute.Water, 44.92f, 5965.164f, 4801.2266f, 5893.1094f, 4730.203f), //east of seth
|
||||
Pool(EnvironmentAttribute.Water, 43.625f, 5296.289f, 5356.8594f, 5265.789f, 5315.9062f), //south of bastet
|
||||
Pool(EnvironmentAttribute.Water, 43.57f, 6263.2812f, 3742.9375f, 6238.0f, 3712.7188f), //north of aton
|
||||
Pool(EnvironmentAttribute.Water, 43.515625f, 4805.5f, 4324.3984f, 4727.867f, 4280.2188f), //north of hapi
|
||||
Pool(EnvironmentAttribute.Water, 43.0625f, 3313.1094f, 4746.4844f, 3259.4219f, 4691.2266f), //east of thoth
|
||||
Pool(EnvironmentAttribute.Water, 43.51f, 1917.1016f, 4086.8984f, 1893.4844f, 4038.2734f) //between horus and amun
|
||||
)
|
||||
)
|
||||
|
||||
case object Map02
|
||||
extends MapInfo(
|
||||
value = "map02",
|
||||
checksum = 1113780607L,
|
||||
scale = MapScale.Dim8192
|
||||
scale = MapScale.Dim8192,
|
||||
environment = {
|
||||
//exclude parts of voltan and naum due to their generator rooms being below sealevel
|
||||
val northVoltan = 3562.4844f
|
||||
val southVoltan = 3401.6875f
|
||||
val eastVoltan = 4556.703f
|
||||
val westVoltan = 4411.6875f
|
||||
val northNaum = 3575.8047f
|
||||
val southNaum = 3539.5234f
|
||||
val eastNaum = 5490.6875f
|
||||
val westNaum = 5427.078f
|
||||
List(
|
||||
Pool(EnvironmentAttribute.Water, 11, 8192, westVoltan, 0, 0), //west of voltan
|
||||
Pool(EnvironmentAttribute.Water, 11, 8192, westNaum, 0, eastVoltan), //between voltan and naum
|
||||
Pool(EnvironmentAttribute.Water, 11, 8192, 8192, 0, eastNaum), //east of naum
|
||||
Pool(EnvironmentAttribute.Water, 11, 8192, eastVoltan, northVoltan, westVoltan), //north of voltan
|
||||
Pool(EnvironmentAttribute.Water, 11, southVoltan, eastVoltan, 0, westVoltan), //south of voltan
|
||||
Pool(EnvironmentAttribute.Water, 11, 8192, eastNaum, northNaum, westNaum), //north of naum
|
||||
Pool(EnvironmentAttribute.Water, 11, southNaum, eastNaum, 0, westNaum) //south of naum
|
||||
//TODO voltan Killplane
|
||||
//TODO naum Killplane
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
case object Map03
|
||||
extends MapInfo(
|
||||
value = "map03",
|
||||
checksum = 1624200906L,
|
||||
scale = MapScale.Dim8192
|
||||
scale = MapScale.Dim8192,
|
||||
environment = List(
|
||||
SeaLevel(EnvironmentAttribute.Water, 35),
|
||||
Pool(EnvironmentAttribute.Water, 67.3125f, 3449.586f, 5870.383f, 3313.75f, 5715.3203f), //east of itan, south of kaang
|
||||
Pool(EnvironmentAttribute.Water, 53.71875f, 6013.0625f, 1861.7969f, 5947.1406f, 1634.7734f), //E6
|
||||
Pool(EnvironmentAttribute.Water, 49.625f, 7181.6953f, 1496.3828f, 6972.992f, 1340.1328f), //east of wele
|
||||
Pool(EnvironmentAttribute.Water, 48.71875f, 992.5156f, 1806.5469f, 811.5547f, 1676.3359f), //west, island of leza
|
||||
Pool(EnvironmentAttribute.Water, 48.5f, 1327.8125f, 2069.5781f, 152.5234f, 1979.3281f), //east, island of leza
|
||||
Pool(EnvironmentAttribute.Water, 46.625f, 2384.9688f, 3659.1172f, 2238.3516f, 3483.3828f), //east of tore
|
||||
Pool(EnvironmentAttribute.Water, 39.15625f, 4112.953f, 2509.3438f, 3778.5781f, 2312.789f), //south of hunhau south geowarp
|
||||
Pool(EnvironmentAttribute.Water, 39.046875f, 5877.8203f, 7131.664f, 5690.5547f, 6955.383f), //north of gate2
|
||||
Pool(EnvironmentAttribute.Water, 37.984375f, 2737.2578f, 3409.9219f, 2648.3984f, 3210.711f), //northeast of tore
|
||||
Pool(EnvironmentAttribute.Water, 37.703125f, 4689.1875f, 4788.922f, 4568.8438f, 4665.1016f), //north of gunuku
|
||||
Pool(EnvironmentAttribute.Water, 37.53125f, 2701.6797f, 806.6172f, 2648.3984f, 738.4375f), //island with mukuru
|
||||
Pool(EnvironmentAttribute.Water, 36.921875f, 3162.1094f, 1689.5703f, 3085.7422f, 1612.7734f), //north of nzame
|
||||
Pool(EnvironmentAttribute.Water, 36.390625f, 4143.797f, 4872.3906f, 4021.9766f, 4798.578f), //south of gunuku
|
||||
Pool(EnvironmentAttribute.Water, 35.71875f, 2591.336f, 1752.5938f, 2512.7578f, 1663.1172f) //south of nzame
|
||||
)
|
||||
)
|
||||
|
||||
case object Map04
|
||||
extends MapInfo(
|
||||
value = "map04",
|
||||
checksum = 2455050867L,
|
||||
scale = MapScale.Dim8192
|
||||
scale = MapScale.Dim8192,
|
||||
environment = List(SeaLevel(EnvironmentAttribute.Water, 19.984375f))
|
||||
)
|
||||
|
||||
case object Map05
|
||||
extends MapInfo(
|
||||
value = "map05",
|
||||
checksum = 107922342L,
|
||||
scale = MapScale.Dim8192
|
||||
scale = MapScale.Dim8192,
|
||||
environment = List(
|
||||
SeaLevel(EnvironmentAttribute.Water, 35.015625f),
|
||||
Pool(EnvironmentAttribute.Water, 51.875f, 4571.8125f, 3015.5547f, 4455.8047f, 2852.711f), //down the road, west of bel
|
||||
Pool(EnvironmentAttribute.Water, 49.8125f, 4902.336f, 3413.461f, 4754.0938f, 3210.8125f), //west of bel
|
||||
Pool(EnvironmentAttribute.Water, 49.515625f, 4044.3984f, 4700.8516f, 3999.9688f, 4517.375f), //southeast of neit
|
||||
Pool(EnvironmentAttribute.Water, 48.515625f, 4553.75f, 4110.2188f, 4438.6875f, 3995.3125f), //northwest of neit
|
||||
Pool(EnvironmentAttribute.Water, 48.28125f, 4474.3906f, 4551.2812f, 4339.3984f, 4472.4375f), //northeast of neit
|
||||
Pool(EnvironmentAttribute.Water, 45.828125f, 3808.0547f, 3901.3828f, 1432.5625f, 3720.9844f), //J17
|
||||
Pool(EnvironmentAttribute.Water, 43.765625f, 3997.2812f, 3991.539f, 3937.8906f, 3937.875f), //southwest of neit
|
||||
Pool(EnvironmentAttribute.Water, 43.671875f, 2694.2031f, 3079.875f, 2552.414f, 2898.8203f), //west of anu
|
||||
Pool(EnvironmentAttribute.Water, 42.671875f, 5174.4844f, 5930.133f, 4981.4297f, 5812.383f), //west of lugh
|
||||
Pool(EnvironmentAttribute.Water, 42.203125f, 4935.742f, 5716.086f, 5496.6953f, 5444.5625f), //across road, west of lugh
|
||||
Pool(EnvironmentAttribute.Water, 41.765625f, 2073.914f, 4982.5938f, 1995.4688f, 4899.086f), //L15-M16
|
||||
Pool(EnvironmentAttribute.Water, 41.3125f, 3761.1484f, 2616.75f, 3627.4297f, 2505.1328f), //G11, south
|
||||
Pool(EnvironmentAttribute.Water, 40.421875f, 4058.8281f, 2791.6562f, 3985.1016f, 2685.3672f) //G11, north
|
||||
)
|
||||
)
|
||||
|
||||
case object Map06
|
||||
extends MapInfo(
|
||||
value = "map06",
|
||||
checksum = 579139514L,
|
||||
scale = MapScale.Dim8192
|
||||
scale = MapScale.Dim8192,
|
||||
environment = List(
|
||||
SeaLevel(EnvironmentAttribute.Water, 10.03125f),
|
||||
Pool(EnvironmentAttribute.Water, 213.03125f, 3116.7266f, 4724.414f, 2685.8281f, 4187.4375f) //southwest of tootega
|
||||
)
|
||||
)
|
||||
|
||||
case object Map07
|
||||
extends MapInfo(
|
||||
value = "map07",
|
||||
checksum = 1564014762L,
|
||||
scale = MapScale.Dim8192
|
||||
scale = MapScale.Dim8192,
|
||||
environment = List(SeaLevel(EnvironmentAttribute.Water, 29.984375f))
|
||||
)
|
||||
|
||||
case object Map08
|
||||
extends MapInfo(
|
||||
value = "map08",
|
||||
checksum = 0L,
|
||||
scale = MapScale.Dim8192
|
||||
scale = MapScale.Dim8192,
|
||||
environment = List(SeaLevel(EnvironmentAttribute.Water, 26.078125f))
|
||||
)
|
||||
|
||||
case object Map09
|
||||
extends MapInfo(
|
||||
value = "map09",
|
||||
checksum = 1380643455L,
|
||||
scale = MapScale.Dim8192
|
||||
scale = MapScale.Dim8192,
|
||||
environment = List(
|
||||
SeaLevel(EnvironmentAttribute.Water, 30),
|
||||
Pool(EnvironmentAttribute.Water, 41.46875f, 5964.461f, 1947.1328f, 5701.6016f, 1529.8438f), //north of wakea
|
||||
Pool(EnvironmentAttribute.Water, 39.21875f, 5694.125f, 6939.8984f, 5516.922f, 6814.211f), //northeast of iva
|
||||
Pool(EnvironmentAttribute.Water, 39.078125f, 4381.789f, 6650.8203f, 4071.4766f, 6445.133f), //south of iva
|
||||
Pool(EnvironmentAttribute.Lava, DeepCircularSurface(Vector3(3901.5547f, 4422.746f, 224.57812f), 82.6797f)), //upper west lava pool
|
||||
Pool(EnvironmentAttribute.Lava, DeepSurface(189.54688f, 4032.914f, 3893.6562f, 3912.3906f, 3666.4453f)), //lower west lava pool
|
||||
Pool(EnvironmentAttribute.Lava, DeepSurface(187.57812f, 4288.1484f, 4589.0703f, 3996.3125f, 4355.6406f)), //lower central lava pool
|
||||
Pool(EnvironmentAttribute.Lava, DeepSurface(181.45312f, 4635.1953f, 4579.3516f, 4406.3438f, 4303.828f)), //upper central lava pool
|
||||
Pool(EnvironmentAttribute.Lava, DeepSurface(176.64062f, 4274.8125f, 4969.9688f, 4101.7734f, 4766.3594f)) //east lava pool
|
||||
)
|
||||
)
|
||||
|
||||
case object Map10
|
||||
extends MapInfo(
|
||||
value = "map10",
|
||||
checksum = 230810349L,
|
||||
scale = MapScale.Dim8192
|
||||
scale = MapScale.Dim8192,
|
||||
environment = List(SeaLevel(EnvironmentAttribute.Water, 28))
|
||||
)
|
||||
|
||||
case object Map11
|
||||
extends MapInfo(
|
||||
value = "map11",
|
||||
checksum = 4129515529L,
|
||||
scale = MapScale.Dim8192
|
||||
scale = MapScale.Dim8192,
|
||||
environment = List(
|
||||
SeaLevel(EnvironmentAttribute.Water, 24),
|
||||
Pool(EnvironmentAttribute.Water, 44.453125f, 4289.4766f, 3124.8125f, 4070.7031f, 2892.9922f), //H10
|
||||
Pool(EnvironmentAttribute.Water, 39.984375f, 5405.9297f, 2843.8672f, 5190.1562f, 2653.5625f), //southeast of hart c campus
|
||||
Pool(EnvironmentAttribute.Water, 36.15625f, 4622.3594f, 3861.6797f, 4497.9844f, 3717.3516f), //J9
|
||||
Pool(EnvironmentAttribute.Water, 35.234375f, 5596.086f, 4019.6797f, 5354.078f, 3814.1875f), //south of hart b campus
|
||||
Pool(EnvironmentAttribute.Water, 34.96875f, 5899.367f, 3235.5781f, 5573.8516f, 2865.7812f), //northeast of hart c campus
|
||||
Pool(EnvironmentAttribute.Water, 34.328125f, 3880.7422f, 5261.508f, 3780.9219f, 5166.953f), //east of hart a campus
|
||||
Pool(EnvironmentAttribute.Water, 31.03125f, 4849.797f, 2415.4297f, 4731.8594f, 2252.1484f) //south of hart c campus
|
||||
)
|
||||
)
|
||||
|
||||
case object Map12
|
||||
extends MapInfo(
|
||||
value = "map12",
|
||||
checksum = 962888126L,
|
||||
scale = MapScale.Dim8192
|
||||
scale = MapScale.Dim8192,
|
||||
environment = List(SeaLevel(EnvironmentAttribute.Water, 20.03125f))
|
||||
)
|
||||
|
||||
case object Map13
|
||||
extends MapInfo(
|
||||
value = "map13",
|
||||
checksum = 3904659548L,
|
||||
scale = MapScale.Dim8192
|
||||
scale = MapScale.Dim8192,
|
||||
environment = List(SeaLevel(EnvironmentAttribute.Water, 30))
|
||||
)
|
||||
|
||||
case object Map14
|
||||
extends MapInfo(
|
||||
value = "map14",
|
||||
checksum = 0L,
|
||||
scale = MapScale.Dim8192
|
||||
scale = MapScale.Dim8192,
|
||||
environment = List(SeaLevel(EnvironmentAttribute.Water, 0))
|
||||
)
|
||||
|
||||
case object Map15
|
||||
extends MapInfo(
|
||||
value = "map15",
|
||||
checksum = 0L,
|
||||
scale = MapScale.Dim8192
|
||||
scale = MapScale.Dim8192,
|
||||
environment = List(SeaLevel(EnvironmentAttribute.Water, 0))
|
||||
)
|
||||
|
||||
case object Map16
|
||||
extends MapInfo(
|
||||
value = "map16",
|
||||
checksum = 0L,
|
||||
scale = MapScale.Dim8192
|
||||
scale = MapScale.Dim8192,
|
||||
environment = List(SeaLevel(EnvironmentAttribute.Water, 0))
|
||||
)
|
||||
|
||||
case object Ugd01
|
||||
extends MapInfo(
|
||||
value = "ugd01",
|
||||
checksum = 3405929729L,
|
||||
scale = MapScale.Dim2560
|
||||
scale = MapScale.Dim2560,
|
||||
environment = List(SeaLevel(EnvironmentAttribute.Water, 50.734375f)) //TODO waterfalls!
|
||||
)
|
||||
|
||||
case object Ugd02
|
||||
extends MapInfo(
|
||||
value = "ugd02",
|
||||
checksum = 2702486449L,
|
||||
scale = MapScale.Dim2560
|
||||
scale = MapScale.Dim2560,
|
||||
environment = List(
|
||||
Pool(EnvironmentAttribute.Water, 194.89062f, 1763.4141f, 1415.125f, 1333.9531f, 1280.4609f), //east, northern pool
|
||||
Pool(EnvironmentAttribute.Water, 192.40625f, 1717.5703f, 1219.3359f, 1572.8828f, 1036.1328f), //bottom, northern pool
|
||||
Pool(EnvironmentAttribute.Water, 192.32812f, 1966.1562f, 1252.7344f, 1889.8047f, 1148.5312f), //top, northern pool
|
||||
Pool(EnvironmentAttribute.Water, 191.65625f, 1869.1484f, 1195.6406f, 1743.8125f, 1050.7344f), //middle, northern pool
|
||||
Pool(EnvironmentAttribute.Water, 183.98438f, 914.33594f, 1369.5f, 626.03906f, 666.3047f), //upper southern pools
|
||||
Pool(EnvironmentAttribute.Water, 182.96875f, 580.7578f, 913.52344f, 520.4531f, 843.97656f) //lowest southern pool
|
||||
)
|
||||
)
|
||||
|
||||
case object Ugd03
|
||||
extends MapInfo(
|
||||
value = "ugd03",
|
||||
checksum = 1673539651L,
|
||||
scale = MapScale.Dim2048
|
||||
scale = MapScale.Dim2048,
|
||||
environment = List(SeaLevel(EnvironmentAttribute.Death, 30)) //not actually lava, but a kill plane if you fall beneath the map
|
||||
)
|
||||
|
||||
case object Ugd04
|
||||
extends MapInfo(
|
||||
value = "ugd04",
|
||||
checksum = 3797992164L,
|
||||
scale = MapScale.Dim2048
|
||||
scale = MapScale.Dim2048,
|
||||
environment = List(SeaLevel(EnvironmentAttribute.Death, 51.215f)) //ADB: 51.414f
|
||||
)
|
||||
|
||||
case object Ugd05
|
||||
extends MapInfo(
|
||||
value = "ugd05",
|
||||
checksum = 1769572498L,
|
||||
scale = MapScale.Dim2048
|
||||
scale = MapScale.Dim2048,
|
||||
environment = List(SeaLevel(EnvironmentAttribute.Death, 115)) //not actually lava, but a kill plane if you fall beneath the map
|
||||
)
|
||||
|
||||
case object Ugd06
|
||||
extends MapInfo(
|
||||
value = "ugd06",
|
||||
checksum = 4274683970L,
|
||||
scale = MapScale.Dim2560
|
||||
scale = MapScale.Dim2560,
|
||||
environment = List(SeaLevel(EnvironmentAttribute.Death, 55)) //not actually lava, but a kill plane if you fall beneath the map
|
||||
)
|
||||
|
||||
case object Map96
|
||||
extends MapInfo(
|
||||
value = "map96",
|
||||
checksum = 846603446L,
|
||||
scale = MapScale.Dim4096
|
||||
scale = MapScale.Dim4096,
|
||||
environment = List(SeaLevel(EnvironmentAttribute.Water, 17.015625f))
|
||||
)
|
||||
|
||||
case object Map97
|
||||
extends MapInfo(
|
||||
value = "map97",
|
||||
checksum = 2810790213L,
|
||||
scale = MapScale.Dim4096
|
||||
scale = MapScale.Dim4096,
|
||||
environment = List(
|
||||
SeaLevel(EnvironmentAttribute.Water, 10.09375f),
|
||||
Pool(EnvironmentAttribute.Water, 20.484375f, 2183.8203f, 2086.5078f, 2127.2266f, 1992.5f), //north
|
||||
Pool(EnvironmentAttribute.Water, 20.421875f, 1880.4375f, 1961.875f, 1816.1484f, 1915.0625f), //west
|
||||
Pool(EnvironmentAttribute.Water, 20.421875f, 2028.1172f, 2232.4375f, 1976.9141f, 2181.0312f) //east
|
||||
)
|
||||
)
|
||||
|
||||
case object Map98
|
||||
extends MapInfo(
|
||||
value = "map98",
|
||||
checksum = 3654267088L,
|
||||
scale = MapScale.Dim4096
|
||||
scale = MapScale.Dim4096,
|
||||
environment = List(SeaLevel(EnvironmentAttribute.Water, 3.5f))
|
||||
)
|
||||
|
||||
case object Map99
|
||||
extends MapInfo(
|
||||
value = "map99",
|
||||
checksum = 4113726460L,
|
||||
scale = MapScale.Dim4096
|
||||
scale = MapScale.Dim4096,
|
||||
environment = List(SeaLevel(EnvironmentAttribute.Water, 44.0625f))
|
||||
)
|
||||
|
||||
val values: IndexedSeq[MapInfo] = findValues
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package net.psforever.objects.zones
|
||||
|
||||
import net.psforever.objects.serverobject.environment.PieceOfEnvironment
|
||||
import net.psforever.objects.serverobject.structures.FoundationBuilder
|
||||
import net.psforever.objects.serverobject.zipline.ZipLinePath
|
||||
import net.psforever.objects.serverobject.{PlanetSideServerObject, ServerObjectBuilder}
|
||||
|
|
@ -32,6 +33,7 @@ class ZoneMap(val name: String) {
|
|||
var checksum: Long = 0
|
||||
var zipLinePaths: List[ZipLinePath] = List()
|
||||
var cavern: Boolean = false
|
||||
var environment: List[PieceOfEnvironment] = List()
|
||||
private var linkTurretWeapon: Map[Int, Int] = Map()
|
||||
private var linkTerminalPad: Map[Int, Int] = Map()
|
||||
private var linkTerminalInterface: Map[Int, Int] = Map()
|
||||
|
|
|
|||
|
|
@ -3,122 +3,141 @@ package net.psforever.packet.game
|
|||
|
||||
import net.psforever.newcodecs.newcodecs
|
||||
import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket}
|
||||
import net.psforever.types.PlanetSideGUID
|
||||
import scodec.{Attempt, Codec}
|
||||
import net.psforever.types.{OxygenState, PlanetSideGUID}
|
||||
import scodec.Codec
|
||||
import scodec.codecs._
|
||||
import shapeless.{::, HNil}
|
||||
|
||||
/**
|
||||
* Alert the condition of a vehicle the player is using when going too far underwater.
|
||||
* The player must be mounted in/on this vehicle at start time for this countdown to display.
|
||||
* @param vehicle_guid the player's mounted vehicle
|
||||
* @param progress the remaining countdown;
|
||||
* for vehicle waterlog condition, the progress per second rate is very high
|
||||
* @param active show a new countdown if `true` (resets any active countdown);
|
||||
* clear any active countdowns if `false`;
|
||||
* defaults to `true`
|
||||
* Infomation about the progress bar displayed for a certain target's drowning condition.
|
||||
* @param guid the target
|
||||
* @param progress the remaining countdown
|
||||
* @param condition in what state of drowning the target is progressing
|
||||
*/
|
||||
final case class WaterloggedVehicleState(vehicle_guid: PlanetSideGUID, progress: Float, active: Boolean = true)
|
||||
final case class DrowningTarget(guid: PlanetSideGUID, progress: Float, condition: OxygenState)
|
||||
|
||||
/**
|
||||
* Dispatched by the server to cause the player to slowly drown.
|
||||
* If the player is mounted in a vehicle at the time, alert the player that the vehicle may be disabled.<br>
|
||||
* <br>
|
||||
* When a player walks too far underwater, a borderless red progress bar with a countdown from 100 (98) is displayed across the screen.
|
||||
* The countdown proceeds to zero at a fixed rate and is timed with the depleting progress bar.
|
||||
* The flavor text reads "Oxygen level".
|
||||
* The countdown proceeds to zero at a fixed rate - it takes approximately 60s - and is timed with the depleting progress bar.
|
||||
* When it reaches zero, the player will be killed.
|
||||
* If the player is in a vehicle after a certain depth, a blue bar and countdown pair will superimpose the red indicators.
|
||||
* It depletes much more rapidly than the red indicators.
|
||||
* It depletes much more rapidly than the red indicators - it takes approximately 5s.
|
||||
* When it reaches zero, the vehicle will become disabled.
|
||||
* All players in the vehicle's seats will be kicked and they will not be allowed back in.<br>
|
||||
* <br>
|
||||
* Normally, the countdowns should be set to begin at 100 (100.0).
|
||||
* This is the earliest the drowning GUI will appear for either blue or red indicators.
|
||||
* Passing greater intervals - up to 204.8 - will start the countdown silently but the GUI will be hidden until 100.0.
|
||||
* Greater intervals - up to 204.8 - will start the countdown silently but the GUI will be hidden until 100.0.
|
||||
* (The progress indicators will actually appear to start counting from 98.)
|
||||
* Managing the secondary vehicle countdown independent of the primary player countdown requires updating with the correct levels.
|
||||
* The countdown can be cancelled by instructing it to be `active = false`.<br>
|
||||
* <br>
|
||||
* Except for updating the indicators, all other functionality of "drowning" is automated by the server.
|
||||
* @param player_guid the player
|
||||
* @param progress the remaining countdown;
|
||||
* for character oxygen, the progress per second rate is about 1
|
||||
* @param active show a new countdown if `true` (resets any active countdown);
|
||||
* clear any active countdowns if `false`
|
||||
* @param vehicle_state optional state of the vehicle the player is driving
|
||||
* @param player the player's oxygen state
|
||||
* @param vehicle optional oxygen state of the vehicle the player is driving;
|
||||
* the player must be mounted in the vehicle (at start time)
|
||||
*/
|
||||
final case class OxygenStateMessage(
|
||||
player_guid: PlanetSideGUID,
|
||||
progress: Float,
|
||||
active: Boolean,
|
||||
vehicle_state: Option[WaterloggedVehicleState] = None
|
||||
) extends PlanetSideGamePacket {
|
||||
player: DrowningTarget,
|
||||
vehicle: Option[DrowningTarget]
|
||||
) extends PlanetSideGamePacket {
|
||||
type Packet = OxygenStateMessage
|
||||
def opcode = GamePacketOpcode.OxygenStateMessage
|
||||
def encode = OxygenStateMessage.encode(this)
|
||||
}
|
||||
|
||||
object DrowningTarget {
|
||||
def apply(guid: PlanetSideGUID): DrowningTarget =
|
||||
DrowningTarget(guid, 100, OxygenState.Suffocation)
|
||||
|
||||
def apply(guid: PlanetSideGUID, progress: Float): DrowningTarget =
|
||||
DrowningTarget(guid, progress, OxygenState.Suffocation)
|
||||
|
||||
def recover(guid: PlanetSideGUID, progress: Float): DrowningTarget =
|
||||
DrowningTarget(guid, progress, OxygenState.Recovery)
|
||||
}
|
||||
|
||||
object OxygenStateMessage extends Marshallable[OxygenStateMessage] {
|
||||
|
||||
/**
|
||||
* Overloaded constructor that removes the optional state of the `WaterloggedVehicleState` parameter.
|
||||
* @param player_guid the player
|
||||
* @param progress the remaining countdown
|
||||
* @param active show or clear the countdown
|
||||
* @param vehicle_state state of the vehicle the player is driving
|
||||
* @return
|
||||
*/
|
||||
def apply(
|
||||
player_guid: PlanetSideGUID,
|
||||
progress: Float,
|
||||
active: Boolean,
|
||||
vehicle_state: WaterloggedVehicleState
|
||||
): OxygenStateMessage =
|
||||
OxygenStateMessage(player_guid, progress, active, Some(vehicle_state))
|
||||
player_guid: PlanetSideGUID
|
||||
): OxygenStateMessage =
|
||||
OxygenStateMessage(DrowningTarget(player_guid), None)
|
||||
|
||||
/**
|
||||
* A simple pattern that expands the datatypes of the packet's basic `Codec`.
|
||||
*/
|
||||
private type basePattern = PlanetSideGUID :: Float :: Boolean :: HNil
|
||||
def apply(
|
||||
player_guid: PlanetSideGUID,
|
||||
progress: Float
|
||||
): OxygenStateMessage =
|
||||
OxygenStateMessage(DrowningTarget(player_guid, progress), None)
|
||||
|
||||
def apply(
|
||||
player: DrowningTarget
|
||||
): OxygenStateMessage =
|
||||
OxygenStateMessage(player, None)
|
||||
|
||||
def apply(
|
||||
player_guid: PlanetSideGUID,
|
||||
player_progress: Float,
|
||||
vehicle_guid: PlanetSideGUID,
|
||||
vehicle_progress: Float
|
||||
): OxygenStateMessage =
|
||||
OxygenStateMessage(
|
||||
DrowningTarget(player_guid, player_progress),
|
||||
Some(DrowningTarget(vehicle_guid, vehicle_progress))
|
||||
)
|
||||
|
||||
def apply(
|
||||
player_guid: PlanetSideGUID,
|
||||
player_progress: Float,
|
||||
vehicle_guid: PlanetSideGUID
|
||||
): OxygenStateMessage =
|
||||
OxygenStateMessage(
|
||||
DrowningTarget(player_guid, player_progress),
|
||||
Some(DrowningTarget(vehicle_guid))
|
||||
)
|
||||
|
||||
def recover(
|
||||
player_guid: PlanetSideGUID,
|
||||
progress: Float
|
||||
): OxygenStateMessage =
|
||||
OxygenStateMessage(DrowningTarget.recover(player_guid, progress), None)
|
||||
|
||||
def recoverVehicle(
|
||||
player_guid: PlanetSideGUID,
|
||||
player_progress: Float,
|
||||
vehicle_guid: PlanetSideGUID,
|
||||
vehicle_progress: Float
|
||||
): OxygenStateMessage =
|
||||
OxygenStateMessage(
|
||||
DrowningTarget(player_guid, player_progress),
|
||||
Some(DrowningTarget.recover(vehicle_guid, vehicle_progress))
|
||||
)
|
||||
|
||||
def recover(
|
||||
player_guid: PlanetSideGUID,
|
||||
player_progress: Float,
|
||||
vehicle_guid: PlanetSideGUID,
|
||||
vehicle_progress: Float
|
||||
): OxygenStateMessage =
|
||||
OxygenStateMessage(
|
||||
DrowningTarget.recover(player_guid, player_progress),
|
||||
Some(DrowningTarget.recover(vehicle_guid, vehicle_progress))
|
||||
)
|
||||
|
||||
/**
|
||||
* A `Codec` for the repeated processing of three values.
|
||||
* This `Codec` is the basis for the packet's data.
|
||||
*/
|
||||
private val base_codec: Codec[basePattern] =
|
||||
private val oxygen_deprivation_codec: Codec[DrowningTarget] = (
|
||||
PlanetSideGUID.codec ::
|
||||
newcodecs.q_float(
|
||||
0.0f,
|
||||
204.8f,
|
||||
11
|
||||
) :: //hackish: 2^11 == 2047, so it should be 204.7; but, 204.8 allows decode == encode
|
||||
bool
|
||||
OxygenState.codec
|
||||
).as[DrowningTarget]
|
||||
|
||||
implicit val codec: Codec[OxygenStateMessage] = (
|
||||
base_codec.exmap[basePattern](
|
||||
{
|
||||
case guid :: time :: active :: HNil =>
|
||||
Attempt.successful(guid :: time :: active :: HNil)
|
||||
},
|
||||
{
|
||||
case guid :: time :: active :: HNil =>
|
||||
Attempt.successful(guid :: time :: active :: HNil)
|
||||
}
|
||||
) :+
|
||||
optional(
|
||||
bool,
|
||||
"vehicle_state" | base_codec
|
||||
.exmap[WaterloggedVehicleState](
|
||||
{
|
||||
case guid :: time :: active :: HNil =>
|
||||
Attempt.successful(WaterloggedVehicleState(guid, time, active))
|
||||
},
|
||||
{
|
||||
case WaterloggedVehicleState(guid, time, active) =>
|
||||
Attempt.successful(guid :: time :: active :: HNil)
|
||||
}
|
||||
)
|
||||
.as[WaterloggedVehicleState]
|
||||
)
|
||||
).as[OxygenStateMessage]
|
||||
("player" | oxygen_deprivation_codec) ::
|
||||
optional(bool, "vehicle" | oxygen_deprivation_codec)
|
||||
).as[OxygenStateMessage]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,8 +65,9 @@ class ServiceManager extends Actor {
|
|||
}
|
||||
catch {
|
||||
case e: InvalidActorNameException => //if an entry already exists, no harm, no foul, just don't do it again
|
||||
log.warn(s"service manager says: ${e.getMessage}")
|
||||
case _ => ;
|
||||
log.warn(s"service manager says: service already exists - ${e.getMessage}")
|
||||
case e: Exception =>
|
||||
log.error(s"service manager says: service could not start - ${e.getMessage}")
|
||||
}
|
||||
|
||||
case Lookup(name) =>
|
||||
|
|
|
|||
|
|
@ -184,6 +184,10 @@ class AvatarService(zone: Zone) extends Actor {
|
|||
AvatarEvents.publish(
|
||||
AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.ObjectHeld(slot))
|
||||
)
|
||||
case AvatarAction.OxygenState(player, vehicle) =>
|
||||
AvatarEvents.publish(
|
||||
AvatarServiceResponse(s"/$forChannel/Avatar", player.guid, AvatarResponse.OxygenState(player, vehicle))
|
||||
)
|
||||
case AvatarAction.PlanetsideAttribute(guid, attribute_type, attribute_value) =>
|
||||
AvatarEvents.publish(
|
||||
AvatarServiceResponse(
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import net.psforever.objects.ballistics.{Projectile, SourceEntry}
|
|||
import net.psforever.objects.ce.Deployable
|
||||
import net.psforever.objects.equipment.Equipment
|
||||
import net.psforever.objects.inventory.InventoryItem
|
||||
import net.psforever.objects.serverobject.environment.OxygenStateTarget
|
||||
import net.psforever.objects.zones.Zone
|
||||
import net.psforever.packet.PlanetSideGamePacket
|
||||
import net.psforever.packet.game.objectcreate.{ConstructorData, ObjectCreateMessageParent}
|
||||
|
|
@ -67,6 +68,7 @@ object AvatarAction {
|
|||
) extends Action
|
||||
final case class ObjectDelete(player_guid: PlanetSideGUID, item_guid: PlanetSideGUID, unk: Int = 0) extends Action
|
||||
final case class ObjectHeld(player_guid: PlanetSideGUID, slot: Int) extends Action
|
||||
final case class OxygenState(player: OxygenStateTarget, vehicle: Option[OxygenStateTarget]) extends Action
|
||||
final case class PlanetsideAttribute(player_guid: PlanetSideGUID, attribute_type: Int, attribute_value: Long)
|
||||
extends Action
|
||||
final case class PlanetsideAttributeToAll(player_guid: PlanetSideGUID, attribute_type: Int, attribute_value: Long)
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import net.psforever.objects.Player
|
|||
import net.psforever.objects.ballistics.{Projectile, SourceEntry}
|
||||
import net.psforever.objects.equipment.Equipment
|
||||
import net.psforever.objects.inventory.InventoryItem
|
||||
import net.psforever.objects.serverobject.environment.OxygenStateTarget
|
||||
import net.psforever.packet.PlanetSideGamePacket
|
||||
import net.psforever.packet.game.objectcreate.ConstructorData
|
||||
import net.psforever.packet.game.ObjectCreateMessage
|
||||
|
|
@ -47,6 +48,7 @@ object AvatarResponse {
|
|||
final case class LoadProjectile(pkt: ObjectCreateMessage) extends Response
|
||||
final case class ObjectDelete(item_guid: PlanetSideGUID, unk: Int) extends Response
|
||||
final case class ObjectHeld(slot: Int) extends Response
|
||||
final case class OxygenState(player: OxygenStateTarget, vehicle: Option[OxygenStateTarget]) extends Response
|
||||
final case class PlanetsideAttribute(attribute_type: Int, attribute_value: Long) extends Response
|
||||
final case class PlanetsideAttributeToAll(attribute_type: Int, attribute_value: Long) extends Response
|
||||
final case class PlanetsideAttributeSelf(attribute_type: Int, attribute_value: Long) extends Response
|
||||
|
|
|
|||
27
src/main/scala/net/psforever/types/OxygenState.scala
Normal file
27
src/main/scala/net/psforever/types/OxygenState.scala
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
package net.psforever.types
|
||||
|
||||
import enumeratum.{Enum, EnumEntry}
|
||||
import net.psforever.packet.PacketHelpers
|
||||
import scodec.Codec
|
||||
import scodec.codecs.uint
|
||||
|
||||
/**
|
||||
* The progress state of being a drowning victim.
|
||||
*/
|
||||
sealed abstract class OxygenState extends EnumEntry {}
|
||||
|
||||
/**
|
||||
* The progress state of being a drowning victim.
|
||||
* `Suffocation` means being too far under water.
|
||||
* In terms of percentage, progress proceeds towards 0.
|
||||
* `Recovery` means emerging from being too far under water.
|
||||
* In terms of percentage, progress proceeds towards 100.
|
||||
*/
|
||||
object OxygenState extends Enum[OxygenState] {
|
||||
val values: IndexedSeq[OxygenState] = findValues
|
||||
|
||||
case object Recovery extends OxygenState
|
||||
case object Suffocation extends OxygenState
|
||||
|
||||
implicit val codec: Codec[OxygenState] = PacketHelpers.createEnumCodec(enum = this, uint(bits = 1))
|
||||
}
|
||||
|
|
@ -222,6 +222,7 @@ object Zones {
|
|||
|
||||
zoneMap.checksum = info.checksum
|
||||
zoneMap.scale = info.scale
|
||||
zoneMap.environment = info.environment
|
||||
|
||||
zoneMap.zipLinePaths = zplData.toList
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ package game
|
|||
import org.specs2.mutable._
|
||||
import net.psforever.packet._
|
||||
import net.psforever.packet.game._
|
||||
import net.psforever.types.PlanetSideGUID
|
||||
import net.psforever.types.{OxygenState, PlanetSideGUID}
|
||||
import scodec.bits._
|
||||
|
||||
class OxygenStateMessageTest extends Specification {
|
||||
|
|
@ -13,11 +13,12 @@ class OxygenStateMessageTest extends Specification {
|
|||
|
||||
"decode (self)" in {
|
||||
PacketCoding.decodePacket(string_self).require match {
|
||||
case OxygenStateMessage(guid, progress, active, veh_state) =>
|
||||
guid mustEqual PlanetSideGUID(75)
|
||||
progress mustEqual 50.0
|
||||
active mustEqual true
|
||||
veh_state.isDefined mustEqual false
|
||||
case OxygenStateMessage(player, vehicle) =>
|
||||
player.guid mustEqual PlanetSideGUID(75)
|
||||
player.progress mustEqual 50.0
|
||||
player.condition mustEqual OxygenState.Suffocation
|
||||
|
||||
vehicle.isDefined mustEqual false
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
|
|
@ -25,21 +26,23 @@ class OxygenStateMessageTest extends Specification {
|
|||
|
||||
"decode (vehicle)" in {
|
||||
PacketCoding.decodePacket(string_vehicle).require match {
|
||||
case OxygenStateMessage(guid, progress, active, veh_state) =>
|
||||
guid mustEqual PlanetSideGUID(75)
|
||||
progress mustEqual 50.0f
|
||||
active mustEqual true
|
||||
veh_state.isDefined mustEqual true
|
||||
veh_state.get.vehicle_guid mustEqual PlanetSideGUID(1546)
|
||||
veh_state.get.progress mustEqual 50.0f
|
||||
veh_state.get.active mustEqual true
|
||||
case OxygenStateMessage(player, vehicle) =>
|
||||
player.guid mustEqual PlanetSideGUID(75)
|
||||
player.progress mustEqual 50.0f
|
||||
player.condition mustEqual OxygenState.Suffocation
|
||||
|
||||
vehicle.isDefined mustEqual true
|
||||
val v = vehicle.get
|
||||
v.guid mustEqual PlanetSideGUID(1546)
|
||||
v.progress mustEqual 50.0f
|
||||
v.condition mustEqual OxygenState.Suffocation
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"encode (self)" in {
|
||||
val msg = OxygenStateMessage(PlanetSideGUID(75), 50.0f, true)
|
||||
val msg = OxygenStateMessage(PlanetSideGUID(75), 50.0f)
|
||||
val pkt = PacketCoding.encodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string_self
|
||||
|
|
@ -47,7 +50,7 @@ class OxygenStateMessageTest extends Specification {
|
|||
|
||||
"encode (vehicle)" in {
|
||||
val msg =
|
||||
OxygenStateMessage(PlanetSideGUID(75), 50.0f, true, WaterloggedVehicleState(PlanetSideGUID(1546), 50.0f, true))
|
||||
OxygenStateMessage(PlanetSideGUID(75), 50.0f, PlanetSideGUID(1546), 50.0f)
|
||||
val pkt = PacketCoding.encodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string_vehicle
|
||||
|
|
|
|||
307
src/test/scala/objects/EnvironmentTest.scala
Normal file
307
src/test/scala/objects/EnvironmentTest.scala
Normal file
|
|
@ -0,0 +1,307 @@
|
|||
// Copyright (c) 2020 PSForever
|
||||
package objects
|
||||
|
||||
import net.psforever.objects.{GlobalDefinitions, Player, Tool, Vehicle}
|
||||
import net.psforever.objects.definition.VehicleDefinition
|
||||
import net.psforever.objects.serverobject.environment._
|
||||
import net.psforever.objects.serverobject.terminals.{Terminal, TerminalDefinition}
|
||||
import net.psforever.objects.vital.Vitality
|
||||
import net.psforever.packet.game.objectcreate.ObjectClass
|
||||
import net.psforever.types.Vector3
|
||||
import org.specs2.mutable.Specification
|
||||
|
||||
class EnvironmentCollisionTest extends Specification {
|
||||
"DeepPlane" should {
|
||||
val point: Float = 10f
|
||||
val plane = DeepPlane(point)
|
||||
|
||||
"have altitude" in {
|
||||
plane.altitude mustEqual point
|
||||
}
|
||||
|
||||
"must have interaction that passes" in {
|
||||
plane.testInteraction(Vector3(0,0,10), varDepth = -1) mustEqual true
|
||||
plane.testInteraction(Vector3(0,0, 9), varDepth = 0) mustEqual true
|
||||
plane.testInteraction(Vector3(0,0, 8), varDepth = 1) mustEqual true
|
||||
}
|
||||
|
||||
"must have interaction that fails" in {
|
||||
plane.testInteraction(Vector3(0,0,11), varDepth = -1) mustEqual false
|
||||
plane.testInteraction(Vector3(0,0,10), varDepth = 0) mustEqual false
|
||||
plane.testInteraction(Vector3(0,0, 9), varDepth = 1) mustEqual false
|
||||
}
|
||||
}
|
||||
|
||||
"DeepSquare" should {
|
||||
val point: Float = 10f
|
||||
val square = DeepSquare(point, 9, 9, 1, 1)
|
||||
|
||||
"must have altitude" in {
|
||||
square.altitude mustEqual point
|
||||
}
|
||||
|
||||
"must have interaction that passes" in {
|
||||
square.testInteraction(Vector3(1,1, 0), varDepth = 0) mustEqual true
|
||||
square.testInteraction(Vector3(1,8, 0), varDepth = 0) mustEqual true
|
||||
square.testInteraction(Vector3(8,8, 0), varDepth = 0) mustEqual true
|
||||
square.testInteraction(Vector3(8,1, 0), varDepth = 0) mustEqual true
|
||||
square.testInteraction(Vector3(1,1,10), varDepth = -1) mustEqual true
|
||||
square.testInteraction(Vector3(1,1, 9), varDepth = 0) mustEqual true
|
||||
square.testInteraction(Vector3(1,1, 8), varDepth = 1) mustEqual true
|
||||
}
|
||||
|
||||
"must have interaction that fails" in {
|
||||
square.testInteraction(Vector3(1,0, 0), varDepth = 0) mustEqual false
|
||||
square.testInteraction(Vector3(1,9, 0), varDepth = 0) mustEqual false
|
||||
square.testInteraction(Vector3(0,9, 0), varDepth = 0) mustEqual false
|
||||
square.testInteraction(Vector3(0,1, 0), varDepth = 0) mustEqual false
|
||||
square.testInteraction(Vector3(1,1,11), varDepth = -1) mustEqual false
|
||||
square.testInteraction(Vector3(1,1,10), varDepth = 0) mustEqual false
|
||||
square.testInteraction(Vector3(1,1, 9), varDepth = 1) mustEqual false
|
||||
}
|
||||
}
|
||||
|
||||
"DeepSurface" should {
|
||||
val point: Float = 10f
|
||||
val surface = DeepSurface(point, 9, 9, 1, 1)
|
||||
|
||||
"must have altitude" in {
|
||||
surface.altitude mustEqual point
|
||||
}
|
||||
|
||||
"must have interaction that passes" in {
|
||||
surface.testInteraction(Vector3(1,1,0), varDepth = 0) mustEqual true
|
||||
surface.testInteraction(Vector3(1,8,0), varDepth = 0) mustEqual true
|
||||
surface.testInteraction(Vector3(8,8,0), varDepth = 0) mustEqual true
|
||||
surface.testInteraction(Vector3(8,1,0), varDepth = 0) mustEqual true
|
||||
surface.testInteraction(Vector3(1,1,9), varDepth = -1) mustEqual true
|
||||
surface.testInteraction(Vector3(1,1,9), varDepth = 0) mustEqual true
|
||||
surface.testInteraction(Vector3(1,1,9), varDepth = 1) mustEqual true
|
||||
}
|
||||
|
||||
"must have interaction that fails" in {
|
||||
surface.testInteraction(Vector3(1,0, 0), varDepth = 0) mustEqual false
|
||||
surface.testInteraction(Vector3(1,9, 0), varDepth = 0) mustEqual false
|
||||
surface.testInteraction(Vector3(0,9, 0), varDepth = 0) mustEqual false
|
||||
surface.testInteraction(Vector3(0,1, 0), varDepth = 0) mustEqual false
|
||||
surface.testInteraction(Vector3(1,1,11), varDepth = -1) mustEqual false
|
||||
surface.testInteraction(Vector3(1,1,10), varDepth = 0) mustEqual false
|
||||
}
|
||||
}
|
||||
|
||||
"DeepCircularSurface" should {
|
||||
val point: Float = 10f
|
||||
val center = Vector3(3, 3, point)
|
||||
val surface = DeepCircularSurface(center, 3)
|
||||
|
||||
"must have altitude" in {
|
||||
surface.altitude mustEqual point
|
||||
}
|
||||
|
||||
"must have interaction that passes" in {
|
||||
surface.testInteraction(Vector3(3,1,0), varDepth = 0) mustEqual true
|
||||
surface.testInteraction(Vector3(1,3,0), varDepth = 0) mustEqual true
|
||||
surface.testInteraction(Vector3(3,5,0), varDepth = 0) mustEqual true
|
||||
surface.testInteraction(Vector3(5,3,0), varDepth = 0) mustEqual true
|
||||
surface.testInteraction(Vector3(2,2,9), varDepth = -1) mustEqual true
|
||||
surface.testInteraction(Vector3(2,2,9), varDepth = 0) mustEqual true
|
||||
surface.testInteraction(Vector3(2,2,9), varDepth = 1) mustEqual true
|
||||
}
|
||||
|
||||
"must have interaction that fails" in {
|
||||
surface.testInteraction(Vector3(3,0, 0), varDepth = 0) mustEqual false
|
||||
surface.testInteraction(Vector3(0,3, 0), varDepth = 0) mustEqual false
|
||||
surface.testInteraction(Vector3(3,6, 0), varDepth = 0) mustEqual false
|
||||
surface.testInteraction(Vector3(6,3, 0), varDepth = 0) mustEqual false
|
||||
surface.testInteraction(Vector3(2,2,11), varDepth = -1) mustEqual false
|
||||
surface.testInteraction(Vector3(2,2,10), varDepth = 0) mustEqual false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class EnvironmentAttributeTest extends Specification {
|
||||
"Water" should {
|
||||
"interact with drownable object" in {
|
||||
EnvironmentAttribute.Water.canInteractWith(
|
||||
Vehicle(
|
||||
new VehicleDefinition(objectId = ObjectClass.apc_tr) { DrownAtMaxDepth = true }
|
||||
)
|
||||
) mustEqual true
|
||||
}
|
||||
|
||||
"not interact with object that does not drown" in {
|
||||
EnvironmentAttribute.Water.canInteractWith(
|
||||
Vehicle(
|
||||
new VehicleDefinition(objectId = ObjectClass.apc_tr) { DrownAtMaxDepth = false }
|
||||
)
|
||||
) mustEqual false
|
||||
}
|
||||
|
||||
"interact with depth-disable object" in {
|
||||
EnvironmentAttribute.Water.canInteractWith(
|
||||
Vehicle(
|
||||
new VehicleDefinition(objectId = ObjectClass.apc_tr) { DisableAtMaxDepth = true }
|
||||
)
|
||||
) mustEqual true
|
||||
}
|
||||
|
||||
"not interact with object that does not depth-disable" in {
|
||||
EnvironmentAttribute.Water.canInteractWith(
|
||||
Vehicle(
|
||||
new VehicleDefinition(objectId = ObjectClass.apc_tr) { DisableAtMaxDepth = false }
|
||||
)
|
||||
) mustEqual false
|
||||
}
|
||||
}
|
||||
|
||||
"Lava" should {
|
||||
"interact with a vital object that is damageable" in {
|
||||
val obj = Terminal(GlobalDefinitions.order_terminal)
|
||||
obj.isInstanceOf[Vitality] mustEqual true
|
||||
obj.asInstanceOf[Vitality].Definition.Damageable mustEqual true
|
||||
EnvironmentAttribute.Lava.canInteractWith(obj) mustEqual true
|
||||
}
|
||||
|
||||
"not interact with a vital object that is not damageable" in {
|
||||
val obj = Terminal(new TerminalDefinition(objectId = 455) {
|
||||
def Request(player : Player, msg : Any) : Terminal.Exchange = null
|
||||
Damageable = false
|
||||
})
|
||||
obj.isInstanceOf[Vitality] mustEqual true
|
||||
obj.asInstanceOf[Vitality].Definition.Damageable mustEqual false
|
||||
EnvironmentAttribute.Lava.canInteractWith(obj) mustEqual false
|
||||
}
|
||||
|
||||
"not interact with an object that has no vitality" in {
|
||||
val obj = Tool(GlobalDefinitions.suppressor)
|
||||
obj.isInstanceOf[Vitality] mustEqual false
|
||||
EnvironmentAttribute.Lava.canInteractWith(obj) mustEqual false
|
||||
}
|
||||
}
|
||||
|
||||
"Death" should {
|
||||
"interact with a vital object that is damageable" in {
|
||||
val obj = Terminal(GlobalDefinitions.order_terminal)
|
||||
obj.isInstanceOf[Vitality] mustEqual true
|
||||
obj.asInstanceOf[Vitality].Definition.Damageable mustEqual true
|
||||
EnvironmentAttribute.Death.canInteractWith(obj) mustEqual true
|
||||
}
|
||||
|
||||
"not interact with a vital object that is not damageable" in {
|
||||
val obj = Terminal(new TerminalDefinition(objectId = 455) {
|
||||
def Request(player : Player, msg : Any) : Terminal.Exchange = null
|
||||
Damageable = false
|
||||
})
|
||||
obj.isInstanceOf[Vitality] mustEqual true
|
||||
obj.asInstanceOf[Vitality].Definition.Damageable mustEqual false
|
||||
EnvironmentAttribute.Death.canInteractWith(obj) mustEqual false
|
||||
}
|
||||
|
||||
"not interact with an object that has no vitality" in {
|
||||
val obj = Tool(GlobalDefinitions.suppressor)
|
||||
obj.isInstanceOf[Vitality] mustEqual false
|
||||
EnvironmentAttribute.Death.canInteractWith(obj) mustEqual false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SeaLevelTest extends Specification {
|
||||
"SeaLevel" should {
|
||||
val point: Float = 10f
|
||||
val plane = DeepPlane(point)
|
||||
val level = SeaLevel(point)
|
||||
|
||||
"have altitude (same as DeepPlane)" in {
|
||||
plane.altitude mustEqual level.altitude
|
||||
}
|
||||
|
||||
"must have interaction that passes (same as DeepPlane)" in {
|
||||
plane.testInteraction(Vector3(0,0,10), varDepth = -1) mustEqual
|
||||
level.testInteraction(Vector3(0,0,10), varDepth = -1)
|
||||
plane.testInteraction(Vector3(0,0, 9), varDepth = 0) mustEqual
|
||||
level.testInteraction(Vector3(0,0, 9), varDepth = 0)
|
||||
plane.testInteraction(Vector3(0,0, 8), varDepth = 1) mustEqual
|
||||
level.testInteraction(Vector3(0,0, 8), varDepth = 1)
|
||||
}
|
||||
|
||||
"must have interaction that fails (same as DeepPlane)" in {
|
||||
plane.testInteraction(Vector3(0,0,11), varDepth = -1) mustEqual
|
||||
level.testInteraction(Vector3(0,0,11), varDepth = -1)
|
||||
plane.testInteraction(Vector3(0,0,10), varDepth = 0) mustEqual
|
||||
level.testInteraction(Vector3(0,0,10), varDepth = 0)
|
||||
plane.testInteraction(Vector3(0,0, 9), varDepth = 1) mustEqual
|
||||
level.testInteraction(Vector3(0,0, 9), varDepth = 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class PoolTest extends Specification {
|
||||
"Pool" should {
|
||||
val point: Float = 10f
|
||||
val square = DeepSquare(point, 1, 10, 10, 1)
|
||||
val pool = Pool(EnvironmentAttribute.Water, point, 1, 10, 10, 1)
|
||||
|
||||
"have altitude (same as DeepSquare)" in {
|
||||
pool.collision.altitude mustEqual square.altitude
|
||||
}
|
||||
|
||||
"must have interaction that passes (same as DeepSquare)" in {
|
||||
pool.testInteraction(Vector3(1,1, 0), varDepth = 0) mustEqual
|
||||
square.testInteraction(Vector3(1,1, 0), varDepth = 0)
|
||||
pool.testInteraction(Vector3(1,8, 0), varDepth = 0) mustEqual
|
||||
square.testInteraction(Vector3(1,8, 0), varDepth = 0)
|
||||
pool.testInteraction(Vector3(8,8, 0), varDepth = 0) mustEqual
|
||||
square.testInteraction(Vector3(8,8, 0), varDepth = 0)
|
||||
pool.testInteraction(Vector3(8,1, 0), varDepth = 0) mustEqual
|
||||
square.testInteraction(Vector3(8,1, 0), varDepth = 0)
|
||||
pool.testInteraction(Vector3(1,1,10), varDepth = -1) mustEqual
|
||||
square.testInteraction(Vector3(1,1,10), varDepth = -1)
|
||||
pool.testInteraction(Vector3(1,1, 9), varDepth = 0) mustEqual
|
||||
square.testInteraction(Vector3(1,1, 9), varDepth = 0)
|
||||
pool.testInteraction(Vector3(1,1, 8), varDepth = 1) mustEqual
|
||||
square.testInteraction(Vector3(1,1, 8), varDepth = 1)
|
||||
}
|
||||
|
||||
"must have interaction that fails (same as DeepSquare)" in {
|
||||
pool.testInteraction(Vector3(1,0, 0), varDepth = 0) mustEqual
|
||||
square.testInteraction(Vector3(1,0, 0), varDepth = 0)
|
||||
pool.testInteraction(Vector3(1,9, 0), varDepth = 0) mustEqual
|
||||
square.testInteraction(Vector3(1,9, 0), varDepth = 0)
|
||||
pool.testInteraction(Vector3(0,9, 0), varDepth = 0) mustEqual
|
||||
square.testInteraction(Vector3(0,9, 0), varDepth = 0)
|
||||
pool.testInteraction(Vector3(0,1, 0), varDepth = 0) mustEqual
|
||||
square.testInteraction(Vector3(0,1, 0), varDepth = 0)
|
||||
pool.testInteraction(Vector3(1,1,11), varDepth = -1) mustEqual
|
||||
square.testInteraction(Vector3(1,1,11), varDepth = -1)
|
||||
pool.testInteraction(Vector3(1,1,10), varDepth = 0) mustEqual
|
||||
square.testInteraction(Vector3(1,1,10), varDepth = 0)
|
||||
pool.testInteraction(Vector3(1,1, 9), varDepth = 1) mustEqual
|
||||
square.testInteraction(Vector3(1,1, 9), varDepth = 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class PieceOfEnvironmentTest extends Specification {
|
||||
"PieceOfEnvironment" should {
|
||||
import PieceOfEnvironment.testStepIntoInteraction
|
||||
val level = SeaLevel(10f)
|
||||
|
||||
"detect entering a critical region" in {
|
||||
testStepIntoInteraction(level, Vector3(0,0,9), Vector3(0,0,11), varDepth = 0).contains(true) mustEqual true
|
||||
}
|
||||
|
||||
"detect leaving a critical region" in {
|
||||
testStepIntoInteraction(level, Vector3(0,0,11), Vector3(0,0,9), varDepth = 0).contains(false) mustEqual true
|
||||
}
|
||||
|
||||
"not detect moving outside of a critical region" in {
|
||||
testStepIntoInteraction(level, Vector3(0,0,12), Vector3(0,0,11), varDepth = 0).isEmpty mustEqual true
|
||||
}
|
||||
|
||||
"not detect moving within a critical region" in {
|
||||
testStepIntoInteraction(level, Vector3(0,0,9), Vector3(0,0,8), varDepth = 0).isEmpty mustEqual true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object EnvironmentTest { }
|
||||
211
src/test/scala/objects/InteractsWithZoneEnvironmentTest.scala
Normal file
211
src/test/scala/objects/InteractsWithZoneEnvironmentTest.scala
Normal file
|
|
@ -0,0 +1,211 @@
|
|||
package objects
|
||||
|
||||
import akka.testkit.TestProbe
|
||||
import base.ActorTest
|
||||
import net.psforever.objects.definition.ObjectDefinition
|
||||
import net.psforever.objects.serverobject.PlanetSideServerObject
|
||||
import net.psforever.objects.serverobject.environment._
|
||||
import net.psforever.objects.vital.{Vitality, VitalityDefinition}
|
||||
import net.psforever.objects.zones.{Zone, ZoneMap}
|
||||
import net.psforever.types.{PlanetSideEmpire, Vector3}
|
||||
|
||||
import scala.concurrent.duration._
|
||||
|
||||
class InteractsWithZoneEnvironmentTest extends ActorTest {
|
||||
val pool1 = Pool(EnvironmentAttribute.Water, DeepSquare(-1, 10, 10, 0, 0))
|
||||
val pool2 = Pool(EnvironmentAttribute.Water, DeepSquare(-1, 10, 15, 5, 10))
|
||||
val pool3 = Pool(EnvironmentAttribute.Lava, DeepSquare(-1, 15, 10, 10, 5))
|
||||
val testZone = {
|
||||
val testMap = new ZoneMap(name = "test-map") {
|
||||
environment = List(pool1, pool2, pool3)
|
||||
}
|
||||
new Zone("test-zone", testMap, zoneNumber = 0)
|
||||
}
|
||||
|
||||
"InteractsWithZoneEnvironment" should {
|
||||
"not interact with any environment when it does not encroach any environment" in {
|
||||
val testProbe = TestProbe()
|
||||
val obj = InteractsWithZoneEnvironmentTest.testObject()
|
||||
obj.Zone = testZone
|
||||
obj.Actor = testProbe.ref
|
||||
|
||||
assert(obj.Position == Vector3.Zero)
|
||||
obj.zoneInteraction()
|
||||
testProbe.expectNoMessage(max = 500 milliseconds)
|
||||
}
|
||||
|
||||
"acknowledge interaction when moved into the critical region of a registered environment object (just once)" in {
|
||||
val testProbe = TestProbe()
|
||||
val obj = InteractsWithZoneEnvironmentTest.testObject()
|
||||
obj.Zone = testZone
|
||||
obj.Actor = testProbe.ref
|
||||
|
||||
obj.Position = Vector3(1,1,-2)
|
||||
obj.zoneInteraction()
|
||||
val msg = testProbe.receiveOne(max = 250 milliseconds)
|
||||
assert(
|
||||
msg match {
|
||||
case InteractWithEnvironment(o, b, _) => (o eq obj) && (b eq pool1)
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
obj.zoneInteraction()
|
||||
testProbe.expectNoMessage(max = 500 milliseconds)
|
||||
}
|
||||
|
||||
"acknowledge ceasation of interaction when moved out of a previous occupied the critical region (just once)" in {
|
||||
val testProbe = TestProbe()
|
||||
val obj = InteractsWithZoneEnvironmentTest.testObject()
|
||||
obj.Zone = testZone
|
||||
obj.Actor = testProbe.ref
|
||||
|
||||
obj.Position = Vector3(1,1,-2)
|
||||
obj.zoneInteraction()
|
||||
val msg1 = testProbe.receiveOne(max = 250 milliseconds)
|
||||
assert(
|
||||
msg1 match {
|
||||
case InteractWithEnvironment(o, b, _) => (o eq obj) && (b eq pool1)
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
|
||||
obj.Position = Vector3(1,1,1)
|
||||
obj.zoneInteraction()
|
||||
val msg2 = testProbe.receiveOne(max = 250 milliseconds)
|
||||
assert(
|
||||
msg2 match {
|
||||
case EscapeFromEnvironment(o, b, _) => (o eq obj) && (b eq pool1)
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
obj.zoneInteraction()
|
||||
testProbe.expectNoMessage(max = 500 milliseconds)
|
||||
}
|
||||
|
||||
"transition between two different critical regions when the regions that the same attribute" in {
|
||||
val testProbe = TestProbe()
|
||||
val obj = InteractsWithZoneEnvironmentTest.testObject()
|
||||
obj.Zone = testZone
|
||||
obj.Actor = testProbe.ref
|
||||
|
||||
obj.Position = Vector3(7,7,-2)
|
||||
obj.zoneInteraction()
|
||||
val msg1 = testProbe.receiveOne(max = 250 milliseconds)
|
||||
assert(
|
||||
msg1 match {
|
||||
case InteractWithEnvironment(o, b, _) => (o eq obj) && (b eq pool1)
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
|
||||
obj.Position = Vector3(12,7,-2)
|
||||
obj.zoneInteraction()
|
||||
val msg2 = testProbe.receiveOne(max = 250 milliseconds)
|
||||
assert(
|
||||
msg2 match {
|
||||
case InteractWithEnvironment(o, b, _) => (o eq obj) && (b eq pool2)
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
assert(pool1.attribute == pool2.attribute)
|
||||
}
|
||||
|
||||
"transition between two different critical regions when the regions have different attributes" in {
|
||||
val testProbe = TestProbe()
|
||||
val obj = InteractsWithZoneEnvironmentTest.testObject()
|
||||
obj.Zone = testZone
|
||||
obj.Actor = testProbe.ref
|
||||
|
||||
obj.Position = Vector3(7,7,-2)
|
||||
obj.zoneInteraction()
|
||||
val msg1 = testProbe.receiveOne(max = 250 milliseconds)
|
||||
assert(
|
||||
msg1 match {
|
||||
case InteractWithEnvironment(o, b, _) => (o eq obj) && (b eq pool1)
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
|
||||
obj.Position = Vector3(7,12,-2)
|
||||
obj.zoneInteraction()
|
||||
val msgs = testProbe.receiveN(2, max = 250 milliseconds)
|
||||
assert(
|
||||
msgs.head match {
|
||||
case EscapeFromEnvironment(o, b, _) => (o eq obj) && (b eq pool1)
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
assert(
|
||||
msgs(1) match {
|
||||
case InteractWithEnvironment(o, b, _) => (o eq obj) && (b eq pool3)
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
assert(pool1.attribute != pool3.attribute)
|
||||
}
|
||||
}
|
||||
|
||||
"when interactions are disallowed, end any current interaction" in {
|
||||
val testProbe = TestProbe()
|
||||
val obj = InteractsWithZoneEnvironmentTest.testObject()
|
||||
obj.Zone = testZone
|
||||
obj.Actor = testProbe.ref
|
||||
|
||||
obj.Position = Vector3(1,1,-2)
|
||||
obj.zoneInteraction()
|
||||
val msg1 = testProbe.receiveOne(max = 250 milliseconds)
|
||||
assert(
|
||||
msg1 match {
|
||||
case InteractWithEnvironment(o, b, _) => (o eq obj) && (b eq pool1)
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
|
||||
obj.allowZoneEnvironmentInteractions = false
|
||||
val msg2 = testProbe.receiveOne(max = 250 milliseconds)
|
||||
assert(
|
||||
msg2 match {
|
||||
case EscapeFromEnvironment(o, b, _) => (o eq obj) && (b eq pool1)
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
obj.zoneInteraction()
|
||||
testProbe.expectNoMessage(max = 500 milliseconds)
|
||||
}
|
||||
|
||||
"when interactions are allowed, after having been disallowed, engage in any detected interaction" in {
|
||||
val testProbe = TestProbe()
|
||||
val obj = InteractsWithZoneEnvironmentTest.testObject()
|
||||
obj.Zone = testZone
|
||||
obj.Actor = testProbe.ref
|
||||
|
||||
obj.allowZoneEnvironmentInteractions = false
|
||||
obj.Position = Vector3(1,1,-2)
|
||||
obj.zoneInteraction()
|
||||
testProbe.expectNoMessage(max = 500 milliseconds)
|
||||
|
||||
obj.allowZoneEnvironmentInteractions = true
|
||||
val msg1 = testProbe.receiveOne(max = 250 milliseconds)
|
||||
assert(
|
||||
msg1 match {
|
||||
case InteractWithEnvironment(o, b, _) => (o eq obj) && (b eq pool1)
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
object InteractsWithZoneEnvironmentTest {
|
||||
def testObject(): PlanetSideServerObject with InteractsWithZoneEnvironment = {
|
||||
new PlanetSideServerObject
|
||||
with InteractsWithZoneEnvironment
|
||||
with Vitality {
|
||||
def Faction: PlanetSideEmpire.Value = PlanetSideEmpire.VS
|
||||
def DamageModel = null
|
||||
def Definition: ObjectDefinition with VitalityDefinition = new ObjectDefinition(objectId = 0) with VitalityDefinition {
|
||||
Damageable = true
|
||||
DrownAtMaxDepth = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,11 @@
|
|||
// Copyright (c) 2020 PSForever
|
||||
package objects
|
||||
/*
|
||||
import akka.actor.Props
|
||||
|
||||
import akka.actor.typed.ActorRef
|
||||
import akka.actor.{ActorSystem, Props}
|
||||
import akka.testkit.TestProbe
|
||||
import base.ActorTest
|
||||
import net.psforever.actors.session.AvatarActor
|
||||
import net.psforever.objects.avatar.{Avatar, PlayerControl}
|
||||
import net.psforever.objects.ballistics._
|
||||
import net.psforever.objects.guid.NumberPoolHub
|
||||
|
|
@ -12,38 +14,41 @@ import net.psforever.objects.vital.Vitality
|
|||
import net.psforever.objects.zones.{Zone, ZoneMap}
|
||||
import net.psforever.objects._
|
||||
import net.psforever.objects.serverobject.CommonMessages
|
||||
import net.psforever.objects.serverobject.environment.{DeepSquare, EnvironmentAttribute, OxygenStateTarget, Pool}
|
||||
import net.psforever.objects.vital.base.DamageResolution
|
||||
import net.psforever.objects.vital.interaction.DamageInteraction
|
||||
import net.psforever.objects.vital.projectile.ProjectileReason
|
||||
import net.psforever.packet.game._
|
||||
import net.psforever.types._
|
||||
import net.psforever.services.Service
|
||||
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||
|
||||
import scala.concurrent.duration._
|
||||
|
||||
class PlayerControlHealTest extends ActorTest {
|
||||
val player1 =
|
||||
Player(Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)) //guid=1
|
||||
val player2 =
|
||||
Player(Avatar(1, "TestCharacter2", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)) //guid=2
|
||||
val avatarProbe = TestProbe()
|
||||
val guid = new NumberPoolHub(new MaxNumberSource(15))
|
||||
val zone = new Zone("test", new ZoneMap("test"), 0) {
|
||||
override def SetupNumberPools() = {}
|
||||
GUID(guid)
|
||||
override def LivePlayers = List(player1, player2)
|
||||
override def AvatarEvents = avatarProbe.ref
|
||||
}
|
||||
val avatarProbe = TestProbe()
|
||||
zone.AvatarEvents = avatarProbe.ref
|
||||
|
||||
val player1 =
|
||||
Player(Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)) //guid=1
|
||||
player1.Zone = zone
|
||||
player1.Spawn()
|
||||
player1.Position = Vector3(2, 0, 0)
|
||||
guid.register(player1.avatar.locker, 5)
|
||||
player1.Actor = system.actorOf(Props(classOf[PlayerControl], player1), "player1-control")
|
||||
val player2 =
|
||||
Player(Avatar(0, "TestCharacter2", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)) //guid=2
|
||||
player1.Actor = system.actorOf(Props(classOf[PlayerControl], player1, null), "player1-control")
|
||||
player2.Zone = zone
|
||||
player2.Spawn()
|
||||
guid.register(player2.avatar.locker, 6)
|
||||
player2.Actor = system.actorOf(Props(classOf[PlayerControl], player2), "player2-control")
|
||||
player2.Actor = system.actorOf(Props(classOf[PlayerControl], player2, null), "player2-control")
|
||||
|
||||
val tool = Tool(GlobalDefinitions.medicalapplicator) //guid=3 & 4
|
||||
|
||||
guid.register(player1, 1)
|
||||
guid.register(player2, 2)
|
||||
guid.register(tool, 3)
|
||||
|
|
@ -104,26 +109,25 @@ class PlayerControlHealTest extends ActorTest {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
class PlayerControlHealSelfTest extends ActorTest {
|
||||
val player1 =
|
||||
Player(Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)) //guid=1
|
||||
val avatarProbe = TestProbe()
|
||||
val guid = new NumberPoolHub(new MaxNumberSource(15))
|
||||
val zone = new Zone("test", new ZoneMap("test"), 0) {
|
||||
override def SetupNumberPools() = {}
|
||||
GUID(guid)
|
||||
override def LivePlayers = List(player1)
|
||||
override def AvatarEvents = avatarProbe.ref
|
||||
}
|
||||
val avatarProbe = TestProbe()
|
||||
zone.AvatarEvents = avatarProbe.ref
|
||||
|
||||
val player1 =
|
||||
Player(Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)) //guid=1
|
||||
player1.Zone = zone
|
||||
player1.Spawn()
|
||||
player1.Position = Vector3(2, 0, 0)
|
||||
guid.register(player1.avatar.locker, 5)
|
||||
player1.Actor = system.actorOf(Props(classOf[PlayerControl], player1), "player1-control")
|
||||
player1.Actor = system.actorOf(Props(classOf[PlayerControl], player1, null), "player1-control")
|
||||
|
||||
val tool = Tool(GlobalDefinitions.medicalapplicator) //guid=3 & 4
|
||||
|
||||
guid.register(player1, 1)
|
||||
guid.register(tool, 3)
|
||||
guid.register(tool.AmmoSlot.Box, 4)
|
||||
|
|
@ -181,30 +185,30 @@ class PlayerControlHealSelfTest extends ActorTest {
|
|||
}
|
||||
|
||||
class PlayerControlRepairTest extends ActorTest {
|
||||
val player1 =
|
||||
Player(Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)) //guid=1
|
||||
val player2 =
|
||||
Player(Avatar(1, "TestCharacter2", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)) //guid=2
|
||||
val avatarProbe = TestProbe()
|
||||
val guid = new NumberPoolHub(new MaxNumberSource(15))
|
||||
val zone = new Zone("test", new ZoneMap("test"), 0) {
|
||||
override def SetupNumberPools() = {}
|
||||
GUID(guid)
|
||||
override def LivePlayers = List(player1, player2)
|
||||
override def AvatarEvents = avatarProbe.ref
|
||||
}
|
||||
val avatarProbe = TestProbe()
|
||||
zone.AvatarEvents = avatarProbe.ref
|
||||
|
||||
val player1 =
|
||||
Player(Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)) //guid=1
|
||||
player1.Zone = zone
|
||||
player1.Spawn()
|
||||
player1.Position = Vector3(2, 0, 0)
|
||||
guid.register(player1.avatar.locker, 5)
|
||||
player1.Actor = system.actorOf(Props(classOf[PlayerControl], player1), "player1-control")
|
||||
val player2 =
|
||||
Player(Avatar(0, "TestCharacter2", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)) //guid=2
|
||||
player1.Actor = system.actorOf(Props(classOf[PlayerControl], player1, null), "player1-control")
|
||||
player2.Zone = zone
|
||||
player2.Spawn()
|
||||
guid.register(player2.avatar.locker, 6)
|
||||
player2.Actor = system.actorOf(Props(classOf[PlayerControl], player2), "player2-control")
|
||||
player2.Actor = system.actorOf(Props(classOf[PlayerControl], player2, null), "player2-control")
|
||||
|
||||
val tool = Tool(GlobalDefinitions.bank) //guid=3 & 4
|
||||
|
||||
guid.register(player1, 1)
|
||||
guid.register(player2, 2)
|
||||
guid.register(tool, 3)
|
||||
|
|
@ -277,24 +281,24 @@ class PlayerControlRepairTest extends ActorTest {
|
|||
}
|
||||
|
||||
class PlayerControlRepairSelfTest extends ActorTest {
|
||||
val player1 =
|
||||
Player(Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)) //guid=1
|
||||
val avatarProbe = TestProbe()
|
||||
val guid = new NumberPoolHub(new MaxNumberSource(15))
|
||||
val zone = new Zone("test", new ZoneMap("test"), 0) {
|
||||
override def SetupNumberPools() = {}
|
||||
GUID(guid)
|
||||
override def LivePlayers = List(player1)
|
||||
override def AvatarEvents = avatarProbe.ref
|
||||
}
|
||||
val avatarProbe = TestProbe()
|
||||
zone.AvatarEvents = avatarProbe.ref
|
||||
|
||||
val player1 =
|
||||
Player(Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)) //guid=1
|
||||
player1.Zone = zone
|
||||
player1.Spawn()
|
||||
player1.Position = Vector3(2, 0, 0)
|
||||
guid.register(player1.avatar.locker, 5)
|
||||
player1.Actor = system.actorOf(Props(classOf[PlayerControl], player1), "player1-control")
|
||||
player1.Actor = system.actorOf(Props(classOf[PlayerControl], player1, null), "player1-control")
|
||||
|
||||
val tool = Tool(GlobalDefinitions.bank) //guid=3 & 4
|
||||
|
||||
guid.register(player1, 1)
|
||||
guid.register(tool, 3)
|
||||
guid.register(tool.AmmoSlot.Box, 4)
|
||||
|
|
@ -352,40 +356,44 @@ class PlayerControlRepairSelfTest extends ActorTest {
|
|||
}
|
||||
|
||||
class PlayerControlDamageTest extends ActorTest {
|
||||
val player1 =
|
||||
Player(Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)) //guid=1
|
||||
val player2 =
|
||||
Player(Avatar(1, "TestCharacter2", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)) //guid=2
|
||||
val avatarProbe = TestProbe()
|
||||
val activityProbe = TestProbe()
|
||||
val guid = new NumberPoolHub(new MaxNumberSource(15))
|
||||
val zone = new Zone("test", new ZoneMap("test"), 0) {
|
||||
override def SetupNumberPools() = {}
|
||||
GUID(guid)
|
||||
override def LivePlayers = List(player1, player2)
|
||||
override def AvatarEvents = avatarProbe.ref
|
||||
override def Activity = activityProbe.ref
|
||||
}
|
||||
val activityProbe = TestProbe()
|
||||
val avatarProbe = TestProbe()
|
||||
zone.Activity = activityProbe.ref
|
||||
zone.AvatarEvents = avatarProbe.ref
|
||||
val player1 =
|
||||
Player(Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)) //guid=1
|
||||
|
||||
player1.Zone = zone
|
||||
player1.Spawn()
|
||||
player1.Position = Vector3(2, 0, 0)
|
||||
guid.register(player1.avatar.locker, 5)
|
||||
player1.Actor = system.actorOf(Props(classOf[PlayerControl], player1), "player1-control")
|
||||
val player2 =
|
||||
Player(Avatar(0, "TestCharacter2", PlanetSideEmpire.NC, CharacterGender.Male, 0, CharacterVoice.Mute)) //guid=2
|
||||
player1.Actor = system.actorOf(Props(classOf[PlayerControl], player1, null), name = "player1-control")
|
||||
player2.Zone = zone
|
||||
player2.Spawn()
|
||||
guid.register(player2.avatar.locker, 6)
|
||||
player2.Actor = system.actorOf(Props(classOf[PlayerControl], player2), "player2-control")
|
||||
val (probe, avatarActor) = PlayerControlTest.DummyAvatar(system)
|
||||
player2.Actor = system.actorOf(Props(classOf[PlayerControl], player2, avatarActor), name = "player2-control")
|
||||
|
||||
val tool = Tool(GlobalDefinitions.suppressor) //guid 3 & 4
|
||||
val projectile = tool.Projectile
|
||||
val playerSource = SourceEntry(player2)
|
||||
val player1Source = PlayerSource(player1)
|
||||
val resolved = DamageInteraction(
|
||||
playerSource,
|
||||
SourceEntry(player2),
|
||||
ProjectileReason(
|
||||
DamageResolution.Hit,
|
||||
Projectile(
|
||||
projectile,
|
||||
tool.Definition,
|
||||
tool.FireMode,
|
||||
PlayerSource(player1),
|
||||
player1Source,
|
||||
0,
|
||||
Vector3(2, 0, 0),
|
||||
Vector3(-1, 0, 0)
|
||||
|
|
@ -400,12 +408,14 @@ class PlayerControlDamageTest extends ActorTest {
|
|||
guid.register(tool, 3)
|
||||
guid.register(tool.AmmoSlot.Box, 4)
|
||||
expectNoMessage(200 milliseconds)
|
||||
|
||||
"PlayerControl" should {
|
||||
"handle damage" in {
|
||||
assert(player2.Health == player2.Definition.DefaultHealth)
|
||||
assert(player2.Armor == player2.MaxArmor)
|
||||
player2.Actor ! Vitality.Damage(applyDamageTo)
|
||||
val msg_avatar = avatarProbe.receiveN(4, 500 milliseconds)
|
||||
val msg_avatar = avatarProbe.receiveN(3, 500 milliseconds)
|
||||
val msg_stamina = probe.receiveOne(500 milliseconds)
|
||||
val msg_activity = activityProbe.receiveOne(200 milliseconds)
|
||||
assert(
|
||||
msg_avatar.head match {
|
||||
|
|
@ -414,13 +424,13 @@ class PlayerControlDamageTest extends ActorTest {
|
|||
}
|
||||
)
|
||||
assert(
|
||||
msg_avatar(1) match {
|
||||
case AvatarServiceMessage("TestCharacter2", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 2, _)) => true
|
||||
case _ => false
|
||||
msg_stamina match {
|
||||
case AvatarActor.ConsumeStamina(_) => true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
assert(
|
||||
msg_avatar(2) match {
|
||||
msg_avatar(1) match {
|
||||
case AvatarServiceMessage("test", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 0, _)) => true
|
||||
case _ => false
|
||||
}
|
||||
|
|
@ -428,17 +438,17 @@ class PlayerControlDamageTest extends ActorTest {
|
|||
assert(
|
||||
msg_activity match {
|
||||
case activity: Zone.HotSpot.Activity =>
|
||||
activity.attacker == PlayerSource(player1) &&
|
||||
activity.defender == playerSource &&
|
||||
activity.attacker == player1Source &&
|
||||
activity.defender == PlayerSource(player2) &&
|
||||
activity.location == Vector3(1, 0, 0)
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
assert(
|
||||
msg_avatar(3) match {
|
||||
msg_avatar(2) match {
|
||||
case AvatarServiceMessage(
|
||||
"TestCharacter2",
|
||||
AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(17, Vector3(2, 0, 0)))
|
||||
AvatarAction.HitHint(PlanetSideGUID(1), PlanetSideGUID(2))
|
||||
) =>
|
||||
true
|
||||
case _ => false
|
||||
|
|
@ -451,39 +461,49 @@ class PlayerControlDamageTest extends ActorTest {
|
|||
}
|
||||
|
||||
class PlayerControlDeathStandingTest extends ActorTest {
|
||||
val player1 =
|
||||
Player(Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)) //guid=1
|
||||
val player2 =
|
||||
Player(Avatar(1, "TestCharacter2", PlanetSideEmpire.NC, CharacterGender.Male, 0, CharacterVoice.Mute)) //guid=2
|
||||
val avatarProbe = TestProbe()
|
||||
val activityProbe = TestProbe()
|
||||
val guid = new NumberPoolHub(new MaxNumberSource(15))
|
||||
val zone = new Zone("test", new ZoneMap("test"), 0) {
|
||||
override def SetupNumberPools() = {}
|
||||
GUID(guid)
|
||||
override def LivePlayers = List(player1, player2)
|
||||
override def AvatarEvents = avatarProbe.ref
|
||||
override def Activity = activityProbe.ref
|
||||
}
|
||||
val avatarProbe = TestProbe()
|
||||
zone.AvatarEvents = avatarProbe.ref
|
||||
val activityProbe = TestProbe()
|
||||
zone.Activity = activityProbe.ref
|
||||
|
||||
val player1 =
|
||||
Player(Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)) //guid=1
|
||||
player1.Zone = zone
|
||||
player1.Spawn()
|
||||
player1.Position = Vector3(2, 0, 0)
|
||||
guid.register(player1.avatar.locker, 5)
|
||||
player1.Actor = system.actorOf(Props(classOf[PlayerControl], player1), "player1-control")
|
||||
val player2 =
|
||||
Player(Avatar(0, "TestCharacter2", PlanetSideEmpire.NC, CharacterGender.Male, 0, CharacterVoice.Mute)) //guid=2
|
||||
player1.Actor = system.actorOf(Props(classOf[PlayerControl], player1, null), name = "player1-control")
|
||||
player2.Zone = zone
|
||||
player2.Spawn()
|
||||
guid.register(player2.avatar.locker, 6)
|
||||
player2.Actor = system.actorOf(Props(classOf[PlayerControl], player2), "player2-control")
|
||||
val (probe, avatarActor) = PlayerControlTest.DummyAvatar(system)
|
||||
player2.Actor = system.actorOf(Props(classOf[PlayerControl], player2, avatarActor), name = "player2-control")
|
||||
|
||||
val tool = Tool(GlobalDefinitions.suppressor) //guid 3 & 4
|
||||
val projectile = tool.Projectile
|
||||
val player1Source = SourceEntry(player1)
|
||||
val tool = Tool(GlobalDefinitions.suppressor) //guid 3 & 4
|
||||
val projectile = tool.Projectile
|
||||
val player1Source = PlayerSource(player1)
|
||||
val resolved = DamageInteraction(
|
||||
SourceEntry(player2),
|
||||
ProjectileReason(
|
||||
DamageResolution.Hit,
|
||||
Projectile(projectile, tool.Definition, tool.FireMode, player1Source, 0, Vector3(2, 0, 0), Vector3(-1, 0, 0)),
|
||||
player2.DamageModel
|
||||
Projectile(
|
||||
projectile,
|
||||
tool.Definition,
|
||||
tool.FireMode,
|
||||
player1Source,
|
||||
0,
|
||||
Vector3(2, 0, 0),
|
||||
Vector3(-1, 0, 0)
|
||||
),
|
||||
player1.DamageModel
|
||||
),
|
||||
Vector3(1, 0, 0)
|
||||
)
|
||||
|
|
@ -506,7 +526,8 @@ class PlayerControlDeathStandingTest extends ActorTest {
|
|||
assert(player2.isAlive)
|
||||
|
||||
player2.Actor ! Vitality.Damage(applyDamageTo)
|
||||
val msg_avatar = avatarProbe.receiveN(8, 500 milliseconds)
|
||||
val msg_avatar = avatarProbe.receiveN(7, 500 milliseconds)
|
||||
val msg_stamina = probe.receiveOne(500 milliseconds)
|
||||
activityProbe.expectNoMessage(200 milliseconds)
|
||||
assert(
|
||||
msg_avatar.head match {
|
||||
|
|
@ -515,43 +536,42 @@ class PlayerControlDeathStandingTest extends ActorTest {
|
|||
}
|
||||
)
|
||||
assert(
|
||||
msg_avatar(1) match {
|
||||
case AvatarServiceMessage("TestCharacter2", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 2, _)) =>
|
||||
true
|
||||
case _ => false
|
||||
msg_stamina match {
|
||||
case AvatarActor.DeinitializeImplants() => true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
assert(
|
||||
msg_avatar(2) match {
|
||||
msg_avatar(1) match {
|
||||
case AvatarServiceMessage("TestCharacter2", AvatarAction.Killed(PlanetSideGUID(2), None)) => true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
assert(
|
||||
msg_avatar(3) match {
|
||||
msg_avatar(2) match {
|
||||
case AvatarServiceMessage("test", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 0, _)) => true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
assert(
|
||||
msg_avatar(4) match {
|
||||
msg_avatar(3) match {
|
||||
case AvatarServiceMessage("TestCharacter2", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 7, _)) =>
|
||||
true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
assert(
|
||||
msg_avatar(5) match {
|
||||
msg_avatar(4) match {
|
||||
case AvatarServiceMessage(
|
||||
"TestCharacter2",
|
||||
AvatarAction.SendResponse(_, DestroyMessage(PlanetSideGUID(2), PlanetSideGUID(2), _, Vector3.Zero))
|
||||
AvatarAction.SendResponse(_, DestroyMessage(PlanetSideGUID(2), PlanetSideGUID(1), _, _))
|
||||
) =>
|
||||
true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
assert(
|
||||
msg_avatar(6) match {
|
||||
msg_avatar(5) match {
|
||||
case AvatarServiceMessage(
|
||||
"TestCharacter2",
|
||||
AvatarAction.SendResponse(
|
||||
|
|
@ -564,7 +584,7 @@ class PlayerControlDeathStandingTest extends ActorTest {
|
|||
}
|
||||
)
|
||||
assert(
|
||||
msg_avatar(7) match {
|
||||
msg_avatar(6) match {
|
||||
case AvatarServiceMessage("test", AvatarAction.DestroyDisplay(killer, victim, _, _))
|
||||
if killer.Name.equals(player1.Name) && victim.Name.equals(player2.Name) =>
|
||||
true
|
||||
|
|
@ -579,51 +599,61 @@ class PlayerControlDeathStandingTest extends ActorTest {
|
|||
}
|
||||
|
||||
class PlayerControlDeathSeatedTest extends ActorTest {
|
||||
val player1 =
|
||||
Player(Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)) //guid=1
|
||||
val player2 =
|
||||
Player(Avatar(1, "TestCharacter2", PlanetSideEmpire.NC, CharacterGender.Male, 0, CharacterVoice.Mute)) //guid=2
|
||||
val avatarProbe = TestProbe()
|
||||
val activityProbe = TestProbe()
|
||||
val guid = new NumberPoolHub(new MaxNumberSource(15))
|
||||
val zone = new Zone("test", new ZoneMap("test"), 0) {
|
||||
override def SetupNumberPools() = {}
|
||||
GUID(guid)
|
||||
override def LivePlayers = List(player1, player2)
|
||||
override def AvatarEvents = avatarProbe.ref
|
||||
override def Activity = activityProbe.ref
|
||||
}
|
||||
val avatarProbe = TestProbe()
|
||||
zone.AvatarEvents = avatarProbe.ref
|
||||
val activityProbe = TestProbe()
|
||||
zone.Activity = activityProbe.ref
|
||||
|
||||
val player1 =
|
||||
Player(Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)) //guid=1
|
||||
player1.Zone = zone
|
||||
player1.Spawn()
|
||||
player1.Position = Vector3(2, 0, 0)
|
||||
guid.register(player1.avatar.locker, 6)
|
||||
player1.Actor = system.actorOf(Props(classOf[PlayerControl], player1), "player1-control")
|
||||
val player2 =
|
||||
Player(Avatar(0, "TestCharacter2", PlanetSideEmpire.NC, CharacterGender.Male, 0, CharacterVoice.Mute)) //guid=2
|
||||
guid.register(player1.avatar.locker, 5)
|
||||
player1.Actor = system.actorOf(Props(classOf[PlayerControl], player1, null), name = "player1-control")
|
||||
player2.Zone = zone
|
||||
player2.Spawn()
|
||||
guid.register(player2.avatar.locker, 7)
|
||||
player2.Actor = system.actorOf(Props(classOf[PlayerControl], player2), "player2-control")
|
||||
guid.register(player2.avatar.locker, 6)
|
||||
val (probe, avatarActor) = PlayerControlTest.DummyAvatar(system)
|
||||
player2.Actor = system.actorOf(Props(classOf[PlayerControl], player2, avatarActor), name = "player2-control")
|
||||
|
||||
val tool = Tool(GlobalDefinitions.suppressor) //guid 3 & 4
|
||||
val vehicle = Vehicle(GlobalDefinitions.quadstealth) //guid=5
|
||||
vehicle.Faction = player2.Faction
|
||||
|
||||
val tool = Tool(GlobalDefinitions.suppressor) //guid 3 & 4
|
||||
val projectile = tool.Projectile
|
||||
val player1Source = SourceEntry(player1)
|
||||
val resolved = DamageInteraction(
|
||||
SourceEntry(player2),
|
||||
ProjectileReason(
|
||||
DamageResolution.Hit,
|
||||
Projectile(projectile, tool.Definition, tool.FireMode, player1Source, 0, Vector3(2, 0, 0), Vector3(-1, 0, 0)),
|
||||
player2.DamageMode
|
||||
),
|
||||
Vector3(1, 0, 0)
|
||||
)
|
||||
val applyDamageTo = resolved.calculate()
|
||||
guid.register(player1, 1)
|
||||
guid.register(player2, 2)
|
||||
guid.register(tool, 3)
|
||||
guid.register(tool.AmmoSlot.Box, 4)
|
||||
guid.register(vehicle, 5)
|
||||
guid.register(vehicle, 7)
|
||||
val projectile = tool.Projectile
|
||||
val player1Source = PlayerSource(player1)
|
||||
val resolved = DamageInteraction(
|
||||
SourceEntry(player2),
|
||||
ProjectileReason(
|
||||
DamageResolution.Hit,
|
||||
Projectile(
|
||||
projectile,
|
||||
tool.Definition,
|
||||
tool.FireMode,
|
||||
player1Source,
|
||||
0,
|
||||
Vector3(2, 0, 0),
|
||||
Vector3(-1, 0, 0)
|
||||
),
|
||||
player1.DamageModel
|
||||
),
|
||||
Vector3(1, 0, 0)
|
||||
)
|
||||
val applyDamageTo = resolved.calculate()
|
||||
expectNoMessage(200 milliseconds)
|
||||
|
||||
"PlayerControl" should {
|
||||
|
|
@ -636,11 +666,21 @@ class PlayerControlDeathSeatedTest extends ActorTest {
|
|||
assert(player2.isAlive)
|
||||
|
||||
player2.Actor ! Vitality.Damage(applyDamageTo)
|
||||
val msg_avatar = avatarProbe.receiveN(9, 500 milliseconds)
|
||||
val msg_avatar = avatarProbe.receiveN(8, 500 milliseconds)
|
||||
val msg_stamina = probe.receiveOne(500 milliseconds)
|
||||
activityProbe.expectNoMessage(200 milliseconds)
|
||||
assert(
|
||||
msg_stamina match {
|
||||
case AvatarActor.DeinitializeImplants() => true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
assert(
|
||||
msg_avatar.head match {
|
||||
case AvatarServiceMessage("TestCharacter2", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 2, _)) =>
|
||||
case AvatarServiceMessage(
|
||||
"TestCharacter2",
|
||||
AvatarAction.Killed(PlanetSideGUID(2), Some(PlanetSideGUID(7)))
|
||||
) =>
|
||||
true
|
||||
case _ => false
|
||||
}
|
||||
|
|
@ -649,7 +689,7 @@ class PlayerControlDeathSeatedTest extends ActorTest {
|
|||
msg_avatar(1) match {
|
||||
case AvatarServiceMessage(
|
||||
"TestCharacter2",
|
||||
AvatarAction.Killed(PlanetSideGUID(2), Some(PlanetSideGUID(5)))
|
||||
AvatarAction.SendResponse(_, ObjectDetachMessage(PlanetSideGUID(7), PlanetSideGUID(2), _, _, _, _))
|
||||
) =>
|
||||
true
|
||||
case _ => false
|
||||
|
|
@ -657,16 +697,6 @@ class PlayerControlDeathSeatedTest extends ActorTest {
|
|||
)
|
||||
assert(
|
||||
msg_avatar(2) match {
|
||||
case AvatarServiceMessage(
|
||||
"TestCharacter2",
|
||||
AvatarAction.SendResponse(_, ObjectDetachMessage(PlanetSideGUID(5), PlanetSideGUID(2), _, _, _, _))
|
||||
) =>
|
||||
true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
assert(
|
||||
msg_avatar(3) match {
|
||||
case AvatarServiceMessage(
|
||||
"TestCharacter2",
|
||||
AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 29, 1)
|
||||
|
|
@ -676,29 +706,29 @@ class PlayerControlDeathSeatedTest extends ActorTest {
|
|||
}
|
||||
)
|
||||
assert(
|
||||
msg_avatar(4) match {
|
||||
msg_avatar(3) match {
|
||||
case AvatarServiceMessage("test", AvatarAction.ObjectDelete(PlanetSideGUID(2), PlanetSideGUID(2), _)) => true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
assert(
|
||||
msg_avatar(5) match {
|
||||
msg_avatar(4) match {
|
||||
case AvatarServiceMessage("test", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 0, _)) => true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
assert(
|
||||
msg_avatar(6) match {
|
||||
msg_avatar(5) match {
|
||||
case AvatarServiceMessage(
|
||||
"TestCharacter2",
|
||||
AvatarAction.SendResponse(_, DestroyMessage(PlanetSideGUID(2), PlanetSideGUID(2), _, Vector3.Zero))
|
||||
AvatarAction.SendResponse(_, DestroyMessage(PlanetSideGUID(2), PlanetSideGUID(1), _, _))
|
||||
) =>
|
||||
true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
assert(
|
||||
msg_avatar(7) match {
|
||||
msg_avatar(6) match {
|
||||
case AvatarServiceMessage(
|
||||
"TestCharacter2",
|
||||
AvatarAction.SendResponse(
|
||||
|
|
@ -711,7 +741,7 @@ class PlayerControlDeathSeatedTest extends ActorTest {
|
|||
}
|
||||
)
|
||||
assert(
|
||||
msg_avatar(8) match {
|
||||
msg_avatar(7) match {
|
||||
case AvatarServiceMessage("test", AvatarAction.DestroyDisplay(killer, victim, _, _))
|
||||
if killer.Name.equals(player1.Name) && victim.Name.equals(player2.Name) =>
|
||||
true
|
||||
|
|
@ -724,7 +754,235 @@ class PlayerControlDeathSeatedTest extends ActorTest {
|
|||
}
|
||||
}
|
||||
|
||||
object PlayerControlTest {}
|
||||
class PlayerControlInteractWithWaterTest extends ActorTest {
|
||||
val player1 =
|
||||
Player(Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)) //guid=1
|
||||
val avatarProbe = TestProbe()
|
||||
val guid = new NumberPoolHub(new MaxNumberSource(15))
|
||||
val pool = Pool(EnvironmentAttribute.Water, DeepSquare(-1, 10, 10, 0, 0))
|
||||
val zone = new Zone(
|
||||
id = "test",
|
||||
new ZoneMap(name = "test-map") {
|
||||
environment = List(pool)
|
||||
},
|
||||
zoneNumber = 0
|
||||
) {
|
||||
override def SetupNumberPools() = {}
|
||||
GUID(guid)
|
||||
override def LivePlayers = List(player1)
|
||||
override def AvatarEvents = avatarProbe.ref
|
||||
}
|
||||
|
||||
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.zoneInteraction() //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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class PlayerControlStopInteractWithWaterTest extends ActorTest {
|
||||
val player1 =
|
||||
Player(Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)) //guid=1
|
||||
val avatarProbe = TestProbe()
|
||||
val guid = new NumberPoolHub(new MaxNumberSource(15))
|
||||
val pool = Pool(EnvironmentAttribute.Water, DeepSquare(-1, 10, 10, 0, 0))
|
||||
val zone = new Zone(
|
||||
id = "test",
|
||||
new ZoneMap(name = "test-map") {
|
||||
environment = List(pool)
|
||||
},
|
||||
zoneNumber = 0
|
||||
) {
|
||||
override def SetupNumberPools() = {}
|
||||
GUID(guid)
|
||||
override def LivePlayers = List(player1)
|
||||
override def AvatarEvents = avatarProbe.ref
|
||||
}
|
||||
|
||||
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 {
|
||||
"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.zoneInteraction() //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.zoneInteraction() //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 {
|
||||
val player1 =
|
||||
Player(Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)) //guid=1
|
||||
val avatarProbe = TestProbe()
|
||||
val guid = new NumberPoolHub(new MaxNumberSource(15))
|
||||
val pool = Pool(EnvironmentAttribute.Lava, DeepSquare(-1, 10, 10, 0, 0))
|
||||
val zone = new Zone(
|
||||
id = "test-map",
|
||||
new ZoneMap(name = "test-map") {
|
||||
environment = List(pool)
|
||||
},
|
||||
zoneNumber = 0
|
||||
) {
|
||||
override def SetupNumberPools() = {}
|
||||
GUID(guid)
|
||||
override def LivePlayers = List(player1)
|
||||
override def AvatarEvents = avatarProbe.ref
|
||||
override def Activity = TestProbe().ref
|
||||
}
|
||||
|
||||
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 {
|
||||
"take continuous damage if player steps into lava" in {
|
||||
assert(player1.Health == 100) //alive
|
||||
player1.Position = Vector3(5,5,-3) //right in the pool
|
||||
player1.zoneInteraction() //trigger
|
||||
|
||||
val msg_burn = avatarProbe.receiveN(3, 1 seconds)
|
||||
assert(
|
||||
msg_burn.head match {
|
||||
case AvatarServiceMessage("test-map", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(1), 0, _)) => true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
assert(
|
||||
msg_burn(1) match {
|
||||
case AvatarServiceMessage("TestCharacter1", AvatarAction.EnvironmentalDamage(PlanetSideGUID(1), _, _)) => true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
assert(
|
||||
msg_burn(2) match {
|
||||
case AvatarServiceMessage("test-map", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(1), 54, _)) => true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
assert(player1.Health > 0) //still alive?
|
||||
probe.receiveOne(65 seconds) //wait until player1's implants deinitialize
|
||||
assert(player1.Health == 0) //ded
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class PlayerControlInteractWithDeathTest extends ActorTest {
|
||||
val player1 =
|
||||
Player(Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)) //guid=1
|
||||
val avatarProbe = TestProbe()
|
||||
val guid = new NumberPoolHub(new MaxNumberSource(15))
|
||||
val pool = Pool(EnvironmentAttribute.Death, DeepSquare(-1, 10, 10, 0, 0))
|
||||
val zone = new Zone(
|
||||
id = "test-map",
|
||||
new ZoneMap(name = "test-map") {
|
||||
environment = List(pool)
|
||||
},
|
||||
zoneNumber = 0
|
||||
) {
|
||||
override def SetupNumberPools() = {}
|
||||
GUID(guid)
|
||||
override def LivePlayers = List(player1)
|
||||
override def AvatarEvents = avatarProbe.ref
|
||||
override def Activity = TestProbe().ref
|
||||
}
|
||||
|
||||
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 {
|
||||
"take continuous damage if player steps into a pool of death" in {
|
||||
assert(player1.Health == 100) //alive
|
||||
player1.Position = Vector3(5,5,-3) //right in the pool
|
||||
player1.zoneInteraction() //trigger
|
||||
|
||||
probe.receiveOne(250 milliseconds) //wait until oplayer1's implants deinitialize
|
||||
assert(player1.Health == 0) //ded
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object PlayerControlTest {
|
||||
/**
|
||||
* A `TestProbe` whose `ActorRef` is packaged as a return type with it
|
||||
* and is passable as a typed `AvatarActor.Command` `Behavior` object.
|
||||
* Used for spawning `PlayControl` `Actor` objects with a refence to the `AvatarActor`,
|
||||
* when messaging callback renders it necessary during tests
|
||||
* but when accurate responses are unnecessary to emulate.
|
||||
* @param system what we use to spawn the `Actor`
|
||||
* @return the resulting probe, and it's modified `ActorRef`
|
||||
*/
|
||||
def DummyAvatar(system: ActorSystem): (TestProbe, ActorRef[AvatarActor.Command]) = {
|
||||
import akka.actor.typed.scaladsl.adapter.ClassicActorRefOps
|
||||
val probe = new TestProbe(system)
|
||||
val actor = ClassicActorRefOps(probe.ref).toTyped[AvatarActor.Command]
|
||||
(probe, actor)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
978
src/test/scala/objects/VehicleControlTest.scala
Normal file
978
src/test/scala/objects/VehicleControlTest.scala
Normal file
|
|
@ -0,0 +1,978 @@
|
|||
// Copyright (c) 2020 PSForever
|
||||
package objects
|
||||
|
||||
import akka.actor.Props
|
||||
import akka.actor.typed.scaladsl.adapter._
|
||||
import akka.testkit.TestProbe
|
||||
import base.{ActorTest, FreedContextActorTest}
|
||||
import net.psforever.actors.zone.ZoneActor
|
||||
import net.psforever.objects.avatar.{Avatar, PlayerControl}
|
||||
import net.psforever.objects.{GlobalDefinitions, Player, Vehicle}
|
||||
import net.psforever.objects.guid.NumberPoolHub
|
||||
import net.psforever.objects.guid.source.MaxNumberSource
|
||||
import net.psforever.objects.serverobject.environment._
|
||||
import net.psforever.objects.serverobject.mount.Mountable
|
||||
import net.psforever.objects.vehicles.{VehicleControl, VehicleLockState}
|
||||
import net.psforever.objects.vital.VehicleShieldCharge
|
||||
import net.psforever.objects.zones.{Zone, ZoneMap}
|
||||
import net.psforever.packet.game.{CargoMountPointStatusMessage, ObjectDetachMessage, PlanetsideAttributeMessage}
|
||||
import net.psforever.services.ServiceManager
|
||||
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
|
||||
import net.psforever.types._
|
||||
|
||||
import scala.concurrent.duration._
|
||||
|
||||
class VehicleControlPrepareForDeletionTest extends ActorTest {
|
||||
val vehicle = Vehicle(GlobalDefinitions.two_man_assault_buggy)
|
||||
vehicle.Faction = PlanetSideEmpire.TR
|
||||
vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test")
|
||||
val vehicleProbe = new TestProbe(system)
|
||||
vehicle.Zone = new Zone("test", new ZoneMap("test"), 0) {
|
||||
VehicleEvents = vehicleProbe.ref
|
||||
}
|
||||
|
||||
vehicle.GUID = PlanetSideGUID(1)
|
||||
expectNoMessage(200 milliseconds)
|
||||
|
||||
"VehicleControl" should {
|
||||
"submit for unregistering when marked for deconstruction" in {
|
||||
vehicle.Actor ! Vehicle.Deconstruct()
|
||||
vehicleProbe.expectNoMessage(5 seconds)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class VehicleControlPrepareForDeletionPassengerTest extends ActorTest {
|
||||
val vehicle = Vehicle(GlobalDefinitions.two_man_assault_buggy)
|
||||
vehicle.Faction = PlanetSideEmpire.TR
|
||||
vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test")
|
||||
val vehicleProbe = new TestProbe(system)
|
||||
vehicle.Zone = new Zone("test", new ZoneMap("test"), 0) {
|
||||
VehicleEvents = vehicleProbe.ref
|
||||
}
|
||||
val player1 = Player(VehicleTest.avatar1)
|
||||
|
||||
vehicle.GUID = PlanetSideGUID(1)
|
||||
player1.GUID = PlanetSideGUID(2)
|
||||
vehicle.Seats(1).Occupant = player1 //passenger seat
|
||||
player1.VehicleSeated = vehicle.GUID
|
||||
expectNoMessage(200 milliseconds)
|
||||
|
||||
"VehicleControl" should {
|
||||
"kick all players when marked for deconstruction" in {
|
||||
vehicle.Actor ! Vehicle.Deconstruct()
|
||||
|
||||
val vehicle_msg = vehicleProbe.receiveN(1, 500 milliseconds)
|
||||
assert(
|
||||
vehicle_msg.head match {
|
||||
case VehicleServiceMessage(
|
||||
"test",
|
||||
VehicleAction.KickPassenger(PlanetSideGUID(2), 4, false, PlanetSideGUID(1))
|
||||
) =>
|
||||
true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
assert(player1.VehicleSeated.isEmpty)
|
||||
assert(vehicle.Seats(1).Occupant.isEmpty)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class VehicleControlPrepareForDeletionMountedInTest extends FreedContextActorTest {
|
||||
ServiceManager.boot
|
||||
val guid = new NumberPoolHub(new MaxNumberSource(10))
|
||||
val zone = new Zone("test", new ZoneMap("test"), 0) {
|
||||
GUID(guid)
|
||||
|
||||
override def SetupNumberPools(): Unit = {}
|
||||
}
|
||||
zone.actor = system.spawn(ZoneActor(zone), "test-zone-actor")
|
||||
// crappy workaround but without it the zone doesn't get initialized in time
|
||||
expectNoMessage(400 milliseconds)
|
||||
|
||||
val vehicle = Vehicle(GlobalDefinitions.two_man_assault_buggy)
|
||||
vehicle.Faction = PlanetSideEmpire.TR
|
||||
vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test-cargo")
|
||||
vehicle.Zone = zone
|
||||
val lodestar = Vehicle(GlobalDefinitions.lodestar)
|
||||
lodestar.Faction = PlanetSideEmpire.TR
|
||||
val player1 = Player(VehicleTest.avatar1) //name="test1"
|
||||
val player2 = Player(VehicleTest.avatar2) //name="test2"
|
||||
|
||||
guid.register(vehicle, 1)
|
||||
guid.register(lodestar, 2)
|
||||
player1.GUID = PlanetSideGUID(3)
|
||||
var utilityId = 10
|
||||
lodestar.Utilities.values.foreach { util =>
|
||||
util().GUID = PlanetSideGUID(utilityId)
|
||||
utilityId += 1
|
||||
}
|
||||
vehicle.Seats(1).Occupant = player1 //passenger seat
|
||||
player1.VehicleSeated = vehicle.GUID
|
||||
lodestar.Seats(0).Occupant = player2
|
||||
player2.VehicleSeated = lodestar.GUID
|
||||
lodestar.CargoHolds(1).Occupant = vehicle
|
||||
vehicle.MountedIn = lodestar.GUID
|
||||
|
||||
val vehicleProbe = new TestProbe(system)
|
||||
zone.VehicleEvents = vehicleProbe.ref
|
||||
zone.Transport ! Zone.Vehicle.Spawn(lodestar) //can not fake this
|
||||
expectNoMessage(200 milliseconds)
|
||||
|
||||
"VehicleControl" should {
|
||||
"if mounted as cargo, self-eject when marked for deconstruction" in {
|
||||
vehicle.Actor ! Vehicle.Deconstruct()
|
||||
|
||||
val vehicle_msg = vehicleProbe.receiveN(6, 500 milliseconds)
|
||||
//dismounting as cargo messages
|
||||
assert(
|
||||
vehicle_msg.head match {
|
||||
case VehicleServiceMessage(
|
||||
_,
|
||||
VehicleAction.SendResponse(_, PlanetsideAttributeMessage(PlanetSideGUID(1), 0, _))
|
||||
) =>
|
||||
true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
assert(
|
||||
vehicle_msg(1) match {
|
||||
case VehicleServiceMessage(
|
||||
_,
|
||||
VehicleAction.SendResponse(_, PlanetsideAttributeMessage(PlanetSideGUID(1), 68, _))
|
||||
) =>
|
||||
true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
assert(
|
||||
vehicle_msg(2) match {
|
||||
case VehicleServiceMessage(
|
||||
"test",
|
||||
VehicleAction.SendResponse(
|
||||
_,
|
||||
CargoMountPointStatusMessage(PlanetSideGUID(2), _, PlanetSideGUID(1), _, 1, CargoStatus.InProgress, 0)
|
||||
)
|
||||
) =>
|
||||
true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
assert(
|
||||
vehicle_msg(3) match {
|
||||
case VehicleServiceMessage(
|
||||
"test",
|
||||
VehicleAction.SendResponse(_, ObjectDetachMessage(PlanetSideGUID(2), PlanetSideGUID(1), _, _, _, _))
|
||||
) =>
|
||||
true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
assert(
|
||||
vehicle_msg(4) match {
|
||||
case VehicleServiceMessage(
|
||||
"test",
|
||||
VehicleAction.SendResponse(
|
||||
_,
|
||||
CargoMountPointStatusMessage(PlanetSideGUID(2), _, _, PlanetSideGUID(1), 1, CargoStatus.Empty, 0)
|
||||
)
|
||||
) =>
|
||||
true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
//dismounting as cargo messages
|
||||
//TODO: does not actually kick out the cargo, but instigates the process
|
||||
assert(
|
||||
vehicle_msg(5) match {
|
||||
case VehicleServiceMessage(
|
||||
"test",
|
||||
VehicleAction.KickPassenger(PlanetSideGUID(3), 4, false, PlanetSideGUID(1))
|
||||
) =>
|
||||
true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
assert(player1.VehicleSeated.isEmpty)
|
||||
assert(vehicle.Seats(1).Occupant.isEmpty)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class VehicleControlPrepareForDeletionMountedCargoTest extends FreedContextActorTest {
|
||||
val guid = new NumberPoolHub(new MaxNumberSource(10))
|
||||
ServiceManager.boot
|
||||
val zone = new Zone("test", new ZoneMap("test"), 0) {
|
||||
GUID(guid)
|
||||
|
||||
override def SetupNumberPools(): Unit = {}
|
||||
}
|
||||
zone.actor = system.spawn(ZoneActor(zone), "test-zone-actor")
|
||||
// crappy workaround but without it the zone doesn't get initialized in time
|
||||
expectNoMessage(200 milliseconds)
|
||||
|
||||
val vehicle = Vehicle(GlobalDefinitions.two_man_assault_buggy)
|
||||
vehicle.Faction = PlanetSideEmpire.TR
|
||||
vehicle.Zone = zone
|
||||
val cargoProbe = new TestProbe(system)
|
||||
vehicle.Actor = cargoProbe.ref
|
||||
val lodestar = Vehicle(GlobalDefinitions.lodestar)
|
||||
lodestar.Faction = PlanetSideEmpire.TR
|
||||
val player1 = Player(VehicleTest.avatar1) //name="test1"
|
||||
val player2 = Player(VehicleTest.avatar2) //name="test2"
|
||||
|
||||
guid.register(vehicle, 1)
|
||||
guid.register(lodestar, 2)
|
||||
player1.GUID = PlanetSideGUID(3)
|
||||
player2.GUID = PlanetSideGUID(4)
|
||||
var utilityId = 10
|
||||
lodestar.Utilities.values.foreach { util =>
|
||||
util().GUID = PlanetSideGUID(utilityId)
|
||||
utilityId += 1
|
||||
}
|
||||
vehicle.Seats(1).Occupant = player1 //passenger seat
|
||||
player1.VehicleSeated = vehicle.GUID
|
||||
lodestar.Seats(0).Occupant = player2
|
||||
player2.VehicleSeated = lodestar.GUID
|
||||
lodestar.CargoHolds(1).Occupant = vehicle
|
||||
vehicle.MountedIn = lodestar.GUID
|
||||
|
||||
val vehicleProbe = new TestProbe(system)
|
||||
zone.VehicleEvents = vehicleProbe.ref
|
||||
zone.Transport ! Zone.Vehicle.Spawn(lodestar) //can not fake this
|
||||
expectNoMessage(200 milliseconds)
|
||||
|
||||
"VehicleControl" should {
|
||||
"if with mounted cargo, eject it when marked for deconstruction" in {
|
||||
lodestar.Actor ! Vehicle.Deconstruct()
|
||||
|
||||
val vehicle_msg = vehicleProbe.receiveN(6, 500 milliseconds)
|
||||
assert(
|
||||
vehicle_msg.head match {
|
||||
case VehicleServiceMessage(
|
||||
"test",
|
||||
VehicleAction.KickPassenger(PlanetSideGUID(4), 4, false, PlanetSideGUID(2))
|
||||
) =>
|
||||
true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
assert(player2.VehicleSeated.isEmpty)
|
||||
assert(lodestar.Seats(0).Occupant.isEmpty)
|
||||
//cargo dismounting messages
|
||||
assert(
|
||||
vehicle_msg(1) match {
|
||||
case VehicleServiceMessage(
|
||||
_,
|
||||
VehicleAction.SendResponse(_, PlanetsideAttributeMessage(PlanetSideGUID(1), 0, _))
|
||||
) =>
|
||||
true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
assert(
|
||||
vehicle_msg(2) match {
|
||||
case VehicleServiceMessage(
|
||||
_,
|
||||
VehicleAction.SendResponse(_, PlanetsideAttributeMessage(PlanetSideGUID(1), 68, _))
|
||||
) =>
|
||||
true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
assert(
|
||||
vehicle_msg(3) match {
|
||||
case VehicleServiceMessage(
|
||||
"test",
|
||||
VehicleAction.SendResponse(
|
||||
_,
|
||||
CargoMountPointStatusMessage(PlanetSideGUID(2), _, PlanetSideGUID(1), _, 1, CargoStatus.InProgress, 0)
|
||||
)
|
||||
) =>
|
||||
true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
assert(
|
||||
vehicle_msg(4) match {
|
||||
case VehicleServiceMessage(
|
||||
"test",
|
||||
VehicleAction.SendResponse(_, ObjectDetachMessage(PlanetSideGUID(2), PlanetSideGUID(1), _, _, _, _))
|
||||
) =>
|
||||
true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
assert(
|
||||
vehicle_msg(5) match {
|
||||
case VehicleServiceMessage(
|
||||
"test",
|
||||
VehicleAction.SendResponse(
|
||||
_,
|
||||
CargoMountPointStatusMessage(PlanetSideGUID(2), _, _, PlanetSideGUID(1), 1, CargoStatus.Empty, 0)
|
||||
)
|
||||
) =>
|
||||
true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class VehicleControlMountingBlockedExosuitTest extends ActorTest {
|
||||
val probe = new TestProbe(system)
|
||||
def checkCanNotMount(): Unit = {
|
||||
val reply = probe.receiveOne(Duration.create(100, "ms"))
|
||||
reply match {
|
||||
case msg: Mountable.MountMessages =>
|
||||
assert(msg.response.isInstanceOf[Mountable.CanNotMount])
|
||||
case _ =>
|
||||
assert(false)
|
||||
}
|
||||
}
|
||||
|
||||
def checkCanMount(): Unit = {
|
||||
val reply = probe.receiveOne(Duration.create(100, "ms"))
|
||||
reply match {
|
||||
case msg: Mountable.MountMessages =>
|
||||
assert(msg.response.isInstanceOf[Mountable.CanMount])
|
||||
case _ =>
|
||||
assert(false)
|
||||
}
|
||||
}
|
||||
val vehicle = Vehicle(GlobalDefinitions.apc_tr)
|
||||
vehicle.Faction = PlanetSideEmpire.TR
|
||||
vehicle.GUID = PlanetSideGUID(10)
|
||||
vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test")
|
||||
|
||||
val player1 = Player(VehicleTest.avatar1)
|
||||
player1.ExoSuit = ExoSuitType.Reinforced
|
||||
player1.GUID = PlanetSideGUID(1)
|
||||
val player2 = Player(VehicleTest.avatar1)
|
||||
player2.ExoSuit = ExoSuitType.MAX
|
||||
player2.GUID = PlanetSideGUID(2)
|
||||
val player3 = Player(VehicleTest.avatar1)
|
||||
player3.ExoSuit = ExoSuitType.Agile
|
||||
player3.GUID = PlanetSideGUID(3)
|
||||
|
||||
"Vehicle Control" should {
|
||||
"block players from sitting if their exo-suit is not allowed by the seat" in {
|
||||
//disallow
|
||||
vehicle.Actor.tell(Mountable.TryMount(player1, 0), probe.ref) //Reinforced in non-MAX seat
|
||||
checkCanNotMount()
|
||||
vehicle.Actor.tell(Mountable.TryMount(player2, 0), probe.ref) //MAX in non-Reinforced seat
|
||||
checkCanNotMount()
|
||||
vehicle.Actor.tell(Mountable.TryMount(player2, 1), probe.ref) //MAX in non-MAX seat
|
||||
checkCanNotMount()
|
||||
vehicle.Actor.tell(Mountable.TryMount(player1, 9), probe.ref) //Reinforced in MAX-only seat
|
||||
checkCanNotMount()
|
||||
vehicle.Actor.tell(Mountable.TryMount(player3, 9), probe.ref) //Agile in MAX-only seat
|
||||
checkCanNotMount()
|
||||
|
||||
//allow
|
||||
vehicle.Actor.tell(Mountable.TryMount(player1, 1), probe.ref)
|
||||
checkCanMount()
|
||||
vehicle.Actor.tell(Mountable.TryMount(player2, 9), probe.ref)
|
||||
checkCanMount()
|
||||
vehicle.Actor.tell(Mountable.TryMount(player3, 0), probe.ref)
|
||||
checkCanMount()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class VehicleControlMountingBlockedSeatPermissionTest extends ActorTest {
|
||||
val probe = new TestProbe(system)
|
||||
def checkCanNotMount(): Unit = {
|
||||
val reply = probe.receiveOne(Duration.create(100, "ms"))
|
||||
reply match {
|
||||
case msg: Mountable.MountMessages =>
|
||||
assert(msg.response.isInstanceOf[Mountable.CanNotMount])
|
||||
case _ =>
|
||||
assert(false)
|
||||
}
|
||||
}
|
||||
|
||||
def checkCanMount(): Unit = {
|
||||
val reply = probe.receiveOne(Duration.create(100, "ms"))
|
||||
reply match {
|
||||
case msg: Mountable.MountMessages =>
|
||||
assert(msg.response.isInstanceOf[Mountable.CanMount])
|
||||
case _ =>
|
||||
assert(false)
|
||||
}
|
||||
}
|
||||
val vehicle = Vehicle(GlobalDefinitions.apc_tr)
|
||||
vehicle.Faction = PlanetSideEmpire.TR
|
||||
vehicle.GUID = PlanetSideGUID(10)
|
||||
vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test")
|
||||
|
||||
val player1 = Player(VehicleTest.avatar1)
|
||||
player1.GUID = PlanetSideGUID(1)
|
||||
val player2 = Player(VehicleTest.avatar1)
|
||||
player2.GUID = PlanetSideGUID(2)
|
||||
|
||||
"Vehicle Control" should {
|
||||
//11 June 2018: Group is not supported yet so do not bother testing it
|
||||
"block players from sitting if the seat does not allow it" in {
|
||||
|
||||
vehicle.PermissionGroup(2, 3) //passenger group -> empire
|
||||
vehicle.Actor.tell(Mountable.TryMount(player1, 3), probe.ref) //passenger seat
|
||||
checkCanMount()
|
||||
vehicle.PermissionGroup(2, 0) //passenger group -> locked
|
||||
vehicle.Actor.tell(Mountable.TryMount(player2, 4), probe.ref) //passenger seat
|
||||
checkCanNotMount()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class VehicleControlMountingDriverSeatTest extends ActorTest {
|
||||
val probe = new TestProbe(system)
|
||||
def checkCanMount(): Unit = {
|
||||
val reply = probe.receiveOne(Duration.create(100, "ms"))
|
||||
reply match {
|
||||
case msg: Mountable.MountMessages =>
|
||||
assert(msg.response.isInstanceOf[Mountable.CanMount])
|
||||
case _ =>
|
||||
assert(false)
|
||||
}
|
||||
}
|
||||
val vehicle = Vehicle(GlobalDefinitions.apc_tr)
|
||||
vehicle.Faction = PlanetSideEmpire.TR
|
||||
vehicle.GUID = PlanetSideGUID(10)
|
||||
vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test")
|
||||
val player1 = Player(VehicleTest.avatar1)
|
||||
player1.GUID = PlanetSideGUID(1)
|
||||
|
||||
"Vehicle Control" should {
|
||||
"allow players to sit in the driver seat, even if it is locked, if the vehicle is unowned" in {
|
||||
assert(vehicle.PermissionGroup(0).contains(VehicleLockState.Locked)) //driver group -> locked
|
||||
assert(vehicle.Seats(0).Occupant.isEmpty)
|
||||
assert(vehicle.Owner.isEmpty)
|
||||
vehicle.Actor.tell(Mountable.TryMount(player1, 0), probe.ref)
|
||||
checkCanMount()
|
||||
assert(vehicle.Seats(0).Occupant.nonEmpty)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class VehicleControlMountingOwnedLockedDriverSeatTest extends ActorTest {
|
||||
val probe = new TestProbe(system)
|
||||
def checkCanNotMount(): Unit = {
|
||||
val reply = probe.receiveOne(Duration.create(100, "ms"))
|
||||
reply match {
|
||||
case msg: Mountable.MountMessages =>
|
||||
assert(msg.response.isInstanceOf[Mountable.CanNotMount])
|
||||
case _ =>
|
||||
assert(false)
|
||||
}
|
||||
}
|
||||
|
||||
def checkCanMount(): Unit = {
|
||||
val reply = probe.receiveOne(Duration.create(100, "ms"))
|
||||
reply match {
|
||||
case msg: Mountable.MountMessages =>
|
||||
assert(msg.response.isInstanceOf[Mountable.CanMount])
|
||||
case _ =>
|
||||
assert(false)
|
||||
}
|
||||
}
|
||||
val vehicle = Vehicle(GlobalDefinitions.apc_tr)
|
||||
vehicle.Faction = PlanetSideEmpire.TR
|
||||
vehicle.GUID = PlanetSideGUID(10)
|
||||
vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test")
|
||||
val player1 = Player(VehicleTest.avatar1)
|
||||
player1.GUID = PlanetSideGUID(1)
|
||||
val player2 = Player(VehicleTest.avatar1)
|
||||
player2.GUID = PlanetSideGUID(2)
|
||||
|
||||
"Vehicle Control" should {
|
||||
"block players that are not the current owner from sitting in the driver seat (locked)" in {
|
||||
assert(vehicle.PermissionGroup(0).contains(VehicleLockState.Locked)) //driver group -> locked
|
||||
assert(vehicle.Seats(0).Occupant.isEmpty)
|
||||
vehicle.Owner = player1.GUID
|
||||
|
||||
vehicle.Actor.tell(Mountable.TryMount(player1, 0), probe.ref)
|
||||
checkCanMount()
|
||||
assert(vehicle.Seats(0).Occupant.nonEmpty)
|
||||
vehicle.Actor.tell(Mountable.TryDismount(player1, 0), probe.ref)
|
||||
probe.receiveOne(Duration.create(100, "ms")) //discard
|
||||
assert(vehicle.Seats(0).Occupant.isEmpty)
|
||||
|
||||
vehicle.Actor.tell(Mountable.TryMount(player2, 0), probe.ref)
|
||||
checkCanNotMount()
|
||||
assert(vehicle.Seats(0).Occupant.isEmpty)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class VehicleControlMountingOwnedUnlockedDriverSeatTest extends ActorTest {
|
||||
val probe = new TestProbe(system)
|
||||
def checkCanMount(): Unit = {
|
||||
val reply = probe.receiveOne(Duration.create(100, "ms"))
|
||||
reply match {
|
||||
case msg: Mountable.MountMessages =>
|
||||
assert(msg.response.isInstanceOf[Mountable.CanMount])
|
||||
case _ =>
|
||||
assert(false)
|
||||
}
|
||||
}
|
||||
val vehicle = Vehicle(GlobalDefinitions.apc_tr)
|
||||
vehicle.Faction = PlanetSideEmpire.TR
|
||||
vehicle.GUID = PlanetSideGUID(10)
|
||||
vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test")
|
||||
val player1 = Player(VehicleTest.avatar1)
|
||||
player1.GUID = PlanetSideGUID(1)
|
||||
val player2 = Player(VehicleTest.avatar1)
|
||||
player2.GUID = PlanetSideGUID(2)
|
||||
|
||||
"Vehicle Control" should {
|
||||
"allow players that are not the current owner to sit in the driver seat (empire)" in {
|
||||
vehicle.PermissionGroup(0, 3) //passenger group -> empire
|
||||
assert(vehicle.PermissionGroup(0).contains(VehicleLockState.Empire)) //driver group -> empire
|
||||
assert(vehicle.Seats(0).Occupant.isEmpty)
|
||||
vehicle.Owner = player1.GUID //owner set
|
||||
|
||||
vehicle.Actor.tell(Mountable.TryMount(player1, 0), probe.ref)
|
||||
checkCanMount()
|
||||
assert(vehicle.Seats(0).Occupant.nonEmpty)
|
||||
vehicle.Actor.tell(Mountable.TryDismount(player1, 0), probe.ref)
|
||||
probe.receiveOne(Duration.create(100, "ms")) //discard
|
||||
assert(vehicle.Seats(0).Occupant.isEmpty)
|
||||
|
||||
vehicle.Actor.tell(Mountable.TryMount(player2, 0), probe.ref)
|
||||
checkCanMount()
|
||||
assert(vehicle.Seats(0).Occupant.nonEmpty)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class VehicleControlShieldsChargingTest extends ActorTest {
|
||||
val probe = new TestProbe(system)
|
||||
val vehicle = Vehicle(GlobalDefinitions.fury)
|
||||
vehicle.GUID = PlanetSideGUID(10)
|
||||
vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test")
|
||||
vehicle.Zone = new Zone("test", new ZoneMap("test"), 0) {
|
||||
VehicleEvents = probe.ref
|
||||
}
|
||||
|
||||
"charge vehicle shields" in {
|
||||
assert(vehicle.Shields == 0)
|
||||
assert(!vehicle.History.exists({ p => p.isInstanceOf[VehicleShieldCharge] }))
|
||||
|
||||
vehicle.Actor ! Vehicle.ChargeShields(15)
|
||||
val msg = probe.receiveOne(500 milliseconds)
|
||||
assert(msg match {
|
||||
case VehicleServiceMessage(_, VehicleAction.PlanetsideAttribute(_, PlanetSideGUID(10), 68, 15)) => true
|
||||
case _ => false
|
||||
})
|
||||
assert(vehicle.Shields == 15)
|
||||
assert(vehicle.History.exists({ p => p.isInstanceOf[VehicleShieldCharge] }))
|
||||
}
|
||||
}
|
||||
|
||||
class VehicleControlShieldsNotChargingVehicleDeadTest extends ActorTest {
|
||||
val probe = new TestProbe(system)
|
||||
val vehicle = Vehicle(GlobalDefinitions.fury)
|
||||
vehicle.GUID = PlanetSideGUID(10)
|
||||
vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test")
|
||||
vehicle.Zone = new Zone("test", new ZoneMap("test"), 0) {
|
||||
VehicleEvents = probe.ref
|
||||
}
|
||||
|
||||
"not charge vehicle shields if the vehicle is destroyed" in {
|
||||
assert(vehicle.Health > 0)
|
||||
vehicle.Health = 0
|
||||
assert(vehicle.Health == 0)
|
||||
assert(vehicle.Shields == 0)
|
||||
assert(!vehicle.History.exists({ p => p.isInstanceOf[VehicleShieldCharge] }))
|
||||
vehicle.Actor.tell(Vehicle.ChargeShields(15), probe.ref)
|
||||
|
||||
probe.expectNoMessage(1 seconds)
|
||||
assert(vehicle.Shields == 0)
|
||||
assert(!vehicle.History.exists({ p => p.isInstanceOf[VehicleShieldCharge] }))
|
||||
}
|
||||
}
|
||||
|
||||
class VehicleControlShieldsNotChargingVehicleShieldsFullTest extends ActorTest {
|
||||
val probe = new TestProbe(system)
|
||||
val vehicle = Vehicle(GlobalDefinitions.fury)
|
||||
vehicle.GUID = PlanetSideGUID(10)
|
||||
vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test")
|
||||
vehicle.Zone = new Zone("test", new ZoneMap("test"), 0) {
|
||||
VehicleEvents = probe.ref
|
||||
}
|
||||
|
||||
"not charge vehicle shields if the vehicle is destroyed" in {
|
||||
assert(vehicle.Shields == 0)
|
||||
vehicle.Shields = vehicle.MaxShields
|
||||
assert(vehicle.Shields == vehicle.MaxShields)
|
||||
assert(!vehicle.History.exists({ p => p.isInstanceOf[VehicleShieldCharge] }))
|
||||
vehicle.Actor ! Vehicle.ChargeShields(15)
|
||||
|
||||
probe.expectNoMessage(1 seconds)
|
||||
assert(!vehicle.History.exists({ p => p.isInstanceOf[VehicleShieldCharge] }))
|
||||
}
|
||||
}
|
||||
|
||||
class VehicleControlShieldsNotChargingTooEarlyTest extends ActorTest {
|
||||
val probe = new TestProbe(system)
|
||||
val vehicle = Vehicle(GlobalDefinitions.fury)
|
||||
vehicle.GUID = PlanetSideGUID(10)
|
||||
vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test")
|
||||
vehicle.Zone = new Zone("test", new ZoneMap("test"), 0) {
|
||||
VehicleEvents = probe.ref
|
||||
}
|
||||
|
||||
"charge vehicle shields" in {
|
||||
assert(vehicle.Shields == 0)
|
||||
|
||||
vehicle.Actor ! Vehicle.ChargeShields(15)
|
||||
val msg = probe.receiveOne(200 milliseconds)
|
||||
//assert(msg.isInstanceOf[Vehicle.UpdateShieldsCharge])
|
||||
assert(msg match {
|
||||
case VehicleServiceMessage(_, VehicleAction.PlanetsideAttribute(_, PlanetSideGUID(10), 68, 15)) => true
|
||||
case _ => false
|
||||
})
|
||||
assert(vehicle.Shields == 15)
|
||||
|
||||
vehicle.Actor ! Vehicle.ChargeShields(15)
|
||||
probe.expectNoMessage(200 milliseconds)
|
||||
assert(vehicle.Shields == 15)
|
||||
}
|
||||
}
|
||||
|
||||
//TODO implement message protocol for zone startup completion
|
||||
//class VehicleControlShieldsNotChargingDamagedTest extends ActorTest {
|
||||
// val probe = new TestProbe(system)
|
||||
// val vehicle = Vehicle(GlobalDefinitions.fury)
|
||||
// vehicle.GUID = PlanetSideGUID(10)
|
||||
// vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test")
|
||||
// vehicle.Zone = new Zone("test", new ZoneMap("test"), 0) {
|
||||
// VehicleEvents = probe.ref
|
||||
// }
|
||||
// //
|
||||
// val beamer_wep = Tool(GlobalDefinitions.beamer)
|
||||
// val p_source = PlayerSource( Player(Avatar(0, "TestTarget", PlanetSideEmpire.NC, CharacterGender.Female, 1, CharacterVoice.Mute)) )
|
||||
// val projectile = Projectile(beamer_wep.Projectile, GlobalDefinitions.beamer, beamer_wep.FireMode, p_source, GlobalDefinitions.beamer.ObjectId, Vector3.Zero, Vector3.Zero)
|
||||
// val fury_dm = Vehicle(GlobalDefinitions.fury).DamageModel
|
||||
// val obj = DamageInteraction(p_source, ProjectileReason(DamageResolution.Hit, projectile, fury_dm), Vector3(1.2f, 3.4f, 5.6f))
|
||||
//
|
||||
// "not charge vehicle shields if recently damaged" in {
|
||||
// assert(vehicle.Shields == 0)
|
||||
// vehicle.Actor.tell(Vitality.Damage({case v : Vehicle => v.History(obj); obj }), probe.ref)
|
||||
//
|
||||
// val msg = probe.receiveOne(200 milliseconds)
|
||||
// assert(msg.isInstanceOf[Vitality.DamageResolution])
|
||||
// assert(vehicle.Shields == 0)
|
||||
// vehicle.Actor.tell(Vehicle.ChargeShields(15), probe.ref)
|
||||
//
|
||||
// probe.expectNoMessage(200 milliseconds)
|
||||
// assert(vehicle.Shields == 0)
|
||||
// }
|
||||
//}
|
||||
|
||||
class VehicleControlInteractWithWaterPartialTest extends ActorTest {
|
||||
val vehicle = Vehicle(GlobalDefinitions.fury) //guid=2
|
||||
val player1 =
|
||||
Player(Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)) //guid=1
|
||||
val playerProbe = TestProbe()
|
||||
val guid = new NumberPoolHub(new MaxNumberSource(15))
|
||||
val pool = Pool(EnvironmentAttribute.Water, DeepSquare(-1, 10, 10, 0, 0))
|
||||
val zone = new Zone(
|
||||
id = "test-zone",
|
||||
new ZoneMap(name = "test-map") {
|
||||
environment = List(pool)
|
||||
},
|
||||
zoneNumber = 0
|
||||
) {
|
||||
override def SetupNumberPools() = {}
|
||||
GUID(guid)
|
||||
override def LivePlayers = List(player1)
|
||||
override def Vehicles = List(vehicle)
|
||||
}
|
||||
|
||||
guid.register(player1, 1)
|
||||
guid.register(vehicle, 2)
|
||||
player1.Zone = zone
|
||||
player1.Spawn()
|
||||
vehicle.Zone = zone
|
||||
vehicle.Faction = PlanetSideEmpire.TR
|
||||
vehicle.Seats(0).Occupant = player1
|
||||
player1.VehicleSeated = vehicle.GUID
|
||||
player1.Actor = playerProbe.ref
|
||||
vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-control")
|
||||
|
||||
"VehicleControl" should {
|
||||
"causes disability when the vehicle drives too deep in water (check driver messaging)" in {
|
||||
vehicle.Position = Vector3(5,5,-3) //right in the pool
|
||||
vehicle.zoneInteraction() //trigger
|
||||
|
||||
val msg_drown = playerProbe.receiveOne(250 milliseconds)
|
||||
assert(
|
||||
msg_drown match {
|
||||
case InteractWithEnvironment(
|
||||
p1,
|
||||
p2,
|
||||
Some(OxygenStateTarget(PlanetSideGUID(2), OxygenState.Suffocation, 100f))
|
||||
) => (p1 eq player1) && (p2 eq pool)
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class VehicleControlInteractWithWaterTest extends ActorTest {
|
||||
val vehicle = Vehicle(GlobalDefinitions.fury) //guid=2
|
||||
val player1 =
|
||||
Player(Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)) //guid=1
|
||||
val avatarProbe = TestProbe()
|
||||
val vehicleProbe = TestProbe()
|
||||
val guid = new NumberPoolHub(new MaxNumberSource(15))
|
||||
val pool = Pool(EnvironmentAttribute.Water, DeepSquare(-1, 10, 10, 0, 0))
|
||||
val zone = new Zone(
|
||||
id = "test-zone",
|
||||
new ZoneMap(name = "test-map") {
|
||||
environment = List(pool)
|
||||
},
|
||||
zoneNumber = 0
|
||||
) {
|
||||
override def SetupNumberPools() = {}
|
||||
GUID(guid)
|
||||
override def LivePlayers = List(player1)
|
||||
override def Vehicles = List(vehicle)
|
||||
override def AvatarEvents = avatarProbe.ref
|
||||
override def VehicleEvents = vehicleProbe.ref
|
||||
}
|
||||
|
||||
guid.register(player1, 1)
|
||||
guid.register(vehicle, 2)
|
||||
guid.register(player1.avatar.locker, 5)
|
||||
player1.Zone = zone
|
||||
player1.Spawn()
|
||||
vehicle.Zone = zone
|
||||
vehicle.Faction = PlanetSideEmpire.TR
|
||||
vehicle.Seats(0).Occupant = player1
|
||||
player1.VehicleSeated = vehicle.GUID
|
||||
val (probe, avatarActor) = PlayerControlTest.DummyAvatar(system)
|
||||
player1.Actor = system.actorOf(Props(classOf[PlayerControl], player1, avatarActor), "player1-control")
|
||||
vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-control")
|
||||
|
||||
"VehicleControl" should {
|
||||
"causes disability when the vehicle drives too deep in water" in {
|
||||
vehicle.Position = Vector3(5,5,-3) //right in the pool
|
||||
vehicle.zoneInteraction() //trigger
|
||||
|
||||
val msg_drown = avatarProbe.receiveOne(250 milliseconds)
|
||||
assert(
|
||||
msg_drown match {
|
||||
case AvatarServiceMessage(
|
||||
"TestCharacter1",
|
||||
AvatarAction.OxygenState(
|
||||
OxygenStateTarget(PlanetSideGUID(1), OxygenState.Suffocation, 100f),
|
||||
Some(OxygenStateTarget(PlanetSideGUID(2), OxygenState.Suffocation, 100f))
|
||||
)
|
||||
) => true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
//player will die in 60s
|
||||
//vehicle will disable in 5s; driver will be kicked
|
||||
val msg_kick = vehicleProbe.receiveOne(6 seconds)
|
||||
assert(
|
||||
msg_kick match {
|
||||
case VehicleServiceMessage(
|
||||
"test-zone",
|
||||
VehicleAction.KickPassenger(PlanetSideGUID(1), 4, _, PlanetSideGUID(2))
|
||||
) => true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
//player will die, but detailing players death messages is not necessary for this test
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class VehicleControlStopInteractWithWaterTest extends ActorTest {
|
||||
val vehicle = Vehicle(GlobalDefinitions.fury) //guid=2
|
||||
val player1 =
|
||||
Player(Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)) //guid=1
|
||||
val playerProbe = TestProbe()
|
||||
val guid = new NumberPoolHub(new MaxNumberSource(15))
|
||||
val pool = Pool(EnvironmentAttribute.Water, DeepSquare(-1, 10, 10, 0, 0))
|
||||
val zone = new Zone(
|
||||
id = "test-zone",
|
||||
new ZoneMap(name = "test-map") {
|
||||
environment = List(pool)
|
||||
},
|
||||
zoneNumber = 0
|
||||
) {
|
||||
override def SetupNumberPools() = {}
|
||||
GUID(guid)
|
||||
override def LivePlayers = List(player1)
|
||||
override def Vehicles = List(vehicle)
|
||||
}
|
||||
|
||||
guid.register(player1, 1)
|
||||
guid.register(vehicle, 2)
|
||||
player1.Zone = zone
|
||||
player1.Spawn()
|
||||
vehicle.Zone = zone
|
||||
vehicle.Faction = PlanetSideEmpire.TR
|
||||
vehicle.Seats(0).Occupant = player1
|
||||
player1.VehicleSeated = vehicle.GUID
|
||||
player1.Actor = playerProbe.ref
|
||||
vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-control")
|
||||
|
||||
"VehicleControl" should {
|
||||
"stop becoming disabled if the vehicle drives out of the water" in {
|
||||
vehicle.Position = Vector3(5,5,-3) //right in the pool
|
||||
vehicle.zoneInteraction() //trigger
|
||||
val msg_drown = playerProbe.receiveOne(250 milliseconds)
|
||||
assert(
|
||||
msg_drown match {
|
||||
case InteractWithEnvironment(
|
||||
p1,
|
||||
p2,
|
||||
Some(OxygenStateTarget(PlanetSideGUID(2), OxygenState.Suffocation, 100f))
|
||||
) => (p1 eq player1) && (p2 eq pool)
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
|
||||
vehicle.Position = Vector3.Zero //that's enough of that
|
||||
vehicle.zoneInteraction()
|
||||
val msg_recover = playerProbe.receiveOne(250 milliseconds)
|
||||
assert(
|
||||
msg_recover match {
|
||||
case EscapeFromEnvironment(
|
||||
p1,
|
||||
p2,
|
||||
Some(OxygenStateTarget(PlanetSideGUID(2), OxygenState.Recovery, _))
|
||||
) => (p1 eq player1) && (p2 eq pool)
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class VehicleControlInteractWithLavaTest extends ActorTest {
|
||||
val vehicle = Vehicle(GlobalDefinitions.fury) //guid=2
|
||||
val player1 =
|
||||
Player(Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)) //guid=1
|
||||
val avatarProbe = TestProbe()
|
||||
val vehicleProbe = TestProbe()
|
||||
val guid = new NumberPoolHub(new MaxNumberSource(15))
|
||||
val pool = Pool(EnvironmentAttribute.Lava, DeepSquare(-1, 10, 10, 0, 0))
|
||||
val zone = new Zone(
|
||||
id = "test-zone",
|
||||
new ZoneMap(name = "test-map") {
|
||||
environment = List(pool)
|
||||
},
|
||||
zoneNumber = 0
|
||||
) {
|
||||
override def SetupNumberPools() = {}
|
||||
GUID(guid)
|
||||
override def LivePlayers = List(player1)
|
||||
override def Vehicles = List(vehicle)
|
||||
override def AvatarEvents = avatarProbe.ref
|
||||
override def VehicleEvents = vehicleProbe.ref
|
||||
override def Activity = TestProbe().ref
|
||||
}
|
||||
|
||||
guid.register(player1, 1)
|
||||
guid.register(vehicle, 2)
|
||||
guid.register(player1.avatar.locker, 5)
|
||||
player1.Zone = zone
|
||||
player1.Spawn()
|
||||
vehicle.Zone = zone
|
||||
vehicle.Faction = PlanetSideEmpire.TR
|
||||
vehicle.Seats(0).Occupant = player1
|
||||
player1.VehicleSeated = vehicle.GUID
|
||||
val (probe, avatarActor) = PlayerControlTest.DummyAvatar(system)
|
||||
player1.Actor = system.actorOf(Props(classOf[PlayerControl], player1, avatarActor), "player1-control")
|
||||
vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-control")
|
||||
|
||||
"VehicleControl" should {
|
||||
"take continuous damage if vehicle drives into lava" in {
|
||||
assert(vehicle.Health > 0) //alive
|
||||
assert(player1.Health == 100) //alive
|
||||
vehicle.Position = Vector3(5,5,-3) //right in the pool
|
||||
vehicle.zoneInteraction() //trigger
|
||||
|
||||
val msg_burn = vehicleProbe.receiveN(3,1 seconds)
|
||||
msg_burn.foreach { msg =>
|
||||
assert(
|
||||
msg match {
|
||||
case VehicleServiceMessage("test-zone", VehicleAction.PlanetsideAttribute(_, PlanetSideGUID(2), 0, _)) => true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
}
|
||||
//etc..
|
||||
probe.receiveOne(65 seconds) //wait until player1's implants deinitialize
|
||||
assert(vehicle.Health == 0) //ded
|
||||
assert(player1.Health == 0) //ded
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class VehicleControlInteractWithDeathTest extends ActorTest {
|
||||
val vehicle = Vehicle(GlobalDefinitions.fury) //guid=2
|
||||
val player1 =
|
||||
Player(Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)) //guid=1
|
||||
val guid = new NumberPoolHub(new MaxNumberSource(15))
|
||||
val pool = Pool(EnvironmentAttribute.Death, DeepSquare(-1, 10, 10, 0, 0))
|
||||
val zone = new Zone(
|
||||
id = "test-zone",
|
||||
new ZoneMap(name = "test-map") {
|
||||
environment = List(pool)
|
||||
},
|
||||
zoneNumber = 0
|
||||
) {
|
||||
override def SetupNumberPools() = {}
|
||||
GUID(guid)
|
||||
override def LivePlayers = List(player1)
|
||||
override def Vehicles = List(vehicle)
|
||||
override def AvatarEvents = TestProbe().ref
|
||||
override def VehicleEvents = TestProbe().ref
|
||||
}
|
||||
|
||||
guid.register(player1, 1)
|
||||
guid.register(vehicle, 2)
|
||||
guid.register(player1.avatar.locker, 5)
|
||||
player1.Zone = zone
|
||||
player1.Spawn()
|
||||
vehicle.Zone = zone
|
||||
vehicle.Faction = PlanetSideEmpire.TR
|
||||
vehicle.Seats(0).Occupant = player1
|
||||
player1.VehicleSeated = vehicle.GUID
|
||||
val (probe, avatarActor) = PlayerControlTest.DummyAvatar(system)
|
||||
player1.Actor = system.actorOf(Props(classOf[PlayerControl], player1, avatarActor), "player1-control")
|
||||
vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-control")
|
||||
|
||||
"VehicleControl" should {
|
||||
"take continuous damage if vehicle drives into a pool of death" in {
|
||||
assert(vehicle.Health > 0) //alive
|
||||
assert(player1.Health == 100) //alive
|
||||
vehicle.Position = Vector3(5,5,-3) //right in the pool
|
||||
vehicle.zoneInteraction() //trigger
|
||||
|
||||
probe.receiveOne(2 seconds) //wait until player1's implants deinitialize
|
||||
assert(vehicle.Health == 0) //ded
|
||||
assert(player1.Health == 0) //ded
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object VehicleControlTest {
|
||||
import net.psforever.objects.avatar.Avatar
|
||||
import net.psforever.types.{CharacterGender, PlanetSideEmpire}
|
||||
|
||||
val avatar1 = Avatar(0, "test1", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)
|
||||
val avatar2 = Avatar(1, "test2", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)
|
||||
}
|
||||
|
|
@ -1,29 +1,13 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package objects
|
||||
|
||||
import akka.actor.Props
|
||||
import akka.testkit.TestProbe
|
||||
import base.{ActorTest, FreedContextActorTest}
|
||||
import net.psforever.objects._
|
||||
import net.psforever.objects.definition.{SeatDefinition, VehicleDefinition}
|
||||
import net.psforever.objects.guid.NumberPoolHub
|
||||
import net.psforever.objects.guid.source.MaxNumberSource
|
||||
import net.psforever.objects.serverobject.mount.Mountable
|
||||
import net.psforever.objects.vehicles._
|
||||
import net.psforever.objects.vital.VehicleShieldCharge
|
||||
import net.psforever.objects.zones.{Zone, ZoneMap}
|
||||
import net.psforever.packet.game.{CargoMountPointStatusMessage, ObjectDetachMessage, PlanetsideAttributeMessage}
|
||||
import net.psforever.types.{PlanetSideGUID, _}
|
||||
import org.specs2.mutable._
|
||||
import net.psforever.services.ServiceManager
|
||||
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
|
||||
|
||||
import scala.concurrent.duration._
|
||||
import akka.actor.typed.scaladsl.adapter._
|
||||
import net.psforever.actors.zone.ZoneActor
|
||||
|
||||
class VehicleTest extends Specification {
|
||||
|
||||
import VehicleTest._
|
||||
|
||||
"SeatDefinition" should {
|
||||
|
|
@ -327,659 +311,7 @@ class VehicleTest extends Specification {
|
|||
}
|
||||
}
|
||||
|
||||
class VehicleControlPrepareForDeletionTest extends ActorTest {
|
||||
val vehicle = Vehicle(GlobalDefinitions.two_man_assault_buggy)
|
||||
vehicle.Faction = PlanetSideEmpire.TR
|
||||
vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test")
|
||||
val vehicleProbe = new TestProbe(system)
|
||||
vehicle.Zone = new Zone("test", new ZoneMap("test"), 0) {
|
||||
VehicleEvents = vehicleProbe.ref
|
||||
}
|
||||
|
||||
vehicle.GUID = PlanetSideGUID(1)
|
||||
expectNoMessage(200 milliseconds)
|
||||
|
||||
"VehicleControl" should {
|
||||
"submit for unregistering when marked for deconstruction" in {
|
||||
vehicle.Actor ! Vehicle.Deconstruct()
|
||||
vehicleProbe.expectNoMessage(5 seconds)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class VehicleControlPrepareForDeletionPassengerTest extends ActorTest {
|
||||
val vehicle = Vehicle(GlobalDefinitions.two_man_assault_buggy)
|
||||
vehicle.Faction = PlanetSideEmpire.TR
|
||||
vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test")
|
||||
val vehicleProbe = new TestProbe(system)
|
||||
vehicle.Zone = new Zone("test", new ZoneMap("test"), 0) {
|
||||
VehicleEvents = vehicleProbe.ref
|
||||
}
|
||||
val player1 = Player(VehicleTest.avatar1)
|
||||
|
||||
vehicle.GUID = PlanetSideGUID(1)
|
||||
player1.GUID = PlanetSideGUID(2)
|
||||
vehicle.Seats(1).Occupant = player1 //passenger seat
|
||||
player1.VehicleSeated = vehicle.GUID
|
||||
expectNoMessage(200 milliseconds)
|
||||
|
||||
"VehicleControl" should {
|
||||
"kick all players when marked for deconstruction" in {
|
||||
vehicle.Actor ! Vehicle.Deconstruct()
|
||||
|
||||
val vehicle_msg = vehicleProbe.receiveN(1, 500 milliseconds)
|
||||
assert(
|
||||
vehicle_msg.head match {
|
||||
case VehicleServiceMessage(
|
||||
"test",
|
||||
VehicleAction.KickPassenger(PlanetSideGUID(2), 4, false, PlanetSideGUID(1))
|
||||
) =>
|
||||
true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
assert(player1.VehicleSeated.isEmpty)
|
||||
assert(vehicle.Seats(1).Occupant.isEmpty)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class VehicleControlPrepareForDeletionMountedInTest extends FreedContextActorTest {
|
||||
ServiceManager.boot
|
||||
val guid = new NumberPoolHub(new MaxNumberSource(10))
|
||||
val zone = new Zone("test", new ZoneMap("test"), 0) {
|
||||
GUID(guid)
|
||||
|
||||
override def SetupNumberPools(): Unit = {}
|
||||
}
|
||||
zone.actor = system.spawn(ZoneActor(zone), "test-zone-actor")
|
||||
// crappy workaround but without it the zone doesn't get initialized in time
|
||||
expectNoMessage(400 milliseconds)
|
||||
|
||||
val vehicle = Vehicle(GlobalDefinitions.two_man_assault_buggy)
|
||||
vehicle.Faction = PlanetSideEmpire.TR
|
||||
vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test-cargo")
|
||||
vehicle.Zone = zone
|
||||
val lodestar = Vehicle(GlobalDefinitions.lodestar)
|
||||
lodestar.Faction = PlanetSideEmpire.TR
|
||||
val player1 = Player(VehicleTest.avatar1) //name="test1"
|
||||
val player2 = Player(VehicleTest.avatar2) //name="test2"
|
||||
|
||||
guid.register(vehicle, 1)
|
||||
guid.register(lodestar, 2)
|
||||
player1.GUID = PlanetSideGUID(3)
|
||||
var utilityId = 10
|
||||
lodestar.Utilities.values.foreach { util =>
|
||||
util().GUID = PlanetSideGUID(utilityId)
|
||||
utilityId += 1
|
||||
}
|
||||
vehicle.Seats(1).Occupant = player1 //passenger seat
|
||||
player1.VehicleSeated = vehicle.GUID
|
||||
lodestar.Seats(0).Occupant = player2
|
||||
player2.VehicleSeated = lodestar.GUID
|
||||
lodestar.CargoHolds(1).Occupant = vehicle
|
||||
vehicle.MountedIn = lodestar.GUID
|
||||
|
||||
val vehicleProbe = new TestProbe(system)
|
||||
zone.VehicleEvents = vehicleProbe.ref
|
||||
zone.Transport ! Zone.Vehicle.Spawn(lodestar) //can not fake this
|
||||
expectNoMessage(200 milliseconds)
|
||||
|
||||
"VehicleControl" should {
|
||||
"if mounted as cargo, self-eject when marked for deconstruction" in {
|
||||
vehicle.Actor ! Vehicle.Deconstruct()
|
||||
|
||||
val vehicle_msg = vehicleProbe.receiveN(6, 500 milliseconds)
|
||||
//dismounting as cargo messages
|
||||
assert(
|
||||
vehicle_msg.head match {
|
||||
case VehicleServiceMessage(
|
||||
_,
|
||||
VehicleAction.SendResponse(_, PlanetsideAttributeMessage(PlanetSideGUID(1), 0, _))
|
||||
) =>
|
||||
true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
assert(
|
||||
vehicle_msg(1) match {
|
||||
case VehicleServiceMessage(
|
||||
_,
|
||||
VehicleAction.SendResponse(_, PlanetsideAttributeMessage(PlanetSideGUID(1), 68, _))
|
||||
) =>
|
||||
true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
assert(
|
||||
vehicle_msg(2) match {
|
||||
case VehicleServiceMessage(
|
||||
"test",
|
||||
VehicleAction.SendResponse(
|
||||
_,
|
||||
CargoMountPointStatusMessage(PlanetSideGUID(2), _, PlanetSideGUID(1), _, 1, CargoStatus.InProgress, 0)
|
||||
)
|
||||
) =>
|
||||
true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
assert(
|
||||
vehicle_msg(3) match {
|
||||
case VehicleServiceMessage(
|
||||
"test",
|
||||
VehicleAction.SendResponse(_, ObjectDetachMessage(PlanetSideGUID(2), PlanetSideGUID(1), _, _, _, _))
|
||||
) =>
|
||||
true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
assert(
|
||||
vehicle_msg(4) match {
|
||||
case VehicleServiceMessage(
|
||||
"test",
|
||||
VehicleAction.SendResponse(
|
||||
_,
|
||||
CargoMountPointStatusMessage(PlanetSideGUID(2), _, _, PlanetSideGUID(1), 1, CargoStatus.Empty, 0)
|
||||
)
|
||||
) =>
|
||||
true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
//dismounting as cargo messages
|
||||
//TODO: does not actually kick out the cargo, but instigates the process
|
||||
assert(
|
||||
vehicle_msg(5) match {
|
||||
case VehicleServiceMessage(
|
||||
"test",
|
||||
VehicleAction.KickPassenger(PlanetSideGUID(3), 4, false, PlanetSideGUID(1))
|
||||
) =>
|
||||
true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
assert(player1.VehicleSeated.isEmpty)
|
||||
assert(vehicle.Seats(1).Occupant.isEmpty)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class VehicleControlPrepareForDeletionMountedCargoTest extends FreedContextActorTest {
|
||||
val guid = new NumberPoolHub(new MaxNumberSource(10))
|
||||
ServiceManager.boot
|
||||
val zone = new Zone("test", new ZoneMap("test"), 0) {
|
||||
GUID(guid)
|
||||
|
||||
override def SetupNumberPools(): Unit = {}
|
||||
}
|
||||
zone.actor = system.spawn(ZoneActor(zone), "test-zone-actor")
|
||||
// crappy workaround but without it the zone doesn't get initialized in time
|
||||
expectNoMessage(200 milliseconds)
|
||||
|
||||
val vehicle = Vehicle(GlobalDefinitions.two_man_assault_buggy)
|
||||
vehicle.Faction = PlanetSideEmpire.TR
|
||||
vehicle.Zone = zone
|
||||
val cargoProbe = new TestProbe(system)
|
||||
vehicle.Actor = cargoProbe.ref
|
||||
val lodestar = Vehicle(GlobalDefinitions.lodestar)
|
||||
lodestar.Faction = PlanetSideEmpire.TR
|
||||
val player1 = Player(VehicleTest.avatar1) //name="test1"
|
||||
val player2 = Player(VehicleTest.avatar2) //name="test2"
|
||||
|
||||
guid.register(vehicle, 1)
|
||||
guid.register(lodestar, 2)
|
||||
player1.GUID = PlanetSideGUID(3)
|
||||
player2.GUID = PlanetSideGUID(4)
|
||||
var utilityId = 10
|
||||
lodestar.Utilities.values.foreach { util =>
|
||||
util().GUID = PlanetSideGUID(utilityId)
|
||||
utilityId += 1
|
||||
}
|
||||
vehicle.Seats(1).Occupant = player1 //passenger seat
|
||||
player1.VehicleSeated = vehicle.GUID
|
||||
lodestar.Seats(0).Occupant = player2
|
||||
player2.VehicleSeated = lodestar.GUID
|
||||
lodestar.CargoHolds(1).Occupant = vehicle
|
||||
vehicle.MountedIn = lodestar.GUID
|
||||
|
||||
val vehicleProbe = new TestProbe(system)
|
||||
zone.VehicleEvents = vehicleProbe.ref
|
||||
zone.Transport ! Zone.Vehicle.Spawn(lodestar) //can not fake this
|
||||
expectNoMessage(200 milliseconds)
|
||||
|
||||
"VehicleControl" should {
|
||||
"if with mounted cargo, eject it when marked for deconstruction" in {
|
||||
lodestar.Actor ! Vehicle.Deconstruct()
|
||||
|
||||
val vehicle_msg = vehicleProbe.receiveN(6, 500 milliseconds)
|
||||
assert(
|
||||
vehicle_msg.head match {
|
||||
case VehicleServiceMessage(
|
||||
"test",
|
||||
VehicleAction.KickPassenger(PlanetSideGUID(4), 4, false, PlanetSideGUID(2))
|
||||
) =>
|
||||
true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
assert(player2.VehicleSeated.isEmpty)
|
||||
assert(lodestar.Seats(0).Occupant.isEmpty)
|
||||
//cargo dismounting messages
|
||||
assert(
|
||||
vehicle_msg(1) match {
|
||||
case VehicleServiceMessage(
|
||||
_,
|
||||
VehicleAction.SendResponse(_, PlanetsideAttributeMessage(PlanetSideGUID(1), 0, _))
|
||||
) =>
|
||||
true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
assert(
|
||||
vehicle_msg(2) match {
|
||||
case VehicleServiceMessage(
|
||||
_,
|
||||
VehicleAction.SendResponse(_, PlanetsideAttributeMessage(PlanetSideGUID(1), 68, _))
|
||||
) =>
|
||||
true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
assert(
|
||||
vehicle_msg(3) match {
|
||||
case VehicleServiceMessage(
|
||||
"test",
|
||||
VehicleAction.SendResponse(
|
||||
_,
|
||||
CargoMountPointStatusMessage(PlanetSideGUID(2), _, PlanetSideGUID(1), _, 1, CargoStatus.InProgress, 0)
|
||||
)
|
||||
) =>
|
||||
true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
assert(
|
||||
vehicle_msg(4) match {
|
||||
case VehicleServiceMessage(
|
||||
"test",
|
||||
VehicleAction.SendResponse(_, ObjectDetachMessage(PlanetSideGUID(2), PlanetSideGUID(1), _, _, _, _))
|
||||
) =>
|
||||
true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
assert(
|
||||
vehicle_msg(5) match {
|
||||
case VehicleServiceMessage(
|
||||
"test",
|
||||
VehicleAction.SendResponse(
|
||||
_,
|
||||
CargoMountPointStatusMessage(PlanetSideGUID(2), _, _, PlanetSideGUID(1), 1, CargoStatus.Empty, 0)
|
||||
)
|
||||
) =>
|
||||
true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class VehicleControlMountingBlockedExosuitTest extends ActorTest {
|
||||
val probe = new TestProbe(system)
|
||||
def checkCanNotMount(): Unit = {
|
||||
val reply = probe.receiveOne(Duration.create(100, "ms"))
|
||||
reply match {
|
||||
case msg: Mountable.MountMessages =>
|
||||
assert(msg.response.isInstanceOf[Mountable.CanNotMount])
|
||||
case _ =>
|
||||
assert(false)
|
||||
}
|
||||
}
|
||||
|
||||
def checkCanMount(): Unit = {
|
||||
val reply = probe.receiveOne(Duration.create(100, "ms"))
|
||||
reply match {
|
||||
case msg: Mountable.MountMessages =>
|
||||
assert(msg.response.isInstanceOf[Mountable.CanMount])
|
||||
case _ =>
|
||||
assert(false)
|
||||
}
|
||||
}
|
||||
val vehicle = Vehicle(GlobalDefinitions.apc_tr)
|
||||
vehicle.Faction = PlanetSideEmpire.TR
|
||||
vehicle.GUID = PlanetSideGUID(10)
|
||||
vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test")
|
||||
|
||||
val player1 = Player(VehicleTest.avatar1)
|
||||
player1.ExoSuit = ExoSuitType.Reinforced
|
||||
player1.GUID = PlanetSideGUID(1)
|
||||
val player2 = Player(VehicleTest.avatar1)
|
||||
player2.ExoSuit = ExoSuitType.MAX
|
||||
player2.GUID = PlanetSideGUID(2)
|
||||
val player3 = Player(VehicleTest.avatar1)
|
||||
player3.ExoSuit = ExoSuitType.Agile
|
||||
player3.GUID = PlanetSideGUID(3)
|
||||
|
||||
"Vehicle Control" should {
|
||||
"block players from sitting if their exo-suit is not allowed by the seat" in {
|
||||
//disallow
|
||||
vehicle.Actor.tell(Mountable.TryMount(player1, 0), probe.ref) //Reinforced in non-MAX seat
|
||||
checkCanNotMount()
|
||||
vehicle.Actor.tell(Mountable.TryMount(player2, 0), probe.ref) //MAX in non-Reinforced seat
|
||||
checkCanNotMount()
|
||||
vehicle.Actor.tell(Mountable.TryMount(player2, 1), probe.ref) //MAX in non-MAX seat
|
||||
checkCanNotMount()
|
||||
vehicle.Actor.tell(Mountable.TryMount(player1, 9), probe.ref) //Reinforced in MAX-only seat
|
||||
checkCanNotMount()
|
||||
vehicle.Actor.tell(Mountable.TryMount(player3, 9), probe.ref) //Agile in MAX-only seat
|
||||
checkCanNotMount()
|
||||
|
||||
//allow
|
||||
vehicle.Actor.tell(Mountable.TryMount(player1, 1), probe.ref)
|
||||
checkCanMount()
|
||||
vehicle.Actor.tell(Mountable.TryMount(player2, 9), probe.ref)
|
||||
checkCanMount()
|
||||
vehicle.Actor.tell(Mountable.TryMount(player3, 0), probe.ref)
|
||||
checkCanMount()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class VehicleControlMountingBlockedSeatPermissionTest extends ActorTest {
|
||||
val probe = new TestProbe(system)
|
||||
def checkCanNotMount(): Unit = {
|
||||
val reply = probe.receiveOne(Duration.create(100, "ms"))
|
||||
reply match {
|
||||
case msg: Mountable.MountMessages =>
|
||||
assert(msg.response.isInstanceOf[Mountable.CanNotMount])
|
||||
case _ =>
|
||||
assert(false)
|
||||
}
|
||||
}
|
||||
|
||||
def checkCanMount(): Unit = {
|
||||
val reply = probe.receiveOne(Duration.create(100, "ms"))
|
||||
reply match {
|
||||
case msg: Mountable.MountMessages =>
|
||||
assert(msg.response.isInstanceOf[Mountable.CanMount])
|
||||
case _ =>
|
||||
assert(false)
|
||||
}
|
||||
}
|
||||
val vehicle = Vehicle(GlobalDefinitions.apc_tr)
|
||||
vehicle.Faction = PlanetSideEmpire.TR
|
||||
vehicle.GUID = PlanetSideGUID(10)
|
||||
vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test")
|
||||
|
||||
val player1 = Player(VehicleTest.avatar1)
|
||||
player1.GUID = PlanetSideGUID(1)
|
||||
val player2 = Player(VehicleTest.avatar1)
|
||||
player2.GUID = PlanetSideGUID(2)
|
||||
|
||||
"Vehicle Control" should {
|
||||
//11 June 2018: Group is not supported yet so do not bother testing it
|
||||
"block players from sitting if the seat does not allow it" in {
|
||||
|
||||
vehicle.PermissionGroup(2, 3) //passenger group -> empire
|
||||
vehicle.Actor.tell(Mountable.TryMount(player1, 3), probe.ref) //passenger seat
|
||||
checkCanMount()
|
||||
vehicle.PermissionGroup(2, 0) //passenger group -> locked
|
||||
vehicle.Actor.tell(Mountable.TryMount(player2, 4), probe.ref) //passenger seat
|
||||
checkCanNotMount()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class VehicleControlMountingDriverSeatTest extends ActorTest {
|
||||
val probe = new TestProbe(system)
|
||||
def checkCanMount(): Unit = {
|
||||
val reply = probe.receiveOne(Duration.create(100, "ms"))
|
||||
reply match {
|
||||
case msg: Mountable.MountMessages =>
|
||||
assert(msg.response.isInstanceOf[Mountable.CanMount])
|
||||
case _ =>
|
||||
assert(false)
|
||||
}
|
||||
}
|
||||
val vehicle = Vehicle(GlobalDefinitions.apc_tr)
|
||||
vehicle.Faction = PlanetSideEmpire.TR
|
||||
vehicle.GUID = PlanetSideGUID(10)
|
||||
vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test")
|
||||
val player1 = Player(VehicleTest.avatar1)
|
||||
player1.GUID = PlanetSideGUID(1)
|
||||
|
||||
"Vehicle Control" should {
|
||||
"allow players to sit in the driver seat, even if it is locked, if the vehicle is unowned" in {
|
||||
assert(vehicle.PermissionGroup(0).contains(VehicleLockState.Locked)) //driver group -> locked
|
||||
assert(vehicle.Seats(0).Occupant.isEmpty)
|
||||
assert(vehicle.Owner.isEmpty)
|
||||
vehicle.Actor.tell(Mountable.TryMount(player1, 0), probe.ref)
|
||||
checkCanMount()
|
||||
assert(vehicle.Seats(0).Occupant.nonEmpty)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class VehicleControlMountingOwnedLockedDriverSeatTest extends ActorTest {
|
||||
val probe = new TestProbe(system)
|
||||
def checkCanNotMount(): Unit = {
|
||||
val reply = probe.receiveOne(Duration.create(100, "ms"))
|
||||
reply match {
|
||||
case msg: Mountable.MountMessages =>
|
||||
assert(msg.response.isInstanceOf[Mountable.CanNotMount])
|
||||
case _ =>
|
||||
assert(false)
|
||||
}
|
||||
}
|
||||
|
||||
def checkCanMount(): Unit = {
|
||||
val reply = probe.receiveOne(Duration.create(100, "ms"))
|
||||
reply match {
|
||||
case msg: Mountable.MountMessages =>
|
||||
assert(msg.response.isInstanceOf[Mountable.CanMount])
|
||||
case _ =>
|
||||
assert(false)
|
||||
}
|
||||
}
|
||||
val vehicle = Vehicle(GlobalDefinitions.apc_tr)
|
||||
vehicle.Faction = PlanetSideEmpire.TR
|
||||
vehicle.GUID = PlanetSideGUID(10)
|
||||
vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test")
|
||||
val player1 = Player(VehicleTest.avatar1)
|
||||
player1.GUID = PlanetSideGUID(1)
|
||||
val player2 = Player(VehicleTest.avatar1)
|
||||
player2.GUID = PlanetSideGUID(2)
|
||||
|
||||
"Vehicle Control" should {
|
||||
"block players that are not the current owner from sitting in the driver seat (locked)" in {
|
||||
assert(vehicle.PermissionGroup(0).contains(VehicleLockState.Locked)) //driver group -> locked
|
||||
assert(vehicle.Seats(0).Occupant.isEmpty)
|
||||
vehicle.Owner = player1.GUID
|
||||
|
||||
vehicle.Actor.tell(Mountable.TryMount(player1, 0), probe.ref)
|
||||
checkCanMount()
|
||||
assert(vehicle.Seats(0).Occupant.nonEmpty)
|
||||
vehicle.Actor.tell(Mountable.TryDismount(player1, 0), probe.ref)
|
||||
probe.receiveOne(Duration.create(100, "ms")) //discard
|
||||
assert(vehicle.Seats(0).Occupant.isEmpty)
|
||||
|
||||
vehicle.Actor.tell(Mountable.TryMount(player2, 0), probe.ref)
|
||||
checkCanNotMount()
|
||||
assert(vehicle.Seats(0).Occupant.isEmpty)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class VehicleControlMountingOwnedUnlockedDriverSeatTest extends ActorTest {
|
||||
val probe = new TestProbe(system)
|
||||
def checkCanMount(): Unit = {
|
||||
val reply = probe.receiveOne(Duration.create(100, "ms"))
|
||||
reply match {
|
||||
case msg: Mountable.MountMessages =>
|
||||
assert(msg.response.isInstanceOf[Mountable.CanMount])
|
||||
case _ =>
|
||||
assert(false)
|
||||
}
|
||||
}
|
||||
val vehicle = Vehicle(GlobalDefinitions.apc_tr)
|
||||
vehicle.Faction = PlanetSideEmpire.TR
|
||||
vehicle.GUID = PlanetSideGUID(10)
|
||||
vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test")
|
||||
val player1 = Player(VehicleTest.avatar1)
|
||||
player1.GUID = PlanetSideGUID(1)
|
||||
val player2 = Player(VehicleTest.avatar1)
|
||||
player2.GUID = PlanetSideGUID(2)
|
||||
|
||||
"Vehicle Control" should {
|
||||
"allow players that are not the current owner to sit in the driver seat (empire)" in {
|
||||
vehicle.PermissionGroup(0, 3) //passenger group -> empire
|
||||
assert(vehicle.PermissionGroup(0).contains(VehicleLockState.Empire)) //driver group -> empire
|
||||
assert(vehicle.Seats(0).Occupant.isEmpty)
|
||||
vehicle.Owner = player1.GUID //owner set
|
||||
|
||||
vehicle.Actor.tell(Mountable.TryMount(player1, 0), probe.ref)
|
||||
checkCanMount()
|
||||
assert(vehicle.Seats(0).Occupant.nonEmpty)
|
||||
vehicle.Actor.tell(Mountable.TryDismount(player1, 0), probe.ref)
|
||||
probe.receiveOne(Duration.create(100, "ms")) //discard
|
||||
assert(vehicle.Seats(0).Occupant.isEmpty)
|
||||
|
||||
vehicle.Actor.tell(Mountable.TryMount(player2, 0), probe.ref)
|
||||
checkCanMount()
|
||||
assert(vehicle.Seats(0).Occupant.nonEmpty)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class VehicleControlShieldsChargingTest extends ActorTest {
|
||||
val probe = new TestProbe(system)
|
||||
val vehicle = Vehicle(GlobalDefinitions.fury)
|
||||
vehicle.GUID = PlanetSideGUID(10)
|
||||
vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test")
|
||||
vehicle.Zone = new Zone("test", new ZoneMap("test"), 0) {
|
||||
VehicleEvents = probe.ref
|
||||
}
|
||||
|
||||
"charge vehicle shields" in {
|
||||
assert(vehicle.Shields == 0)
|
||||
assert(!vehicle.History.exists({ p => p.isInstanceOf[VehicleShieldCharge] }))
|
||||
|
||||
vehicle.Actor ! Vehicle.ChargeShields(15)
|
||||
val msg = probe.receiveOne(500 milliseconds)
|
||||
assert(msg match {
|
||||
case VehicleServiceMessage(_, VehicleAction.PlanetsideAttribute(_, PlanetSideGUID(10), 68, 15)) => true
|
||||
case _ => false
|
||||
})
|
||||
assert(vehicle.Shields == 15)
|
||||
assert(vehicle.History.exists({ p => p.isInstanceOf[VehicleShieldCharge] }))
|
||||
}
|
||||
}
|
||||
|
||||
class VehicleControlShieldsNotChargingVehicleDeadTest extends ActorTest {
|
||||
val probe = new TestProbe(system)
|
||||
val vehicle = Vehicle(GlobalDefinitions.fury)
|
||||
vehicle.GUID = PlanetSideGUID(10)
|
||||
vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test")
|
||||
vehicle.Zone = new Zone("test", new ZoneMap("test"), 0) {
|
||||
VehicleEvents = probe.ref
|
||||
}
|
||||
|
||||
"not charge vehicle shields if the vehicle is destroyed" in {
|
||||
assert(vehicle.Health > 0)
|
||||
vehicle.Health = 0
|
||||
assert(vehicle.Health == 0)
|
||||
assert(vehicle.Shields == 0)
|
||||
assert(!vehicle.History.exists({ p => p.isInstanceOf[VehicleShieldCharge] }))
|
||||
vehicle.Actor.tell(Vehicle.ChargeShields(15), probe.ref)
|
||||
|
||||
probe.expectNoMessage(1 seconds)
|
||||
assert(vehicle.Shields == 0)
|
||||
assert(!vehicle.History.exists({ p => p.isInstanceOf[VehicleShieldCharge] }))
|
||||
}
|
||||
}
|
||||
|
||||
class VehicleControlShieldsNotChargingVehicleShieldsFullTest extends ActorTest {
|
||||
val probe = new TestProbe(system)
|
||||
val vehicle = Vehicle(GlobalDefinitions.fury)
|
||||
vehicle.GUID = PlanetSideGUID(10)
|
||||
vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test")
|
||||
vehicle.Zone = new Zone("test", new ZoneMap("test"), 0) {
|
||||
VehicleEvents = probe.ref
|
||||
}
|
||||
|
||||
"not charge vehicle shields if the vehicle is destroyed" in {
|
||||
assert(vehicle.Shields == 0)
|
||||
vehicle.Shields = vehicle.MaxShields
|
||||
assert(vehicle.Shields == vehicle.MaxShields)
|
||||
assert(!vehicle.History.exists({ p => p.isInstanceOf[VehicleShieldCharge] }))
|
||||
vehicle.Actor ! Vehicle.ChargeShields(15)
|
||||
|
||||
probe.expectNoMessage(1 seconds)
|
||||
assert(!vehicle.History.exists({ p => p.isInstanceOf[VehicleShieldCharge] }))
|
||||
}
|
||||
}
|
||||
|
||||
class VehicleControlShieldsNotChargingTooEarlyTest extends ActorTest {
|
||||
val probe = new TestProbe(system)
|
||||
val vehicle = Vehicle(GlobalDefinitions.fury)
|
||||
vehicle.GUID = PlanetSideGUID(10)
|
||||
vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test")
|
||||
vehicle.Zone = new Zone("test", new ZoneMap("test"), 0) {
|
||||
VehicleEvents = probe.ref
|
||||
}
|
||||
|
||||
"charge vehicle shields" in {
|
||||
assert(vehicle.Shields == 0)
|
||||
|
||||
vehicle.Actor ! Vehicle.ChargeShields(15)
|
||||
val msg = probe.receiveOne(200 milliseconds)
|
||||
//assert(msg.isInstanceOf[Vehicle.UpdateShieldsCharge])
|
||||
assert(msg match {
|
||||
case VehicleServiceMessage(_, VehicleAction.PlanetsideAttribute(_, PlanetSideGUID(10), 68, 15)) => true
|
||||
case _ => false
|
||||
})
|
||||
assert(vehicle.Shields == 15)
|
||||
|
||||
vehicle.Actor ! Vehicle.ChargeShields(15)
|
||||
probe.expectNoMessage(200 milliseconds)
|
||||
assert(vehicle.Shields == 15)
|
||||
}
|
||||
}
|
||||
|
||||
//TODO implement message protocol for zone startup completion
|
||||
//class VehicleControlShieldsNotChargingDamagedTest extends ActorTest {
|
||||
// val probe = new TestProbe(system)
|
||||
// val vehicle = Vehicle(GlobalDefinitions.fury)
|
||||
// vehicle.GUID = PlanetSideGUID(10)
|
||||
// vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test")
|
||||
// vehicle.Zone = new Zone("test", new ZoneMap("test"), 0) {
|
||||
// VehicleEvents = probe.ref
|
||||
// }
|
||||
// //
|
||||
// val beamer_wep = Tool(GlobalDefinitions.beamer)
|
||||
// val p_source = PlayerSource( Player(Avatar(0, "TestTarget", PlanetSideEmpire.NC, CharacterGender.Female, 1, CharacterVoice.Mute)) )
|
||||
// val projectile = Projectile(beamer_wep.Projectile, GlobalDefinitions.beamer, beamer_wep.FireMode, p_source, GlobalDefinitions.beamer.ObjectId, Vector3.Zero, Vector3.Zero)
|
||||
// val fury_dm = Vehicle(GlobalDefinitions.fury).DamageModel
|
||||
// val obj = DamageInteraction(p_source, ProjectileReason(DamageResolution.Hit, projectile, fury_dm), Vector3(1.2f, 3.4f, 5.6f))
|
||||
//
|
||||
// "not charge vehicle shields if recently damaged" in {
|
||||
// assert(vehicle.Shields == 0)
|
||||
// vehicle.Actor.tell(Vitality.Damage({case v : Vehicle => v.History(obj); obj }), probe.ref)
|
||||
//
|
||||
// val msg = probe.receiveOne(200 milliseconds)
|
||||
// assert(msg.isInstanceOf[Vitality.DamageResolution])
|
||||
// assert(vehicle.Shields == 0)
|
||||
// vehicle.Actor.tell(Vehicle.ChargeShields(15), probe.ref)
|
||||
//
|
||||
// probe.expectNoMessage(200 milliseconds)
|
||||
// assert(vehicle.Shields == 0)
|
||||
// }
|
||||
//}
|
||||
|
||||
object VehicleTest {
|
||||
|
||||
import net.psforever.objects.avatar.Avatar
|
||||
import net.psforever.types.{CharacterGender, PlanetSideEmpire}
|
||||
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ class VitalityTest extends Specification {
|
|||
player.History(HealFromExoSuitChange(pSource, ExoSuitType.Standard))
|
||||
player.History(RepairFromTerm(vSource, 10, GlobalDefinitions.order_terminal))
|
||||
player.History(VehicleShieldCharge(vSource, 10))
|
||||
player.History(PlayerSuicide(pSource))
|
||||
player.History(PlayerSuicide())
|
||||
ok
|
||||
}
|
||||
|
||||
|
|
@ -56,7 +56,7 @@ class VitalityTest extends Specification {
|
|||
player.History(HealFromExoSuitChange(pSource, ExoSuitType.Standard))
|
||||
player.History(RepairFromTerm(vSource, 10, GlobalDefinitions.order_terminal))
|
||||
player.History(VehicleShieldCharge(vSource, 10))
|
||||
player.History(PlayerSuicide(pSource))
|
||||
player.History(PlayerSuicide())
|
||||
player.History.size mustEqual 7
|
||||
|
||||
val list = player.ClearHistory()
|
||||
|
|
@ -92,7 +92,7 @@ class VitalityTest extends Specification {
|
|||
player.History(HealFromExoSuitChange(pSource, ExoSuitType.Standard))
|
||||
player.History(RepairFromTerm(vSource, 10, GlobalDefinitions.order_terminal))
|
||||
player.History(VehicleShieldCharge(vSource, 10))
|
||||
player.History(PlayerSuicide(pSource))
|
||||
player.History(PlayerSuicide())
|
||||
|
||||
player.LastShot match {
|
||||
case Some(resolved_projectile) =>
|
||||
|
|
|
|||
Loading…
Reference in a new issue